Skip to content

Commit

Permalink
fixes serialize problems with define and attributes working at the sa…
Browse files Browse the repository at this point in the history
…me time
  • Loading branch information
justinbmeyer committed May 1, 2014
1 parent 7dff78a commit 995aea4
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 93 deletions.
76 changes: 31 additions & 45 deletions map/attributes/attributes.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
steal('can/util', 'can/map', 'can/list', function (can, Map) {

//!steal-remove-start
can.dev.warn("can/map/attributes is a deprecated plugin and will be removed in a future release. "+
"can/map/define provides the same functionality in a more complete API.");
//!steal-remove-end

can.each([
can.Map,
can.Model
], function (clss) {

//!steal-remove-start
can.dev.warn("can/map/attributes is a deprecated plugin and will be removed in a future release. "+
"can/map/define provides the same functionality in a more complete API.");
//!steal-remove-end

// in some cases model might not be defined quite yet.
if (clss === undefined) {
return;
Expand Down Expand Up @@ -123,49 +124,34 @@ steal('can/util', 'can/map', 'can/list', function (can, Map) {
}
return value === null || !type ? value : converter.call(Class, value, oldVal, function () {}, type);
};
can.List.prototype.serialize = function (attrName, stack) {
return can.makeArray(can.Map.prototype.serialize.apply(this, arguments));

var oldSerialize = can.Map.helpers._serialize;
can.Map.helpers._serialize = function(map, name, val){

var constructor = map.constructor,
type = constructor.attributes ? constructor.attributes[name] : 0,
converter = constructor.serialize ? constructor.serialize[type] : 0;

return val && typeof val.serialize === 'function' ?
// call attrs or serialize to get the original data back
oldSerialize.apply(this, arguments) :
// otherwise if we have a converter
converter ?
// use the converter
converter(val, type) :
// or return the val
oldSerialize.apply(this, arguments);
};
can.Map.prototype.serialize = function (attrName, stack) {
var where = {}, Class = this.constructor,
attrs = {};
stack = can.isArray(stack) ? stack : [];
stack.push(this._cid);
if (attrName !== undefined) {
attrs[attrName] = this[attrName];
// add support for single value serialize
var mapSerialize = can.Map.prototype.serialize;
can.Map.prototype.serialize = function (attrName) {
var baseResult = mapSerialize.apply(this, arguments);
if(attrName){
return baseResult[attrName];
} else {
attrs = this.__get();
}
can.each(attrs, function (val, name) {
can.__reading(val, name);
var type, converter;
// If this is an observe, check that it wasn't serialized earlier in the stack.
if (val instanceof can.Map && can.inArray(val._cid, stack) > -1) {
// Since this object has already been serialized once,
// just reference the id (or undefined if it doesn't exist).
where[name] = val.attr('id');
} else {
type = Class.attributes ? Class.attributes[name] : 0;
converter = Class.serialize ? Class.serialize[type] : 0;
// if the value is an object, and has a attrs or serialize function
where[name] = val && typeof val.serialize === 'function' ?
// call attrs or serialize to get the original data back
val.serialize(undefined, stack) :
// otherwise if we have a converter
converter ?
// use the converter
converter(val, type) :
// or return the val
val;
}
});

can.__reading(this, '__keys');

if (typeof attrs.length !== 'undefined') {
where.length = attrs.length;
return baseResult;
}
return attrName !== undefined ? where[attrName] : where;
};

return can.Map;
});
5 changes: 5 additions & 0 deletions map/backup/backup_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ steal("can/map/backup", "can/model", "can/test", function () {
recipe.restore(true);
ok(!recipe.isDirty(true), 'cleaned all of recipe and its associations');
});

test('backup restore nested observables', function () {
var observe = new can.Map({
nested: {
Expand All @@ -72,15 +73,19 @@ steal("can/map/backup", "can/model", "can/test", function () {
equal(observe.attr('nested')
.attr('test'), 'property', 'Nested object got converted');
observe.backup();

observe.attr('nested')
.attr('test', 'changed property');

equal(observe.attr('nested')
.attr('test'), 'changed property', 'Nested property changed');

ok(observe.isDirty(true), 'Observe is dirty');
observe.restore(true);
equal(observe.attr('nested')
.attr('test'), 'property', 'Nested object got restored');
});

test('backup removes properties that were added (#607)', function () {
var map = new can.Map({});
map.backup();
Expand Down
66 changes: 37 additions & 29 deletions map/define/define.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ steal('can/util', 'can/observe', function (can) {
}
},
'number': function (val) {
return parseFloat(val);
return +(val);
},
'boolean': function (val) {
if (val === 'false' || val === '0' || !val) {
Expand Down Expand Up @@ -206,38 +206,46 @@ steal('can/util', 'can/observe', function (can) {
}
}
};

proto.serialize = function () {
var serialized = {},
serializer, val,
serializedVal;
// Overwrite the invidual property serializer b/c we will overwrite it.
var oldSingleSerialize = can.Map.helpers._serialize;
can.Map.helpers._serialize = function(map, name, val){
return serializeProp(map, name, val);
};
// If the map has a define serializer for the given attr, run it.
var serializeProp = function(map, attr, val) {
var serializer = map.define && map.define[attr] && map.define[attr].serialize;
if(serializer === undefined) {
return oldSingleSerialize.apply(this, arguments);
} else if(serializer !== false){
return typeof serializer === "function" ? serializer.call(this, val, attr): oldSingleSerialize.apply(this, arguments);
}
};

// Overwrite serialize to add in any missing define serialized properties.
var oldSerialize = proto.serialize;
proto.serialize = function (property) {
var serialized = oldSerialize.apply(this, arguments);
if(property){
return serialized;
}
// add in properties not already serialized

var serializer,
val;
// Go through each property.
for(var attr in this.define){
val = this.attr(attr);
serializer = this.define && this.define[attr] && this.define[attr].serialize;
// skip anything that has serialize: false
if(serializer === false){
continue;
}
// If the value is an `object`, and has an `attrs` or `serialize` function.
serializedVal = serializer? serializer.call(this, val): (can.Map.helpers.isObservable(val) && can.isFunction(val.serialize) ?
// Call `attrs` or `serialize` to get the original data back.
val.serialize() :
// Otherwise return the value.
val);

// if the serializer method returns false, don't include this property
if(serializedVal === false){
continue;
// if it's not already defined
if(!(attr in serialized)) {
// check there is a serializer so we aren't doing extra work on serializer:false
serializer = this.define && this.define[attr] && this.define[attr].serialize;
if(serializer) {
val = serializeProp(this, attr, this.attr(attr));
if(val !== undefined) {
serialized[attr] = val;
}
}
}

serialized[attr] = serializedVal;

can.__reading(this, attr);
}

can.__reading(this, '__keys');

return serialized;
};

Expand Down
2 changes: 1 addition & 1 deletion map/define/define_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ steal("can/map/define", "can/test", function () {
define: {
name: {
serialize: function(){
return false;
return;
}
},
locations: {
Expand Down
51 changes: 43 additions & 8 deletions map/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ steal('can/util', 'can/util/bind','./bubble.js', 'can/construct', 'can/util/batc
var getMapFromObject = function (obj) {
return madeMap && madeMap[obj._cid] && madeMap[obj._cid].instance;
};
// A temporary map of Maps
var serializeMap = null;


/**
* @add can.Map
Expand Down Expand Up @@ -167,21 +170,53 @@ steal('can/util', 'can/util/bind','./bubble.js', 'can/construct', 'can/util/batc
// ### can.Map.helpers.serialize
// Serializes a Map or Map.List
serialize: function (map, how, where) {
var cid = can.cid(map),
firstSerialize = false;
if(!serializeMap) {
firstSerialize = true;
// Serialize might call .attr() so we need to keep different map
serializeMap = {
attr: {},
serialize: {}
};
}
serializeMap[how][cid] = where;
// Go through each property.
map.each(function (val, name) {
// If the value is an `object`, and has an `attrs` or `serialize` function.
where[name] = Map.helpers.isObservable(val) && can.isFunction(val[how]) ?
// Call `attrs` or `serialize` to get the original data back.
val[how]() :
// Otherwise return the value.
val;

can.__reading(map, name);
var result,
isObservable = Map.helpers.isObservable(val),
serialized = isObservable && serializeMap[how][can.cid(val)];
if( serialized ) {
result = serialized;
} else {
if(how === "serialize") {
result = Map.helpers._serialize(map, name, val);
} else {
result = Map.helpers._getValue(map, name, val, how);
}
}
// this is probably removable
if(result !== undefined){
where[name] = result;
}
});

can.__reading(map, '__keys');

if(firstSerialize) {
serializeMap = null;
}
return where;
},
_serialize: function(map, name, val){
return Map.helpers._getValue(map, name, val, "serialize");
},
_getValue: function(map, name, val, how){
if( Map.helpers.isObservable(val) ) {
return val[how]();
} else {
return val;
}
}
},
/**
Expand Down
31 changes: 21 additions & 10 deletions map/map_test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* jshint asi:true*/
steal("can/map", "can/compute", "can/test", "can/list", function (undefined) {
steal("can/map", "can/compute", "can/test", "can/list", function(){

module('can/map')
module('can/map');

test("Basic Map", 4, function () {

Expand All @@ -11,10 +11,10 @@ steal("can/map", "can/compute", "can/test", "can/list", function (undefined) {
});

state.bind("change", function (ev, attr, how, val, old) {
equal(attr, "category", "correct change name")
equal(how, "set")
equal(val, 6, "correct")
equal(old, 5, "correct")
equal(attr, "category", "correct change name");
equal(how, "set");
equal(val, 6, "correct");
equal(old, 5, "correct");
});

state.attr("category", 6);
Expand Down Expand Up @@ -63,16 +63,16 @@ steal("can/map", "can/compute", "can/test", "can/list", function (undefined) {
var state2 = new can.Map({
"key.with.dots": 4,
key: {
with: {
"with": {
someValue: 20
}
}
})
});
state.removeAttr("key.with.dots");
state2.removeAttr("key.with.someValue");
deepEqual(can.Map.keys(state), ["productType"], "one property");
deepEqual(can.Map.keys(state2), ["key.with.dots", "key"], "two properties");
deepEqual(can.Map.keys(state2.key.with), [], "zero properties");
deepEqual( can.Map.keys( state2.key["with"] ) , [], "zero properties");
});

test("nested event handlers are not run by changing the parent property (#280)", function () {
Expand Down Expand Up @@ -227,5 +227,16 @@ steal("can/map", "can/compute", "can/test", "can/list", function (undefined) {
ok(!map._computedBindings.name.handler, 'computed property handler removed');
});


test("serializing cycles", function(){
var map1 = new can.Map({name: "map1"});
var map2 = new can.Map({name: "map2"});

map1.attr("map2", map2);
map2.attr("map1", map1);

var res = map1.serialize();
equal(res.name, "map1");
equal(res.map2.name, "map2");
});

});

0 comments on commit 995aea4

Please sign in to comment.