bee_api 0.0.5
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.
- data/bin/bee_api +84 -0
- data/lib/mdpreview.rb +80 -0
- data/lib/mdpreview/translator.rb +60 -0
- data/lib/mdpreview/version.rb +3 -0
- data/test/mdptest.rb +100 -0
- data/vendor/HISTORY.md +237 -0
- data/vendor/Jakefile.js +316 -0
- data/vendor/LICENSE +176 -0
- data/vendor/NOTICE +17 -0
- data/vendor/README.md +102 -0
- data/vendor/app/chrome/documentation.txt +12 -0
- data/vendor/app/chrome/manifest.json +24 -0
- data/vendor/app/web/ajax.js +43 -0
- data/vendor/app/web/app.css +292 -0
- data/vendor/app/web/app.js +377 -0
- data/vendor/app/web/beta/index.html +17 -0
- data/vendor/app/web/datapolicy.txt +48 -0
- data/vendor/app/web/doc/doc.css +60 -0
- data/vendor/app/web/doc/img/actions_menu.png +0 -0
- data/vendor/app/web/doc/img/button_actions_menu.png +0 -0
- data/vendor/app/web/doc/img/button_dragarea.png +0 -0
- data/vendor/app/web/doc/img/jsoneditor.png +0 -0
- data/vendor/app/web/doc/img/jsonformatter.png +0 -0
- data/vendor/app/web/doc/img/main_menu.png +0 -0
- data/vendor/app/web/doc/img/splitter.png +0 -0
- data/vendor/app/web/doc/index.html +201 -0
- data/vendor/app/web/favicon.ico +0 -0
- data/vendor/app/web/fileretriever.css +54 -0
- data/vendor/app/web/fileretriever.js +567 -0
- data/vendor/app/web/fileretriever.php +120 -0
- data/vendor/app/web/googlea47c4a0b36d11021.html +1 -0
- data/vendor/app/web/hash.js +133 -0
- data/vendor/app/web/img/description.txt +20 -0
- data/vendor/app/web/img/header_background.png +0 -0
- data/vendor/app/web/img/icon_128.png +0 -0
- data/vendor/app/web/img/icon_16.png +0 -0
- data/vendor/app/web/img/icon_gray.svg +151 -0
- data/vendor/app/web/img/icon_gray_16.svg +150 -0
- data/vendor/app/web/img/icon_orange.svg +151 -0
- data/vendor/app/web/img/logo.png +0 -0
- data/vendor/app/web/img/logo.xcf +0 -0
- data/vendor/app/web/img/logo_app.png +0 -0
- data/vendor/app/web/img/logo_app.xcf +0 -0
- data/vendor/app/web/index.html +191 -0
- data/vendor/app/web/notify.js +150 -0
- data/vendor/app/web/queryparams.js +71 -0
- data/vendor/app/web/robots.txt +0 -0
- data/vendor/app/web/splitter.js +179 -0
- data/vendor/app/web/test.html +224 -0
- data/vendor/component.json +33 -0
- data/vendor/docs/api.md +188 -0
- data/vendor/docs/usage.md +137 -0
- data/vendor/examples/01_basic_usage.html +45 -0
- data/vendor/examples/02_viewer.html +38 -0
- data/vendor/examples/03_switch_mode.html +98 -0
- data/vendor/examples/cur.file +1 -0
- data/vendor/examples/jquery.js +2 -0
- data/vendor/examples/meta.js +1 -0
- data/vendor/examples/requirejs_demo/requirejs_demo.html +19 -0
- data/vendor/examples/requirejs_demo/scripts/main.js +25 -0
- data/vendor/examples/requirejs_demo/scripts/require.js +35 -0
- data/vendor/img/jsoneditor-icons.png +0 -0
- data/vendor/jsoneditor-min.css +1 -0
- data/vendor/jsoneditor-min.js +34 -0
- data/vendor/jsoneditor.css +597 -0
- data/vendor/jsoneditor.js +6069 -0
- data/vendor/jsoneditor/css/contextmenu.css +219 -0
- data/vendor/jsoneditor/css/img/description.txt +13 -0
- data/vendor/jsoneditor/css/img/export.sh +16 -0
- data/vendor/jsoneditor/css/img/jsoneditor-icons.png +0 -0
- data/vendor/jsoneditor/css/img/jsoneditor-icons.svg +861 -0
- data/vendor/jsoneditor/css/jsoneditor.css +220 -0
- data/vendor/jsoneditor/css/menu.css +81 -0
- data/vendor/jsoneditor/css/searchbox.css +73 -0
- data/vendor/jsoneditor/js/appendnode.js +211 -0
- data/vendor/jsoneditor/js/contextmenu.js +440 -0
- data/vendor/jsoneditor/js/header.js +32 -0
- data/vendor/jsoneditor/js/highlighter.js +82 -0
- data/vendor/jsoneditor/js/history.js +218 -0
- data/vendor/jsoneditor/js/jsoneditor.js +206 -0
- data/vendor/jsoneditor/js/module.js +50 -0
- data/vendor/jsoneditor/js/node.js +2864 -0
- data/vendor/jsoneditor/js/searchbox.js +288 -0
- data/vendor/jsoneditor/js/texteditor.js +311 -0
- data/vendor/jsoneditor/js/treeeditor.js +770 -0
- data/vendor/jsoneditor/js/util.js +582 -0
- data/vendor/lib/ace/ace.js +11 -0
- data/vendor/lib/ace/mode-json.js +1 -0
- data/vendor/lib/ace/theme-jsoneditor.js +144 -0
- data/vendor/lib/ace/theme-textmate.js +163 -0
- data/vendor/lib/ace/worker-json.js +1 -0
- data/vendor/lib/jsonlint/README.md +62 -0
- data/vendor/lib/jsonlint/jsonlint.js +432 -0
- data/vendor/misc/screenshots/actionsmenu_640x400.png +0 -0
- data/vendor/misc/screenshots/codeeditor_640x400.png +0 -0
- data/vendor/misc/screenshots/description.json +17 -0
- data/vendor/misc/screenshots/jsoneditoronline.png +0 -0
- data/vendor/misc/screenshots/jsoneditoronline_640x400.png +0 -0
- data/vendor/misc/screenshots/search_640x400.png +0 -0
- data/vendor/misc/screenshots/small_tile.xcf +0 -0
- data/vendor/misc/screenshots/small_tile_440x280.png +0 -0
- data/vendor/misc/todo.txt +101 -0
- data/vendor/package.json +28 -0
- data/vendor/test/couchdbeditor.html +100 -0
- data/vendor/test/largefile.json +12605 -0
- data/vendor/test/test_ace.html +60 -0
- data/vendor/test/test_editable_div.html +449 -0
- metadata +154 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
|
|
2
|
+
// module exports
|
|
3
|
+
var jsoneditor = {
|
|
4
|
+
'JSONEditor': JSONEditor,
|
|
5
|
+
'JSONFormatter': function () {
|
|
6
|
+
throw new Error('JSONFormatter is deprecated. ' +
|
|
7
|
+
'Use JSONEditor with mode "text" or "code" instead');
|
|
8
|
+
},
|
|
9
|
+
'util': util
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* load jsoneditor.css
|
|
14
|
+
*/
|
|
15
|
+
var loadCss = function () {
|
|
16
|
+
// get the script location, and built the css file name from the js file name
|
|
17
|
+
// http://stackoverflow.com/a/2161748/1262753
|
|
18
|
+
var scripts = document.getElementsByTagName('script');
|
|
19
|
+
var jsFile = scripts[scripts.length-1].src.split('?')[0];
|
|
20
|
+
var cssFile = jsFile.substring(0, jsFile.length - 2) + 'css';
|
|
21
|
+
|
|
22
|
+
// load css
|
|
23
|
+
var link = document.createElement('link');
|
|
24
|
+
link.type = 'text/css';
|
|
25
|
+
link.rel = 'stylesheet';
|
|
26
|
+
link.href = cssFile;
|
|
27
|
+
document.getElementsByTagName('head')[0].appendChild(link);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* CommonJS module exports
|
|
32
|
+
*/
|
|
33
|
+
if (typeof(module) != 'undefined' && typeof(exports) != 'undefined') {
|
|
34
|
+
loadCss();
|
|
35
|
+
module.exports = exports = jsoneditor;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* AMD module exports
|
|
40
|
+
*/
|
|
41
|
+
if (typeof(require) != 'undefined' && typeof(define) != 'undefined') {
|
|
42
|
+
define(function () {
|
|
43
|
+
loadCss();
|
|
44
|
+
return jsoneditor;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// attach the module to the window, load as a regular javascript file
|
|
49
|
+
window['jsoneditor'] = jsoneditor;
|
|
50
|
+
}
|
|
@@ -0,0 +1,2864 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @constructor Node
|
|
3
|
+
* Create a new Node
|
|
4
|
+
* @param {TreeEditor} editor
|
|
5
|
+
* @param {Object} [params] Can contain parameters:
|
|
6
|
+
* {string} field
|
|
7
|
+
* {boolean} fieldEditable
|
|
8
|
+
* {*} value
|
|
9
|
+
* {String} type Can have values 'auto', 'array',
|
|
10
|
+
* 'object', or 'string'.
|
|
11
|
+
*/
|
|
12
|
+
function Node (editor, params) {
|
|
13
|
+
/** @type {TreeEditor} */
|
|
14
|
+
this.editor = editor;
|
|
15
|
+
this.dom = {};
|
|
16
|
+
this.expanded = false;
|
|
17
|
+
|
|
18
|
+
if(params && (params instanceof Object)) {
|
|
19
|
+
this.setField(params.field, params.fieldEditable);
|
|
20
|
+
this.setValue(params.value, params.type);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
this.setField('');
|
|
24
|
+
this.setValue(null);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Set parent node
|
|
30
|
+
* @param {Node} parent
|
|
31
|
+
*/
|
|
32
|
+
Node.prototype.setParent = function(parent) {
|
|
33
|
+
this.parent = parent;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Set field
|
|
38
|
+
* @param {String} field
|
|
39
|
+
* @param {boolean} [fieldEditable]
|
|
40
|
+
*/
|
|
41
|
+
Node.prototype.setField = function(field, fieldEditable) {
|
|
42
|
+
this.field = field;
|
|
43
|
+
this.fieldEditable = (fieldEditable == true);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get field
|
|
48
|
+
* @return {String}
|
|
49
|
+
*/
|
|
50
|
+
Node.prototype.getField = function() {
|
|
51
|
+
if (this.field === undefined) {
|
|
52
|
+
this._getDomField();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return this.field;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Set value. Value is a JSON structure or an element String, Boolean, etc.
|
|
60
|
+
* @param {*} value
|
|
61
|
+
* @param {String} [type] Specify the type of the value. Can be 'auto',
|
|
62
|
+
* 'array', 'object', or 'string'
|
|
63
|
+
*/
|
|
64
|
+
Node.prototype.setValue = function(value, type) {
|
|
65
|
+
var childValue, child;
|
|
66
|
+
|
|
67
|
+
// first clear all current childs (if any)
|
|
68
|
+
var childs = this.childs;
|
|
69
|
+
if (childs) {
|
|
70
|
+
while (childs.length) {
|
|
71
|
+
this.removeChild(childs[0]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// TODO: remove the DOM of this Node
|
|
76
|
+
|
|
77
|
+
this.type = this._getType(value);
|
|
78
|
+
|
|
79
|
+
// check if type corresponds with the provided type
|
|
80
|
+
if (type && type != this.type) {
|
|
81
|
+
if (type == 'string' && this.type == 'auto') {
|
|
82
|
+
this.type = type;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
throw new Error('Type mismatch: ' +
|
|
86
|
+
'cannot cast value of type "' + this.type +
|
|
87
|
+
' to the specified type "' + type + '"');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.type == 'array') {
|
|
92
|
+
// array
|
|
93
|
+
this.childs = [];
|
|
94
|
+
for (var i = 0, iMax = value.length; i < iMax; i++) {
|
|
95
|
+
childValue = value[i];
|
|
96
|
+
if (childValue !== undefined && !(childValue instanceof Function)) {
|
|
97
|
+
// ignore undefined and functions
|
|
98
|
+
child = new Node(this.editor, {
|
|
99
|
+
'value': childValue
|
|
100
|
+
});
|
|
101
|
+
this.appendChild(child);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
this.value = '';
|
|
105
|
+
}
|
|
106
|
+
else if (this.type == 'object') {
|
|
107
|
+
// object
|
|
108
|
+
this.childs = [];
|
|
109
|
+
for (var childField in value) {
|
|
110
|
+
if (value.hasOwnProperty(childField)) {
|
|
111
|
+
childValue = value[childField];
|
|
112
|
+
if (childValue !== undefined && !(childValue instanceof Function)) {
|
|
113
|
+
// ignore undefined and functions
|
|
114
|
+
child = new Node(this.editor, {
|
|
115
|
+
'field': childField,
|
|
116
|
+
'value': childValue
|
|
117
|
+
});
|
|
118
|
+
this.appendChild(child);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
this.value = '';
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// value
|
|
126
|
+
this.childs = undefined;
|
|
127
|
+
this.value = value;
|
|
128
|
+
/* TODO
|
|
129
|
+
if (typeof(value) == 'string') {
|
|
130
|
+
var escValue = JSON.stringify(value);
|
|
131
|
+
this.value = escValue.substring(1, escValue.length - 1);
|
|
132
|
+
util.log('check', value, this.value);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
this.value = value;
|
|
136
|
+
}
|
|
137
|
+
*/
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get value. Value is a JSON structure
|
|
143
|
+
* @return {*} value
|
|
144
|
+
*/
|
|
145
|
+
Node.prototype.getValue = function() {
|
|
146
|
+
//var childs, i, iMax;
|
|
147
|
+
|
|
148
|
+
if (this.type == 'array') {
|
|
149
|
+
var arr = [];
|
|
150
|
+
this.childs.forEach (function (child) {
|
|
151
|
+
arr.push(child.getValue());
|
|
152
|
+
});
|
|
153
|
+
return arr;
|
|
154
|
+
}
|
|
155
|
+
else if (this.type == 'object') {
|
|
156
|
+
var obj = {};
|
|
157
|
+
this.childs.forEach (function (child) {
|
|
158
|
+
obj[child.getField()] = child.getValue();
|
|
159
|
+
});
|
|
160
|
+
return obj;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
if (this.value === undefined) {
|
|
164
|
+
this._getDomValue();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return this.value;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get the nesting level of this node
|
|
173
|
+
* @return {Number} level
|
|
174
|
+
*/
|
|
175
|
+
Node.prototype.getLevel = function() {
|
|
176
|
+
return (this.parent ? this.parent.getLevel() + 1 : 0);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create a clone of a node
|
|
181
|
+
* The complete state of a clone is copied, including whether it is expanded or
|
|
182
|
+
* not. The DOM elements are not cloned.
|
|
183
|
+
* @return {Node} clone
|
|
184
|
+
*/
|
|
185
|
+
Node.prototype.clone = function() {
|
|
186
|
+
var clone = new Node(this.editor);
|
|
187
|
+
clone.type = this.type;
|
|
188
|
+
clone.field = this.field;
|
|
189
|
+
clone.fieldInnerText = this.fieldInnerText;
|
|
190
|
+
clone.fieldEditable = this.fieldEditable;
|
|
191
|
+
clone.value = this.value;
|
|
192
|
+
clone.valueInnerText = this.valueInnerText;
|
|
193
|
+
clone.expanded = this.expanded;
|
|
194
|
+
|
|
195
|
+
if (this.childs) {
|
|
196
|
+
// an object or array
|
|
197
|
+
var cloneChilds = [];
|
|
198
|
+
this.childs.forEach(function (child) {
|
|
199
|
+
var childClone = child.clone();
|
|
200
|
+
childClone.setParent(clone);
|
|
201
|
+
cloneChilds.push(childClone);
|
|
202
|
+
});
|
|
203
|
+
clone.childs = cloneChilds;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
// a value
|
|
207
|
+
clone.childs = undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return clone;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Expand this node and optionally its childs.
|
|
215
|
+
* @param {boolean} [recurse] Optional recursion, true by default. When
|
|
216
|
+
* true, all childs will be expanded recursively
|
|
217
|
+
*/
|
|
218
|
+
Node.prototype.expand = function(recurse) {
|
|
219
|
+
if (!this.childs) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// set this node expanded
|
|
224
|
+
this.expanded = true;
|
|
225
|
+
if (this.dom.expand) {
|
|
226
|
+
this.dom.expand.className = 'expanded';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
this.showChilds();
|
|
230
|
+
|
|
231
|
+
if (recurse != false) {
|
|
232
|
+
this.childs.forEach(function (child) {
|
|
233
|
+
child.expand(recurse);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Collapse this node and optionally its childs.
|
|
240
|
+
* @param {boolean} [recurse] Optional recursion, true by default. When
|
|
241
|
+
* true, all childs will be collapsed recursively
|
|
242
|
+
*/
|
|
243
|
+
Node.prototype.collapse = function(recurse) {
|
|
244
|
+
if (!this.childs) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
this.hideChilds();
|
|
249
|
+
|
|
250
|
+
// collapse childs in case of recurse
|
|
251
|
+
if (recurse != false) {
|
|
252
|
+
this.childs.forEach(function (child) {
|
|
253
|
+
child.collapse(recurse);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// make this node collapsed
|
|
259
|
+
if (this.dom.expand) {
|
|
260
|
+
this.dom.expand.className = 'collapsed';
|
|
261
|
+
}
|
|
262
|
+
this.expanded = false;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Recursively show all childs when they are expanded
|
|
267
|
+
*/
|
|
268
|
+
Node.prototype.showChilds = function() {
|
|
269
|
+
var childs = this.childs;
|
|
270
|
+
if (!childs) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (!this.expanded) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
var tr = this.dom.tr;
|
|
278
|
+
var table = tr ? tr.parentNode : undefined;
|
|
279
|
+
if (table) {
|
|
280
|
+
// show row with append button
|
|
281
|
+
var append = this.getAppend();
|
|
282
|
+
var nextTr = tr.nextSibling;
|
|
283
|
+
if (nextTr) {
|
|
284
|
+
table.insertBefore(append, nextTr);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
table.appendChild(append);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// show childs
|
|
291
|
+
this.childs.forEach(function (child) {
|
|
292
|
+
table.insertBefore(child.getDom(), append);
|
|
293
|
+
child.showChilds();
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Hide the node with all its childs
|
|
300
|
+
*/
|
|
301
|
+
Node.prototype.hide = function() {
|
|
302
|
+
var tr = this.dom.tr;
|
|
303
|
+
var table = tr ? tr.parentNode : undefined;
|
|
304
|
+
if (table) {
|
|
305
|
+
table.removeChild(tr);
|
|
306
|
+
}
|
|
307
|
+
this.hideChilds();
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Recursively hide all childs
|
|
313
|
+
*/
|
|
314
|
+
Node.prototype.hideChilds = function() {
|
|
315
|
+
var childs = this.childs;
|
|
316
|
+
if (!childs) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (!this.expanded) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// hide append row
|
|
324
|
+
var append = this.getAppend();
|
|
325
|
+
if (append.parentNode) {
|
|
326
|
+
append.parentNode.removeChild(append);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// hide childs
|
|
330
|
+
this.childs.forEach(function (child) {
|
|
331
|
+
child.hide();
|
|
332
|
+
});
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Add a new child to the node.
|
|
338
|
+
* Only applicable when Node value is of type array or object
|
|
339
|
+
* @param {Node} node
|
|
340
|
+
*/
|
|
341
|
+
Node.prototype.appendChild = function(node) {
|
|
342
|
+
if (this._hasChilds()) {
|
|
343
|
+
// adjust the link to the parent
|
|
344
|
+
node.setParent(this);
|
|
345
|
+
node.fieldEditable = (this.type == 'object');
|
|
346
|
+
if (this.type == 'array') {
|
|
347
|
+
node.index = this.childs.length;
|
|
348
|
+
}
|
|
349
|
+
this.childs.push(node);
|
|
350
|
+
|
|
351
|
+
if (this.expanded) {
|
|
352
|
+
// insert into the DOM, before the appendRow
|
|
353
|
+
var newTr = node.getDom();
|
|
354
|
+
var appendTr = this.getAppend();
|
|
355
|
+
var table = appendTr ? appendTr.parentNode : undefined;
|
|
356
|
+
if (appendTr && table) {
|
|
357
|
+
table.insertBefore(newTr, appendTr);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
node.showChilds();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
this.updateDom({'updateIndexes': true});
|
|
364
|
+
node.updateDom({'recurse': true});
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Move a node from its current parent to this node
|
|
371
|
+
* Only applicable when Node value is of type array or object
|
|
372
|
+
* @param {Node} node
|
|
373
|
+
* @param {Node} beforeNode
|
|
374
|
+
*/
|
|
375
|
+
Node.prototype.moveBefore = function(node, beforeNode) {
|
|
376
|
+
if (this._hasChilds()) {
|
|
377
|
+
// create a temporary row, to prevent the scroll position from jumping
|
|
378
|
+
// when removing the node
|
|
379
|
+
var tbody = (this.dom.tr) ? this.dom.tr.parentNode : undefined;
|
|
380
|
+
if (tbody) {
|
|
381
|
+
var trTemp = document.createElement('tr');
|
|
382
|
+
trTemp.style.height = tbody.clientHeight + 'px';
|
|
383
|
+
tbody.appendChild(trTemp);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (node.parent) {
|
|
387
|
+
node.parent.removeChild(node);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (beforeNode instanceof AppendNode) {
|
|
391
|
+
this.appendChild(node);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
this.insertBefore(node, beforeNode);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (tbody) {
|
|
398
|
+
tbody.removeChild(trTemp);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Move a node from its current parent to this node
|
|
405
|
+
* Only applicable when Node value is of type array or object.
|
|
406
|
+
* If index is out of range, the node will be appended to the end
|
|
407
|
+
* @param {Node} node
|
|
408
|
+
* @param {Number} index
|
|
409
|
+
*/
|
|
410
|
+
Node.prototype.moveTo = function (node, index) {
|
|
411
|
+
if (node.parent == this) {
|
|
412
|
+
// same parent
|
|
413
|
+
var currentIndex = this.childs.indexOf(node);
|
|
414
|
+
if (currentIndex < index) {
|
|
415
|
+
// compensate the index for removal of the node itself
|
|
416
|
+
index++;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
var beforeNode = this.childs[index] || this.append;
|
|
421
|
+
this.moveBefore(node, beforeNode);
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Insert a new child before a given node
|
|
426
|
+
* Only applicable when Node value is of type array or object
|
|
427
|
+
* @param {Node} node
|
|
428
|
+
* @param {Node} beforeNode
|
|
429
|
+
*/
|
|
430
|
+
Node.prototype.insertBefore = function(node, beforeNode) {
|
|
431
|
+
if (this._hasChilds()) {
|
|
432
|
+
if (beforeNode == this.append) {
|
|
433
|
+
// append to the child nodes
|
|
434
|
+
|
|
435
|
+
// adjust the link to the parent
|
|
436
|
+
node.setParent(this);
|
|
437
|
+
node.fieldEditable = (this.type == 'object');
|
|
438
|
+
this.childs.push(node);
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
// insert before a child node
|
|
442
|
+
var index = this.childs.indexOf(beforeNode);
|
|
443
|
+
if (index == -1) {
|
|
444
|
+
throw new Error('Node not found');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// adjust the link to the parent
|
|
448
|
+
node.setParent(this);
|
|
449
|
+
node.fieldEditable = (this.type == 'object');
|
|
450
|
+
this.childs.splice(index, 0, node);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (this.expanded) {
|
|
454
|
+
// insert into the DOM
|
|
455
|
+
var newTr = node.getDom();
|
|
456
|
+
var nextTr = beforeNode.getDom();
|
|
457
|
+
var table = nextTr ? nextTr.parentNode : undefined;
|
|
458
|
+
if (nextTr && table) {
|
|
459
|
+
table.insertBefore(newTr, nextTr);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
node.showChilds();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
this.updateDom({'updateIndexes': true});
|
|
466
|
+
node.updateDom({'recurse': true});
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Insert a new child before a given node
|
|
472
|
+
* Only applicable when Node value is of type array or object
|
|
473
|
+
* @param {Node} node
|
|
474
|
+
* @param {Node} afterNode
|
|
475
|
+
*/
|
|
476
|
+
Node.prototype.insertAfter = function(node, afterNode) {
|
|
477
|
+
if (this._hasChilds()) {
|
|
478
|
+
var index = this.childs.indexOf(afterNode);
|
|
479
|
+
var beforeNode = this.childs[index + 1];
|
|
480
|
+
if (beforeNode) {
|
|
481
|
+
this.insertBefore(node, beforeNode);
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
this.appendChild(node);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Search in this node
|
|
491
|
+
* The node will be expanded when the text is found one of its childs, else
|
|
492
|
+
* it will be collapsed. Searches are case insensitive.
|
|
493
|
+
* @param {String} text
|
|
494
|
+
* @return {Node[]} results Array with nodes containing the search text
|
|
495
|
+
*/
|
|
496
|
+
Node.prototype.search = function(text) {
|
|
497
|
+
var results = [];
|
|
498
|
+
var index;
|
|
499
|
+
var search = text ? text.toLowerCase() : undefined;
|
|
500
|
+
|
|
501
|
+
// delete old search data
|
|
502
|
+
delete this.searchField;
|
|
503
|
+
delete this.searchValue;
|
|
504
|
+
|
|
505
|
+
// search in field
|
|
506
|
+
if (this.field != undefined) {
|
|
507
|
+
var field = String(this.field).toLowerCase();
|
|
508
|
+
index = field.indexOf(search);
|
|
509
|
+
if (index != -1) {
|
|
510
|
+
this.searchField = true;
|
|
511
|
+
results.push({
|
|
512
|
+
'node': this,
|
|
513
|
+
'elem': 'field'
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// update dom
|
|
518
|
+
this._updateDomField();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// search in value
|
|
522
|
+
if (this._hasChilds()) {
|
|
523
|
+
// array, object
|
|
524
|
+
|
|
525
|
+
// search the nodes childs
|
|
526
|
+
if (this.childs) {
|
|
527
|
+
var childResults = [];
|
|
528
|
+
this.childs.forEach(function (child) {
|
|
529
|
+
childResults = childResults.concat(child.search(text));
|
|
530
|
+
});
|
|
531
|
+
results = results.concat(childResults);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// update dom
|
|
535
|
+
if (search != undefined) {
|
|
536
|
+
var recurse = false;
|
|
537
|
+
if (childResults.length == 0) {
|
|
538
|
+
this.collapse(recurse);
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
this.expand(recurse);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
// string, auto
|
|
547
|
+
if (this.value != undefined ) {
|
|
548
|
+
var value = String(this.value).toLowerCase();
|
|
549
|
+
index = value.indexOf(search);
|
|
550
|
+
if (index != -1) {
|
|
551
|
+
this.searchValue = true;
|
|
552
|
+
results.push({
|
|
553
|
+
'node': this,
|
|
554
|
+
'elem': 'value'
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// update dom
|
|
560
|
+
this._updateDomValue();
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return results;
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Move the scroll position such that this node is in the visible area.
|
|
568
|
+
* The node will not get the focus
|
|
569
|
+
* @param {function(boolean)} [callback]
|
|
570
|
+
*/
|
|
571
|
+
Node.prototype.scrollTo = function(callback) {
|
|
572
|
+
if (!this.dom.tr || !this.dom.tr.parentNode) {
|
|
573
|
+
// if the node is not visible, expand its parents
|
|
574
|
+
var parent = this.parent;
|
|
575
|
+
var recurse = false;
|
|
576
|
+
while (parent) {
|
|
577
|
+
parent.expand(recurse);
|
|
578
|
+
parent = parent.parent;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (this.dom.tr && this.dom.tr.parentNode) {
|
|
583
|
+
this.editor.scrollTo(this.dom.tr.offsetTop, callback);
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
// stores the element name currently having the focus
|
|
589
|
+
Node.focusElement = undefined;
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Set focus to this node
|
|
593
|
+
* @param {String} [elementName] The field name of the element to get the
|
|
594
|
+
* focus available values: 'drag', 'menu',
|
|
595
|
+
* 'expand', 'field', 'value' (default)
|
|
596
|
+
*/
|
|
597
|
+
Node.prototype.focus = function(elementName) {
|
|
598
|
+
Node.focusElement = elementName;
|
|
599
|
+
|
|
600
|
+
if (this.dom.tr && this.dom.tr.parentNode) {
|
|
601
|
+
var dom = this.dom;
|
|
602
|
+
|
|
603
|
+
switch (elementName) {
|
|
604
|
+
case 'drag':
|
|
605
|
+
if (dom.drag) {
|
|
606
|
+
dom.drag.focus();
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
dom.menu.focus();
|
|
610
|
+
}
|
|
611
|
+
break;
|
|
612
|
+
|
|
613
|
+
case 'menu':
|
|
614
|
+
dom.menu.focus();
|
|
615
|
+
break;
|
|
616
|
+
|
|
617
|
+
case 'expand':
|
|
618
|
+
if (this._hasChilds()) {
|
|
619
|
+
dom.expand.focus();
|
|
620
|
+
}
|
|
621
|
+
else if (dom.field && this.fieldEditable) {
|
|
622
|
+
dom.field.focus();
|
|
623
|
+
util.selectContentEditable(dom.field);
|
|
624
|
+
}
|
|
625
|
+
else if (dom.value && !this._hasChilds()) {
|
|
626
|
+
dom.value.focus();
|
|
627
|
+
util.selectContentEditable(dom.value);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
dom.menu.focus();
|
|
631
|
+
}
|
|
632
|
+
break;
|
|
633
|
+
|
|
634
|
+
case 'field':
|
|
635
|
+
if (dom.field && this.fieldEditable) {
|
|
636
|
+
dom.field.focus();
|
|
637
|
+
util.selectContentEditable(dom.field);
|
|
638
|
+
}
|
|
639
|
+
else if (dom.value && !this._hasChilds()) {
|
|
640
|
+
dom.value.focus();
|
|
641
|
+
util.selectContentEditable(dom.value);
|
|
642
|
+
}
|
|
643
|
+
else if (this._hasChilds()) {
|
|
644
|
+
dom.expand.focus();
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
dom.menu.focus();
|
|
648
|
+
}
|
|
649
|
+
break;
|
|
650
|
+
|
|
651
|
+
case 'value':
|
|
652
|
+
default:
|
|
653
|
+
if (dom.value && !this._hasChilds()) {
|
|
654
|
+
dom.value.focus();
|
|
655
|
+
util.selectContentEditable(dom.value);
|
|
656
|
+
}
|
|
657
|
+
else if (dom.field && this.fieldEditable) {
|
|
658
|
+
dom.field.focus();
|
|
659
|
+
util.selectContentEditable(dom.field);
|
|
660
|
+
}
|
|
661
|
+
else if (this._hasChilds()) {
|
|
662
|
+
dom.expand.focus();
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
dom.menu.focus();
|
|
666
|
+
}
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Select all text in an editable div after a delay of 0 ms
|
|
674
|
+
* @param {Element} editableDiv
|
|
675
|
+
*/
|
|
676
|
+
Node.select = function(editableDiv) {
|
|
677
|
+
setTimeout(function () {
|
|
678
|
+
util.selectContentEditable(editableDiv);
|
|
679
|
+
}, 0);
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Update the values from the DOM field and value of this node
|
|
684
|
+
*/
|
|
685
|
+
Node.prototype.blur = function() {
|
|
686
|
+
// retrieve the actual field and value from the DOM.
|
|
687
|
+
this._getDomValue(false);
|
|
688
|
+
this._getDomField(false);
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Duplicate given child node
|
|
693
|
+
* new structure will be added right before the cloned node
|
|
694
|
+
* @param {Node} node the childNode to be duplicated
|
|
695
|
+
* @return {Node} clone the clone of the node
|
|
696
|
+
* @private
|
|
697
|
+
*/
|
|
698
|
+
Node.prototype._duplicate = function(node) {
|
|
699
|
+
var clone = node.clone();
|
|
700
|
+
|
|
701
|
+
/* TODO: adjust the field name (to prevent equal field names)
|
|
702
|
+
if (this.type == 'object') {
|
|
703
|
+
}
|
|
704
|
+
*/
|
|
705
|
+
|
|
706
|
+
this.insertAfter(clone, node);
|
|
707
|
+
|
|
708
|
+
return clone;
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Check if given node is a child. The method will check recursively to find
|
|
713
|
+
* this node.
|
|
714
|
+
* @param {Node} node
|
|
715
|
+
* @return {boolean} containsNode
|
|
716
|
+
*/
|
|
717
|
+
Node.prototype.containsNode = function(node) {
|
|
718
|
+
if (this == node) {
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
var childs = this.childs;
|
|
723
|
+
if (childs) {
|
|
724
|
+
// TODO: use the js5 Array.some() here?
|
|
725
|
+
for (var i = 0, iMax = childs.length; i < iMax; i++) {
|
|
726
|
+
if (childs[i].containsNode(node)) {
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return false;
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Move given node into this node
|
|
737
|
+
* @param {Node} node the childNode to be moved
|
|
738
|
+
* @param {Node} beforeNode node will be inserted before given
|
|
739
|
+
* node. If no beforeNode is given,
|
|
740
|
+
* the node is appended at the end
|
|
741
|
+
* @private
|
|
742
|
+
*/
|
|
743
|
+
Node.prototype._move = function(node, beforeNode) {
|
|
744
|
+
if (node == beforeNode) {
|
|
745
|
+
// nothing to do...
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// check if this node is not a child of the node to be moved here
|
|
750
|
+
if (node.containsNode(this)) {
|
|
751
|
+
throw new Error('Cannot move a field into a child of itself');
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// remove the original node
|
|
755
|
+
if (node.parent) {
|
|
756
|
+
node.parent.removeChild(node);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// create a clone of the node
|
|
760
|
+
var clone = node.clone();
|
|
761
|
+
node.clearDom();
|
|
762
|
+
|
|
763
|
+
// insert or append the node
|
|
764
|
+
if (beforeNode) {
|
|
765
|
+
this.insertBefore(clone, beforeNode);
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
this.appendChild(clone);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/* TODO: adjust the field name (to prevent equal field names)
|
|
772
|
+
if (this.type == 'object') {
|
|
773
|
+
}
|
|
774
|
+
*/
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Remove a child from the node.
|
|
779
|
+
* Only applicable when Node value is of type array or object
|
|
780
|
+
* @param {Node} node The child node to be removed;
|
|
781
|
+
* @return {Node | undefined} node The removed node on success,
|
|
782
|
+
* else undefined
|
|
783
|
+
*/
|
|
784
|
+
Node.prototype.removeChild = function(node) {
|
|
785
|
+
if (this.childs) {
|
|
786
|
+
var index = this.childs.indexOf(node);
|
|
787
|
+
|
|
788
|
+
if (index != -1) {
|
|
789
|
+
node.hide();
|
|
790
|
+
|
|
791
|
+
// delete old search results
|
|
792
|
+
delete node.searchField;
|
|
793
|
+
delete node.searchValue;
|
|
794
|
+
|
|
795
|
+
var removedNode = this.childs.splice(index, 1)[0];
|
|
796
|
+
|
|
797
|
+
this.updateDom({'updateIndexes': true});
|
|
798
|
+
|
|
799
|
+
return removedNode;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return undefined;
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Remove a child node node from this node
|
|
808
|
+
* This method is equal to Node.removeChild, except that _remove firex an
|
|
809
|
+
* onChange event.
|
|
810
|
+
* @param {Node} node
|
|
811
|
+
* @private
|
|
812
|
+
*/
|
|
813
|
+
Node.prototype._remove = function (node) {
|
|
814
|
+
this.removeChild(node);
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Change the type of the value of this Node
|
|
819
|
+
* @param {String} newType
|
|
820
|
+
*/
|
|
821
|
+
Node.prototype.changeType = function (newType) {
|
|
822
|
+
var oldType = this.type;
|
|
823
|
+
|
|
824
|
+
if (oldType == newType) {
|
|
825
|
+
// type is not changed
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if ((newType == 'string' || newType == 'auto') &&
|
|
830
|
+
(oldType == 'string' || oldType == 'auto')) {
|
|
831
|
+
// this is an easy change
|
|
832
|
+
this.type = newType;
|
|
833
|
+
}
|
|
834
|
+
else {
|
|
835
|
+
// change from array to object, or from string/auto to object/array
|
|
836
|
+
var table = this.dom.tr ? this.dom.tr.parentNode : undefined;
|
|
837
|
+
var lastTr;
|
|
838
|
+
if (this.expanded) {
|
|
839
|
+
lastTr = this.getAppend();
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
lastTr = this.getDom();
|
|
843
|
+
}
|
|
844
|
+
var nextTr = (lastTr && lastTr.parentNode) ? lastTr.nextSibling : undefined;
|
|
845
|
+
|
|
846
|
+
// hide current field and all its childs
|
|
847
|
+
this.hide();
|
|
848
|
+
this.clearDom();
|
|
849
|
+
|
|
850
|
+
// adjust the field and the value
|
|
851
|
+
this.type = newType;
|
|
852
|
+
|
|
853
|
+
// adjust childs
|
|
854
|
+
if (newType == 'object') {
|
|
855
|
+
if (!this.childs) {
|
|
856
|
+
this.childs = [];
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
this.childs.forEach(function (child, index) {
|
|
860
|
+
child.clearDom();
|
|
861
|
+
delete child.index;
|
|
862
|
+
child.fieldEditable = true;
|
|
863
|
+
if (child.field == undefined) {
|
|
864
|
+
child.field = '';
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
if (oldType == 'string' || oldType == 'auto') {
|
|
869
|
+
this.expanded = true;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
else if (newType == 'array') {
|
|
873
|
+
if (!this.childs) {
|
|
874
|
+
this.childs = [];
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
this.childs.forEach(function (child, index) {
|
|
878
|
+
child.clearDom();
|
|
879
|
+
child.fieldEditable = false;
|
|
880
|
+
child.index = index;
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
if (oldType == 'string' || oldType == 'auto') {
|
|
884
|
+
this.expanded = true;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
this.expanded = false;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// create new DOM
|
|
892
|
+
if (table) {
|
|
893
|
+
if (nextTr) {
|
|
894
|
+
table.insertBefore(this.getDom(), nextTr);
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
table.appendChild(this.getDom());
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
this.showChilds();
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
if (newType == 'auto' || newType == 'string') {
|
|
904
|
+
// cast value to the correct type
|
|
905
|
+
if (newType == 'string') {
|
|
906
|
+
this.value = String(this.value);
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
this.value = this._stringCast(String(this.value));
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
this.focus();
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
this.updateDom({'updateIndexes': true});
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Retrieve value from DOM
|
|
920
|
+
* @param {boolean} [silent] If true (default), no errors will be thrown in
|
|
921
|
+
* case of invalid data
|
|
922
|
+
* @private
|
|
923
|
+
*/
|
|
924
|
+
Node.prototype._getDomValue = function(silent) {
|
|
925
|
+
if (this.dom.value && this.type != 'array' && this.type != 'object') {
|
|
926
|
+
this.valueInnerText = util.getInnerText(this.dom.value);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (this.valueInnerText != undefined) {
|
|
930
|
+
try {
|
|
931
|
+
// retrieve the value
|
|
932
|
+
var value;
|
|
933
|
+
if (this.type == 'string') {
|
|
934
|
+
value = this._unescapeHTML(this.valueInnerText);
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
var str = this._unescapeHTML(this.valueInnerText);
|
|
938
|
+
value = this._stringCast(str);
|
|
939
|
+
}
|
|
940
|
+
if (value !== this.value) {
|
|
941
|
+
var oldValue = this.value;
|
|
942
|
+
this.value = value;
|
|
943
|
+
this.editor._onAction('editValue', {
|
|
944
|
+
'node': this,
|
|
945
|
+
'oldValue': oldValue,
|
|
946
|
+
'newValue': value,
|
|
947
|
+
'oldSelection': this.editor.selection,
|
|
948
|
+
'newSelection': this.editor.getSelection()
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
catch (err) {
|
|
953
|
+
this.value = undefined;
|
|
954
|
+
// TODO: sent an action with the new, invalid value?
|
|
955
|
+
if (silent != true) {
|
|
956
|
+
throw err;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Update dom value:
|
|
964
|
+
* - the text color of the value, depending on the type of the value
|
|
965
|
+
* - the height of the field, depending on the width
|
|
966
|
+
* - background color in case it is empty
|
|
967
|
+
* @private
|
|
968
|
+
*/
|
|
969
|
+
Node.prototype._updateDomValue = function () {
|
|
970
|
+
var domValue = this.dom.value;
|
|
971
|
+
if (domValue) {
|
|
972
|
+
// set text color depending on value type
|
|
973
|
+
// TODO: put colors in css
|
|
974
|
+
var v = this.value;
|
|
975
|
+
var t = (this.type == 'auto') ? typeof(v) : this.type;
|
|
976
|
+
var isUrl = (t == 'string' && util.isUrl(v));
|
|
977
|
+
var color = '';
|
|
978
|
+
if (isUrl && !this.editor.mode.edit) {
|
|
979
|
+
color = '';
|
|
980
|
+
}
|
|
981
|
+
else if (t == 'string') {
|
|
982
|
+
color = 'green';
|
|
983
|
+
}
|
|
984
|
+
else if (t == 'number') {
|
|
985
|
+
color = 'red';
|
|
986
|
+
}
|
|
987
|
+
else if (t == 'boolean') {
|
|
988
|
+
color = 'orange';
|
|
989
|
+
}
|
|
990
|
+
else if (this._hasChilds()) {
|
|
991
|
+
// note: typeof(null)=="object", therefore check this.type instead of t
|
|
992
|
+
color = '';
|
|
993
|
+
}
|
|
994
|
+
else if (v === null) {
|
|
995
|
+
color = '#004ED0'; // blue
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
// invalid value
|
|
999
|
+
color = 'black';
|
|
1000
|
+
}
|
|
1001
|
+
domValue.style.color = color;
|
|
1002
|
+
|
|
1003
|
+
// make backgound color lightgray when empty
|
|
1004
|
+
var isEmpty = (String(this.value) == '' && this.type != 'array' && this.type != 'object');
|
|
1005
|
+
if (isEmpty) {
|
|
1006
|
+
util.addClassName(domValue, 'empty');
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
util.removeClassName(domValue, 'empty');
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// underline url
|
|
1013
|
+
if (isUrl) {
|
|
1014
|
+
util.addClassName(domValue, 'url');
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
util.removeClassName(domValue, 'url');
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// update title
|
|
1021
|
+
if (t == 'array' || t == 'object') {
|
|
1022
|
+
var count = this.childs ? this.childs.length : 0;
|
|
1023
|
+
domValue.title = this.type + ' containing ' + count + ' items';
|
|
1024
|
+
}
|
|
1025
|
+
else if (t == 'string' && util.isUrl(v)) {
|
|
1026
|
+
if (this.editor.mode.edit) {
|
|
1027
|
+
domValue.title = 'Ctrl+Click or Ctrl+Enter to open url in new window';
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
else {
|
|
1031
|
+
domValue.title = '';
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// highlight when there is a search result
|
|
1035
|
+
if (this.searchValueActive) {
|
|
1036
|
+
util.addClassName(domValue, 'highlight-active');
|
|
1037
|
+
}
|
|
1038
|
+
else {
|
|
1039
|
+
util.removeClassName(domValue, 'highlight-active');
|
|
1040
|
+
}
|
|
1041
|
+
if (this.searchValue) {
|
|
1042
|
+
util.addClassName(domValue, 'highlight');
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
util.removeClassName(domValue, 'highlight');
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// strip formatting from the contents of the editable div
|
|
1049
|
+
util.stripFormatting(domValue);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Update dom field:
|
|
1055
|
+
* - the text color of the field, depending on the text
|
|
1056
|
+
* - the height of the field, depending on the width
|
|
1057
|
+
* - background color in case it is empty
|
|
1058
|
+
* @private
|
|
1059
|
+
*/
|
|
1060
|
+
Node.prototype._updateDomField = function () {
|
|
1061
|
+
var domField = this.dom.field;
|
|
1062
|
+
if (domField) {
|
|
1063
|
+
// make backgound color lightgray when empty
|
|
1064
|
+
var isEmpty = (String(this.field) == '' && this.parent.type != 'array');
|
|
1065
|
+
if (isEmpty) {
|
|
1066
|
+
util.addClassName(domField, 'empty');
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
util.removeClassName(domField, 'empty');
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// highlight when there is a search result
|
|
1073
|
+
if (this.searchFieldActive) {
|
|
1074
|
+
util.addClassName(domField, 'highlight-active');
|
|
1075
|
+
}
|
|
1076
|
+
else {
|
|
1077
|
+
util.removeClassName(domField, 'highlight-active');
|
|
1078
|
+
}
|
|
1079
|
+
if (this.searchField) {
|
|
1080
|
+
util.addClassName(domField, 'highlight');
|
|
1081
|
+
}
|
|
1082
|
+
else {
|
|
1083
|
+
util.removeClassName(domField, 'highlight');
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// strip formatting from the contents of the editable div
|
|
1087
|
+
util.stripFormatting(domField);
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Retrieve field from DOM
|
|
1093
|
+
* @param {boolean} [silent] If true (default), no errors will be thrown in
|
|
1094
|
+
* case of invalid data
|
|
1095
|
+
* @private
|
|
1096
|
+
*/
|
|
1097
|
+
Node.prototype._getDomField = function(silent) {
|
|
1098
|
+
if (this.dom.field && this.fieldEditable) {
|
|
1099
|
+
this.fieldInnerText = util.getInnerText(this.dom.field);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
if (this.fieldInnerText != undefined) {
|
|
1103
|
+
try {
|
|
1104
|
+
var field = this._unescapeHTML(this.fieldInnerText);
|
|
1105
|
+
|
|
1106
|
+
if (field !== this.field) {
|
|
1107
|
+
var oldField = this.field;
|
|
1108
|
+
this.field = field;
|
|
1109
|
+
this.editor._onAction('editField', {
|
|
1110
|
+
'node': this,
|
|
1111
|
+
'oldValue': oldField,
|
|
1112
|
+
'newValue': field,
|
|
1113
|
+
'oldSelection': this.editor.selection,
|
|
1114
|
+
'newSelection': this.editor.getSelection()
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
catch (err) {
|
|
1119
|
+
this.field = undefined;
|
|
1120
|
+
// TODO: sent an action here, with the new, invalid value?
|
|
1121
|
+
if (silent != true) {
|
|
1122
|
+
throw err;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* Clear the dom of the node
|
|
1130
|
+
*/
|
|
1131
|
+
Node.prototype.clearDom = function() {
|
|
1132
|
+
// TODO: hide the node first?
|
|
1133
|
+
//this.hide();
|
|
1134
|
+
// TODO: recursively clear dom?
|
|
1135
|
+
|
|
1136
|
+
this.dom = {};
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* Get the HTML DOM TR element of the node.
|
|
1141
|
+
* The dom will be generated when not yet created
|
|
1142
|
+
* @return {Element} tr HTML DOM TR Element
|
|
1143
|
+
*/
|
|
1144
|
+
Node.prototype.getDom = function() {
|
|
1145
|
+
var dom = this.dom;
|
|
1146
|
+
if (dom.tr) {
|
|
1147
|
+
return dom.tr;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// create row
|
|
1151
|
+
dom.tr = document.createElement('tr');
|
|
1152
|
+
dom.tr.node = this;
|
|
1153
|
+
|
|
1154
|
+
if (this.editor.mode.edit) {
|
|
1155
|
+
// create draggable area
|
|
1156
|
+
var tdDrag = document.createElement('td');
|
|
1157
|
+
if (this.parent) {
|
|
1158
|
+
var domDrag = document.createElement('button');
|
|
1159
|
+
dom.drag = domDrag;
|
|
1160
|
+
domDrag.className = 'dragarea';
|
|
1161
|
+
domDrag.title = 'Drag to move this field (Alt+Shift+Arrows)';
|
|
1162
|
+
tdDrag.appendChild(domDrag);
|
|
1163
|
+
}
|
|
1164
|
+
dom.tr.appendChild(tdDrag);
|
|
1165
|
+
|
|
1166
|
+
// create context menu
|
|
1167
|
+
var tdMenu = document.createElement('td');
|
|
1168
|
+
var menu = document.createElement('button');
|
|
1169
|
+
dom.menu = menu;
|
|
1170
|
+
menu.className = 'contextmenu';
|
|
1171
|
+
menu.title = 'Click to open the actions menu (Ctrl+M)';
|
|
1172
|
+
tdMenu.appendChild(dom.menu);
|
|
1173
|
+
dom.tr.appendChild(tdMenu);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// create tree and field
|
|
1177
|
+
var tdField = document.createElement('td');
|
|
1178
|
+
dom.tr.appendChild(tdField);
|
|
1179
|
+
dom.tree = this._createDomTree();
|
|
1180
|
+
tdField.appendChild(dom.tree);
|
|
1181
|
+
|
|
1182
|
+
this.updateDom({'updateIndexes': true});
|
|
1183
|
+
|
|
1184
|
+
return dom.tr;
|
|
1185
|
+
};
|
|
1186
|
+
|
|
1187
|
+
/**
|
|
1188
|
+
* DragStart event, fired on mousedown on the dragarea at the left side of a Node
|
|
1189
|
+
* @param {Event} event
|
|
1190
|
+
* @private
|
|
1191
|
+
*/
|
|
1192
|
+
Node.prototype._onDragStart = function (event) {
|
|
1193
|
+
event = event || window.event;
|
|
1194
|
+
|
|
1195
|
+
var node = this;
|
|
1196
|
+
if (!this.mousemove) {
|
|
1197
|
+
this.mousemove = util.addEventListener(document, 'mousemove',
|
|
1198
|
+
function (event) {
|
|
1199
|
+
node._onDrag(event);
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
if (!this.mouseup) {
|
|
1204
|
+
this.mouseup = util.addEventListener(document, 'mouseup',
|
|
1205
|
+
function (event ) {
|
|
1206
|
+
node._onDragEnd(event);
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
this.editor.highlighter.lock();
|
|
1211
|
+
this.drag = {
|
|
1212
|
+
'oldCursor': document.body.style.cursor,
|
|
1213
|
+
'startParent': this.parent,
|
|
1214
|
+
'startIndex': this.parent.childs.indexOf(this),
|
|
1215
|
+
'mouseX': util.getMouseX(event),
|
|
1216
|
+
'level': this.getLevel()
|
|
1217
|
+
};
|
|
1218
|
+
document.body.style.cursor = 'move';
|
|
1219
|
+
|
|
1220
|
+
util.preventDefault(event);
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* Drag event, fired when moving the mouse while dragging a Node
|
|
1225
|
+
* @param {Event} event
|
|
1226
|
+
* @private
|
|
1227
|
+
*/
|
|
1228
|
+
Node.prototype._onDrag = function (event) {
|
|
1229
|
+
// TODO: this method has grown too large. Split it in a number of methods
|
|
1230
|
+
event = event || window.event;
|
|
1231
|
+
var mouseY = util.getMouseY(event);
|
|
1232
|
+
var mouseX = util.getMouseX(event);
|
|
1233
|
+
|
|
1234
|
+
var trThis, trPrev, trNext, trFirst, trLast, trRoot;
|
|
1235
|
+
var nodePrev, nodeNext;
|
|
1236
|
+
var topThis, topPrev, topFirst, heightThis, bottomNext, heightNext;
|
|
1237
|
+
var moved = false;
|
|
1238
|
+
|
|
1239
|
+
// TODO: add an ESC option, which resets to the original position
|
|
1240
|
+
|
|
1241
|
+
// move up/down
|
|
1242
|
+
trThis = this.dom.tr;
|
|
1243
|
+
topThis = util.getAbsoluteTop(trThis);
|
|
1244
|
+
heightThis = trThis.offsetHeight;
|
|
1245
|
+
if (mouseY < topThis) {
|
|
1246
|
+
// move up
|
|
1247
|
+
trPrev = trThis;
|
|
1248
|
+
do {
|
|
1249
|
+
trPrev = trPrev.previousSibling;
|
|
1250
|
+
nodePrev = Node.getNodeFromTarget(trPrev);
|
|
1251
|
+
topPrev = trPrev ? util.getAbsoluteTop(trPrev) : 0;
|
|
1252
|
+
}
|
|
1253
|
+
while (trPrev && mouseY < topPrev);
|
|
1254
|
+
|
|
1255
|
+
if (nodePrev && !nodePrev.parent) {
|
|
1256
|
+
nodePrev = undefined;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
if (!nodePrev) {
|
|
1260
|
+
// move to the first node
|
|
1261
|
+
trRoot = trThis.parentNode.firstChild;
|
|
1262
|
+
trPrev = trRoot ? trRoot.nextSibling : undefined;
|
|
1263
|
+
nodePrev = Node.getNodeFromTarget(trPrev);
|
|
1264
|
+
if (nodePrev == this) {
|
|
1265
|
+
nodePrev = undefined;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
if (nodePrev) {
|
|
1270
|
+
// check if mouseY is really inside the found node
|
|
1271
|
+
trPrev = nodePrev.dom.tr;
|
|
1272
|
+
topPrev = trPrev ? util.getAbsoluteTop(trPrev) : 0;
|
|
1273
|
+
if (mouseY > topPrev + heightThis) {
|
|
1274
|
+
nodePrev = undefined;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
if (nodePrev) {
|
|
1279
|
+
nodePrev.parent.moveBefore(this, nodePrev);
|
|
1280
|
+
moved = true;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
else {
|
|
1284
|
+
// move down
|
|
1285
|
+
trLast = (this.expanded && this.append) ? this.append.getDom() : this.dom.tr;
|
|
1286
|
+
trFirst = trLast ? trLast.nextSibling : undefined;
|
|
1287
|
+
if (trFirst) {
|
|
1288
|
+
topFirst = util.getAbsoluteTop(trFirst);
|
|
1289
|
+
trNext = trFirst;
|
|
1290
|
+
do {
|
|
1291
|
+
nodeNext = Node.getNodeFromTarget(trNext);
|
|
1292
|
+
if (trNext) {
|
|
1293
|
+
bottomNext = trNext.nextSibling ?
|
|
1294
|
+
util.getAbsoluteTop(trNext.nextSibling) : 0;
|
|
1295
|
+
heightNext = trNext ? (bottomNext - topFirst) : 0;
|
|
1296
|
+
|
|
1297
|
+
if (nodeNext.parent.childs.length == 1 && nodeNext.parent.childs[0] == this) {
|
|
1298
|
+
// We are about to remove the last child of this parent,
|
|
1299
|
+
// which will make the parents appendNode visible.
|
|
1300
|
+
topThis += 24 - 1;
|
|
1301
|
+
// TODO: dangerous to suppose the height of the appendNode a constant of 24-1 px.
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
trNext = trNext.nextSibling;
|
|
1306
|
+
}
|
|
1307
|
+
while (trNext && mouseY > topThis + heightNext);
|
|
1308
|
+
|
|
1309
|
+
if (nodeNext && nodeNext.parent) {
|
|
1310
|
+
// calculate the desired level
|
|
1311
|
+
var diffX = (mouseX - this.drag.mouseX);
|
|
1312
|
+
var diffLevel = Math.round(diffX / 24 / 2);
|
|
1313
|
+
var level = this.drag.level + diffLevel; // desired level
|
|
1314
|
+
var levelNext = nodeNext.getLevel(); // level to be
|
|
1315
|
+
|
|
1316
|
+
// find the best fitting level (move upwards over the append nodes)
|
|
1317
|
+
trPrev = nodeNext.dom.tr.previousSibling;
|
|
1318
|
+
while (levelNext < level && trPrev) {
|
|
1319
|
+
nodePrev = Node.getNodeFromTarget(trPrev);
|
|
1320
|
+
if (nodePrev == this || nodePrev._isChildOf(this)) {
|
|
1321
|
+
// neglect itself and its childs
|
|
1322
|
+
}
|
|
1323
|
+
else if (nodePrev instanceof AppendNode) {
|
|
1324
|
+
var childs = nodePrev.parent.childs;
|
|
1325
|
+
if (childs.length > 1 ||
|
|
1326
|
+
(childs.length == 1 && childs[0] != this)) {
|
|
1327
|
+
// non-visible append node of a list of childs
|
|
1328
|
+
// consisting of not only this node (else the
|
|
1329
|
+
// append node will change into a visible "empty"
|
|
1330
|
+
// text when removing this node).
|
|
1331
|
+
nodeNext = Node.getNodeFromTarget(trPrev);
|
|
1332
|
+
levelNext = nodeNext.getLevel();
|
|
1333
|
+
}
|
|
1334
|
+
else {
|
|
1335
|
+
break;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
else {
|
|
1339
|
+
break;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
trPrev = trPrev.previousSibling;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// move the node when its position is changed
|
|
1346
|
+
if (trLast.nextSibling != nodeNext.dom.tr) {
|
|
1347
|
+
nodeNext.parent.moveBefore(this, nodeNext);
|
|
1348
|
+
moved = true;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
if (moved) {
|
|
1355
|
+
// update the dragging parameters when moved
|
|
1356
|
+
this.drag.mouseX = mouseX;
|
|
1357
|
+
this.drag.level = this.getLevel();
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// auto scroll when hovering around the top of the editor
|
|
1361
|
+
this.editor.startAutoScroll(mouseY);
|
|
1362
|
+
|
|
1363
|
+
util.preventDefault(event);
|
|
1364
|
+
};
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* Drag event, fired on mouseup after having dragged a node
|
|
1368
|
+
* @param {Event} event
|
|
1369
|
+
* @private
|
|
1370
|
+
*/
|
|
1371
|
+
Node.prototype._onDragEnd = function (event) {
|
|
1372
|
+
event = event || window.event;
|
|
1373
|
+
|
|
1374
|
+
var params = {
|
|
1375
|
+
'node': this,
|
|
1376
|
+
'startParent': this.drag.startParent,
|
|
1377
|
+
'startIndex': this.drag.startIndex,
|
|
1378
|
+
'endParent': this.parent,
|
|
1379
|
+
'endIndex': this.parent.childs.indexOf(this)
|
|
1380
|
+
};
|
|
1381
|
+
if ((params.startParent != params.endParent) ||
|
|
1382
|
+
(params.startIndex != params.endIndex)) {
|
|
1383
|
+
// only register this action if the node is actually moved to another place
|
|
1384
|
+
this.editor._onAction('moveNode', params);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
document.body.style.cursor = this.drag.oldCursor;
|
|
1388
|
+
this.editor.highlighter.unlock();
|
|
1389
|
+
delete this.drag;
|
|
1390
|
+
|
|
1391
|
+
if (this.mousemove) {
|
|
1392
|
+
util.removeEventListener(document, 'mousemove', this.mousemove);
|
|
1393
|
+
delete this.mousemove;}
|
|
1394
|
+
if (this.mouseup) {
|
|
1395
|
+
util.removeEventListener(document, 'mouseup', this.mouseup);
|
|
1396
|
+
delete this.mouseup;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// Stop any running auto scroll
|
|
1400
|
+
this.editor.stopAutoScroll();
|
|
1401
|
+
|
|
1402
|
+
util.preventDefault(event);
|
|
1403
|
+
};
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* Test if this node is a child of an other node
|
|
1407
|
+
* @param {Node} node
|
|
1408
|
+
* @return {boolean} isChild
|
|
1409
|
+
* @private
|
|
1410
|
+
*/
|
|
1411
|
+
Node.prototype._isChildOf = function (node) {
|
|
1412
|
+
var n = this.parent;
|
|
1413
|
+
while (n) {
|
|
1414
|
+
if (n == node) {
|
|
1415
|
+
return true;
|
|
1416
|
+
}
|
|
1417
|
+
n = n.parent;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
return false;
|
|
1421
|
+
};
|
|
1422
|
+
|
|
1423
|
+
/**
|
|
1424
|
+
* Create an editable field
|
|
1425
|
+
* @return {Element} domField
|
|
1426
|
+
* @private
|
|
1427
|
+
*/
|
|
1428
|
+
Node.prototype._createDomField = function () {
|
|
1429
|
+
return document.createElement('div');
|
|
1430
|
+
};
|
|
1431
|
+
|
|
1432
|
+
/**
|
|
1433
|
+
* Set highlighting for this node and all its childs.
|
|
1434
|
+
* Only applied to the currently visible (expanded childs)
|
|
1435
|
+
* @param {boolean} highlight
|
|
1436
|
+
*/
|
|
1437
|
+
Node.prototype.setHighlight = function (highlight) {
|
|
1438
|
+
if (this.dom.tr) {
|
|
1439
|
+
this.dom.tr.className = (highlight ? 'highlight' : '');
|
|
1440
|
+
|
|
1441
|
+
if (this.append) {
|
|
1442
|
+
this.append.setHighlight(highlight);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
if (this.childs) {
|
|
1446
|
+
this.childs.forEach(function (child) {
|
|
1447
|
+
child.setHighlight(highlight);
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
|
|
1453
|
+
/**
|
|
1454
|
+
* Update the value of the node. Only primitive types are allowed, no Object
|
|
1455
|
+
* or Array is allowed.
|
|
1456
|
+
* @param {String | Number | Boolean | null} value
|
|
1457
|
+
*/
|
|
1458
|
+
Node.prototype.updateValue = function (value) {
|
|
1459
|
+
this.value = value;
|
|
1460
|
+
this.updateDom();
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
/**
|
|
1464
|
+
* Update the field of the node.
|
|
1465
|
+
* @param {String} field
|
|
1466
|
+
*/
|
|
1467
|
+
Node.prototype.updateField = function (field) {
|
|
1468
|
+
this.field = field;
|
|
1469
|
+
this.updateDom();
|
|
1470
|
+
};
|
|
1471
|
+
|
|
1472
|
+
/**
|
|
1473
|
+
* Update the HTML DOM, optionally recursing through the childs
|
|
1474
|
+
* @param {Object} [options] Available parameters:
|
|
1475
|
+
* {boolean} [recurse] If true, the
|
|
1476
|
+
* DOM of the childs will be updated recursively.
|
|
1477
|
+
* False by default.
|
|
1478
|
+
* {boolean} [updateIndexes] If true, the childs
|
|
1479
|
+
* indexes of the node will be updated too. False by
|
|
1480
|
+
* default.
|
|
1481
|
+
*/
|
|
1482
|
+
Node.prototype.updateDom = function (options) {
|
|
1483
|
+
// update level indentation
|
|
1484
|
+
var domTree = this.dom.tree;
|
|
1485
|
+
if (domTree) {
|
|
1486
|
+
domTree.style.marginLeft = this.getLevel() * 24 + 'px';
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// update field
|
|
1490
|
+
var domField = this.dom.field;
|
|
1491
|
+
if (domField) {
|
|
1492
|
+
if (this.fieldEditable == true) {
|
|
1493
|
+
// parent is an object
|
|
1494
|
+
domField.contentEditable = this.editor.mode.edit;
|
|
1495
|
+
domField.spellcheck = false;
|
|
1496
|
+
domField.className = 'field';
|
|
1497
|
+
}
|
|
1498
|
+
else {
|
|
1499
|
+
// parent is an array this is the root node
|
|
1500
|
+
domField.className = 'readonly';
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
var field;
|
|
1504
|
+
if (this.index != undefined) {
|
|
1505
|
+
field = this.index;
|
|
1506
|
+
}
|
|
1507
|
+
else if (this.field != undefined) {
|
|
1508
|
+
field = this.field;
|
|
1509
|
+
}
|
|
1510
|
+
else if (this._hasChilds()) {
|
|
1511
|
+
field = this.type;
|
|
1512
|
+
}
|
|
1513
|
+
else {
|
|
1514
|
+
field = '';
|
|
1515
|
+
}
|
|
1516
|
+
domField.innerHTML = this._escapeHTML(field);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// update value
|
|
1520
|
+
var domValue = this.dom.value;
|
|
1521
|
+
if (domValue) {
|
|
1522
|
+
var count = this.childs ? this.childs.length : 0;
|
|
1523
|
+
if (this.type == 'array') {
|
|
1524
|
+
domValue.innerHTML = '[' + count + ']';
|
|
1525
|
+
}
|
|
1526
|
+
else if (this.type == 'object') {
|
|
1527
|
+
domValue.innerHTML = '{' + count + '}';
|
|
1528
|
+
}
|
|
1529
|
+
else {
|
|
1530
|
+
domValue.innerHTML = this._escapeHTML(this.value);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// update field and value
|
|
1535
|
+
this._updateDomField();
|
|
1536
|
+
this._updateDomValue();
|
|
1537
|
+
|
|
1538
|
+
// update childs indexes
|
|
1539
|
+
if (options && options.updateIndexes == true) {
|
|
1540
|
+
// updateIndexes is true or undefined
|
|
1541
|
+
this._updateDomIndexes();
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
if (options && options.recurse == true) {
|
|
1545
|
+
// recurse is true or undefined. update childs recursively
|
|
1546
|
+
if (this.childs) {
|
|
1547
|
+
this.childs.forEach(function (child) {
|
|
1548
|
+
child.updateDom(options);
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// update row with append button
|
|
1554
|
+
if (this.append) {
|
|
1555
|
+
this.append.updateDom();
|
|
1556
|
+
}
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
/**
|
|
1560
|
+
* Update the DOM of the childs of a node: update indexes and undefined field
|
|
1561
|
+
* names.
|
|
1562
|
+
* Only applicable when structure is an array or object
|
|
1563
|
+
* @private
|
|
1564
|
+
*/
|
|
1565
|
+
Node.prototype._updateDomIndexes = function () {
|
|
1566
|
+
var domValue = this.dom.value;
|
|
1567
|
+
var childs = this.childs;
|
|
1568
|
+
if (domValue && childs) {
|
|
1569
|
+
if (this.type == 'array') {
|
|
1570
|
+
childs.forEach(function (child, index) {
|
|
1571
|
+
child.index = index;
|
|
1572
|
+
var childField = child.dom.field;
|
|
1573
|
+
if (childField) {
|
|
1574
|
+
childField.innerHTML = index;
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
else if (this.type == 'object') {
|
|
1579
|
+
childs.forEach(function (child) {
|
|
1580
|
+
if (child.index != undefined) {
|
|
1581
|
+
delete child.index;
|
|
1582
|
+
|
|
1583
|
+
if (child.field == undefined) {
|
|
1584
|
+
child.field = '';
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
|
|
1592
|
+
/**
|
|
1593
|
+
* Create an editable value
|
|
1594
|
+
* @private
|
|
1595
|
+
*/
|
|
1596
|
+
Node.prototype._createDomValue = function () {
|
|
1597
|
+
var domValue;
|
|
1598
|
+
|
|
1599
|
+
if (this.type == 'array') {
|
|
1600
|
+
domValue = document.createElement('div');
|
|
1601
|
+
domValue.className = 'readonly';
|
|
1602
|
+
domValue.innerHTML = '[...]';
|
|
1603
|
+
}
|
|
1604
|
+
else if (this.type == 'object') {
|
|
1605
|
+
domValue = document.createElement('div');
|
|
1606
|
+
domValue.className = 'readonly';
|
|
1607
|
+
domValue.innerHTML = '{...}';
|
|
1608
|
+
}
|
|
1609
|
+
else {
|
|
1610
|
+
if (!this.editor.mode.edit && util.isUrl(this.value)) {
|
|
1611
|
+
// create a link in case of read-only editor and value containing an url
|
|
1612
|
+
domValue = document.createElement('a');
|
|
1613
|
+
domValue.className = 'value';
|
|
1614
|
+
domValue.href = this.value;
|
|
1615
|
+
domValue.target = '_blank';
|
|
1616
|
+
domValue.innerHTML = this._escapeHTML(this.value);
|
|
1617
|
+
}
|
|
1618
|
+
else {
|
|
1619
|
+
// create and editable or read-only div
|
|
1620
|
+
domValue = document.createElement('div');
|
|
1621
|
+
domValue.contentEditable = !this.editor.mode.view;
|
|
1622
|
+
domValue.spellcheck = false;
|
|
1623
|
+
domValue.className = 'value';
|
|
1624
|
+
domValue.innerHTML = this._escapeHTML(this.value);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
return domValue;
|
|
1629
|
+
};
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* Create an expand/collapse button
|
|
1633
|
+
* @return {Element} expand
|
|
1634
|
+
* @private
|
|
1635
|
+
*/
|
|
1636
|
+
Node.prototype._createDomExpandButton = function () {
|
|
1637
|
+
// create expand button
|
|
1638
|
+
var expand = document.createElement('button');
|
|
1639
|
+
if (this._hasChilds()) {
|
|
1640
|
+
expand.className = this.expanded ? 'expanded' : 'collapsed';
|
|
1641
|
+
expand.title =
|
|
1642
|
+
'Click to expand/collapse this field (Ctrl+E). \n' +
|
|
1643
|
+
'Ctrl+Click to expand/collapse including all childs.';
|
|
1644
|
+
}
|
|
1645
|
+
else {
|
|
1646
|
+
expand.className = 'invisible';
|
|
1647
|
+
expand.title = '';
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
return expand;
|
|
1651
|
+
};
|
|
1652
|
+
|
|
1653
|
+
|
|
1654
|
+
/**
|
|
1655
|
+
* Create a DOM tree element, containing the expand/collapse button
|
|
1656
|
+
* @return {Element} domTree
|
|
1657
|
+
* @private
|
|
1658
|
+
*/
|
|
1659
|
+
Node.prototype._createDomTree = function () {
|
|
1660
|
+
var dom = this.dom;
|
|
1661
|
+
var domTree = document.createElement('table');
|
|
1662
|
+
var tbody = document.createElement('tbody');
|
|
1663
|
+
domTree.style.borderCollapse = 'collapse'; // TODO: put in css
|
|
1664
|
+
domTree.appendChild(tbody);
|
|
1665
|
+
var tr = document.createElement('tr');
|
|
1666
|
+
tbody.appendChild(tr);
|
|
1667
|
+
|
|
1668
|
+
// create expand button
|
|
1669
|
+
var tdExpand = document.createElement('td');
|
|
1670
|
+
tdExpand.className = 'tree';
|
|
1671
|
+
tr.appendChild(tdExpand);
|
|
1672
|
+
dom.expand = this._createDomExpandButton();
|
|
1673
|
+
tdExpand.appendChild(dom.expand);
|
|
1674
|
+
dom.tdExpand = tdExpand;
|
|
1675
|
+
|
|
1676
|
+
// create the field
|
|
1677
|
+
var tdField = document.createElement('td');
|
|
1678
|
+
tdField.className = 'tree';
|
|
1679
|
+
tr.appendChild(tdField);
|
|
1680
|
+
dom.field = this._createDomField();
|
|
1681
|
+
tdField.appendChild(dom.field);
|
|
1682
|
+
dom.tdField = tdField;
|
|
1683
|
+
|
|
1684
|
+
// create a separator
|
|
1685
|
+
var tdSeparator = document.createElement('td');
|
|
1686
|
+
tdSeparator.className = 'tree';
|
|
1687
|
+
tr.appendChild(tdSeparator);
|
|
1688
|
+
if (this.type != 'object' && this.type != 'array') {
|
|
1689
|
+
tdSeparator.appendChild(document.createTextNode(':'));
|
|
1690
|
+
tdSeparator.className = 'separator';
|
|
1691
|
+
}
|
|
1692
|
+
dom.tdSeparator = tdSeparator;
|
|
1693
|
+
|
|
1694
|
+
// create the value
|
|
1695
|
+
var tdValue = document.createElement('td');
|
|
1696
|
+
tdValue.className = 'tree';
|
|
1697
|
+
tr.appendChild(tdValue);
|
|
1698
|
+
dom.value = this._createDomValue();
|
|
1699
|
+
tdValue.appendChild(dom.value);
|
|
1700
|
+
dom.tdValue = tdValue;
|
|
1701
|
+
|
|
1702
|
+
return domTree;
|
|
1703
|
+
};
|
|
1704
|
+
|
|
1705
|
+
/**
|
|
1706
|
+
* Handle an event. The event is catched centrally by the editor
|
|
1707
|
+
* @param {Event} event
|
|
1708
|
+
*/
|
|
1709
|
+
Node.prototype.onEvent = function (event) {
|
|
1710
|
+
var type = event.type,
|
|
1711
|
+
target = event.target || event.srcElement,
|
|
1712
|
+
dom = this.dom,
|
|
1713
|
+
node = this,
|
|
1714
|
+
focusNode,
|
|
1715
|
+
expandable = this._hasChilds();
|
|
1716
|
+
|
|
1717
|
+
// check if mouse is on menu or on dragarea.
|
|
1718
|
+
// If so, highlight current row and its childs
|
|
1719
|
+
if (target == dom.drag || target == dom.menu) {
|
|
1720
|
+
if (type == 'mouseover') {
|
|
1721
|
+
this.editor.highlighter.highlight(this);
|
|
1722
|
+
}
|
|
1723
|
+
else if (type == 'mouseout') {
|
|
1724
|
+
this.editor.highlighter.unhighlight();
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
// drag events
|
|
1729
|
+
if (type == 'mousedown' && target == dom.drag) {
|
|
1730
|
+
this._onDragStart(event);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
// context menu events
|
|
1734
|
+
if (type == 'click' && target == dom.menu) {
|
|
1735
|
+
var highlighter = node.editor.highlighter;
|
|
1736
|
+
highlighter.highlight(node);
|
|
1737
|
+
highlighter.lock();
|
|
1738
|
+
util.addClassName(dom.menu, 'selected');
|
|
1739
|
+
this.showContextMenu(dom.menu, function () {
|
|
1740
|
+
util.removeClassName(dom.menu, 'selected');
|
|
1741
|
+
highlighter.unlock();
|
|
1742
|
+
highlighter.unhighlight();
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// expand events
|
|
1747
|
+
if (type == 'click' && target == dom.expand) {
|
|
1748
|
+
if (expandable) {
|
|
1749
|
+
var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all
|
|
1750
|
+
this._onExpand(recurse);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
// value events
|
|
1755
|
+
var domValue = dom.value;
|
|
1756
|
+
if (target == domValue) {
|
|
1757
|
+
//noinspection FallthroughInSwitchStatementJS
|
|
1758
|
+
switch (type) {
|
|
1759
|
+
case 'focus':
|
|
1760
|
+
focusNode = this;
|
|
1761
|
+
break;
|
|
1762
|
+
|
|
1763
|
+
case 'blur':
|
|
1764
|
+
case 'change':
|
|
1765
|
+
this._getDomValue(true);
|
|
1766
|
+
this._updateDomValue();
|
|
1767
|
+
if (this.value) {
|
|
1768
|
+
domValue.innerHTML = this._escapeHTML(this.value);
|
|
1769
|
+
}
|
|
1770
|
+
break;
|
|
1771
|
+
|
|
1772
|
+
case 'input':
|
|
1773
|
+
this._getDomValue(true);
|
|
1774
|
+
this._updateDomValue();
|
|
1775
|
+
break;
|
|
1776
|
+
|
|
1777
|
+
case 'keydown':
|
|
1778
|
+
case 'mousedown':
|
|
1779
|
+
this.editor.selection = this.editor.getSelection();
|
|
1780
|
+
break;
|
|
1781
|
+
|
|
1782
|
+
case 'click':
|
|
1783
|
+
if (event.ctrlKey && this.editor.mode.edit) {
|
|
1784
|
+
if (util.isUrl(this.value)) {
|
|
1785
|
+
window.open(this.value, '_blank');
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
break;
|
|
1789
|
+
|
|
1790
|
+
case 'keyup':
|
|
1791
|
+
this._getDomValue(true);
|
|
1792
|
+
this._updateDomValue();
|
|
1793
|
+
break;
|
|
1794
|
+
|
|
1795
|
+
case 'cut':
|
|
1796
|
+
case 'paste':
|
|
1797
|
+
setTimeout(function () {
|
|
1798
|
+
node._getDomValue(true);
|
|
1799
|
+
node._updateDomValue();
|
|
1800
|
+
}, 1);
|
|
1801
|
+
break;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
// field events
|
|
1806
|
+
var domField = dom.field;
|
|
1807
|
+
if (target == domField) {
|
|
1808
|
+
switch (type) {
|
|
1809
|
+
case 'focus':
|
|
1810
|
+
focusNode = this;
|
|
1811
|
+
break;
|
|
1812
|
+
|
|
1813
|
+
case 'blur':
|
|
1814
|
+
case 'change':
|
|
1815
|
+
this._getDomField(true);
|
|
1816
|
+
this._updateDomField();
|
|
1817
|
+
if (this.field) {
|
|
1818
|
+
domField.innerHTML = this._escapeHTML(this.field);
|
|
1819
|
+
}
|
|
1820
|
+
break;
|
|
1821
|
+
|
|
1822
|
+
case 'input':
|
|
1823
|
+
this._getDomField(true);
|
|
1824
|
+
this._updateDomField();
|
|
1825
|
+
break;
|
|
1826
|
+
|
|
1827
|
+
case 'keydown':
|
|
1828
|
+
case 'mousedown':
|
|
1829
|
+
this.editor.selection = this.editor.getSelection();
|
|
1830
|
+
break;
|
|
1831
|
+
|
|
1832
|
+
case 'keyup':
|
|
1833
|
+
this._getDomField(true);
|
|
1834
|
+
this._updateDomField();
|
|
1835
|
+
break;
|
|
1836
|
+
|
|
1837
|
+
case 'cut':
|
|
1838
|
+
case 'paste':
|
|
1839
|
+
setTimeout(function () {
|
|
1840
|
+
node._getDomField(true);
|
|
1841
|
+
node._updateDomField();
|
|
1842
|
+
}, 1);
|
|
1843
|
+
break;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
// focus
|
|
1848
|
+
// when clicked in whitespace left or right from the field or value, set focus
|
|
1849
|
+
var domTree = dom.tree;
|
|
1850
|
+
if (target == domTree.parentNode) {
|
|
1851
|
+
switch (type) {
|
|
1852
|
+
case 'click':
|
|
1853
|
+
var left = (event.offsetX != undefined) ?
|
|
1854
|
+
(event.offsetX < (this.getLevel() + 1) * 24) :
|
|
1855
|
+
(util.getMouseX(event) < util.getAbsoluteLeft(dom.tdSeparator));// for FF
|
|
1856
|
+
if (left || expandable) {
|
|
1857
|
+
// node is expandable when it is an object or array
|
|
1858
|
+
if (domField) {
|
|
1859
|
+
util.setEndOfContentEditable(domField);
|
|
1860
|
+
domField.focus();
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
else {
|
|
1864
|
+
if (domValue) {
|
|
1865
|
+
util.setEndOfContentEditable(domValue);
|
|
1866
|
+
domValue.focus();
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
break;
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
if ((target == dom.tdExpand && !expandable) || target == dom.tdField ||
|
|
1873
|
+
target == dom.tdSeparator) {
|
|
1874
|
+
switch (type) {
|
|
1875
|
+
case 'click':
|
|
1876
|
+
if (domField) {
|
|
1877
|
+
util.setEndOfContentEditable(domField);
|
|
1878
|
+
domField.focus();
|
|
1879
|
+
}
|
|
1880
|
+
break;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
if (type == 'keydown') {
|
|
1885
|
+
this.onKeyDown(event);
|
|
1886
|
+
}
|
|
1887
|
+
};
|
|
1888
|
+
|
|
1889
|
+
/**
|
|
1890
|
+
* Key down event handler
|
|
1891
|
+
* @param {Event} event
|
|
1892
|
+
*/
|
|
1893
|
+
Node.prototype.onKeyDown = function (event) {
|
|
1894
|
+
var keynum = event.which || event.keyCode;
|
|
1895
|
+
var target = event.target || event.srcElement;
|
|
1896
|
+
var ctrlKey = event.ctrlKey;
|
|
1897
|
+
var shiftKey = event.shiftKey;
|
|
1898
|
+
var altKey = event.altKey;
|
|
1899
|
+
var handled = false;
|
|
1900
|
+
var prevNode, nextNode, nextDom, nextDom2;
|
|
1901
|
+
|
|
1902
|
+
// util.log(ctrlKey, keynum, event.charCode); // TODO: cleanup
|
|
1903
|
+
if (keynum == 13) { // Enter
|
|
1904
|
+
if (target == this.dom.value) {
|
|
1905
|
+
if (!this.editor.mode.edit || event.ctrlKey) {
|
|
1906
|
+
if (util.isUrl(this.value)) {
|
|
1907
|
+
window.open(this.value, '_blank');
|
|
1908
|
+
handled = true;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
else if (target == this.dom.expand) {
|
|
1913
|
+
var expandable = this._hasChilds();
|
|
1914
|
+
if (expandable) {
|
|
1915
|
+
var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all
|
|
1916
|
+
this._onExpand(recurse);
|
|
1917
|
+
target.focus();
|
|
1918
|
+
handled = true;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
else if (keynum == 68) { // D
|
|
1923
|
+
if (ctrlKey) { // Ctrl+D
|
|
1924
|
+
this._onDuplicate();
|
|
1925
|
+
handled = true;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
else if (keynum == 69) { // E
|
|
1929
|
+
if (ctrlKey) { // Ctrl+E and Ctrl+Shift+E
|
|
1930
|
+
this._onExpand(shiftKey); // recurse = shiftKey
|
|
1931
|
+
target.focus(); // TODO: should restore focus in case of recursing expand (which takes DOM offline)
|
|
1932
|
+
handled = true;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
else if (keynum == 77) { // M
|
|
1936
|
+
if (ctrlKey) { // Ctrl+M
|
|
1937
|
+
this.showContextMenu(target);
|
|
1938
|
+
handled = true;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
else if (keynum == 46) { // Del
|
|
1942
|
+
if (ctrlKey) { // Ctrl+Del
|
|
1943
|
+
this._onRemove();
|
|
1944
|
+
handled = true;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
else if (keynum == 45) { // Ins
|
|
1948
|
+
if (ctrlKey && !shiftKey) { // Ctrl+Ins
|
|
1949
|
+
this._onInsertBefore();
|
|
1950
|
+
handled = true;
|
|
1951
|
+
}
|
|
1952
|
+
else if (ctrlKey && shiftKey) { // Ctrl+Shift+Ins
|
|
1953
|
+
this._onInsertAfter();
|
|
1954
|
+
handled = true;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
else if (keynum == 35) { // End
|
|
1958
|
+
if (altKey) { // Alt+End
|
|
1959
|
+
// find the last node
|
|
1960
|
+
var lastNode = this._lastNode();
|
|
1961
|
+
if (lastNode) {
|
|
1962
|
+
lastNode.focus(Node.focusElement || this._getElementName(target));
|
|
1963
|
+
}
|
|
1964
|
+
handled = true;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
else if (keynum == 36) { // Home
|
|
1968
|
+
if (altKey) { // Alt+Home
|
|
1969
|
+
// find the first node
|
|
1970
|
+
var firstNode = this._firstNode();
|
|
1971
|
+
if (firstNode) {
|
|
1972
|
+
firstNode.focus(Node.focusElement || this._getElementName(target));
|
|
1973
|
+
}
|
|
1974
|
+
handled = true;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
else if (keynum == 37) { // Arrow Left
|
|
1978
|
+
if (altKey && !shiftKey) { // Alt + Arrow Left
|
|
1979
|
+
// move to left element
|
|
1980
|
+
var prevElement = this._previousElement(target);
|
|
1981
|
+
if (prevElement) {
|
|
1982
|
+
this.focus(this._getElementName(prevElement));
|
|
1983
|
+
}
|
|
1984
|
+
handled = true;
|
|
1985
|
+
}
|
|
1986
|
+
else if (altKey && shiftKey) { // Alt + Shift Arrow left
|
|
1987
|
+
if (this.expanded) {
|
|
1988
|
+
var appendDom = this.getAppend();
|
|
1989
|
+
nextDom = appendDom ? appendDom.nextSibling : undefined;
|
|
1990
|
+
}
|
|
1991
|
+
else {
|
|
1992
|
+
var dom = this.getDom();
|
|
1993
|
+
nextDom = dom.nextSibling;
|
|
1994
|
+
}
|
|
1995
|
+
if (nextDom) {
|
|
1996
|
+
nextNode = Node.getNodeFromTarget(nextDom);
|
|
1997
|
+
nextDom2 = nextDom.nextSibling;
|
|
1998
|
+
nextNode2 = Node.getNodeFromTarget(nextDom2);
|
|
1999
|
+
if (nextNode && nextNode instanceof AppendNode &&
|
|
2000
|
+
!(this.parent.childs.length == 1) &&
|
|
2001
|
+
nextNode2 && nextNode2.parent) {
|
|
2002
|
+
nextNode2.parent.moveBefore(this, nextNode2);
|
|
2003
|
+
this.focus(Node.focusElement || this._getElementName(target));
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
else if (keynum == 38) { // Arrow Up
|
|
2009
|
+
if (altKey && !shiftKey) { // Alt + Arrow Up
|
|
2010
|
+
// find the previous node
|
|
2011
|
+
prevNode = this._previousNode();
|
|
2012
|
+
if (prevNode) {
|
|
2013
|
+
prevNode.focus(Node.focusElement || this._getElementName(target));
|
|
2014
|
+
}
|
|
2015
|
+
handled = true;
|
|
2016
|
+
}
|
|
2017
|
+
else if (altKey && shiftKey) { // Alt + Shift + Arrow Up
|
|
2018
|
+
// find the previous node
|
|
2019
|
+
prevNode = this._previousNode();
|
|
2020
|
+
if (prevNode && prevNode.parent) {
|
|
2021
|
+
prevNode.parent.moveBefore(this, prevNode);
|
|
2022
|
+
this.focus(Node.focusElement || this._getElementName(target));
|
|
2023
|
+
}
|
|
2024
|
+
handled = true;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
else if (keynum == 39) { // Arrow Right
|
|
2028
|
+
if (altKey && !shiftKey) { // Alt + Arrow Right
|
|
2029
|
+
// move to right element
|
|
2030
|
+
var nextElement = this._nextElement(target);
|
|
2031
|
+
if (nextElement) {
|
|
2032
|
+
this.focus(this._getElementName(nextElement));
|
|
2033
|
+
}
|
|
2034
|
+
handled = true;
|
|
2035
|
+
}
|
|
2036
|
+
else if (altKey && shiftKey) { // Alt + Shift Arrow Right
|
|
2037
|
+
dom = this.getDom();
|
|
2038
|
+
var prevDom = dom.previousSibling;
|
|
2039
|
+
if (prevDom) {
|
|
2040
|
+
prevNode = Node.getNodeFromTarget(prevDom);
|
|
2041
|
+
if (prevNode && prevNode.parent &&
|
|
2042
|
+
(prevNode instanceof AppendNode)
|
|
2043
|
+
&& !prevNode.isVisible()) {
|
|
2044
|
+
prevNode.parent.moveBefore(this, prevNode);
|
|
2045
|
+
this.focus(Node.focusElement || this._getElementName(target));
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
else if (keynum == 40) { // Arrow Down
|
|
2051
|
+
if (altKey && !shiftKey) { // Alt + Arrow Down
|
|
2052
|
+
// find the next node
|
|
2053
|
+
nextNode = this._nextNode();
|
|
2054
|
+
if (nextNode) {
|
|
2055
|
+
nextNode.focus(Node.focusElement || this._getElementName(target));
|
|
2056
|
+
}
|
|
2057
|
+
handled = true;
|
|
2058
|
+
}
|
|
2059
|
+
else if (altKey && shiftKey) { // Alt + Shift + Arrow Down
|
|
2060
|
+
// find the 2nd next node and move before that one
|
|
2061
|
+
if (this.expanded) {
|
|
2062
|
+
nextNode = this.append ? this.append._nextNode() : undefined;
|
|
2063
|
+
}
|
|
2064
|
+
else {
|
|
2065
|
+
nextNode = this._nextNode();
|
|
2066
|
+
}
|
|
2067
|
+
nextDom = nextNode ? nextNode.getDom() : undefined;
|
|
2068
|
+
if (this.parent.childs.length == 1) {
|
|
2069
|
+
nextDom2 = nextDom;
|
|
2070
|
+
}
|
|
2071
|
+
else {
|
|
2072
|
+
nextDom2 = nextDom ? nextDom.nextSibling : undefined;
|
|
2073
|
+
}
|
|
2074
|
+
var nextNode2 = Node.getNodeFromTarget(nextDom2);
|
|
2075
|
+
if (nextNode2 && nextNode2.parent) {
|
|
2076
|
+
nextNode2.parent.moveBefore(this, nextNode2);
|
|
2077
|
+
this.focus(Node.focusElement || this._getElementName(target));
|
|
2078
|
+
}
|
|
2079
|
+
handled = true;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
if (handled) {
|
|
2084
|
+
util.preventDefault(event);
|
|
2085
|
+
util.stopPropagation(event);
|
|
2086
|
+
}
|
|
2087
|
+
};
|
|
2088
|
+
|
|
2089
|
+
/**
|
|
2090
|
+
* Handle the expand event, when clicked on the expand button
|
|
2091
|
+
* @param {boolean} recurse If true, child nodes will be expanded too
|
|
2092
|
+
* @private
|
|
2093
|
+
*/
|
|
2094
|
+
Node.prototype._onExpand = function (recurse) {
|
|
2095
|
+
if (recurse) {
|
|
2096
|
+
// Take the table offline
|
|
2097
|
+
var table = this.dom.tr.parentNode; // TODO: not nice to access the main table like this
|
|
2098
|
+
var frame = table.parentNode;
|
|
2099
|
+
var scrollTop = frame.scrollTop;
|
|
2100
|
+
frame.removeChild(table);
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
if (this.expanded) {
|
|
2104
|
+
this.collapse(recurse);
|
|
2105
|
+
}
|
|
2106
|
+
else {
|
|
2107
|
+
this.expand(recurse);
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
if (recurse) {
|
|
2111
|
+
// Put the table online again
|
|
2112
|
+
frame.appendChild(table);
|
|
2113
|
+
frame.scrollTop = scrollTop;
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
|
|
2117
|
+
/**
|
|
2118
|
+
* Remove this node
|
|
2119
|
+
* @private
|
|
2120
|
+
*/
|
|
2121
|
+
Node.prototype._onRemove = function() {
|
|
2122
|
+
this.editor.highlighter.unhighlight();
|
|
2123
|
+
var childs = this.parent.childs;
|
|
2124
|
+
var index = childs.indexOf(this);
|
|
2125
|
+
|
|
2126
|
+
// adjust the focus
|
|
2127
|
+
var oldSelection = this.editor.getSelection();
|
|
2128
|
+
if (childs[index + 1]) {
|
|
2129
|
+
childs[index + 1].focus();
|
|
2130
|
+
}
|
|
2131
|
+
else if (childs[index - 1]) {
|
|
2132
|
+
childs[index - 1].focus();
|
|
2133
|
+
}
|
|
2134
|
+
else {
|
|
2135
|
+
this.parent.focus();
|
|
2136
|
+
}
|
|
2137
|
+
var newSelection = this.editor.getSelection();
|
|
2138
|
+
|
|
2139
|
+
// remove the node
|
|
2140
|
+
this.parent._remove(this);
|
|
2141
|
+
|
|
2142
|
+
// store history action
|
|
2143
|
+
this.editor._onAction('removeNode', {
|
|
2144
|
+
'node': this,
|
|
2145
|
+
'parent': this.parent,
|
|
2146
|
+
'index': index,
|
|
2147
|
+
'oldSelection': oldSelection,
|
|
2148
|
+
'newSelection': newSelection
|
|
2149
|
+
});
|
|
2150
|
+
};
|
|
2151
|
+
|
|
2152
|
+
/**
|
|
2153
|
+
* Duplicate this node
|
|
2154
|
+
* @private
|
|
2155
|
+
*/
|
|
2156
|
+
Node.prototype._onDuplicate = function() {
|
|
2157
|
+
var oldSelection = this.editor.getSelection();
|
|
2158
|
+
var clone = this.parent._duplicate(this);
|
|
2159
|
+
clone.focus();
|
|
2160
|
+
var newSelection = this.editor.getSelection();
|
|
2161
|
+
|
|
2162
|
+
this.editor._onAction('duplicateNode', {
|
|
2163
|
+
'node': this,
|
|
2164
|
+
'clone': clone,
|
|
2165
|
+
'parent': this.parent,
|
|
2166
|
+
'oldSelection': oldSelection,
|
|
2167
|
+
'newSelection': newSelection
|
|
2168
|
+
});
|
|
2169
|
+
};
|
|
2170
|
+
|
|
2171
|
+
/**
|
|
2172
|
+
* Handle insert before event
|
|
2173
|
+
* @param {String} [field]
|
|
2174
|
+
* @param {*} [value]
|
|
2175
|
+
* @param {String} [type] Can be 'auto', 'array', 'object', or 'string'
|
|
2176
|
+
* @private
|
|
2177
|
+
*/
|
|
2178
|
+
Node.prototype._onInsertBefore = function (field, value, type) {
|
|
2179
|
+
var oldSelection = this.editor.getSelection();
|
|
2180
|
+
|
|
2181
|
+
var newNode = new Node(this.editor, {
|
|
2182
|
+
'field': (field != undefined) ? field : '',
|
|
2183
|
+
'value': (value != undefined) ? value : '',
|
|
2184
|
+
'type': type
|
|
2185
|
+
});
|
|
2186
|
+
newNode.expand(true);
|
|
2187
|
+
this.parent.insertBefore(newNode, this);
|
|
2188
|
+
this.editor.highlighter.unhighlight();
|
|
2189
|
+
newNode.focus('field');
|
|
2190
|
+
var newSelection = this.editor.getSelection();
|
|
2191
|
+
|
|
2192
|
+
this.editor._onAction('insertBeforeNode', {
|
|
2193
|
+
'node': newNode,
|
|
2194
|
+
'beforeNode': this,
|
|
2195
|
+
'parent': this.parent,
|
|
2196
|
+
'oldSelection': oldSelection,
|
|
2197
|
+
'newSelection': newSelection
|
|
2198
|
+
});
|
|
2199
|
+
};
|
|
2200
|
+
|
|
2201
|
+
/**
|
|
2202
|
+
* Handle insert after event
|
|
2203
|
+
* @param {String} [field]
|
|
2204
|
+
* @param {*} [value]
|
|
2205
|
+
* @param {String} [type] Can be 'auto', 'array', 'object', or 'string'
|
|
2206
|
+
* @private
|
|
2207
|
+
*/
|
|
2208
|
+
Node.prototype._onInsertAfter = function (field, value, type) {
|
|
2209
|
+
var oldSelection = this.editor.getSelection();
|
|
2210
|
+
|
|
2211
|
+
var newNode = new Node(this.editor, {
|
|
2212
|
+
'field': (field != undefined) ? field : '',
|
|
2213
|
+
'value': (value != undefined) ? value : '',
|
|
2214
|
+
'type': type
|
|
2215
|
+
});
|
|
2216
|
+
newNode.expand(true);
|
|
2217
|
+
this.parent.insertAfter(newNode, this);
|
|
2218
|
+
this.editor.highlighter.unhighlight();
|
|
2219
|
+
newNode.focus('field');
|
|
2220
|
+
var newSelection = this.editor.getSelection();
|
|
2221
|
+
|
|
2222
|
+
this.editor._onAction('insertAfterNode', {
|
|
2223
|
+
'node': newNode,
|
|
2224
|
+
'afterNode': this,
|
|
2225
|
+
'parent': this.parent,
|
|
2226
|
+
'oldSelection': oldSelection,
|
|
2227
|
+
'newSelection': newSelection
|
|
2228
|
+
});
|
|
2229
|
+
};
|
|
2230
|
+
|
|
2231
|
+
/**
|
|
2232
|
+
* Handle append event
|
|
2233
|
+
* @param {String} [field]
|
|
2234
|
+
* @param {*} [value]
|
|
2235
|
+
* @param {String} [type] Can be 'auto', 'array', 'object', or 'string'
|
|
2236
|
+
* @private
|
|
2237
|
+
*/
|
|
2238
|
+
Node.prototype._onAppend = function (field, value, type) {
|
|
2239
|
+
var oldSelection = this.editor.getSelection();
|
|
2240
|
+
|
|
2241
|
+
var newNode = new Node(this.editor, {
|
|
2242
|
+
'field': (field != undefined) ? field : '',
|
|
2243
|
+
'value': (value != undefined) ? value : '',
|
|
2244
|
+
'type': type
|
|
2245
|
+
});
|
|
2246
|
+
newNode.expand(true);
|
|
2247
|
+
this.parent.appendChild(newNode);
|
|
2248
|
+
this.editor.highlighter.unhighlight();
|
|
2249
|
+
newNode.focus('field');
|
|
2250
|
+
var newSelection = this.editor.getSelection();
|
|
2251
|
+
|
|
2252
|
+
this.editor._onAction('appendNode', {
|
|
2253
|
+
'node': newNode,
|
|
2254
|
+
'parent': this.parent,
|
|
2255
|
+
'oldSelection': oldSelection,
|
|
2256
|
+
'newSelection': newSelection
|
|
2257
|
+
});
|
|
2258
|
+
};
|
|
2259
|
+
|
|
2260
|
+
/**
|
|
2261
|
+
* Change the type of the node's value
|
|
2262
|
+
* @param {String} newType
|
|
2263
|
+
* @private
|
|
2264
|
+
*/
|
|
2265
|
+
Node.prototype._onChangeType = function (newType) {
|
|
2266
|
+
var oldType = this.type;
|
|
2267
|
+
if (newType != oldType) {
|
|
2268
|
+
var oldSelection = this.editor.getSelection();
|
|
2269
|
+
this.changeType(newType);
|
|
2270
|
+
var newSelection = this.editor.getSelection();
|
|
2271
|
+
|
|
2272
|
+
this.editor._onAction('changeType', {
|
|
2273
|
+
'node': this,
|
|
2274
|
+
'oldType': oldType,
|
|
2275
|
+
'newType': newType,
|
|
2276
|
+
'oldSelection': oldSelection,
|
|
2277
|
+
'newSelection': newSelection
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
};
|
|
2281
|
+
|
|
2282
|
+
/**
|
|
2283
|
+
* Sort the childs of the node. Only applicable when the node has type 'object'
|
|
2284
|
+
* or 'array'.
|
|
2285
|
+
* @param {String} direction Sorting direction. Available values: "asc", "desc"
|
|
2286
|
+
* @private
|
|
2287
|
+
*/
|
|
2288
|
+
Node.prototype._onSort = function (direction) {
|
|
2289
|
+
if (this._hasChilds()) {
|
|
2290
|
+
var order = (direction == 'desc') ? -1 : 1;
|
|
2291
|
+
var prop = (this.type == 'array') ? 'value': 'field';
|
|
2292
|
+
this.hideChilds();
|
|
2293
|
+
|
|
2294
|
+
var oldChilds = this.childs;
|
|
2295
|
+
var oldSort = this.sort;
|
|
2296
|
+
|
|
2297
|
+
// copy the array (the old one will be kept for an undo action
|
|
2298
|
+
this.childs = this.childs.concat();
|
|
2299
|
+
|
|
2300
|
+
// sort the arrays
|
|
2301
|
+
this.childs.sort(function (a, b) {
|
|
2302
|
+
if (a[prop] > b[prop]) return order;
|
|
2303
|
+
if (a[prop] < b[prop]) return -order;
|
|
2304
|
+
return 0;
|
|
2305
|
+
});
|
|
2306
|
+
this.sort = (order == 1) ? 'asc' : 'desc';
|
|
2307
|
+
|
|
2308
|
+
this.editor._onAction('sort', {
|
|
2309
|
+
'node': this,
|
|
2310
|
+
'oldChilds': oldChilds,
|
|
2311
|
+
'oldSort': oldSort,
|
|
2312
|
+
'newChilds': this.childs,
|
|
2313
|
+
'newSort': this.sort
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2316
|
+
this.showChilds();
|
|
2317
|
+
}
|
|
2318
|
+
};
|
|
2319
|
+
|
|
2320
|
+
/**
|
|
2321
|
+
* Create a table row with an append button.
|
|
2322
|
+
* @return {HTMLElement | undefined} buttonAppend or undefined when inapplicable
|
|
2323
|
+
*/
|
|
2324
|
+
Node.prototype.getAppend = function () {
|
|
2325
|
+
if (!this.append) {
|
|
2326
|
+
this.append = new AppendNode(this.editor);
|
|
2327
|
+
this.append.setParent(this);
|
|
2328
|
+
}
|
|
2329
|
+
return this.append.getDom();
|
|
2330
|
+
};
|
|
2331
|
+
|
|
2332
|
+
/**
|
|
2333
|
+
* Find the node from an event target
|
|
2334
|
+
* @param {Node} target
|
|
2335
|
+
* @return {Node | undefined} node or undefined when not found
|
|
2336
|
+
* @static
|
|
2337
|
+
*/
|
|
2338
|
+
Node.getNodeFromTarget = function (target) {
|
|
2339
|
+
while (target) {
|
|
2340
|
+
if (target.node) {
|
|
2341
|
+
return target.node;
|
|
2342
|
+
}
|
|
2343
|
+
target = target.parentNode;
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
return undefined;
|
|
2347
|
+
};
|
|
2348
|
+
|
|
2349
|
+
/**
|
|
2350
|
+
* Get the previously rendered node
|
|
2351
|
+
* @return {Node | null} previousNode
|
|
2352
|
+
* @private
|
|
2353
|
+
*/
|
|
2354
|
+
Node.prototype._previousNode = function () {
|
|
2355
|
+
var prevNode = null;
|
|
2356
|
+
var dom = this.getDom();
|
|
2357
|
+
if (dom && dom.parentNode) {
|
|
2358
|
+
// find the previous field
|
|
2359
|
+
var prevDom = dom;
|
|
2360
|
+
do {
|
|
2361
|
+
prevDom = prevDom.previousSibling;
|
|
2362
|
+
prevNode = Node.getNodeFromTarget(prevDom);
|
|
2363
|
+
}
|
|
2364
|
+
while (prevDom && (prevNode instanceof AppendNode && !prevNode.isVisible()));
|
|
2365
|
+
}
|
|
2366
|
+
return prevNode;
|
|
2367
|
+
};
|
|
2368
|
+
|
|
2369
|
+
/**
|
|
2370
|
+
* Get the next rendered node
|
|
2371
|
+
* @return {Node | null} nextNode
|
|
2372
|
+
* @private
|
|
2373
|
+
*/
|
|
2374
|
+
Node.prototype._nextNode = function () {
|
|
2375
|
+
var nextNode = null;
|
|
2376
|
+
var dom = this.getDom();
|
|
2377
|
+
if (dom && dom.parentNode) {
|
|
2378
|
+
// find the previous field
|
|
2379
|
+
var nextDom = dom;
|
|
2380
|
+
do {
|
|
2381
|
+
nextDom = nextDom.nextSibling;
|
|
2382
|
+
nextNode = Node.getNodeFromTarget(nextDom);
|
|
2383
|
+
}
|
|
2384
|
+
while (nextDom && (nextNode instanceof AppendNode && !nextNode.isVisible()));
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
return nextNode;
|
|
2388
|
+
};
|
|
2389
|
+
|
|
2390
|
+
/**
|
|
2391
|
+
* Get the first rendered node
|
|
2392
|
+
* @return {Node | null} firstNode
|
|
2393
|
+
* @private
|
|
2394
|
+
*/
|
|
2395
|
+
Node.prototype._firstNode = function () {
|
|
2396
|
+
var firstNode = null;
|
|
2397
|
+
var dom = this.getDom();
|
|
2398
|
+
if (dom && dom.parentNode) {
|
|
2399
|
+
var firstDom = dom.parentNode.firstChild;
|
|
2400
|
+
firstNode = Node.getNodeFromTarget(firstDom);
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
return firstNode;
|
|
2404
|
+
};
|
|
2405
|
+
|
|
2406
|
+
/**
|
|
2407
|
+
* Get the last rendered node
|
|
2408
|
+
* @return {Node | null} lastNode
|
|
2409
|
+
* @private
|
|
2410
|
+
*/
|
|
2411
|
+
Node.prototype._lastNode = function () {
|
|
2412
|
+
var lastNode = null;
|
|
2413
|
+
var dom = this.getDom();
|
|
2414
|
+
if (dom && dom.parentNode) {
|
|
2415
|
+
var lastDom = dom.parentNode.lastChild;
|
|
2416
|
+
lastNode = Node.getNodeFromTarget(lastDom);
|
|
2417
|
+
while (lastDom && (lastNode instanceof AppendNode && !lastNode.isVisible())) {
|
|
2418
|
+
lastDom = lastDom.previousSibling;
|
|
2419
|
+
lastNode = Node.getNodeFromTarget(lastDom);
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
return lastNode;
|
|
2423
|
+
};
|
|
2424
|
+
|
|
2425
|
+
/**
|
|
2426
|
+
* Get the next element which can have focus.
|
|
2427
|
+
* @param {Element} elem
|
|
2428
|
+
* @return {Element | null} nextElem
|
|
2429
|
+
* @private
|
|
2430
|
+
*/
|
|
2431
|
+
Node.prototype._previousElement = function (elem) {
|
|
2432
|
+
var dom = this.dom;
|
|
2433
|
+
// noinspection FallthroughInSwitchStatementJS
|
|
2434
|
+
switch (elem) {
|
|
2435
|
+
case dom.value:
|
|
2436
|
+
if (this.fieldEditable) {
|
|
2437
|
+
return dom.field;
|
|
2438
|
+
}
|
|
2439
|
+
// intentional fall through
|
|
2440
|
+
case dom.field:
|
|
2441
|
+
if (this._hasChilds()) {
|
|
2442
|
+
return dom.expand;
|
|
2443
|
+
}
|
|
2444
|
+
// intentional fall through
|
|
2445
|
+
case dom.expand:
|
|
2446
|
+
return dom.menu;
|
|
2447
|
+
case dom.menu:
|
|
2448
|
+
if (dom.drag) {
|
|
2449
|
+
return dom.drag;
|
|
2450
|
+
}
|
|
2451
|
+
// intentional fall through
|
|
2452
|
+
default:
|
|
2453
|
+
return null;
|
|
2454
|
+
}
|
|
2455
|
+
};
|
|
2456
|
+
|
|
2457
|
+
/**
|
|
2458
|
+
* Get the next element which can have focus.
|
|
2459
|
+
* @param {Element} elem
|
|
2460
|
+
* @return {Element | null} nextElem
|
|
2461
|
+
* @private
|
|
2462
|
+
*/
|
|
2463
|
+
Node.prototype._nextElement = function (elem) {
|
|
2464
|
+
var dom = this.dom;
|
|
2465
|
+
// noinspection FallthroughInSwitchStatementJS
|
|
2466
|
+
switch (elem) {
|
|
2467
|
+
case dom.drag:
|
|
2468
|
+
return dom.menu;
|
|
2469
|
+
case dom.menu:
|
|
2470
|
+
if (this._hasChilds()) {
|
|
2471
|
+
return dom.expand;
|
|
2472
|
+
}
|
|
2473
|
+
// intentional fall through
|
|
2474
|
+
case dom.expand:
|
|
2475
|
+
if (this.fieldEditable) {
|
|
2476
|
+
return dom.field;
|
|
2477
|
+
}
|
|
2478
|
+
// intentional fall through
|
|
2479
|
+
case dom.field:
|
|
2480
|
+
if (!this._hasChilds()) {
|
|
2481
|
+
return dom.value;
|
|
2482
|
+
}
|
|
2483
|
+
default:
|
|
2484
|
+
return null;
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2487
|
+
|
|
2488
|
+
/**
|
|
2489
|
+
* Get the dom name of given element. returns null if not found.
|
|
2490
|
+
* For example when element == dom.field, "field" is returned.
|
|
2491
|
+
* @param {Element} element
|
|
2492
|
+
* @return {String | null} elementName Available elements with name: 'drag',
|
|
2493
|
+
* 'menu', 'expand', 'field', 'value'
|
|
2494
|
+
* @private
|
|
2495
|
+
*/
|
|
2496
|
+
Node.prototype._getElementName = function (element) {
|
|
2497
|
+
var dom = this.dom;
|
|
2498
|
+
for (var name in dom) {
|
|
2499
|
+
if (dom.hasOwnProperty(name)) {
|
|
2500
|
+
if (dom[name] == element) {
|
|
2501
|
+
return name;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
return null;
|
|
2506
|
+
};
|
|
2507
|
+
|
|
2508
|
+
/**
|
|
2509
|
+
* Test if this node has childs. This is the case when the node is an object
|
|
2510
|
+
* or array.
|
|
2511
|
+
* @return {boolean} hasChilds
|
|
2512
|
+
* @private
|
|
2513
|
+
*/
|
|
2514
|
+
Node.prototype._hasChilds = function () {
|
|
2515
|
+
return this.type == 'array' || this.type == 'object';
|
|
2516
|
+
};
|
|
2517
|
+
|
|
2518
|
+
// titles with explanation for the different types
|
|
2519
|
+
Node.TYPE_TITLES = {
|
|
2520
|
+
'auto': 'Field type "auto". ' +
|
|
2521
|
+
'The field type is automatically determined from the value ' +
|
|
2522
|
+
'and can be a string, number, boolean, or null.',
|
|
2523
|
+
'object': 'Field type "object". ' +
|
|
2524
|
+
'An object contains an unordered set of key/value pairs.',
|
|
2525
|
+
'array': 'Field type "array". ' +
|
|
2526
|
+
'An array contains an ordered collection of values.',
|
|
2527
|
+
'string': 'Field type "string". ' +
|
|
2528
|
+
'Field type is not determined from the value, ' +
|
|
2529
|
+
'but always returned as string.'
|
|
2530
|
+
};
|
|
2531
|
+
|
|
2532
|
+
/**
|
|
2533
|
+
* Show a contextmenu for this node
|
|
2534
|
+
* @param {HTMLElement} anchor Anchor element to attache the context menu to.
|
|
2535
|
+
* @param {function} [onClose] Callback method called when the context menu
|
|
2536
|
+
* is being closed.
|
|
2537
|
+
*/
|
|
2538
|
+
Node.prototype.showContextMenu = function (anchor, onClose) {
|
|
2539
|
+
var node = this;
|
|
2540
|
+
var titles = Node.TYPE_TITLES;
|
|
2541
|
+
var items = [];
|
|
2542
|
+
|
|
2543
|
+
items.push({
|
|
2544
|
+
'text': 'Type',
|
|
2545
|
+
'title': 'Change the type of this field',
|
|
2546
|
+
'className': 'type-' + this.type,
|
|
2547
|
+
'submenu': [
|
|
2548
|
+
{
|
|
2549
|
+
'text': 'Auto',
|
|
2550
|
+
'className': 'type-auto' +
|
|
2551
|
+
(this.type == 'auto' ? ' selected' : ''),
|
|
2552
|
+
'title': titles.auto,
|
|
2553
|
+
'click': function () {
|
|
2554
|
+
node._onChangeType('auto');
|
|
2555
|
+
}
|
|
2556
|
+
},
|
|
2557
|
+
{
|
|
2558
|
+
'text': 'Array',
|
|
2559
|
+
'className': 'type-array' +
|
|
2560
|
+
(this.type == 'array' ? ' selected' : ''),
|
|
2561
|
+
'title': titles.array,
|
|
2562
|
+
'click': function () {
|
|
2563
|
+
node._onChangeType('array');
|
|
2564
|
+
}
|
|
2565
|
+
},
|
|
2566
|
+
{
|
|
2567
|
+
'text': 'Object',
|
|
2568
|
+
'className': 'type-object' +
|
|
2569
|
+
(this.type == 'object' ? ' selected' : ''),
|
|
2570
|
+
'title': titles.object,
|
|
2571
|
+
'click': function () {
|
|
2572
|
+
node._onChangeType('object');
|
|
2573
|
+
}
|
|
2574
|
+
},
|
|
2575
|
+
{
|
|
2576
|
+
'text': 'String',
|
|
2577
|
+
'className': 'type-string' +
|
|
2578
|
+
(this.type == 'string' ? ' selected' : ''),
|
|
2579
|
+
'title': titles.string,
|
|
2580
|
+
'click': function () {
|
|
2581
|
+
node._onChangeType('string');
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
]
|
|
2585
|
+
});
|
|
2586
|
+
|
|
2587
|
+
if (this._hasChilds()) {
|
|
2588
|
+
var direction = ((this.sort == 'asc') ? 'desc': 'asc');
|
|
2589
|
+
items.push({
|
|
2590
|
+
'text': 'Sort',
|
|
2591
|
+
'title': 'Sort the childs of this ' + this.type,
|
|
2592
|
+
'className': 'sort-' + direction,
|
|
2593
|
+
'click': function () {
|
|
2594
|
+
node._onSort(direction);
|
|
2595
|
+
},
|
|
2596
|
+
'submenu': [
|
|
2597
|
+
{
|
|
2598
|
+
'text': 'Ascending',
|
|
2599
|
+
'className': 'sort-asc',
|
|
2600
|
+
'title': 'Sort the childs of this ' + this.type + ' in ascending order',
|
|
2601
|
+
'click': function () {
|
|
2602
|
+
node._onSort('asc');
|
|
2603
|
+
}
|
|
2604
|
+
},
|
|
2605
|
+
{
|
|
2606
|
+
'text': 'Descending',
|
|
2607
|
+
'className': 'sort-desc',
|
|
2608
|
+
'title': 'Sort the childs of this ' + this.type +' in descending order',
|
|
2609
|
+
'click': function () {
|
|
2610
|
+
node._onSort('desc');
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
]
|
|
2614
|
+
});
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
if (this.parent && this.parent._hasChilds()) {
|
|
2618
|
+
// create a separator
|
|
2619
|
+
items.push({
|
|
2620
|
+
'type': 'separator'
|
|
2621
|
+
});
|
|
2622
|
+
|
|
2623
|
+
// create append button (for last child node only)
|
|
2624
|
+
var childs = node.parent.childs;
|
|
2625
|
+
if (node == childs[childs.length - 1]) {
|
|
2626
|
+
items.push({
|
|
2627
|
+
'text': 'Append',
|
|
2628
|
+
'title': 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)',
|
|
2629
|
+
'submenuTitle': 'Select the type of the field to be appended',
|
|
2630
|
+
'className': 'append',
|
|
2631
|
+
'click': function () {
|
|
2632
|
+
node._onAppend('', '', 'auto');
|
|
2633
|
+
},
|
|
2634
|
+
'submenu': [
|
|
2635
|
+
{
|
|
2636
|
+
'text': 'Auto',
|
|
2637
|
+
'className': 'type-auto',
|
|
2638
|
+
'title': titles.auto,
|
|
2639
|
+
'click': function () {
|
|
2640
|
+
node._onAppend('', '', 'auto');
|
|
2641
|
+
}
|
|
2642
|
+
},
|
|
2643
|
+
{
|
|
2644
|
+
'text': 'Array',
|
|
2645
|
+
'className': 'type-array',
|
|
2646
|
+
'title': titles.array,
|
|
2647
|
+
'click': function () {
|
|
2648
|
+
node._onAppend('', []);
|
|
2649
|
+
}
|
|
2650
|
+
},
|
|
2651
|
+
{
|
|
2652
|
+
'text': 'Object',
|
|
2653
|
+
'className': 'type-object',
|
|
2654
|
+
'title': titles.object,
|
|
2655
|
+
'click': function () {
|
|
2656
|
+
node._onAppend('', {});
|
|
2657
|
+
}
|
|
2658
|
+
},
|
|
2659
|
+
{
|
|
2660
|
+
'text': 'String',
|
|
2661
|
+
'className': 'type-string',
|
|
2662
|
+
'title': titles.string,
|
|
2663
|
+
'click': function () {
|
|
2664
|
+
node._onAppend('', '', 'string');
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
]
|
|
2668
|
+
});
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
// create insert button
|
|
2672
|
+
items.push({
|
|
2673
|
+
'text': 'Insert',
|
|
2674
|
+
'title': 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)',
|
|
2675
|
+
'submenuTitle': 'Select the type of the field to be inserted',
|
|
2676
|
+
'className': 'insert',
|
|
2677
|
+
'click': function () {
|
|
2678
|
+
node._onInsertBefore('', '', 'auto');
|
|
2679
|
+
},
|
|
2680
|
+
'submenu': [
|
|
2681
|
+
{
|
|
2682
|
+
'text': 'Auto',
|
|
2683
|
+
'className': 'type-auto',
|
|
2684
|
+
'title': titles.auto,
|
|
2685
|
+
'click': function () {
|
|
2686
|
+
node._onInsertBefore('', '', 'auto');
|
|
2687
|
+
}
|
|
2688
|
+
},
|
|
2689
|
+
{
|
|
2690
|
+
'text': 'Array',
|
|
2691
|
+
'className': 'type-array',
|
|
2692
|
+
'title': titles.array,
|
|
2693
|
+
'click': function () {
|
|
2694
|
+
node._onInsertBefore('', []);
|
|
2695
|
+
}
|
|
2696
|
+
},
|
|
2697
|
+
{
|
|
2698
|
+
'text': 'Object',
|
|
2699
|
+
'className': 'type-object',
|
|
2700
|
+
'title': titles.object,
|
|
2701
|
+
'click': function () {
|
|
2702
|
+
node._onInsertBefore('', {});
|
|
2703
|
+
}
|
|
2704
|
+
},
|
|
2705
|
+
{
|
|
2706
|
+
'text': 'String',
|
|
2707
|
+
'className': 'type-string',
|
|
2708
|
+
'title': titles.string,
|
|
2709
|
+
'click': function () {
|
|
2710
|
+
node._onInsertBefore('', '', 'string');
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
]
|
|
2714
|
+
});
|
|
2715
|
+
|
|
2716
|
+
// create duplicate button
|
|
2717
|
+
items.push({
|
|
2718
|
+
'text': 'Duplicate',
|
|
2719
|
+
'title': 'Duplicate this field (Ctrl+D)',
|
|
2720
|
+
'className': 'duplicate',
|
|
2721
|
+
'click': function () {
|
|
2722
|
+
node._onDuplicate();
|
|
2723
|
+
}
|
|
2724
|
+
});
|
|
2725
|
+
|
|
2726
|
+
// create remove button
|
|
2727
|
+
items.push({
|
|
2728
|
+
'text': 'Remove',
|
|
2729
|
+
'title': 'Remove this field (Ctrl+Del)',
|
|
2730
|
+
'className': 'remove',
|
|
2731
|
+
'click': function () {
|
|
2732
|
+
node._onRemove();
|
|
2733
|
+
}
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
var menu = new ContextMenu(items, {close: onClose});
|
|
2738
|
+
menu.show(anchor);
|
|
2739
|
+
};
|
|
2740
|
+
|
|
2741
|
+
/**
|
|
2742
|
+
* get the type of a value
|
|
2743
|
+
* @param {*} value
|
|
2744
|
+
* @return {String} type Can be 'object', 'array', 'string', 'auto'
|
|
2745
|
+
* @private
|
|
2746
|
+
*/
|
|
2747
|
+
Node.prototype._getType = function(value) {
|
|
2748
|
+
if (value instanceof Array) {
|
|
2749
|
+
return 'array';
|
|
2750
|
+
}
|
|
2751
|
+
if (value instanceof Object) {
|
|
2752
|
+
return 'object';
|
|
2753
|
+
}
|
|
2754
|
+
if (typeof(value) == 'string' && typeof(this._stringCast(value)) != 'string') {
|
|
2755
|
+
return 'string';
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
return 'auto';
|
|
2759
|
+
};
|
|
2760
|
+
|
|
2761
|
+
/**
|
|
2762
|
+
* cast contents of a string to the correct type. This can be a string,
|
|
2763
|
+
* a number, a boolean, etc
|
|
2764
|
+
* @param {String} str
|
|
2765
|
+
* @return {*} castedStr
|
|
2766
|
+
* @private
|
|
2767
|
+
*/
|
|
2768
|
+
Node.prototype._stringCast = function(str) {
|
|
2769
|
+
var lower = str.toLowerCase(),
|
|
2770
|
+
num = Number(str), // will nicely fail with '123ab'
|
|
2771
|
+
numFloat = parseFloat(str); // will nicely fail with ' '
|
|
2772
|
+
|
|
2773
|
+
if (str == '') {
|
|
2774
|
+
return '';
|
|
2775
|
+
}
|
|
2776
|
+
else if (lower == 'null') {
|
|
2777
|
+
return null;
|
|
2778
|
+
}
|
|
2779
|
+
else if (lower == 'true') {
|
|
2780
|
+
return true;
|
|
2781
|
+
}
|
|
2782
|
+
else if (lower == 'false') {
|
|
2783
|
+
return false;
|
|
2784
|
+
}
|
|
2785
|
+
else if (!isNaN(num) && !isNaN(numFloat)) {
|
|
2786
|
+
return num;
|
|
2787
|
+
}
|
|
2788
|
+
else {
|
|
2789
|
+
return str;
|
|
2790
|
+
}
|
|
2791
|
+
};
|
|
2792
|
+
|
|
2793
|
+
/**
|
|
2794
|
+
* escape a text, such that it can be displayed safely in an HTML element
|
|
2795
|
+
* @param {String} text
|
|
2796
|
+
* @return {String} escapedText
|
|
2797
|
+
* @private
|
|
2798
|
+
*/
|
|
2799
|
+
Node.prototype._escapeHTML = function (text) {
|
|
2800
|
+
var htmlEscaped = String(text)
|
|
2801
|
+
.replace(/</g, '<')
|
|
2802
|
+
.replace(/>/g, '>')
|
|
2803
|
+
.replace(/ /g, ' ') // replace double space with an nbsp and space
|
|
2804
|
+
.replace(/^ /, ' ') // space at start
|
|
2805
|
+
.replace(/ $/, ' '); // space at end
|
|
2806
|
+
|
|
2807
|
+
var json = JSON.stringify(htmlEscaped);
|
|
2808
|
+
return json.substring(1, json.length - 1);
|
|
2809
|
+
};
|
|
2810
|
+
|
|
2811
|
+
/**
|
|
2812
|
+
* unescape a string.
|
|
2813
|
+
* @param {String} escapedText
|
|
2814
|
+
* @return {String} text
|
|
2815
|
+
* @private
|
|
2816
|
+
*/
|
|
2817
|
+
Node.prototype._unescapeHTML = function (escapedText) {
|
|
2818
|
+
var json = '"' + this._escapeJSON(escapedText) + '"';
|
|
2819
|
+
var htmlEscaped = util.parse(json);
|
|
2820
|
+
return htmlEscaped
|
|
2821
|
+
.replace(/</g, '<')
|
|
2822
|
+
.replace(/>/g, '>')
|
|
2823
|
+
.replace(/ /g, ' ');
|
|
2824
|
+
};
|
|
2825
|
+
|
|
2826
|
+
/**
|
|
2827
|
+
* escape a text to make it a valid JSON string. The method will:
|
|
2828
|
+
* - replace unescaped double quotes with '\"'
|
|
2829
|
+
* - replace unescaped backslash with '\\'
|
|
2830
|
+
* - replace returns with '\n'
|
|
2831
|
+
* @param {String} text
|
|
2832
|
+
* @return {String} escapedText
|
|
2833
|
+
* @private
|
|
2834
|
+
*/
|
|
2835
|
+
Node.prototype._escapeJSON = function (text) {
|
|
2836
|
+
// TODO: replace with some smart regex (only when a new solution is faster!)
|
|
2837
|
+
var escaped = '';
|
|
2838
|
+
var i = 0, iMax = text.length;
|
|
2839
|
+
while (i < iMax) {
|
|
2840
|
+
var c = text.charAt(i);
|
|
2841
|
+
if (c == '\n') {
|
|
2842
|
+
escaped += '\\n';
|
|
2843
|
+
}
|
|
2844
|
+
else if (c == '\\') {
|
|
2845
|
+
escaped += c;
|
|
2846
|
+
i++;
|
|
2847
|
+
|
|
2848
|
+
c = text.charAt(i);
|
|
2849
|
+
if ('"\\/bfnrtu'.indexOf(c) == -1) {
|
|
2850
|
+
escaped += '\\'; // no valid escape character
|
|
2851
|
+
}
|
|
2852
|
+
escaped += c;
|
|
2853
|
+
}
|
|
2854
|
+
else if (c == '"') {
|
|
2855
|
+
escaped += '\\"';
|
|
2856
|
+
}
|
|
2857
|
+
else {
|
|
2858
|
+
escaped += c;
|
|
2859
|
+
}
|
|
2860
|
+
i++;
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
return escaped;
|
|
2864
|
+
};
|