diff --git a/changelog.txt b/changelog.txt index 1ca1741a6..aea545279 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,88 +4,91 @@ http://jsoneditoronline.org , version 1.2.0 -- added search functionality. Search results are expanded and highlighed. -- the position of the vertical separator between left and right panel is stored. -- link to the sourcecode on github added at the bottom of the page. -- refinements in the layout: fonts, colors, icons. -- fixed leading an trailing spaces not being displayed in the editor. -- fixed wrapping of long words and urls in Chrome. -- fixed ignoring functions and undefined values in the loaded JSON object +- Added search functionality. Search results are expanded and highlighed. + Quickkeys in the search box: Enter (next), Shift+Enter (previous), Ctrl+Enter + (search again). Quickkeys in the editor: Ctrl+F (focus to search box), + F3 (next), Shift+F3 (previous). +- The position of the vertical separator between left and right panel is stored. +- Link to the sourcecode on github added at the bottom of the page. +- Refinements in the layout: fonts, colors, icons. +- Fixed leading an trailing spaces not being displayed in the editor. +- Fixed wrapping of long words and urls in Chrome. +- Fixed ignoring functions and undefined values in the loaded JSON object (they where interpreted as empty object and string instead of being ignored). 2012-07-01, version 1.1.1 -- fixed global event listener for the focus/blur events, causing changes in +- Fixed global event listener for the focus/blur events, causing changes in fields and values not always being registered. -- fixed a css issue with Firefox (box-sizing of the editor). +- Fixed a css issue with Firefox (box-sizing of the editor). 2012-04-24, version 1.1 -- fixed a bug. Dragging an object down which has been expanded and collapsed +- Fixed a bug. Dragging an object down which has been expanded and collapsed again did not work. -- using a minified version of jsoneditor.js, to improve page load time and +- Using a minified version of jsoneditor.js, to improve page load time and save bandwidth. 2012-04-21, version 1.0 -- values are no longer aligned in one global column, but placed directly right +- Values are no longer aligned in one global column, but placed directly right from the field. Having field and value close together improves readability, especially in case of deeply nested data. -- values are colorized by their type: strings are green, values read, booleans +- Values are colorized by their type: strings are green, values read, booleans blue, and null is purple. -- font is changed to a monotype font for better readability. -- special characters like \t are now handled nicely. -- overall performance and memory usage improved. -- when clicking on whitespace, focus is set to the closest field or value. +- Font is changed to a monotype font for better readability. +- Special characters like \t are now handled nicely. +- Overall performance and memory usage improved. +- When clicking on whitespace, focus is set to the closest field or value. - some other small interface tweaks. -- fixed a bug with casting a value from type auto to string and vice versa +- Fixed a bug with casting a value from type auto to string and vice versa (the value was not casted at all). 2012-03-01, version 0.9.10 -- nicer looking select box for the field types, with icons. -- improved drag and drop: better visualized, and now working in all browers. -- previous values will be restored after changing the type of a field. When +- Nicer looking select box for the field types, with icons. +- Improved drag and drop: better visualized, and now working in all browers. +- Previous values will be restored after changing the type of a field. When changing the type back, the previous value or childs will be restored. -- when hovering buttons (fieldtype, duplicate, delete, add) or when dragging +- When hovering buttons (fieldtype, duplicate, delete, add) or when dragging a field, corresponding field including its childs is highlighted. This makes it easier to see what part of the data will be edited. -- errors are now displayed in a message window on top of the page instead of +- Errors are now displayed in a message window on top of the page instead of an alert which pops up. -- fixed a bug with displaying enters in fields. -- fixed a bug where the last trailing enter was removed when setting json +- Fixed a bug with displaying enters in fields. +- Fixed a bug where the last trailing enter was removed when setting json in the editor. -- added a fix to get around Internet Explorer 8 issues with vertical scrollbars. +- Added a fix to get around Internet Explorer 8 issues with vertical scrollbars. 2012-01-29, version 0.9.9 -- fields can be duplicated -- support for drag and drop: +- Fields can be duplicated +- Support for drag and drop: - fields in the editor itself can be moved via drag and drop - fields can be exported from the editor as JSON - external JSON can be dropped inside the editor -- when changing type from array to object and vice versa, childs will be +- When changing type from array to object and vice versa, childs will be maintained instead of removed. -- updated interface. Works now in IE8 too. +- Updated interface. Works now in IE8 too. 2012-01-16, version 0.9.8 -- improved the performance of expanding a node with all its childs. +- Improved the performance of expanding a node with all its childs. 2012-01-09, version 0.9.7 -- added functionallity to expand/collapse a node and all its childs. Click +- Added functionallity to expand/collapse a node and all its childs. Click the expand button of a node while holding Ctrl down. -- small interface improvements +- Small interface improvements 2011-11-28, version 0.9.6 -- first fully usable version of the JSON editor +- First fully usable version of the JSON editor diff --git a/jsoneditor-min.js b/jsoneditor-min.js index b6569237f..bc2c1c899 100644 --- a/jsoneditor-min.js +++ b/jsoneditor-min.js @@ -19,15 +19,17 @@ Copyright (c) 2011-2012 Jos de Jong, http://jsoneditoronline.org @author Jos de Jong, - @date 2012-07-22 + @date 2012-08-07 */ -if(!Array.prototype.indexOf)Array.prototype.indexOf=function(a){for(var b=0;b3)b.scrollTop+=a/3,c.animateTimeout=setTimeout(e,50)};e()}}; +JSONEditor.Node=function(a){this.dom={};this.expanded=!1;a&&a instanceof Object?(this.setField(a.field,a.fieldEditable),this.setValue(a.value),a.search&&this.search(a.search)):(this.setField(),this.setValue())};JSONEditor.Node.prototype.setParent=function(a){this.parent=a};JSONEditor.Node.prototype.getParent=function(){return this.parent};JSONEditor.Node.prototype.getEditor=function(){return this.parent?this.parent.getEditor():void 0}; +JSONEditor.Node.prototype.setField=function(a,b){this.field=a;this.fieldEditable=b==!0};JSONEditor.Node.prototype.getField=function(){if(this.field===void 0)this.field=this._getDomField();return this.field}; +JSONEditor.Node.prototype.setValue=function(a){var b=this.childs;if(b)for(;b.length;)this.removeChild(b[0]);this.type=this._getType(a);if(this.type=="array"){this.childs=[];for(var c=0,b=a.length;c0?c:void 0;if(c!=e||b){e=c;var d=g.search(c);if(c!=void 0)switch(d){case 0:f.innerHTML="no results";break;case 1:f.innerHTML="1 result";break;default:f.innerHTML=d+" results"}else f.innerHTML=""}}function c(){h!=void 0&&clearTimeout(h);h=setTimeout(b,k)}var d=document.createElement("table");d.className="jsoneditor-search";a.appendChild(d);a=document.createElement("tbody"); -d.appendChild(a);d=document.createElement("tr");a.appendChild(d);a=document.createElement("td");a.className="jsoneditor-search";d.appendChild(a);var f=document.createElement("div");f.className="jsoneditor-search-results";a.appendChild(f);a=document.createElement("td");a.className="jsoneditor-search";d.appendChild(a);d=document.createElement("div");d.className="jsoneditor-search";d.title="Search fields and values";a.appendChild(d);var e=void 0,g=this,h=void 0,k=200,a=document.createElement("table"); -a.className="jsoneditor-search-input";d.appendChild(a);var j=document.createElement("tbody");a.appendChild(j);d=document.createElement("tr");j.appendChild(d);var i=document.createElement("input");i.className="jsoneditor-search";i.oninput=c;i.onchange=b;i.onkeyup=function(a){var a=a||window.event,d=a.which||a.keyCode;d==27?(i.value="",b(a)):d==13?b(a,!0):c(a)};a=document.createElement("td");d.appendChild(a);a.appendChild(i);j=document.createElement("button");j.className="jsoneditor-search-refresh"; -j.onclick=function(a){b(a,!0)};a=document.createElement("td");d.appendChild(a);a.appendChild(j)};JSONEditor.getNodeFromTarget=function(a){for(;a;){if(a.node)return a.node;a=a.parentNode}}; +JSONEditor.prototype._createFrame=function(){this.container.innerHTML="";this.frame=document.createElement("div");this.frame.className="jsoneditor-frame";this.container.appendChild(this.frame);var a=this,b=function(b){var b=b||window.event,c=b.target||b.srcElement;if(a.options.enableSearch&&b.type=="keydown"){var d=b.which||b.keyCode;d==70&&b.ctrlKey?a.searchBox&&(a.searchBox.dom.search.focus(),a.searchBox.dom.search.select(),JSONEditor.Events.preventDefault(b)):d==114&&(b.shiftKey?a.searchBox.previous(): +a.searchBox.next(),a.searchBox.focusActiveResult(),JSONEditor.Events.preventDefault(b))}if(c=JSONEditor.getNodeFromTarget(c))c.onEvent(b)};this.frame.onclick=function(a){b(a);JSONEditor.Events.preventDefault(a)};this.frame.onchange=b;this.frame.onkeydown=b;this.frame.onkeyup=b;this.frame.oncut=b;this.frame.onpaste=b;this.frame.onmousedown=b;this.frame.onmouseup=b;this.frame.onmouseover=b;this.frame.onmouseout=b;JSONEditor.Events.addEventListener(this.frame,"focus",b,!0);JSONEditor.Events.addEventListener(this.frame, +"blur",b,!0);this.frame.onfocusin=b;this.frame.onfocusout=b;this.head=document.createElement("table");this.head.className="jsoneditor-menu";var c=document.createElement("tbody");this.head.appendChild(c);var d=document.createElement("tr");c.appendChild(d);this.tdMenu=c=document.createElement("td");c.className="jsoneditor-menu";d.appendChild(c);d=document.createElement("button");d.innerHTML="Expand All";d.title="Expand all fields";d.onclick=function(){a.expandAll()};c.appendChild(d);d=document.createElement("button"); +d.innerHTML="Collapse All";d.title="Collapse all fields";d.onclick=function(){a.collapseAll()};c.appendChild(d);if(this.options.enableSearch)this.searchBox=new JSONEditor.SearchBox(this,c);this.frame.appendChild(this.head)}; +JSONEditor.prototype._createTable=function(){var a=document.createElement("div");a.className="jsoneditor-content-outer";this.contentOuter=a;this.content=document.createElement("div");this.content.className="jsoneditor-content";a.appendChild(this.content);this.table=document.createElement("table");this.table.className="jsoneditor-table";this.content.appendChild(this.table);if(JSONEditor.getInternetExplorerVersion()==8)this.content.style.overflow="scroll";var b;this.colgroupContent=document.createElement("colgroup"); +b=document.createElement("col");b.width="24px";this.colgroupContent.appendChild(b);b=document.createElement("col");this.colgroupContent.appendChild(b);b=document.createElement("col");b.width="24px";this.colgroupContent.appendChild(b);b=document.createElement("col");b.width="24px";this.colgroupContent.appendChild(b);b=document.createElement("col");b.width="24px";this.colgroupContent.appendChild(b);this.table.appendChild(this.colgroupContent);this.tbody=document.createElement("tbody");this.table.appendChild(this.tbody); +this.frame.appendChild(a)};JSONEditor.getNodeFromTarget=function(a){for(;a;){if(a.node)return a.node;a=a.parentNode}}; JSONFormatter=function(a){if(!JSON)throw Error("Your browser does not support JSON. \n\nPlease install the newest version of your browser.\n(all modern browsers support JSON).");this.container=a;this.width=a.clientWidth;this.height=a.clientHeight;this.frame=document.createElement("div");this.frame.className="jsoneditor-frame";this.frame.onclick=function(a){JSONEditor.Events.preventDefault(a)};this.head=document.createElement("table");this.head.className="jsoneditor-menu";a=document.createElement("tbody"); this.head.appendChild(a);var b=document.createElement("tr");a.appendChild(b);a=document.createElement("td");a.className="jsoneditor-menu";b.appendChild(a);b=document.createElement("button");b.innerHTML="Format";b.title="Format JSON data, with proper indentation and line feeds";b.className="jsoneditor-button";a.appendChild(b);var c=document.createElement("button");c.innerHTML="Compact";c.title="Compact JSON data, remove all whitespaces";c.className="jsoneditor-button";a.appendChild(c);this.frame.appendChild(this.head); this.content=document.createElement("div");this.content.className="jsonformatter-content";this.frame.appendChild(this.content);this.textarea=document.createElement("textarea");this.textarea.className="jsonformatter-textarea";this.textarea.spellcheck=!1;this.content.appendChild(this.textarea);var d=this.textarea,f=this;b.onclick=function(){try{d.value=JSON.stringify(JSON.parse(d.value),null," ")}catch(a){f.onError(a)}};c.onclick=function(){try{d.value=JSON.stringify(JSON.parse(d.value))}catch(a){f.onError(a)}}; -this.container.appendChild(this.frame)};JSONFormatter.prototype.onError=function(){};JSONFormatter.prototype._checkChange=function(){var a=this.textarea.value;if(a!=this.lastContent&&(this.lastContent=a,this.onChangeCallback))this.onChangeCallback()};JSONFormatter.prototype.set=function(a){this.textarea.value=JSON.stringify(a,null," ")};JSONFormatter.prototype.get=function(){return JSON.parse(this.textarea.value)};JSONEditor.Events={}; +this.container.appendChild(this.frame)};JSONFormatter.prototype.onError=function(){};JSONFormatter.prototype._checkChange=function(){var a=this.textarea.value;if(a!=this.lastContent&&(this.lastContent=a,this.onChangeCallback))this.onChangeCallback()};JSONFormatter.prototype.set=function(a){this.textarea.value=JSON.stringify(a,null," ")};JSONFormatter.prototype.get=function(){return JSON.parse(this.textarea.value)}; +JSONEditor.SearchBox=function(a,b){var c=this;this.editor=a;this.timeout=void 0;this.delay=200;this.lastText=void 0;this.dom={};this.dom.container=b;var d=document.createElement("table");this.dom.table=d;d.className="jsoneditor-search";b.appendChild(d);var f=document.createElement("tbody");this.dom.tbody=f;d.appendChild(f);d=document.createElement("tr");f.appendChild(d);f=document.createElement("td");f.className="jsoneditor-search";d.appendChild(f);var e=document.createElement("div");this.dom.results= +e;e.className="jsoneditor-search-results";f.appendChild(e);f=document.createElement("td");f.className="jsoneditor-search";d.appendChild(f);d=document.createElement("div");this.dom.input=d;d.className="jsoneditor-search";d.title="Search fields and values";f.appendChild(d);f=document.createElement("table");f.className="jsoneditor-search-input";d.appendChild(f);e=document.createElement("tbody");f.appendChild(e);d=document.createElement("tr");e.appendChild(d);e=document.createElement("input");this.dom.search= +e;e.className="jsoneditor-search";e.oninput=function(a){c.onDelayedSearch(a)};e.onchange=function(a){c.onSearch(a)};e.onkeyup=function(a){c.onKeyUp(a)};f=document.createElement("td");d.appendChild(f);f.appendChild(e);e=document.createElement("button");e.className="jsoneditor-search-refresh";e.onclick=function(a){c.onSearch(a,!0)};f=document.createElement("td");d.appendChild(f);f.appendChild(e)}; +JSONEditor.SearchBox.prototype.next=function(){if(this.results!=void 0){var a=this.resultIndex!=void 0?this.resultIndex+1:0;a>this.results.length-1&&(a=0);this.setActiveResult(a)}};JSONEditor.SearchBox.prototype.previous=function(){if(this.results!=void 0){var a=this.results.length-1,b=this.resultIndex!=void 0?this.resultIndex-1:a;b<0&&(b=a);this.setActiveResult(b)}}; +JSONEditor.SearchBox.prototype.setActiveResult=function(a){if(this.activeResult){var b=this.activeResult.node;this.activeResult.elem=="field"?delete b.searchFieldActive:delete b.searchValueActive;b.updateDom()}!this.results||!this.results[a]?this.activeResult=this.resultIndex=void 0:(this.resultIndex=a,a=this.results[this.resultIndex].node,this.results[this.resultIndex].elem=="field"?a.searchFieldActive=!0:a.searchValueActive=!0,this.activeResult=this.results[this.resultIndex],a.updateDom(),a.scrollTo())}; +JSONEditor.SearchBox.prototype.focusActiveResult=function(){this.activeResult||this.next();this.activeResult&&this.activeResult.node.focus(this.activeResult.elem)};JSONEditor.SearchBox.prototype.clearDelay=function(){this.timeout!=void 0&&(clearTimeout(this.timeout),delete this.timeout)};JSONEditor.SearchBox.prototype.onDelayedSearch=function(){this.clearDelay();var a=this;this.timeout=setTimeout(function(b){a.onSearch(b)},this.delay)}; +JSONEditor.SearchBox.prototype.onSearch=function(a,b){this.clearDelay();var c=this.dom.search.value,c=c.length>0?c:void 0;if(c!=this.lastText||b)if(this.lastText=c,this.results=editor.search(c),this.setActiveResult(void 0),c!=void 0)switch(c=this.results.length,c){case 0:this.dom.results.innerHTML="no results";break;case 1:this.dom.results.innerHTML="1 result";break;default:this.dom.results.innerHTML=c+" results"}else this.dom.results.innerHTML=""}; +JSONEditor.SearchBox.prototype.onKeyUp=function(a){var a=a||window.event,b=a.which||a.keyCode;if(b==27)this.dom.search.value="",this.onSearch(a);else if(b==13)if(a.ctrlKey)this.onSearch(a,!0);else a.shiftKey?this.previous():this.next();else this.onDelayedSearch(a)};JSONEditor.Events={}; JSONEditor.Events.addEventListener=function(a,b,c,d){return a.addEventListener?(d===void 0&&(d=!1),b==="mousewheel"&&navigator.userAgent.indexOf("Firefox")>=0&&(b="DOMMouseScroll"),a.addEventListener(b,c,d),c):(d=function(){return c.call(a,window.event)},a.attachEvent("on"+b,d),d)}; JSONEditor.Events.removeEventListener=function(a,b,c,d){a.removeEventListener?(d===void 0&&(d=!1),b==="mousewheel"&&navigator.userAgent.indexOf("Firefox")>=0&&(b="DOMMouseScroll"),a.removeEventListener(b,c,d)):a.detachEvent("on"+b,c)};JSONEditor.Events.stopPropagation=function(a){if(!a)a=window.event;a.stopPropagation?a.stopPropagation():a.cancelBubble=!0};JSONEditor.Events.preventDefault=function(a){if(!a)a=window.event;a.preventDefault?a.preventDefault():a.returnValue=!1}; JSONEditor.getAbsoluteLeft=function(a){for(var b=0,c=document.body;a!=null&&a!=c;)b+=a.offsetLeft,b-=a.scrollLeft,a=a.offsetParent;return b};JSONEditor.getAbsoluteTop=function(a){for(var b=0,c=document.body;a!=null&&a!=c;)b+=a.offsetTop,b-=a.scrollTop,a=a.offsetParent;return b};JSONEditor.addClassName=function(a,b){var c=a.className;if(c.indexOf(b)==-1)c+=" "+b,a.className=c}; diff --git a/jsoneditor.css b/jsoneditor.css index a0b5cf1e4..eda3f4ac7 100644 --- a/jsoneditor.css +++ b/jsoneditor.css @@ -15,6 +15,7 @@ .jsoneditor-empty { background-color: #E5E5E5; + border-radius: 2px; } .jsoneditor-separator { @@ -23,10 +24,19 @@ } .jsoneditor-value:focus, .jsoneditor-field:focus, -.jsoneditor-value:hover, .jsoneditor-field:hover, -.jsoneditor-search-highlight { + .jsoneditor-value:hover, .jsoneditor-field:hover, + .jsoneditor-search-highlight { background-color: #FFFFAB; border: 1px solid yellow; + border-radius: 2px; +} + +.jsoneditor-search-highlight-active, + .jsoneditor-search-highlight-active:focus, + .jsoneditor-search-highlight-active:hover { + background-color: #ffee00; + border: 1px solid #ffc700; + border-radius: 2px; } .jsoneditor-field-readonly:hover { @@ -328,3 +338,11 @@ td.jsoneditor-droparea { font-size: 10pt; color: #1A1A1A; } + +.jsoneditor-hidden-focus { + position: absolute; + left: -1000px; + top: -1000px; + border: none; + outline: none; +} \ No newline at end of file diff --git a/jsoneditor.js b/jsoneditor.js index f8820ed1d..e56aacf56 100644 --- a/jsoneditor.js +++ b/jsoneditor.js @@ -26,7 +26,7 @@ * Copyright (c) 2011-2012 Jos de Jong, http://jsoneditoronline.org * * @author Jos de Jong, - * @date 2012-07-22 + * @date 2012-08-07 */ @@ -166,6 +166,14 @@ JSONEditor.prototype._setRoot = function (node) { this.clear(); this.node = node; + + // override the getEditor method + var editor = this; + node.getEditor = function () { + return editor; + }; + + // append to the dom this.tbody.appendChild(node.getDom()); }; @@ -174,7 +182,12 @@ JSONEditor.prototype._setRoot = function (node) { * The nodes will be expanded when the text is found one of its childs, * else it will be collapsed. Searches are case insensitive. * @param {String} text - * @return {Node[]} results Array with nodes containing the search text + * @return {Object[]} results Array with nodes containing the search results + * The result objects contains fields: + * - {Node} node, + * - {String} elem the dom element name where + * the result is found ('field' or + * 'value') */ JSONEditor.prototype.search = function (text) { var results; @@ -212,6 +225,72 @@ JSONEditor.prototype.collapseAll = function () { } }; +/** + * Set the focus to the JSONEditor. A hidden input field will be created + * which captures key events + */ +// TODO: use the focus method? +JSONEditor.prototype.focus = function () { + /* + if (!this.dom.focus) { + this.dom.focus = document.createElement('input'); + this.dom.focus.className = 'jsoneditor-hidden-focus'; + + var editor = this; + this.dom.focus.onblur = function () { + // remove itself + if (editor.dom.focus) { + var focus = editor.dom.focus; + delete editor.dom.focus; + editor.frame.removeChild(focus); + } + }; + + // attach the hidden input box to the DOM + if (this.frame.firstChild) { + this.frame.insertBefore(this.dom.focus, this.frame.firstChild); + } + else { + this.frame.appendChild(this.dom.focus); + } + } + this.dom.focus.focus(); + */ +}; + +/** + * Adjust the scroll position such that given top position is shown at 1/4 + * of the window height. + * @param {Number} top + */ +JSONEditor.prototype.scrollTo = function (top) { + var content = this.content; + if (content) { + // cancel any running animation + var editor = this; + if (editor.animateTimeout) { + clearTimeout(editor.animateTimeout); + delete editor.animateTimeout; + } + + // calculate final scroll position + var height = content.clientHeight; + var bottom = content.scrollHeight - height; + var finalScrollTop = Math.min(Math.max(top - height / 4, 0), bottom); + + // animate towards the new scroll position + var animate = function () { + var scrollTop = content.scrollTop; + var diff = (finalScrollTop - scrollTop); + if (Math.abs(diff) > 3) { + content.scrollTop += diff / 3; + editor.animateTimeout = setTimeout(animate, 50); + } + }; + animate(); + } +}; + /** * @constructor JSONEditor.Node * Create a new Node @@ -250,6 +329,14 @@ JSONEditor.Node.prototype.getParent = function () { return this.parent; }; +/** + * Get the JSONEditor + * @return {JSONEditor} editor + */ +JSONEditor.Node.prototype.getEditor = function () { + return this.parent ? this.parent.getEditor() : undefined; +}; + /** * Set field * @param {String} field @@ -677,7 +764,10 @@ JSONEditor.Node.prototype.search = function(text) { index = field.indexOf(search); if (index != -1) { this.searchField = true; - results.push(this); + results.push({ + 'node': this, + 'elem': 'field' + }); } // update dom @@ -716,7 +806,10 @@ JSONEditor.Node.prototype.search = function(text) { index = value.indexOf(search); if (index != -1) { this.searchValue = true; - results.push(this); + results.push({ + 'node': this, + 'elem': 'value' + }); } } @@ -727,12 +820,37 @@ JSONEditor.Node.prototype.search = function(text) { return results; }; +/** + * Move the scroll position such that this node is in the visible area. + * The node will not get the focus + */ +JSONEditor.Node.prototype.scrollTo = function() { + if (!this.dom.tr || !this.dom.tr.parentNode) { + // if the node is not visible, expand its parents + var parent = this.parent; + var recurse = false; + while (parent) { + parent.expand(recurse); + parent = parent.parent; + } + } + + if (this.dom.tr && this.dom.tr.parentNode) { + var editor = this.getEditor(); + if (editor) { + editor.scrollTo(this.dom.tr.offsetTop); + } + } +}; + /** * Set focus to the value of this node + * @param {String} [field] The field name of the element to get the focus + * available values: 'field', 'value' */ -JSONEditor.Node.prototype.focus = function() { +JSONEditor.Node.prototype.focus = function(field) { if (this.dom.tr && this.dom.tr.parentNode) { - if (this.fieldEditable) { + if (field != 'value' && this.fieldEditable) { var domField = this.dom.field; if (domField) { domField.focus(); @@ -1041,6 +1159,12 @@ JSONEditor.Node.prototype._updateDomValue = function () { } // highlight when there is a search result + if (this.searchValueActive) { + JSONEditor.addClassName(domValue, 'jsoneditor-search-highlight-active'); + } + else { + JSONEditor.removeClassName(domValue, 'jsoneditor-search-highlight-active'); + } if (this.searchValue) { JSONEditor.addClassName(domValue, 'jsoneditor-search-highlight'); } @@ -1072,6 +1196,12 @@ JSONEditor.Node.prototype._updateDomField = function () { } // highlight when there is a search result + if (this.searchFieldActive) { + JSONEditor.addClassName(domField, 'jsoneditor-search-highlight-active'); + } + else { + JSONEditor.removeClassName(domField, 'jsoneditor-search-highlight-active'); + } if (this.searchField) { JSONEditor.addClassName(domField, 'jsoneditor-search-highlight'); } @@ -1559,7 +1689,6 @@ JSONEditor.Node.prototype._createDomTree = function (domExpand, domField, domVal dom.tdField = tdField; // add a separator - // TODO: format correctly. Hide in case of array/object var tdSeparator = document.createElement('td'); tdSeparator.className = 'jsoneditor-td-tree'; tr.appendChild(tdSeparator); @@ -2157,45 +2286,45 @@ JSONEditor.prototype._createFrame = function () { // TODO: move this onEvent to JSONEditor.prototype.onEvent var onEvent = function (event) { event = event || window.event; + var target = event.target || event.srcElement; - /* TODO: check for Ctrl+F and F3? or not? + // Check for search quickkeys, Ctrl+F and F3 if (editor.options.enableSearch) { if (event.type == 'keydown') { var keynum = event.which || event.keyCode; if (keynum == 70 && event.ctrlKey) { // Ctrl+F - console.log('Ctrl+F'); - if (editor.dom.search) { - editor.dom.search.focus(); - editor.dom.search.select(); + if (editor.searchBox) { + editor.searchBox.dom.search.focus(); + editor.searchBox.dom.search.select(); JSONEditor.Events.preventDefault(event); } } else if (keynum == 114) { // F3 if (!event.shiftKey) { // select next search result - editor.searchResultIndex ++; - if (editor.searchResultIndex >= editor.searchResults.length) { - editor.searchResultIndex = 0; - } + editor.searchBox.next(); } else { // select previous search result - editor.searchResultIndex--; - if (editor.searchResultIndex < 0) { - editor.searchResultIndex = editor.searchResults.length - 1; - } - } - if (editor.searchResultIndex < editor.searchResults.length) { - editor.searchResults[editor.searchResultIndex].focus(); + editor.searchBox.previous(); } + editor.searchBox.focusActiveResult(); + // set selection to the current JSONEditor.Events.preventDefault(event); } } } + + /* TODO: focus to editor? + if (event.type == 'click' && + (target == editor.content || target == editor.contentOuter)) { + // set selection to a hidden input box in the editor, + // such that quickkeys will be catched + editor.focus(); + } */ - var target = event.target || event.srcElement; var node = JSONEditor.getNodeFromTarget(target); if (node) { node.onEvent(event); @@ -2232,6 +2361,7 @@ JSONEditor.prototype._createFrame = function () { var tr = document.createElement('tr'); tbody.appendChild(tr); var td = document.createElement('td'); + this.tdMenu = td; td.className = 'jsoneditor-menu'; tr.appendChild(td); @@ -2267,6 +2397,7 @@ JSONEditor.prototype._createFrame = function () { JSONEditor.prototype._createTable = function () { var contentOuter = document.createElement('div'); contentOuter.className = 'jsoneditor-content-outer'; + this.contentOuter = contentOuter; this.content = document.createElement('div'); this.content.className = 'jsoneditor-content'; @@ -2492,7 +2623,6 @@ JSONFormatter.prototype.get = function() { * @param {Element} container HTML container element of where to create the * search box */ -// TODO: put the SearchBox in a separate prototype JSONEditor.SearchBox = function(editor, container) { var searchBox = this; @@ -2568,6 +2698,89 @@ JSONEditor.SearchBox = function(editor, container) { td.appendChild(refreshSearch); }; +/** + * Go to the next search result + */ +JSONEditor.SearchBox.prototype.next = function() { + if (this.results != undefined) { + var index = (this.resultIndex != undefined) ? this.resultIndex + 1 : 0; + if (index > this.results.length - 1) { + index = 0; + } + this.setActiveResult(index); + } +}; + +/** + * Go to the prevous search result + */ +JSONEditor.SearchBox.prototype.previous = function() { + if (this.results != undefined) { + var max = this.results.length - 1; + var index = (this.resultIndex != undefined) ? this.resultIndex - 1 : max; + if (index < 0) { + index = max; + } + this.setActiveResult(index); + } +}; + +/** + * Set new value for the current active result + * @param {Number} index + */ +JSONEditor.SearchBox.prototype.setActiveResult = function(index) { + // de-activate current active result + if (this.activeResult) { + var prevNode = this.activeResult.node; + var prevElem = this.activeResult.elem; + if (prevElem == 'field') { + delete prevNode.searchFieldActive; + } + else { + delete prevNode.searchValueActive; + } + prevNode.updateDom(); + } + + if (!this.results || !this.results[index]) { + // out of range, set to undefined + this.resultIndex = undefined; + this.activeResult = undefined; + return; + } + + this.resultIndex = index; + + // set new node active + var node = this.results[this.resultIndex].node; + var elem = this.results[this.resultIndex].elem; + if (elem == 'field') { + node.searchFieldActive = true; + } + else { + node.searchValueActive = true; + } + this.activeResult = this.results[this.resultIndex]; + node.updateDom(); + + node.scrollTo(); +}; + +/** + * Set the focus to the currently active result. If there is no currently + * active result, the next search result will get focus + */ +JSONEditor.SearchBox.prototype.focusActiveResult = function() { + if (!this.activeResult) { + this.next(); + } + + if (this.activeResult) { + this.activeResult.node.focus(this.activeResult.elem); + } +}; + /** * Cancel any running onDelayedSearch. */ @@ -2609,7 +2822,7 @@ JSONEditor.SearchBox.prototype.onSearch = function (event, forceSearch) { // only search again when changed this.lastText = text; this.results = editor.search(text); - this.resultIndex = 0; + this.setActiveResult(undefined); // display search results if (text != undefined) { @@ -2619,11 +2832,6 @@ JSONEditor.SearchBox.prototype.onSearch = function (event, forceSearch) { case 1: this.dom.results.innerHTML = '1 result'; break; default: this.dom.results.innerHTML = resultCount + ' results'; break; } - /* TODO: do we want this? - if (resultCount > 0) { - editor.results[editor.searchResultIndex].focus(); - } - */ } else { this.dom.results.innerHTML = ''; @@ -2643,7 +2851,18 @@ JSONEditor.SearchBox.prototype.onKeyUp = function (event) { this.onSearch(event); } else if (keynum == 13) { // Enter - this.onSearch(event, true); // force to execute search again + if (event.ctrlKey) { + // force to search again + this.onSearch(event, true); + } + else if (event.shiftKey) { + // move to the previous search result + this.previous(); + } + else { + // move to the next search result + this.next(); + } } else { this.onDelayedSearch(event); // For IE 8 @@ -2798,6 +3017,7 @@ JSONEditor.addClassName = function(elem, className) { JSONEditor.removeClassName = function(elem, className) { var c = elem.className; if (c.indexOf(className) != -1) { + // TODO: improve to classname separated by space or start/end of string c = c.replace(className, ''); // remove classname c = c.replace(/ /g, ''); // remove double spaces elem.className = c;