Set up code style linting with standardjs

This commit is contained in:
jos 2019-08-28 13:21:14 +02:00
parent 8e2a7de17c
commit a5d6b8a65b
43 changed files with 7982 additions and 6993 deletions

View File

@ -1,3 +1,10 @@
language: node_js language: node_js
node_js: node_js:
- "lts/*" - "lts/*"
jobs:
include:
- name: Lint
stage: other
script: npm run lint
node_js: lts/*

View File

@ -6,10 +6,18 @@ up with ideas and suggestions, and contribute to the code.
There are a few preferences regarding code contributions: There are a few preferences regarding code contributions:
- `jsoneditor` follows the node.js code style as described - Send pull requests to the `develop` branch, not the `master` branch.
[here](http://nodeguide.com/style.html). - You can use modern JavaScript features, the code is transpiled using Babel.
- Send pull requests to the `develop` branch, not the `master` branch. - `jsoneditor` follows the https://standardjs.com/ code style. To test:
- Only commit changes done in the source files under `./src`, not to the builds
which are located under the `./dist` folder. ```
npm run lint
```
- If possible, create a unit test for any new functionality. To run tests:
```
npm test
```
Thanks! Thanks!

View File

@ -96,9 +96,9 @@ with npm (recommended):
<script> <script>
// create the editor // create the editor
var container = document.getElementById("jsoneditor"); var container = document.getElementById("jsoneditor")
var options = {}; var options = {}
var editor = new JSONEditor(container, options); var editor = new JSONEditor(container, options)
// set json // set json
var json = { var json = {
@ -108,11 +108,11 @@ with npm (recommended):
"Number": 123, "Number": 123,
"Object": {"a": "b", "c": "d"}, "Object": {"a": "b", "c": "d"},
"String": "Hello World" "String": "Hello World"
}; }
editor.set(json); editor.set(json)
// get json // get json
var json = editor.get(); var json = editor.get()
</script> </script>
</body> </body>
</html> </html>

View File

@ -9,7 +9,7 @@
} }
</style> </style>
<link rel="stylesheet" type="text/css" href="../../dist/jsoneditor.css"> <link rel="stylesheet" type="text/css" href="../../dist/jsoneditor.css">
<script data-main="scripts/main" src="scripts/require.js"></script> <script data-main="scripts/main" src="https://requirejs.org/docs/release/2.3.6/minified/require.js"></script>
</head> </head>
<body> <body>
<p> <p>

View File

@ -1,25 +1,25 @@
var module = '../../../dist/jsoneditor'; var module = '../../../dist/jsoneditor'
require([module], function (JSONEditor) { require([module], function (JSONEditor) {
// create the editor // create the editor
var container = document.getElementById('jsoneditor'); var container = document.getElementById('jsoneditor')
var editor = new JSONEditor(container); var editor = new JSONEditor(container)
// set json // set json
document.getElementById('setJSON').onclick = function () { document.getElementById('setJSON').onclick = function () {
var json = { var json = {
'array': [1, 2, 3], array: [1, 2, 3],
'boolean': true, boolean: true,
'null': null, null: null,
'number': 123, number: 123,
'object': {'a': 'b', 'c': 'd'}, object: { a: 'b', c: 'd' },
'string': 'Hello World' string: 'Hello World'
}; }
editor.set(json); editor.set(json)
}; }
// get json // get json
document.getElementById('getJSON').onclick = function () { document.getElementById('getJSON').onclick = function () {
var json = editor.get(); var json = editor.get()
alert(JSON.stringify(json, null, 2)); window.alert(JSON.stringify(json, null, 2))
}; }
}); })

View File

@ -1,36 +0,0 @@
/*
RequireJS 2.1.13 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
Available via the MIT or new BSD license.
see: http://github.com/jrburke/requirejs for details
*/
var requirejs,require,define;
(function(ba){function G(b){return"[object Function]"===K.call(b)}function H(b){return"[object Array]"===K.call(b)}function v(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function T(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));d-=1);}}function t(b,c){return fa.call(b,c)}function m(b,c){return t(b,c)&&b[c]}function B(b,c){for(var d in b)if(t(b,d)&&c(b[d],d))break}function U(b,c,d,e){c&&B(c,function(c,g){if(d||!t(b,g))e&&"object"===typeof c&&c&&!H(c)&&!G(c)&&!(c instanceof
RegExp)?(b[g]||(b[g]={}),U(b[g],c,d,e)):b[g]=c});return b}function u(b,c){return function(){return c.apply(b,arguments)}}function ca(b){throw b;}function da(b){if(!b)return b;var c=ba;v(b.split("."),function(b){c=c[b]});return c}function C(b,c,d,e){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=e;d&&(c.originalError=d);return c}function ga(b){function c(a,k,b){var f,l,c,d,e,g,i,p,k=k&&k.split("/"),h=j.map,n=h&&h["*"];if(a){a=a.split("/");l=a.length-1;j.nodeIdCompat&&
Q.test(a[l])&&(a[l]=a[l].replace(Q,""));"."===a[0].charAt(0)&&k&&(l=k.slice(0,k.length-1),a=l.concat(a));l=a;for(c=0;c<l.length;c++)if(d=l[c],"."===d)l.splice(c,1),c-=1;else if(".."===d&&!(0===c||1==c&&".."===l[2]||".."===l[c-1])&&0<c)l.splice(c-1,2),c-=2;a=a.join("/")}if(b&&h&&(k||n)){l=a.split("/");c=l.length;a:for(;0<c;c-=1){e=l.slice(0,c).join("/");if(k)for(d=k.length;0<d;d-=1)if(b=m(h,k.slice(0,d).join("/")))if(b=m(b,e)){f=b;g=c;break a}!i&&(n&&m(n,e))&&(i=m(n,e),p=c)}!f&&i&&(f=i,g=p);f&&(l.splice(0,
g,f),a=l.join("/"))}return(f=m(j.pkgs,a))?f:a}function d(a){z&&v(document.getElementsByTagName("script"),function(k){if(k.getAttribute("data-requiremodule")===a&&k.getAttribute("data-requirecontext")===i.contextName)return k.parentNode.removeChild(k),!0})}function e(a){var k=m(j.paths,a);if(k&&H(k)&&1<k.length)return k.shift(),i.require.undef(a),i.makeRequire(null,{skipMap:!0})([a]),!0}function n(a){var k,c=a?a.indexOf("!"):-1;-1<c&&(k=a.substring(0,c),a=a.substring(c+1,a.length));return[k,a]}function p(a,
k,b,f){var l,d,e=null,g=k?k.name:null,j=a,p=!0,h="";a||(p=!1,a="_@r"+(K+=1));a=n(a);e=a[0];a=a[1];e&&(e=c(e,g,f),d=m(r,e));a&&(e?h=d&&d.normalize?d.normalize(a,function(a){return c(a,g,f)}):c(a,g,f):(h=c(a,g,f),a=n(h),e=a[0],h=a[1],b=!0,l=i.nameToUrl(h)));b=e&&!d&&!b?"_unnormalized"+(O+=1):"";return{prefix:e,name:h,parentMap:k,unnormalized:!!b,url:l,originalName:j,isDefine:p,id:(e?e+"!"+h:h)+b}}function s(a){var k=a.id,b=m(h,k);b||(b=h[k]=new i.Module(a));return b}function q(a,k,b){var f=a.id,c=m(h,
f);if(t(r,f)&&(!c||c.defineEmitComplete))"defined"===k&&b(r[f]);else if(c=s(a),c.error&&"error"===k)b(c.error);else c.on(k,b)}function w(a,b){var c=a.requireModules,f=!1;if(b)b(a);else if(v(c,function(b){if(b=m(h,b))b.error=a,b.events.error&&(f=!0,b.emit("error",a))}),!f)g.onError(a)}function x(){R.length&&(ha.apply(A,[A.length,0].concat(R)),R=[])}function y(a){delete h[a];delete V[a]}function F(a,b,c){var f=a.map.id;a.error?a.emit("error",a.error):(b[f]=!0,v(a.depMaps,function(f,d){var e=f.id,g=
m(h,e);g&&(!a.depMatched[d]&&!c[e])&&(m(b,e)?(a.defineDep(d,r[e]),a.check()):F(g,b,c))}),c[f]=!0)}function D(){var a,b,c=(a=1E3*j.waitSeconds)&&i.startTime+a<(new Date).getTime(),f=[],l=[],g=!1,h=!0;if(!W){W=!0;B(V,function(a){var i=a.map,j=i.id;if(a.enabled&&(i.isDefine||l.push(a),!a.error))if(!a.inited&&c)e(j)?g=b=!0:(f.push(j),d(j));else if(!a.inited&&(a.fetched&&i.isDefine)&&(g=!0,!i.prefix))return h=!1});if(c&&f.length)return a=C("timeout","Load timeout for modules: "+f,null,f),a.contextName=
i.contextName,w(a);h&&v(l,function(a){F(a,{},{})});if((!c||b)&&g)if((z||ea)&&!X)X=setTimeout(function(){X=0;D()},50);W=!1}}function E(a){t(r,a[0])||s(p(a[0],null,!0)).init(a[1],a[2])}function I(a){var a=a.currentTarget||a.srcElement,b=i.onScriptLoad;a.detachEvent&&!Y?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=i.onScriptError;(!a.detachEvent||Y)&&a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function J(){var a;for(x();A.length;){a=
A.shift();if(null===a[0])return w(C("mismatch","Mismatched anonymous define() module: "+a[a.length-1]));E(a)}}var W,Z,i,L,X,j={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},h={},V={},$={},A=[],r={},S={},aa={},K=1,O=1;L={require:function(a){return a.require?a.require:a.require=i.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?r[a.map.id]=a.exports:a.exports=r[a.map.id]={}},module:function(a){return a.module?a.module:a.module=
{id:a.map.id,uri:a.map.url,config:function(){return m(j.config,a.map.id)||{}},exports:a.exports||(a.exports={})}}};Z=function(a){this.events=m($,a.id)||{};this.map=a;this.shim=m(j.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};Z.prototype={init:function(a,b,c,f){f=f||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=u(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=c;this.inited=
!0;this.ignore=f.ignore;f.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=b)},fetch:function(){if(!this.fetched){this.fetched=!0;i.startTime=(new Date).getTime();var a=this.map;if(this.shim)i.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],u(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=
this.map.url;S[a]||(S[a]=!0,i.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,b,c=this.map.id;b=this.depExports;var f=this.exports,l=this.factory;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(G(l)){if(this.events.error&&this.map.isDefine||g.onError!==ca)try{f=i.execCb(c,l,b,f)}catch(d){a=d}else f=i.execCb(c,l,b,f);this.map.isDefine&&void 0===f&&((b=this.module)?f=b.exports:this.usingExports&&
(f=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(r[c]=f,g.onResourceLoad))g.onResourceLoad(i,this.map,this.depMaps);y(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=
this.map,b=a.id,d=p(a.prefix);this.depMaps.push(d);q(d,"defined",u(this,function(f){var l,d;d=m(aa,this.map.id);var e=this.map.name,P=this.map.parentMap?this.map.parentMap.name:null,n=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(e=f.normalize(e,function(a){return c(a,P,!0)})||""),f=p(a.prefix+"!"+e,this.map.parentMap),q(f,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=m(h,f.id)){this.depMaps.push(f);
if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else d?(this.map.url=i.nameToUrl(d),this.load()):(l=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];B(h,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),l.fromText=u(this,function(f,c){var d=a.name,e=p(d),P=M;c&&(f=c);P&&(M=!1);s(e);t(j.config,b)&&(j.config[d]=j.config[b]);try{g.exec(f)}catch(h){return w(C("fromtexteval",
"fromText eval for "+b+" failed: "+h,h,[b]))}P&&(M=!0);this.depMaps.push(e);i.completeLoad(d);n([d],l)}),f.load(a.name,n,l,j))}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=this;this.enabling=this.enabled=!0;v(this.depMaps,u(this,function(a,b){var c,f;if("string"===typeof a){a=p(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(L,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;q(a,"defined",u(this,function(a){this.defineDep(b,
a);this.check()}));this.errback&&q(a,"error",u(this,this.errback))}c=a.id;f=h[c];!t(L,c)&&(f&&!f.enabled)&&i.enable(a,this)}));B(this.pluginMaps,u(this,function(a){var b=m(h,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){v(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:j,contextName:b,registry:h,defined:r,urlFetched:S,defQueue:A,Module:Z,makeModuleMap:p,
nextTick:g.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=j.shim,c={paths:!0,bundles:!0,config:!0,map:!0};B(a,function(a,b){c[b]?(j[b]||(j[b]={}),U(j[b],a,!0,!0)):j[b]=a});a.bundles&&B(a.bundles,function(a,b){v(a,function(a){a!==b&&(aa[a]=b)})});a.shim&&(B(a.shim,function(a,c){H(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);b[c]=a}),j.shim=b);a.packages&&v(a.packages,function(a){var b,
a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(j.paths[b]=a.location);j.pkgs[b]=a.name+"/"+(a.main||"main").replace(ia,"").replace(Q,"")});B(h,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=p(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ba,arguments));return b||a.exports&&da(a.exports)}},makeRequire:function(a,e){function j(c,d,m){var n,q;e.enableBuildCallback&&(d&&G(d))&&(d.__requireJsBuild=
!0);if("string"===typeof c){if(G(d))return w(C("requireargs","Invalid require call"),m);if(a&&t(L,c))return L[c](h[a.id]);if(g.get)return g.get(i,c,a,j);n=p(c,a,!1,!0);n=n.id;return!t(r,n)?w(C("notloaded",'Module name "'+n+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[n]}J();i.nextTick(function(){J();q=s(p(null,a));q.skipMap=e.skipMap;q.init(c,d,m,{enabled:!0});D()});return j}e=e||{};U(j,{isBrowser:z,toUrl:function(b){var d,e=b.lastIndexOf("."),k=b.split("/")[0];if(-1!==
e&&(!("."===k||".."===k)||1<e))d=b.substring(e,b.length),b=b.substring(0,e);return i.nameToUrl(c(b,a&&a.id,!0),d,!0)},defined:function(b){return t(r,p(b,a,!1,!0).id)},specified:function(b){b=p(b,a,!1,!0).id;return t(r,b)||t(h,b)}});a||(j.undef=function(b){x();var c=p(b,a,!0),e=m(h,b);d(b);delete r[b];delete S[c.url];delete $[b];T(A,function(a,c){a[0]===b&&A.splice(c,1)});e&&(e.events.defined&&($[b]=e.events),y(b))});return j},enable:function(a){m(h,a.id)&&s(a).enable()},completeLoad:function(a){var b,
c,d=m(j.shim,a)||{},g=d.exports;for(x();A.length;){c=A.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);E(c)}c=m(h,a);if(!b&&!t(r,a)&&c&&!c.inited){if(j.enforceDefine&&(!g||!da(g)))return e(a)?void 0:w(C("nodefine","No define call for "+a,null,[a]));E([a,d.deps||[],d.exportsFn])}D()},nameToUrl:function(a,b,c){var d,e,h;(d=m(j.pkgs,a))&&(a=d);if(d=m(aa,a))return i.nameToUrl(d,b,c);if(g.jsExtRegExp.test(a))d=a+(b||"");else{d=j.paths;a=a.split("/");for(e=a.length;0<e;e-=1)if(h=a.slice(0,
e).join("/"),h=m(d,h)){H(h)&&(h=h[0]);a.splice(0,e,h);break}d=a.join("/");d+=b||(/^data\:|\?/.test(d)||c?"":".js");d=("/"===d.charAt(0)||d.match(/^[\w\+\.\-]+:/)?"":j.baseUrl)+d}return j.urlArgs?d+((-1===d.indexOf("?")?"?":"&")+j.urlArgs):d},load:function(a,b){g.load(i,a,b)},execCb:function(a,b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if("load"===a.type||ja.test((a.currentTarget||a.srcElement).readyState))N=null,a=I(a),i.completeLoad(a.id)},onScriptError:function(a){var b=I(a);if(!e(b.id))return w(C("scripterror",
"Script error for: "+b.id,a,[b.id]))}};i.require=i.makeRequire();return i}var g,x,y,D,I,E,N,J,s,O,ka=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,la=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,Q=/\.js$/,ia=/^\.\//;x=Object.prototype;var K=x.toString,fa=x.hasOwnProperty,ha=Array.prototype.splice,z=!!("undefined"!==typeof window&&"undefined"!==typeof navigator&&window.document),ea=!z&&"undefined"!==typeof importScripts,ja=z&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,
Y="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),F={},q={},R=[],M=!1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(G(requirejs))return;q=requirejs;requirejs=void 0}"undefined"!==typeof require&&!G(require)&&(q=require,require=void 0);g=requirejs=function(b,c,d,e){var n,p="_";!H(b)&&"string"!==typeof b&&(n=b,H(c)?(b=c,c=d,d=e):b=[]);n&&n.context&&(p=n.context);(e=m(F,p))||(e=F[p]=g.s.newContext(p));n&&e.configure(n);return e.require(b,c,d)};g.config=function(b){return g(b)};
g.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,4)}:function(b){b()};require||(require=g);g.version="2.1.13";g.jsExtRegExp=/^\/|:|\?|\.js$/;g.isBrowser=z;x=g.s={contexts:F,newContext:ga};g({});v(["toUrl","undef","defined","specified"],function(b){g[b]=function(){var c=F._;return c.require[b].apply(c,arguments)}});if(z&&(y=x.head=document.getElementsByTagName("head")[0],D=document.getElementsByTagName("base")[0]))y=x.head=D.parentNode;g.onError=ca;g.createNode=function(b){var c=
b.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");c.type=b.scriptType||"text/javascript";c.charset="utf-8";c.async=!0;return c};g.load=function(b,c,d){var e=b&&b.config||{};if(z)return e=g.createNode(e,c,d),e.setAttribute("data-requirecontext",b.contextName),e.setAttribute("data-requiremodule",c),e.attachEvent&&!(e.attachEvent.toString&&0>e.attachEvent.toString().indexOf("[native code"))&&!Y?(M=!0,e.attachEvent("onreadystatechange",b.onScriptLoad)):
(e.addEventListener("load",b.onScriptLoad,!1),e.addEventListener("error",b.onScriptError,!1)),e.src=d,J=e,D?y.insertBefore(e,D):y.appendChild(e),J=null,e;if(ea)try{importScripts(d),b.completeLoad(c)}catch(m){b.onError(C("importscripts","importScripts failed for "+c+" at "+d,m,[c]))}};z&&!q.skipDataMain&&T(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(I=b.getAttribute("data-main"))return s=I,q.baseUrl||(E=s.split("/"),s=E.pop(),O=E.length?E.join("/")+"/":"./",q.baseUrl=
O),s=s.replace(Q,""),g.jsExtRegExp.test(s)&&(s=I),q.deps=q.deps?q.deps.concat(s):[s],!0});define=function(b,c,d){var e,g;"string"!==typeof b&&(d=c,c=b,b=null);H(c)||(d=c,c=null);!c&&G(d)&&(c=[],d.length&&(d.toString().replace(ka,"").replace(la,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(M){if(!(e=J))N&&"interactive"===N.readyState||T(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return N=b}),e=N;e&&(b||
(b=e.getAttribute("data-requiremodule")),g=F[e.getAttribute("data-requirecontext")])}(g?g.defQueue:R).push([b,c,d])};define.amd={jQuery:!0};g.exec=function(b){return eval(b)};g(q)}})(this);

View File

@ -1,38 +1,38 @@
var fs = require('fs'); var fs = require('fs')
var path = require('path'); var path = require('path')
var gulp = require('gulp'); var gulp = require('gulp')
var log = require('fancy-log'); var log = require('fancy-log')
var format = require('date-format'); var format = require('date-format')
var concatCss = require('gulp-concat-css'); var concatCss = require('gulp-concat-css')
var minifyCSS = require('gulp-clean-css'); var minifyCSS = require('gulp-clean-css')
var sass = require('gulp-sass') var sass = require('gulp-sass')
var mkdirp = require('mkdirp'); var mkdirp = require('mkdirp')
var webpack = require('webpack'); var webpack = require('webpack')
var uglify = require('uglify-js'); var uglify = require('uglify-js')
var NAME = 'jsoneditor'; var NAME = 'jsoneditor'
var NAME_MINIMALIST = 'jsoneditor-minimalist'; var NAME_MINIMALIST = 'jsoneditor-minimalist'
var ENTRY = './src/js/JSONEditor.js'; var ENTRY = './src/js/JSONEditor.js'
var HEADER = './src/js/header.js'; var HEADER = './src/js/header.js'
var IMAGE = './src/scss/img/jsoneditor-icons.svg'; var IMAGE = './src/scss/img/jsoneditor-icons.svg'
var DOCS = './src/docs/*'; var DOCS = './src/docs/*'
var DIST = path.join(__dirname, 'dist'); var DIST = path.join(__dirname, 'dist')
// generate banner with today's date and correct version // generate banner with today's date and correct version
function createBanner() { function createBanner () {
var today = format.asString('yyyy-MM-dd', new Date()); // today, formatted as yyyy-MM-dd var today = format.asString('yyyy-MM-dd', new Date()) // today, formatted as yyyy-MM-dd
var version = require('./package.json').version; // math.js version var version = require('./package.json').version // math.js version
return String(fs.readFileSync(HEADER)) return String(fs.readFileSync(HEADER))
.replace('@@date', today) .replace('@@date', today)
.replace('@@version', version); .replace('@@version', version)
} }
var bannerPlugin = new webpack.BannerPlugin({ var bannerPlugin = new webpack.BannerPlugin({
banner: createBanner(), banner: createBanner(),
entryOnly: true, entryOnly: true,
raw: true raw: true
}); })
var webpackConfigModule = { var webpackConfigModule = {
rules: [ rules: [
@ -47,7 +47,7 @@ var webpackConfigModule = {
} }
} }
] ]
}; }
// create a single instance of the compiler to allow caching // create a single instance of the compiler to allow caching
var compiler = webpack({ var compiler = webpack({
@ -66,10 +66,10 @@ var compiler = webpack({
module: webpackConfigModule, module: webpackConfigModule,
resolve: { resolve: {
extensions: ['.js'], extensions: ['.js'],
mainFields: [ 'main' ], // pick ES5 version of vanilla-picker mainFields: ['main'] // pick ES5 version of vanilla-picker
}, },
cache: true cache: true
}); })
// create a single instance of the compiler to allow caching // create a single instance of the compiler to allow caching
var compilerMinimalist = webpack({ var compilerMinimalist = webpack({
@ -92,10 +92,10 @@ var compilerMinimalist = webpack({
minimize: false minimize: false
}, },
cache: true cache: true
}); })
function minify(name) { function minify (name) {
var code = String(fs.readFileSync(DIST + '/' + name + '.js')); var code = String(fs.readFileSync(DIST + '/' + name + '.js'))
var result = uglify.minify(code, { var result = uglify.minify(code, {
sourceMap: { sourceMap: {
url: name + '.map' url: name + '.map'
@ -104,80 +104,80 @@ function minify(name) {
comments: /@license/, comments: /@license/,
max_line_len: 64000 // extra large because we have embedded code for workers max_line_len: 64000 // extra large because we have embedded code for workers
} }
}); })
if (result.error) { if (result.error) {
throw result.error; throw result.error
} }
var fileMin = DIST + '/' + name + '.min.js'; var fileMin = DIST + '/' + name + '.min.js'
var fileMap = DIST + '/' + name + '.map'; var fileMap = DIST + '/' + name + '.map'
fs.writeFileSync(fileMin, result.code); fs.writeFileSync(fileMin, result.code)
fs.writeFileSync(fileMap, result.map); fs.writeFileSync(fileMap, result.map)
log('Minified ' + fileMin); log('Minified ' + fileMin)
log('Mapped ' + fileMap); log('Mapped ' + fileMap)
} }
// make dist folder structure // make dist folder structure
gulp.task('mkdir', function(done) { gulp.task('mkdir', function (done) {
mkdirp.sync(DIST); mkdirp.sync(DIST)
mkdirp.sync(DIST + '/img'); mkdirp.sync(DIST + '/img')
done(); done()
}); })
// bundle javascript // bundle javascript
gulp.task('bundle', function(done) { gulp.task('bundle', function (done) {
// update the banner contents (has a date in it which should stay up to date) // update the banner contents (has a date in it which should stay up to date)
bannerPlugin.banner = createBanner(); bannerPlugin.banner = createBanner()
compiler.run(function(err, stats) { compiler.run(function (err, stats) {
if (err) { if (err) {
log(err); log(err)
} }
log('bundled ' + NAME + '.js'); log('bundled ' + NAME + '.js')
done(); done()
}); })
}); })
// bundle minimalist version of javascript // bundle minimalist version of javascript
gulp.task('bundle-minimalist', function(done) { gulp.task('bundle-minimalist', function (done) {
// update the banner contents (has a date in it which should stay up to date) // update the banner contents (has a date in it which should stay up to date)
bannerPlugin.banner = createBanner(); bannerPlugin.banner = createBanner()
compilerMinimalist.run(function(err, stats) { compilerMinimalist.run(function (err, stats) {
if (err) { if (err) {
log(err); log(err)
} }
log('bundled ' + NAME_MINIMALIST + '.js'); log('bundled ' + NAME_MINIMALIST + '.js')
done(); done()
}); })
}); })
// bundle css // bundle css
gulp.task('bundle-css', function(done) { gulp.task('bundle-css', function (done) {
gulp gulp
.src([ .src([
'src/scss/reset.scss', 'src/scss/reset.scss',
'src/scss/jsoneditor.scss', 'src/scss/jsoneditor.scss',
'src/scss/contextmenu.scss', 'src/scss/contextmenu.scss',
'src/scss/menu.scss', 'src/scss/menu.scss',
'src/scss/searchbox.scss', 'src/scss/searchbox.scss',
'src/scss/autocomplete.scss', 'src/scss/autocomplete.scss',
'src/scss/treepath.scss', 'src/scss/treepath.scss',
'src/scss/statusbar.scss', 'src/scss/statusbar.scss',
'src/scss/navigationbar.scss', 'src/scss/navigationbar.scss',
'src/js/assets/selectr/selectr.scss', 'src/js/assets/selectr/selectr.scss'
]) ])
.pipe( .pipe(
sass({ sass({
// importer: tildeImporter // importer: tildeImporter
}) })
) )
.pipe(concatCss(NAME + '.css')) .pipe(concatCss(NAME + '.css'))
@ -189,48 +189,48 @@ gulp.task('bundle-css', function(done) {
}) })
// create a folder img and copy the icons // create a folder img and copy the icons
gulp.task('copy-img', function(done) { gulp.task('copy-img', function (done) {
gulp.src(IMAGE).pipe(gulp.dest(DIST + '/img')); gulp.src(IMAGE).pipe(gulp.dest(DIST + '/img'))
log('Copied images'); log('Copied images')
done(); done()
}); })
// create a folder img and copy the icons // create a folder img and copy the icons
gulp.task('copy-docs', function(done) { gulp.task('copy-docs', function (done) {
gulp.src(DOCS).pipe(gulp.dest(DIST)); gulp.src(DOCS).pipe(gulp.dest(DIST))
log('Copied doc'); log('Copied doc')
done(); done()
}); })
gulp.task('minify', function(done) { gulp.task('minify', function (done) {
minify(NAME); minify(NAME)
done(); done()
}); })
gulp.task('minify-minimalist', function(done) { gulp.task('minify-minimalist', function (done) {
minify(NAME_MINIMALIST); minify(NAME_MINIMALIST)
done(); done()
}); })
// The watch task (to automatically rebuild when the source code changes) // The watch task (to automatically rebuild when the source code changes)
// Does only generate jsoneditor.js and jsoneditor.css, and copy the image // Does only generate jsoneditor.js and jsoneditor.css, and copy the image
// Does NOT minify the code and does NOT generate the minimalist version // Does NOT minify the code and does NOT generate the minimalist version
gulp.task('watch', gulp.series('bundle', 'bundle-css', 'copy-img', function() { gulp.task('watch', gulp.series('bundle', 'bundle-css', 'copy-img', function () {
gulp.watch(['src/**/*'], gulp.series('bundle', 'bundle-css', 'copy-img')); gulp.watch(['src/**/*'], gulp.series('bundle', 'bundle-css', 'copy-img'))
})); }))
// The default task (called when you run `gulp`) // The default task (called when you run `gulp`)
gulp.task('default', gulp.series( gulp.task('default', gulp.series(
'mkdir', 'mkdir',
gulp.parallel( gulp.parallel(
'copy-img', 'copy-img',
'copy-docs', 'copy-docs',
'bundle-css', 'bundle-css',
gulp.series('bundle', 'minify'), gulp.series('bundle', 'minify'),
gulp.series('bundle-minimalist', 'minify-minimalist') gulp.series('bundle-minimalist', 'minify-minimalist')
) )
)); ))

View File

@ -1 +1 @@
module.exports = require('./src/js/JSONEditor'); module.exports = require('./src/js/JSONEditor')

1331
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,8 @@
"build": "gulp", "build": "gulp",
"minify": "gulp minify", "minify": "gulp minify",
"start": "gulp watch", "start": "gulp watch",
"test": "mocha test" "test": "mocha test",
"lint": "standard --env=mocha"
}, },
"dependencies": { "dependencies": {
"ajv": "6.10.2", "ajv": "6.10.2",
@ -47,7 +48,14 @@
"json-loader": "0.5.7", "json-loader": "0.5.7",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"mocha": "6.2.0", "mocha": "6.2.0",
"standard": "14.0.2",
"uglify-js": "3.6.0", "uglify-js": "3.6.0",
"webpack": "4.39.3" "webpack": "4.39.3"
},
"standard": {
"ignore": [
"src/js/assets",
"examples/react*"
]
} }
} }

View File

@ -1,8 +1,8 @@
'use strict'; 'use strict'
var createAbsoluteAnchor = require('./createAbsoluteAnchor').createAbsoluteAnchor; var createAbsoluteAnchor = require('./createAbsoluteAnchor').createAbsoluteAnchor
var util = require('./util'); var util = require('./util')
var translate = require('./i18n').translate; var translate = require('./i18n').translate
/** /**
* A context menu * A context menu
@ -14,153 +14,150 @@ var translate = require('./i18n').translate;
* @constructor * @constructor
*/ */
function ContextMenu (items, options) { function ContextMenu (items, options) {
this.dom = {}; this.dom = {}
var me = this; var me = this
var dom = this.dom; var dom = this.dom
this.anchor = undefined; this.anchor = undefined
this.items = items; this.items = items
this.eventListeners = {}; this.eventListeners = {}
this.selection = undefined; // holds the selection before the menu was opened this.selection = undefined // holds the selection before the menu was opened
this.onClose = options ? options.close : undefined; this.onClose = options ? options.close : undefined
// create root element // create root element
var root = document.createElement('div'); var root = document.createElement('div')
root.className = 'jsoneditor-contextmenu-root'; root.className = 'jsoneditor-contextmenu-root'
dom.root = root; dom.root = root
// create a container element // create a container element
var menu = document.createElement('div'); var menu = document.createElement('div')
menu.className = 'jsoneditor-contextmenu'; menu.className = 'jsoneditor-contextmenu'
dom.menu = menu; dom.menu = menu
root.appendChild(menu); root.appendChild(menu)
// create a list to hold the menu items // create a list to hold the menu items
var list = document.createElement('ul'); var list = document.createElement('ul')
list.className = 'jsoneditor-menu'; list.className = 'jsoneditor-menu'
menu.appendChild(list); menu.appendChild(list)
dom.list = list; dom.list = list
dom.items = []; // list with all buttons dom.items = [] // list with all buttons
// create a (non-visible) button to set the focus to the menu // create a (non-visible) button to set the focus to the menu
var focusButton = document.createElement('button'); var focusButton = document.createElement('button')
focusButton.type = 'button'; focusButton.type = 'button'
dom.focusButton = focusButton; dom.focusButton = focusButton
var li = document.createElement('li'); var li = document.createElement('li')
li.style.overflow = 'hidden'; li.style.overflow = 'hidden'
li.style.height = '0'; li.style.height = '0'
li.appendChild(focusButton); li.appendChild(focusButton)
list.appendChild(li); list.appendChild(li)
function createMenuItems (list, domItems, items) { function createMenuItems (list, domItems, items) {
items.forEach(function (item) { items.forEach(function (item) {
if (item.type == 'separator') { if (item.type === 'separator') {
// create a separator // create a separator
var separator = document.createElement('div'); var separator = document.createElement('div')
separator.className = 'jsoneditor-separator'; separator.className = 'jsoneditor-separator'
li = document.createElement('li'); const li = document.createElement('li')
li.appendChild(separator); li.appendChild(separator)
list.appendChild(li); list.appendChild(li)
} } else {
else { var domItem = {}
var domItem = {};
// create a menu item // create a menu item
var li = document.createElement('li'); const li = document.createElement('li')
list.appendChild(li); list.appendChild(li)
// create a button in the menu item // create a button in the menu item
var button = document.createElement('button'); var button = document.createElement('button')
button.type = 'button'; button.type = 'button'
button.className = item.className; button.className = item.className
domItem.button = button; domItem.button = button
if (item.title) { if (item.title) {
button.title = item.title; button.title = item.title
} }
if (item.click) { if (item.click) {
button.onclick = function (event) { button.onclick = function (event) {
event.preventDefault(); event.preventDefault()
me.hide(); me.hide()
item.click(); item.click()
}; }
} }
li.appendChild(button); li.appendChild(button)
// create the contents of the button // create the contents of the button
if (item.submenu) { if (item.submenu) {
// add the icon to the button // add the icon to the button
var divIcon = document.createElement('div'); var divIcon = document.createElement('div')
divIcon.className = 'jsoneditor-icon'; divIcon.className = 'jsoneditor-icon'
button.appendChild(divIcon); button.appendChild(divIcon)
var divText = document.createElement('div'); var divText = document.createElement('div')
divText.className = 'jsoneditor-text' + divText.className = 'jsoneditor-text' +
(item.click ? '' : ' jsoneditor-right-margin'); (item.click ? '' : ' jsoneditor-right-margin')
divText.appendChild(document.createTextNode(item.text)); divText.appendChild(document.createTextNode(item.text))
button.appendChild(divText); button.appendChild(divText)
var buttonSubmenu; var buttonSubmenu
if (item.click) { if (item.click) {
// submenu and a button with a click handler // submenu and a button with a click handler
button.className += ' jsoneditor-default'; button.className += ' jsoneditor-default'
var buttonExpand = document.createElement('button'); var buttonExpand = document.createElement('button')
buttonExpand.type = 'button'; buttonExpand.type = 'button'
domItem.buttonExpand = buttonExpand; domItem.buttonExpand = buttonExpand
buttonExpand.className = 'jsoneditor-expand'; buttonExpand.className = 'jsoneditor-expand'
buttonExpand.innerHTML = '<div class="jsoneditor-expand"></div>'; buttonExpand.innerHTML = '<div class="jsoneditor-expand"></div>'
li.appendChild(buttonExpand); li.appendChild(buttonExpand)
if (item.submenuTitle) { if (item.submenuTitle) {
buttonExpand.title = item.submenuTitle; buttonExpand.title = item.submenuTitle
} }
buttonSubmenu = buttonExpand; buttonSubmenu = buttonExpand
} } else {
else {
// submenu and a button without a click handler // submenu and a button without a click handler
var divExpand = document.createElement('div'); var divExpand = document.createElement('div')
divExpand.className = 'jsoneditor-expand'; divExpand.className = 'jsoneditor-expand'
button.appendChild(divExpand); button.appendChild(divExpand)
buttonSubmenu = button; buttonSubmenu = button
} }
// attach a handler to expand/collapse the submenu // attach a handler to expand/collapse the submenu
buttonSubmenu.onclick = function (event) { buttonSubmenu.onclick = function (event) {
event.preventDefault(); event.preventDefault()
me._onExpandItem(domItem); me._onExpandItem(domItem)
buttonSubmenu.focus(); buttonSubmenu.focus()
}; }
// create the submenu // create the submenu
var domSubItems = []; var domSubItems = []
domItem.subItems = domSubItems; domItem.subItems = domSubItems
var ul = document.createElement('ul'); var ul = document.createElement('ul')
domItem.ul = ul; domItem.ul = ul
ul.className = 'jsoneditor-menu'; ul.className = 'jsoneditor-menu'
ul.style.height = '0'; ul.style.height = '0'
li.appendChild(ul); li.appendChild(ul)
createMenuItems(ul, domSubItems, item.submenu); createMenuItems(ul, domSubItems, item.submenu)
} } else {
else {
// no submenu, just a button with clickhandler // no submenu, just a button with clickhandler
button.innerHTML = '<div class="jsoneditor-icon"></div>' + button.innerHTML = '<div class="jsoneditor-icon"></div>' +
'<div class="jsoneditor-text">' + translate(item.text) + '</div>'; '<div class="jsoneditor-text">' + translate(item.text) + '</div>'
} }
domItems.push(domItem); domItems.push(domItem)
} }
}); })
} }
createMenuItems(list, this.dom.items, items); createMenuItems(list, this.dom.items, items)
// TODO: when the editor is small, show the submenu on the right instead of inline? // TODO: when the editor is small, show the submenu on the right instead of inline?
// calculate the max height of the menu with one submenu expanded // calculate the max height of the menu with one submenu expanded
this.maxHeight = 0; // height in pixels this.maxHeight = 0 // height in pixels
items.forEach(function (item) { items.forEach(function (item) {
var height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24; var height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24
me.maxHeight = Math.max(me.maxHeight, height); me.maxHeight = Math.max(me.maxHeight, height)
}); })
} }
/** /**
@ -169,29 +166,29 @@ function ContextMenu (items, options) {
* @private * @private
*/ */
ContextMenu.prototype._getVisibleButtons = function () { ContextMenu.prototype._getVisibleButtons = function () {
var buttons = []; var buttons = []
var me = this; var me = this
this.dom.items.forEach(function (item) { this.dom.items.forEach(function (item) {
buttons.push(item.button); buttons.push(item.button)
if (item.buttonExpand) { if (item.buttonExpand) {
buttons.push(item.buttonExpand); buttons.push(item.buttonExpand)
} }
if (item.subItems && item == me.expandedItem) { if (item.subItems && item === me.expandedItem) {
item.subItems.forEach(function (subItem) { item.subItems.forEach(function (subItem) {
buttons.push(subItem.button); buttons.push(subItem.button)
if (subItem.buttonExpand) { if (subItem.buttonExpand) {
buttons.push(subItem.buttonExpand); buttons.push(subItem.buttonExpand)
} }
// TODO: change to fully recursive method // TODO: change to fully recursive method
}); })
} }
}); })
return buttons; return buttons
}; }
// currently displayed context menu, a singleton. We may only have one visible context menu // currently displayed context menu, a singleton. We may only have one visible context menu
ContextMenu.visibleMenu = undefined; ContextMenu.visibleMenu = undefined
/** /**
* Attach the menu to an anchor * Attach the menu to an anchor
@ -200,64 +197,61 @@ ContextMenu.visibleMenu = undefined;
* @param {Boolean=} ignoreParent ignore anchor parent in regard to the calculation of the position, needed when the parent position is absolute * @param {Boolean=} ignoreParent ignore anchor parent in regard to the calculation of the position, needed when the parent position is absolute
*/ */
ContextMenu.prototype.show = function (anchor, frame, ignoreParent) { ContextMenu.prototype.show = function (anchor, frame, ignoreParent) {
this.hide(); this.hide()
// determine whether to display the menu below or above the anchor // determine whether to display the menu below or above the anchor
var showBelow = true; var showBelow = true
var parent = anchor.parentNode; var parent = anchor.parentNode
var anchorRect = anchor.getBoundingClientRect(); var anchorRect = anchor.getBoundingClientRect()
var parentRect = parent.getBoundingClientRect(); var parentRect = parent.getBoundingClientRect()
var frameRect = frame.getBoundingClientRect(); var frameRect = frame.getBoundingClientRect()
var me = this; var me = this
this.dom.absoluteAnchor = createAbsoluteAnchor(anchor, frame, function () { this.dom.absoluteAnchor = createAbsoluteAnchor(anchor, frame, function () {
me.hide() me.hide()
}); })
if (anchorRect.bottom + this.maxHeight < frameRect.bottom) { if (anchorRect.bottom + this.maxHeight < frameRect.bottom) {
// fits below -> show below // fits below -> show below
} } else if (anchorRect.top - this.maxHeight > frameRect.top) {
else if (anchorRect.top - this.maxHeight > frameRect.top) {
// fits above -> show above // fits above -> show above
showBelow = false; showBelow = false
} } else {
else {
// doesn't fit above nor below -> show below // doesn't fit above nor below -> show below
} }
var topGap = ignoreParent ? 0 : (anchorRect.top - parentRect.top); var topGap = ignoreParent ? 0 : (anchorRect.top - parentRect.top)
// position the menu // position the menu
if (showBelow) { if (showBelow) {
// display the menu below the anchor // display the menu below the anchor
var anchorHeight = anchor.offsetHeight; var anchorHeight = anchor.offsetHeight
this.dom.menu.style.left = '0'; this.dom.menu.style.left = '0'
this.dom.menu.style.top = topGap + anchorHeight + 'px'; this.dom.menu.style.top = topGap + anchorHeight + 'px'
this.dom.menu.style.bottom = ''; this.dom.menu.style.bottom = ''
} } else {
else {
// display the menu above the anchor // display the menu above the anchor
this.dom.menu.style.left = '0'; this.dom.menu.style.left = '0'
this.dom.menu.style.top = ''; this.dom.menu.style.top = ''
this.dom.menu.style.bottom = '0px'; this.dom.menu.style.bottom = '0px'
} }
// attach the menu to the temporary, absolute anchor // attach the menu to the temporary, absolute anchor
// parent.insertBefore(this.dom.root, anchor); // parent.insertBefore(this.dom.root, anchor);
this.dom.absoluteAnchor.appendChild(this.dom.root); this.dom.absoluteAnchor.appendChild(this.dom.root)
// move focus to the first button in the context menu // move focus to the first button in the context menu
this.selection = util.getSelection(); this.selection = util.getSelection()
this.anchor = anchor; this.anchor = anchor
setTimeout(function () { setTimeout(function () {
me.dom.focusButton.focus(); me.dom.focusButton.focus()
}, 0); }, 0)
if (ContextMenu.visibleMenu) { if (ContextMenu.visibleMenu) {
ContextMenu.visibleMenu.hide(); ContextMenu.visibleMenu.hide()
} }
ContextMenu.visibleMenu = this; ContextMenu.visibleMenu = this
}; }
/** /**
* Hide the context menu if visible * Hide the context menu if visible
@ -265,22 +259,22 @@ ContextMenu.prototype.show = function (anchor, frame, ignoreParent) {
ContextMenu.prototype.hide = function () { ContextMenu.prototype.hide = function () {
// remove temporary absolutely positioned anchor // remove temporary absolutely positioned anchor
if (this.dom.absoluteAnchor) { if (this.dom.absoluteAnchor) {
this.dom.absoluteAnchor.destroy(); this.dom.absoluteAnchor.destroy()
delete this.dom.absoluteAnchor; delete this.dom.absoluteAnchor
} }
// remove the menu from the DOM // remove the menu from the DOM
if (this.dom.root.parentNode) { if (this.dom.root.parentNode) {
this.dom.root.parentNode.removeChild(this.dom.root); this.dom.root.parentNode.removeChild(this.dom.root)
if (this.onClose) { if (this.onClose) {
this.onClose(); this.onClose()
} }
} }
if (ContextMenu.visibleMenu == this) { if (ContextMenu.visibleMenu === this) {
ContextMenu.visibleMenu = undefined; ContextMenu.visibleMenu = undefined
} }
}; }
/** /**
* Expand a submenu * Expand a submenu
@ -289,42 +283,43 @@ ContextMenu.prototype.hide = function () {
* @private * @private
*/ */
ContextMenu.prototype._onExpandItem = function (domItem) { ContextMenu.prototype._onExpandItem = function (domItem) {
var me = this; var me = this
var alreadyVisible = (domItem == this.expandedItem); var alreadyVisible = (domItem === this.expandedItem)
// hide the currently visible submenu // hide the currently visible submenu
var expandedItem = this.expandedItem; var expandedItem = this.expandedItem
if (expandedItem) { if (expandedItem) {
//var ul = expandedItem.ul; // var ul = expandedItem.ul;
expandedItem.ul.style.height = '0'; expandedItem.ul.style.height = '0'
expandedItem.ul.style.padding = ''; expandedItem.ul.style.padding = ''
setTimeout(function () { setTimeout(function () {
if (me.expandedItem != expandedItem) { if (me.expandedItem !== expandedItem) {
expandedItem.ul.style.display = ''; expandedItem.ul.style.display = ''
util.removeClassName(expandedItem.ul.parentNode, 'jsoneditor-selected'); util.removeClassName(expandedItem.ul.parentNode, 'jsoneditor-selected')
} }
}, 300); // timeout duration must match the css transition duration }, 300) // timeout duration must match the css transition duration
this.expandedItem = undefined; this.expandedItem = undefined
} }
if (!alreadyVisible) { if (!alreadyVisible) {
var ul = domItem.ul; var ul = domItem.ul
ul.style.display = 'block'; ul.style.display = 'block'
var height = ul.clientHeight; // force a reflow in Firefox // eslint-disable-next-line no-unused-expressions
ul.clientHeight // force a reflow in Firefox
setTimeout(function () { setTimeout(function () {
if (me.expandedItem == domItem) { if (me.expandedItem === domItem) {
var childsHeight = 0; var childsHeight = 0
for (var i = 0; i < ul.childNodes.length; i++) { for (var i = 0; i < ul.childNodes.length; i++) {
childsHeight += ul.childNodes[i].clientHeight; childsHeight += ul.childNodes[i].clientHeight
} }
ul.style.height = childsHeight + 'px'; ul.style.height = childsHeight + 'px'
ul.style.padding = '5px 10px'; ul.style.padding = '5px 10px'
} }
}, 0); }, 0)
util.addClassName(ul.parentNode, 'jsoneditor-selected'); util.addClassName(ul.parentNode, 'jsoneditor-selected')
this.expandedItem = domItem; this.expandedItem = domItem
} }
}; }
/** /**
* Handle onkeydown event * Handle onkeydown event
@ -332,107 +327,101 @@ ContextMenu.prototype._onExpandItem = function (domItem) {
* @private * @private
*/ */
ContextMenu.prototype._onKeyDown = function (event) { ContextMenu.prototype._onKeyDown = function (event) {
var target = event.target; var target = event.target
var keynum = event.which; var keynum = event.which
var handled = false; var handled = false
var buttons, targetIndex, prevButton, nextButton; var buttons, targetIndex, prevButton, nextButton
if (keynum == 27) { // ESC if (keynum === 27) { // ESC
// hide the menu on ESC key // hide the menu on ESC key
// restore previous selection and focus // restore previous selection and focus
if (this.selection) { if (this.selection) {
util.setSelection(this.selection); util.setSelection(this.selection)
} }
if (this.anchor) { if (this.anchor) {
this.anchor.focus(); this.anchor.focus()
} }
this.hide(); this.hide()
handled = true; handled = true
} } else if (keynum === 9) { // Tab
else if (keynum == 9) { // Tab
if (!event.shiftKey) { // Tab if (!event.shiftKey) { // Tab
buttons = this._getVisibleButtons(); buttons = this._getVisibleButtons()
targetIndex = buttons.indexOf(target); targetIndex = buttons.indexOf(target)
if (targetIndex == buttons.length - 1) { if (targetIndex === buttons.length - 1) {
// move to first button // move to first button
buttons[0].focus(); buttons[0].focus()
handled = true; handled = true
} }
} } else { // Shift+Tab
else { // Shift+Tab buttons = this._getVisibleButtons()
buttons = this._getVisibleButtons(); targetIndex = buttons.indexOf(target)
targetIndex = buttons.indexOf(target); if (targetIndex === 0) {
if (targetIndex == 0) {
// move to last button // move to last button
buttons[buttons.length - 1].focus(); buttons[buttons.length - 1].focus()
handled = true; handled = true
} }
} }
} } else if (keynum === 37) { // Arrow Left
else if (keynum == 37) { // Arrow Left if (target.className === 'jsoneditor-expand') {
if (target.className == 'jsoneditor-expand') { buttons = this._getVisibleButtons()
buttons = this._getVisibleButtons(); targetIndex = buttons.indexOf(target)
targetIndex = buttons.indexOf(target); prevButton = buttons[targetIndex - 1]
prevButton = buttons[targetIndex - 1];
if (prevButton) { if (prevButton) {
prevButton.focus(); prevButton.focus()
} }
} }
handled = true; handled = true
} } else if (keynum === 38) { // Arrow Up
else if (keynum == 38) { // Arrow Up buttons = this._getVisibleButtons()
buttons = this._getVisibleButtons(); targetIndex = buttons.indexOf(target)
targetIndex = buttons.indexOf(target); prevButton = buttons[targetIndex - 1]
prevButton = buttons[targetIndex - 1]; if (prevButton && prevButton.className === 'jsoneditor-expand') {
if (prevButton && prevButton.className == 'jsoneditor-expand') {
// skip expand button // skip expand button
prevButton = buttons[targetIndex - 2]; prevButton = buttons[targetIndex - 2]
} }
if (!prevButton) { if (!prevButton) {
// move to last button // move to last button
prevButton = buttons[buttons.length - 1]; prevButton = buttons[buttons.length - 1]
} }
if (prevButton) { if (prevButton) {
prevButton.focus(); prevButton.focus()
} }
handled = true; handled = true
} } else if (keynum === 39) { // Arrow Right
else if (keynum == 39) { // Arrow Right buttons = this._getVisibleButtons()
buttons = this._getVisibleButtons(); targetIndex = buttons.indexOf(target)
targetIndex = buttons.indexOf(target); nextButton = buttons[targetIndex + 1]
nextButton = buttons[targetIndex + 1]; if (nextButton && nextButton.className === 'jsoneditor-expand') {
if (nextButton && nextButton.className == 'jsoneditor-expand') { nextButton.focus()
nextButton.focus();
} }
handled = true; handled = true
} } else if (keynum === 40) { // Arrow Down
else if (keynum == 40) { // Arrow Down buttons = this._getVisibleButtons()
buttons = this._getVisibleButtons(); targetIndex = buttons.indexOf(target)
targetIndex = buttons.indexOf(target); nextButton = buttons[targetIndex + 1]
nextButton = buttons[targetIndex + 1]; if (nextButton && nextButton.className === 'jsoneditor-expand') {
if (nextButton && nextButton.className == 'jsoneditor-expand') {
// skip expand button // skip expand button
nextButton = buttons[targetIndex + 2]; nextButton = buttons[targetIndex + 2]
} }
if (!nextButton) { if (!nextButton) {
// move to first button // move to first button
nextButton = buttons[0]; nextButton = buttons[0]
} }
if (nextButton) { if (nextButton) {
nextButton.focus(); nextButton.focus()
handled = true; handled = true
} }
handled = true; handled = true
} }
// TODO: arrow left and right // TODO: arrow left and right
if (handled) { if (handled) {
event.stopPropagation(); event.stopPropagation()
event.preventDefault(); event.preventDefault()
} }
}; }
module.exports = ContextMenu; module.exports = ContextMenu

View File

@ -8,170 +8,167 @@
* @constructor * @constructor
*/ */
function ErrorTable (config) { function ErrorTable (config) {
this.errorTableVisible = config.errorTableVisible; this.errorTableVisible = config.errorTableVisible
this.onToggleVisibility = config.onToggleVisibility; this.onToggleVisibility = config.onToggleVisibility
this.onFocusLine = config.onFocusLine || function () {}; this.onFocusLine = config.onFocusLine || function () {}
this.onChangeHeight = config.onChangeHeight; this.onChangeHeight = config.onChangeHeight
this.dom = {}; this.dom = {}
var validationErrorsContainer = document.createElement('div'); var validationErrorsContainer = document.createElement('div')
validationErrorsContainer.className = 'jsoneditor-validation-errors-container'; validationErrorsContainer.className = 'jsoneditor-validation-errors-container'
this.dom.validationErrorsContainer = validationErrorsContainer; this.dom.validationErrorsContainer = validationErrorsContainer
var additionalErrorsIndication = document.createElement('div'); var additionalErrorsIndication = document.createElement('div')
additionalErrorsIndication.style.display = 'none'; additionalErrorsIndication.style.display = 'none'
additionalErrorsIndication.className = "jsoneditor-additional-errors fadein"; additionalErrorsIndication.className = 'jsoneditor-additional-errors fadein'
additionalErrorsIndication.innerHTML = "Scroll for more &#9663;"; additionalErrorsIndication.innerHTML = 'Scroll for more &#9663;'
this.dom.additionalErrorsIndication = additionalErrorsIndication; this.dom.additionalErrorsIndication = additionalErrorsIndication
validationErrorsContainer.appendChild(additionalErrorsIndication); validationErrorsContainer.appendChild(additionalErrorsIndication)
var validationErrorIcon = document.createElement('span'); var validationErrorIcon = document.createElement('span')
validationErrorIcon.className = 'jsoneditor-validation-error-icon'; validationErrorIcon.className = 'jsoneditor-validation-error-icon'
validationErrorIcon.style.display = 'none'; validationErrorIcon.style.display = 'none'
this.dom.validationErrorIcon = validationErrorIcon; this.dom.validationErrorIcon = validationErrorIcon
var validationErrorCount = document.createElement('span'); var validationErrorCount = document.createElement('span')
validationErrorCount.className = 'jsoneditor-validation-error-count'; validationErrorCount.className = 'jsoneditor-validation-error-count'
validationErrorCount.style.display = 'none'; validationErrorCount.style.display = 'none'
this.dom.validationErrorCount = validationErrorCount; this.dom.validationErrorCount = validationErrorCount
this.dom.parseErrorIndication = document.createElement('span'); this.dom.parseErrorIndication = document.createElement('span')
this.dom.parseErrorIndication.className = 'jsoneditor-parse-error-icon'; this.dom.parseErrorIndication.className = 'jsoneditor-parse-error-icon'
this.dom.parseErrorIndication.style.display = 'none'; this.dom.parseErrorIndication.style.display = 'none'
} }
ErrorTable.prototype.getErrorTable = function () { ErrorTable.prototype.getErrorTable = function () {
return this.dom.validationErrorsContainer; return this.dom.validationErrorsContainer
}; }
ErrorTable.prototype.getErrorCounter = function () { ErrorTable.prototype.getErrorCounter = function () {
return this.dom.validationErrorCount; return this.dom.validationErrorCount
}; }
ErrorTable.prototype.getWarningIcon = function () { ErrorTable.prototype.getWarningIcon = function () {
return this.dom.validationErrorIcon; return this.dom.validationErrorIcon
}; }
ErrorTable.prototype.getErrorIcon = function () { ErrorTable.prototype.getErrorIcon = function () {
return this.dom.parseErrorIndication; return this.dom.parseErrorIndication
}; }
ErrorTable.prototype.toggleTableVisibility = function () { ErrorTable.prototype.toggleTableVisibility = function () {
this.errorTableVisible = !this.errorTableVisible; this.errorTableVisible = !this.errorTableVisible
this.onToggleVisibility(this.errorTableVisible); this.onToggleVisibility(this.errorTableVisible)
} }
ErrorTable.prototype.setErrors = function (errors, errorLocations) { ErrorTable.prototype.setErrors = function (errors, errorLocations) {
var me = this; var me = this
// clear any previous errors // clear any previous errors
if (this.dom.validationErrors) { if (this.dom.validationErrors) {
this.dom.validationErrors.parentNode.removeChild(this.dom.validationErrors); this.dom.validationErrors.parentNode.removeChild(this.dom.validationErrors)
this.dom.validationErrors = null; this.dom.validationErrors = null
this.dom.additionalErrorsIndication.style.display = 'none'; this.dom.additionalErrorsIndication.style.display = 'none'
} }
// create the table with errors // create the table with errors
// keep default behavior for parse errors // keep default behavior for parse errors
if (this.errorTableVisible && errors.length > 0) { if (this.errorTableVisible && errors.length > 0) {
var validationErrors = document.createElement('div'); var validationErrors = document.createElement('div')
validationErrors.className = 'jsoneditor-validation-errors'; validationErrors.className = 'jsoneditor-validation-errors'
validationErrors.innerHTML = '<table class="jsoneditor-text-errors"><tbody></tbody></table>'; validationErrors.innerHTML = '<table class="jsoneditor-text-errors"><tbody></tbody></table>'
var tbody = validationErrors.getElementsByTagName('tbody')[0]; var tbody = validationErrors.getElementsByTagName('tbody')[0]
errors.forEach(function (error) { errors.forEach(function (error) {
var message; var message
if (typeof error === 'string') { if (typeof error === 'string') {
message = '<td colspan="2"><pre>' + error + '</pre></td>'; message = '<td colspan="2"><pre>' + error + '</pre></td>'
} } else {
else {
message = message =
'<td>' + (error.dataPath || '') + '</td>' + '<td>' + (error.dataPath || '') + '</td>' +
'<td><pre>' + error.message + '</pre></td>'; '<td><pre>' + error.message + '</pre></td>'
} }
var line; var line
if (!isNaN(error.line)) { if (!isNaN(error.line)) {
line = error.line; line = error.line
} else if (error.dataPath) { } else if (error.dataPath) {
var errLoc = errorLocations.find(function(loc) { return loc.path === error.dataPath; }); var errLoc = errorLocations.find(function (loc) { return loc.path === error.dataPath })
if (errLoc) { if (errLoc) {
line = errLoc.line + 1; line = errLoc.line + 1
} }
} }
var trEl = document.createElement('tr'); var trEl = document.createElement('tr')
trEl.className = !isNaN(line) ? 'jump-to-line' : ''; trEl.className = !isNaN(line) ? 'jump-to-line' : ''
if (error.type === 'error') { if (error.type === 'error') {
trEl.className += ' parse-error'; trEl.className += ' parse-error'
} else { } else {
trEl.className += ' validation-error'; trEl.className += ' validation-error'
} }
trEl.innerHTML = ('<td><button class="jsoneditor-schema-error"></button></td><td style="white-space:nowrap;">'+ (!isNaN(line) ? ('Ln ' + line) : '') +'</td>' + message); trEl.innerHTML = ('<td><button class="jsoneditor-schema-error"></button></td><td style="white-space:nowrap;">' + (!isNaN(line) ? ('Ln ' + line) : '') + '</td>' + message)
trEl.onclick = function() { trEl.onclick = function () {
me.onFocusLine(line); me.onFocusLine(line)
}; }
tbody.appendChild(trEl); tbody.appendChild(trEl)
}); })
this.dom.validationErrors = validationErrors; this.dom.validationErrors = validationErrors
this.dom.validationErrorsContainer.appendChild(validationErrors); this.dom.validationErrorsContainer.appendChild(validationErrors)
this.dom.additionalErrorsIndication.title = errors.length + " errors total"; this.dom.additionalErrorsIndication.title = errors.length + ' errors total'
if (this.dom.validationErrorsContainer.clientHeight < this.dom.validationErrorsContainer.scrollHeight) { if (this.dom.validationErrorsContainer.clientHeight < this.dom.validationErrorsContainer.scrollHeight) {
this.dom.additionalErrorsIndication.style.display = 'block'; this.dom.additionalErrorsIndication.style.display = 'block'
this.dom.validationErrorsContainer.onscroll = function () { this.dom.validationErrorsContainer.onscroll = function () {
me.dom.additionalErrorsIndication.style.display = me.dom.additionalErrorsIndication.style.display =
(me.dom.validationErrorsContainer.clientHeight > 0 && me.dom.validationErrorsContainer.scrollTop === 0) ? 'block' : 'none'; (me.dom.validationErrorsContainer.clientHeight > 0 && me.dom.validationErrorsContainer.scrollTop === 0) ? 'block' : 'none'
} }
} else { } else {
this.dom.validationErrorsContainer.onscroll = undefined; this.dom.validationErrorsContainer.onscroll = undefined
} }
var height = this.dom.validationErrorsContainer.clientHeight + (this.dom.statusBar ? this.dom.statusBar.clientHeight : 0); var height = this.dom.validationErrorsContainer.clientHeight + (this.dom.statusBar ? this.dom.statusBar.clientHeight : 0)
// this.content.style.marginBottom = (-height) + 'px'; // this.content.style.marginBottom = (-height) + 'px';
// this.content.style.paddingBottom = height + 'px'; // this.content.style.paddingBottom = height + 'px';
this.onChangeHeight(height); this.onChangeHeight(height)
} else { } else {
this.onChangeHeight(0); this.onChangeHeight(0)
} }
// update the status bar // update the status bar
var validationErrorsCount = errors.filter(function (error) { var validationErrorsCount = errors.filter(function (error) {
return error.type !== 'error' return error.type !== 'error'
}).length; }).length
if (validationErrorsCount > 0) { if (validationErrorsCount > 0) {
this.dom.validationErrorCount.style.display = 'inline'; this.dom.validationErrorCount.style.display = 'inline'
this.dom.validationErrorCount.innerText = validationErrorsCount; this.dom.validationErrorCount.innerText = validationErrorsCount
this.dom.validationErrorCount.onclick = this.toggleTableVisibility.bind(this); this.dom.validationErrorCount.onclick = this.toggleTableVisibility.bind(this)
this.dom.validationErrorIcon.style.display = 'inline'; this.dom.validationErrorIcon.style.display = 'inline'
this.dom.validationErrorIcon.title = validationErrorsCount + ' schema validation error(s) found'; this.dom.validationErrorIcon.title = validationErrorsCount + ' schema validation error(s) found'
this.dom.validationErrorIcon.onclick = this.toggleTableVisibility.bind(this); this.dom.validationErrorIcon.onclick = this.toggleTableVisibility.bind(this)
} } else {
else { this.dom.validationErrorCount.style.display = 'none'
this.dom.validationErrorCount.style.display = 'none'; this.dom.validationErrorIcon.style.display = 'none'
this.dom.validationErrorIcon.style.display = 'none';
} }
// update the parse error icon // update the parse error icon
var hasParseErrors = errors.some(function (error) { var hasParseErrors = errors.some(function (error) {
return error.type === 'error' return error.type === 'error'
}); })
if (hasParseErrors) { if (hasParseErrors) {
var line = errors[0].line var line = errors[0].line
this.dom.parseErrorIndication.style.display = 'block'; this.dom.parseErrorIndication.style.display = 'block'
this.dom.parseErrorIndication.title = !isNaN(line) this.dom.parseErrorIndication.title = !isNaN(line)
? ('parse error on line ' + line) ? ('parse error on line ' + line)
: 'parse error - check that the json is valid'; : 'parse error - check that the json is valid'
} else {
this.dom.parseErrorIndication.style.display = 'none'
} }
else { }
this.dom.parseErrorIndication.style.display = 'none';
}
};
module.exports = ErrorTable; module.exports = ErrorTable

View File

@ -1,4 +1,4 @@
'use strict'; 'use strict'
/** /**
* The highlighter can highlight/unhighlight a node, and * The highlighter can highlight/unhighlight a node, and
@ -6,7 +6,7 @@
* @constructor Highlighter * @constructor Highlighter
*/ */
function Highlighter () { function Highlighter () {
this.locked = false; this.locked = false
} }
/** /**
@ -15,23 +15,23 @@ function Highlighter () {
*/ */
Highlighter.prototype.highlight = function (node) { Highlighter.prototype.highlight = function (node) {
if (this.locked) { if (this.locked) {
return; return
} }
if (this.node != node) { if (this.node !== node) {
// unhighlight current node // unhighlight current node
if (this.node) { if (this.node) {
this.node.setHighlight(false); this.node.setHighlight(false)
} }
// highlight new node // highlight new node
this.node = node; this.node = node
this.node.setHighlight(true); this.node.setHighlight(true)
} }
// cancel any current timeout // cancel any current timeout
this._cancelUnhighlight(); this._cancelUnhighlight()
}; }
/** /**
* Unhighlight currently highlighted node. * Unhighlight currently highlighted node.
@ -39,23 +39,23 @@ Highlighter.prototype.highlight = function (node) {
*/ */
Highlighter.prototype.unhighlight = function () { Highlighter.prototype.unhighlight = function () {
if (this.locked) { if (this.locked) {
return; return
} }
var me = this; var me = this
if (this.node) { if (this.node) {
this._cancelUnhighlight(); this._cancelUnhighlight()
// do the unhighlighting after a small delay, to prevent re-highlighting // do the unhighlighting after a small delay, to prevent re-highlighting
// the same node when moving from the drag-icon to the contextmenu-icon // the same node when moving from the drag-icon to the contextmenu-icon
// or vice versa. // or vice versa.
this.unhighlightTimer = setTimeout(function () { this.unhighlightTimer = setTimeout(function () {
me.node.setHighlight(false); me.node.setHighlight(false)
me.node = undefined; me.node = undefined
me.unhighlightTimer = undefined; me.unhighlightTimer = undefined
}, 0); }, 0)
} }
}; }
/** /**
* Cancel an unhighlight action (if before the timeout of the unhighlight action) * Cancel an unhighlight action (if before the timeout of the unhighlight action)
@ -63,24 +63,24 @@ Highlighter.prototype.unhighlight = function () {
*/ */
Highlighter.prototype._cancelUnhighlight = function () { Highlighter.prototype._cancelUnhighlight = function () {
if (this.unhighlightTimer) { if (this.unhighlightTimer) {
clearTimeout(this.unhighlightTimer); clearTimeout(this.unhighlightTimer)
this.unhighlightTimer = undefined; this.unhighlightTimer = undefined
} }
}; }
/** /**
* Lock highlighting or unhighlighting nodes. * Lock highlighting or unhighlighting nodes.
* methods highlight and unhighlight do not work while locked. * methods highlight and unhighlight do not work while locked.
*/ */
Highlighter.prototype.lock = function () { Highlighter.prototype.lock = function () {
this.locked = true; this.locked = true
}; }
/** /**
* Unlock highlighting or unhighlighting nodes * Unlock highlighting or unhighlighting nodes
*/ */
Highlighter.prototype.unlock = function () { Highlighter.prototype.unlock = function () {
this.locked = false; this.locked = false
}; }
module.exports = Highlighter; module.exports = Highlighter

View File

@ -7,82 +7,81 @@
* @constructor * @constructor
*/ */
function History (onChange, calculateItemSize, limit) { function History (onChange, calculateItemSize, limit) {
this.onChange = onChange; this.onChange = onChange
this.calculateItemSize = calculateItemSize || function () { this.calculateItemSize = calculateItemSize || function () {
return 1; return 1
}; }
this.limit = limit; this.limit = limit
this.items = []; this.items = []
this.index = -1; this.index = -1
} }
History.prototype.add = function (item) { History.prototype.add = function (item) {
// limit number of items in history so that the total size doesn't // limit number of items in history so that the total size doesn't
// always keep at least one item in memory // always keep at least one item in memory
while (this._calculateHistorySize() > this.limit && this.items.length > 1) { while (this._calculateHistorySize() > this.limit && this.items.length > 1) {
this.items.shift(); this.items.shift()
this.index--; this.index--
} }
// cleanup any redo action that are not valid anymore // cleanup any redo action that are not valid anymore
this.items = this.items.slice(0, this.index + 1); this.items = this.items.slice(0, this.index + 1)
this.items.push(item); this.items.push(item)
this.index++; this.index++
this.onChange(); this.onChange()
}; }
History.prototype._calculateHistorySize = function () { History.prototype._calculateHistorySize = function () {
var calculateItemSize = this.calculateItemSize; var calculateItemSize = this.calculateItemSize
var totalSize = 0; var totalSize = 0
this.items.forEach(function (item) { this.items.forEach(function (item) {
totalSize += calculateItemSize(item); totalSize += calculateItemSize(item)
}); })
return totalSize; return totalSize
} }
History.prototype.undo = function () { History.prototype.undo = function () {
if (!this.canUndo()) { if (!this.canUndo()) {
return; return
} }
this.index--; this.index--
this.onChange(); this.onChange()
return this.items[this.index]; return this.items[this.index]
}; }
History.prototype.redo = function () { History.prototype.redo = function () {
if (!this.canRedo()) { if (!this.canRedo()) {
return; return
} }
this.index++; this.index++
this.onChange(); this.onChange()
return this.items[this.index]; return this.items[this.index]
}; }
History.prototype.canUndo = function () { History.prototype.canUndo = function () {
return this.index > 0; return this.index > 0
}; }
History.prototype.canRedo = function () { History.prototype.canRedo = function () {
return this.index < this.items.length - 1; return this.index < this.items.length - 1
}; }
History.prototype.clear = function () { History.prototype.clear = function () {
this.items = []; this.items = []
this.index = -1; this.index = -1
this.onChange(); this.onChange()
}; }
module.exports = History
module.exports = History;

View File

@ -1,23 +1,22 @@
'use strict'; 'use strict'
var Ajv; var Ajv
try { try {
Ajv = require('ajv'); Ajv = require('ajv')
} } catch (err) {
catch (err) {
// no problem... when we need Ajv we will throw a neat exception // no problem... when we need Ajv we will throw a neat exception
} }
var ace = require('./ace'); // may be undefined in case of minimalist bundle var ace = require('./ace') // may be undefined in case of minimalist bundle
var VanillaPicker = require('./vanilla-picker'); // may be undefined in case of minimalist bundle var VanillaPicker = require('./vanilla-picker') // may be undefined in case of minimalist bundle
var treemode = require('./treemode'); var treemode = require('./treemode')
var textmode = require('./textmode'); var textmode = require('./textmode')
var previewmode = require('./previewmode'); var previewmode = require('./previewmode')
var util = require('./util'); var util = require('./util')
if (typeof Promise === 'undefined') { if (typeof Promise === 'undefined') {
console.error('Promise undefined. Please load a Promise polyfill in the browser in order to use JSONEditor'); console.error('Promise undefined. Please load a Promise polyfill in the browser in order to use JSONEditor')
} }
/** /**
@ -89,41 +88,41 @@ if (typeof Promise === 'undefined') {
* Only applicable for * Only applicable for
* modes 'form', 'tree' and * modes 'form', 'tree' and
* 'view' * 'view'
* {Number} maxVisibleChilds Number of children allowed for a node * {Number} maxVisibleChilds Number of children allowed for a node
* in 'tree', 'view', or 'form' mode before * in 'tree', 'view', or 'form' mode before
* the "show more/show all" buttons appear. * the "show more/show all" buttons appear.
* 100 by default. * 100 by default.
* *
* @param {Object | undefined} json JSON object * @param {Object | undefined} json JSON object
*/ */
function JSONEditor (container, options, json) { function JSONEditor (container, options, json) {
if (!(this instanceof JSONEditor)) { if (!(this instanceof JSONEditor)) {
throw new Error('JSONEditor constructor called without "new".'); throw new Error('JSONEditor constructor called without "new".')
} }
// check for unsupported browser (IE8 and older) // check for unsupported browser (IE8 and older)
var ieVersion = util.getInternetExplorerVersion(); var ieVersion = util.getInternetExplorerVersion()
if (ieVersion != -1 && ieVersion < 9) { if (ieVersion !== -1 && ieVersion < 9) {
throw new Error('Unsupported browser, IE9 or newer required. ' + throw new Error('Unsupported browser, IE9 or newer required. ' +
'Please install the newest version of your browser.'); 'Please install the newest version of your browser.')
} }
if (options) { if (options) {
// check for deprecated options // check for deprecated options
if (options.error) { if (options.error) {
console.warn('Option "error" has been renamed to "onError"'); console.warn('Option "error" has been renamed to "onError"')
options.onError = options.error; options.onError = options.error
delete options.error; delete options.error
} }
if (options.change) { if (options.change) {
console.warn('Option "change" has been renamed to "onChange"'); console.warn('Option "change" has been renamed to "onChange"')
options.onChange = options.change; options.onChange = options.change
delete options.change; delete options.change
} }
if (options.editable) { if (options.editable) {
console.warn('Option "editable" has been renamed to "onEditable"'); console.warn('Option "editable" has been renamed to "onEditable"')
options.onEditable = options.editable; options.onEditable = options.editable
delete options.editable; delete options.editable
} }
// warn if onChangeJSON is used when mode can be `text` or `code` // warn if onChangeJSON is used when mode can be `text` or `code`
@ -131,7 +130,7 @@ function JSONEditor (container, options, json) {
if (options.mode === 'text' || options.mode === 'code' || if (options.mode === 'text' || options.mode === 'code' ||
(options.modes && (options.modes.indexOf('text') !== -1 || options.modes.indexOf('code') !== -1))) { (options.modes && (options.modes.indexOf('text') !== -1 || options.modes.indexOf('code') !== -1))) {
console.warn('Option "onChangeJSON" is not applicable to modes "text" and "code". ' + console.warn('Option "onChangeJSON" is not applicable to modes "text" and "code". ' +
'Use "onChangeText" or "onChange" instead.'); 'Use "onChangeText" or "onChange" instead.')
} }
} }
@ -139,14 +138,14 @@ function JSONEditor (container, options, json) {
if (options) { if (options) {
Object.keys(options).forEach(function (option) { Object.keys(options).forEach(function (option) {
if (JSONEditor.VALID_OPTIONS.indexOf(option) === -1) { if (JSONEditor.VALID_OPTIONS.indexOf(option) === -1) {
console.warn('Unknown option "' + option + '". This option will be ignored'); console.warn('Unknown option "' + option + '". This option will be ignored')
} }
}); })
} }
} }
if (arguments.length) { if (arguments.length) {
this._create(container, options, json); this._create(container, options, json)
} }
} }
@ -165,13 +164,13 @@ function JSONEditor (container, options, json) {
* *
* @type { Object.<String, {mixin: Object, data: String} > } * @type { Object.<String, {mixin: Object, data: String} > }
*/ */
JSONEditor.modes = {}; JSONEditor.modes = {}
// debounce interval for JSON schema vaidation in milliseconds // debounce interval for JSON schema vaidation in milliseconds
JSONEditor.prototype.DEBOUNCE_INTERVAL = 150; JSONEditor.prototype.DEBOUNCE_INTERVAL = 150
JSONEditor.VALID_OPTIONS = [ JSONEditor.VALID_OPTIONS = [
'ajv', 'schema', 'schemaRefs','templates', 'ajv', 'schema', 'schemaRefs', 'templates',
'ace', 'theme', 'autocomplete', 'ace', 'theme', 'autocomplete',
'onChange', 'onChangeJSON', 'onChangeText', 'onChange', 'onChangeJSON', 'onChangeText',
'onEditable', 'onError', 'onEvent', 'onModeChange', 'onNodeName', 'onValidate', 'onCreateMenu', 'onEditable', 'onError', 'onEvent', 'onModeChange', 'onNodeName', 'onValidate', 'onCreateMenu',
@ -181,7 +180,7 @@ JSONEditor.VALID_OPTIONS = [
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation', 'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation',
'sortObjectKeys', 'navigationBar', 'statusBar', 'mainMenuBar', 'languages', 'language', 'enableSort', 'enableTransform', 'sortObjectKeys', 'navigationBar', 'statusBar', 'mainMenuBar', 'languages', 'language', 'enableSort', 'enableTransform',
'maxVisibleChilds' 'maxVisibleChilds'
]; ]
/** /**
* Create the JSONEditor * Create the JSONEditor
@ -191,50 +190,50 @@ JSONEditor.VALID_OPTIONS = [
* @private * @private
*/ */
JSONEditor.prototype._create = function (container, options, json) { JSONEditor.prototype._create = function (container, options, json) {
this.container = container; this.container = container
this.options = options || {}; this.options = options || {}
this.json = json || {}; this.json = json || {}
var mode = this.options.mode || (this.options.modes && this.options.modes[0]) || 'tree'; var mode = this.options.mode || (this.options.modes && this.options.modes[0]) || 'tree'
this.setMode(mode); this.setMode(mode)
}; }
/** /**
* Destroy the editor. Clean up DOM, event listeners, and web workers. * Destroy the editor. Clean up DOM, event listeners, and web workers.
*/ */
JSONEditor.prototype.destroy = function () {}; JSONEditor.prototype.destroy = function () {}
/** /**
* Set JSON object in editor * Set JSON object in editor
* @param {Object | undefined} json JSON data * @param {Object | undefined} json JSON data
*/ */
JSONEditor.prototype.set = function (json) { JSONEditor.prototype.set = function (json) {
this.json = json; this.json = json
}; }
/** /**
* Get JSON from the editor * Get JSON from the editor
* @returns {Object} json * @returns {Object} json
*/ */
JSONEditor.prototype.get = function () { JSONEditor.prototype.get = function () {
return this.json; return this.json
}; }
/** /**
* Set string containing JSON for the editor * Set string containing JSON for the editor
* @param {String | undefined} jsonText * @param {String | undefined} jsonText
*/ */
JSONEditor.prototype.setText = function (jsonText) { JSONEditor.prototype.setText = function (jsonText) {
this.json = util.parse(jsonText); this.json = util.parse(jsonText)
}; }
/** /**
* Get stringified JSON contents from the editor * Get stringified JSON contents from the editor
* @returns {String} jsonText * @returns {String} jsonText
*/ */
JSONEditor.prototype.getText = function () { JSONEditor.prototype.getText = function () {
return JSON.stringify(this.json); return JSON.stringify(this.json)
}; }
/** /**
* Set a field name for the root node. * Set a field name for the root node.
@ -242,18 +241,18 @@ JSONEditor.prototype.getText = function () {
*/ */
JSONEditor.prototype.setName = function (name) { JSONEditor.prototype.setName = function (name) {
if (!this.options) { if (!this.options) {
this.options = {}; this.options = {}
} }
this.options.name = name; this.options.name = name
}; }
/** /**
* Get the field name for the root node. * Get the field name for the root node.
* @return {String | undefined} name * @return {String | undefined} name
*/ */
JSONEditor.prototype.getName = function () { JSONEditor.prototype.getName = function () {
return this.options && this.options.name; return this.options && this.options.name
}; }
/** /**
* Change the mode of the editor. * Change the mode of the editor.
@ -264,65 +263,61 @@ JSONEditor.prototype.getName = function () {
JSONEditor.prototype.setMode = function (mode) { JSONEditor.prototype.setMode = function (mode) {
// if the mode is the same as current mode (and it's not the first time), do nothing. // if the mode is the same as current mode (and it's not the first time), do nothing.
if (mode === this.options.mode && this.create) { if (mode === this.options.mode && this.create) {
return; return
} }
var container = this.container; var container = this.container
var options = util.extend({}, this.options); var options = util.extend({}, this.options)
var oldMode = options.mode; var oldMode = options.mode
var data; var data
var name; var name
options.mode = mode; options.mode = mode
var config = JSONEditor.modes[mode]; var config = JSONEditor.modes[mode]
if (config) { if (config) {
try { try {
var asText = (config.data == 'text'); var asText = (config.data === 'text')
name = this.getName(); name = this.getName()
data = this[asText ? 'getText' : 'get'](); // get text or json data = this[asText ? 'getText' : 'get']() // get text or json
this.destroy(); this.destroy()
util.clear(this); util.clear(this)
util.extend(this, config.mixin); util.extend(this, config.mixin)
this.create(container, options); this.create(container, options)
this.setName(name); this.setName(name)
this[asText ? 'setText' : 'set'](data); // set text or json this[asText ? 'setText' : 'set'](data) // set text or json
if (typeof config.load === 'function') { if (typeof config.load === 'function') {
try { try {
config.load.call(this); config.load.call(this)
} } catch (err) {
catch (err) { console.error(err)
console.error(err);
} }
} }
if (typeof options.onModeChange === 'function' && mode !== oldMode) { if (typeof options.onModeChange === 'function' && mode !== oldMode) {
try { try {
options.onModeChange(mode, oldMode); options.onModeChange(mode, oldMode)
} } catch (err) {
catch (err) { console.error(err)
console.error(err);
} }
} }
} catch (err) {
this._onError(err)
} }
catch (err) { } else {
this._onError(err); throw new Error('Unknown mode "' + options.mode + '"')
}
} }
else { }
throw new Error('Unknown mode "' + options.mode + '"');
}
};
/** /**
* Get the current mode * Get the current mode
* @return {string} * @return {string}
*/ */
JSONEditor.prototype.getMode = function () { JSONEditor.prototype.getMode = function () {
return this.options.mode; return this.options.mode
}; }
/** /**
* Throw an error. If an error callback is configured in options.error, this * Throw an error. If an error callback is configured in options.error, this
@ -330,14 +325,13 @@ JSONEditor.prototype.getMode = function () {
* @param {Error} err * @param {Error} err
* @private * @private
*/ */
JSONEditor.prototype._onError = function(err) { JSONEditor.prototype._onError = function (err) {
if (this.options && typeof this.options.onError === 'function') { if (this.options && typeof this.options.onError === 'function') {
this.options.onError(err); this.options.onError(err)
} else {
throw err
} }
else { }
throw err;
}
};
/** /**
* Set a JSON schema for validation of the JSON object. * Set a JSON schema for validation of the JSON object.
@ -349,60 +343,57 @@ JSONEditor.prototype._onError = function(err) {
JSONEditor.prototype.setSchema = function (schema, schemaRefs) { JSONEditor.prototype.setSchema = function (schema, schemaRefs) {
// compile a JSON schema validator if a JSON schema is provided // compile a JSON schema validator if a JSON schema is provided
if (schema) { if (schema) {
var ajv; var ajv
try { try {
// grab ajv from options if provided, else create a new instance // grab ajv from options if provided, else create a new instance
if (this.options.ajv) { if (this.options.ajv) {
ajv = this.options.ajv ajv = this.options.ajv
} } else {
else {
ajv = Ajv({ ajv = Ajv({
allErrors: true, allErrors: true,
verbose: true, verbose: true,
schemaId: 'auto', schemaId: 'auto',
$data: true $data: true
}); })
// support both draft-04 and draft-06 alongside the latest draft-07 // support both draft-04 and draft-06 alongside the latest draft-07
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json')); ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'))
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json')); ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'))
} }
} } catch (err) {
catch (err) { console.warn('Failed to create an instance of Ajv, JSON Schema validation is not available. Please use a JSONEditor bundle including Ajv, or pass an instance of Ajv as via the configuration option `ajv`.')
console.warn('Failed to create an instance of Ajv, JSON Schema validation is not available. Please use a JSONEditor bundle including Ajv, or pass an instance of Ajv as via the configuration option `ajv`.');
} }
if (ajv) { if (ajv) {
if(schemaRefs) { if (schemaRefs) {
for (var ref in schemaRefs) { for (var ref in schemaRefs) {
ajv.removeSchema(ref); // When updating a schema - old refs has to be removed first ajv.removeSchema(ref) // When updating a schema - old refs has to be removed first
if(schemaRefs[ref]) { if (schemaRefs[ref]) {
ajv.addSchema(schemaRefs[ref], ref); ajv.addSchema(schemaRefs[ref], ref)
} }
} }
this.options.schemaRefs = schemaRefs; this.options.schemaRefs = schemaRefs
} }
this.validateSchema = ajv.compile(schema); this.validateSchema = ajv.compile(schema)
// add schema to the options, so that when switching to an other mode, // add schema to the options, so that when switching to an other mode,
// the set schema is not lost // the set schema is not lost
this.options.schema = schema; this.options.schema = schema
// validate now // validate now
this.validate(); this.validate()
} }
this.refresh(); // update DOM this.refresh() // update DOM
} } else {
else {
// remove current schema // remove current schema
this.validateSchema = null; this.validateSchema = null
this.options.schema = null; this.options.schema = null
this.options.schemaRefs = null; this.options.schemaRefs = null
this.validate(); // to clear current error messages this.validate() // to clear current error messages
this.refresh(); // update DOM this.refresh() // update DOM
} }
}; }
/** /**
* Validate current JSON object against the configured JSON schema * Validate current JSON object against the configured JSON schema
@ -410,14 +401,14 @@ JSONEditor.prototype.setSchema = function (schema, schemaRefs) {
*/ */
JSONEditor.prototype.validate = function () { JSONEditor.prototype.validate = function () {
// must be implemented by treemode and textmode // must be implemented by treemode and textmode
}; }
/** /**
* Refresh the rendered contents * Refresh the rendered contents
*/ */
JSONEditor.prototype.refresh = function () { JSONEditor.prototype.refresh = function () {
// can be implemented by treemode and textmode // can be implemented by treemode and textmode
}; }
/** /**
* Register a plugin with one ore multiple modes for the JSON Editor. * Register a plugin with one ore multiple modes for the JSON Editor.
@ -439,51 +430,50 @@ JSONEditor.prototype.refresh = function () {
* @param {Object | Array} mode A mode object or an array with multiple mode objects. * @param {Object | Array} mode A mode object or an array with multiple mode objects.
*/ */
JSONEditor.registerMode = function (mode) { JSONEditor.registerMode = function (mode) {
var i, prop; var i, prop
if (util.isArray(mode)) { if (util.isArray(mode)) {
// multiple modes // multiple modes
for (i = 0; i < mode.length; i++) { for (i = 0; i < mode.length; i++) {
JSONEditor.registerMode(mode[i]); JSONEditor.registerMode(mode[i])
} }
} } else {
else {
// validate the new mode // validate the new mode
if (!('mode' in mode)) throw new Error('Property "mode" missing'); if (!('mode' in mode)) throw new Error('Property "mode" missing')
if (!('mixin' in mode)) throw new Error('Property "mixin" missing'); if (!('mixin' in mode)) throw new Error('Property "mixin" missing')
if (!('data' in mode)) throw new Error('Property "data" missing'); if (!('data' in mode)) throw new Error('Property "data" missing')
var name = mode.mode; var name = mode.mode
if (name in JSONEditor.modes) { if (name in JSONEditor.modes) {
throw new Error('Mode "' + name + '" already registered'); throw new Error('Mode "' + name + '" already registered')
} }
// validate the mixin // validate the mixin
if (typeof mode.mixin.create !== 'function') { if (typeof mode.mixin.create !== 'function') {
throw new Error('Required function "create" missing on mixin'); throw new Error('Required function "create" missing on mixin')
} }
var reserved = ['setMode', 'registerMode', 'modes']; var reserved = ['setMode', 'registerMode', 'modes']
for (i = 0; i < reserved.length; i++) { for (i = 0; i < reserved.length; i++) {
prop = reserved[i]; prop = reserved[i]
if (prop in mode.mixin) { if (prop in mode.mixin) {
throw new Error('Reserved property "' + prop + '" not allowed in mixin'); throw new Error('Reserved property "' + prop + '" not allowed in mixin')
} }
} }
JSONEditor.modes[name] = mode; JSONEditor.modes[name] = mode
} }
}; }
// register tree, text, and preview modes // register tree, text, and preview modes
JSONEditor.registerMode(treemode); JSONEditor.registerMode(treemode)
JSONEditor.registerMode(textmode); JSONEditor.registerMode(textmode)
JSONEditor.registerMode(previewmode); JSONEditor.registerMode(previewmode)
// expose some of the libraries that can be used customized // expose some of the libraries that can be used customized
JSONEditor.ace = ace; JSONEditor.ace = ace
JSONEditor.Ajv = Ajv; JSONEditor.Ajv = Ajv
JSONEditor.VanillaPicker = VanillaPicker; JSONEditor.VanillaPicker = VanillaPicker
// default export for TypeScript ES6 projects // default export for TypeScript ES6 projects
JSONEditor.default = JSONEditor; JSONEditor.default = JSONEditor
module.exports = JSONEditor; module.exports = JSONEditor

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict'
var ContextMenu = require('./ContextMenu'); var ContextMenu = require('./ContextMenu')
var translate = require('./i18n').translate; var translate = require('./i18n').translate
/** /**
* Create a select box to be used in the editor menu's, which allows to switch mode * Create a select box to be used in the editor menu's, which allows to switch mode
@ -11,113 +11,113 @@ var translate = require('./i18n').translate;
* @param {function(mode: string)} onSwitch Callback invoked on switch * @param {function(mode: string)} onSwitch Callback invoked on switch
* @constructor * @constructor
*/ */
function ModeSwitcher(container, modes, current, onSwitch) { function ModeSwitcher (container, modes, current, onSwitch) {
// available modes // available modes
var availableModes = { var availableModes = {
code: { code: {
'text': translate('modeCodeText'), text: translate('modeCodeText'),
'title': translate('modeCodeTitle'), title: translate('modeCodeTitle'),
'click': function () { click: function () {
onSwitch('code') onSwitch('code')
} }
}, },
form: { form: {
'text': translate('modeFormText'), text: translate('modeFormText'),
'title': translate('modeFormTitle'), title: translate('modeFormTitle'),
'click': function () { click: function () {
onSwitch('form'); onSwitch('form')
} }
}, },
text: { text: {
'text': translate('modeTextText'), text: translate('modeTextText'),
'title': translate('modeTextTitle'), title: translate('modeTextTitle'),
'click': function () { click: function () {
onSwitch('text'); onSwitch('text')
} }
}, },
tree: { tree: {
'text': translate('modeTreeText'), text: translate('modeTreeText'),
'title': translate('modeTreeTitle'), title: translate('modeTreeTitle'),
'click': function () { click: function () {
onSwitch('tree'); onSwitch('tree')
} }
}, },
view: { view: {
'text': translate('modeViewText'), text: translate('modeViewText'),
'title': translate('modeViewTitle'), title: translate('modeViewTitle'),
'click': function () { click: function () {
onSwitch('view'); onSwitch('view')
} }
}, },
preview: { preview: {
'text': translate('modePreviewText'), text: translate('modePreviewText'),
'title': translate('modePreviewTitle'), title: translate('modePreviewTitle'),
'click': function () { click: function () {
onSwitch('preview'); onSwitch('preview')
} }
} }
}; }
// list the selected modes // list the selected modes
var items = []; var items = []
for (var i = 0; i < modes.length; i++) { for (var i = 0; i < modes.length; i++) {
var mode = modes[i]; var mode = modes[i]
var item = availableModes[mode]; var item = availableModes[mode]
if (!item) { if (!item) {
throw new Error('Unknown mode "' + mode + '"'); throw new Error('Unknown mode "' + mode + '"')
} }
item.className = 'jsoneditor-type-modes' + ((current == mode) ? ' jsoneditor-selected' : ''); item.className = 'jsoneditor-type-modes' + ((current === mode) ? ' jsoneditor-selected' : '')
items.push(item); items.push(item)
} }
// retrieve the title of current mode // retrieve the title of current mode
var currentMode = availableModes[current]; var currentMode = availableModes[current]
if (!currentMode) { if (!currentMode) {
throw new Error('Unknown mode "' + current + '"'); throw new Error('Unknown mode "' + current + '"')
} }
var currentTitle = currentMode.text; var currentTitle = currentMode.text
// create the html element // create the html element
var box = document.createElement('button'); var box = document.createElement('button')
box.type = 'button'; box.type = 'button'
box.className = 'jsoneditor-modes jsoneditor-separator'; box.className = 'jsoneditor-modes jsoneditor-separator'
box.innerHTML = currentTitle + ' &#x25BE;'; box.innerHTML = currentTitle + ' &#x25BE;'
box.title = 'Switch editor mode'; box.title = 'Switch editor mode'
box.onclick = function () { box.onclick = function () {
var menu = new ContextMenu(items); var menu = new ContextMenu(items)
menu.show(box, container); menu.show(box, container)
}; }
var frame = document.createElement('div'); var frame = document.createElement('div')
frame.className = 'jsoneditor-modes'; frame.className = 'jsoneditor-modes'
frame.style.position = 'relative'; frame.style.position = 'relative'
frame.appendChild(box); frame.appendChild(box)
container.appendChild(frame); container.appendChild(frame)
this.dom = { this.dom = {
container: container, container: container,
box: box, box: box,
frame: frame frame: frame
}; }
} }
/** /**
* Set focus to switcher * Set focus to switcher
*/ */
ModeSwitcher.prototype.focus = function () { ModeSwitcher.prototype.focus = function () {
this.dom.box.focus(); this.dom.box.focus()
}; }
/** /**
* Destroy the ModeSwitcher, remove from DOM * Destroy the ModeSwitcher, remove from DOM
*/ */
ModeSwitcher.prototype.destroy = function () { ModeSwitcher.prototype.destroy = function () {
if (this.dom && this.dom.frame && this.dom.frame.parentNode) { if (this.dom && this.dom.frame && this.dom.frame.parentNode) {
this.dom.frame.parentNode.removeChild(this.dom.frame); this.dom.frame.parentNode.removeChild(this.dom.frame)
} }
this.dom = null; this.dom = null
}; }
module.exports = ModeSwitcher; module.exports = ModeSwitcher

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict'
var util = require('./util'); var util = require('./util')
/** /**
* @constructor History * @constructor History
@ -8,190 +8,190 @@ var util = require('./util');
* @param {JSONEditor} editor * @param {JSONEditor} editor
*/ */
function NodeHistory (editor) { function NodeHistory (editor) {
this.editor = editor; this.editor = editor
this.history = []; this.history = []
this.index = -1; this.index = -1
this.clear(); this.clear()
// helper function to find a Node from a path // helper function to find a Node from a path
function findNode(path) { function findNode (path) {
return editor.node.findNodeByInternalPath(path) return editor.node.findNodeByInternalPath(path)
} }
// map with all supported actions // map with all supported actions
this.actions = { this.actions = {
'editField': { editField: {
'undo': function (params) { undo: function (params) {
var parentNode = findNode(params.parentPath); var parentNode = findNode(params.parentPath)
var node = parentNode.childs[params.index]; var node = parentNode.childs[params.index]
node.updateField(params.oldValue); node.updateField(params.oldValue)
}, },
'redo': function (params) { redo: function (params) {
var parentNode = findNode(params.parentPath); var parentNode = findNode(params.parentPath)
var node = parentNode.childs[params.index]; var node = parentNode.childs[params.index]
node.updateField(params.newValue); node.updateField(params.newValue)
} }
}, },
'editValue': { editValue: {
'undo': function (params) { undo: function (params) {
findNode(params.path).updateValue(params.oldValue); findNode(params.path).updateValue(params.oldValue)
}, },
'redo': function (params) { redo: function (params) {
findNode(params.path).updateValue(params.newValue); findNode(params.path).updateValue(params.newValue)
} }
}, },
'changeType': { changeType: {
'undo': function (params) { undo: function (params) {
findNode(params.path).changeType(params.oldType); findNode(params.path).changeType(params.oldType)
}, },
'redo': function (params) { redo: function (params) {
findNode(params.path).changeType(params.newType); findNode(params.path).changeType(params.newType)
} }
}, },
'appendNodes': { appendNodes: {
'undo': function (params) { undo: function (params) {
var parentNode = findNode(params.parentPath); var parentNode = findNode(params.parentPath)
params.paths.map(findNode).forEach(function (node) { params.paths.map(findNode).forEach(function (node) {
parentNode.removeChild(node); parentNode.removeChild(node)
}); })
},
'redo': function (params) {
var parentNode = findNode(params.parentPath);
params.nodes.forEach(function (node) {
parentNode.appendChild(node);
});
}
},
'insertBeforeNodes': {
'undo': function (params) {
var parentNode = findNode(params.parentPath);
params.paths.map(findNode).forEach(function (node) {
parentNode.removeChild(node);
});
}, },
'redo': function (params) { redo: function (params) {
var parentNode = findNode(params.parentPath); var parentNode = findNode(params.parentPath)
var beforeNode = findNode(params.beforePath);
params.nodes.forEach(function (node) { params.nodes.forEach(function (node) {
parentNode.insertBefore(node, beforeNode); parentNode.appendChild(node)
}); })
} }
}, },
'insertAfterNodes': { insertBeforeNodes: {
'undo': function (params) { undo: function (params) {
var parentNode = findNode(params.parentPath); var parentNode = findNode(params.parentPath)
params.paths.map(findNode).forEach(function (node) { params.paths.map(findNode).forEach(function (node) {
parentNode.removeChild(node); parentNode.removeChild(node)
}); })
}, },
'redo': function (params) { redo: function (params) {
var parentNode = findNode(params.parentPath); var parentNode = findNode(params.parentPath)
var afterNode = findNode(params.afterPath); var beforeNode = findNode(params.beforePath)
params.nodes.forEach(function (node) { params.nodes.forEach(function (node) {
parentNode.insertAfter(node, afterNode); parentNode.insertBefore(node, beforeNode)
afterNode = node; })
});
} }
}, },
'removeNodes': { insertAfterNodes: {
'undo': function (params) { undo: function (params) {
var parentNode = findNode(params.parentPath); var parentNode = findNode(params.parentPath)
var beforeNode = parentNode.childs[params.index] || parentNode.append;
params.nodes.forEach(function (node) {
parentNode.insertBefore(node, beforeNode);
});
},
'redo': function (params) {
var parentNode = findNode(params.parentPath);
params.paths.map(findNode).forEach(function (node) { params.paths.map(findNode).forEach(function (node) {
parentNode.removeChild(node); parentNode.removeChild(node)
}); })
},
redo: function (params) {
var parentNode = findNode(params.parentPath)
var afterNode = findNode(params.afterPath)
params.nodes.forEach(function (node) {
parentNode.insertAfter(node, afterNode)
afterNode = node
})
} }
}, },
'duplicateNodes': { removeNodes: {
'undo': function (params) { undo: function (params) {
var parentNode = findNode(params.parentPath); var parentNode = findNode(params.parentPath)
var beforeNode = parentNode.childs[params.index] || parentNode.append
params.nodes.forEach(function (node) {
parentNode.insertBefore(node, beforeNode)
})
},
redo: function (params) {
var parentNode = findNode(params.parentPath)
params.paths.map(findNode).forEach(function (node) {
parentNode.removeChild(node)
})
}
},
duplicateNodes: {
undo: function (params) {
var parentNode = findNode(params.parentPath)
params.clonePaths.map(findNode).forEach(function (node) { params.clonePaths.map(findNode).forEach(function (node) {
parentNode.removeChild(node); parentNode.removeChild(node)
}); })
}, },
'redo': function (params) { redo: function (params) {
var parentNode = findNode(params.parentPath); var parentNode = findNode(params.parentPath)
var afterNode = findNode(params.afterPath); var afterNode = findNode(params.afterPath)
var nodes = params.paths.map(findNode); var nodes = params.paths.map(findNode)
nodes.forEach(function (node) { nodes.forEach(function (node) {
var clone = node.clone(); var clone = node.clone()
if (parentNode.type === 'object') { if (parentNode.type === 'object') {
var existingFieldNames = parentNode.getFieldNames(); var existingFieldNames = parentNode.getFieldNames()
clone.field = util.findUniqueName(node.field, existingFieldNames); clone.field = util.findUniqueName(node.field, existingFieldNames)
} }
parentNode.insertAfter(clone, afterNode); parentNode.insertAfter(clone, afterNode)
afterNode = clone; afterNode = clone
}); })
} }
}, },
'moveNodes': { moveNodes: {
'undo': function (params) { undo: function (params) {
var oldParentNode = findNode(params.oldParentPath); var oldParentNode = findNode(params.oldParentPath)
var newParentNode = findNode(params.newParentPath); var newParentNode = findNode(params.newParentPath)
var oldBeforeNode = oldParentNode.childs[params.oldIndex] || oldParentNode.append; var oldBeforeNode = oldParentNode.childs[params.oldIndex] || oldParentNode.append
// first copy the nodes, then move them // first copy the nodes, then move them
var nodes = newParentNode.childs.slice(params.newIndex, params.newIndex + params.count); var nodes = newParentNode.childs.slice(params.newIndex, params.newIndex + params.count)
nodes.forEach(function (node, index) { nodes.forEach(function (node, index) {
node.field = params.fieldNames[index]; node.field = params.fieldNames[index]
oldParentNode.moveBefore(node, oldBeforeNode); oldParentNode.moveBefore(node, oldBeforeNode)
}); })
// This is a hack to work around an issue that we don't know tha original // This is a hack to work around an issue that we don't know tha original
// path of the new parent after dragging, as the node is already moved at that time. // path of the new parent after dragging, as the node is already moved at that time.
if (params.newParentPathRedo === null) { if (params.newParentPathRedo === null) {
params.newParentPathRedo = newParentNode.getInternalPath(); params.newParentPathRedo = newParentNode.getInternalPath()
} }
}, },
'redo': function (params) { redo: function (params) {
var oldParentNode = findNode(params.oldParentPathRedo); var oldParentNode = findNode(params.oldParentPathRedo)
var newParentNode = findNode(params.newParentPathRedo); var newParentNode = findNode(params.newParentPathRedo)
var newBeforeNode = newParentNode.childs[params.newIndexRedo] || newParentNode.append; var newBeforeNode = newParentNode.childs[params.newIndexRedo] || newParentNode.append
// first copy the nodes, then move them // first copy the nodes, then move them
var nodes = oldParentNode.childs.slice(params.oldIndexRedo, params.oldIndexRedo + params.count); var nodes = oldParentNode.childs.slice(params.oldIndexRedo, params.oldIndexRedo + params.count)
nodes.forEach(function (node, index) { nodes.forEach(function (node, index) {
node.field = params.fieldNames[index]; node.field = params.fieldNames[index]
newParentNode.moveBefore(node, newBeforeNode); newParentNode.moveBefore(node, newBeforeNode)
}); })
} }
}, },
'sort': { sort: {
'undo': function (params) { undo: function (params) {
var node = findNode(params.path); var node = findNode(params.path)
node.hideChilds(); node.hideChilds()
node.childs = params.oldChilds; node.childs = params.oldChilds
node.updateDom({updateIndexes: true}); node.updateDom({ updateIndexes: true })
node.showChilds(); node.showChilds()
}, },
'redo': function (params) { redo: function (params) {
var node = findNode(params.path); var node = findNode(params.path)
node.hideChilds(); node.hideChilds()
node.childs = params.newChilds; node.childs = params.newChilds
node.updateDom({updateIndexes: true}); node.updateDom({ updateIndexes: true })
node.showChilds(); node.showChilds()
} }
}, },
'transform': { transform: {
'undo': function (params) { undo: function (params) {
findNode(params.path).setInternalValue(params.oldValue); findNode(params.path).setInternalValue(params.oldValue)
// TODO: would be nice to restore the state of the node and childs // TODO: would be nice to restore the state of the node and childs
}, },
'redo': function (params) { redo: function (params) {
findNode(params.path).setInternalValue(params.newValue); findNode(params.path).setInternalValue(params.newValue)
// TODO: would be nice to restore the state of the node and childs // TODO: would be nice to restore the state of the node and childs
} }
@ -199,14 +199,14 @@ function NodeHistory (editor) {
// TODO: restore the original caret position and selection with each undo // TODO: restore the original caret position and selection with each undo
// TODO: implement history for actions "expand", "collapse", "scroll", "setDocument" // TODO: implement history for actions "expand", "collapse", "scroll", "setDocument"
}; }
} }
/** /**
* The method onChange is executed when the History is changed, and can * The method onChange is executed when the History is changed, and can
* be overloaded. * be overloaded.
*/ */
NodeHistory.prototype.onChange = function () {}; NodeHistory.prototype.onChange = function () {}
/** /**
* Add a new action to the history * Add a new action to the history
@ -220,118 +220,114 @@ NodeHistory.prototype.onChange = function () {};
* needed to undo or redo the action. * needed to undo or redo the action.
*/ */
NodeHistory.prototype.add = function (action, params) { NodeHistory.prototype.add = function (action, params) {
this.index++; this.index++
this.history[this.index] = { this.history[this.index] = {
'action': action, action: action,
'params': params, params: params,
'timestamp': new Date() timestamp: new Date()
}; }
// remove redo actions which are invalid now // remove redo actions which are invalid now
if (this.index < this.history.length - 1) { if (this.index < this.history.length - 1) {
this.history.splice(this.index + 1, this.history.length - this.index - 1); this.history.splice(this.index + 1, this.history.length - this.index - 1)
} }
// fire onchange event // fire onchange event
this.onChange(); this.onChange()
}; }
/** /**
* Clear history * Clear history
*/ */
NodeHistory.prototype.clear = function () { NodeHistory.prototype.clear = function () {
this.history = []; this.history = []
this.index = -1; this.index = -1
// fire onchange event // fire onchange event
this.onChange(); this.onChange()
}; }
/** /**
* Check if there is an action available for undo * Check if there is an action available for undo
* @return {Boolean} canUndo * @return {Boolean} canUndo
*/ */
NodeHistory.prototype.canUndo = function () { NodeHistory.prototype.canUndo = function () {
return (this.index >= 0); return (this.index >= 0)
}; }
/** /**
* Check if there is an action available for redo * Check if there is an action available for redo
* @return {Boolean} canRedo * @return {Boolean} canRedo
*/ */
NodeHistory.prototype.canRedo = function () { NodeHistory.prototype.canRedo = function () {
return (this.index < this.history.length - 1); return (this.index < this.history.length - 1)
}; }
/** /**
* Undo the last action * Undo the last action
*/ */
NodeHistory.prototype.undo = function () { NodeHistory.prototype.undo = function () {
if (this.canUndo()) { if (this.canUndo()) {
var obj = this.history[this.index]; var obj = this.history[this.index]
if (obj) { if (obj) {
var action = this.actions[obj.action]; var action = this.actions[obj.action]
if (action && action.undo) { if (action && action.undo) {
action.undo(obj.params); action.undo(obj.params)
if (obj.params.oldSelection) { if (obj.params.oldSelection) {
try { try {
this.editor.setDomSelection(obj.params.oldSelection); this.editor.setDomSelection(obj.params.oldSelection)
} } catch (err) {
catch (err) { console.error(err)
console.error(err);
} }
} }
} } else {
else { console.error(new Error('unknown action "' + obj.action + '"'))
console.error(new Error('unknown action "' + obj.action + '"'));
} }
} }
this.index--; this.index--
// fire onchange event // fire onchange event
this.onChange(); this.onChange()
} }
}; }
/** /**
* Redo the last action * Redo the last action
*/ */
NodeHistory.prototype.redo = function () { NodeHistory.prototype.redo = function () {
if (this.canRedo()) { if (this.canRedo()) {
this.index++; this.index++
var obj = this.history[this.index]; var obj = this.history[this.index]
if (obj) { if (obj) {
var action = this.actions[obj.action]; var action = this.actions[obj.action]
if (action && action.redo) { if (action && action.redo) {
action.redo(obj.params); action.redo(obj.params)
if (obj.params.newSelection) { if (obj.params.newSelection) {
try { try {
this.editor.setDomSelection(obj.params.newSelection); this.editor.setDomSelection(obj.params.newSelection)
} } catch (err) {
catch (err) { console.error(err)
console.error(err);
} }
} }
} } else {
else { console.error(new Error('unknown action "' + obj.action + '"'))
console.error(new Error('unknown action "' + obj.action + '"'));
} }
} }
// fire onchange event // fire onchange event
this.onChange(); this.onChange()
} }
}; }
/** /**
* Destroy history * Destroy history
*/ */
NodeHistory.prototype.destroy = function () { NodeHistory.prototype.destroy = function () {
this.editor = null; this.editor = null
this.history = []; this.history = []
this.index = -1; this.index = -1
}; }
module.exports = NodeHistory; module.exports = NodeHistory

View File

@ -1,4 +1,4 @@
"use strict"; 'use strict'
/** /**
* @constructor SearchBox * @constructor SearchBox
@ -7,80 +7,80 @@
* @param {Element} container HTML container element of where to * @param {Element} container HTML container element of where to
* create the search box * create the search box
*/ */
function SearchBox(editor, container) { function SearchBox (editor, container) {
var searchBox = this; var searchBox = this
this.editor = editor; this.editor = editor
this.timeout = undefined; this.timeout = undefined
this.delay = 200; // ms this.delay = 200 // ms
this.lastText = undefined; this.lastText = undefined
this.dom = {}; this.dom = {}
this.dom.container = container; this.dom.container = container
var wrapper = document.createElement("div"); var wrapper = document.createElement('div')
this.dom.wrapper = wrapper; this.dom.wrapper = wrapper
wrapper.className = "jsoneditor-search"; wrapper.className = 'jsoneditor-search'
container.appendChild(wrapper); container.appendChild(wrapper)
var results = document.createElement("div"); var results = document.createElement('div')
this.dom.results = results; this.dom.results = results
results.className = "jsoneditor-results"; results.className = 'jsoneditor-results'
wrapper.appendChild(results); wrapper.appendChild(results)
var divInput = document.createElement("div"); var divInput = document.createElement('div')
this.dom.input = divInput; this.dom.input = divInput
divInput.className = "jsoneditor-frame"; divInput.className = 'jsoneditor-frame'
divInput.title = "Search fields and values"; divInput.title = 'Search fields and values'
wrapper.appendChild(divInput); wrapper.appendChild(divInput)
var refreshSearch = document.createElement("button"); var refreshSearch = document.createElement('button')
refreshSearch.type = "button"; refreshSearch.type = 'button'
refreshSearch.className = "jsoneditor-refresh"; refreshSearch.className = 'jsoneditor-refresh'
divInput.appendChild(refreshSearch); divInput.appendChild(refreshSearch)
var search = document.createElement("input"); var search = document.createElement('input')
search.type = "text"; search.type = 'text'
this.dom.search = search; this.dom.search = search
search.oninput = function(event) { search.oninput = function (event) {
searchBox._onDelayedSearch(event); searchBox._onDelayedSearch(event)
}; }
search.onchange = function(event) { search.onchange = function (event) {
// For IE 9 // For IE 9
searchBox._onSearch(); searchBox._onSearch()
}; }
search.onkeydown = function(event) { search.onkeydown = function (event) {
searchBox._onKeyDown(event); searchBox._onKeyDown(event)
}; }
search.onkeyup = function(event) { search.onkeyup = function (event) {
searchBox._onKeyUp(event); searchBox._onKeyUp(event)
}; }
refreshSearch.onclick = function(event) { refreshSearch.onclick = function (event) {
search.select(); search.select()
}; }
// TODO: ESC in FF restores the last input, is a FF bug, https://bugzilla.mozilla.org/show_bug.cgi?id=598819 // TODO: ESC in FF restores the last input, is a FF bug, https://bugzilla.mozilla.org/show_bug.cgi?id=598819
divInput.appendChild(search); divInput.appendChild(search)
var searchNext = document.createElement("button"); var searchNext = document.createElement('button')
searchNext.type = "button"; searchNext.type = 'button'
searchNext.title = "Next result (Enter)"; searchNext.title = 'Next result (Enter)'
searchNext.className = "jsoneditor-next"; searchNext.className = 'jsoneditor-next'
searchNext.onclick = function() { searchNext.onclick = function () {
searchBox.next(); searchBox.next()
}; }
divInput.appendChild(searchNext); divInput.appendChild(searchNext)
var searchPrevious = document.createElement("button"); var searchPrevious = document.createElement('button')
searchPrevious.type = "button"; searchPrevious.type = 'button'
searchPrevious.title = "Previous result (Shift+Enter)"; searchPrevious.title = 'Previous result (Shift+Enter)'
searchPrevious.className = "jsoneditor-previous"; searchPrevious.className = 'jsoneditor-previous'
searchPrevious.onclick = function() { searchPrevious.onclick = function () {
searchBox.previous(); searchBox.previous()
}; }
divInput.appendChild(searchPrevious); divInput.appendChild(searchPrevious)
} }
/** /**
@ -88,31 +88,31 @@ function SearchBox(editor, container) {
* @param {boolean} [focus] If true, focus will be set to the next result * @param {boolean} [focus] If true, focus will be set to the next result
* focus is false by default. * focus is false by default.
*/ */
SearchBox.prototype.next = function(focus) { SearchBox.prototype.next = function (focus) {
if (this.results != undefined) { if (this.results !== null) {
var index = this.resultIndex != undefined ? this.resultIndex + 1 : 0; var index = this.resultIndex !== null ? this.resultIndex + 1 : 0
if (index > this.results.length - 1) { if (index > this.results.length - 1) {
index = 0; index = 0
} }
this._setActiveResult(index, focus); this._setActiveResult(index, focus)
} }
}; }
/** /**
* Go to the prevous search result * Go to the prevous search result
* @param {boolean} [focus] If true, focus will be set to the next result * @param {boolean} [focus] If true, focus will be set to the next result
* focus is false by default. * focus is false by default.
*/ */
SearchBox.prototype.previous = function(focus) { SearchBox.prototype.previous = function (focus) {
if (this.results != undefined) { if (this.results !== null) {
var max = this.results.length - 1; var max = this.results.length - 1
var index = this.resultIndex != undefined ? this.resultIndex - 1 : max; var index = this.resultIndex !== null ? this.resultIndex - 1 : max
if (index < 0) { if (index < 0) {
index = max; index = max
} }
this._setActiveResult(index, focus); this._setActiveResult(index, focus)
} }
}; }
/** /**
* Set new value for the current active result * Set new value for the current active result
@ -121,57 +121,57 @@ SearchBox.prototype.previous = function(focus) {
* focus is false by default. * focus is false by default.
* @private * @private
*/ */
SearchBox.prototype._setActiveResult = function(index, focus) { SearchBox.prototype._setActiveResult = function (index, focus) {
// de-activate current active result // de-activate current active result
if (this.activeResult) { if (this.activeResult) {
var prevNode = this.activeResult.node; var prevNode = this.activeResult.node
var prevElem = this.activeResult.elem; var prevElem = this.activeResult.elem
if (prevElem == "field") { if (prevElem === 'field') {
delete prevNode.searchFieldActive; delete prevNode.searchFieldActive
} else { } else {
delete prevNode.searchValueActive; delete prevNode.searchValueActive
} }
prevNode.updateDom(); prevNode.updateDom()
} }
if (!this.results || !this.results[index]) { if (!this.results || !this.results[index]) {
// out of range, set to undefined // out of range, set to undefined
this.resultIndex = undefined; this.resultIndex = undefined
this.activeResult = undefined; this.activeResult = undefined
return; return
} }
this.resultIndex = index; this.resultIndex = index
// set new node active // set new node active
var node = this.results[this.resultIndex].node; var node = this.results[this.resultIndex].node
var elem = this.results[this.resultIndex].elem; var elem = this.results[this.resultIndex].elem
if (elem == "field") { if (elem === 'field') {
node.searchFieldActive = true; node.searchFieldActive = true
} else { } else {
node.searchValueActive = true; node.searchValueActive = true
} }
this.activeResult = this.results[this.resultIndex]; this.activeResult = this.results[this.resultIndex]
node.updateDom(); node.updateDom()
// TODO: not so nice that the focus is only set after the animation is finished // TODO: not so nice that the focus is only set after the animation is finished
node.scrollTo(function() { node.scrollTo(function () {
if (focus) { if (focus) {
node.focus(elem); node.focus(elem)
} }
}); })
}; }
/** /**
* Cancel any running onDelayedSearch. * Cancel any running onDelayedSearch.
* @private * @private
*/ */
SearchBox.prototype._clearDelay = function() { SearchBox.prototype._clearDelay = function () {
if (this.timeout != undefined) { if (this.timeout !== undefined) {
clearTimeout(this.timeout); clearTimeout(this.timeout)
delete this.timeout; delete this.timeout
} }
}; }
/** /**
* Start a timer to execute a search after a short delay. * Start a timer to execute a search after a short delay.
@ -179,15 +179,15 @@ SearchBox.prototype._clearDelay = function() {
* @param {Event} event * @param {Event} event
* @private * @private
*/ */
SearchBox.prototype._onDelayedSearch = function(event) { SearchBox.prototype._onDelayedSearch = function (event) {
// execute the search after a short delay (reduces the number of // execute the search after a short delay (reduces the number of
// search actions while typing in the search text box) // search actions while typing in the search text box)
this._clearDelay(); this._clearDelay()
var searchBox = this; var searchBox = this
this.timeout = setTimeout(function(event) { this.timeout = setTimeout(function (event) {
searchBox._onSearch(); searchBox._onSearch()
}, this.delay); }, this.delay)
}; }
/** /**
* Handle onSearch event * Handle onSearch event
@ -196,128 +196,128 @@ SearchBox.prototype._onDelayedSearch = function(event) {
* Default is false. * Default is false.
* @private * @private
*/ */
SearchBox.prototype._onSearch = function(forceSearch) { SearchBox.prototype._onSearch = function (forceSearch) {
this._clearDelay(); this._clearDelay()
var value = this.dom.search.value; var value = this.dom.search.value
var text = value.length > 0 ? value : undefined; var text = value.length > 0 ? value : undefined
if (text !== this.lastText || forceSearch) { if (text !== this.lastText || forceSearch) {
// only search again when changed // only search again when changed
this.lastText = text; this.lastText = text
this.results = this.editor.search(text); this.results = this.editor.search(text)
var MAX_SEARCH_RESULTS = this.results[0] var MAX_SEARCH_RESULTS = this.results[0]
? this.results[0].node.MAX_SEARCH_RESULTS ? this.results[0].node.MAX_SEARCH_RESULTS
: Infinity; : Infinity
// try to maintain the current active result if this is still part of the new search results // try to maintain the current active result if this is still part of the new search results
var activeResultIndex = 0; var activeResultIndex = 0
if (this.activeResult) { if (this.activeResult) {
for (var i = 0; i < this.results.length; i++) { for (var i = 0; i < this.results.length; i++) {
if (this.results[i].node === this.activeResult.node) { if (this.results[i].node === this.activeResult.node) {
activeResultIndex = i; activeResultIndex = i
break; break
} }
} }
} }
this._setActiveResult(activeResultIndex, false); this._setActiveResult(activeResultIndex, false)
// display search results // display search results
if (text !== undefined) { if (text !== undefined) {
var resultCount = this.results.length; var resultCount = this.results.length
if (resultCount === 0) { if (resultCount === 0) {
this.dom.results.innerHTML = "no&nbsp;results"; this.dom.results.innerHTML = 'no&nbsp;results'
} else if (resultCount === 1) { } else if (resultCount === 1) {
this.dom.results.innerHTML = "1&nbsp;result"; this.dom.results.innerHTML = '1&nbsp;result'
} else if (resultCount > MAX_SEARCH_RESULTS) { } else if (resultCount > MAX_SEARCH_RESULTS) {
this.dom.results.innerHTML = MAX_SEARCH_RESULTS + "+&nbsp;results"; this.dom.results.innerHTML = MAX_SEARCH_RESULTS + '+&nbsp;results'
} else { } else {
this.dom.results.innerHTML = resultCount + "&nbsp;results"; this.dom.results.innerHTML = resultCount + '&nbsp;results'
} }
} else { } else {
this.dom.results.innerHTML = ""; this.dom.results.innerHTML = ''
} }
} }
}; }
/** /**
* Handle onKeyDown event in the input box * Handle onKeyDown event in the input box
* @param {Event} event * @param {Event} event
* @private * @private
*/ */
SearchBox.prototype._onKeyDown = function(event) { SearchBox.prototype._onKeyDown = function (event) {
var keynum = event.which; var keynum = event.which
if (keynum == 27) { if (keynum === 27) {
// ESC // ESC
this.dom.search.value = ""; // clear search this.dom.search.value = '' // clear search
this._onSearch(); this._onSearch()
event.preventDefault(); event.preventDefault()
event.stopPropagation(); event.stopPropagation()
} else if (keynum == 13) { } else if (keynum === 13) {
// Enter // Enter
if (event.ctrlKey) { if (event.ctrlKey) {
// force to search again // force to search again
this._onSearch(true); this._onSearch(true)
} else if (event.shiftKey) { } else if (event.shiftKey) {
// move to the previous search result // move to the previous search result
this.previous(); this.previous()
} else { } else {
// move to the next search result // move to the next search result
this.next(); this.next()
} }
event.preventDefault(); event.preventDefault()
event.stopPropagation(); event.stopPropagation()
} }
}; }
/** /**
* Handle onKeyUp event in the input box * Handle onKeyUp event in the input box
* @param {Event} event * @param {Event} event
* @private * @private
*/ */
SearchBox.prototype._onKeyUp = function(event) { SearchBox.prototype._onKeyUp = function (event) {
var keynum = event.keyCode; var keynum = event.keyCode
if (keynum != 27 && keynum != 13) { if (keynum !== 27 && keynum !== 13) {
// !show and !Enter // !show and !Enter
this._onDelayedSearch(event); // For IE 9 this._onDelayedSearch(event) // For IE 9
} }
}; }
/** /**
* Clear the search results * Clear the search results
*/ */
SearchBox.prototype.clear = function() { SearchBox.prototype.clear = function () {
this.dom.search.value = ""; this.dom.search.value = ''
this._onSearch(); this._onSearch()
}; }
/** /**
* Refresh searchResults if there is a search value * Refresh searchResults if there is a search value
*/ */
SearchBox.prototype.forceSearch = function() { SearchBox.prototype.forceSearch = function () {
this._onSearch(true); this._onSearch(true)
}; }
/** /**
* Test whether the search box value is empty * Test whether the search box value is empty
* @returns {boolean} Returns true when empty. * @returns {boolean} Returns true when empty.
*/ */
SearchBox.prototype.isEmpty = function() { SearchBox.prototype.isEmpty = function () {
return this.dom.search.value === ""; return this.dom.search.value === ''
}; }
/** /**
* Destroy the search box * Destroy the search box
*/ */
SearchBox.prototype.destroy = function() { SearchBox.prototype.destroy = function () {
this.editor = null; this.editor = null
this.dom.container.removeChild(this.dom.wrapper); this.dom.container.removeChild(this.dom.wrapper)
this.dom = null; this.dom = null
this.results = null; this.results = null
this.activeResult = null; this.activeResult = null
this._clearDelay(); this._clearDelay()
}; }
module.exports = SearchBox; module.exports = SearchBox

View File

@ -1,24 +1,24 @@
'use strict'; 'use strict'
var ContextMenu = require('./ContextMenu'); var ContextMenu = require('./ContextMenu')
var translate = require('./i18n').translate; var translate = require('./i18n').translate
var util = require('./util'); var util = require('./util')
/** /**
* Creates a component that visualize path selection in tree based editors * Creates a component that visualize path selection in tree based editors
* @param {HTMLElement} container * @param {HTMLElement} container
* @param {HTMLElement} root * @param {HTMLElement} root
* @constructor * @constructor
*/ */
function TreePath(container, root) { function TreePath (container, root) {
if (container) { if (container) {
this.root = root; this.root = root
this.path = document.createElement('div'); this.path = document.createElement('div')
this.path.className = 'jsoneditor-treepath'; this.path.className = 'jsoneditor-treepath'
this.path.setAttribute('tabindex',0); this.path.setAttribute('tabindex', 0)
this.contentMenuClicked; this.contentMenuClicked = false
container.appendChild(this.path); container.appendChild(this.path)
this.reset(); this.reset()
} }
} }
@ -26,98 +26,98 @@ function TreePath(container, root) {
* Reset component to initial status * Reset component to initial status
*/ */
TreePath.prototype.reset = function () { TreePath.prototype.reset = function () {
this.path.innerHTML = translate('selectNode'); this.path.innerHTML = translate('selectNode')
}; }
/** /**
* Renders the component UI according to a given path objects * Renders the component UI according to a given path objects
* @param {Array<{name: String, childs: Array}>} pathObjs a list of path objects * @param {Array<{name: String, childs: Array}>} pathObjs a list of path objects
* *
*/ */
TreePath.prototype.setPath = function (pathObjs) { TreePath.prototype.setPath = function (pathObjs) {
var me = this; var me = this
this.path.innerHTML = ''; this.path.innerHTML = ''
if (pathObjs && pathObjs.length) { if (pathObjs && pathObjs.length) {
pathObjs.forEach(function (pathObj, idx) { pathObjs.forEach(function (pathObj, idx) {
var pathEl = document.createElement('span'); var pathEl = document.createElement('span')
var sepEl; var sepEl
pathEl.className = 'jsoneditor-treepath-element'; pathEl.className = 'jsoneditor-treepath-element'
pathEl.innerText = pathObj.name; pathEl.innerText = pathObj.name
pathEl.onclick = _onSegmentClick.bind(me, pathObj); pathEl.onclick = _onSegmentClick.bind(me, pathObj)
me.path.appendChild(pathEl); me.path.appendChild(pathEl)
if (pathObj.children.length) { if (pathObj.children.length) {
sepEl = document.createElement('span'); sepEl = document.createElement('span')
sepEl.className = 'jsoneditor-treepath-seperator'; sepEl.className = 'jsoneditor-treepath-seperator'
sepEl.innerHTML = '&#9658;'; sepEl.innerHTML = '&#9658;'
sepEl.onclick = function () { sepEl.onclick = function () {
me.contentMenuClicked = true; me.contentMenuClicked = true
var items = []; var items = []
pathObj.children.forEach(function (child) { pathObj.children.forEach(function (child) {
items.push({ items.push({
'text': child.name, text: child.name,
'className': 'jsoneditor-type-modes' + (pathObjs[idx + 1] + 1 && pathObjs[idx + 1].name === child.name ? ' jsoneditor-selected' : ''), className: 'jsoneditor-type-modes' + (pathObjs[idx + 1] + 1 && pathObjs[idx + 1].name === child.name ? ' jsoneditor-selected' : ''),
'click': _onContextMenuItemClick.bind(me, pathObj, child.name) click: _onContextMenuItemClick.bind(me, pathObj, child.name)
}); })
}); })
var menu = new ContextMenu(items); var menu = new ContextMenu(items)
menu.show(sepEl, me.root, true); menu.show(sepEl, me.root, true)
}; }
me.path.appendChild(sepEl); me.path.appendChild(sepEl)
} }
if(idx === pathObjs.length - 1) { if (idx === pathObjs.length - 1) {
var leftRectPos = (sepEl || pathEl).getBoundingClientRect().right; var leftRectPos = (sepEl || pathEl).getBoundingClientRect().right
if(me.path.offsetWidth < leftRectPos) { if (me.path.offsetWidth < leftRectPos) {
me.path.scrollLeft = leftRectPos; me.path.scrollLeft = leftRectPos
} }
if (me.path.scrollLeft) { if (me.path.scrollLeft) {
var showAllBtn = document.createElement('span'); var showAllBtn = document.createElement('span')
showAllBtn.className = 'jsoneditor-treepath-show-all-btn'; showAllBtn.className = 'jsoneditor-treepath-show-all-btn'
showAllBtn.title = 'show all path'; showAllBtn.title = 'show all path'
showAllBtn.innerHTML = '...'; showAllBtn.innerHTML = '...'
showAllBtn.onclick = _onShowAllClick.bind(me, pathObjs); showAllBtn.onclick = _onShowAllClick.bind(me, pathObjs)
me.path.insertBefore(showAllBtn, me.path.firstChild); me.path.insertBefore(showAllBtn, me.path.firstChild)
} }
} }
}); })
} }
function _onShowAllClick(pathObjs) { function _onShowAllClick (pathObjs) {
me.contentMenuClicked = false; me.contentMenuClicked = false
util.addClassName(me.path, 'show-all'); util.addClassName(me.path, 'show-all')
me.path.style.width = me.path.parentNode.getBoundingClientRect().width - 10 + 'px'; me.path.style.width = me.path.parentNode.getBoundingClientRect().width - 10 + 'px'
me.path.onblur = function() { me.path.onblur = function () {
if (me.contentMenuClicked) { if (me.contentMenuClicked) {
me.contentMenuClicked = false; me.contentMenuClicked = false
me.path.focus(); me.path.focus()
return; return
} }
util.removeClassName(me.path, 'show-all'); util.removeClassName(me.path, 'show-all')
me.path.onblur = undefined; me.path.onblur = undefined
me.path.style.width = ''; me.path.style.width = ''
me.setPath(pathObjs); me.setPath(pathObjs)
}; }
} }
function _onSegmentClick(pathObj) { function _onSegmentClick (pathObj) {
if (this.selectionCallback) { if (this.selectionCallback) {
this.selectionCallback(pathObj); this.selectionCallback(pathObj)
} }
} }
function _onContextMenuItemClick(pathObj, selection) { function _onContextMenuItemClick (pathObj, selection) {
if (this.contextMenuCallback) { if (this.contextMenuCallback) {
this.contextMenuCallback(pathObj, selection); this.contextMenuCallback(pathObj, selection)
} }
} }
}; }
/** /**
* set a callback function for selection of path section * set a callback function for selection of path section
@ -125,9 +125,9 @@ TreePath.prototype.setPath = function (pathObjs) {
*/ */
TreePath.prototype.onSectionSelected = function (callback) { TreePath.prototype.onSectionSelected = function (callback) {
if (typeof callback === 'function') { if (typeof callback === 'function') {
this.selectionCallback = callback; this.selectionCallback = callback
} }
}; }
/** /**
* set a callback function for selection of path section * set a callback function for selection of path section
@ -135,8 +135,8 @@ TreePath.prototype.onSectionSelected = function (callback) {
*/ */
TreePath.prototype.onContextMenuItemSelected = function (callback) { TreePath.prototype.onContextMenuItemSelected = function (callback) {
if (typeof callback === 'function') { if (typeof callback === 'function') {
this.contextMenuCallback = callback; this.contextMenuCallback = callback
} }
}; }
module.exports = TreePath; module.exports = TreePath

View File

@ -2,20 +2,18 @@ var ace
if (window.ace) { if (window.ace) {
// use the already loaded instance of Ace // use the already loaded instance of Ace
ace = window.ace ace = window.ace
} } else {
else {
try { try {
// load brace // load brace
ace = require('brace'); ace = require('brace')
// load required Ace plugins // load required Ace plugins
require('brace/mode/json'); require('brace/mode/json')
require('brace/ext/searchbox'); require('brace/ext/searchbox')
} } catch (err) {
catch (err) {
// failed to load brace (can be minimalist bundle). // failed to load brace (can be minimalist bundle).
// No worries, the editor will fall back to plain text if needed. // No worries, the editor will fall back to plain text if needed.
} }
} }
module.exports = ace; module.exports = ace

View File

@ -3,7 +3,7 @@
* *
* Copyright (c) 2010, Ajax.org B.V. * Copyright (c) 2010, Ajax.org B.V.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: * modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright * * Redistributions of source code must retain the above copyright
@ -14,7 +14,7 @@
* * Neither the name of Ajax.org B.V. nor the * * Neither the name of Ajax.org B.V. nor the
* names of its contributors may be used to endorse or promote products * names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission. * derived from this software without specific prior written permission.
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@ -28,118 +28,117 @@
* *
* ***** END LICENSE BLOCK ***** */ * ***** END LICENSE BLOCK ***** */
ace.define('ace/theme/jsoneditor', ['require', 'exports', 'module', 'ace/lib/dom'], function(acequire, exports, module) { window.ace.define('ace/theme/jsoneditor', ['require', 'exports', 'module', 'ace/lib/dom'], function (acequire, exports, module) {
exports.isDark = false
exports.cssClass = 'ace-jsoneditor'
exports.cssText = `.ace-jsoneditor .ace_gutter {
background: #ebebeb;
color: #333
}
exports.isDark = false; .ace-jsoneditor.ace_editor {
exports.cssClass = "ace-jsoneditor"; font-family: "dejavu sans mono", "droid sans mono", consolas, monaco, "lucida console", "courier new", courier, monospace, sans-serif;
exports.cssText = ".ace-jsoneditor .ace_gutter {\ line-height: 1.3;
background: #ebebeb;\ background-color: #fff;
color: #333\ }
}\ .ace-jsoneditor .ace_print-margin {
\ width: 1px;
.ace-jsoneditor.ace_editor {\ background: #e8e8e8
font-family: \"dejavu sans mono\", \"droid sans mono\", consolas, monaco, \"lucida console\", \"courier new\", courier, monospace, sans-serif;\ }
line-height: 1.3;\ .ace-jsoneditor .ace_scroller {
background-color: #fff;\ background-color: #FFFFFF
}\ }
.ace-jsoneditor .ace_print-margin {\ .ace-jsoneditor .ace_text-layer {
width: 1px;\ color: gray
background: #e8e8e8\ }
}\ .ace-jsoneditor .ace_variable {
.ace-jsoneditor .ace_scroller {\ color: #1a1a1a
background-color: #FFFFFF\ }
}\ .ace-jsoneditor .ace_cursor {
.ace-jsoneditor .ace_text-layer {\ border-left: 2px solid #000000
color: gray\ }
}\ .ace-jsoneditor .ace_overwrite-cursors .ace_cursor {
.ace-jsoneditor .ace_variable {\ border-left: 0px;
color: #1a1a1a\ border-bottom: 1px solid #000000
}\ }
.ace-jsoneditor .ace_cursor {\ .ace-jsoneditor .ace_marker-layer .ace_selection {
border-left: 2px solid #000000\ background: lightgray
}\ }
.ace-jsoneditor .ace_overwrite-cursors .ace_cursor {\ .ace-jsoneditor.ace_multiselect .ace_selection.ace_start {
border-left: 0px;\ box-shadow: 0 0 3px 0px #FFFFFF;
border-bottom: 1px solid #000000\ border-radius: 2px
}\ }
.ace-jsoneditor .ace_marker-layer .ace_selection {\ .ace-jsoneditor .ace_marker-layer .ace_step {
background: lightgray\ background: rgb(255, 255, 0)
}\ }
.ace-jsoneditor.ace_multiselect .ace_selection.ace_start {\ .ace-jsoneditor .ace_marker-layer .ace_bracket {
box-shadow: 0 0 3px 0px #FFFFFF;\ margin: -1px 0 0 -1px;
border-radius: 2px\ border: 1px solid #BFBFBF
}\ }
.ace-jsoneditor .ace_marker-layer .ace_step {\ .ace-jsoneditor .ace_marker-layer .ace_active-line {
background: rgb(255, 255, 0)\ background: #FFFBD1
}\ }
.ace-jsoneditor .ace_marker-layer .ace_bracket {\ .ace-jsoneditor .ace_gutter-active-line {
margin: -1px 0 0 -1px;\ background-color : #dcdcdc
border: 1px solid #BFBFBF\ }
}\ .ace-jsoneditor .ace_marker-layer .ace_selected-word {
.ace-jsoneditor .ace_marker-layer .ace_active-line {\ border: 1px solid lightgray
background: #FFFBD1\ }
}\ .ace-jsoneditor .ace_invisible {
.ace-jsoneditor .ace_gutter-active-line {\ color: #BFBFBF
background-color : #dcdcdc\ }
}\ .ace-jsoneditor .ace_keyword,
.ace-jsoneditor .ace_marker-layer .ace_selected-word {\ .ace-jsoneditor .ace_meta,
border: 1px solid lightgray\ .ace-jsoneditor .ace_support.ace_constant.ace_property-value {
}\ color: #AF956F
.ace-jsoneditor .ace_invisible {\ }
color: #BFBFBF\ .ace-jsoneditor .ace_keyword.ace_operator {
}\ color: #484848
.ace-jsoneditor .ace_keyword,\ }
.ace-jsoneditor .ace_meta,\ .ace-jsoneditor .ace_keyword.ace_other.ace_unit {
.ace-jsoneditor .ace_support.ace_constant.ace_property-value {\ color: #96DC5F
color: #AF956F\ }
}\ .ace-jsoneditor .ace_constant.ace_language {
.ace-jsoneditor .ace_keyword.ace_operator {\ color: darkorange
color: #484848\ }
}\ .ace-jsoneditor .ace_constant.ace_numeric {
.ace-jsoneditor .ace_keyword.ace_other.ace_unit {\ color: red
color: #96DC5F\ }
}\ .ace-jsoneditor .ace_constant.ace_character.ace_entity {
.ace-jsoneditor .ace_constant.ace_language {\ color: #BF78CC
color: darkorange\ }
}\ .ace-jsoneditor .ace_invalid {
.ace-jsoneditor .ace_constant.ace_numeric {\ color: #FFFFFF;
color: red\ background-color: #FF002A;
}\ }
.ace-jsoneditor .ace_constant.ace_character.ace_entity {\ .ace-jsoneditor .ace_fold {
color: #BF78CC\ background-color: #AF956F;
}\ border-color: #000000
.ace-jsoneditor .ace_invalid {\ }
color: #FFFFFF;\ .ace-jsoneditor .ace_storage,
background-color: #FF002A;\ .ace-jsoneditor .ace_support.ace_class,
}\ .ace-jsoneditor .ace_support.ace_function,
.ace-jsoneditor .ace_fold {\ .ace-jsoneditor .ace_support.ace_other,
background-color: #AF956F;\ .ace-jsoneditor .ace_support.ace_type {
border-color: #000000\ color: #C52727
}\ }
.ace-jsoneditor .ace_storage,\ .ace-jsoneditor .ace_string {
.ace-jsoneditor .ace_support.ace_class,\ color: green
.ace-jsoneditor .ace_support.ace_function,\ }
.ace-jsoneditor .ace_support.ace_other,\ .ace-jsoneditor .ace_comment {
.ace-jsoneditor .ace_support.ace_type {\ color: #BCC8BA
color: #C52727\ }
}\ .ace-jsoneditor .ace_entity.ace_name.ace_tag,
.ace-jsoneditor .ace_string {\ .ace-jsoneditor .ace_entity.ace_other.ace_attribute-name {
color: green\ color: #606060
}\ }
.ace-jsoneditor .ace_comment {\ .ace-jsoneditor .ace_markup.ace_underline {
color: #BCC8BA\ text-decoration: underline
}\ }
.ace-jsoneditor .ace_entity.ace_name.ace_tag,\ .ace-jsoneditor .ace_indent-guide {
.ace-jsoneditor .ace_entity.ace_other.ace_attribute-name {\ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y
color: #606060\ }`
}\
.ace-jsoneditor .ace_markup.ace_underline {\
text-decoration: underline\
}\
.ace-jsoneditor .ace_indent-guide {\
background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==\") right repeat-y\
}";
var dom = acequire("../lib/dom"); var dom = acequire('../lib/dom')
dom.importCssString(exports.cssText, exports.cssClass); dom.importCssString(exports.cssText, exports.cssClass)
}); })

View File

@ -1,14 +1,14 @@
'use strict'; 'use strict'
var util = require('./util'); var util = require('./util')
var ContextMenu = require('./ContextMenu'); var ContextMenu = require('./ContextMenu')
var translate = require('./i18n').translate; var translate = require('./i18n').translate
/** /**
* A factory function to create an AppendNode, which depends on a Node * A factory function to create an AppendNode, which depends on a Node
* @param {Node} Node * @param {Node} Node
*/ */
function appendNodeFactory(Node) { function appendNodeFactory (Node) {
/** /**
* @constructor AppendNode * @constructor AppendNode
* @extends Node * @extends Node
@ -18,11 +18,11 @@ function appendNodeFactory(Node) {
*/ */
function AppendNode (editor) { function AppendNode (editor) {
/** @type {TreeEditor} */ /** @type {TreeEditor} */
this.editor = editor; this.editor = editor
this.dom = {}; this.dom = {}
} }
AppendNode.prototype = new Node(); AppendNode.prototype = new Node()
/** /**
* Return a table row with an append button. * Return a table row with an append button.
@ -30,109 +30,108 @@ function appendNodeFactory(Node) {
*/ */
AppendNode.prototype.getDom = function () { AppendNode.prototype.getDom = function () {
// TODO: implement a new solution for the append node // TODO: implement a new solution for the append node
var dom = this.dom; var dom = this.dom
if (dom.tr) { if (dom.tr) {
return dom.tr; return dom.tr
} }
this._updateEditability(); this._updateEditability()
// a row for the append button // a row for the append button
var trAppend = document.createElement('tr'); var trAppend = document.createElement('tr')
trAppend.className = 'jsoneditor-append'; trAppend.className = 'jsoneditor-append'
trAppend.node = this; trAppend.node = this
dom.tr = trAppend; dom.tr = trAppend
// TODO: consistent naming // TODO: consistent naming
if (this.editor.options.mode === 'tree') { if (this.editor.options.mode === 'tree') {
// a cell for the dragarea column // a cell for the dragarea column
dom.tdDrag = document.createElement('td'); dom.tdDrag = document.createElement('td')
// create context menu // create context menu
var tdMenu = document.createElement('td'); var tdMenu = document.createElement('td')
dom.tdMenu = tdMenu; dom.tdMenu = tdMenu
var menu = document.createElement('button'); var menu = document.createElement('button')
menu.type = 'button'; menu.type = 'button'
menu.className = 'jsoneditor-button jsoneditor-contextmenu'; menu.className = 'jsoneditor-button jsoneditor-contextmenu'
menu.title = 'Click to open the actions menu (Ctrl+M)'; menu.title = 'Click to open the actions menu (Ctrl+M)'
dom.menu = menu; dom.menu = menu
tdMenu.appendChild(dom.menu); tdMenu.appendChild(dom.menu)
} }
// a cell for the contents (showing text 'empty') // a cell for the contents (showing text 'empty')
var tdAppend = document.createElement('td'); var tdAppend = document.createElement('td')
var domText = document.createElement('div'); var domText = document.createElement('div')
domText.innerHTML = '(' + translate('empty') + ')'; domText.innerHTML = '(' + translate('empty') + ')'
domText.className = 'jsoneditor-readonly'; domText.className = 'jsoneditor-readonly'
tdAppend.appendChild(domText); tdAppend.appendChild(domText)
dom.td = tdAppend; dom.td = tdAppend
dom.text = domText; dom.text = domText
this.updateDom(); this.updateDom()
return trAppend; return trAppend
}; }
/** /**
* Append node doesn't have a path * Append node doesn't have a path
* @returns {null} * @returns {null}
*/ */
AppendNode.prototype.getPath = function() { AppendNode.prototype.getPath = function () {
return null; return null
}; }
/** /**
* Append node doesn't have an index * Append node doesn't have an index
* @returns {null} * @returns {null}
*/ */
AppendNode.prototype.getIndex = function() { AppendNode.prototype.getIndex = function () {
return null; return null
}; }
/** /**
* Update the HTML dom of the Node * Update the HTML dom of the Node
*/ */
AppendNode.prototype.updateDom = function(options) { AppendNode.prototype.updateDom = function (options) {
var dom = this.dom; var dom = this.dom
var tdAppend = dom.td; var tdAppend = dom.td
if (tdAppend) { if (tdAppend) {
tdAppend.style.paddingLeft = (this.getLevel() * 24 + 26) + 'px'; tdAppend.style.paddingLeft = (this.getLevel() * 24 + 26) + 'px'
// TODO: not so nice hard coded offset // TODO: not so nice hard coded offset
} }
var domText = dom.text; var domText = dom.text
if (domText) { if (domText) {
domText.innerHTML = '(' + translate('empty') + ' ' + this.parent.type + ')'; domText.innerHTML = '(' + translate('empty') + ' ' + this.parent.type + ')'
} }
// attach or detach the contents of the append node: // attach or detach the contents of the append node:
// hide when the parent has childs, show when the parent has no childs // hide when the parent has childs, show when the parent has no childs
var trAppend = dom.tr; var trAppend = dom.tr
if (!this.isVisible()) { if (!this.isVisible()) {
if (dom.tr.firstChild) { if (dom.tr.firstChild) {
if (dom.tdDrag) { if (dom.tdDrag) {
trAppend.removeChild(dom.tdDrag); trAppend.removeChild(dom.tdDrag)
} }
if (dom.tdMenu) { if (dom.tdMenu) {
trAppend.removeChild(dom.tdMenu); trAppend.removeChild(dom.tdMenu)
} }
trAppend.removeChild(tdAppend); trAppend.removeChild(tdAppend)
} }
} } else {
else {
if (!dom.tr.firstChild) { if (!dom.tr.firstChild) {
if (dom.tdDrag) { if (dom.tdDrag) {
trAppend.appendChild(dom.tdDrag); trAppend.appendChild(dom.tdDrag)
} }
if (dom.tdMenu) { if (dom.tdMenu) {
trAppend.appendChild(dom.tdMenu); trAppend.appendChild(dom.tdMenu)
} }
trAppend.appendChild(tdAppend); trAppend.appendChild(tdAppend)
} }
} }
}; }
/** /**
* Check whether the AppendNode is currently visible. * Check whether the AppendNode is currently visible.
@ -140,8 +139,8 @@ function appendNodeFactory(Node) {
* @return {boolean} isVisible * @return {boolean} isVisible
*/ */
AppendNode.prototype.isVisible = function () { AppendNode.prototype.isVisible = function () {
return (this.parent.childs.length == 0); return (this.parent.childs.length === 0)
}; }
/** /**
* Show a contextmenu for this node * Show a contextmenu for this node
@ -150,110 +149,109 @@ function appendNodeFactory(Node) {
* is being closed. * is being closed.
*/ */
AppendNode.prototype.showContextMenu = function (anchor, onClose) { AppendNode.prototype.showContextMenu = function (anchor, onClose) {
var node = this; var node = this
var titles = Node.TYPE_TITLES; var titles = Node.TYPE_TITLES
var appendSubmenu = [ var appendSubmenu = [
{ {
text: translate('auto'), text: translate('auto'),
className: 'jsoneditor-type-auto', className: 'jsoneditor-type-auto',
title: titles.auto, title: titles.auto,
click: function () { click: function () {
node._onAppend('', '', 'auto'); node._onAppend('', '', 'auto')
}
},
{
text: translate('array'),
className: 'jsoneditor-type-array',
title: titles.array,
click: function () {
node._onAppend('', []);
}
},
{
text: translate('object'),
className: 'jsoneditor-type-object',
title: titles.object,
click: function () {
node._onAppend('', {});
}
},
{
text: translate('string'),
className: 'jsoneditor-type-string',
title: titles.string,
click: function () {
node._onAppend('', '', 'string');
}
} }
]; },
node.addTemplates(appendSubmenu, true); {
text: translate('array'),
className: 'jsoneditor-type-array',
title: titles.array,
click: function () {
node._onAppend('', [])
}
},
{
text: translate('object'),
className: 'jsoneditor-type-object',
title: titles.object,
click: function () {
node._onAppend('', {})
}
},
{
text: translate('string'),
className: 'jsoneditor-type-string',
title: titles.string,
click: function () {
node._onAppend('', '', 'string')
}
}
]
node.addTemplates(appendSubmenu, true)
var items = [ var items = [
// create append button // create append button
{ {
'text': translate('appendText'), text: translate('appendText'),
'title': translate('appendTitleAuto'), title: translate('appendTitleAuto'),
'submenuTitle': translate('appendSubmenuTitle'), submenuTitle: translate('appendSubmenuTitle'),
'className': 'jsoneditor-insert', className: 'jsoneditor-insert',
'click': function () { click: function () {
node._onAppend('', '', 'auto'); node._onAppend('', '', 'auto')
}, },
'submenu': appendSubmenu submenu: appendSubmenu
} }
]; ]
if (this.editor.options.onCreateMenu) { if (this.editor.options.onCreateMenu) {
var path = node.parent.getPath(); var path = node.parent.getPath()
items = this.editor.options.onCreateMenu(items, { items = this.editor.options.onCreateMenu(items, {
type: 'append', type: 'append',
path: path, path: path,
paths: [path] paths: [path]
}); })
} }
var menu = new ContextMenu(items, {close: onClose}); var menu = new ContextMenu(items, { close: onClose })
menu.show(anchor, this.editor.frame); menu.show(anchor, this.editor.frame)
}; }
/** /**
* Handle an event. The event is caught centrally by the editor * Handle an event. The event is caught centrally by the editor
* @param {Event} event * @param {Event} event
*/ */
AppendNode.prototype.onEvent = function (event) { AppendNode.prototype.onEvent = function (event) {
var type = event.type; var type = event.type
var target = event.target || event.srcElement; var target = event.target || event.srcElement
var dom = this.dom; var dom = this.dom
// highlight the append nodes parent // highlight the append nodes parent
var menu = dom.menu; var menu = dom.menu
if (target == menu) { if (target === menu) {
if (type == 'mouseover') { if (type === 'mouseover') {
this.editor.highlighter.highlight(this.parent); this.editor.highlighter.highlight(this.parent)
} } else if (type === 'mouseout') {
else if (type == 'mouseout') { this.editor.highlighter.unhighlight()
this.editor.highlighter.unhighlight();
} }
} }
// context menu events // context menu events
if (type == 'click' && target == dom.menu) { if (type === 'click' && target === dom.menu) {
var highlighter = this.editor.highlighter; var highlighter = this.editor.highlighter
highlighter.highlight(this.parent); highlighter.highlight(this.parent)
highlighter.lock(); highlighter.lock()
util.addClassName(dom.menu, 'jsoneditor-selected'); util.addClassName(dom.menu, 'jsoneditor-selected')
this.showContextMenu(dom.menu, function () { this.showContextMenu(dom.menu, function () {
util.removeClassName(dom.menu, 'jsoneditor-selected'); util.removeClassName(dom.menu, 'jsoneditor-selected')
highlighter.unlock(); highlighter.unlock()
highlighter.unhighlight(); highlighter.unhighlight()
}); })
} }
if (type == 'keydown') { if (type === 'keydown') {
this.onKeyDown(event); this.onKeyDown(event)
} }
}; }
return AppendNode; return AppendNode
} }
module.exports = appendNodeFactory; module.exports = appendNodeFactory

View File

@ -1,396 +1,384 @@
'use strict'; 'use strict'
var defaultFilterFunction = { var defaultFilterFunction = {
start: function (token, match, config) { start: function (token, match, config) {
return match.indexOf(token) === 0; return match.indexOf(token) === 0
}, },
contain: function (token, match, config) { contain: function (token, match, config) {
return match.indexOf(token) > -1; return match.indexOf(token) > -1
} }
};
function completely(config) {
config = config || {};
config.filter = config.filter || 'start';
config.trigger = config.trigger || 'keydown';
config.confirmKeys = config.confirmKeys || [39, 35, 9] // right, end, tab
config.caseSensitive = config.caseSensitive || false // autocomplete case sensitive
var fontSize = '';
var fontFamily = '';
var wrapper = document.createElement('div');
wrapper.style.position = 'relative';
wrapper.style.outline = '0';
wrapper.style.border = '0';
wrapper.style.margin = '0';
wrapper.style.padding = '0';
var dropDown = document.createElement('div');
dropDown.className = 'autocomplete dropdown';
dropDown.style.position = 'absolute';
dropDown.style.visibility = 'hidden';
var spacer;
var leftSide; // <-- it will contain the leftSide part of the textfield (the bit that was already autocompleted)
var createDropDownController = function (elem, rs) {
var rows = [];
var ix = 0;
var oldIndex = -1;
var onMouseOver = function () { this.style.outline = '1px solid #ddd'; }
var onMouseOut = function () { this.style.outline = '0'; }
var onMouseDown = function () { p.hide(); p.onmouseselection(this.__hint, p.rs); }
var p = {
rs: rs,
hide: function () {
elem.style.visibility = 'hidden';
//rs.hideDropDown();
},
refresh: function (token, array) {
elem.style.visibility = 'hidden';
ix = 0;
elem.innerHTML = '';
var vph = (window.innerHeight || document.documentElement.clientHeight);
var rect = elem.parentNode.getBoundingClientRect();
var distanceToTop = rect.top - 6; // heuristic give 6px
var distanceToBottom = vph - rect.bottom - 6; // distance from the browser border.
rows = [];
var filterFn = typeof config.filter === 'function' ? config.filter : defaultFilterFunction[config.filter];
var filtered = !filterFn ? [] : array.filter(function (match) {
return filterFn(config.caseSensitive ? token : token.toLowerCase(), config.caseSensitive ? match : match.toLowerCase(), config);
});
rows = filtered.map(function (row) {
var divRow = document.createElement('div');
divRow.className = 'item';
//divRow.style.color = config.color;
divRow.onmouseover = onMouseOver;
divRow.onmouseout = onMouseOut;
divRow.onmousedown = onMouseDown;
divRow.__hint = row;
divRow.innerHTML = row.substring(0, token.length) + '<b>' + row.substring(token.length) + '</b>';
elem.appendChild(divRow);
return divRow;
});
if (rows.length === 0) {
return; // nothing to show.
}
if (rows.length === 1 && ( (token.toLowerCase() === rows[0].__hint.toLowerCase() && !config.caseSensitive)
||(token === rows[0].__hint && config.caseSensitive))){
return; // do not show the dropDown if it has only one element which matches what we have just displayed.
}
if (rows.length < 2) return;
p.highlight(0);
if (distanceToTop > distanceToBottom * 3) { // Heuristic (only when the distance to the to top is 4 times more than distance to the bottom
elem.style.maxHeight = distanceToTop + 'px'; // we display the dropDown on the top of the input text
elem.style.top = '';
elem.style.bottom = '100%';
} else {
elem.style.top = '100%';
elem.style.bottom = '';
elem.style.maxHeight = distanceToBottom + 'px';
}
elem.style.visibility = 'visible';
},
highlight: function (index) {
if (oldIndex != -1 && rows[oldIndex]) {
rows[oldIndex].className = "item";
}
rows[index].className = "item hover";
oldIndex = index;
},
move: function (step) { // moves the selection either up or down (unless it's not possible) step is either +1 or -1.
if (elem.style.visibility === 'hidden') return ''; // nothing to move if there is no dropDown. (this happens if the user hits escape and then down or up)
if (ix + step === -1 || ix + step === rows.length) return rows[ix].__hint; // NO CIRCULAR SCROLLING.
ix += step;
p.highlight(ix);
return rows[ix].__hint;//txtShadow.value = uRows[uIndex].__hint ;
},
onmouseselection: function () { } // it will be overwritten.
};
return p;
}
function setEndOfContenteditable(contentEditableElement) {
var range, selection;
if (document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
{
range = document.createRange();//Create a range (a range is a like the selection but invisible)
range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
selection = window.getSelection();//get the selection object (allows you to change selection)
selection.removeAllRanges();//remove any selections already made
selection.addRange(range);//make the range you have just created the visible selection
}
else if (document.selection)//IE 8 and lower
{
range = document.body.createTextRange();//Create a range (a range is a like the selection but invisible)
range.moveToElementText(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
range.select();//Select the range (make it the visible selection
}
}
function calculateWidthForText(text) {
if (spacer === undefined) { // on first call only.
spacer = document.createElement('span');
spacer.style.visibility = 'hidden';
spacer.style.position = 'fixed';
spacer.style.outline = '0';
spacer.style.margin = '0';
spacer.style.padding = '0';
spacer.style.border = '0';
spacer.style.left = '0';
spacer.style.whiteSpace = 'pre';
spacer.style.fontSize = fontSize;
spacer.style.fontFamily = fontFamily;
spacer.style.fontWeight = 'normal';
document.body.appendChild(spacer);
}
// Used to encode an HTML string into a plain text.
// taken from http://stackoverflow.com/questions/1219860/javascript-jquery-html-encoding
spacer.innerHTML = String(text).replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
return spacer.getBoundingClientRect().right;
}
var rs = {
onArrowDown: function () { }, // defaults to no action.
onArrowUp: function () { }, // defaults to no action.
onEnter: function () { }, // defaults to no action.
onTab: function () { }, // defaults to no action.
startFrom: 0,
options: [],
element: null,
elementHint: null,
elementStyle: null,
wrapper: wrapper, // Only to allow easy access to the HTML elements to the final user (possibly for minor customizations)
show: function (element, startPos, options) {
this.startFrom = startPos;
this.wrapper.remove();
if (this.elementHint) {
this.elementHint.remove();
this.elementHint = null;
}
if (fontSize == '') {
fontSize = window.getComputedStyle(element).getPropertyValue('font-size');
}
if (fontFamily == '') {
fontFamily = window.getComputedStyle(element).getPropertyValue('font-family');
}
var w = element.getBoundingClientRect().right - element.getBoundingClientRect().left;
dropDown.style.marginLeft = '0';
dropDown.style.marginTop = element.getBoundingClientRect().height + 'px';
this.options = options;
if (this.element != element) {
this.element = element;
this.elementStyle = {
zIndex: this.element.style.zIndex,
position: this.element.style.position,
backgroundColor: this.element.style.backgroundColor,
borderColor: this.element.style.borderColor
}
}
this.element.style.zIndex = 3;
this.element.style.position = 'relative';
this.element.style.backgroundColor = 'transparent';
this.element.style.borderColor = 'transparent';
this.elementHint = element.cloneNode();
this.elementHint.className = 'autocomplete hint';
this.elementHint.style.zIndex = 2;
this.elementHint.style.position = 'absolute';
this.elementHint.onfocus = function () { this.element.focus(); }.bind(this);
if (this.element.addEventListener) {
this.element.removeEventListener("keydown", keyDownHandler);
this.element.addEventListener("keydown", keyDownHandler, false);
this.element.removeEventListener("blur", onBlurHandler);
this.element.addEventListener("blur", onBlurHandler, false);
}
wrapper.appendChild(this.elementHint);
wrapper.appendChild(dropDown);
element.parentElement.appendChild(wrapper);
this.repaint(element);
},
setText: function (text) {
this.element.innerText = text;
},
getText: function () {
return this.element.innerText;
},
hideDropDown: function () {
this.wrapper.remove();
if (this.elementHint) {
this.elementHint.remove();
this.elementHint = null;
dropDownController.hide();
this.element.style.zIndex = this.elementStyle.zIndex;
this.element.style.position = this.elementStyle.position;
this.element.style.backgroundColor = this.elementStyle.backgroundColor;
this.element.style.borderColor = this.elementStyle.borderColor;
}
},
repaint: function (element) {
var text = element.innerText;
text = text.replace('\n', '');
var startFrom = this.startFrom;
var options = this.options;
var optionsLength = this.options.length;
// breaking text in leftSide and token.
var token = text.substring(this.startFrom);
leftSide = text.substring(0, this.startFrom);
for (var i = 0; i < optionsLength; i++) {
var opt = this.options[i];
if ( (!config.caseSensitive && opt.toLowerCase().indexOf(token.toLowerCase()) === 0)
|| (config.caseSensitive && opt.indexOf(token) === 0)) { // <-- how about upperCase vs. lowercase
this.elementHint.innerText = leftSide + token + opt.substring(token.length);
this.elementHint.realInnerText = leftSide + opt;
break;
}
}
// moving the dropDown and refreshing it.
dropDown.style.left = calculateWidthForText(leftSide) + 'px';
dropDownController.refresh(token, this.options);
this.elementHint.style.width = calculateWidthForText(this.elementHint.innerText) + 10 + 'px'
var wasDropDownHidden = (dropDown.style.visibility == 'hidden');
if (!wasDropDownHidden)
this.elementHint.style.width = calculateWidthForText(this.elementHint.innerText) + dropDown.clientWidth + 'px';
}
};
var dropDownController = createDropDownController(dropDown, rs);
var keyDownHandler = function (e) {
//console.log("Keydown:" + e.keyCode);
e = e || window.event;
var keyCode = e.keyCode;
if (this.elementHint == null) return;
if (keyCode == 33) { return; } // page up (do nothing)
if (keyCode == 34) { return; } // page down (do nothing);
if (keyCode == 27) { //escape
rs.hideDropDown();
rs.element.focus();
e.preventDefault();
e.stopPropagation();
return;
}
var text = this.element.innerText;
text = text.replace('\n', '');
var startFrom = this.startFrom;
if (config.confirmKeys.indexOf(keyCode) >= 0) { // (autocomplete triggered)
if (keyCode == 9) {
if (this.elementHint.innerText.length == 0) {
rs.onTab();
}
}
if (this.elementHint.innerText.length > 0) { // if there is a hint
if (this.element.innerText != this.elementHint.realInnerText) {
this.element.innerText = this.elementHint.realInnerText;
rs.hideDropDown();
setEndOfContenteditable(this.element);
if (keyCode == 9) {
rs.element.focus();
e.preventDefault();
e.stopPropagation();
}
}
}
return;
}
if (keyCode == 13) { // enter (autocomplete triggered)
if (this.elementHint.innerText.length == 0) { // if there is a hint
rs.onEnter();
} else {
var wasDropDownHidden = (dropDown.style.visibility == 'hidden');
dropDownController.hide();
if (wasDropDownHidden) {
rs.hideDropDown();
rs.element.focus();
rs.onEnter();
return;
}
this.element.innerText = this.elementHint.realInnerText;
rs.hideDropDown();
setEndOfContenteditable(this.element);
e.preventDefault();
e.stopPropagation();
}
return;
}
if (keyCode == 40) { // down
var token = text.substring(this.startFrom);
var m = dropDownController.move(+1);
if (m == '') { rs.onArrowDown(); }
this.elementHint.innerText = leftSide + token + m.substring(token.length);
this.elementHint.realInnerText = leftSide + m;
e.preventDefault();
e.stopPropagation();
return;
}
if (keyCode == 38) { // up
var token = text.substring(this.startFrom);
var m = dropDownController.move(-1);
if (m == '') { rs.onArrowUp(); }
this.elementHint.innerText = leftSide + token + m.substring(token.length);
this.elementHint.realInnerText = leftSide + m;
e.preventDefault();
e.stopPropagation();
return;
}
}.bind(rs);
var onBlurHandler = function (e) {
rs.hideDropDown();
//console.log("Lost focus.");
}.bind(rs);
dropDownController.onmouseselection = function (text, rs) {
rs.element.innerText = rs.elementHint.innerText = leftSide + text;
rs.hideDropDown();
window.setTimeout(function () {
rs.element.focus();
setEndOfContenteditable(rs.element);
}, 1);
};
return rs;
} }
module.exports = completely; function completely (config) {
config = config || {}
config.filter = config.filter || 'start'
config.trigger = config.trigger || 'keydown'
config.confirmKeys = config.confirmKeys || [39, 35, 9] // right, end, tab
config.caseSensitive = config.caseSensitive || false // autocomplete case sensitive
var fontSize = ''
var fontFamily = ''
var wrapper = document.createElement('div')
wrapper.style.position = 'relative'
wrapper.style.outline = '0'
wrapper.style.border = '0'
wrapper.style.margin = '0'
wrapper.style.padding = '0'
var dropDown = document.createElement('div')
dropDown.className = 'autocomplete dropdown'
dropDown.style.position = 'absolute'
dropDown.style.visibility = 'hidden'
var spacer
var leftSide // <-- it will contain the leftSide part of the textfield (the bit that was already autocompleted)
var createDropDownController = function (elem, rs) {
var rows = []
var ix = 0
var oldIndex = -1
var onMouseOver = function () { this.style.outline = '1px solid #ddd' }
var onMouseOut = function () { this.style.outline = '0' }
var onMouseDown = function () { p.hide(); p.onmouseselection(this.__hint, p.rs) }
var p = {
rs: rs,
hide: function () {
elem.style.visibility = 'hidden'
// rs.hideDropDown();
},
refresh: function (token, array) {
elem.style.visibility = 'hidden'
ix = 0
elem.innerHTML = ''
var vph = (window.innerHeight || document.documentElement.clientHeight)
var rect = elem.parentNode.getBoundingClientRect()
var distanceToTop = rect.top - 6 // heuristic give 6px
var distanceToBottom = vph - rect.bottom - 6 // distance from the browser border.
rows = []
var filterFn = typeof config.filter === 'function' ? config.filter : defaultFilterFunction[config.filter]
var filtered = !filterFn ? [] : array.filter(function (match) {
return filterFn(config.caseSensitive ? token : token.toLowerCase(), config.caseSensitive ? match : match.toLowerCase(), config)
})
rows = filtered.map(function (row) {
var divRow = document.createElement('div')
divRow.className = 'item'
// divRow.style.color = config.color;
divRow.onmouseover = onMouseOver
divRow.onmouseout = onMouseOut
divRow.onmousedown = onMouseDown
divRow.__hint = row
divRow.innerHTML = row.substring(0, token.length) + '<b>' + row.substring(token.length) + '</b>'
elem.appendChild(divRow)
return divRow
})
if (rows.length === 0) {
return // nothing to show.
}
if (rows.length === 1 && ((token.toLowerCase() === rows[0].__hint.toLowerCase() && !config.caseSensitive) ||
(token === rows[0].__hint && config.caseSensitive))) {
return // do not show the dropDown if it has only one element which matches what we have just displayed.
}
if (rows.length < 2) return
p.highlight(0)
if (distanceToTop > distanceToBottom * 3) { // Heuristic (only when the distance to the to top is 4 times more than distance to the bottom
elem.style.maxHeight = distanceToTop + 'px' // we display the dropDown on the top of the input text
elem.style.top = ''
elem.style.bottom = '100%'
} else {
elem.style.top = '100%'
elem.style.bottom = ''
elem.style.maxHeight = distanceToBottom + 'px'
}
elem.style.visibility = 'visible'
},
highlight: function (index) {
if (oldIndex !== -1 && rows[oldIndex]) {
rows[oldIndex].className = 'item'
}
rows[index].className = 'item hover'
oldIndex = index
},
move: function (step) { // moves the selection either up or down (unless it's not possible) step is either +1 or -1.
if (elem.style.visibility === 'hidden') return '' // nothing to move if there is no dropDown. (this happens if the user hits escape and then down or up)
if (ix + step === -1 || ix + step === rows.length) return rows[ix].__hint // NO CIRCULAR SCROLLING.
ix += step
p.highlight(ix)
return rows[ix].__hint// txtShadow.value = uRows[uIndex].__hint ;
},
onmouseselection: function () { } // it will be overwritten.
}
return p
}
function setEndOfContenteditable (contentEditableElement) {
var range, selection
if (document.createRange) {
// Firefox, Chrome, Opera, Safari, IE 9+
range = document.createRange()// Create a range (a range is a like the selection but invisible)
range.selectNodeContents(contentEditableElement)// Select the entire contents of the element with the range
range.collapse(false)// collapse the range to the end point. false means collapse to end rather than the start
selection = window.getSelection()// get the selection object (allows you to change selection)
selection.removeAllRanges()// remove any selections already made
selection.addRange(range)// make the range you have just created the visible selection
} else if (document.selection) {
// IE 8 and lower
range = document.body.createTextRange()// Create a range (a range is a like the selection but invisible)
range.moveToElementText(contentEditableElement)// Select the entire contents of the element with the range
range.collapse(false)// collapse the range to the end point. false means collapse to end rather than the start
range.select()// Select the range (make it the visible selection
}
}
function calculateWidthForText (text) {
if (spacer === undefined) { // on first call only.
spacer = document.createElement('span')
spacer.style.visibility = 'hidden'
spacer.style.position = 'fixed'
spacer.style.outline = '0'
spacer.style.margin = '0'
spacer.style.padding = '0'
spacer.style.border = '0'
spacer.style.left = '0'
spacer.style.whiteSpace = 'pre'
spacer.style.fontSize = fontSize
spacer.style.fontFamily = fontFamily
spacer.style.fontWeight = 'normal'
document.body.appendChild(spacer)
}
// Used to encode an HTML string into a plain text.
// taken from http://stackoverflow.com/questions/1219860/javascript-jquery-html-encoding
spacer.innerHTML = String(text).replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
return spacer.getBoundingClientRect().right
}
var rs = {
onArrowDown: function () { }, // defaults to no action.
onArrowUp: function () { }, // defaults to no action.
onEnter: function () { }, // defaults to no action.
onTab: function () { }, // defaults to no action.
startFrom: 0,
options: [],
element: null,
elementHint: null,
elementStyle: null,
wrapper: wrapper, // Only to allow easy access to the HTML elements to the final user (possibly for minor customizations)
show: function (element, startPos, options) {
this.startFrom = startPos
this.wrapper.remove()
if (this.elementHint) {
this.elementHint.remove()
this.elementHint = null
}
if (fontSize === '') {
fontSize = window.getComputedStyle(element).getPropertyValue('font-size')
}
if (fontFamily === '') {
fontFamily = window.getComputedStyle(element).getPropertyValue('font-family')
}
dropDown.style.marginLeft = '0'
dropDown.style.marginTop = element.getBoundingClientRect().height + 'px'
this.options = options
if (this.element !== element) {
this.element = element
this.elementStyle = {
zIndex: this.element.style.zIndex,
position: this.element.style.position,
backgroundColor: this.element.style.backgroundColor,
borderColor: this.element.style.borderColor
}
}
this.element.style.zIndex = 3
this.element.style.position = 'relative'
this.element.style.backgroundColor = 'transparent'
this.element.style.borderColor = 'transparent'
this.elementHint = element.cloneNode()
this.elementHint.className = 'autocomplete hint'
this.elementHint.style.zIndex = 2
this.elementHint.style.position = 'absolute'
this.elementHint.onfocus = function () { this.element.focus() }.bind(this)
if (this.element.addEventListener) {
this.element.removeEventListener('keydown', keyDownHandler)
this.element.addEventListener('keydown', keyDownHandler, false)
this.element.removeEventListener('blur', onBlurHandler)
this.element.addEventListener('blur', onBlurHandler, false)
}
wrapper.appendChild(this.elementHint)
wrapper.appendChild(dropDown)
element.parentElement.appendChild(wrapper)
this.repaint(element)
},
setText: function (text) {
this.element.innerText = text
},
getText: function () {
return this.element.innerText
},
hideDropDown: function () {
this.wrapper.remove()
if (this.elementHint) {
this.elementHint.remove()
this.elementHint = null
dropDownController.hide()
this.element.style.zIndex = this.elementStyle.zIndex
this.element.style.position = this.elementStyle.position
this.element.style.backgroundColor = this.elementStyle.backgroundColor
this.element.style.borderColor = this.elementStyle.borderColor
}
},
repaint: function (element) {
var text = element.innerText
text = text.replace('\n', '')
var optionsLength = this.options.length
// breaking text in leftSide and token.
var token = text.substring(this.startFrom)
leftSide = text.substring(0, this.startFrom)
for (var i = 0; i < optionsLength; i++) {
var opt = this.options[i]
if ((!config.caseSensitive && opt.toLowerCase().indexOf(token.toLowerCase()) === 0) ||
(config.caseSensitive && opt.indexOf(token) === 0)) { // <-- how about upperCase vs. lowercase
this.elementHint.innerText = leftSide + token + opt.substring(token.length)
this.elementHint.realInnerText = leftSide + opt
break
}
}
// moving the dropDown and refreshing it.
dropDown.style.left = calculateWidthForText(leftSide) + 'px'
dropDownController.refresh(token, this.options)
this.elementHint.style.width = calculateWidthForText(this.elementHint.innerText) + 10 + 'px'
var wasDropDownHidden = (dropDown.style.visibility === 'hidden')
if (!wasDropDownHidden) { this.elementHint.style.width = calculateWidthForText(this.elementHint.innerText) + dropDown.clientWidth + 'px' }
}
}
var dropDownController = createDropDownController(dropDown, rs)
var keyDownHandler = function (e) {
// console.log("Keydown:" + e.keyCode);
e = e || window.event
var keyCode = e.keyCode
if (this.elementHint == null) return
if (keyCode === 33) { return } // page up (do nothing)
if (keyCode === 34) { return } // page down (do nothing);
if (keyCode === 27) { // escape
rs.hideDropDown()
rs.element.focus()
e.preventDefault()
e.stopPropagation()
return
}
var text = this.element.innerText
text = text.replace('\n', '')
if (config.confirmKeys.indexOf(keyCode) >= 0) { // (autocomplete triggered)
if (keyCode === 9) {
if (this.elementHint.innerText.length === 0) {
rs.onTab()
}
}
if (this.elementHint.innerText.length > 0) { // if there is a hint
if (this.element.innerText !== this.elementHint.realInnerText) {
this.element.innerText = this.elementHint.realInnerText
rs.hideDropDown()
setEndOfContenteditable(this.element)
if (keyCode === 9) {
rs.element.focus()
e.preventDefault()
e.stopPropagation()
}
}
}
return
}
if (keyCode === 13) { // enter (autocomplete triggered)
if (this.elementHint.innerText.length === 0) { // if there is a hint
rs.onEnter()
} else {
var wasDropDownHidden = (dropDown.style.visibility === 'hidden')
dropDownController.hide()
if (wasDropDownHidden) {
rs.hideDropDown()
rs.element.focus()
rs.onEnter()
return
}
this.element.innerText = this.elementHint.realInnerText
rs.hideDropDown()
setEndOfContenteditable(this.element)
e.preventDefault()
e.stopPropagation()
}
return
}
if (keyCode === 40) { // down
const token = text.substring(this.startFrom)
const m = dropDownController.move(+1)
if (m === '') { rs.onArrowDown() }
this.elementHint.innerText = leftSide + token + m.substring(token.length)
this.elementHint.realInnerText = leftSide + m
e.preventDefault()
e.stopPropagation()
return
}
if (keyCode === 38) { // up
const token = text.substring(this.startFrom)
const m = dropDownController.move(-1)
if (m === '') { rs.onArrowUp() }
this.elementHint.innerText = leftSide + token + m.substring(token.length)
this.elementHint.realInnerText = leftSide + m
e.preventDefault()
e.stopPropagation()
}
}.bind(rs)
var onBlurHandler = function (e) {
rs.hideDropDown()
// console.log("Lost focus.");
}
dropDownController.onmouseselection = function (text, rs) {
rs.element.innerText = rs.elementHint.innerText = leftSide + text
rs.hideDropDown()
window.setTimeout(function () {
rs.element.focus()
setEndOfContenteditable(rs.element)
}, 1)
}
return rs
}
module.exports = completely

View File

@ -1,7 +1,7 @@
exports.DEFAULT_MODAL_ANCHOR = document.body; exports.DEFAULT_MODAL_ANCHOR = document.body
exports.SIZE_LARGE = 10 * 1024 * 1024; // 10 MB exports.SIZE_LARGE = 10 * 1024 * 1024 // 10 MB
exports.MAX_PREVIEW_CHARACTERS = 20000; exports.MAX_PREVIEW_CHARACTERS = 20000
exports.PREVIEW_HISTORY_LIMIT = 2 * 1024 * 1024 * 1024; // 2 GB exports.PREVIEW_HISTORY_LIMIT = 2 * 1024 * 1024 * 1024 // 2 GB

View File

@ -1,4 +1,4 @@
var util = require('./util'); var util = require('./util')
/** /**
* Create an anchor element absolutely positioned in the `parent` * Create an anchor element absolutely positioned in the `parent`
@ -9,58 +9,58 @@ var util = require('./util');
* @returns {HTMLElement} * @returns {HTMLElement}
*/ */
exports.createAbsoluteAnchor = function (anchor, parent, onDestroy) { exports.createAbsoluteAnchor = function (anchor, parent, onDestroy) {
var root = getRootNode(anchor); var root = getRootNode(anchor)
var eventListeners = {}; var eventListeners = {}
var anchorRect = anchor.getBoundingClientRect(); var anchorRect = anchor.getBoundingClientRect()
var frameRect = parent.getBoundingClientRect(); var frameRect = parent.getBoundingClientRect()
var absoluteAnchor = document.createElement('div'); var absoluteAnchor = document.createElement('div')
absoluteAnchor.className = 'jsoneditor-anchor'; absoluteAnchor.className = 'jsoneditor-anchor'
absoluteAnchor.style.position = 'absolute'; absoluteAnchor.style.position = 'absolute'
absoluteAnchor.style.left = (anchorRect.left - frameRect.left) + 'px'; absoluteAnchor.style.left = (anchorRect.left - frameRect.left) + 'px'
absoluteAnchor.style.top = (anchorRect.top - frameRect.top) + 'px'; absoluteAnchor.style.top = (anchorRect.top - frameRect.top) + 'px'
absoluteAnchor.style.width = (anchorRect.width - 2) + 'px'; absoluteAnchor.style.width = (anchorRect.width - 2) + 'px'
absoluteAnchor.style.height = (anchorRect.height - 2) + 'px'; absoluteAnchor.style.height = (anchorRect.height - 2) + 'px'
absoluteAnchor.style.boxSizing = 'border-box'; absoluteAnchor.style.boxSizing = 'border-box'
parent.appendChild(absoluteAnchor); parent.appendChild(absoluteAnchor)
function destroy () { function destroy () {
// remove temporary absolutely positioned anchor // remove temporary absolutely positioned anchor
if (absoluteAnchor && absoluteAnchor.parentNode) { if (absoluteAnchor && absoluteAnchor.parentNode) {
absoluteAnchor.parentNode.removeChild(absoluteAnchor); absoluteAnchor.parentNode.removeChild(absoluteAnchor)
// remove all event listeners // remove all event listeners
// all event listeners are supposed to be attached to document. // all event listeners are supposed to be attached to document.
for (var name in eventListeners) { for (var name in eventListeners) {
if (eventListeners.hasOwnProperty(name)) { if (hasOwnProperty(eventListeners, name)) {
var fn = eventListeners[name]; var fn = eventListeners[name]
if (fn) { if (fn) {
util.removeEventListener(root, name, fn); util.removeEventListener(root, name, fn)
} }
delete eventListeners[name]; delete eventListeners[name]
} }
} }
if (typeof onDestroy === 'function') { if (typeof onDestroy === 'function') {
onDestroy(anchor); onDestroy(anchor)
} }
} }
} }
// create and attach event listeners // create and attach event listeners
var destroyIfOutside = function (event) { var destroyIfOutside = function (event) {
var target = event.target; var target = event.target
if ((target !== absoluteAnchor) && !util.isChildOf(target, absoluteAnchor)) { if ((target !== absoluteAnchor) && !util.isChildOf(target, absoluteAnchor)) {
destroy(); destroy()
} }
} }
eventListeners.mousedown = util.addEventListener(root, 'mousedown', destroyIfOutside); eventListeners.mousedown = util.addEventListener(root, 'mousedown', destroyIfOutside)
eventListeners.mousewheel = util.addEventListener(root, 'mousewheel', destroyIfOutside); eventListeners.mousewheel = util.addEventListener(root, 'mousewheel', destroyIfOutside)
// eventListeners.scroll = util.addEventListener(root, 'scroll', destroyIfOutside); // eventListeners.scroll = util.addEventListener(root, 'scroll', destroyIfOutside);
absoluteAnchor.destroy = destroy; absoluteAnchor.destroy = destroy
return absoluteAnchor return absoluteAnchor
} }
@ -70,8 +70,12 @@ exports.createAbsoluteAnchor = function (anchor, parent, onDestroy) {
* @param {HTMLElement} node node to check * @param {HTMLElement} node node to check
* @return {HTMLElement} node's rootNode or `window` if there is ShadowDOM is not supported. * @return {HTMLElement} node's rootNode or `window` if there is ShadowDOM is not supported.
*/ */
function getRootNode(node){ function getRootNode (node) {
return (typeof node.getRootNode === 'function') return (typeof node.getRootNode === 'function')
? node.getRootNode() ? node.getRootNode()
: window; : window
}
function hasOwnProperty (object, key) {
return Object.prototype.hasOwnProperty.call(object, key)
} }

View File

@ -26,4 +26,4 @@
* @author Jos de Jong, <wjosdejong@gmail.com> * @author Jos de Jong, <wjosdejong@gmail.com>
* @version @@version * @version @@version
* @date @@date * @date @@date
*/ */

View File

@ -1,8 +1,10 @@
'use strict'; 'use strict'
require('./polyfills'); /* eslint-disable no-template-curly-in-string */
var _locales = ['en', 'pt-BR', 'zh-CN', 'tr']; require('./polyfills')
var _locales = ['en', 'pt-BR', 'zh-CN', 'tr']
var _defs = { var _defs = {
en: { en: {
array: 'Array', array: 'Array',
@ -93,7 +95,7 @@ var _defs = {
modePreviewText: 'Preview', modePreviewText: 'Preview',
modePreviewTitle: 'Switch to preview mode', modePreviewTitle: 'Switch to preview mode',
examples: 'Examples', examples: 'Examples',
default: 'Default', default: 'Default'
}, },
'zh-CN': { 'zh-CN': {
array: '数组', array: '数组',
@ -184,7 +186,7 @@ var _defs = {
modePreviewText: '预览', modePreviewText: '预览',
modePreviewTitle: '切换至预览模式', modePreviewTitle: '切换至预览模式',
examples: '例子', examples: '例子',
default: '缺省', default: '缺省'
}, },
'pt-BR': { 'pt-BR': {
array: 'Lista', array: 'Lista',
@ -283,7 +285,7 @@ var _defs = {
'Campo do tipo nao é determinado através do seu valor, ' + 'Campo do tipo nao é determinado através do seu valor, ' +
'mas sempre retornara um texto.', 'mas sempre retornara um texto.',
examples: 'Exemplos', examples: 'Exemplos',
default: 'Revelia', default: 'Revelia'
}, },
tr: { tr: {
array: 'Dizin', array: 'Dizin',
@ -370,20 +372,20 @@ var _defs = {
modeViewText: 'Görünüm', modeViewText: 'Görünüm',
modeViewTitle: 'Ağaç görünümüne geç', modeViewTitle: 'Ağaç görünümüne geç',
examples: 'Örnekler', examples: 'Örnekler',
default: 'Varsayılan', default: 'Varsayılan'
} }
}; }
var _defaultLang = 'en'; var _defaultLang = 'en'
var _lang; var _lang
var userLang = typeof navigator !== 'undefined' ? var userLang = typeof navigator !== 'undefined'
navigator.language || navigator.userLanguage : ? navigator.language || navigator.userLanguage
undefined; : undefined
_lang = _locales.find(function (l) { _lang = _locales.find(function (l) {
return l === userLang; return l === userLang
}); })
if (!_lang) { if (!_lang) {
_lang = _defaultLang; _lang = _defaultLang
} }
module.exports = { module.exports = {
@ -393,41 +395,41 @@ module.exports = {
_lang: _lang, _lang: _lang,
setLanguage: function (lang) { setLanguage: function (lang) {
if (!lang) { if (!lang) {
return; return
} }
var langFound = _locales.find(function (l) { var langFound = _locales.find(function (l) {
return l === lang; return l === lang
}); })
if (langFound) { if (langFound) {
_lang = langFound; _lang = langFound
} else { } else {
console.error('Language not found'); console.error('Language not found')
} }
}, },
setLanguages: function (languages) { setLanguages: function (languages) {
if (!languages) { if (!languages) {
return; return
} }
for (var key in languages) { for (var key in languages) {
var langFound = _locales.find(function (l) { var langFound = _locales.find(function (l) {
return l === key; return l === key
}); })
if (!langFound) { if (!langFound) {
_locales.push(key); _locales.push(key)
} }
_defs[key] = Object.assign({}, _defs[_defaultLang], _defs[key], languages[key]); _defs[key] = Object.assign({}, _defs[_defaultLang], _defs[key], languages[key])
} }
}, },
translate: function (key, data, lang) { translate: function (key, data, lang) {
if (!lang) { if (!lang) {
lang = _lang; lang = _lang
} }
var text = _defs[lang][key]; var text = _defs[lang][key]
if (data) { if (data) {
for (key in data) { for (key in data) {
text = text.replace('${' + key + '}', data[key]); text = text.replace('${' + key + '}', data[key])
} }
} }
return text || key; return text || key
} }
}; }

View File

@ -1,4 +1,4 @@
'use strict'; 'use strict'
/** /**
* Convert part of a JSON object to a JSON string. * Convert part of a JSON object to a JSON string.
@ -21,26 +21,24 @@
* *
* @returns {string | undefined} Returns the string representation of the JSON object. * @returns {string | undefined} Returns the string representation of the JSON object.
*/ */
function stringifyPartial(value, space, limit) { function stringifyPartial (value, space, limit) {
var _space; // undefined by default var _space // undefined by default
if (typeof space === 'number') { if (typeof space === 'number') {
if (space > 10) { if (space > 10) {
_space = repeat(' ', 10); _space = repeat(' ', 10)
} } else if (space >= 1) {
else if (space >= 1) { _space = repeat(' ', space)
_space = repeat(' ', space);
} }
// else ignore // else ignore
} } else if (typeof space === 'string' && space !== '') {
else if (typeof space === 'string' && space !== '') { _space = space
_space = space;
} }
var output = stringifyValue(value, _space, '', limit); var output = stringifyValue(value, _space, '', limit)
return output.length > limit return output.length > limit
? (slice(output, limit) + '...') ? (slice(output, limit) + '...')
: output; : output
} }
/** /**
@ -51,27 +49,27 @@ function stringifyPartial(value, space, limit) {
* @param {number} limit * @param {number} limit
* @return {string | undefined} * @return {string | undefined}
*/ */
function stringifyValue(value, space, indent, limit) { function stringifyValue (value, space, indent, limit) {
// boolean, null, number, string, or date // boolean, null, number, string, or date
if (typeof value === 'boolean' || value instanceof Boolean || if (typeof value === 'boolean' || value instanceof Boolean ||
value === null || value === null ||
typeof value === 'number' || value instanceof Number || typeof value === 'number' || value instanceof Number ||
typeof value === 'string' || value instanceof String || typeof value === 'string' || value instanceof String ||
value instanceof Date) { value instanceof Date) {
return JSON.stringify(value); return JSON.stringify(value)
} }
// array // array
if (Array.isArray(value)) { if (Array.isArray(value)) {
return stringifyArray(value, space, indent, limit); return stringifyArray(value, space, indent, limit)
} }
// object (test lastly!) // object (test lastly!)
if (value && typeof value === 'object') { if (value && typeof value === 'object') {
return stringifyObject(value, space, indent, limit); return stringifyObject(value, space, indent, limit)
} }
return undefined; return undefined
} }
/** /**
@ -82,36 +80,35 @@ function stringifyValue(value, space, indent, limit) {
* @param {number} limit * @param {number} limit
* @return {string} * @return {string}
*/ */
function stringifyArray(array, space, indent, limit) { function stringifyArray (array, space, indent, limit) {
var childIndent = space ? (indent + space) : undefined; var childIndent = space ? (indent + space) : undefined
var str = space ? '[\n' : '['; var str = space ? '[\n' : '['
for (var i = 0; i < array.length; i++) { for (var i = 0; i < array.length; i++) {
var item = array[i]; var item = array[i]
if (space) { if (space) {
str += childIndent; str += childIndent
} }
if (typeof item !== 'undefined' && typeof item !== 'function') { if (typeof item !== 'undefined' && typeof item !== 'function') {
str += stringifyValue(item, space, childIndent, limit); str += stringifyValue(item, space, childIndent, limit)
} } else {
else {
str += 'null' str += 'null'
} }
if (i < array.length - 1) { if (i < array.length - 1) {
str += space ? ',\n' : ','; str += space ? ',\n' : ','
} }
// stop as soon as we're exceeding the limit // stop as soon as we're exceeding the limit
if (str.length > limit) { if (str.length > limit) {
return str + '...'; return str + '...'
} }
} }
str += space ? ('\n' + indent + ']') : ']'; str += space ? ('\n' + indent + ']') : ']'
return str; return str
} }
/** /**
@ -122,41 +119,40 @@ function stringifyArray(array, space, indent, limit) {
* @param {number} limit * @param {number} limit
* @return {string} * @return {string}
*/ */
function stringifyObject(object, space, indent, limit) { function stringifyObject (object, space, indent, limit) {
var childIndent = space ? (indent + space) : undefined; var childIndent = space ? (indent + space) : undefined
var first = true; var first = true
var str = space ? '{\n' : '{'; var str = space ? '{\n' : '{'
if (typeof object.toJSON === 'function') { if (typeof object.toJSON === 'function') {
return stringifyValue(object.toJSON(), space, indent, limit); return stringifyValue(object.toJSON(), space, indent, limit)
} }
for (var key in object) { for (var key in object) {
if (object.hasOwnProperty(key)) { if (hasOwnProperty(object, key)) {
var value = object[key]; var value = object[key]
if (first) { if (first) {
first = false; first = false
} } else {
else { str += space ? ',\n' : ','
str += space ? ',\n' : ',';
} }
str += space str += space
? (childIndent + '"' + key + '": ') ? (childIndent + '"' + key + '": ')
: ('"' + key + '":'); : ('"' + key + '":')
str += stringifyValue(value, space, childIndent, limit); str += stringifyValue(value, space, childIndent, limit)
// stop as soon as we're exceeding the limit // stop as soon as we're exceeding the limit
if (str.length > limit) { if (str.length > limit) {
return str + '...'; return str + '...'
} }
} }
} }
str += space ? ('\n' + indent + '}') : '}'; str += space ? ('\n' + indent + '}') : '}'
return str; return str
} }
/** /**
@ -167,11 +163,11 @@ function stringifyObject(object, space, indent, limit) {
* @return {string} * @return {string}
*/ */
function repeat (text, times) { function repeat (text, times) {
var res = ''; var res = ''
while (times-- > 0) { while (times-- > 0) {
res += text; res += text
} }
return res; return res
} }
/** /**
@ -180,10 +176,10 @@ function repeat (text, times) {
* @param {number} [limit] * @param {number} [limit]
* @return {string} * @return {string}
*/ */
function slice(text, limit) { function slice (text, limit) {
return typeof limit === 'number' return typeof limit === 'number'
? text.slice(0, limit) ? text.slice(0, limit)
: text; : text
} }
/** /**
@ -196,5 +192,9 @@ function containsArray (jsonText) {
return /^\s*\[/.test(jsonText) return /^\s*\[/.test(jsonText)
} }
exports.stringifyPartial = stringifyPartial; function hasOwnProperty (object, key) {
exports.containsArray = containsArray; return Object.prototype.hasOwnProperty.call(object, key)
}
exports.stringifyPartial = stringifyPartial
exports.containsArray = containsArray

View File

@ -3,42 +3,33 @@ if (typeof Element !== 'undefined') {
// Polyfill for array remove // Polyfill for array remove
(function () { (function () {
function polyfill (item) { function polyfill (item) {
if (item.hasOwnProperty('remove')) { if ('remove' in item) {
return; return
} }
Object.defineProperty(item, 'remove', { Object.defineProperty(item, 'remove', {
configurable: true, configurable: true,
enumerable: true, enumerable: true,
writable: true, writable: true,
value: function remove() { value: function remove () {
if (this.parentNode != null) if (this.parentNode !== undefined) { this.parentNode.removeChild(this) }
this.parentNode.removeChild(this);
} }
}); })
} }
if (typeof Element !== 'undefined') { polyfill(Element.prototype); } if (typeof window.Element !== 'undefined') { polyfill(window.Element.prototype) }
if (typeof CharacterData !== 'undefined') { polyfill(CharacterData.prototype); } if (typeof window.CharacterData !== 'undefined') { polyfill(window.CharacterData.prototype) }
if (typeof DocumentType !== 'undefined') { polyfill(DocumentType.prototype); } if (typeof window.DocumentType !== 'undefined') { polyfill(window.DocumentType.prototype) }
})(); })()
}
// Polyfill for startsWith
if (!String.prototype.startsWith) {
String.prototype.startsWith = function (searchString, position) {
position = position || 0;
return this.substr(position, searchString.length) === searchString;
};
} }
// Polyfill for Array.find // Polyfill for Array.find
if (!Array.prototype.find) { if (!Array.prototype.find) {
Array.prototype.find = function(callback) { // eslint-disable-next-line no-extend-native
Array.prototype.find = function (callback) {
for (var i = 0; i < this.length; i++) { for (var i = 0; i < this.length; i++) {
var element = this[i]; var element = this[i]
if ( callback.call(this, element, i, this) ) { if (callback.call(this, element, i, this)) {
return element; return element
} }
} }
} }
@ -46,7 +37,8 @@ if (!Array.prototype.find) {
// Polyfill for String.trim // Polyfill for String.trim
if (!String.prototype.trim) { if (!String.prototype.trim) {
// eslint-disable-next-line no-extend-native
String.prototype.trim = function () { String.prototype.trim = function () {
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')
}; }
} }

View File

@ -1,21 +1,21 @@
'use strict'; 'use strict'
var jmespath = require('jmespath'); var jmespath = require('jmespath')
var translate = require('./i18n').translate; var translate = require('./i18n').translate
var ModeSwitcher = require('./ModeSwitcher'); var ModeSwitcher = require('./ModeSwitcher')
var ErrorTable = require('./ErrorTable'); var ErrorTable = require('./ErrorTable')
var textmode = require('./textmode')[0].mixin; var textmode = require('./textmode')[0].mixin
var showSortModal = require('./showSortModal'); var showSortModal = require('./showSortModal')
var showTransformModal = require('./showTransformModal'); var showTransformModal = require('./showTransformModal')
var MAX_PREVIEW_CHARACTERS = require('./constants').MAX_PREVIEW_CHARACTERS; var MAX_PREVIEW_CHARACTERS = require('./constants').MAX_PREVIEW_CHARACTERS
var DEFAULT_MODAL_ANCHOR = require('./constants').DEFAULT_MODAL_ANCHOR; var DEFAULT_MODAL_ANCHOR = require('./constants').DEFAULT_MODAL_ANCHOR
var SIZE_LARGE = require('./constants').SIZE_LARGE; var SIZE_LARGE = require('./constants').SIZE_LARGE
var PREVIEW_HISTORY_LIMIT = require('./constants').PREVIEW_HISTORY_LIMIT; var PREVIEW_HISTORY_LIMIT = require('./constants').PREVIEW_HISTORY_LIMIT
var util = require('./util'); var util = require('./util')
var History = require('./History'); var History = require('./History')
// create a mixin with the functions for text mode // create a mixin with the functions for text mode
var previewmode = {}; var previewmode = {}
/** /**
* Create a JSON document preview, suitable for processing of large documents * Create a JSON document preview, suitable for processing of large documents
@ -25,274 +25,269 @@ var previewmode = {};
*/ */
previewmode.create = function (container, options) { previewmode.create = function (container, options) {
// read options // read options
options = options || {}; options = options || {}
if (typeof options.statusBar === 'undefined') { if (typeof options.statusBar === 'undefined') {
options.statusBar = true; options.statusBar = true
} }
// setting default for previewmode // setting default for previewmode
options.mainMenuBar = options.mainMenuBar !== false; options.mainMenuBar = options.mainMenuBar !== false
options.enableSort = options.enableSort !== false; options.enableSort = options.enableSort !== false
options.enableTransform = options.enableTransform !== false; options.enableTransform = options.enableTransform !== false
this.options = options; this.options = options
// indentation // indentation
if (options.indentation) { if (options.indentation) {
this.indentation = Number(options.indentation); this.indentation = Number(options.indentation)
} } else {
else { this.indentation = 2 // number of spaces
this.indentation = 2; // number of spaces
} }
// determine mode // determine mode
this.mode = 'preview'; this.mode = 'preview'
var me = this; var me = this
this.container = container; this.container = container
this.dom = {}; this.dom = {}
this.json = undefined; this.json = undefined
this.text = ''; this.text = ''
// TODO: JSON Schema support // TODO: JSON Schema support
// create a debounced validate function // create a debounced validate function
this._debouncedValidate = util.debounce(this.validate.bind(this), this.DEBOUNCE_INTERVAL); this._debouncedValidate = util.debounce(this.validate.bind(this), this.DEBOUNCE_INTERVAL)
this.width = container.clientWidth; this.width = container.clientWidth
this.height = container.clientHeight; this.height = container.clientHeight
this.frame = document.createElement('div'); this.frame = document.createElement('div')
this.frame.className = 'jsoneditor jsoneditor-mode-preview'; this.frame.className = 'jsoneditor jsoneditor-mode-preview'
this.frame.onclick = function (event) { this.frame.onclick = function (event) {
// prevent default submit action when the editor is located inside a form // prevent default submit action when the editor is located inside a form
event.preventDefault(); event.preventDefault()
}; }
this.content = document.createElement('div'); this.content = document.createElement('div')
this.content.className = 'jsoneditor-outer'; this.content.className = 'jsoneditor-outer'
this.dom.busy = document.createElement('div') this.dom.busy = document.createElement('div')
this.dom.busy.className = 'jsoneditor-busy'; this.dom.busy.className = 'jsoneditor-busy'
this.dom.busyContent = document.createElement('span'); this.dom.busyContent = document.createElement('span')
this.dom.busyContent.innerHTML = 'busy...'; this.dom.busyContent.innerHTML = 'busy...'
this.dom.busy.appendChild(this.dom.busyContent); this.dom.busy.appendChild(this.dom.busyContent)
this.content.appendChild(this.dom.busy); this.content.appendChild(this.dom.busy)
this.dom.previewContent = document.createElement('pre'); this.dom.previewContent = document.createElement('pre')
this.dom.previewContent.className = 'jsoneditor-preview'; this.dom.previewContent.className = 'jsoneditor-preview'
this.dom.previewText = document.createTextNode(''); this.dom.previewText = document.createTextNode('')
this.dom.previewContent.appendChild(this.dom.previewText); this.dom.previewContent.appendChild(this.dom.previewText)
this.content.appendChild(this.dom.previewContent); this.content.appendChild(this.dom.previewContent)
if (this.options.mainMenuBar) { if (this.options.mainMenuBar) {
util.addClassName(this.content, 'has-main-menu-bar'); util.addClassName(this.content, 'has-main-menu-bar')
// create menu // create menu
this.menu = document.createElement('div'); this.menu = document.createElement('div')
this.menu.className = 'jsoneditor-menu'; this.menu.className = 'jsoneditor-menu'
this.frame.appendChild(this.menu); this.frame.appendChild(this.menu)
// create format button // create format button
var buttonFormat = document.createElement('button'); var buttonFormat = document.createElement('button')
buttonFormat.type = 'button'; buttonFormat.type = 'button'
buttonFormat.className = 'jsoneditor-format'; buttonFormat.className = 'jsoneditor-format'
buttonFormat.title = 'Format JSON data, with proper indentation and line feeds (Ctrl+\\)'; buttonFormat.title = 'Format JSON data, with proper indentation and line feeds (Ctrl+\\)'
this.menu.appendChild(buttonFormat); this.menu.appendChild(buttonFormat)
buttonFormat.onclick = function handleFormat() { buttonFormat.onclick = function handleFormat () {
me.executeWithBusyMessage(function () { me.executeWithBusyMessage(function () {
try { try {
me.format(); me.format()
} catch (err) {
me._onError(err)
} }
catch (err) { }, 'formatting...')
me._onError(err); }
}
}, 'formatting...');
};
// create compact button // create compact button
var buttonCompact = document.createElement('button'); var buttonCompact = document.createElement('button')
buttonCompact.type = 'button'; buttonCompact.type = 'button'
buttonCompact.className = 'jsoneditor-compact'; buttonCompact.className = 'jsoneditor-compact'
buttonCompact.title = 'Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)'; buttonCompact.title = 'Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)'
this.menu.appendChild(buttonCompact); this.menu.appendChild(buttonCompact)
buttonCompact.onclick = function handleCompact() { buttonCompact.onclick = function handleCompact () {
me.executeWithBusyMessage(function () { me.executeWithBusyMessage(function () {
try { try {
me.compact(); me.compact()
} catch (err) {
me._onError(err)
} }
catch (err) { }, 'compacting...')
me._onError(err); }
}
}, 'compacting...');
};
// create sort button // create sort button
if (this.options.enableSort) { if (this.options.enableSort) {
var sort = document.createElement('button'); var sort = document.createElement('button')
sort.type = 'button'; sort.type = 'button'
sort.className = 'jsoneditor-sort'; sort.className = 'jsoneditor-sort'
sort.title = translate('sortTitleShort'); sort.title = translate('sortTitleShort')
sort.onclick = function () { sort.onclick = function () {
me._showSortModal(); me._showSortModal()
}; }
this.menu.appendChild(sort); this.menu.appendChild(sort)
} }
// create transform button // create transform button
if (this.options.enableTransform) { if (this.options.enableTransform) {
var transform = document.createElement('button'); var transform = document.createElement('button')
transform.type = 'button'; transform.type = 'button'
transform.title = translate('transformTitleShort'); transform.title = translate('transformTitleShort')
transform.className = 'jsoneditor-transform'; transform.className = 'jsoneditor-transform'
transform.onclick = function () { transform.onclick = function () {
me._showTransformModal(); me._showTransformModal()
}; }
this.dom.transform = transform; this.dom.transform = transform
this.menu.appendChild(transform); this.menu.appendChild(transform)
} }
// create repair button // create repair button
var buttonRepair = document.createElement('button'); var buttonRepair = document.createElement('button')
buttonRepair.type = 'button'; buttonRepair.type = 'button'
buttonRepair.className = 'jsoneditor-repair'; buttonRepair.className = 'jsoneditor-repair'
buttonRepair.title = 'Repair JSON: fix quotes and escape characters, remove comments and JSONP notation, turn JavaScript objects into JSON.'; buttonRepair.title = 'Repair JSON: fix quotes and escape characters, remove comments and JSONP notation, turn JavaScript objects into JSON.'
this.menu.appendChild(buttonRepair); this.menu.appendChild(buttonRepair)
buttonRepair.onclick = function () { buttonRepair.onclick = function () {
if (me.json === undefined) { // only repair if we don't have valid JSON if (me.json === undefined) { // only repair if we don't have valid JSON
me.executeWithBusyMessage(function () { me.executeWithBusyMessage(function () {
try { try {
me.repair(); me.repair()
} catch (err) {
me._onError(err)
} }
catch (err) { }, 'repairing...')
me._onError(err);
}
}, 'repairing...');
} }
}; }
// create history and undo/redo buttons // create history and undo/redo buttons
if (this.options.history !== false) { // default option value is true if (this.options.history !== false) { // default option value is true
var onHistoryChange = function () { var onHistoryChange = function () {
me.dom.undo.disabled = !me.history.canUndo(); me.dom.undo.disabled = !me.history.canUndo()
me.dom.redo.disabled = !me.history.canRedo(); me.dom.redo.disabled = !me.history.canRedo()
};
var calculateItemSize = function (item) {
return item.text.length * 2; // times two to account for the json object
} }
this.history = new History(onHistoryChange, calculateItemSize, PREVIEW_HISTORY_LIMIT); var calculateItemSize = function (item) {
return item.text.length * 2 // times two to account for the json object
}
this.history = new History(onHistoryChange, calculateItemSize, PREVIEW_HISTORY_LIMIT)
// create undo button // create undo button
var undo = document.createElement('button'); var undo = document.createElement('button')
undo.type = 'button'; undo.type = 'button'
undo.className = 'jsoneditor-undo jsoneditor-separator'; undo.className = 'jsoneditor-undo jsoneditor-separator'
undo.title = translate('undo'); undo.title = translate('undo')
undo.onclick = function () { undo.onclick = function () {
var action = me.history.undo(); var action = me.history.undo()
if (action) { if (action) {
me._applyHistory(action); me._applyHistory(action)
} }
}; }
this.menu.appendChild(undo); this.menu.appendChild(undo)
this.dom.undo = undo; this.dom.undo = undo
// create redo button // create redo button
var redo = document.createElement('button'); var redo = document.createElement('button')
redo.type = 'button'; redo.type = 'button'
redo.className = 'jsoneditor-redo'; redo.className = 'jsoneditor-redo'
redo.title = translate('redo'); redo.title = translate('redo')
redo.onclick = function () { redo.onclick = function () {
var action = me.history.redo(); var action = me.history.redo()
if (action) { if (action) {
me._applyHistory(action); me._applyHistory(action)
} }
}; }
this.menu.appendChild(redo); this.menu.appendChild(redo)
this.dom.redo = redo; this.dom.redo = redo
// force enabling/disabling the undo/redo button // force enabling/disabling the undo/redo button
this.history.onChange(); this.history.onChange()
} }
// create mode box // create mode box
if (this.options && this.options.modes && this.options.modes.length) { if (this.options && this.options.modes && this.options.modes.length) {
this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch(mode) { this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch (mode) {
// switch mode and restore focus // switch mode and restore focus
me.setMode(mode); me.setMode(mode)
me.modeSwitcher.focus(); me.modeSwitcher.focus()
}); })
} }
} }
this.errorTable = new ErrorTable({ this.errorTable = new ErrorTable({
errorTableVisible: true, errorTableVisible: true,
onToggleVisibility: function () { onToggleVisibility: function () {
me.validate(); me.validate()
}, },
onFocusLine: null, onFocusLine: null,
onChangeHeight: function (height) { onChangeHeight: function (height) {
// TODO: change CSS to using flex box, remove setting height using JavaScript // TODO: change CSS to using flex box, remove setting height using JavaScript
var statusBarHeight = me.dom.statusBar ? me.dom.statusBar.clientHeight : 0; var statusBarHeight = me.dom.statusBar ? me.dom.statusBar.clientHeight : 0
var totalHeight = height + statusBarHeight + 1; var totalHeight = height + statusBarHeight + 1
me.content.style.marginBottom = (-totalHeight) + 'px'; me.content.style.marginBottom = (-totalHeight) + 'px'
me.content.style.paddingBottom = totalHeight + 'px'; me.content.style.paddingBottom = totalHeight + 'px'
} }
}); })
this.frame.appendChild(this.content); this.frame.appendChild(this.content)
this.frame.appendChild(this.errorTable.getErrorTable()); this.frame.appendChild(this.errorTable.getErrorTable())
this.container.appendChild(this.frame); this.container.appendChild(this.frame)
if (options.statusBar) { if (options.statusBar) {
util.addClassName(this.content, 'has-status-bar'); util.addClassName(this.content, 'has-status-bar')
var statusBar = document.createElement('div'); var statusBar = document.createElement('div')
this.dom.statusBar = statusBar; this.dom.statusBar = statusBar
statusBar.className = 'jsoneditor-statusbar'; statusBar.className = 'jsoneditor-statusbar'
this.frame.appendChild(statusBar); this.frame.appendChild(statusBar)
this.dom.fileSizeInfo = document.createElement('span'); this.dom.fileSizeInfo = document.createElement('span')
this.dom.fileSizeInfo.className = 'jsoneditor-size-info'; this.dom.fileSizeInfo.className = 'jsoneditor-size-info'
this.dom.fileSizeInfo.innerText = ''; this.dom.fileSizeInfo.innerText = ''
statusBar.appendChild(this.dom.fileSizeInfo); statusBar.appendChild(this.dom.fileSizeInfo)
this.dom.arrayInfo = document.createElement('span'); this.dom.arrayInfo = document.createElement('span')
this.dom.arrayInfo.className = 'jsoneditor-size-info'; this.dom.arrayInfo.className = 'jsoneditor-size-info'
this.dom.arrayInfo.innerText = ''; this.dom.arrayInfo.innerText = ''
statusBar.appendChild(this.dom.arrayInfo); statusBar.appendChild(this.dom.arrayInfo)
statusBar.appendChild(this.errorTable.getErrorCounter()); statusBar.appendChild(this.errorTable.getErrorCounter())
statusBar.appendChild(this.errorTable.getWarningIcon()); statusBar.appendChild(this.errorTable.getWarningIcon())
statusBar.appendChild(this.errorTable.getErrorIcon()); statusBar.appendChild(this.errorTable.getErrorIcon())
} }
this._renderPreview(); this._renderPreview()
this.setSchema(this.options.schema, this.options.schemaRefs); this.setSchema(this.options.schema, this.options.schemaRefs)
}; }
previewmode._renderPreview = function () { previewmode._renderPreview = function () {
var text = this.getText(); var text = this.getText()
this.dom.previewText.nodeValue = util.limitCharacters(text, MAX_PREVIEW_CHARACTERS); this.dom.previewText.nodeValue = util.limitCharacters(text, MAX_PREVIEW_CHARACTERS)
if (this.dom.fileSizeInfo) { if (this.dom.fileSizeInfo) {
this.dom.fileSizeInfo.innerText = 'Size: ' + util.formatSize(text.length); this.dom.fileSizeInfo.innerText = 'Size: ' + util.formatSize(text.length)
} }
if (this.dom.arrayInfo) { if (this.dom.arrayInfo) {
if (Array.isArray(this.json)) { if (Array.isArray(this.json)) {
this.dom.arrayInfo.innerText = ('Array: ' + this.json.length + ' items'); this.dom.arrayInfo.innerText = ('Array: ' + this.json.length + ' items')
} } else {
else { this.dom.arrayInfo.innerText = ''
this.dom.arrayInfo.innerText = '';
} }
} }
}; }
/** /**
* Handle a change: * Handle a change:
@ -302,73 +297,70 @@ previewmode._renderPreview = function () {
*/ */
previewmode._onChange = function () { previewmode._onChange = function () {
// validate JSON schema (if configured) // validate JSON schema (if configured)
this._debouncedValidate(); this._debouncedValidate()
// trigger the onChange callback // trigger the onChange callback
if (this.options.onChange) { if (this.options.onChange) {
try { try {
this.options.onChange(); this.options.onChange()
} } catch (err) {
catch (err) { console.error('Error in onChange callback: ', err)
console.error('Error in onChange callback: ', err);
} }
} }
// trigger the onChangeJSON callback // trigger the onChangeJSON callback
if (this.options.onChangeJSON) { if (this.options.onChangeJSON) {
try { try {
this.options.onChangeJSON(this.get()); this.options.onChangeJSON(this.get())
} } catch (err) {
catch (err) { console.error('Error in onChangeJSON callback: ', err)
console.error('Error in onChangeJSON callback: ', err);
} }
} }
// trigger the onChangeText callback // trigger the onChangeText callback
if (this.options.onChangeText) { if (this.options.onChangeText) {
try { try {
this.options.onChangeText(this.getText()); this.options.onChangeText(this.getText())
} } catch (err) {
catch (err) { console.error('Error in onChangeText callback: ', err)
console.error('Error in onChangeText callback: ', err);
} }
} }
}; }
/** /**
* Open a sort modal * Open a sort modal
* @private * @private
*/ */
previewmode._showSortModal = function () { previewmode._showSortModal = function () {
var me = this; var me = this
function onSort (json, sortedBy) { function onSort (json, sortedBy) {
if (Array.isArray(json)) { if (Array.isArray(json)) {
var sortedArray = util.sort(json, sortedBy.path, sortedBy.direction); var sortedArray = util.sort(json, sortedBy.path, sortedBy.direction)
me.sortedBy = sortedBy me.sortedBy = sortedBy
me._setAndFireOnChange(sortedArray); me._setAndFireOnChange(sortedArray)
} }
if (util.isObject(json)) { if (util.isObject(json)) {
var sortedObject = util.sortObjectKeys(json, sortedBy.direction); var sortedObject = util.sortObjectKeys(json, sortedBy.direction)
me.sortedBy = sortedBy; me.sortedBy = sortedBy
me._setAndFireOnChange(sortedObject); me._setAndFireOnChange(sortedObject)
} }
} }
this.executeWithBusyMessage(function () { this.executeWithBusyMessage(function () {
var container = me.options.modalAnchor || DEFAULT_MODAL_ANCHOR; var container = me.options.modalAnchor || DEFAULT_MODAL_ANCHOR
var json = me.get(); var json = me.get()
me._renderPreview(); // update array count me._renderPreview() // update array count
showSortModal(container, json, function (sortedBy) { showSortModal(container, json, function (sortedBy) {
me.executeWithBusyMessage(function () { me.executeWithBusyMessage(function () {
onSort(json, sortedBy); onSort(json, sortedBy)
}, 'sorting...'); }, 'sorting...')
}, me.sortedBy) }, me.sortedBy)
}, 'parsing...'); }, 'parsing...')
} }
/** /**
@ -376,17 +368,17 @@ previewmode._showSortModal = function () {
* @private * @private
*/ */
previewmode._showTransformModal = function () { previewmode._showTransformModal = function () {
var me = this; var me = this
this.executeWithBusyMessage(function () { this.executeWithBusyMessage(function () {
var anchor = me.options.modalAnchor || DEFAULT_MODAL_ANCHOR; var anchor = me.options.modalAnchor || DEFAULT_MODAL_ANCHOR
var json = me.get(); var json = me.get()
me._renderPreview(); // update array count me._renderPreview() // update array count
showTransformModal(anchor, json, function (query) { showTransformModal(anchor, json, function (query) {
me.executeWithBusyMessage(function () { me.executeWithBusyMessage(function () {
var updatedJson = jmespath.search(json, query); var updatedJson = jmespath.search(json, query)
me._setAndFireOnChange(updatedJson); me._setAndFireOnChange(updatedJson)
}, 'transforming...') }, 'transforming...')
}) })
}, 'parsing...') }, 'parsing...')
@ -397,51 +389,51 @@ previewmode._showTransformModal = function () {
*/ */
previewmode.destroy = function () { previewmode.destroy = 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)
} }
if (this.modeSwitcher) { if (this.modeSwitcher) {
this.modeSwitcher.destroy(); this.modeSwitcher.destroy()
this.modeSwitcher = null; this.modeSwitcher = null
} }
this._debouncedValidate = null; this._debouncedValidate = null
this.history.clear(); this.history.clear()
this.history = null; this.history = null
}; }
/** /**
* Compact the code in the text editor * Compact the code in the text editor
*/ */
previewmode.compact = function () { previewmode.compact = function () {
var json = this.get(); var json = this.get()
var text = JSON.stringify(json); var text = JSON.stringify(json)
// we know that in this case the json is still the same, so we pass json too // we know that in this case the json is still the same, so we pass json too
this._setTextAndFireOnChange(text, json); this._setTextAndFireOnChange(text, json)
}; }
/** /**
* Format the code in the text editor * Format the code in the text editor
*/ */
previewmode.format = function () { previewmode.format = function () {
var json = this.get(); var json = this.get()
var text = JSON.stringify(json, null, this.indentation); var text = JSON.stringify(json, null, this.indentation)
// we know that in this case the json is still the same, so we pass json too // we know that in this case the json is still the same, so we pass json too
this._setTextAndFireOnChange(text, json); this._setTextAndFireOnChange(text, json)
}; }
/** /**
* Repair the code in the text editor * Repair the code in the text editor
*/ */
previewmode.repair = function () { previewmode.repair = function () {
var text = this.getText(); var text = this.getText()
var repairedText = util.repair(text); var repairedText = util.repair(text)
this._setTextAndFireOnChange(repairedText); this._setTextAndFireOnChange(repairedText)
}; }
/** /**
* Set focus to the editor * Set focus to the editor
@ -449,104 +441,104 @@ previewmode.repair = function () {
previewmode.focus = function () { previewmode.focus = function () {
// we don't really have a place to focus, // we don't really have a place to focus,
// let's focus on the transform button // let's focus on the transform button
this.dom.transform.focus(); this.dom.transform.focus()
}; }
/** /**
* Set json data in the editor * Set json data in the editor
* @param {*} json * @param {*} json
*/ */
previewmode.set = function(json) { previewmode.set = function (json) {
if (this.history) { if (this.history) {
this.history.clear(); this.history.clear()
} }
this._set(json); this._set(json)
}; }
/** /**
* Update data. Same as calling `set` in text/code mode. * Update data. Same as calling `set` in text/code mode.
* @param {*} json * @param {*} json
*/ */
previewmode.update = function(json) { previewmode.update = function (json) {
this._set(json); this._set(json)
}; }
/** /**
* Set json data * Set json data
* @param {*} json * @param {*} json
*/ */
previewmode._set = function(json) { previewmode._set = function (json) {
this.text = undefined; this.text = undefined
this.json = json; this.json = json
this._renderPreview(); this._renderPreview()
this._pushHistory(); this._pushHistory()
// validate JSON schema // validate JSON schema
this._debouncedValidate(); this._debouncedValidate()
}; }
previewmode._setAndFireOnChange = function (json) { previewmode._setAndFireOnChange = function (json) {
this._set(json); this._set(json)
this._onChange(); this._onChange()
} }
/** /**
* Get json data * Get json data
* @return {*} json * @return {*} json
*/ */
previewmode.get = function() { previewmode.get = function () {
if (this.json === undefined) { if (this.json === undefined) {
var text = this.getText(); var text = this.getText()
this.json = util.parse(text); // this can throw an error this.json = util.parse(text) // this can throw an error
} }
return this.json; return this.json
}; }
/** /**
* Get the text contents of the editor * Get the text contents of the editor
* @return {String} jsonText * @return {String} jsonText
*/ */
previewmode.getText = function() { previewmode.getText = function () {
if (this.text === undefined) { if (this.text === undefined) {
this.text = JSON.stringify(this.json, null, this.indentation); this.text = JSON.stringify(this.json, null, this.indentation)
if (this.options.escapeUnicode === true) { if (this.options.escapeUnicode === true) {
this.text = util.escapeUnicodeChars(this.text); this.text = util.escapeUnicodeChars(this.text)
} }
} }
return this.text; return this.text
}; }
/** /**
* Set the text contents of the editor * Set the text contents of the editor
* @param {String} jsonText * @param {String} jsonText
*/ */
previewmode.setText = function(jsonText) { previewmode.setText = function (jsonText) {
if (this.history) { if (this.history) {
this.history.clear(); this.history.clear()
} }
this._setText(jsonText); this._setText(jsonText)
}; }
/** /**
* Update the text contents * Update the text contents
* @param {string} jsonText * @param {string} jsonText
*/ */
previewmode.updateText = function(jsonText) { previewmode.updateText = function (jsonText) {
// don't update if there are no changes // don't update if there are no changes
if (this.getText() === jsonText) { if (this.getText() === jsonText) {
return; return
} }
this._setText(jsonText); this._setText(jsonText)
}; }
/** /**
* Set the text contents of the editor * Set the text contents of the editor
@ -554,37 +546,34 @@ previewmode.updateText = function(jsonText) {
* @param {*} [json] Optional JSON instance of the text * @param {*} [json] Optional JSON instance of the text
* @private * @private
*/ */
previewmode._setText = function(jsonText, json) { previewmode._setText = function (jsonText, json) {
if (this.options.escapeUnicode === true) { if (this.options.escapeUnicode === true) {
this.text = util.escapeUnicodeChars(jsonText); this.text = util.escapeUnicodeChars(jsonText)
} else {
this.text = jsonText
} }
else { this.json = json
this.text = jsonText;
}
this.json = json;
this._renderPreview(); this._renderPreview()
if (this.json === undefined) { if (this.json === undefined) {
var me = this; var me = this
this.executeWithBusyMessage(function () { this.executeWithBusyMessage(function () {
try { try {
// force parsing the json now, else it will be done in validate without feedback // force parsing the json now, else it will be done in validate without feedback
me.json = me.get(); me.json = me.get()
me._renderPreview(); me._renderPreview()
me._pushHistory(); me._pushHistory()
} } catch (err) {
catch (err) {
// no need to throw an error, validation will show an error // no need to throw an error, validation will show an error
} }
}, 'parsing...'); }, 'parsing...')
} } else {
else { this._pushHistory()
this._pushHistory();
} }
this._debouncedValidate(); this._debouncedValidate()
}; }
/** /**
* Set text and fire onChange callback * Set text and fire onChange callback
@ -593,8 +582,8 @@ previewmode._setText = function(jsonText, json) {
* @private * @private
*/ */
previewmode._setTextAndFireOnChange = function (jsonText, json) { previewmode._setTextAndFireOnChange = function (jsonText, json) {
this._setText(jsonText, json); this._setText(jsonText, json)
this._onChange(); this._onChange()
} }
/** /**
@ -603,13 +592,13 @@ previewmode._setTextAndFireOnChange = function (jsonText, json) {
* @private * @private
*/ */
previewmode._applyHistory = function (action) { previewmode._applyHistory = function (action) {
this.json = action.json; this.json = action.json
this.text = action.text; this.text = action.text
this._renderPreview(); this._renderPreview()
this._debouncedValidate(); this._debouncedValidate()
}; }
/** /**
* Push the current state to history * Push the current state to history
@ -617,15 +606,15 @@ previewmode._applyHistory = function (action) {
*/ */
previewmode._pushHistory = function () { previewmode._pushHistory = function () {
if (!this.history) { if (!this.history) {
return; return
} }
var action = { var action = {
text: this.text, text: this.text,
json: this.json json: this.json
}; }
this.history.add(action); this.history.add(action)
} }
/** /**
@ -635,23 +624,22 @@ previewmode._pushHistory = function () {
* @param {string} message * @param {string} message
*/ */
previewmode.executeWithBusyMessage = function (fn, message) { previewmode.executeWithBusyMessage = function (fn, message) {
var size = this.getText().length; var size = this.getText().length
if (size > SIZE_LARGE) { if (size > SIZE_LARGE) {
var me = this; var me = this
util.addClassName(me.frame, 'busy'); util.addClassName(me.frame, 'busy')
me.dom.busyContent.innerText = message; me.dom.busyContent.innerText = message
setTimeout(function () { setTimeout(function () {
fn(); fn()
util.removeClassName(me.frame, 'busy'); util.removeClassName(me.frame, 'busy')
me.dom.busyContent.innerText = ''; me.dom.busyContent.innerText = ''
}, 100); }, 100)
} else {
fn()
} }
else { }
fn();
}
};
// TODO: refactor into composable functions instead of this shaky mixin-like structure // TODO: refactor into composable functions instead of this shaky mixin-like structure
previewmode.validate = textmode.validate previewmode.validate = textmode.validate
@ -664,4 +652,4 @@ module.exports = [
mixin: previewmode, mixin: previewmode,
data: 'json' data: 'json'
} }
]; ]

View File

@ -1,12 +1,12 @@
'use strict'; 'use strict'
var translate = require('./i18n').translate; var translate = require('./i18n').translate
/** /**
* A factory function to create an ShowMoreNode, which depends on a Node * A factory function to create an ShowMoreNode, which depends on a Node
* @param {function} Node * @param {function} Node
*/ */
function showMoreNodeFactory(Node) { function showMoreNodeFactory (Node) {
/** /**
* @constructor ShowMoreNode * @constructor ShowMoreNode
* @extends Node * @extends Node
@ -17,12 +17,12 @@ function showMoreNodeFactory(Node) {
*/ */
function ShowMoreNode (editor, parent) { function ShowMoreNode (editor, parent) {
/** @type {TreeEditor} */ /** @type {TreeEditor} */
this.editor = editor; this.editor = editor
this.parent = parent; this.parent = parent
this.dom = {}; this.dom = {}
} }
ShowMoreNode.prototype = new Node(); ShowMoreNode.prototype = new Node()
/** /**
* Return a table row with an append button. * Return a table row with an append button.
@ -30,105 +30,104 @@ function showMoreNodeFactory(Node) {
*/ */
ShowMoreNode.prototype.getDom = function () { ShowMoreNode.prototype.getDom = function () {
if (this.dom.tr) { if (this.dom.tr) {
return this.dom.tr; return this.dom.tr
} }
this._updateEditability(); this._updateEditability()
// display "show more" // display "show more"
if (!this.dom.tr) { if (!this.dom.tr) {
var me = this; var me = this
var parent = this.parent; var parent = this.parent
var showMoreButton = document.createElement('a'); var showMoreButton = document.createElement('a')
showMoreButton.appendChild(document.createTextNode(translate('showMore'))); showMoreButton.appendChild(document.createTextNode(translate('showMore')))
showMoreButton.href = '#'; showMoreButton.href = '#'
showMoreButton.onclick = function (event) { showMoreButton.onclick = function (event) {
// TODO: use callback instead of accessing a method of the parent // TODO: use callback instead of accessing a method of the parent
parent.visibleChilds = Math.floor(parent.visibleChilds / parent.getMaxVisibleChilds() + 1) * parent.visibleChilds = Math.floor(parent.visibleChilds / parent.getMaxVisibleChilds() + 1) *
parent.getMaxVisibleChilds(); parent.getMaxVisibleChilds()
me.updateDom(); me.updateDom()
parent.showChilds(); parent.showChilds()
event.preventDefault(); event.preventDefault()
return false; return false
}; }
var showAllButton = document.createElement('a'); var showAllButton = document.createElement('a')
showAllButton.appendChild(document.createTextNode(translate('showAll'))); showAllButton.appendChild(document.createTextNode(translate('showAll')))
showAllButton.href = '#'; showAllButton.href = '#'
showAllButton.onclick = function (event) { showAllButton.onclick = function (event) {
// TODO: use callback instead of accessing a method of the parent // TODO: use callback instead of accessing a method of the parent
parent.visibleChilds = Infinity; parent.visibleChilds = Infinity
me.updateDom(); me.updateDom()
parent.showChilds(); parent.showChilds()
event.preventDefault(); event.preventDefault()
return false; return false
};
var moreContents = document.createElement('div');
var moreText = document.createTextNode(this._getShowMoreText());
moreContents.className = 'jsoneditor-show-more';
moreContents.appendChild(moreText);
moreContents.appendChild(showMoreButton);
moreContents.appendChild(document.createTextNode('. '));
moreContents.appendChild(showAllButton);
moreContents.appendChild(document.createTextNode('. '));
var tdContents = document.createElement('td');
tdContents.appendChild(moreContents);
var moreTr = document.createElement('tr');
if (this.editor.options.mode === 'tree') {
moreTr.appendChild(document.createElement('td'));
moreTr.appendChild(document.createElement('td'));
} }
moreTr.appendChild(tdContents);
moreTr.className = 'jsoneditor-show-more'; var moreContents = document.createElement('div')
this.dom.tr = moreTr; var moreText = document.createTextNode(this._getShowMoreText())
this.dom.moreContents = moreContents; moreContents.className = 'jsoneditor-show-more'
this.dom.moreText = moreText; moreContents.appendChild(moreText)
moreContents.appendChild(showMoreButton)
moreContents.appendChild(document.createTextNode('. '))
moreContents.appendChild(showAllButton)
moreContents.appendChild(document.createTextNode('. '))
var tdContents = document.createElement('td')
tdContents.appendChild(moreContents)
var moreTr = document.createElement('tr')
if (this.editor.options.mode === 'tree') {
moreTr.appendChild(document.createElement('td'))
moreTr.appendChild(document.createElement('td'))
}
moreTr.appendChild(tdContents)
moreTr.className = 'jsoneditor-show-more'
this.dom.tr = moreTr
this.dom.moreContents = moreContents
this.dom.moreText = moreText
} }
this.updateDom(); this.updateDom()
return this.dom.tr; return this.dom.tr
}; }
/** /**
* Update the HTML dom of the Node * Update the HTML dom of the Node
*/ */
ShowMoreNode.prototype.updateDom = function(options) { ShowMoreNode.prototype.updateDom = function (options) {
if (this.isVisible()) { if (this.isVisible()) {
// attach to the right child node (the first non-visible child) // attach to the right child node (the first non-visible child)
this.dom.tr.node = this.parent.childs[this.parent.visibleChilds]; this.dom.tr.node = this.parent.childs[this.parent.visibleChilds]
if (!this.dom.tr.parentNode) { if (!this.dom.tr.parentNode) {
var nextTr = this.parent._getNextTr(); var nextTr = this.parent._getNextTr()
if (nextTr) { if (nextTr) {
nextTr.parentNode.insertBefore(this.dom.tr, nextTr); nextTr.parentNode.insertBefore(this.dom.tr, nextTr)
} }
} }
// update the counts in the text // update the counts in the text
this.dom.moreText.nodeValue = this._getShowMoreText(); this.dom.moreText.nodeValue = this._getShowMoreText()
// update left margin // update left margin
this.dom.moreContents.style.marginLeft = (this.getLevel() + 1) * 24 + 'px'; this.dom.moreContents.style.marginLeft = (this.getLevel() + 1) * 24 + 'px'
} } else {
else {
if (this.dom.tr && this.dom.tr.parentNode) { if (this.dom.tr && this.dom.tr.parentNode) {
this.dom.tr.parentNode.removeChild(this.dom.tr); this.dom.tr.parentNode.removeChild(this.dom.tr)
} }
} }
}; }
ShowMoreNode.prototype._getShowMoreText = function() { ShowMoreNode.prototype._getShowMoreText = function () {
return translate('showMoreStatus', { return translate('showMoreStatus', {
visibleChilds: this.parent.visibleChilds, visibleChilds: this.parent.visibleChilds,
totalChilds: this.parent.childs.length totalChilds: this.parent.childs.length
}) + ' '; }) + ' '
}; }
/** /**
* Check whether the ShowMoreNode is currently visible. * Check whether the ShowMoreNode is currently visible.
@ -137,21 +136,21 @@ function showMoreNodeFactory(Node) {
* @return {boolean} isVisible * @return {boolean} isVisible
*/ */
ShowMoreNode.prototype.isVisible = function () { ShowMoreNode.prototype.isVisible = function () {
return this.parent.expanded && this.parent.childs.length > this.parent.visibleChilds; return this.parent.expanded && this.parent.childs.length > this.parent.visibleChilds
}; }
/** /**
* Handle an event. The event is caught centrally by the editor * Handle an event. The event is caught centrally by the editor
* @param {Event} event * @param {Event} event
*/ */
ShowMoreNode.prototype.onEvent = function (event) { ShowMoreNode.prototype.onEvent = function (event) {
var type = event.type; var type = event.type
if (type === 'keydown') { if (type === 'keydown') {
this.onKeyDown(event); this.onKeyDown(event)
} }
}; }
return ShowMoreNode; return ShowMoreNode
} }
module.exports = showMoreNodeFactory; module.exports = showMoreNodeFactory

View File

@ -1,6 +1,6 @@
var picoModal = require('picomodal'); var picoModal = require('picomodal')
var translate = require('./i18n').translate; var translate = require('./i18n').translate
var util = require('./util'); var util = require('./util')
/** /**
* Show advanced sorting modal * Show advanced sorting modal
@ -17,12 +17,12 @@ var util = require('./util');
*/ */
function showSortModal (container, json, onSort, options) { function showSortModal (container, json, onSort, options) {
var paths = Array.isArray(json) var paths = Array.isArray(json)
? util.getChildPaths(json) ? util.getChildPaths(json)
: ['']; : ['']
var selectedPath = options && options.path && util.contains(paths, options.path) var selectedPath = options && options.path && util.contains(paths, options.path)
? options.path ? options.path
: paths[0] : paths[0]
var selectedDirection = options && options.direction || 'asc' var selectedDirection = (options && options.direction) || 'asc'
var content = '<div class="pico-modal-contents">' + var content = '<div class="pico-modal-contents">' +
'<div class="pico-modal-header">' + translate('sort') + '</div>' + '<div class="pico-modal-header">' + translate('sort') + '</div>' +
@ -44,7 +44,7 @@ function showSortModal (container, json, onSort, options) {
' <div id="direction" class="jsoneditor-button-group">' + ' <div id="direction" class="jsoneditor-button-group">' +
'<input type="button" ' + '<input type="button" ' +
'value="' + translate('sortAscending') + '" ' + 'value="' + translate('sortAscending') + '" ' +
'title="' + translate('sortAscendingTitle') + '" ' + 'title="' + translate('sortAscendingTitle') + '" ' +
'data-value="asc" ' + 'data-value="asc" ' +
'class="jsoneditor-button-first jsoneditor-button-asc"/>' + 'class="jsoneditor-button-first jsoneditor-button-asc"/>' +
'<input type="button" ' + '<input type="button" ' +
@ -63,71 +63,71 @@ function showSortModal (container, json, onSort, options) {
'</tbody>' + '</tbody>' +
'</table>' + '</table>' +
'</form>' + '</form>' +
'</div>'; '</div>'
picoModal({ picoModal({
parent: container, parent: container,
content: content, content: content,
overlayClass: 'jsoneditor-modal-overlay', overlayClass: 'jsoneditor-modal-overlay',
overlayStyles: { overlayStyles: {
backgroundColor: "rgb(1,1,1)", backgroundColor: 'rgb(1,1,1)',
opacity: 0.3 opacity: 0.3
}, },
modalClass: 'jsoneditor-modal jsoneditor-modal-sort' modalClass: 'jsoneditor-modal jsoneditor-modal-sort'
}) })
.afterCreate(function (modal) { .afterCreate(function (modal) {
var form = modal.modalElem().querySelector('form'); var form = modal.modalElem().querySelector('form')
var ok = modal.modalElem().querySelector('#ok'); var ok = modal.modalElem().querySelector('#ok')
var field = modal.modalElem().querySelector('#field'); var field = modal.modalElem().querySelector('#field')
var direction = modal.modalElem().querySelector('#direction'); var direction = modal.modalElem().querySelector('#direction')
function preprocessPath(path) { function preprocessPath (path) {
return (path === '') return (path === '')
? '@' ? '@'
: (path[0] === '.') : (path[0] === '.')
? path.slice(1) ? path.slice(1)
: path; : path
} }
paths.forEach(function (path) { paths.forEach(function (path) {
var option = document.createElement('option'); var option = document.createElement('option')
option.text = preprocessPath(path); option.text = preprocessPath(path)
option.value = path; option.value = path
field.appendChild(option); field.appendChild(option)
});
function setDirection(value) {
direction.value = value;
direction.className = 'jsoneditor-button-group jsoneditor-button-group-value-' + direction.value;
}
field.value = selectedPath || paths[0];
setDirection(selectedDirection || 'asc');
direction.onclick = function (event) {
setDirection(event.target.getAttribute('data-value'));
};
ok.onclick = function (event) {
event.preventDefault();
event.stopPropagation();
modal.close();
onSort({
path: field.value,
direction: direction.value
})
};
if (form) { // form is not available when JSONEditor is created inside a form
form.onsubmit = ok.onclick;
}
}) })
.afterClose(function (modal) {
modal.destroy(); function setDirection (value) {
}) direction.value = value
.show(); direction.className = 'jsoneditor-button-group jsoneditor-button-group-value-' + direction.value
}
field.value = selectedPath || paths[0]
setDirection(selectedDirection || 'asc')
direction.onclick = function (event) {
setDirection(event.target.getAttribute('data-value'))
}
ok.onclick = function (event) {
event.preventDefault()
event.stopPropagation()
modal.close()
onSort({
path: field.value,
direction: direction.value
})
}
if (form) { // form is not available when JSONEditor is created inside a form
form.onsubmit = ok.onclick
}
})
.afterClose(function (modal) {
modal.destroy()
})
.show()
} }
module.exports = showSortModal; module.exports = showSortModal

View File

@ -1,11 +1,11 @@
var jmespath = require('jmespath'); var jmespath = require('jmespath')
var picoModal = require('picomodal'); var picoModal = require('picomodal')
var Selectr = require('./assets/selectr/selectr'); var Selectr = require('./assets/selectr/selectr')
var translate = require('./i18n').translate; var translate = require('./i18n').translate
var stringifyPartial = require('./jsonUtils').stringifyPartial; var stringifyPartial = require('./jsonUtils').stringifyPartial
var util = require('./util'); var util = require('./util')
var MAX_PREVIEW_CHARACTERS = require('./constants').MAX_PREVIEW_CHARACTERS var MAX_PREVIEW_CHARACTERS = require('./constants').MAX_PREVIEW_CHARACTERS
var debounce = util.debounce; var debounce = util.debounce
/** /**
* Show advanced filter and transform modal using JMESPath * Show advanced filter and transform modal using JMESPath
@ -16,7 +16,7 @@ var debounce = util.debounce;
* query as callback * query as callback
*/ */
function showTransformModal (container, json, onTransform) { function showTransformModal (container, json, onTransform) {
var value = json; var value = json
var content = '<label class="pico-modal-contents">' + var content = '<label class="pico-modal-contents">' +
'<div class="pico-modal-header">' + translate('transform') + '</div>' + '<div class="pico-modal-header">' + translate('transform') + '</div>' +
@ -93,208 +93,202 @@ function showTransformModal (container, json, onTransform) {
'<div class="jsoneditor-jmespath-block jsoneditor-modal-actions">' + '<div class="jsoneditor-jmespath-block jsoneditor-modal-actions">' +
' <input type="submit" id="ok" value="' + translate('ok') + '" autofocus />' + ' <input type="submit" id="ok" value="' + translate('ok') + '" autofocus />' +
'</div>' + '</div>' +
'</div>'; '</div>'
picoModal({ picoModal({
parent: container, parent: container,
content: content, content: content,
overlayClass: 'jsoneditor-modal-overlay', overlayClass: 'jsoneditor-modal-overlay',
overlayStyles: { overlayStyles: {
backgroundColor: "rgb(1,1,1)", backgroundColor: 'rgb(1,1,1)',
opacity: 0.3 opacity: 0.3
}, },
modalClass: 'jsoneditor-modal jsoneditor-modal-transform', modalClass: 'jsoneditor-modal jsoneditor-modal-transform',
focus: false focus: false
}) })
.afterCreate(function (modal) { .afterCreate(function (modal) {
var elem = modal.modalElem(); var elem = modal.modalElem()
var wizard = elem.querySelector('#wizard'); var wizard = elem.querySelector('#wizard')
var ok = elem.querySelector('#ok'); var ok = elem.querySelector('#ok')
var filterField = elem.querySelector('#filterField'); var filterField = elem.querySelector('#filterField')
var filterRelation = elem.querySelector('#filterRelation'); var filterRelation = elem.querySelector('#filterRelation')
var filterValue = elem.querySelector('#filterValue'); var filterValue = elem.querySelector('#filterValue')
var sortField = elem.querySelector('#sortField'); var sortField = elem.querySelector('#sortField')
var sortOrder = elem.querySelector('#sortOrder'); var sortOrder = elem.querySelector('#sortOrder')
var selectFields = elem.querySelector('#selectFields'); var selectFields = elem.querySelector('#selectFields')
var query = elem.querySelector('#query'); var query = elem.querySelector('#query')
var preview = elem.querySelector('#preview'); var preview = elem.querySelector('#preview')
if (!Array.isArray(value)) { if (!Array.isArray(value)) {
wizard.style.fontStyle = 'italic'; wizard.style.fontStyle = 'italic'
wizard.innerHTML = '(wizard not available for objects, only for arrays)' wizard.innerHTML = '(wizard not available for objects, only for arrays)'
}
var sortablePaths = util.getChildPaths(json)
sortablePaths.forEach(function (path) {
var formattedPath = preprocessPath(path)
var filterOption = document.createElement('option')
filterOption.text = formattedPath
filterOption.value = formattedPath
filterField.appendChild(filterOption)
var sortOption = document.createElement('option')
sortOption.text = formattedPath
sortOption.value = formattedPath
sortField.appendChild(sortOption)
})
var selectablePaths = util.getChildPaths(json, true).filter(function (path) {
return path !== ''
})
if (selectablePaths.length > 0) {
selectablePaths.forEach(function (path) {
var formattedPath = preprocessPath(path)
var option = document.createElement('option')
option.text = formattedPath
option.value = formattedPath
selectFields.appendChild(option)
})
} else {
var selectFieldsPart = elem.querySelector('#selectFieldsPart')
if (selectFieldsPart) {
selectFieldsPart.style.display = 'none'
} }
}
var sortablePaths = util.getChildPaths(json); var selectrFilterField = new Selectr(filterField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' })
var selectrFilterRelation = new Selectr(filterRelation, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'compare...' })
var selectrSortField = new Selectr(sortField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' })
var selectrSortOrder = new Selectr(sortOrder, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'order...' })
var selectrSelectFields = new Selectr(selectFields, { multiple: true, clearable: true, defaultSelected: false, placeholder: 'select fields...' })
sortablePaths.forEach(function (path) { selectrFilterField.on('selectr.change', generateQueryFromWizard)
var formattedPath = preprocessPath(path); selectrFilterRelation.on('selectr.change', generateQueryFromWizard)
var filterOption = document.createElement('option'); filterValue.oninput = generateQueryFromWizard
filterOption.text = formattedPath; selectrSortField.on('selectr.change', generateQueryFromWizard)
filterOption.value = formattedPath; selectrSortOrder.on('selectr.change', generateQueryFromWizard)
filterField.appendChild(filterOption); selectrSelectFields.on('selectr.change', generateQueryFromWizard)
var sortOption = document.createElement('option'); elem.querySelector('.pico-modal-contents').onclick = function (event) {
sortOption.text = formattedPath; // prevent the first clear button (in any select box) from getting
sortOption.value = formattedPath; // focus when clicking anywhere in the modal. Only allow clicking links.
sortField.appendChild(sortOption); if (event.target.nodeName !== 'A') {
}); event.preventDefault()
var selectablePaths = util.getChildPaths(json, true).filter(function(path) {
return path !== '';
});
if (selectablePaths.length > 0) {
selectablePaths.forEach(function (path) {
var formattedPath = preprocessPath(path);
var option = document.createElement('option');
option.text = formattedPath;
option.value = formattedPath;
selectFields.appendChild(option);
});
}
else {
var selectFieldsPart = elem.querySelector('#selectFieldsPart');
if (selectFieldsPart) {
selectFieldsPart.style.display = 'none';
}
} }
}
var selectrFilterField = new Selectr(filterField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' }); query.value = Array.isArray(value) ? '[*]' : '@'
var selectrFilterRelation = new Selectr(filterRelation, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'compare...' });
var selectrSortField = new Selectr(sortField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' });
var selectrSortOrder = new Selectr(sortOrder, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'order...' });
var selectrSelectFields = new Selectr(selectFields, {multiple: true, clearable: true, defaultSelected: false, placeholder: 'select fields...'});
selectrFilterField.on('selectr.change', generateQueryFromWizard); function preprocessPath (path) {
selectrFilterRelation.on('selectr.change', generateQueryFromWizard); return (path === '')
filterValue.oninput = generateQueryFromWizard; ? '@'
selectrSortField.on('selectr.change', generateQueryFromWizard); : (path[0] === '.')
selectrSortOrder.on('selectr.change', generateQueryFromWizard); ? path.slice(1)
selectrSelectFields.on('selectr.change', generateQueryFromWizard); : path
}
elem.querySelector('.pico-modal-contents').onclick = function (event) { function generateQueryFromWizard () {
// prevent the first clear button (in any select box) from getting if (filterField.value && filterRelation.value && filterValue.value) {
// focus when clicking anywhere in the modal. Only allow clicking links. var field1 = filterField.value
if (event.target.nodeName !== 'A') { var examplePath = field1 !== '@'
event.preventDefault(); ? ['0'].concat(util.parsePath('.' + field1))
} : ['0']
}; var exampleValue = util.get(value, examplePath)
var value1 = typeof exampleValue === 'string'
? filterValue.value
: util.parseString(filterValue.value)
query.value = Array.isArray(value) ? '[*]' : '@'; query.value = '[? ' +
function preprocessPath(path) {
return (path === '')
? '@'
: (path[0] === '.')
? path.slice(1)
: path;
}
function generateQueryFromWizard () {
if (filterField.value && filterRelation.value && filterValue.value) {
var field1 = filterField.value;
var examplePath = field1 !== '@'
? ['0'].concat(util.parsePath('.' + field1))
: ['0']
var exampleValue = util.get(value, examplePath)
var value1 = typeof exampleValue === 'string'
? filterValue.value
: util.parseString(filterValue.value);
query.value = '[? ' +
field1 + ' ' + field1 + ' ' +
filterRelation.value + ' ' + filterRelation.value + ' ' +
'`' + JSON.stringify(value1) + '`' + '`' + JSON.stringify(value1) + '`' +
']'; ']'
} } else {
else { query.value = '[*]'
query.value = '[*]'; }
}
if (sortField.value && sortOrder.value) { if (sortField.value && sortOrder.value) {
var field2 = sortField.value; var field2 = sortField.value
if (sortOrder.value === 'desc') { if (sortOrder.value === 'desc') {
query.value += ' | reverse(sort_by(@, &' + field2 + '))'; query.value += ' | reverse(sort_by(@, &' + field2 + '))'
} } else {
else { query.value += ' | sort_by(@, &' + field2 + ')'
query.value += ' | sort_by(@, &' + field2 + ')'; }
}
if (selectFields.value) {
var values = []
for (var i = 0; i < selectFields.options.length; i++) {
if (selectFields.options[i].selected) {
var selectedValue = selectFields.options[i].value
values.push(selectedValue)
} }
} }
if (selectFields.value) { if (query.value[query.value.length - 1] !== ']') {
var values = []; query.value += ' | [*]'
for (var i=0; i < selectFields.options.length; i++) { }
if (selectFields.options[i].selected) {
var selectedValue = selectFields.options[i].value;
values.push(selectedValue);
}
}
if (query.value[query.value.length - 1] !== ']') { if (values.length === 1) {
query.value += ' | [*]'; query.value += '.' + values[0]
} } else if (values.length > 1) {
query.value += '.{' +
if (values.length === 1) {
query.value += '.' + values[0];
}
else if (values.length > 1) {
query.value += '.{' +
values.map(function (value) { values.map(function (value) {
var parts = value.split('.'); var parts = value.split('.')
var last = parts[parts.length - 1]; var last = parts[parts.length - 1]
return last + ': ' + value; return last + ': ' + value
}).join(', ') + }).join(', ') +
'}'; '}'
} } else { // values.length === 0
else { // values.length === 0 // ignore
// ignore
}
}
debouncedUpdatePreview();
}
function updatePreview() {
try {
var transformed = jmespath.search(value, query.value);
preview.className = 'jsoneditor-transform-preview';
preview.value = stringifyPartial(transformed, 2, MAX_PREVIEW_CHARACTERS);
ok.disabled = false;
}
catch (err) {
preview.className = 'jsoneditor-transform-preview jsoneditor-error';
preview.value = err.toString();
ok.disabled = true;
} }
} }
var debouncedUpdatePreview = debounce(updatePreview, 300); debouncedUpdatePreview()
}
query.oninput = debouncedUpdatePreview; function updatePreview () {
debouncedUpdatePreview(); try {
var transformed = jmespath.search(value, query.value)
ok.onclick = function (event) { preview.className = 'jsoneditor-transform-preview'
event.preventDefault(); preview.value = stringifyPartial(transformed, 2, MAX_PREVIEW_CHARACTERS)
event.stopPropagation();
modal.close(); ok.disabled = false
} catch (err) {
preview.className = 'jsoneditor-transform-preview jsoneditor-error'
preview.value = err.toString()
ok.disabled = true
}
}
onTransform(query.value) var debouncedUpdatePreview = debounce(updatePreview, 300)
};
setTimeout(function () { query.oninput = debouncedUpdatePreview
query.select(); debouncedUpdatePreview()
query.focus();
query.selectionStart = 3; ok.onclick = function (event) {
query.selectionEnd = 3; event.preventDefault()
}); event.stopPropagation()
modal.close()
onTransform(query.value)
}
setTimeout(function () {
query.select()
query.focus()
query.selectionStart = 3
query.selectionEnd = 3
}) })
.afterClose(function (modal) { })
modal.destroy(); .afterClose(function (modal) {
}) modal.destroy()
.show(); })
.show()
} }
module.exports = showTransformModal; module.exports = showTransformModal

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
var isPromise = require('./util').isPromise; var isPromise = require('./util').isPromise
var isValidValidationError = require('./util').isValidValidationError; var isValidValidationError = require('./util').isValidValidationError
var stringifyPath = require('./util').stringifyPath; var stringifyPath = require('./util').stringifyPath
/** /**
* Execute custom validation if configured. * Execute custom validation if configured.
@ -9,46 +9,44 @@ var stringifyPath = require('./util').stringifyPath;
*/ */
function validateCustom (json, onValidate) { function validateCustom (json, onValidate) {
if (!onValidate) { if (!onValidate) {
return Promise.resolve([]); return Promise.resolve([])
} }
try { try {
var customValidateResults = onValidate(json); var customValidateResults = onValidate(json)
var resultPromise = isPromise(customValidateResults) var resultPromise = isPromise(customValidateResults)
? customValidateResults ? customValidateResults
: Promise.resolve(customValidateResults); : Promise.resolve(customValidateResults)
return resultPromise.then(function (customValidationPathErrors) { return resultPromise.then(function (customValidationPathErrors) {
if (Array.isArray(customValidationPathErrors)) { if (Array.isArray(customValidationPathErrors)) {
return customValidationPathErrors return customValidationPathErrors
.filter(function (error) { .filter(function (error) {
var valid = isValidValidationError(error); var valid = isValidValidationError(error)
if (!valid) { if (!valid) {
console.warn('Ignoring a custom validation error with invalid structure. ' + console.warn('Ignoring a custom validation error with invalid structure. ' +
'Expected structure: {path: [...], message: "..."}. ' + 'Expected structure: {path: [...], message: "..."}. ' +
'Actual error:', error); 'Actual error:', error)
} }
return valid; return valid
}) })
.map(function (error) { .map(function (error) {
// change data structure into the structure matching the JSON schema errors // change data structure into the structure matching the JSON schema errors
return { return {
dataPath: stringifyPath(error.path), dataPath: stringifyPath(error.path),
message: error.message message: error.message
} }
}); })
} else {
return []
} }
else { })
return []; } catch (err) {
} return Promise.reject(err)
});
}
catch (err) {
return Promise.reject(err);
} }
} }
exports.validateCustom = validateCustom; exports.validateCustom = validateCustom

View File

@ -1,17 +1,15 @@
var VanillaPicker; var VanillaPicker
if (window.Picker) { if (window.Picker) {
// use the already loaded instance of VanillaPicker // use the already loaded instance of VanillaPicker
VanillaPicker = window.Picker; VanillaPicker = window.Picker
} } else {
else {
try { try {
// load color picker // load color picker
VanillaPicker = require('vanilla-picker'); VanillaPicker = require('vanilla-picker')
} } catch (err) {
catch (err) {
// probably running the minimalist bundle // probably running the minimalist bundle
} }
} }
module.exports = VanillaPicker; module.exports = VanillaPicker

View File

@ -1,295 +1,287 @@
var assert = require('assert'); var assert = require('assert')
var setUpTestEnvironment = require('./setup'); var setUpTestEnvironment = require('./setup')
setUpTestEnvironment(); setUpTestEnvironment()
var JSONEditor = require('../src/js/JSONEditor'); var Node = require('../src/js/Node')
var Node = require('../src/js/Node');
describe('Node', function () { describe('Node', function () {
var node; describe('_findSchema', function () {
it('should find schema', function () {
beforeEach(function () { var schema = {
var editor = new JSONEditor(document.createElement('foo')); type: 'object',
node = new Node(editor); properties: {
}); child: {
type: 'string'
}
}
}
var path = ['child']
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.child)
})
describe('_findSchema', function () { it('should find schema inside an array item', function () {
it('should find schema', function () { var schema = {
var schema = { properties: {
job: {
type: 'array',
items: {
type: 'object',
properties: {
company: {
enum: ['test1', 'test2']
}
}
}
}
}
}
assert.strictEqual(Node._findSchema(schema, {}, []), schema)
assert.strictEqual(Node._findSchema(schema, {}, ['job']), schema.properties.job)
assert.strictEqual(Node._findSchema(schema, {}, ['job', 0]),
schema.properties.job.items)
assert.strictEqual(Node._findSchema(schema, {}, ['job', 0, 'company']),
schema.properties.job.items.properties.company)
})
it('should find schema within multi-level object properties', function () {
var schema = {
type: 'object',
properties: {
levelTwo: {
type: 'object',
properties: {
levelThree: {
type: 'object', type: 'object',
properties: { properties: {
child: { bool: {
type: 'string' type: 'boolean'
} }
} }
}; }
var path = ['child']; }
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.child); }
}); }
}
var path = []
assert.strictEqual(Node._findSchema(schema, {}, path), schema)
path = ['levelTwo']
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo)
path = ['levelTwo', 'levelThree']
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo.properties.levelThree)
path = ['levelTwo', 'levelThree', 'bool']
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.properties.levelTwo.properties.levelThree.properties.bool
)
})
it('should find schema inside an array item', function () { it('should return null for path that has no schema', function () {
var schema = { var schema = {
properties: { type: 'object',
job: { properties: {
type: 'array', foo: {
items: { type: 'object',
type: 'object', properties: {
properties: { baz: {
company: { type: 'number'
enum: ['test1', 'test2'] }
} }
} }
} }
}
var path = ['bar']
assert.strictEqual(Node._findSchema(schema, {}, path), null)
path = ['foo', 'bar']
assert.strictEqual(Node._findSchema(schema, {}, path), null)
})
describe('with $ref', function () {
it('should find a referenced schema', function () {
var schema = {
type: 'object',
properties: {
foo: {
$ref: 'foo'
}
}
}
var fooSchema = {
type: 'number',
title: 'Foo'
}
var path = ['foo']
assert.strictEqual(Node._findSchema(schema, { foo: fooSchema }, path), fooSchema)
})
})
describe('with pattern properties', function () {
it('should find schema', function () {
var schema = {
type: 'object',
properties: {
str: {
title: 'str',
type: 'boolean'
}
},
patternProperties: {
'^foo[0-9]': {
title: 'foo[0-] pattern property',
type: 'string'
}
}
}
var path = []
assert.strictEqual(Node._findSchema(schema, {}, path), schema, 'top level')
path = ['str']
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.str, 'normal property')
})
it('should find schema within multi-level object properties', function () {
var schema = {
type: 'object',
properties: {
levelTwo: {
type: 'object',
properties: {
levelThree: {
type: 'object',
properties: {
bool: {
title: 'bool',
type: 'boolean'
} }
}
} }
}; }
}
},
patternProperties: {
'^foo[0-9]': {
title: 'foo[0-9] pattern property',
type: 'string'
}
}
}
var path = []
assert.strictEqual(Node._findSchema(schema, {}, path), schema, 'top level')
path = ['levelTwo']
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo, 'level two')
path = ['levelTwo', 'levelThree']
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo.properties.levelThree, 'level three')
path = ['levelTwo', 'levelThree', 'bool']
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.properties.levelTwo.properties.levelThree.properties.bool,
'normal property'
)
})
assert.strictEqual(Node._findSchema(schema, {}, []), schema); it('should find schema for pattern properties', function () {
var schema = {
type: 'object',
patternProperties: {
'^foo[0-9]': {
title: 'foo[0-9] pattern property',
type: 'string'
},
'^bar[0-9]': {
title: 'bar[0-9] pattern property',
type: 'string'
}
}
}
var path = ['foo1']
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.patternProperties['^foo[0-9]'],
'first pattern property'
)
path = ['bar5']
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.patternProperties['^bar[0-9]'],
'second pattern property'
)
})
assert.strictEqual(Node._findSchema(schema, {}, ['job']), schema.properties.job); it('should find schema for multi-level pattern properties', function () {
var schema = {
assert.strictEqual(Node._findSchema(schema, {}, ['job', 0]), type: 'object',
schema.properties.job.items); patternProperties: {
'^foo[0-9]': {
assert.strictEqual(Node._findSchema(schema, {}, ['job', 0, 'company']), title: 'foo[0-9] pattern property',
schema.properties.job.items.properties.company); type: 'object',
}); properties: {
fooChild: {
it('should find schema within multi-level object properties', function () { type: 'object',
var schema = { properties: {
type: 'object', fooChild2: {
properties: { type: 'string'
levelTwo: {
type: 'object',
properties: {
levelThree: {
type: 'object',
properties: {
bool: {
type: 'boolean'
}
}
}
}
} }
}
} }
}; }
var path = []; },
assert.strictEqual(Node._findSchema(schema, {}, path), schema); '^bar[0-9]': {
path = ['levelTwo']; title: 'bar[0-9] pattern property',
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo); type: 'object',
path = ['levelTwo', 'levelThree']; properties: {
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo.properties.levelThree); barChild: {
path = ['levelTwo', 'levelThree', 'bool']; type: 'string'
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.properties.levelTwo.properties.levelThree.properties.bool
);
});
it('should return null for path that has no schema', function () {
var schema = {
type: 'object',
properties: {
foo: {
type: 'object',
properties: {
baz: {
type: 'number'
}
}
}
} }
};
var path = ['bar'];
assert.strictEqual(Node._findSchema(schema, {}, path), null);
path = ['foo', 'bar'];
assert.strictEqual(Node._findSchema(schema, {}, path), null);
});
describe('with $ref', function () { }
it('should find a referenced schema', function () { }
var schema = { }
type: 'object', }
properties: { var path = ['foo1', 'fooChild', 'fooChild2']
foo: { assert.strictEqual(
$ref: 'foo' Node._findSchema(schema, {}, path),
} schema.patternProperties['^foo[0-9]'].properties.fooChild.properties.fooChild2,
} 'first pattern property child of child'
}; )
var fooSchema = { path = ['bar5', 'barChild']
type: 'number', assert.strictEqual(
title: 'Foo' Node._findSchema(schema, {}, path),
}; schema.patternProperties['^bar[0-9]'].properties.barChild,
var path = ['foo']; 'second pattern property child'
assert.strictEqual(Node._findSchema(schema, {foo: fooSchema}, path), fooSchema); )
}); })
});
describe('with pattern properties', function () { it('should return null for path that has no schema', function () {
it('should find schema', function () { var schema = {
var schema = { type: 'object',
type: 'object', properties: {
properties: { levelTwo: {
str: { type: 'object',
title: 'str', properties: {
type: 'boolean' levelThree: {
} type: 'number'
}, }
patternProperties: { }
'^foo[0-9]': { }
title: 'foo[0-] pattern property', },
type: 'string' patternProperties: {
} '^foo[0-9]': {
} title: 'foo[0-9] pattern property',
}; type: 'string'
var path = []; },
assert.strictEqual(Node._findSchema(schema, {}, path), schema, 'top level'); '^bar[0-9]': {
path = ['str']; title: 'bar[0-9] pattern property',
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.str, 'normal property'); type: 'string'
}); }
}
it('should find schema within multi-level object properties', function () { }
var schema = { var path = ['not-in-schema']
type: 'object', assert.strictEqual(Node._findSchema(schema, {}, path), null)
properties: { path = ['levelOne', 'not-in-schema']
levelTwo: { assert.strictEqual(Node._findSchema(schema, {}, path), null)
type: 'object', })
properties: { })
levelThree: { })
type: 'object', })
properties: {
bool: {
title: 'bool',
type: 'boolean'
}
}
}
}
}
},
patternProperties: {
'^foo[0-9]': {
title: 'foo[0-9] pattern property',
type: 'string'
}
}
};
var path = [];
assert.strictEqual(Node._findSchema(schema, {}, path), schema, 'top level');
path = ['levelTwo'];
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo, 'level two');
path = ['levelTwo', 'levelThree'];
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo.properties.levelThree, 'level three');
path = ['levelTwo', 'levelThree', 'bool'];
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.properties.levelTwo.properties.levelThree.properties.bool,
'normal property'
);
});
it('should find schema for pattern properties', function () {
var schema = {
type: 'object',
patternProperties: {
'^foo[0-9]': {
title: 'foo[0-9] pattern property',
type: 'string'
},
'^bar[0-9]': {
title: 'bar[0-9] pattern property',
type: 'string'
}
}
};
var path = ['foo1'];
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.patternProperties['^foo[0-9]'],
'first pattern property'
);
path = ['bar5'];
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.patternProperties['^bar[0-9]'],
'second pattern property'
);
});
it('should find schema for multi-level pattern properties', function () {
var schema = {
type: 'object',
patternProperties: {
'^foo[0-9]': {
title: 'foo[0-9] pattern property',
type: 'object',
properties: {
fooChild: {
type: 'object',
properties: {
fooChild2: {
type: 'string'
}
}
}
}
},
'^bar[0-9]': {
title: 'bar[0-9] pattern property',
type: 'object',
properties: {
barChild: {
type: 'string'
}
}
}
}
};
var path = ['foo1', 'fooChild', 'fooChild2'];
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.patternProperties['^foo[0-9]'].properties.fooChild.properties.fooChild2,
'first pattern property child of child'
);
path = ['bar5', 'barChild'];
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.patternProperties['^bar[0-9]'].properties.barChild,
'second pattern property child'
);
});
it('should return null for path that has no schema', function () {
var schema = {
type: 'object',
properties: {
levelTwo: {
type: 'object',
properties: {
levelThree: {
type: 'number'
}
}
}
},
patternProperties: {
'^foo[0-9]': {
title: 'foo[0-9] pattern property',
type: 'string'
},
'^bar[0-9]': {
title: 'bar[0-9] pattern property',
type: 'string'
}
}
};
var path = ['not-in-schema'];
assert.strictEqual(Node._findSchema(schema, {}, path), null);
path = ['levelOne', 'not-in-schema'];
assert.strictEqual(Node._findSchema(schema, {}, path), null);
});
});
});
});

View File

@ -1,21 +1,20 @@
var assert = require('assert'); var assert = require('assert')
var stringifyPartial = require('../src/js/jsonUtils').stringifyPartial; var stringifyPartial = require('../src/js/jsonUtils').stringifyPartial
var containsArray = require('../src/js/jsonUtils').containsArray; var containsArray = require('../src/js/jsonUtils').containsArray
describe('jsonUtils', function () { describe('jsonUtils', function () {
it('should stringify a small object', function () { it('should stringify a small object', function () {
var json = { var json = {
a: 2, a: 2,
b: 'foo', b: 'foo',
c: null, c: null,
d: false, d: false,
e: [ 1, 2, 3], e: [1, 2, 3],
f: { g: 'h' } f: { g: 'h' }
}; }
assert.strictEqual(stringifyPartial(json), '{"a":2,"b":"foo","c":null,"d":false,"e":[1,2,3],"f":{"g":"h"}}'); assert.strictEqual(stringifyPartial(json), '{"a":2,"b":"foo","c":null,"d":false,"e":[1,2,3],"f":{"g":"h"}}')
}); })
it('should stringify a small object with formatting', function () { it('should stringify a small object with formatting', function () {
var json = { var json = {
@ -23,12 +22,12 @@ describe('jsonUtils', function () {
b: 'foo', b: 'foo',
c: null, c: null,
d: false, d: false,
e: [ 1, 2, 3], e: [1, 2, 3],
f: { g: 'h' } f: { g: 'h' }
}; }
assert.strictEqual(stringifyPartial(json, 2), assert.strictEqual(stringifyPartial(json, 2),
'{\n' + '{\n' +
' "a": 2,\n' + ' "a": 2,\n' +
' "b": "foo",\n' + ' "b": "foo",\n' +
' "c": null,\n' + ' "c": null,\n' +
@ -41,7 +40,7 @@ describe('jsonUtils', function () {
' "f": {\n' + ' "f": {\n' +
' "g": "h"\n' + ' "g": "h"\n' +
' }\n' + ' }\n' +
'}'); '}')
assert.strictEqual(stringifyPartial(json, ' '), '{\n' + assert.strictEqual(stringifyPartial(json, ' '), '{\n' +
' "a": 2,\n' + ' "a": 2,\n' +
@ -56,8 +55,8 @@ describe('jsonUtils', function () {
' "f": {\n' + ' "f": {\n' +
' "g": "h"\n' + ' "g": "h"\n' +
' }\n' + ' }\n' +
'}'); '}')
}); })
it('should limit stringified output', function () { it('should limit stringified output', function () {
var json = { var json = {
@ -65,23 +64,23 @@ describe('jsonUtils', function () {
b: 'foo', b: 'foo',
c: null, c: null,
d: false, d: false,
e: [ 1, 2, 3], e: [1, 2, 3],
f: { g: 'h' } f: { g: 'h' }
}; }
var all = '{"a":2,"b":"foo","c":null,"d":false,"e":[1,2,3],"f":{"g":"h"}}'; var all = '{"a":2,"b":"foo","c":null,"d":false,"e":[1,2,3],"f":{"g":"h"}}'
var limit = 20; var limit = 20
assert.strictEqual(stringifyPartial(json, undefined, limit), assert.strictEqual(stringifyPartial(json, undefined, limit),
all.slice(0, limit) + '...'); all.slice(0, limit) + '...')
assert.strictEqual(stringifyPartial(json, undefined, all.length), all); assert.strictEqual(stringifyPartial(json, undefined, all.length), all)
assert.strictEqual(stringifyPartial([1,2,3,4,5,6,7,8,9,10], undefined, 10), assert.strictEqual(stringifyPartial([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], undefined, 10),
'[1,2,3,4,5...'); '[1,2,3,4,5...')
assert.strictEqual(stringifyPartial(12345678, undefined, 4),'1234...'); assert.strictEqual(stringifyPartial(12345678, undefined, 4), '1234...')
}); })
it('should count array items', function () { it('should count array items', function () {
// assert.strictEqual(countArrayItems('[1,2,3]'), 3) // assert.strictEqual(countArrayItems('[1,2,3]'), 3)
@ -93,6 +92,5 @@ describe('jsonUtils', function () {
assert.strictEqual(containsArray('2'), false) assert.strictEqual(containsArray('2'), false)
assert.strictEqual(containsArray('null'), false) assert.strictEqual(containsArray('null'), false)
assert.strictEqual(containsArray('{}'), false) assert.strictEqual(containsArray('{}'), false)
}); })
})
});

View File

@ -1,20 +1,20 @@
var JSDOM = require('jsdom').JSDOM; var JSDOM = require('jsdom').JSDOM
/** /**
* Set up the test environment by simulating browser globals. * Set up the test environment by simulating browser globals.
* @param {string} [locale=en] A locale to set in navigator.language * @param {string} [locale=en] A locale to set in navigator.language
* @return {void} * @return {void}
*/ */
module.exports = function setUpTestEnvironment(locale) { module.exports = function setUpTestEnvironment (locale) {
if (!locale) { if (!locale) {
locale = 'en' locale = 'en'
} }
var dom = new JSDOM('...'); var dom = new JSDOM('...')
global.window = dom.window; global.window = dom.window
global.document = dom.window.document; global.document = dom.window.document
global.navigator = dom.window.navigator; global.navigator = dom.window.navigator
// JSDom has no setter defined for navigator.language, so defineProperty is necessary in order to override it // JSDom has no setter defined for navigator.language, so defineProperty is necessary in order to override it
Object.defineProperty(navigator, 'language', {value: locale}) Object.defineProperty(navigator, 'language', { value: locale })
}; }

View File

@ -1,38 +1,36 @@
var assert = require('assert'); var assert = require('assert')
var util = require('../src/js/util'); var util = require('../src/js/util')
describe('util', function () { describe('util', function () {
describe('repair', function () { describe('repair', function () {
it('should leave valid JSON as is', function () { it('should leave valid JSON as is', function () {
assert.strictEqual(util.repair('{"a":2}'), '{"a":2}'); assert.strictEqual(util.repair('{"a":2}'), '{"a":2}')
}); })
it('should replace JavaScript with JSON', function () { it('should replace JavaScript with JSON', function () {
assert.strictEqual(util.repair('{a:2}'), '{"a":2}'); assert.strictEqual(util.repair('{a:2}'), '{"a":2}')
assert.strictEqual(util.repair('{a: 2}'), '{"a": 2}'); assert.strictEqual(util.repair('{a: 2}'), '{"a": 2}')
assert.strictEqual(util.repair('{\n a: 2\n}'), '{\n "a": 2\n}'); assert.strictEqual(util.repair('{\n a: 2\n}'), '{\n "a": 2\n}')
assert.strictEqual(util.repair('{\'a\':2}'), '{"a":2}'); assert.strictEqual(util.repair('{\'a\':2}'), '{"a":2}')
assert.strictEqual(util.repair('{a:\'foo\'}'), '{"a":"foo"}'); assert.strictEqual(util.repair('{a:\'foo\'}'), '{"a":"foo"}')
assert.strictEqual(util.repair('{a:\'foo\',b:\'bar\'}'), '{"a":"foo","b":"bar"}'); assert.strictEqual(util.repair('{a:\'foo\',b:\'bar\'}'), '{"a":"foo","b":"bar"}')
// should leave string content untouched // should leave string content untouched
assert.strictEqual(util.repair('"{a:b}"'), '"{a:b}"'); assert.strictEqual(util.repair('"{a:b}"'), '"{a:b}"')
}); })
it('should add/remove escape characters', function () { it('should add/remove escape characters', function () {
assert.strictEqual(util.repair('"foo\'bar"'), '"foo\'bar"'); assert.strictEqual(util.repair('"foo\'bar"'), '"foo\'bar"')
assert.strictEqual(util.repair('"foo\\"bar"'), '"foo\\"bar"'); assert.strictEqual(util.repair('"foo\\"bar"'), '"foo\\"bar"')
assert.strictEqual(util.repair('\'foo"bar\''), '"foo\\"bar"'); assert.strictEqual(util.repair('\'foo"bar\''), '"foo\\"bar"')
assert.strictEqual(util.repair('\'foo\\\'bar\''), '"foo\'bar"'); assert.strictEqual(util.repair('\'foo\\\'bar\''), '"foo\'bar"')
assert.strictEqual(util.repair('"foo\\\'bar"'), '"foo\'bar"'); assert.strictEqual(util.repair('"foo\\\'bar"'), '"foo\'bar"')
}); })
it('should replace special white characters', function () { it('should replace special white characters', function () {
assert.strictEqual(util.repair('{"a":\u00a0"foo\u00a0bar"}'), '{"a": "foo\u00a0bar"}'); assert.strictEqual(util.repair('{"a":\u00a0"foo\u00a0bar"}'), '{"a": "foo\u00a0bar"}')
assert.strictEqual(util.repair('{"a":\u2009"foo"}'), '{"a": "foo"}'); assert.strictEqual(util.repair('{"a":\u2009"foo"}'), '{"a": "foo"}')
}); })
it('should escape unescaped control characters', function () { it('should escape unescaped control characters', function () {
assert.strictEqual(util.repair('"hello\bworld"'), '"hello\\bworld"') assert.strictEqual(util.repair('"hello\bworld"'), '"hello\\bworld"')
@ -47,50 +45,50 @@ describe('util', function () {
assert.strictEqual(util.repair('\u2018foo\u2019'), '"foo"') assert.strictEqual(util.repair('\u2018foo\u2019'), '"foo"')
assert.strictEqual(util.repair('\u201Cfoo\u201D'), '"foo"') assert.strictEqual(util.repair('\u201Cfoo\u201D'), '"foo"')
assert.strictEqual(util.repair('\u0060foo\u00B4'), '"foo"') assert.strictEqual(util.repair('\u0060foo\u00B4'), '"foo"')
}); })
it('remove comments', function () { it('remove comments', function () {
assert.strictEqual(util.repair('/* foo */ {}'), ' {}'); assert.strictEqual(util.repair('/* foo */ {}'), ' {}')
assert.strictEqual(util.repair('/* foo */ {}'), ' {}'); assert.strictEqual(util.repair('/* foo */ {}'), ' {}')
assert.strictEqual(util.repair('{a:\'foo\',/*hello*/b:\'bar\'}'), '{"a":"foo","b":"bar"}'); assert.strictEqual(util.repair('{a:\'foo\',/*hello*/b:\'bar\'}'), '{"a":"foo","b":"bar"}')
assert.strictEqual(util.repair('{\na:\'foo\',//hello\nb:\'bar\'\n}'), '{\n"a":"foo",\n"b":"bar"\n}'); assert.strictEqual(util.repair('{\na:\'foo\',//hello\nb:\'bar\'\n}'), '{\n"a":"foo",\n"b":"bar"\n}')
// should not remove comments in string // should not remove comments in string
assert.strictEqual(util.repair('{"str":"/* foo */"}'), '{"str":"/* foo */"}'); assert.strictEqual(util.repair('{"str":"/* foo */"}'), '{"str":"/* foo */"}')
}); })
it('should strip JSONP notation', function () { it('should strip JSONP notation', function () {
// matching // matching
assert.strictEqual(util.repair('callback_123({});'), '{}'); assert.strictEqual(util.repair('callback_123({});'), '{}')
assert.strictEqual(util.repair('callback_123([]);'), '[]'); assert.strictEqual(util.repair('callback_123([]);'), '[]')
assert.strictEqual(util.repair('callback_123(2);'), '2'); assert.strictEqual(util.repair('callback_123(2);'), '2')
assert.strictEqual(util.repair('callback_123("foo");'), '"foo"'); assert.strictEqual(util.repair('callback_123("foo");'), '"foo"')
assert.strictEqual(util.repair('callback_123(null);'), 'null'); assert.strictEqual(util.repair('callback_123(null);'), 'null')
assert.strictEqual(util.repair('callback_123(true);'), 'true'); assert.strictEqual(util.repair('callback_123(true);'), 'true')
assert.strictEqual(util.repair('callback_123(false);'), 'false'); assert.strictEqual(util.repair('callback_123(false);'), 'false')
assert.strictEqual(util.repair('/* foo bar */ callback_123 ({})'), '{}'); assert.strictEqual(util.repair('/* foo bar */ callback_123 ({})'), '{}')
assert.strictEqual(util.repair('/* foo bar */ callback_123 ({})'), '{}'); assert.strictEqual(util.repair('/* foo bar */ callback_123 ({})'), '{}')
assert.strictEqual(util.repair('/* foo bar */\ncallback_123({})'), '{}'); assert.strictEqual(util.repair('/* foo bar */\ncallback_123({})'), '{}')
assert.strictEqual(util.repair('/* foo bar */ callback_123 ( {} )'), ' {} '); assert.strictEqual(util.repair('/* foo bar */ callback_123 ( {} )'), ' {} ')
assert.strictEqual(util.repair(' /* foo bar */ callback_123 ({}); '), '{}'); assert.strictEqual(util.repair(' /* foo bar */ callback_123 ({}); '), '{}')
assert.strictEqual(util.repair('\n/* foo\nbar */\ncallback_123 ({});\n\n'), '{}'); assert.strictEqual(util.repair('\n/* foo\nbar */\ncallback_123 ({});\n\n'), '{}')
// non-matching // non-matching
assert.strictEqual(util.repair('callback {}'), 'callback {}'); assert.strictEqual(util.repair('callback {}'), 'callback {}')
assert.strictEqual(util.repair('callback({}'), 'callback({}'); assert.strictEqual(util.repair('callback({}'), 'callback({}')
}); })
it('should strip trailing zeros', function () { it('should strip trailing zeros', function () {
// matching // matching
assert.strictEqual(util.repair('[1,2,3,]'), '[1,2,3]'); assert.strictEqual(util.repair('[1,2,3,]'), '[1,2,3]')
assert.strictEqual(util.repair('[1,2,3,\n]'), '[1,2,3\n]'); assert.strictEqual(util.repair('[1,2,3,\n]'), '[1,2,3\n]')
assert.strictEqual(util.repair('[1,2,3, \n ]'), '[1,2,3 \n ]'); assert.strictEqual(util.repair('[1,2,3, \n ]'), '[1,2,3 \n ]')
assert.strictEqual(util.repair('{"a":2,}'), '{"a":2}'); assert.strictEqual(util.repair('{"a":2,}'), '{"a":2}')
// not matching // not matching
assert.strictEqual(util.repair('"[1,2,3,]"'), '"[1,2,3,]"'); assert.strictEqual(util.repair('"[1,2,3,]"'), '"[1,2,3,]"')
assert.strictEqual(util.repair('"{a:2,}"'), '"{a:2,}"'); assert.strictEqual(util.repair('"{a:2,}"'), '"{a:2,}"')
}); })
it('should strip MongoDB data types', function () { it('should strip MongoDB data types', function () {
var mongoDocument = '{\n' + var mongoDocument = '{\n' +
@ -103,7 +101,7 @@ describe('util', function () {
' "int2" : NumberInt(3),\n' + ' "int2" : NumberInt(3),\n' +
' "decimal" : NumberDecimal("4"),\n' + ' "decimal" : NumberDecimal("4"),\n' +
' "decimal2" : NumberDecimal(4)\n' + ' "decimal2" : NumberDecimal(4)\n' +
'}'; '}'
var expectedJson = '{\n' + var expectedJson = '{\n' +
' "_id" : "123",\n' + ' "_id" : "123",\n' +
@ -115,78 +113,75 @@ describe('util', function () {
' "int2" : 3,\n' + ' "int2" : 3,\n' +
' "decimal" : "4",\n' + ' "decimal" : "4",\n' +
' "decimal2" : 4\n' + ' "decimal2" : 4\n' +
'}'; '}'
assert.strictEqual(util.repair(mongoDocument), expectedJson); assert.strictEqual(util.repair(mongoDocument), expectedJson)
}); })
}); })
describe('jsonPath', function () { describe('jsonPath', function () {
it('should stringify an array of paths', function () {
it('should stringify an array of paths', function() { assert.deepStrictEqual(util.stringifyPath([]), '')
assert.deepStrictEqual(util.stringifyPath([]), ''); assert.deepStrictEqual(util.stringifyPath(['foo']), '.foo')
assert.deepStrictEqual(util.stringifyPath(['foo']), '.foo'); assert.deepStrictEqual(util.stringifyPath(['foo', 'bar']), '.foo.bar')
assert.deepStrictEqual(util.stringifyPath(['foo', 'bar']), '.foo.bar'); assert.deepStrictEqual(util.stringifyPath(['foo', 2]), '.foo[2]')
assert.deepStrictEqual(util.stringifyPath(['foo', 2]), '.foo[2]'); assert.deepStrictEqual(util.stringifyPath(['foo', 2, 'bar']), '.foo[2].bar')
assert.deepStrictEqual(util.stringifyPath(['foo', 2, 'bar']), '.foo[2].bar'); assert.deepStrictEqual(util.stringifyPath(['foo', 2, 'bar_baz']), '.foo[2].bar_baz')
assert.deepStrictEqual(util.stringifyPath(['foo', 2, 'bar_baz']), '.foo[2].bar_baz'); assert.deepStrictEqual(util.stringifyPath([2]), '[2]')
assert.deepStrictEqual(util.stringifyPath([2]), '[2]'); assert.deepStrictEqual(util.stringifyPath(['foo', 'prop-with-hyphens']), '.foo["prop-with-hyphens"]')
assert.deepStrictEqual(util.stringifyPath(['foo', 'prop-with-hyphens']), '.foo["prop-with-hyphens"]'); assert.deepStrictEqual(util.stringifyPath(['foo', 'prop with spaces']), '.foo["prop with spaces"]')
assert.deepStrictEqual(util.stringifyPath(['foo', 'prop with spaces']), '.foo["prop with spaces"]');
}) })
it ('should parse a json path', function () { it('should parse a json path', function () {
assert.deepStrictEqual(util.parsePath(''), []); assert.deepStrictEqual(util.parsePath(''), [])
assert.deepStrictEqual(util.parsePath('.foo'), ['foo']); assert.deepStrictEqual(util.parsePath('.foo'), ['foo'])
assert.deepStrictEqual(util.parsePath('.foo.bar'), ['foo', 'bar']); assert.deepStrictEqual(util.parsePath('.foo.bar'), ['foo', 'bar'])
assert.deepStrictEqual(util.parsePath('.foo[2]'), ['foo', 2]); assert.deepStrictEqual(util.parsePath('.foo[2]'), ['foo', 2])
assert.deepStrictEqual(util.parsePath('.foo[2].bar'), ['foo', 2, 'bar']); assert.deepStrictEqual(util.parsePath('.foo[2].bar'), ['foo', 2, 'bar'])
assert.deepStrictEqual(util.parsePath('.foo["prop with spaces"]'), ['foo', 'prop with spaces']); assert.deepStrictEqual(util.parsePath('.foo["prop with spaces"]'), ['foo', 'prop with spaces'])
assert.deepStrictEqual(util.parsePath('.foo[\'prop with single quotes as outputted by ajv library\']'), ['foo', 'prop with single quotes as outputted by ajv library']); assert.deepStrictEqual(util.parsePath('.foo[\'prop with single quotes as outputted by ajv library\']'), ['foo', 'prop with single quotes as outputted by ajv library'])
assert.deepStrictEqual(util.parsePath('.foo["prop with . dot"]'), ['foo', 'prop with . dot']); assert.deepStrictEqual(util.parsePath('.foo["prop with . dot"]'), ['foo', 'prop with . dot'])
assert.deepStrictEqual(util.parsePath('.foo["prop with ] character"]'), ['foo', 'prop with ] character']); assert.deepStrictEqual(util.parsePath('.foo["prop with ] character"]'), ['foo', 'prop with ] character'])
assert.deepStrictEqual(util.parsePath('.foo[*].bar'), ['foo', '*', 'bar']); assert.deepStrictEqual(util.parsePath('.foo[*].bar'), ['foo', '*', 'bar'])
assert.deepStrictEqual(util.parsePath('[2]'), [2]); assert.deepStrictEqual(util.parsePath('[2]'), [2])
}); })
it ('should throw an exception in case of an invalid path', function () { it('should throw an exception in case of an invalid path', function () {
assert.throws(function () {util.parsePath('.')}, /Invalid JSON path: property name expected at index 1/); assert.throws(function () { util.parsePath('.') }, /Invalid JSON path: property name expected at index 1/)
assert.throws(function () {util.parsePath('[')}, /Invalid JSON path: unexpected end, character ] expected/); assert.throws(function () { util.parsePath('[') }, /Invalid JSON path: unexpected end, character ] expected/)
assert.throws(function () {util.parsePath('[]')}, /Invalid JSON path: array value expected at index 1/); assert.throws(function () { util.parsePath('[]') }, /Invalid JSON path: array value expected at index 1/)
assert.throws(function () {util.parsePath('.foo[ ]')}, /Invalid JSON path: array value expected at index 7/); assert.throws(function () { util.parsePath('.foo[ ]') }, /Invalid JSON path: array value expected at index 7/)
assert.throws(function () {util.parsePath('.[]')}, /Invalid JSON path: property name expected at index 1/); assert.throws(function () { util.parsePath('.[]') }, /Invalid JSON path: property name expected at index 1/)
assert.throws(function () {util.parsePath('["23]')}, /Invalid JSON path: unexpected end, character " expected/); assert.throws(function () { util.parsePath('["23]') }, /Invalid JSON path: unexpected end, character " expected/)
assert.throws(function () {util.parsePath('.foo bar')}, /Invalid JSON path: unexpected character " " at index 4/); assert.throws(function () { util.parsePath('.foo bar') }, /Invalid JSON path: unexpected character " " at index 4/)
}); })
})
});
describe('getIndexForPosition', function () { describe('getIndexForPosition', function () {
var el = { var el = {
value: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' value: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
}; }
it('happy flows - row and column in range', function () { it('happy flows - row and column in range', function () {
assert.strictEqual(util.getIndexForPosition(el, 1, 1), 0); assert.strictEqual(util.getIndexForPosition(el, 1, 1), 0)
assert.strictEqual(util.getIndexForPosition(el, 2, 1), 124); assert.strictEqual(util.getIndexForPosition(el, 2, 1), 124)
assert.strictEqual(util.getIndexForPosition(el, 3, 8), 239); assert.strictEqual(util.getIndexForPosition(el, 3, 8), 239)
assert.strictEqual(util.getIndexForPosition(el, 4, 22), 356); assert.strictEqual(util.getIndexForPosition(el, 4, 22), 356)
}); })
it('if range exceeds it should be considered as if it is last row or column length', function () { it('if range exceeds it should be considered as if it is last row or column length', function () {
assert.strictEqual(util.getIndexForPosition(el, 1, 100000), 123); assert.strictEqual(util.getIndexForPosition(el, 1, 100000), 123)
assert.strictEqual(util.getIndexForPosition(el, 100000, 1), 335); assert.strictEqual(util.getIndexForPosition(el, 100000, 1), 335)
assert.strictEqual(util.getIndexForPosition(el, 100000, 100000), 445); assert.strictEqual(util.getIndexForPosition(el, 100000, 100000), 445)
}); })
it('missing or wrong input sould return -1', function () { it('missing or wrong input sould return -1', function () {
assert.strictEqual(util.getIndexForPosition(el), -1); assert.strictEqual(util.getIndexForPosition(el), -1)
assert.strictEqual(util.getIndexForPosition(el, undefined, 1), -1); assert.strictEqual(util.getIndexForPosition(el, undefined, 1), -1)
assert.strictEqual(util.getIndexForPosition(el, 1, undefined), -1); assert.strictEqual(util.getIndexForPosition(el, 1, undefined), -1)
assert.strictEqual(util.getIndexForPosition(el, -2, -2), -1); assert.strictEqual(util.getIndexForPosition(el, -2, -2), -1)
}); })
})
});
describe('get', function () { describe('get', function () {
it('should get a nested property from an object', function () { it('should get a nested property from an object', function () {
@ -199,105 +194,105 @@ describe('util', function () {
e: undefined e: undefined
} }
assert.strictEqual(util.get(obj, ['a', 'b']), 2); assert.strictEqual(util.get(obj, ['a', 'b']), 2)
assert.strictEqual(util.get(obj, ['c']), 3); assert.strictEqual(util.get(obj, ['c']), 3)
assert.deepStrictEqual(util.get(obj, ['a']), { b: 2}); assert.deepStrictEqual(util.get(obj, ['a']), { b: 2 })
assert.strictEqual(util.get(obj, ['a', 'foo']), undefined); assert.strictEqual(util.get(obj, ['a', 'foo']), undefined)
assert.strictEqual(util.get(obj, ['a', 'foo', 'bar']), undefined); assert.strictEqual(util.get(obj, ['a', 'foo', 'bar']), undefined)
assert.strictEqual(util.get(obj, ['d']), null); assert.strictEqual(util.get(obj, ['d']), null)
assert.strictEqual(util.get(obj, ['d', 'foo', 'bar']), null); assert.strictEqual(util.get(obj, ['d', 'foo', 'bar']), null)
assert.strictEqual(util.get(obj, ['e']), undefined); assert.strictEqual(util.get(obj, ['e']), undefined)
}) })
}); })
describe('makeFieldTooltip', function () { describe('makeFieldTooltip', function () {
it('should return empty string when the schema is missing all relevant fields', function () { it('should return empty string when the schema is missing all relevant fields', function () {
assert.strictEqual(util.makeFieldTooltip({}), '') assert.strictEqual(util.makeFieldTooltip({}), '')
assert.strictEqual(util.makeFieldTooltip({additionalProperties: false}), '') assert.strictEqual(util.makeFieldTooltip({ additionalProperties: false }), '')
assert.strictEqual(util.makeFieldTooltip(), '') assert.strictEqual(util.makeFieldTooltip(), '')
}); })
it('should make tooltips with only title', function () { it('should make tooltips with only title', function () {
assert.strictEqual(util.makeFieldTooltip({title: 'foo'}), 'foo'); assert.strictEqual(util.makeFieldTooltip({ title: 'foo' }), 'foo')
}); })
it('should make tooltips with only description', function () { it('should make tooltips with only description', function () {
assert.strictEqual(util.makeFieldTooltip({description: 'foo'}), 'foo'); assert.strictEqual(util.makeFieldTooltip({ description: 'foo' }), 'foo')
}); })
it('should make tooltips with only default', function () { it('should make tooltips with only default', function () {
assert.strictEqual(util.makeFieldTooltip({default: 'foo'}), 'Default\n"foo"'); assert.strictEqual(util.makeFieldTooltip({ default: 'foo' }), 'Default\n"foo"')
}); })
it('should make tooltips with only examples', function () { it('should make tooltips with only examples', function () {
assert.strictEqual(util.makeFieldTooltip({examples: ['foo', 'bar']}), 'Examples\n"foo"\n"bar"'); assert.strictEqual(util.makeFieldTooltip({ examples: ['foo', 'bar'] }), 'Examples\n"foo"\n"bar"')
}); })
it('should make tooltips with title and description', function () { it('should make tooltips with title and description', function () {
assert.strictEqual(util.makeFieldTooltip({title: 'foo', description: 'bar'}), 'foo\nbar'); assert.strictEqual(util.makeFieldTooltip({ title: 'foo', description: 'bar' }), 'foo\nbar')
var longTitle = 'Lorem Ipsum Dolor'; var longTitle = 'Lorem Ipsum Dolor'
var longDescription = 'Duis id elit non ante gravida vestibulum non nec est. ' + var longDescription = 'Duis id elit non ante gravida vestibulum non nec est. ' +
'Proin vitae ligula at elit dapibus tempor. ' + 'Proin vitae ligula at elit dapibus tempor. ' +
'Etiam lacinia augue vel condimentum interdum. '; 'Etiam lacinia augue vel condimentum interdum. '
assert.strictEqual( assert.strictEqual(
util.makeFieldTooltip({title: longTitle, description: longDescription}), util.makeFieldTooltip({ title: longTitle, description: longDescription }),
longTitle + '\n' + longDescription longTitle + '\n' + longDescription
); )
}); })
it('should make tooltips with title, description, and examples', function () { it('should make tooltips with title, description, and examples', function () {
assert.strictEqual( assert.strictEqual(
util.makeFieldTooltip({title: 'foo', description: 'bar', examples: ['baz']}), util.makeFieldTooltip({ title: 'foo', description: 'bar', examples: ['baz'] }),
'foo\nbar\n\nExamples\n"baz"', 'foo\nbar\n\nExamples\n"baz"'
); )
}); })
it('should make tooltips with title, description, default, and examples', function () { it('should make tooltips with title, description, default, and examples', function () {
assert.strictEqual( assert.strictEqual(
util.makeFieldTooltip({title: 'foo', description: 'bar', default: 'bat', examples: ['baz']}), util.makeFieldTooltip({ title: 'foo', description: 'bar', default: 'bat', examples: ['baz'] }),
'foo\nbar\n\nDefault\n"bat"\n\nExamples\n"baz"', 'foo\nbar\n\nDefault\n"bat"\n\nExamples\n"baz"'
); )
}); })
it('should handle empty fields', function () { it('should handle empty fields', function () {
assert.strictEqual(util.makeFieldTooltip({title: '', description: 'bar'}), 'bar'); assert.strictEqual(util.makeFieldTooltip({ title: '', description: 'bar' }), 'bar')
assert.strictEqual(util.makeFieldTooltip({title: 'foo', description: ''}), 'foo'); assert.strictEqual(util.makeFieldTooltip({ title: 'foo', description: '' }), 'foo')
assert.strictEqual(util.makeFieldTooltip({description: 'bar', examples: []}), 'bar'); assert.strictEqual(util.makeFieldTooltip({ description: 'bar', examples: [] }), 'bar')
assert.strictEqual(util.makeFieldTooltip({description: 'bar', examples: ['']}), 'bar\n\nExamples\n""'); assert.strictEqual(util.makeFieldTooltip({ description: 'bar', examples: [''] }), 'bar\n\nExamples\n""')
}); })
it('should internationalize "Defaults" correctly', function () { it('should internationalize "Defaults" correctly', function () {
assert.strictEqual(util.makeFieldTooltip({default: 'foo'}, 'pt-BR'), 'Revelia\n"foo"'); assert.strictEqual(util.makeFieldTooltip({ default: 'foo' }, 'pt-BR'), 'Revelia\n"foo"')
}); })
it('should internationalize "Examples" correctly', function () { it('should internationalize "Examples" correctly', function () {
assert.strictEqual(util.makeFieldTooltip({examples: ['foo']}, 'pt-BR'), 'Exemplos\n"foo"'); assert.strictEqual(util.makeFieldTooltip({ examples: ['foo'] }, 'pt-BR'), 'Exemplos\n"foo"')
}); })
}); })
describe('getChildPaths', function () { describe('getChildPaths', function () {
it('should extract all child paths of an array containing objects', function () { it('should extract all child paths of an array containing objects', function () {
var json = [ var json = [
{ name: 'A', location: {latitude: 1, longitude: 2} }, { name: 'A', location: { latitude: 1, longitude: 2 } },
{ name: 'B', location: {latitude: 1, longitude: 2} }, { name: 'B', location: { latitude: 1, longitude: 2 } },
{ name: 'C', timestamp: 0 }, { name: 'C', timestamp: 0 }
]; ]
assert.deepStrictEqual(util.getChildPaths(json), [ assert.deepStrictEqual(util.getChildPaths(json), [
'.location.latitude', '.location.latitude',
'.location.longitude', '.location.longitude',
'.name', '.name',
'.timestamp', '.timestamp'
]) ])
}); })
it('should extract all child paths of an array containing objects, including objects', function () { it('should extract all child paths of an array containing objects, including objects', function () {
var json = [ var json = [
{ name: 'A', location: {latitude: 1, longitude: 2} }, { name: 'A', location: { latitude: 1, longitude: 2 } },
{ name: 'B', location: {latitude: 1, longitude: 2} }, { name: 'B', location: { latitude: 1, longitude: 2 } },
{ name: 'C', timestamp: 0 }, { name: 'C', timestamp: 0 }
]; ]
assert.deepStrictEqual(util.getChildPaths(json, true), [ assert.deepStrictEqual(util.getChildPaths(json, true), [
'', '',
@ -305,41 +300,41 @@ describe('util', function () {
'.location.latitude', '.location.latitude',
'.location.longitude', '.location.longitude',
'.name', '.name',
'.timestamp', '.timestamp'
]) ])
}); })
it('should extract all child paths of an array containing values', function () { it('should extract all child paths of an array containing values', function () {
var json = [ 1, 2, 3 ]; var json = [1, 2, 3]
assert.deepStrictEqual(util.getChildPaths(json), [ assert.deepStrictEqual(util.getChildPaths(json), [
'' ''
]) ])
}); })
it('should extract all child paths of a non-array', function () { it('should extract all child paths of a non-array', function () {
assert.deepStrictEqual(util.getChildPaths({a: 2, b: {c: 3}}), ['']) assert.deepStrictEqual(util.getChildPaths({ a: 2, b: { c: 3 } }), [''])
assert.deepStrictEqual(util.getChildPaths('foo'), ['']) assert.deepStrictEqual(util.getChildPaths('foo'), [''])
assert.deepStrictEqual(util.getChildPaths(123), ['']) assert.deepStrictEqual(util.getChildPaths(123), [''])
}); })
}) })
it('should test whether something is an object', function () { it('should test whether something is an object', function () {
assert.strictEqual(util.isObject({}), true); assert.strictEqual(util.isObject({}), true)
assert.strictEqual(util.isObject(new Date()), true); assert.strictEqual(util.isObject(new Date()), true)
assert.strictEqual(util.isObject([]), false); assert.strictEqual(util.isObject([]), false)
assert.strictEqual(util.isObject(2), false); assert.strictEqual(util.isObject(2), false)
assert.strictEqual(util.isObject(null), false); assert.strictEqual(util.isObject(null), false)
assert.strictEqual(util.isObject(undefined), false); assert.strictEqual(util.isObject(undefined), false)
assert.strictEqual(util.isObject(), false); assert.strictEqual(util.isObject(), false)
}); })
describe('sort', function () { describe('sort', function () {
it('should sort an array', function () { it('should sort an array', function () {
var array = [4, 1, 10, 2]; var array = [4, 1, 10, 2]
assert.deepStrictEqual(util.sort(array), [1, 2, 4, 10]); assert.deepStrictEqual(util.sort(array), [1, 2, 4, 10])
assert.deepStrictEqual(util.sort(array, '.', 'desc'), [10, 4, 2, 1]); assert.deepStrictEqual(util.sort(array, '.', 'desc'), [10, 4, 2, 1])
}); })
it('should sort an array containing objects', function () { it('should sort an array containing objects', function () {
var array = [ var array = [
@ -347,23 +342,23 @@ describe('util', function () {
{ value: 1 }, { value: 1 },
{ value: 10 }, { value: 10 },
{ value: 2 } { value: 2 }
]; ]
assert.deepStrictEqual(util.sort(array, '.value'), [ assert.deepStrictEqual(util.sort(array, '.value'), [
{ value: 1 }, { value: 1 },
{ value: 2 }, { value: 2 },
{ value: 4 }, { value: 4 },
{ value: 10 } { value: 10 }
]); ])
assert.deepStrictEqual(util.sort(array, '.value', 'desc'), [ assert.deepStrictEqual(util.sort(array, '.value', 'desc'), [
{ value: 10 }, { value: 10 },
{ value: 4 }, { value: 4 },
{ value: 2 }, { value: 2 },
{ value: 1 } { value: 1 }
]); ])
}); })
}); })
describe('sortObjectKeys', function () { describe('sortObjectKeys', function () {
it('should sort the keys of an object', function () { it('should sort the keys of an object', function () {
@ -376,16 +371,16 @@ describe('util', function () {
assert.strictEqual(JSON.stringify(util.sortObjectKeys(object)), '{"a":"a","b":"b","c":"c"}') assert.strictEqual(JSON.stringify(util.sortObjectKeys(object)), '{"a":"a","b":"b","c":"c"}')
assert.strictEqual(JSON.stringify(util.sortObjectKeys(object, 'asc')), '{"a":"a","b":"b","c":"c"}') assert.strictEqual(JSON.stringify(util.sortObjectKeys(object, 'asc')), '{"a":"a","b":"b","c":"c"}')
assert.strictEqual(JSON.stringify(util.sortObjectKeys(object, 'desc')), '{"c":"c","b":"b","a":"a"}') assert.strictEqual(JSON.stringify(util.sortObjectKeys(object, 'desc')), '{"c":"c","b":"b","a":"a"}')
}); })
}); })
it('should parse a string', function () { it('should parse a string', function () {
assert.strictEqual(util.parseString('foo'), 'foo'); assert.strictEqual(util.parseString('foo'), 'foo')
assert.strictEqual(util.parseString('234foo'), '234foo'); assert.strictEqual(util.parseString('234foo'), '234foo')
assert.strictEqual(util.parseString('2.3'), 2.3); assert.strictEqual(util.parseString('2.3'), 2.3)
assert.strictEqual(util.parseString('null'), null); assert.strictEqual(util.parseString('null'), null)
assert.strictEqual(util.parseString('true'), true); assert.strictEqual(util.parseString('true'), true)
assert.strictEqual(util.parseString('false'), false); assert.strictEqual(util.parseString('false'), false)
}) })
it('should find a unique name', function () { it('should find a unique name', function () {
@ -434,20 +429,20 @@ describe('util', function () {
}) })
it('should format a document size in a human readable way', function () { it('should format a document size in a human readable way', function () {
assert.strictEqual(util.formatSize(500), '500 B'); assert.strictEqual(util.formatSize(500), '500 B')
assert.strictEqual(util.formatSize(900), '0.9 KiB'); assert.strictEqual(util.formatSize(900), '0.9 KiB')
assert.strictEqual(util.formatSize(77.89 * 1024), '77.9 KiB'); assert.strictEqual(util.formatSize(77.89 * 1024), '77.9 KiB')
assert.strictEqual(util.formatSize(950 * 1024), '0.9 MiB'); assert.strictEqual(util.formatSize(950 * 1024), '0.9 MiB')
assert.strictEqual(util.formatSize(7.22 * 1024 * 1024), '7.2 MiB'); assert.strictEqual(util.formatSize(7.22 * 1024 * 1024), '7.2 MiB')
assert.strictEqual(util.formatSize(955.4 * 1024 * 1024), '0.9 GiB'); assert.strictEqual(util.formatSize(955.4 * 1024 * 1024), '0.9 GiB')
assert.strictEqual(util.formatSize(22.37 * 1024 * 1024 * 1024), '22.4 GiB'); assert.strictEqual(util.formatSize(22.37 * 1024 * 1024 * 1024), '22.4 GiB')
assert.strictEqual(util.formatSize(1024 * 1024 * 1024 * 1024), '1.0 TiB'); assert.strictEqual(util.formatSize(1024 * 1024 * 1024 * 1024), '1.0 TiB')
}); })
it ('should limit characters', function () { it('should limit characters', function () {
assert.strictEqual(util.limitCharacters('hello world', 11), 'hello world'); assert.strictEqual(util.limitCharacters('hello world', 11), 'hello world')
assert.strictEqual(util.limitCharacters('hello world', 5), 'hello...'); assert.strictEqual(util.limitCharacters('hello world', 5), 'hello...')
assert.strictEqual(util.limitCharacters('hello world', 100), 'hello world'); assert.strictEqual(util.limitCharacters('hello world', 100), 'hello world')
}) })
it('should compile a JSON pointer', function () { it('should compile a JSON pointer', function () {
@ -455,7 +450,7 @@ describe('util', function () {
assert.strictEqual(util.compileJSONPointer(['foo', '/~ ~/']), '/foo/~1~0 ~0~1') assert.strictEqual(util.compileJSONPointer(['foo', '/~ ~/']), '/foo/~1~0 ~0~1')
assert.strictEqual(util.compileJSONPointer(['']), '/') assert.strictEqual(util.compileJSONPointer(['']), '/')
assert.strictEqual(util.compileJSONPointer([]), '') assert.strictEqual(util.compileJSONPointer([]), '')
}); })
// TODO: thoroughly test all util methods // TODO: thoroughly test all util methods
}); })