ObservableObject.js
3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/**
* ObservableObject.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This class is a object that is observable when properties changes a change event gets emitted.
*
* @private
* @class tinymce.data.ObservableObject
*/
define("tinymce/data/ObservableObject", [
"tinymce/data/Binding",
"tinymce/util/Observable",
"tinymce/util/Class",
"tinymce/util/Tools"
], function(Binding, Observable, Class, Tools) {
function isNode(node) {
return node.nodeType > 0;
}
// Todo: Maybe this should be shallow compare since it might be huge object references
function isEqual(a, b) {
var k, checked;
// Strict equals
if (a === b) {
return true;
}
// Compare null
if (a === null || b === null) {
return a === b;
}
// Compare number, boolean, string, undefined
if (typeof a !== "object" || typeof b !== "object") {
return a === b;
}
// Compare arrays
if (Tools.isArray(b)) {
if (a.length !== b.length) {
return false;
}
k = a.length;
while (k--) {
if (!isEqual(a[k], b[k])) {
return false;
}
}
}
// Shallow compare nodes
if (isNode(a) || isNode(b)) {
return a === b;
}
// Compare objects
checked = {};
for (k in b) {
if (!isEqual(a[k], b[k])) {
return false;
}
checked[k] = true;
}
for (k in a) {
if (!checked[k] && !isEqual(a[k], b[k])) {
return false;
}
}
return true;
}
return Class.extend({
Mixins: [Observable],
/**
* Constructs a new observable object instance.
*
* @constructor
* @param {Object} data Initial data for the object.
*/
init: function(data) {
var name, value;
data = data || {};
for (name in data) {
value = data[name];
if (value instanceof Binding) {
data[name] = value.create(this, name);
}
}
this.data = data;
},
/**
* Sets a property on the value this will call
* observers if the value is a change from the current value.
*
* @method set
* @param {String/object} name Name of the property to set or a object of items to set.
* @param {Object} value Value to set for the property.
* @return {tinymce.data.ObservableObject} Observable object instance.
*/
set: function(name, value) {
var key, args, oldValue = this.data[name];
if (value instanceof Binding) {
value = value.create(this, name);
}
if (typeof name === "object") {
for (key in name) {
this.set(key, name[key]);
}
return this;
}
if (!isEqual(oldValue, value)) {
this.data[name] = value;
args = {
target: this,
name: name,
value: value,
oldValue: oldValue
};
this.fire('change:' + name, args);
this.fire('change', args);
}
return this;
},
/**
* Gets a property by name.
*
* @method get
* @param {String} name Name of the property to get.
* @return {Object} Object value of propery.
*/
get: function(name) {
return this.data[name];
},
/**
* Returns true/false if the specified property exists.
*
* @method has
* @param {String} name Name of the property to check for.
* @return {Boolean} true/false if the item exists.
*/
has: function(name) {
return name in this.data;
},
/**
* Returns a dynamic property binding for the specified property name. This makes
* it possible to sync the state of two properties in two ObservableObject instances.
*
* @method bind
* @param {String} name Name of the property to sync with the property it's inserted to.
* @return {tinymce.data.Binding} Data binding instance.
*/
bind: function(name) {
return Binding.create(this, name);
},
/**
* Destroys the observable object and fires the "destroy"
* event and clean up any internal resources.
*
* @method destroy
*/
destroy: function() {
this.fire('destroy');
}
});
});