Changed TreeEditor and TextEditor into mixins for JSONEditor

This commit is contained in:
jos 2014-05-30 10:53:57 +02:00
parent 955232d587
commit 0c1331ba12
4 changed files with 96 additions and 117 deletions

View File

@ -1,4 +1,4 @@
define(['./TreeEditor', './TextEditor', './util'], function (TreeEditor, TextEditor, util) { define(['./treemode', './textmode', './util'], function (treemode, textmode, util) {
/** /**
* @constructor JSONEditor * @constructor JSONEditor
@ -46,17 +46,17 @@ define(['./TreeEditor', './TextEditor', './util'], function (TreeEditor, TextEdi
/** /**
* Configuration for all registered modes. Example: * Configuration for all registered modes. Example:
* { * {
* tree: { * tree: {
* editor: TreeEditor, * mixin: TreeEditor,
* data: 'json' * data: 'json'
* }, * },
* text: { * text: {
* editor: TextEditor, * mixin: TextEditor,
* data: 'text' * data: 'text'
* } * }
* } * }
* *
* @type { Object.<String, {editor: Object, data: String} > } * @type { Object.<String, {mixin: Object, data: String} > }
*/ */
JSONEditor.modes = {}; JSONEditor.modes = {};
@ -156,8 +156,8 @@ define(['./TreeEditor', './TextEditor', './util'], function (TreeEditor, TextEdi
this._delete(); this._delete();
util.clear(this); util.clear(this);
util.extend(this, config.editor.prototype); util.extend(this, config.mixin);
this._create(container, options); this.create(container, options);
this.setName(name); this.setName(name);
this.setText(data); this.setText(data);
@ -169,8 +169,8 @@ define(['./TreeEditor', './TextEditor', './util'], function (TreeEditor, TextEdi
this._delete(); this._delete();
util.clear(this); util.clear(this);
util.extend(this, config.editor.prototype); util.extend(this, config.mixin);
this._create(container, options); this.create(container, options);
this.setName(name); this.setName(name);
this.set(data); this.set(data);
@ -215,26 +215,30 @@ define(['./TreeEditor', './TextEditor', './util'], function (TreeEditor, TextEdi
}; };
/** /**
* Register modes for the JSON Editor * Register a plugin with one ore multiple modes for the JSON Editor
* TODO: describe the mode format * TODO: describe the mode format
* @param {Object} modes An object with the mode names as keys, and an object * @param {Object} modes An object with the mode names as keys, and an object
* defining the mode as value * defining the mode as value
*/ */
JSONEditor.registerModes = function (modes) { JSONEditor.register = function (modes) {
for (var mode in modes) { for (var mode in modes) {
if (modes.hasOwnProperty(mode)) { if (modes.hasOwnProperty(mode)) {
if (mode in JSONEditor.modes) { if (mode in JSONEditor.modes) {
throw new Error('Mode "' + mode + '" already registered'); throw new Error('Mode "' + mode + '" already registered');
} }
// TODO: validate the new mode mixin,
// must have functions: create, get, getText, set, setText
// may not have functions: setMode, register
JSONEditor.modes[mode] = modes[mode]; JSONEditor.modes[mode] = modes[mode];
} }
} }
}; };
// register TreeEditor and TextEditor // register tree and text modes
JSONEditor.registerModes(TreeEditor.modes); JSONEditor.register(treemode);
JSONEditor.registerModes(TextEditor.modes); JSONEditor.register(textmode);
return JSONEditor; return JSONEditor;
}); });

View File

@ -1,4 +1,4 @@
define(['./util'], function (util) { define(['./ContextMenu', './util'], function (ContextMenu, util) {
/** /**
* A factory function to create an AppendNode, which depends on a Node * A factory function to create an AppendNode, which depends on a Node

View File

@ -1,8 +1,10 @@
define(['./modebox', './util'], function (modebox, util) { define(['./modebox', './util'], function (modebox, util) {
// create a mixin with the functions for text mode
var textmode = {};
/** /**
* Create a TextEditor and attach it to given container * Create a text editor
* @constructor TextEditor
* @param {Element} container * @param {Element} container
* @param {Object} [options] Object with options. available options: * @param {Object} [options] Object with options. available options:
* {String} mode Available values: * {String} mode Available values:
@ -13,24 +15,9 @@ define(['./modebox', './util'], function (modebox, util) {
* {function} change Callback method * {function} change Callback method
* triggered on change * triggered on change
* @param {JSON | String} [json] initial contents of the formatter * @param {JSON | String} [json] initial contents of the formatter
*/
function TextEditor(container, options, json) {
if (!(this instanceof TextEditor)) {
throw new Error('TextEditor constructor called without "new".');
}
this._create(container, options, json);
}
/**
* Create a TextEditor and attach it to given container
* @constructor TextEditor
* @param {Element} container
* @param {Object} [options] See description in constructor
* @param {JSON | String} [json] initial contents of the formatter
* @private * @private
*/ */
TextEditor.prototype._create = function (container, options, json) { textmode.create = function (container, options, json) {
// read options // read options
options = options || {}; options = options || {};
this.options = options; this.options = options;
@ -62,7 +49,7 @@ define(['./modebox', './util'], function (modebox, util) {
this.frame = document.createElement('div'); this.frame = document.createElement('div');
this.frame.className = 'jsoneditor'; this.frame.className = 'jsoneditor';
this.frame.onclick = function (event) { this.frame.onclick = function (event) {
// prevent default submit action when TextEditor is located inside a form // prevent default submit action when the editor is located inside a form
event.preventDefault(); event.preventDefault();
}; };
@ -185,7 +172,7 @@ define(['./modebox', './util'], function (modebox, util) {
* Detach the editor from the DOM * Detach the editor from the DOM
* @private * @private
*/ */
TextEditor.prototype._delete = function () { textmode._delete = function () {
if (this.frame && this.container && this.frame.parentNode == this.container) { if (this.frame && this.container && this.frame.parentNode == this.container) {
this.container.removeChild(this.frame); this.container.removeChild(this.frame);
} }
@ -197,7 +184,7 @@ define(['./modebox', './util'], function (modebox, util) {
* @param {Error} err * @param {Error} err
* @private * @private
*/ */
TextEditor.prototype._onError = function(err) { textmode._onError = function(err) {
// TODO: onError is deprecated since version 2.2.0. cleanup some day // TODO: onError is deprecated since version 2.2.0. cleanup some day
if (typeof this.onError === 'function') { if (typeof this.onError === 'function') {
util.log('WARNING: JSONEditor.onError is deprecated. ' + util.log('WARNING: JSONEditor.onError is deprecated. ' +
@ -216,7 +203,7 @@ define(['./modebox', './util'], function (modebox, util) {
/** /**
* Compact the code in the formatter * Compact the code in the formatter
*/ */
TextEditor.prototype.compact = function () { textmode.compact = function () {
var json = util.parse(this.getText()); var json = util.parse(this.getText());
this.setText(JSON.stringify(json)); this.setText(JSON.stringify(json));
}; };
@ -224,7 +211,7 @@ define(['./modebox', './util'], function (modebox, util) {
/** /**
* Format the code in the formatter * Format the code in the formatter
*/ */
TextEditor.prototype.format = function () { textmode.format = function () {
var json = util.parse(this.getText()); var json = util.parse(this.getText());
this.setText(JSON.stringify(json, null, this.indentation)); this.setText(JSON.stringify(json, null, this.indentation));
}; };
@ -232,7 +219,7 @@ define(['./modebox', './util'], function (modebox, util) {
/** /**
* Set focus to the formatter * Set focus to the formatter
*/ */
TextEditor.prototype.focus = function () { textmode.focus = function () {
if (this.textarea) { if (this.textarea) {
this.textarea.focus(); this.textarea.focus();
} }
@ -244,7 +231,7 @@ define(['./modebox', './util'], function (modebox, util) {
/** /**
* Resize the formatter * Resize the formatter
*/ */
TextEditor.prototype.resize = function () { textmode.resize = function () {
if (this.editor) { if (this.editor) {
var force = false; var force = false;
this.editor.resize(force); this.editor.resize(force);
@ -255,7 +242,7 @@ define(['./modebox', './util'], function (modebox, util) {
* Set json data in the formatter * Set json data in the formatter
* @param {Object} json * @param {Object} json
*/ */
TextEditor.prototype.set = function(json) { textmode.set = function(json) {
this.setText(JSON.stringify(json, null, this.indentation)); this.setText(JSON.stringify(json, null, this.indentation));
}; };
@ -263,15 +250,15 @@ define(['./modebox', './util'], function (modebox, util) {
* Get json data from the formatter * Get json data from the formatter
* @return {Object} json * @return {Object} json
*/ */
TextEditor.prototype.get = function() { textmode.get = function() {
return util.parse(this.getText()); return util.parse(this.getText());
}; };
/** /**
* Get the text contents of the TextEditor * Get the text contents of the editor
* @return {String} jsonText * @return {String} jsonText
*/ */
TextEditor.prototype.getText = function() { textmode.getText = function() {
if (this.textarea) { if (this.textarea) {
return this.textarea.value; return this.textarea.value;
} }
@ -282,10 +269,10 @@ define(['./modebox', './util'], function (modebox, util) {
}; };
/** /**
* Set the text contents of the TextEditor * Set the text contents of the editor
* @param {String} jsonText * @param {String} jsonText
*/ */
TextEditor.prototype.setText = function(jsonText) { textmode.setText = function(jsonText) {
if (this.textarea) { if (this.textarea) {
this.textarea.value = jsonText; this.textarea.value = jsonText;
} }
@ -295,18 +282,16 @@ define(['./modebox', './util'], function (modebox, util) {
}; };
// define modes // define modes
TextEditor.modes = { return {
text: { text: {
editor: TextEditor, mixin: textmode,
data: 'text', data: 'text',
load: TextEditor.prototype.format load: textmode.format
}, },
code: { code: {
editor: TextEditor, mixin: textmode,
data: 'text', data: 'text',
load: TextEditor.prototype.format load: textmode.format
} }
}; };
return TextEditor;
}); });

View File

@ -1,8 +1,11 @@
define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './util'], define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './util'],
function (Highlighter, History, SearchBox, Node, modebox, util) { function (Highlighter, History, SearchBox, Node, modebox, util) {
// create a mixin with the functions for tree mode
var treemode = {};
/** /**
* @constructor TreeEditor * Create a tree editor
* @param {Element} container Container element * @param {Element} container Container element
* @param {Object} [options] Object with options. available options: * @param {Object} [options] Object with options. available options:
* {String} mode Editor mode. Available values: * {String} mode Editor mode. Available values:
@ -16,23 +19,9 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* on change of contents * on change of contents
* {String} name Field name for the root node. * {String} name Field name for the root node.
* @param {Object | undefined} json JSON object * @param {Object | undefined} json JSON object
*/
function TreeEditor(container, options, json) {
if (!(this instanceof TreeEditor)) {
throw new Error('TreeEditor constructor called without "new".');
}
this._create(container, options, json);
}
/**
* Create the TreeEditor
* @param {Element} container Container element
* @param {Object} [options] See description in constructor
* @param {Object | undefined} json JSON object
* @private * @private
*/ */
TreeEditor.prototype._create = function (container, options, json) { treemode.create = function (container, options, json) {
if (!container) { if (!container) {
throw new Error('No container element provided.'); throw new Error('No container element provided.');
} }
@ -57,7 +46,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* Detach the editor from the DOM * Detach the editor from the DOM
* @private * @private
*/ */
TreeEditor.prototype._delete = function () { treemode._delete = function () {
if (this.frame && this.container && this.frame.parentNode == this.container) { if (this.frame && this.container && this.frame.parentNode == this.container) {
this.container.removeChild(this.frame); this.container.removeChild(this.frame);
} }
@ -68,7 +57,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* @param {Object} [options] See description in constructor * @param {Object} [options] See description in constructor
* @private * @private
*/ */
TreeEditor.prototype._setOptions = function (options) { treemode._setOptions = function (options) {
this.options = { this.options = {
search: true, search: true,
history: true, history: true,
@ -93,8 +82,11 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
}; };
}; };
// node currently being edited // node currently being edited
TreeEditor.focusNode = undefined; var focusNode = undefined;
// dom having focus
var domFocus = null;
/** /**
* Set JSON object in editor * Set JSON object in editor
@ -102,7 +94,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* @param {String} [name] Optional field name for the root node. * @param {String} [name] Optional field name for the root node.
* Can also be set using setName(name). * Can also be set using setName(name).
*/ */
TreeEditor.prototype.set = function (json, name) { treemode.set = function (json, name) {
// adjust field name for root node // adjust field name for root node
if (name) { if (name) {
// TODO: deprecated since version 2.2.0. Cleanup some day. // TODO: deprecated since version 2.2.0. Cleanup some day.
@ -143,10 +135,10 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* Get JSON object from editor * Get JSON object from editor
* @return {Object | undefined} json * @return {Object | undefined} json
*/ */
TreeEditor.prototype.get = function () { treemode.get = function () {
// remove focus from currently edited node // remove focus from currently edited node
if (TreeEditor.focusNode) { if (focusNode) {
TreeEditor.focusNode.blur(); focusNode.blur();
} }
if (this.node) { if (this.node) {
@ -158,18 +150,18 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
}; };
/** /**
* Get the text contents of the TreeEditor * Get the text contents of the editor
* @return {String} jsonText * @return {String} jsonText
*/ */
TreeEditor.prototype.getText = function() { treemode.getText = function() {
return JSON.stringify(this.get()); return JSON.stringify(this.get());
}; };
/** /**
* Set the text contents of the TreeEditor * Set the text contents of the editor
* @param {String} jsonText * @param {String} jsonText
*/ */
TreeEditor.prototype.setText = function(jsonText) { treemode.setText = function(jsonText) {
this.set(util.parse(jsonText)); this.set(util.parse(jsonText));
}; };
@ -177,7 +169,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* Set a field name for the root node. * Set a field name for the root node.
* @param {String | undefined} name * @param {String | undefined} name
*/ */
TreeEditor.prototype.setName = function (name) { treemode.setName = function (name) {
this.options.name = name; this.options.name = name;
if (this.node) { if (this.node) {
this.node.updateField(this.options.name); this.node.updateField(this.options.name);
@ -188,14 +180,14 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* Get the field name for the root node. * Get the field name for the root node.
* @return {String | undefined} name * @return {String | undefined} name
*/ */
TreeEditor.prototype.getName = function () { treemode.getName = function () {
return this.options.name; return this.options.name;
}; };
/** /**
* Remove the root node from the editor * Remove the root node from the editor
*/ */
TreeEditor.prototype.clear = function () { treemode.clear = function () {
if (this.node) { if (this.node) {
this.node.collapse(); this.node.collapse();
this.tbody.removeChild(this.node.getDom()); this.tbody.removeChild(this.node.getDom());
@ -208,7 +200,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* @param {Node} node * @param {Node} node
* @private * @private
*/ */
TreeEditor.prototype._setRoot = function (node) { treemode._setRoot = function (node) {
this.clear(); this.clear();
this.node = node; this.node = node;
@ -229,7 +221,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* the result is found ('field' or * the result is found ('field' or
* 'value') * 'value')
*/ */
TreeEditor.prototype.search = function (text) { treemode.search = function (text) {
var results; var results;
if (this.node) { if (this.node) {
this.content.removeChild(this.table); // Take the table offline this.content.removeChild(this.table); // Take the table offline
@ -246,7 +238,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
/** /**
* Expand all nodes * Expand all nodes
*/ */
TreeEditor.prototype.expandAll = function () { treemode.expandAll = function () {
if (this.node) { if (this.node) {
this.content.removeChild(this.table); // Take the table offline this.content.removeChild(this.table); // Take the table offline
this.node.expand(); this.node.expand();
@ -257,7 +249,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
/** /**
* Collapse all nodes * Collapse all nodes
*/ */
TreeEditor.prototype.collapseAll = function () { treemode.collapseAll = function () {
if (this.node) { if (this.node) {
this.content.removeChild(this.table); // Take the table offline this.content.removeChild(this.table); // Take the table offline
this.node.collapse(); this.node.collapse();
@ -279,7 +271,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* needed to undo or redo the action. * needed to undo or redo the action.
* @private * @private
*/ */
TreeEditor.prototype._onAction = function (action, params) { treemode._onAction = function (action, params) {
// add an action to the history // add an action to the history
if (this.history) { if (this.history) {
this.history.add(action, params); this.history.add(action, params);
@ -301,7 +293,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* editor contents, or below the bottom. * editor contents, or below the bottom.
* @param {Number} mouseY Absolute mouse position in pixels * @param {Number} mouseY Absolute mouse position in pixels
*/ */
TreeEditor.prototype.startAutoScroll = function (mouseY) { treemode.startAutoScroll = function (mouseY) {
var me = this; var me = this;
var content = this.content; var content = this.content;
var top = util.getAbsoluteTop(content); var top = util.getAbsoluteTop(content);
@ -341,7 +333,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
/** /**
* Stop auto scrolling. Only applicable when scrolling * Stop auto scrolling. Only applicable when scrolling
*/ */
TreeEditor.prototype.stopAutoScroll = function () { treemode.stopAutoScroll = function () {
if (this.autoScrollTimer) { if (this.autoScrollTimer) {
clearTimeout(this.autoScrollTimer); clearTimeout(this.autoScrollTimer);
delete this.autoScrollTimer; delete this.autoScrollTimer;
@ -353,7 +345,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
/** /**
* Set the focus to an element in the TreeEditor, set text selection, and * Set the focus to an element in the editor, set text selection, and
* set scroll position. * set scroll position.
* @param {Object} selection An object containing fields: * @param {Object} selection An object containing fields:
* {Element | undefined} dom The dom element * {Element | undefined} dom The dom element
@ -361,7 +353,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* {Range | TextRange} range A text selection * {Range | TextRange} range A text selection
* {Number} scrollTop Scroll position * {Number} scrollTop Scroll position
*/ */
TreeEditor.prototype.setSelection = function (selection) { treemode.setSelection = function (selection) {
if (!selection) { if (!selection) {
return; return;
} }
@ -386,9 +378,9 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* {Range | TextRange} range A text selection * {Range | TextRange} range A text selection
* {Number} scrollTop Scroll position * {Number} scrollTop Scroll position
*/ */
TreeEditor.prototype.getSelection = function () { treemode.getSelection = function () {
return { return {
dom: TreeEditor.domFocus, dom: domFocus,
scrollTop: this.content ? this.content.scrollTop : 0, scrollTop: this.content ? this.content.scrollTop : 0,
range: util.getSelectionOffset() range: util.getSelectionOffset()
}; };
@ -403,7 +395,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* when animation is finished, or false * when animation is finished, or false
* when not. * when not.
*/ */
TreeEditor.prototype.scrollTo = function (top, callback) { treemode.scrollTo = function (top, callback) {
var content = this.content; var content = this.content;
if (content) { if (content) {
var editor = this; var editor = this;
@ -454,7 +446,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* Create main frame * Create main frame
* @private * @private
*/ */
TreeEditor.prototype._createFrame = function () { treemode._createFrame = function () {
// create the frame // create the frame
this.frame = document.createElement('div'); this.frame = document.createElement('div');
this.frame.className = 'jsoneditor'; this.frame.className = 'jsoneditor';
@ -470,7 +462,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
onEvent(event); onEvent(event);
// prevent default submit action of buttons when TreeEditor is located // prevent default submit action of buttons when editor is located
// inside a form // inside a form
if (target.nodeName == 'BUTTON') { if (target.nodeName == 'BUTTON') {
event.preventDefault(); event.preventDefault();
@ -564,7 +556,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* Perform an undo action * Perform an undo action
* @private * @private
*/ */
TreeEditor.prototype._onUndo = function () { treemode._onUndo = function () {
if (this.history) { if (this.history) {
// undo last action // undo last action
this.history.undo(); this.history.undo();
@ -580,7 +572,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* Perform a redo action * Perform a redo action
* @private * @private
*/ */
TreeEditor.prototype._onRedo = function () { treemode._onRedo = function () {
if (this.history) { if (this.history) {
// redo last action // redo last action
this.history.redo(); this.history.redo();
@ -597,7 +589,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* @param event * @param event
* @private * @private
*/ */
TreeEditor.prototype._onEvent = function (event) { treemode._onEvent = function (event) {
var target = event.target; var target = event.target;
if (event.type == 'keydown') { if (event.type == 'keydown') {
@ -605,7 +597,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
} }
if (event.type == 'focus') { if (event.type == 'focus') {
TreeEditor.domFocus = target; domFocus = target;
} }
var node = Node.getNodeFromTarget(target); var node = Node.getNodeFromTarget(target);
@ -619,7 +611,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* @param {Event} event * @param {Event} event
* @private * @private
*/ */
TreeEditor.prototype._onKeyDown = function (event) { treemode._onKeyDown = function (event) {
var keynum = event.which || event.keyCode; var keynum = event.which || event.keyCode;
var ctrlKey = event.ctrlKey; var ctrlKey = event.ctrlKey;
var shiftKey = event.shiftKey; var shiftKey = event.shiftKey;
@ -628,7 +620,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
if (keynum == 9) { // Tab or Shift+Tab if (keynum == 9) { // Tab or Shift+Tab
setTimeout(function () { setTimeout(function () {
// select all text when moving focus to an editable div // select all text when moving focus to an editable div
util.selectContentEditable(TreeEditor.domFocus); util.selectContentEditable(domFocus);
}, 0); }, 0);
} }
@ -676,7 +668,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
* Create main table * Create main table
* @private * @private
*/ */
TreeEditor.prototype._createTable = function () { treemode._createTable = function () {
var contentOuter = document.createElement('div'); var contentOuter = document.createElement('div');
contentOuter.className = 'outer'; contentOuter.className = 'outer';
this.contentOuter = contentOuter; this.contentOuter = contentOuter;
@ -712,20 +704,18 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modebox', './u
}; };
// define modes // define modes
TreeEditor.modes = { return {
tree: { tree: {
editor: TreeEditor, mixin: treemode,
data: 'json' data: 'json'
}, },
view: { view: {
editor: TreeEditor, mixin: treemode,
data: 'json' data: 'json'
}, },
form: { form: {
editor: TreeEditor, mixin: treemode,
data: 'json' data: 'json'
} }
}; };
return TreeEditor;
}); });