simplyview 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/simply.app.js +1120 -0
- package/dist/simply.app.js.map +7 -0
- package/dist/simply.app.min.js +2 -0
- package/dist/simply.app.min.js.map +7 -0
- package/dist/simply.everything.js +1583 -2025
- package/dist/simply.everything.js.map +7 -0
- package/dist/simply.everything.min.js +2 -0
- package/dist/simply.everything.min.js.map +7 -0
- package/package.json +8 -3
- package/src/action.mjs +12 -0
- package/src/activate.mjs +63 -0
- package/src/app.mjs +40 -0
- package/src/bind.mjs +572 -0
- package/src/command.mjs +125 -0
- package/src/everything.mjs +27 -0
- package/src/include.mjs +191 -0
- package/src/key.mjs +55 -0
- package/src/model.mjs +151 -0
- package/src/route.mjs +222 -0
- package/src/state.mjs +536 -0
- package/js/.eslintrc.json +0 -29
- package/js/simply.action.js +0 -115
- package/js/simply.activate.js +0 -79
- package/js/simply.api.js +0 -228
- package/js/simply.app.js +0 -63
- package/js/simply.collect.js +0 -72
- package/js/simply.command.js +0 -196
- package/js/simply.include.js +0 -226
- package/js/simply.keyboard.js +0 -62
- package/js/simply.modules.js +0 -22
- package/js/simply.observe.js +0 -349
- package/js/simply.path.js +0 -48
- package/js/simply.render.js +0 -125
- package/js/simply.resize.js +0 -80
- package/js/simply.route.js +0 -234
- package/js/simply.view.js +0 -35
- package/js/simply.viewmodel.js +0 -189
- /package/{js/simply.include.next.js → src/include.next.js} +0 -0
package/js/simply.include.js
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
(function (global) {
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
var throttle = function( callbackFunction, intervalTime ) {
|
|
5
|
-
var eventId = 0;
|
|
6
|
-
return function() {
|
|
7
|
-
var myArguments = arguments;
|
|
8
|
-
var me = this;
|
|
9
|
-
if ( eventId ) {
|
|
10
|
-
return;
|
|
11
|
-
} else {
|
|
12
|
-
eventId = global.setTimeout( function() {
|
|
13
|
-
callbackFunction.apply(me, myArguments);
|
|
14
|
-
eventId = 0;
|
|
15
|
-
}, intervalTime );
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
var runWhenIdle = (function() {
|
|
21
|
-
if (global.requestIdleCallback) {
|
|
22
|
-
return function(callback) {
|
|
23
|
-
global.requestIdleCallback(callback, {timeout: 500});
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
return global.requestAnimationFrame;
|
|
27
|
-
})();
|
|
28
|
-
|
|
29
|
-
var rebaseHref = function(relative, base) {
|
|
30
|
-
if (/^[a-z-]*:?\//.test(relative)) {
|
|
31
|
-
return relative; // absolute href, no need to rebase
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
var stack = base.split('/'),
|
|
35
|
-
parts = relative.split('/');
|
|
36
|
-
stack.pop(); // remove current file name (or empty string)
|
|
37
|
-
for (var i=0; i<parts.length; i++) {
|
|
38
|
-
if (parts[i] == '.')
|
|
39
|
-
continue;
|
|
40
|
-
if (parts[i] == '..')
|
|
41
|
-
stack.pop();
|
|
42
|
-
else
|
|
43
|
-
stack.push(parts[i]);
|
|
44
|
-
}
|
|
45
|
-
return stack.join('/');
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
var observer, loaded = {};
|
|
49
|
-
var head = global.document.querySelector('head');
|
|
50
|
-
var currentScript = global.document.currentScript;
|
|
51
|
-
if (!currentScript) {
|
|
52
|
-
var getScriptURL = (function() {
|
|
53
|
-
var scripts = document.getElementsByTagName('script');
|
|
54
|
-
var index = scripts.length - 1;
|
|
55
|
-
var myScript = scripts[index];
|
|
56
|
-
return function() { return myScript.src; };
|
|
57
|
-
})();
|
|
58
|
-
var currentScriptURL = getScriptURL();
|
|
59
|
-
} else {
|
|
60
|
-
var currentScriptURL = currentScript.src;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
var waitForPreviousScripts = function() {
|
|
64
|
-
// because of the async=false attribute, this script will run after
|
|
65
|
-
// the previous scripts have been loaded and run
|
|
66
|
-
// simply.include.next.js only fires the simply-next-script event
|
|
67
|
-
// that triggers the Promise.resolve method
|
|
68
|
-
return new Promise(function(resolve) {
|
|
69
|
-
var next = global.document.createElement('script');
|
|
70
|
-
next.src = rebaseHref('simply.include.next.js', currentScriptURL);
|
|
71
|
-
next.async = false;
|
|
72
|
-
global.document.addEventListener('simply-include-next', function() {
|
|
73
|
-
head.removeChild(next);
|
|
74
|
-
resolve();
|
|
75
|
-
}, { once: true, passive: true});
|
|
76
|
-
head.appendChild(next);
|
|
77
|
-
});
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
var scriptLocations = [];
|
|
81
|
-
|
|
82
|
-
var include = {
|
|
83
|
-
scripts: function(scripts, base) {
|
|
84
|
-
var arr = [];
|
|
85
|
-
for(var i = scripts.length; i--; arr.unshift(scripts[i]));
|
|
86
|
-
var importScript = function() {
|
|
87
|
-
var script = arr.shift();
|
|
88
|
-
if (!script) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
var attrs = [].map.call(script.attributes, function(attr) {
|
|
92
|
-
return attr.name;
|
|
93
|
-
});
|
|
94
|
-
var clone = global.document.createElement('script');
|
|
95
|
-
attrs.forEach(function(attr) {
|
|
96
|
-
clone.setAttribute(attr, script.getAttribute(attr));
|
|
97
|
-
});
|
|
98
|
-
clone.removeAttribute('data-simply-location');
|
|
99
|
-
if (!clone.src) {
|
|
100
|
-
// this is an inline script, so copy the content and wait for previous scripts to run
|
|
101
|
-
clone.innerHTML = script.innerHTML;
|
|
102
|
-
waitForPreviousScripts()
|
|
103
|
-
.then(function() {
|
|
104
|
-
var node = scriptLocations[script.dataset.simplyLocation];
|
|
105
|
-
node.parentNode.insertBefore(clone, node);
|
|
106
|
-
node.parentNode.removeChild(node);
|
|
107
|
-
importScript();
|
|
108
|
-
});
|
|
109
|
-
} else {
|
|
110
|
-
clone.src = rebaseHref(clone.src, base);
|
|
111
|
-
if (!clone.hasAttribute('async') && !clone.hasAttribute('defer')) {
|
|
112
|
-
clone.async = false; //important! do not use clone.setAttribute('async', false) - it has no effect
|
|
113
|
-
}
|
|
114
|
-
var node = scriptLocations[script.dataset.simplyLocation];
|
|
115
|
-
node.parentNode.insertBefore(clone, node);
|
|
116
|
-
node.parentNode.removeChild(node);
|
|
117
|
-
loaded[clone.src]=true;
|
|
118
|
-
importScript();
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
if (arr.length) {
|
|
122
|
-
importScript();
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
html: function(html, link) {
|
|
126
|
-
var fragment = global.document.createRange().createContextualFragment(html);
|
|
127
|
-
var stylesheets = fragment.querySelectorAll('link[rel="stylesheet"],style');
|
|
128
|
-
// add all stylesheets to head
|
|
129
|
-
[].forEach.call(stylesheets, function(stylesheet) {
|
|
130
|
-
if (stylesheet.href) {
|
|
131
|
-
stylesheet.href = rebaseHref(stylesheet.href, link.href);
|
|
132
|
-
}
|
|
133
|
-
head.appendChild(stylesheet);
|
|
134
|
-
});
|
|
135
|
-
// remove the scripts from the fragment, as they will not run in the
|
|
136
|
-
// order in which they are defined
|
|
137
|
-
var scriptsFragment = global.document.createDocumentFragment();
|
|
138
|
-
// FIXME: this loses the original position of the script
|
|
139
|
-
// should add a placeholder so we can reinsert the clone
|
|
140
|
-
var scripts = fragment.querySelectorAll('script');
|
|
141
|
-
[].forEach.call(scripts, function(script) {
|
|
142
|
-
var placeholder = global.document.createComment(script.src || 'inline script');
|
|
143
|
-
script.parentNode.insertBefore(placeholder, script);
|
|
144
|
-
script.dataset.simplyLocation = scriptLocations.length;
|
|
145
|
-
scriptLocations.push(placeholder);
|
|
146
|
-
scriptsFragment.appendChild(script);
|
|
147
|
-
});
|
|
148
|
-
// add the remainder before the include link
|
|
149
|
-
link.parentNode.insertBefore(fragment, link ? link : null);
|
|
150
|
-
global.setTimeout(function() {
|
|
151
|
-
if (global.editor && global.editor.data && fragment.querySelector('[data-simply-field],[data-simply-list]')) {
|
|
152
|
-
//TODO: remove this dependency and let simply.bind listen for dom node insertions (and simply-edit.js use simply.bind)
|
|
153
|
-
global.editor.data.apply(global.editor.currentData, global.document);
|
|
154
|
-
}
|
|
155
|
-
simply.include.scripts(scriptsFragment.childNodes, link ? link.href : global.location.href );
|
|
156
|
-
}, 10);
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
var included = {};
|
|
161
|
-
var includeLinks = function(links) {
|
|
162
|
-
// mark them as in progress, so handleChanges doesn't find them again
|
|
163
|
-
var remainingLinks = [].reduce.call(links, function(remainder, link) {
|
|
164
|
-
if (link.rel=='simply-include-once' && included[link.href]) {
|
|
165
|
-
link.parentNode.removeChild(link);
|
|
166
|
-
} else {
|
|
167
|
-
included[link.href]=true;
|
|
168
|
-
link.rel = 'simply-include-loading';
|
|
169
|
-
remainder.push(link);
|
|
170
|
-
}
|
|
171
|
-
return remainder;
|
|
172
|
-
}, []);
|
|
173
|
-
[].forEach.call(remainingLinks, function(link) {
|
|
174
|
-
if (!link.href) {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
// fetch the html
|
|
178
|
-
fetch(link.href)
|
|
179
|
-
.then(function(response) {
|
|
180
|
-
if (response.ok) {
|
|
181
|
-
console.log('simply-include: loaded '+link.href);
|
|
182
|
-
return response.text();
|
|
183
|
-
} else {
|
|
184
|
-
console.log('simply-include: failed to load '+link.href);
|
|
185
|
-
}
|
|
186
|
-
})
|
|
187
|
-
.then(function(html) {
|
|
188
|
-
// if succesfull import the html
|
|
189
|
-
simply.include.html(html, link);
|
|
190
|
-
// remove the include link
|
|
191
|
-
link.parentNode.removeChild(link);
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
var handleChanges = throttle(function() {
|
|
197
|
-
runWhenIdle(function() {
|
|
198
|
-
var links = global.document.querySelectorAll('link[rel="simply-include"],link[rel="simply-include-once"]');
|
|
199
|
-
if (links.length) {
|
|
200
|
-
includeLinks(links);
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
var observe = function() {
|
|
206
|
-
observer = new MutationObserver(handleChanges);
|
|
207
|
-
observer.observe(global.document, {
|
|
208
|
-
subtree: true,
|
|
209
|
-
childList: true,
|
|
210
|
-
});
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
observe();
|
|
214
|
-
handleChanges(); // check if there are include links in the dom already
|
|
215
|
-
|
|
216
|
-
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
|
217
|
-
module.exports = include;
|
|
218
|
-
} else {
|
|
219
|
-
if (!global.simply) {
|
|
220
|
-
global.simply = {};
|
|
221
|
-
}
|
|
222
|
-
global.simply.include = include;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
})(this);
|
package/js/simply.keyboard.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
(function(global) {
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
function keyboard(app, config) {
|
|
5
|
-
var keys = config;
|
|
6
|
-
|
|
7
|
-
if (!app) {
|
|
8
|
-
app = {};
|
|
9
|
-
}
|
|
10
|
-
if (!app.container) {
|
|
11
|
-
app.container = document.body;
|
|
12
|
-
}
|
|
13
|
-
app.container.addEventListener('keydown', (e) => {
|
|
14
|
-
if (e.isComposing || e.keyCode === 229) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
if (e.defaultPrevented) {
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
if (!e.target) {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
let selectedKeyboard = 'default';
|
|
25
|
-
if (e.target.closest('[data-simply-keyboard]')) {
|
|
26
|
-
selectedKeyboard = e.target.closest('[data-simply-keyboard]').dataset.simplyKeyboard;
|
|
27
|
-
}
|
|
28
|
-
let key = '';
|
|
29
|
-
if (e.ctrlKey && e.keyCode!=17) {
|
|
30
|
-
key+='Control+';
|
|
31
|
-
}
|
|
32
|
-
if (e.metaKey && e.keyCode!=224) {
|
|
33
|
-
key+='Meta+';
|
|
34
|
-
}
|
|
35
|
-
if (e.altKey && e.keyCode!=18) {
|
|
36
|
-
key+='Alt+';
|
|
37
|
-
}
|
|
38
|
-
if (e.shiftKey && e.keyCode!=16) {
|
|
39
|
-
key+='Shift+';
|
|
40
|
-
}
|
|
41
|
-
key+=e.key;
|
|
42
|
-
|
|
43
|
-
if (keys[selectedKeyboard] && keys[selectedKeyboard][key]) {
|
|
44
|
-
let keyboard = keys[selectedKeyboard]
|
|
45
|
-
keyboard.app = app;
|
|
46
|
-
keyboard[key].call(keyboard,e);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
return keys;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
|
55
|
-
module.exports = keyboard;
|
|
56
|
-
} else {
|
|
57
|
-
if (!global.simply) {
|
|
58
|
-
global.simply = {};
|
|
59
|
-
}
|
|
60
|
-
global.simply.keyboard = keyboard;
|
|
61
|
-
}
|
|
62
|
-
})(this);
|
package/js/simply.modules.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
(function() {
|
|
2
|
-
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
|
3
|
-
var simply = {
|
|
4
|
-
command: require('simply.command.js'),
|
|
5
|
-
action: require('simply.action.js'),
|
|
6
|
-
route: require('simply.route.js'),
|
|
7
|
-
view: require('simply.view.js'),
|
|
8
|
-
viewmodel: require('simply.viewmodel.js'),
|
|
9
|
-
resize: require('simply.resize.js'),
|
|
10
|
-
activate:require('simply.active.js'),
|
|
11
|
-
include: require('simply.include.js'),
|
|
12
|
-
render: require('simply.render.js'),
|
|
13
|
-
observe: require('simply.observe.js'),
|
|
14
|
-
bind: require('simply.bind.js'),
|
|
15
|
-
app: require('simply.app.js'),
|
|
16
|
-
collect: require('simply.collect.js'),
|
|
17
|
-
path: require('simply.path.js')
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
module.exports = simply;
|
|
21
|
-
}
|
|
22
|
-
})();
|
package/js/simply.observe.js
DELETED
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* simply.observe
|
|
3
|
-
* This component lets you observe changes in a json compatible data structure
|
|
4
|
-
* It doesn't support linking the same object multiple times
|
|
5
|
-
* It doesn't register deletion of properties using the delete keyword, assign
|
|
6
|
-
* null to the property instead.
|
|
7
|
-
* It doesn't register addition of new properties.
|
|
8
|
-
* It doesn't register directly assigning new entries in an array on a previously
|
|
9
|
-
* non-existant index.
|
|
10
|
-
*
|
|
11
|
-
* usage:
|
|
12
|
-
*
|
|
13
|
-
* (function) simply.observe( (object) model, (string) path, (function) callback)
|
|
14
|
-
*
|
|
15
|
-
* var model = { foo: { bar: 'baz' } };
|
|
16
|
-
* var removeObserver = simply.observe(model, 'foo.bar', function(value, sourcePath) {
|
|
17
|
-
* console.log(sourcePath+': '+value);
|
|
18
|
-
* };
|
|
19
|
-
*
|
|
20
|
-
* The function returns a function that removes the observer when called.
|
|
21
|
-
*
|
|
22
|
-
* The component can observe in place changes in arrays, either by changing
|
|
23
|
-
* an item in a specific index, by calling methods on the array that change
|
|
24
|
-
* the array in place or by reassigning the array with a new value.
|
|
25
|
-
*
|
|
26
|
-
* The sourcePath contains the exact entry that was changed, the value is the
|
|
27
|
-
* value for the path passed to simply.observe.
|
|
28
|
-
* If an array method was called that changes the array in place, the sourcePath
|
|
29
|
-
* also contains that method and its arguments JSON serialized.
|
|
30
|
-
*
|
|
31
|
-
* sourcePath parts are always seperated with '.', even for array indexes.
|
|
32
|
-
* so if foo = [ 'bar' ], the path to 'bar' would be 'foo.0'
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
/*
|
|
36
|
-
FIXME: child properties added after initial observe() call aren't added to the
|
|
37
|
-
childListeners. onMissingChildren can't then find them.
|
|
38
|
-
TODO: onMissingChildren must loop through all fields to get only the direct child
|
|
39
|
-
properties for a given parent, keep seperate index for this?
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
(function (global) {
|
|
43
|
-
'use strict';
|
|
44
|
-
|
|
45
|
-
var changeListeners = new WeakMap();
|
|
46
|
-
var parentListeners = new WeakMap();
|
|
47
|
-
var childListeners = new WeakMap();
|
|
48
|
-
var changesSignalled = {};
|
|
49
|
-
var observersPaused = 0;
|
|
50
|
-
|
|
51
|
-
function signalChange(model, path, value, sourcePath) {
|
|
52
|
-
if (observersPaused) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
sourcePath = sourcePath ? sourcePath : path;
|
|
57
|
-
changesSignalled = {};
|
|
58
|
-
|
|
59
|
-
var signalRecursion = function(model, path, value, sourcePath) {
|
|
60
|
-
if (changeListeners.has(model) && changeListeners.get(model)[path]) {
|
|
61
|
-
// changeListeners[model][path] contains callback methods
|
|
62
|
-
changeListeners.get(model)[path].forEach(function(callback) {
|
|
63
|
-
changesSignalled[path] = true;
|
|
64
|
-
callback(value, sourcePath);
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
//TODO: check if this is correct
|
|
70
|
-
//previous version only triggered parentListeners when no changeListeners were
|
|
71
|
-
//triggered. that created problems with arrays. make an exhaustive unit test.
|
|
72
|
-
signalRecursion(model, path, value, sourcePath);
|
|
73
|
-
|
|
74
|
-
if (parentListeners.has(model) && parentListeners.get(model)[path]) {
|
|
75
|
-
// parentListeners[model][path] contains child paths to signal change on
|
|
76
|
-
// if a parent object is changed, this signals the change to the child objects
|
|
77
|
-
parentListeners.get(model)[path].forEach(function(childPath) {
|
|
78
|
-
if (!changesSignalled[childPath]) {
|
|
79
|
-
var value = getByPath(model, childPath);
|
|
80
|
-
if (value) {
|
|
81
|
-
attach(model, childPath);
|
|
82
|
-
}
|
|
83
|
-
signalRecursion(model, childPath, value, sourcePath);
|
|
84
|
-
changesSignalled[childPath] = true;
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (childListeners.has(model) && childListeners.get(model)[path]) {
|
|
90
|
-
// childListeners[model][path] contains parent paths to signal change on
|
|
91
|
-
// if a child object is changed, this signals the change to the parent objects
|
|
92
|
-
childListeners.get(model)[path].forEach(function(parentPath) {
|
|
93
|
-
if (!changesSignalled[parentPath]) {
|
|
94
|
-
var value = getByPath(model, parentPath);
|
|
95
|
-
signalRecursion(model, parentPath, value, sourcePath);
|
|
96
|
-
changesSignalled[parentPath] = true;
|
|
97
|
-
// check if the parent object still has this child property
|
|
98
|
-
//FIXME: add a setter trigger here to restore observers once the child property get set again
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function getByPath(model, path) {
|
|
107
|
-
var parts = path.split('.');
|
|
108
|
-
var curr = model;
|
|
109
|
-
do {
|
|
110
|
-
curr = curr[parts.shift()];
|
|
111
|
-
} while (parts.length && curr);
|
|
112
|
-
return curr;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function parent(path) {
|
|
116
|
-
var parts = path.split('.');
|
|
117
|
-
parts.pop();
|
|
118
|
-
return parts.join('.');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function head(path) {
|
|
122
|
-
return path.split('.').shift();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function onParents(model, path, callback) {
|
|
126
|
-
var parent = '';
|
|
127
|
-
var parentOb = model;
|
|
128
|
-
var parents = path.split('.');
|
|
129
|
-
do {
|
|
130
|
-
var head = parents.shift();
|
|
131
|
-
if (parentOb && typeof parentOb[head] != 'undefined') {
|
|
132
|
-
callback(parentOb, head, (parent ? parent + '.' + head : head));
|
|
133
|
-
parentOb = parentOb[head];
|
|
134
|
-
}
|
|
135
|
-
parent = (parent ? parent + '.' + head : head );
|
|
136
|
-
} while (parents.length);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function onChildren(model, path, callback) {
|
|
140
|
-
var onChildObjects = function(object, path, callback) {
|
|
141
|
-
if (typeof object != 'object' || object == null) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (Array.isArray(object)) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
// register the current keys
|
|
148
|
-
Object.keys(object).forEach(function(key) {
|
|
149
|
-
callback(object, key, path+'.'+key);
|
|
150
|
-
onChildObjects(object[key], path+'.'+key, callback);
|
|
151
|
-
});
|
|
152
|
-
};
|
|
153
|
-
var parent = getByPath(model, path);
|
|
154
|
-
onChildObjects(parent, path, callback);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function onMissingChildren(model, path, callback) {
|
|
158
|
-
var allChildren = Object.keys(childListeners.get(model) || []).filter(function(childPath) {
|
|
159
|
-
return childPath.substr(0, path.length)==path && childPath.length>path.length;
|
|
160
|
-
});
|
|
161
|
-
if (!allChildren.length) {
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
var object = getByPath(model, path);
|
|
165
|
-
var keysSeen = {};
|
|
166
|
-
allChildren.forEach(function(childPath) {
|
|
167
|
-
var key = head(childPath.substr(path.length+1));
|
|
168
|
-
if (typeof object[key] == 'undefined') {
|
|
169
|
-
if (!keysSeen[key]) {
|
|
170
|
-
callback(object, key, path+'.'+key);
|
|
171
|
-
keysSeen[key] = true;
|
|
172
|
-
}
|
|
173
|
-
} else {
|
|
174
|
-
onMissingChildren(model, path+'.'+key, callback);
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function addChangeListener(model, path, callback) {
|
|
180
|
-
if (!changeListeners.has(model)) {
|
|
181
|
-
changeListeners.set(model, {});
|
|
182
|
-
}
|
|
183
|
-
if (!changeListeners.get(model)[path]) {
|
|
184
|
-
changeListeners.get(model)[path] = [];
|
|
185
|
-
}
|
|
186
|
-
changeListeners.get(model)[path].push(callback);
|
|
187
|
-
|
|
188
|
-
if (!parentListeners.has(model)) {
|
|
189
|
-
parentListeners.set(model, {});
|
|
190
|
-
}
|
|
191
|
-
var parentPath = parent(path);
|
|
192
|
-
onParents(model, parentPath, function(parentOb, key, currPath) {
|
|
193
|
-
if (!parentListeners.get(model)[currPath]) {
|
|
194
|
-
parentListeners.get(model)[currPath] = [];
|
|
195
|
-
}
|
|
196
|
-
parentListeners.get(model)[currPath].push(path);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
if (!childListeners.has(model)) {
|
|
200
|
-
childListeners.set(model, {});
|
|
201
|
-
}
|
|
202
|
-
onChildren(model, path, function(childOb, key, currPath) {
|
|
203
|
-
if (!childListeners.get(model)[currPath]) {
|
|
204
|
-
childListeners.get(model)[currPath] = [];
|
|
205
|
-
}
|
|
206
|
-
childListeners.get(model)[currPath].push(path);
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function removeChangeListener(model, path, callback) {
|
|
211
|
-
if (!changeListeners.has(model)) {
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
if (changeListeners.get(model)[path]) {
|
|
215
|
-
changeListeners.get(model)[path] = changeListeners.get(model)[path].filter(function(f) {
|
|
216
|
-
return f != callback;
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function pauseObservers() {
|
|
222
|
-
observersPaused++;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function resumeObservers() {
|
|
226
|
-
observersPaused--;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function attach(model, path, options) {
|
|
230
|
-
|
|
231
|
-
var attachArray = function(object, path) {
|
|
232
|
-
var desc = Object.getOwnPropertyDescriptor(object, 'push');
|
|
233
|
-
if (!desc || desc.configurable) {
|
|
234
|
-
for (var f of ['push','pop','reverse','shift','sort','splice','unshift','copyWithin']) {
|
|
235
|
-
(function(f) {
|
|
236
|
-
try {
|
|
237
|
-
Object.defineProperty(object, f, {
|
|
238
|
-
value: function() {
|
|
239
|
-
pauseObservers();
|
|
240
|
-
var result = Array.prototype[f].apply(this, arguments);
|
|
241
|
-
attach(model, path);
|
|
242
|
-
var args = [].slice.call(arguments).map(function(arg) {
|
|
243
|
-
return JSON.stringify(arg);
|
|
244
|
-
});
|
|
245
|
-
resumeObservers();
|
|
246
|
-
signalChange(model, path, this, path+'.'+f+'('+args.join(',')+')');
|
|
247
|
-
return result;
|
|
248
|
-
},
|
|
249
|
-
readable: false,
|
|
250
|
-
enumerable: false,
|
|
251
|
-
configurable: false
|
|
252
|
-
});
|
|
253
|
-
} catch(e) {
|
|
254
|
-
console.error('simply.observer: Error: Couldn\'t redefine array method '+f+' on '+path, e);
|
|
255
|
-
}
|
|
256
|
-
}(f));
|
|
257
|
-
}
|
|
258
|
-
for (var i=0, l=object.length; i<l; i++) {
|
|
259
|
-
//FIXME: options becomes undefined here somewhere
|
|
260
|
-
// if (options.skipArray) {
|
|
261
|
-
addSetter(object, i, path+'.'+i);
|
|
262
|
-
// } else {
|
|
263
|
-
// attach(model, path+'.'+i, options);
|
|
264
|
-
// }
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
var addSetTrigger = function(object, key, currPath) {
|
|
270
|
-
Object.defineProperty(object, key, {
|
|
271
|
-
set: function(value) {
|
|
272
|
-
addSetter(object, key, currPath);
|
|
273
|
-
object[key] = value;
|
|
274
|
-
},
|
|
275
|
-
configurable: true,
|
|
276
|
-
readable: false,
|
|
277
|
-
enumerable: false
|
|
278
|
-
});
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
var addSetter = function(object, key, currPath) {
|
|
282
|
-
if (Object.getOwnPropertyDescriptor(object, key).configurable) {
|
|
283
|
-
// assume object keys are only unconfigurable if the
|
|
284
|
-
// following code has already been run on this property
|
|
285
|
-
var _value = object[key];
|
|
286
|
-
Object.defineProperty(object, key, {
|
|
287
|
-
set: function(value) {
|
|
288
|
-
_value = value;
|
|
289
|
-
signalChange(model, currPath, value);
|
|
290
|
-
if (value!=null) {
|
|
291
|
-
onChildren(model, currPath, addSetter);
|
|
292
|
-
onMissingChildren(model, currPath, addSetTrigger);
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
get: function() {
|
|
296
|
-
return _value;
|
|
297
|
-
},
|
|
298
|
-
configurable: false,
|
|
299
|
-
readable: true,
|
|
300
|
-
enumerable: true
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
if (Array.isArray(object[key])) {
|
|
304
|
-
attachArray(object[key], currPath, options);
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
onParents(model, path, addSetter);
|
|
309
|
-
onChildren(model, path, addSetter);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// FIXME: if you remove a key by reassigning the parent object
|
|
313
|
-
// and then assign that missing key a new value
|
|
314
|
-
// the observer doesn't get triggered
|
|
315
|
-
// var model = { foo: { bar: 'baz' } };
|
|
316
|
-
// simply.observer(model, 'foo.bar', ...)
|
|
317
|
-
// model.foo = { }
|
|
318
|
-
// model.foo.bar = 'zab'; // this should trigger the observer but doesn't
|
|
319
|
-
|
|
320
|
-
var observe = function(model, path, callback, options) {
|
|
321
|
-
if (!path) {
|
|
322
|
-
var keys = Object.keys(model);
|
|
323
|
-
keys.forEach(function(key) {
|
|
324
|
-
attach(model, key, options);
|
|
325
|
-
addChangeListener(model, key, callback);
|
|
326
|
-
});
|
|
327
|
-
return function() {
|
|
328
|
-
keys.forEach(function(key) {
|
|
329
|
-
removeChangeListener(model, key, callback);
|
|
330
|
-
});
|
|
331
|
-
};
|
|
332
|
-
} else {
|
|
333
|
-
attach(model, path, options);
|
|
334
|
-
addChangeListener(model, path, callback);
|
|
335
|
-
return function() {
|
|
336
|
-
removeChangeListener(model, path, callback);
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
|
342
|
-
module.exports = observe;
|
|
343
|
-
} else {
|
|
344
|
-
if (!global.simply) {
|
|
345
|
-
global.simply = {};
|
|
346
|
-
}
|
|
347
|
-
global.simply.observe = observe;
|
|
348
|
-
}
|
|
349
|
-
})(this);
|