-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
allow for deep state check and save (#3262)
- Loading branch information
Showing
7 changed files
with
211 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,45 +1,75 @@ | ||
/* | ||
Depends on `stateProperties` | ||
*/ | ||
fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { | ||
|
||
/** | ||
* Returns true if object state (one of its state properties) was changed | ||
* @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called | ||
*/ | ||
hasStateChanged: function() { | ||
return this.stateProperties.some(function(prop) { | ||
return this.get(prop) !== this.originalState[prop]; | ||
}, this); | ||
}, | ||
|
||
/** | ||
* Saves state of an object | ||
* @param {Object} [options] Object with additional `stateProperties` array to include when saving state | ||
* @return {fabric.Object} thisArg | ||
*/ | ||
saveState: function(options) { | ||
this.stateProperties.forEach(function(prop) { | ||
this.originalState[prop] = this.get(prop); | ||
}, this); | ||
|
||
if (options && options.stateProperties) { | ||
options.stateProperties.forEach(function(prop) { | ||
this.originalState[prop] = this.get(prop); | ||
}, this); | ||
} | ||
(function() { | ||
|
||
return this; | ||
}, | ||
var extend = fabric.util.object.extend; | ||
|
||
/** | ||
* Setups state of an object | ||
* @return {fabric.Object} thisArg | ||
*/ | ||
setupState: function() { | ||
this.originalState = { }; | ||
this.saveState(); | ||
/* | ||
Depends on `stateProperties` | ||
*/ | ||
function saveProps(origin, destination, props) { | ||
var tmpObj = { }, deep = true; | ||
props.forEach(function(prop) { | ||
tmpObj[prop] = origin[prop]; | ||
}); | ||
extend(origin[destination], tmpObj, deep); | ||
} | ||
|
||
return this; | ||
function _isEqual(origValue, currentValue) { | ||
if (origValue instanceof Array) { | ||
if (origValue.length !== currentValue.length) { | ||
return false | ||
} | ||
var _currentValue = currentValue.concat().sort(), | ||
_origValue = origValue.concat().sort(); | ||
return !_origValue.some(function(v, i) { | ||
return !_isEqual(_currentValue[i], v); | ||
}); | ||
} | ||
else if (origValue instanceof Object) { | ||
for (var key in origValue) { | ||
if (!_isEqual(origValue[key], currentValue[key])) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
else { | ||
return origValue === currentValue; | ||
} | ||
} | ||
}); | ||
|
||
|
||
fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { | ||
|
||
/** | ||
* Returns true if object state (one of its state properties) was changed | ||
* @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called | ||
*/ | ||
hasStateChanged: function() { | ||
return !_isEqual(this.originalState, this); | ||
}, | ||
|
||
/** | ||
* Saves state of an object | ||
* @param {Object} [options] Object with additional `stateProperties` array to include when saving state | ||
* @return {fabric.Object} thisArg | ||
*/ | ||
saveState: function(options) { | ||
saveProps(this, 'originalState', this.stateProperties); | ||
if (options && options.stateProperties) { | ||
saveProps(this, 'originalState', options.stateProperties); | ||
} | ||
return this; | ||
}, | ||
|
||
/** | ||
* Setups state of an object | ||
* @param {Object} [options] Object with additional `stateProperties` array to include when saving state | ||
* @return {fabric.Object} thisArg | ||
*/ | ||
setupState: function(options) { | ||
this.originalState = { }; | ||
this.saveState(options); | ||
return this; | ||
} | ||
}); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
(function(){ | ||
|
||
QUnit.module('fabric.stateful'); | ||
|
||
test('hasStateChanged', function() { | ||
var cObj = new fabric.Object(); | ||
ok(typeof cObj.hasStateChanged == 'function'); | ||
cObj.setupState(); | ||
ok(!cObj.hasStateChanged(), 'state should not be changed'); | ||
cObj.saveState(); | ||
cObj.set('left', 123).set('top', 456); | ||
ok(cObj.hasStateChanged()); | ||
}); | ||
|
||
test('saveState', function() { | ||
var cObj = new fabric.Object(); | ||
ok(typeof cObj.saveState == 'function'); | ||
cObj.setupState(); | ||
equal(cObj.saveState(), cObj, 'chainable'); | ||
cObj.set('left', 123).set('top', 456); | ||
cObj.saveState(); | ||
cObj.set('left', 223).set('top', 556); | ||
equal(cObj.originalState.left, 123); | ||
equal(cObj.originalState.top, 456); | ||
}); | ||
|
||
test('saveState with extra props', function() { | ||
var cObj = new fabric.Object(); | ||
cObj.prop1 = 'a'; | ||
cObj.prop2 = 'b'; | ||
cObj.left = 123; | ||
var extraProps = ['prop1', 'prop2']; | ||
var options = { stateProperties: extraProps }; | ||
cObj.setupState(options); | ||
equal(cObj.originalState.prop1, 'a', 'it saves the extra props'); | ||
equal(cObj.originalState.prop2, 'b', 'it saves the extra props'); | ||
cObj.prop1 = 'c'; | ||
ok(cObj.hasStateChanged(), 'it detects changes in extra props'); | ||
equal(cObj.originalState.left, 123, 'normal props are still there'); | ||
}); | ||
|
||
test('saveState with array', function() { | ||
var cObj = new fabric.Text('Hello'); | ||
cObj.set('textDecoration', ['underline']); | ||
cObj.setupState(); | ||
deepEqual(cObj.textDecoration, cObj.originalState.textDecoration, 'textDecoration in state is deepEqual'); | ||
notEqual(cObj.textDecoration, cObj.originalState.textDecoration, 'textDecoration in not same Object'); | ||
cObj.textDecoration[0] = 'overline'; | ||
ok(cObj.hasStateChanged(), 'hasStateChanged detects changes in nested props'); | ||
|
||
cObj.set('textDecoration', ['overline', 'underline']); | ||
cObj.saveState(); | ||
cObj.set('textDecoration', ['underline', 'overline']); | ||
ok(!cObj.hasStateChanged(), 'order does no matter'); | ||
|
||
cObj.set('textDecoration', ['underline']); | ||
cObj.saveState(); | ||
cObj.set('textDecoration', ['underline', 'overline']); | ||
ok(cObj.hasStateChanged(), 'more properties added'); | ||
|
||
cObj.set('textDecoration', ['underline', 'overline']); | ||
cObj.saveState(); | ||
cObj.set('textDecoration', ['overline']); | ||
ok(cObj.hasStateChanged(), 'less properties'); | ||
}); | ||
|
||
test('saveState with fabric class gradient', function() { | ||
var cObj = new fabric.Object(); | ||
var gradient = new fabric.Gradient({ | ||
type: 'linear', | ||
coords: { | ||
x1: 0, | ||
y1: 10, | ||
x2: 100, | ||
y2: 200, | ||
}, | ||
colorStops: [ | ||
{ offset: 0, color: 'red', opacity: 0 }, | ||
{ offset: 1, color: 'green' } | ||
] | ||
}); | ||
|
||
cObj.set('fill', '#FF0000'); | ||
cObj.setupState(); | ||
cObj.setFill(gradient); | ||
ok(cObj.hasStateChanged(), 'hasStateChanged detects changes in nested props'); | ||
cObj.saveState(); | ||
gradient.type = 'radial'; | ||
ok(cObj.hasStateChanged(), 'hasStateChanged detects changes in nested props on first level of nesting'); | ||
cObj.saveState(); | ||
gradient.coords.x1 = 3; | ||
ok(cObj.hasStateChanged(), 'hasStateChanged detects changes in nested props on second level of nesting'); | ||
cObj.saveState(); | ||
gradient.colorStops[0].color = 'blue'; | ||
ok(cObj.hasStateChanged(), 'hasStateChanged detects changes in nested props on third level of nesting'); | ||
}); | ||
|
||
})(); |