Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite the clone and merge helpers #4422

Merged
merged 1 commit into from
Jul 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 42 additions & 69 deletions src/core/core.helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,7 @@ module.exports = function(Chart) {
var helpers = Chart.helpers;

// -- Basic js utility methods
helpers.clone = function(obj) {
var objClone = {};
helpers.each(obj, function(value, key) {
if (helpers.isArray(value)) {
objClone[key] = value.slice(0);
} else if (typeof value === 'object' && value !== null) {
objClone[key] = helpers.clone(value);
} else {
objClone[key] = value;
}
});
return objClone;
};

helpers.extend = function(base) {
var setFn = function(value, key) {
base[key] = value;
Expand All @@ -30,75 +18,60 @@ module.exports = function(Chart) {
}
return base;
};
// Need a special merge function to chart configs since they are now grouped
helpers.configMerge = function(_base) {
var base = helpers.clone(_base);
helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) {
helpers.each(extension, function(value, key) {
var baseHasProperty = base.hasOwnProperty(key);
var baseVal = baseHasProperty ? base[key] : {};

helpers.configMerge = function(/* objects ... */) {
return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
merger: function(key, target, source, options) {
var tval = target[key] || {};
var sval = source[key];

if (key === 'scales') {
// Scale config merging is complex. Add our own function here for that
base[key] = helpers.scaleMerge(baseVal, value);
// scale config merging is complex. Add our own function here for that
target[key] = helpers.scaleMerge(tval, sval);
} else if (key === 'scale') {
// Used in polar area & radar charts since there is only one scale
base[key] = helpers.configMerge(baseVal, Chart.scaleService.getScaleDefaults(value.type), value);
} else if (baseHasProperty
&& typeof baseVal === 'object'
&& !helpers.isArray(baseVal)
&& baseVal !== null
&& typeof value === 'object'
&& !helpers.isArray(value)) {
// If we are overwriting an object with an object, do a merge of the properties.
base[key] = helpers.configMerge(baseVal, value);
// used in polar area & radar charts since there is only one scale
target[key] = helpers.merge(tval, [Chart.scaleService.getScaleDefaults(sval.type), sval]);
} else {
// can just overwrite the value in this case
base[key] = value;
helpers._merger(key, target, source, options);
}
});
}
});

return base;
};
helpers.scaleMerge = function(_base, extension) {
var base = helpers.clone(_base);

helpers.each(extension, function(value, key) {
if (key === 'xAxes' || key === 'yAxes') {
// These properties are arrays of items
if (base.hasOwnProperty(key)) {
helpers.each(value, function(valueObj, index) {
var axisType = helpers.valueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');
var axisDefaults = Chart.scaleService.getScaleDefaults(axisType);
if (index >= base[key].length || !base[key][index].type) {
base[key].push(helpers.configMerge(axisDefaults, valueObj));
} else if (valueObj.type && valueObj.type !== base[key][index].type) {
// Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults
base[key][index] = helpers.configMerge(base[key][index], axisDefaults, valueObj);

helpers.scaleMerge = function(/* objects ... */) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't wait until v3 when this goes away since everything has a key

return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
merger: function(key, target, source, options) {
if (key === 'xAxes' || key === 'yAxes') {
var slen = source[key].length;
var i, type, scale, defaults;

if (!target[key]) {
target[key] = [];
}

for (i = 0; i < slen; ++i) {
scale = source[key][i];
type = helpers.valueOrDefault(scale.type, key === 'xAxes'? 'category' : 'linear');
defaults = Chart.scaleService.getScaleDefaults(type);

if (i >= target[key].length) {
target[key].push({});
}

if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) {
// new/untyped scale or type changed: let's apply the new defaults
// then merge source scale to correctly overwrite the defaults.
helpers.merge(target[key][i], [defaults, scale]);
} else {
// Type is the same
base[key][index] = helpers.configMerge(base[key][index], valueObj);
// scales type are the same
helpers.merge(target[key][i], scale);
}
});
}
} else {
base[key] = [];
helpers.each(value, function(valueObj) {
var axisType = helpers.valueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');
base[key].push(helpers.configMerge(Chart.scaleService.getScaleDefaults(axisType), valueObj));
});
helpers._merger(key, target, source, options);
}
} else if (base.hasOwnProperty(key) && typeof base[key] === 'object' && base[key] !== null && typeof value === 'object') {
// If we are overwriting an object with an object, do a merge of the properties.
base[key] = helpers.configMerge(base[key], value);

} else {
// can just overwrite the value in this case
base[key] = value;
}
});

return base;
};

helpers.where = function(collection, filterCallback) {
Expand Down
2 changes: 1 addition & 1 deletion src/core/core.scaleService.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = function(Chart) {
},
getScaleDefaults: function(type) {
// Return the scale defaults merged with the global settings so that we always use the latest ones
return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {};
return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [Chart.defaults.scale, this.defaults[type]]) : {};
},
updateScaleDefaults: function(type, additions) {
var defaults = this.defaults;
Expand Down
104 changes: 104 additions & 0 deletions src/helpers/helpers.core.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,110 @@ module.exports = function(Chart) {
}

return true;
},

/**
* Returns a deep copy of `source` without keeping references on objects and arrays.
* @param {*} source - The value to clone.
* @returns {*}
*/
clone: function(source) {
if (helpers.isArray(source)) {
return source.map(helpers.clone);
}

if (helpers.isObject(source)) {
var target = {};
var keys = Object.keys(source);
var klen = keys.length;
var k = 0;

for (; k<klen; ++k) {
target[keys[k]] = helpers.clone(source[keys[k]]);
}

return target;
}

return source;
},

/**
* The default merger when Chart.helpers.merge is called without merger option.
* Note(SB): this method is also used by configMerge and scaleMerge as fallback.
* @private
*/
_merger: function(key, target, source, options) {
var tval = target[key];
var sval = source[key];

if (helpers.isObject(tval) && helpers.isObject(sval)) {
helpers.merge(tval, sval, options);
} else {
target[key] = helpers.clone(sval);
}
},

/**
* Merges source[key] in target[key] only if target[key] is undefined.
* @private
*/
_mergerIf: function(key, target, source) {
var tval = target[key];
var sval = source[key];

if (helpers.isObject(tval) && helpers.isObject(sval)) {
helpers.mergeIf(tval, sval);
} else if (!target.hasOwnProperty(key)) {
target[key] = helpers.clone(sval);
}
},

/**
* Recursively deep copies `source` properties into `target` with the given `options`.
* IMPORTANT: `target` is not cloned and will be updated with `source` properties.
* @param {Object} target - The target object in which all sources are merged into.
* @param {Object|Array(Object)} source - Object(s) to merge into `target`.
* @param {Object} [options] - Merging options:
* @param {Function} [options.merger] - The merge method (key, target, source, options)
* @returns {Object} The `target` object.
*/
merge: function(target, source, options) {
var sources = helpers.isArray(source)? source : [source];
var ilen = sources.length;
var merge, i, keys, klen, k;

if (!helpers.isObject(target)) {
return target;
}

options = options || {};
merge = options.merger || helpers._merger;

for (i=0; i<ilen; ++i) {
source = sources[i];
if (!helpers.isObject(source)) {
continue;
}

keys = Object.keys(source);
for (k=0, klen = keys.length; k<klen; ++k) {
merge(keys[k], target, source, options);
}
}

return target;
},

/**
* Recursively deep copies `source` properties into `target` *only* if not defined in target.
* IMPORTANT: `target` is not cloned and will be updated with `source` properties.
* @param {Object} target - The target object in which all sources are merged into.
* @param {Object|Array(Object)} source - Object(s) to merge into `target`.
* @returns {Object} The `target` object.
*/
mergeIf: function(target, source) {
return helpers.merge(target, source, {merger: helpers._mergerIf});
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/plugins/plugin.legend.js
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ module.exports = function(Chart) {
var legend = chart.legend;

if (legendOpts) {
legendOpts = helpers.configMerge(Chart.defaults.global.legend, legendOpts);
helpers.mergeIf(legendOpts, Chart.defaults.global.legend);

if (legend) {
layout.configure(chart, legend, legendOpts);
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/plugin.title.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ module.exports = function(Chart) {
var titleBlock = chart.titleBlock;

if (titleOpts) {
titleOpts = helpers.configMerge(Chart.defaults.global.title, titleOpts);
helpers.mergeIf(titleOpts, Chart.defaults.global.title);

if (titleBlock) {
layout.configure(chart, titleBlock, titleOpts);
Expand Down
19 changes: 0 additions & 19 deletions test/specs/core.helpers.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,6 @@ describe('Core helper tests', function() {
helpers = window.Chart.helpers;
});

it('should clone an object', function() {
var testData = {
myProp1: 'abc',
myProp2: ['a', 'b'],
myProp3: {
myProp4: 5,
myProp5: [1, 2]
}
};

var clone = helpers.clone(testData);
expect(clone).toEqual(testData);
expect(clone).not.toBe(testData);

expect(clone.myProp2).not.toBe(testData.myProp2);
expect(clone.myProp3).not.toBe(testData.myProp3);
expect(clone.myProp3.myProp5).not.toBe(testData.myProp3.myProp5);
});

it('should extend an object', function() {
var original = {
myProp1: 'abc',
Expand Down
Loading