| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898 |
- /**
- * @file jsoneditor.js
- *
- * @brief
- * JSONEditor is a web-based tool to view, edit, and format JSON.
- * It shows data a clear, editable treeview.
- *
- * Supported browsers: Chrome, Firefox, Safari, Opera, Internet Explorer 8+
- *
- * @license
- * This json editor is open sourced with the intention to use the editor as
- * a component in your own application. Not to just copy and monetize the editor
- * as it is.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- *
- * Copyright (c) 2011-2012 Jos de Jong, http://jsoneditoronline.org
- *
- * @author Jos de Jong, <wjosdejong@gmail.com>
- * @date 2012-12-08
- */
- // Internet Explorer 8 and older does not support Array.indexOf,
- // so we define it here in that case
- // http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/
- if (!Array.prototype.indexOf) {
- Array.prototype.indexOf = function(obj) {
- for (var i = 0; i < this.length; i++) {
- if (this[i] == obj) {
- return i;
- }
- }
- return -1;
- };
- }
- // Internet Explorer 8 and older does not support Array.forEach,
- // so we define it here in that case
- // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach
- if (!Array.prototype.forEach) {
- Array.prototype.forEach = function(fn, scope) {
- for (var i = 0, len = this.length; i < len; ++i) {
- fn.call(scope || this, this[i], i, this);
- }
- };
- }
- // define variable JSON, needed for correct error handling on IE7 and older
- var JSON;
- /**
- * JSONEditor
- * @param {Element} container Container element
- * @param {Object} [options] Object with options. available options:
- * {String} mode Editor mode. Available values:
- * 'editor' (default), 'viewer'.
- * {Boolean} search Enable search box.
- * True by default
- * {Boolean} history Enable history (undo/redo).
- * True by default
- * {function} change Callback method, triggered
- * on change of contents
- * {String} name Field name for the root node.
- * @param {Object | undefined} json JSON object
- */
- JSONEditor = function(container, options, json) {
- // check availability of JSON parser (not available in IE7 and older)
- if (!JSON) {
- throw new Error(
- "您当前使用的浏览器不支持 JSON. \n\n" +
- "请下载安装最新版本的浏览, 本站推荐Google Chrome.\n" +
- "(PS: 当前主流浏览器都支持JSON)."
- );
- }
- if (!container) {
- throw new Error("没有提供容器元素.");
- }
- this.container = container;
- this.dom = {};
- this._setOptions(options);
- if (this.options.history && this.editable) {
- this.history = new JSONEditor.History(this);
- }
- this._createFrame();
- this._createTable();
- this.set(json || {});
- };
- /**
- * Initialize and set default options
- * @param {Object} [options] Object with options. available options:
- * {String} mode Editor mode. Available values:
- * 'editor' (default), 'viewer'.
- * {Boolean} search Enable search box.
- * True by default.
- * {Boolean} history Enable history (undo/redo).
- * True by default.
- * {function} change Callback method, triggered
- * on change of contents.
- * {String} name Field name for the root node.
- * @private
- */
- JSONEditor.prototype._setOptions = function(options) {
- this.options = {
- search: true,
- history: true,
- mode: "editor",
- name: undefined // field name of root node
- };
- // copy all options
- if (options) {
- for (var prop in options) {
- if (options.hasOwnProperty(prop)) {
- this.options[prop] = options[prop];
- }
- }
- // check for deprecated options
- if (options.enableSearch) {
- // deprecated since version 1.6.0, 2012-11-03
- this.options.search = options.enableSearch;
- // console.log('WARNING: Option "enableSearch" is deprecated. Use "search" instead.');
- }
- if (options.enableHistory) {
- // deprecated since version 1.6.0, 2012-11-03
- this.options.search = options.enableHistory;
- // console.log('WARNING: Option "enableHistory" is deprecated. Use "history" instead.');
- }
- }
- // interpret the options
- this.editable = this.options.mode != "viewer";
- };
- // node currently being edited
- JSONEditor.focusNode = undefined;
- /**
- * Set JSON object in editor
- * @param {Object | undefined} json JSON data
- * @param {String} [name] Optional field name for the root node.
- * Can also be set using setName(name).
- */
- JSONEditor.prototype.set = function(json, name) {
- // adjust field name for root node
- if (name) {
- this.options.name = name;
- }
- // verify if json is valid JSON, ignore when a function
- if (json instanceof Function || json === undefined) {
- this.clear();
- } else {
- this.content.removeChild(this.table); // Take the table offline
- // replace the root node
- var params = {
- field: this.options.name,
- value: json
- };
- var node = new JSONEditor.Node(this, params);
- this._setRoot(node);
- // expand
- var recurse = false;
- this.node.expand(recurse);
- this.content.appendChild(this.table); // Put the table online again
- }
- // TODO: maintain history, store last state and previous document
- if (this.history) {
- this.history.clear();
- }
- };
- /**
- * Get JSON object from editor
- * @return {Object | undefined} json
- */
- JSONEditor.prototype.get = function() {
- // remove focus from currently edited node
- if (JSONEditor.focusNode) {
- JSONEditor.focusNode.blur();
- }
- if (this.node) {
- return this.node.getValue();
- } else {
- return undefined;
- }
- };
- /**
- * Set a field name for the root node.
- * @param {String | undefined} name
- */
- JSONEditor.prototype.setName = function(name) {
- this.options.name = name;
- if (this.node) {
- this.node.updateField(this.options.name);
- }
- };
- /**
- * Get the field name for the root node.
- * @return {String | undefined} name
- */
- JSONEditor.prototype.getName = function() {
- return this.options.name;
- };
- /**
- * Remove the root node from the editor
- */
- JSONEditor.prototype.clear = function() {
- if (this.node) {
- this.node.collapse();
- this.tbody.removeChild(this.node.getDom());
- delete this.node;
- }
- };
- /**
- * Set the root node for the json editor
- * @param {JSONEditor.Node} node
- * @private
- */
- JSONEditor.prototype._setRoot = function(node) {
- this.clear();
- this.node = node;
- // append to the dom
- this.tbody.appendChild(node.getDom());
- };
- /**
- * Search text in all nodes
- * The nodes will be expanded when the text is found one of its childs,
- * else it will be collapsed. Searches are case insensitive.
- * @param {String} text
- * @return {Object[]} results Array with nodes containing the search results
- * The result objects contains fields:
- * - {JSONEditor.Node} node,
- * - {String} elem the dom element name where
- * the result is found ('field' or
- * 'value')
- */
- JSONEditor.prototype.search = function(text) {
- var results;
- if (this.node) {
- this.content.removeChild(this.table); // Take the table offline
- results = this.node.search(text);
- this.content.appendChild(this.table); // Put the table online again
- } else {
- results = [];
- }
- return results;
- };
- /**
- * Expand all nodes
- */
- JSONEditor.prototype.expandAll = function() {
- if (this.node) {
- this.content.removeChild(this.table); // Take the table offline
- this.node.expand();
- this.content.appendChild(this.table); // Put the table online again
- }
- };
- /**
- * Collapse all nodes
- */
- JSONEditor.prototype.collapseAll = function() {
- if (this.node) {
- this.content.removeChild(this.table); // Take the table offline
- this.node.collapse();
- this.content.appendChild(this.table); // Put the table online again
- }
- };
- /**
- * The method onChange is called whenever a field or value is changed, created,
- * deleted, duplicated, etc.
- * @param {String} action Change action. Available values: "editField",
- * "editValue", "changeType", "appendNode",
- * "removeNode", "duplicateNode", "moveNode", "expand",
- * "collapse".
- * @param {Object} params Object containing parameters describing the change.
- * The parameters in params depend on the action (for
- * example for "editValue" the Node, old value, and new
- * value are provided). params contains all information
- * needed to undo or redo the action.
- */
- JSONEditor.prototype.onAction = function(action, params) {
- // add an action to the history
- if (this.history) {
- this.history.add(action, params);
- }
- // trigger the onChange callback
- if (this.options.change) {
- try {
- this.options.change();
- } catch (err) {
- //console.log('Error in change callback: ', err);
- }
- }
- };
- /**
- * Set the focus to the JSONEditor. A hidden input field will be created
- * which captures key events
- */
- // TODO: use the focus method?
- JSONEditor.prototype.focus = function() {
- /*
- if (!this.dom.focus) {
- this.dom.focus = document.createElement('input');
- this.dom.focus.className = 'jsoneditor-hidden-focus';
- var editor = this;
- this.dom.focus.onblur = function () {
- // remove itself
- if (editor.dom.focus) {
- var focus = editor.dom.focus;
- delete editor.dom.focus;
- editor.frame.removeChild(focus);
- }
- };
- // attach the hidden input box to the DOM
- if (this.frame.firstChild) {
- this.frame.insertBefore(this.dom.focus, this.frame.firstChild);
- }
- else {
- this.frame.appendChild(this.dom.focus);
- }
- }
- this.dom.focus.focus();
- */
- };
- /**
- * Adjust the scroll position such that given top position is shown at 1/4
- * of the window height.
- * @param {Number} top
- */
- JSONEditor.prototype.scrollTo = function(top) {
- var content = this.content;
- if (content) {
- // cancel any running animation
- var editor = this;
- if (editor.animateTimeout) {
- clearTimeout(editor.animateTimeout);
- delete editor.animateTimeout;
- }
- // calculate final scroll position
- var height = content.clientHeight;
- var bottom = content.scrollHeight - height;
- var finalScrollTop = Math.min(Math.max(top - height / 4, 0), bottom);
- // animate towards the new scroll position
- var animate = function() {
- var scrollTop = content.scrollTop;
- var diff = finalScrollTop - scrollTop;
- if (Math.abs(diff) > 3) {
- content.scrollTop += diff / 3;
- editor.animateTimeout = setTimeout(animate, 50);
- }
- };
- animate();
- }
- };
- /**
- * @constructor JSONEditor.History
- * Store action history, enables undo and redo
- * @param {JSONEditor} editor
- */
- JSONEditor.History = function(editor) {
- this.editor = editor;
- this.clear();
- // map with all supported actions
- this.actions = {
- editField: {
- undo: function(obj) {
- obj.params.node.updateField(obj.params.oldValue);
- },
- redo: function(obj) {
- obj.params.node.updateField(obj.params.newValue);
- }
- },
- editValue: {
- undo: function(obj) {
- obj.params.node.updateValue(obj.params.oldValue);
- },
- redo: function(obj) {
- obj.params.node.updateValue(obj.params.newValue);
- }
- },
- appendNode: {
- undo: function(obj) {
- obj.params.parent.removeChild(obj.params.node);
- },
- redo: function(obj) {
- obj.params.parent.appendChild(obj.params.node);
- }
- },
- removeNode: {
- undo: function(obj) {
- var parent = obj.params.parent;
- var beforeNode = parent.childs[obj.params.index] || parent.append;
- parent.insertBefore(obj.params.node, beforeNode);
- },
- redo: function(obj) {
- obj.params.parent.removeChild(obj.params.node);
- }
- },
- duplicateNode: {
- undo: function(obj) {
- obj.params.parent.removeChild(obj.params.clone);
- },
- redo: function(obj) {
- // TODO: insert after instead of insert before
- obj.params.parent.insertBefore(obj.params.clone, obj.params.node);
- }
- },
- changeType: {
- undo: function(obj) {
- obj.params.node.changeType(obj.params.oldType);
- },
- redo: function(obj) {
- obj.params.node.changeType(obj.params.newType);
- }
- },
- moveNode: {
- undo: function(obj) {
- obj.params.startParent.moveTo(obj.params.node, obj.params.startIndex);
- },
- redo: function(obj) {
- obj.params.endParent.moveTo(obj.params.node, obj.params.endIndex);
- }
- }
- // TODO: restore the original caret position and selection with each undo
- // TODO: implement history for actions "expand", "collapse", "scroll", "setDocument"
- };
- };
- /**
- * The method onChange is executed when the History is changed, and can
- * be overloaded.
- */
- JSONEditor.History.prototype.onChange = function() {};
- /**
- * Add a new action to the history
- * @param {String} action The executed action. Available actions: "editField",
- * "editValue", "changeType", "appendNode",
- * "removeNode", "duplicateNode", "moveNode"
- * @param {Object} params Object containing parameters describing the change.
- * The parameters in params depend on the action (for
- * example for "editValue" the Node, old value, and new
- * value are provided). params contains all information
- * needed to undo or redo the action.
- */
- JSONEditor.History.prototype.add = function(action, params) {
- this.index++;
- this.history[this.index] = {
- action: action,
- params: params,
- timestamp: new Date()
- };
- // remove redo actions which are invalid now
- if (this.index < this.history.length - 1) {
- this.history.splice(this.index + 1, this.history.length - this.index - 1);
- }
- // fire onchange event
- this.onChange();
- };
- /**
- * Clear history
- */
- JSONEditor.History.prototype.clear = function() {
- this.history = [];
- this.index = -1;
- // fire onchange event
- this.onChange();
- };
- /**
- * Check if there is an action available for undo
- * @return {Boolean} canUndo
- */
- JSONEditor.History.prototype.canUndo = function() {
- return this.index >= 0;
- };
- /**
- * Check if there is an action available for redo
- * @return {Boolean} canRedo
- */
- JSONEditor.History.prototype.canRedo = function() {
- return this.index < this.history.length - 1;
- };
- /**
- * Undo the last action
- */
- JSONEditor.History.prototype.undo = function() {
- if (this.canUndo()) {
- var obj = this.history[this.index];
- if (obj) {
- var action = this.actions[obj.action];
- if (action && action.undo) {
- action.undo(obj);
- } else {
- //console.log('Error: unknown action "' + obj.action + '"');
- }
- }
- this.index--;
- // fire onchange event
- this.onChange();
- }
- };
- /**
- * Redo the last action
- */
- JSONEditor.History.prototype.redo = function() {
- if (this.canRedo()) {
- this.index++;
- var obj = this.history[this.index];
- if (obj) {
- if (obj) {
- var action = this.actions[obj.action];
- if (action && action.redo) {
- action.redo(obj);
- } else {
- //console.log('Error: unknown action "' + obj.action + '"');
- }
- }
- }
- // fire onchange event
- this.onChange();
- }
- };
- /**
- * @constructor JSONEditor.Node
- * Create a new Node
- * @param {JSONEditor} editor
- * @param {Object} params Can contain parameters: field, fieldEditable, value.
- */
- JSONEditor.Node = function(editor, params) {
- this.editor = editor;
- this.dom = {};
- this.expanded = false;
- if (params && params instanceof Object) {
- this.setField(params.field, params.fieldEditable);
- this.setValue(params.value);
- } else {
- this.setField();
- this.setValue();
- }
- };
- /**
- * Set parent node
- * @param {JSONEditor.Node} parent
- */
- JSONEditor.Node.prototype.setParent = function(parent) {
- this.parent = parent;
- };
- /**
- * Get parent node. Returns undefined when no parent node is set.
- * @return {JSONEditor.Node} parent
- */
- JSONEditor.Node.prototype.getParent = function() {
- return this.parent;
- };
- /**
- * Set field
- * @param {String} field
- * @param {boolean} fieldEditable
- */
- JSONEditor.Node.prototype.setField = function(field, fieldEditable) {
- this.field = field;
- this.fieldEditable = fieldEditable == true;
- };
- /**
- * Get field
- * @return {String}
- */
- JSONEditor.Node.prototype.getField = function() {
- if (this.field === undefined) {
- this._getDomField();
- }
- return this.field;
- };
- /**
- * Set value. Value is a JSON structure or an element String, Boolean, etc.
- * @param {*} value
- */
- JSONEditor.Node.prototype.setValue = function(value) {
- var childValue, child;
- // first clear all current childs (if any)
- var childs = this.childs;
- if (childs) {
- while (childs.length) {
- this.removeChild(childs[0]);
- }
- }
- // TODO: remove the DOM of this Node
- this.type = this._getType(value);
- if (this.type == "array") {
- // array
- this.childs = [];
- for (var i = 0, iMax = value.length; i < iMax; i++) {
- childValue = value[i];
- if (childValue !== undefined && !(childValue instanceof Function)) {
- // ignore undefined and functions
- child = new JSONEditor.Node(this.editor, {
- value: childValue
- });
- this.appendChild(child);
- }
- }
- this.value = "";
- } else if (this.type == "object") {
- // object
- this.childs = [];
- for (var childField in value) {
- if (value.hasOwnProperty(childField)) {
- childValue = value[childField];
- if (childValue !== undefined && !(childValue instanceof Function)) {
- // ignore undefined and functions
- child = new JSONEditor.Node(this.editor, {
- field: childField,
- value: childValue
- });
- this.appendChild(child);
- }
- }
- }
- this.value = "";
- } else {
- // value
- this.childs = undefined;
- this.value = value;
- /* TODO
- if (typeof(value) == 'string') {
- var escValue = JSON.stringify(value);
- this.value = escValue.substring(1, escValue.length - 1);
- console.log('check', value, this.value);
- }
- else {
- this.value = value;
- }
- */
- }
- };
- /**
- * Get value. Value is a JSON structure
- * @return {*} value
- */
- JSONEditor.Node.prototype.getValue = function() {
- //var childs, i, iMax;
- if (this.type == "array") {
- var arr = [];
- this.childs.forEach(function(child) {
- arr.push(child.getValue());
- });
- return arr;
- } else if (this.type == "object") {
- var obj = {};
- this.childs.forEach(function(child) {
- obj[child.getField()] = child.getValue();
- });
- return obj;
- } else {
- if (this.value === undefined) {
- this._getDomValue();
- }
- return this.value;
- }
- };
- /**
- * Get the nesting level of this node
- * @return {Number} level
- */
- JSONEditor.Node.prototype.getLevel = function() {
- return this.parent ? this.parent.getLevel() + 1 : 0;
- };
- /**
- * Create a clone of a node
- * The complete state of a clone is copied, including whether it is expanded or
- * not. The DOM elements are not cloned.
- * @return {JSONEditor.Node} clone
- */
- JSONEditor.Node.prototype.clone = function() {
- var clone = new JSONEditor.Node(this.editor);
- clone.type = this.type;
- clone.field = this.field;
- clone.fieldInnerText = this.fieldInnerText;
- clone.fieldEditable = this.fieldEditable;
- clone.value = this.value;
- clone.valueInnerText = this.valueInnerText;
- clone.expanded = this.expanded;
- if (this.childs) {
- // an object or array
- var cloneChilds = [];
- this.childs.forEach(function(child) {
- var childClone = child.clone();
- childClone.setParent(clone);
- cloneChilds.push(childClone);
- });
- clone.childs = cloneChilds;
- } else {
- // a value
- clone.childs = undefined;
- }
- return clone;
- };
- /**
- * Expand this node and optionally its childs.
- * @param {boolean} recurse Optional recursion, true by default. When
- * true, all childs will be expanded recursively
- */
- JSONEditor.Node.prototype.expand = function(recurse) {
- if (!this.childs) {
- return;
- }
- // set this node expanded
- this.expanded = true;
- if (this.dom.expand) {
- this.dom.expand.className = "jsoneditor-expanded";
- }
- this.showChilds();
- if (recurse != false) {
- this.childs.forEach(function(child) {
- child.expand(recurse);
- });
- }
- };
- /**
- * Collapse this node and optionally its childs.
- * @param {Number} recurse Optional recursion, true by default. When
- * true, all childs will be collapsed recursively
- */
- JSONEditor.Node.prototype.collapse = function(recurse) {
- if (!this.childs) {
- return;
- }
- this.hideChilds();
- // collapse childs in case of recurse
- if (recurse != false) {
- this.childs.forEach(function(child) {
- child.collapse(recurse);
- });
- }
- // make this node collapsed
- if (this.dom.expand) {
- this.dom.expand.className = "jsoneditor-collapsed";
- }
- this.expanded = false;
- };
- /**
- * Recursively show all childs when they are expanded
- */
- JSONEditor.Node.prototype.showChilds = function() {
- var childs = this.childs;
- if (!childs) {
- return;
- }
- if (!this.expanded) {
- return;
- }
- var tr = this.dom.tr;
- var table = tr ? tr.parentNode : undefined;
- if (table) {
- // show row with append button
- var append = this.getAppend();
- var nextTr = tr.nextSibling;
- if (nextTr) {
- table.insertBefore(append, nextTr);
- } else {
- table.appendChild(append);
- }
- // show childs
- this.childs.forEach(function(child) {
- table.insertBefore(child.getDom(), append);
- child.showChilds();
- });
- }
- };
- /**
- * Hide the node with all its childs
- */
- JSONEditor.Node.prototype.hide = function() {
- var tr = this.dom.tr;
- var table = tr ? tr.parentNode : undefined;
- if (table) {
- table.removeChild(tr);
- }
- this.hideChilds();
- };
- /**
- * Recursively hide all childs
- */
- JSONEditor.Node.prototype.hideChilds = function() {
- var childs = this.childs;
- if (!childs) {
- return;
- }
- if (!this.expanded) {
- return;
- }
- // hide append row
- var append = this.getAppend();
- if (append.parentNode) {
- append.parentNode.removeChild(append);
- }
- // hide childs
- this.childs.forEach(function(child) {
- child.hide();
- });
- };
- /**
- * Add a new child to the node.
- * Only applicable when Node value is of type array or object
- * @param {JSONEditor.Node} node
- */
- JSONEditor.Node.prototype.appendChild = function(node) {
- if (this.type == "array" || this.type == "object") {
- // adjust the link to the parent
- node.setParent(this);
- node.fieldEditable = this.type == "object";
- if (this.type == "array") {
- node.index = this.childs.length;
- }
- this.childs.push(node);
- if (this.expanded) {
- // insert into the DOM, before the appendRow
- var newtr = node.getDom();
- var appendTr = this.getAppend();
- var table = appendTr ? appendTr.parentNode : undefined;
- if (appendTr && table) {
- table.insertBefore(newtr, appendTr);
- }
- node.showChilds();
- }
- this.updateDom({
- updateIndexes: true
- });
- node.updateDom({
- recurse: true
- });
- }
- };
- /**
- * Move a node from its current parent to this node
- * Only applicable when Node value is of type array or object
- * @param {JSONEditor.Node} node
- * @param {JSONEditor.Node} beforeNode
- */
- JSONEditor.Node.prototype.moveBefore = function(node, beforeNode) {
- if (this.type == "array" || this.type == "object") {
- // create a temporary row, to prevent the scroll position from jumping
- // when removing the node
- var tbody = this.dom.tr ? this.dom.tr.parentNode : undefined;
- if (tbody) {
- var trTemp = document.createElement("tr");
- trTemp.style.height = tbody.clientHeight + "px";
- tbody.appendChild(trTemp);
- }
- var parent = node.getParent();
- if (parent) {
- parent.removeChild(node);
- }
- if (beforeNode instanceof JSONEditor.AppendNode) {
- this.appendChild(node);
- } else {
- this.insertBefore(node, beforeNode);
- }
- if (tbody) {
- tbody.removeChild(trTemp);
- }
- }
- };
- /**
- * Move a node from its current parent to this node
- * Only applicable when Node value is of type array or object.
- * If index is out of range, the node will be appended to the end
- * @param {JSONEditor.Node} node
- * @param {Number} index
- */
- JSONEditor.Node.prototype.moveTo = function(node, index) {
- if (node.parent == this) {
- // same parent
- var currentIndex = this.childs.indexOf(node);
- if (currentIndex < index) {
- // compensate the index for removal of the node itself
- index++;
- }
- }
- var beforeNode = this.childs[index] || this.append;
- this.moveBefore(node, beforeNode);
- };
- /**
- * Insert a new child before a given node
- * Only applicable when Node value is of type array or object
- * @param {JSONEditor.Node} node
- * @param {JSONEditor.Node} beforeNode
- */
- JSONEditor.Node.prototype.insertBefore = function(node, beforeNode) {
- if (this.type == "array" || this.type == "object") {
- if (beforeNode == this.append) {
- // append to the child nodes
- // adjust the link to the parent
- node.setParent(this);
- node.fieldEditable = this.type == "object";
- this.childs.push(node);
- } else {
- // insert before a child node
- var index = this.childs.indexOf(beforeNode);
- if (index == -1) {
- throw new Error("节点未找到.");
- }
- // adjust the link to the parent
- node.setParent(this);
- node.fieldEditable = this.type == "object";
- this.childs.splice(index, 0, node);
- }
- if (this.expanded) {
- // insert into the DOM
- var newTr = node.getDom();
- var nextTr = beforeNode.getDom();
- var table = nextTr ? nextTr.parentNode : undefined;
- if (nextTr && table) {
- table.insertBefore(newTr, nextTr);
- }
- node.showChilds();
- }
- this.updateDom({
- updateIndexes: true
- });
- node.updateDom({
- recurse: true
- });
- }
- };
- /**
- * Search in this node
- * The node will be expanded when the text is found one of its childs, else
- * it will be collapsed. Searches are case insensitive.
- * @param {String} text
- * @return {JSONEditor.Node[]} results Array with nodes containing the search text
- */
- JSONEditor.Node.prototype.search = function(text) {
- var results = [];
- var index;
- var search = text ? text.toLowerCase() : undefined;
- // delete old search data
- delete this.searchField;
- delete this.searchValue;
- // search in field
- if (this.field != undefined) {
- var field = String(this.field).toLowerCase();
- index = field.indexOf(search);
- if (index != -1) {
- this.searchField = true;
- results.push({
- node: this,
- elem: "field"
- });
- }
- // update dom
- this._updateDomField();
- }
- // search in value
- if (this.type == "array" || this.type == "object") {
- // array, object
- // search the nodes childs
- if (this.childs) {
- var childResults = [];
- this.childs.forEach(function(child) {
- childResults = childResults.concat(child.search(text));
- });
- results = results.concat(childResults);
- }
- // update dom
- if (search != undefined) {
- var recurse = false;
- if (childResults.length == 0) {
- this.collapse(recurse);
- } else {
- this.expand(recurse);
- }
- }
- } else {
- // string, auto
- if (this.value != undefined) {
- var value = String(this.value).toLowerCase();
- index = value.indexOf(search);
- if (index != -1) {
- this.searchValue = true;
- results.push({
- node: this,
- elem: "value"
- });
- }
- }
- // update dom
- this._updateDomValue();
- }
- return results;
- };
- /**
- * Move the scroll position such that this node is in the visible area.
- * The node will not get the focus
- */
- JSONEditor.Node.prototype.scrollTo = function() {
- if (!this.dom.tr || !this.dom.tr.parentNode) {
- // if the node is not visible, expand its parents
- var parent = this.parent;
- var recurse = false;
- while (parent) {
- parent.expand(recurse);
- parent = parent.parent;
- }
- }
- if (this.dom.tr && this.dom.tr.parentNode) {
- this.editor.scrollTo(this.dom.tr.offsetTop);
- }
- };
- /**
- * Set focus to the value of this node
- * @param {String} [field] The field name of the element to get the focus
- * available values: 'field', 'value'
- */
- JSONEditor.Node.prototype.focus = function(field) {
- if (this.dom.tr && this.dom.tr.parentNode) {
- if (field != "value" && this.fieldEditable) {
- var domField = this.dom.field;
- if (domField) {
- domField.focus();
- }
- } else {
- var domValue = this.dom.value;
- if (domValue) {
- domValue.focus();
- }
- }
- }
- };
- /**
- * Update the values from the DOM field and value of this node
- */
- JSONEditor.Node.prototype.blur = function() {
- // retrieve the actual field and value from the DOM.
- this._getDomValue(false);
- this._getDomField(false);
- };
- /**
- * Duplicate given child node
- * new structure will be added right before the cloned node
- * @param {JSONEditor.Node} node the childNode to be duplicated
- * @return {JSONEditor.Node} clone the clone of the node
- * @private
- */
- JSONEditor.Node.prototype._duplicate = function(node) {
- var clone = node.clone();
- /* TODO: adjust the field name (to prevent equal field names)
- if (this.type == 'object') {
- }
- */
- // TODO: insert after instead of insert before
- this.insertBefore(clone, node);
- return clone;
- };
- /**
- * Check if given node is a child. The method will check recursively to find
- * this node.
- * @param {JSONEditor.Node} node
- * @return {boolean} containsNode
- */
- JSONEditor.Node.prototype.containsNode = function(node) {
- if (this == node) {
- return true;
- }
- var childs = this.childs;
- if (childs) {
- // TOOD: use the js5 Array.some() here?
- for (var i = 0, iMax = childs.length; i < iMax; i++) {
- if (childs[i].containsNode(node)) {
- return true;
- }
- }
- }
- return false;
- };
- /**
- * Move given node into this node
- * @param {JSONEditor.Node} node the childNode to be moved
- * @param {JSONEditor.Node} beforeNode node will be inserted before given
- * node. If no beforeNode is given,
- * the node is appended at the end
- * @private
- */
- JSONEditor.Node.prototype._move = function(node, beforeNode) {
- if (node == beforeNode) {
- // nothing to do...
- return;
- }
- // check if this node is not a child of the node to be moved here
- if (node.containsNode(this)) {
- throw new Error("不能把区域移动到自身的子节点.");
- }
- // remove the original node
- if (node.parent) {
- node.parent.removeChild(node);
- }
- // create a clone of the node
- var clone = node.clone();
- node.clearDom();
- // insert or append the node
- if (beforeNode) {
- this.insertBefore(clone, beforeNode);
- } else {
- this.appendChild(clone);
- }
- /* TODO: adjust the field name (to prevent equal field names)
- if (this.type == 'object') {
- }
- */
- };
- /**
- * Remove a child from the node.
- * Only applicable when Node value is of type array or object
- * @param {JSONEditor.Node} node The child node to be removed;
- * @return {JSONEditor.Node | undefined} node The removed node on success,
- * else undefined
- */
- JSONEditor.Node.prototype.removeChild = function(node) {
- if (this.childs) {
- var index = this.childs.indexOf(node);
- if (index != -1) {
- node.hide();
- // delete old search results
- delete node.searchField;
- delete node.searchValue;
- var removedNode = this.childs.splice(index, 1)[0];
- this.updateDom({
- updateIndexes: true
- });
- return removedNode;
- }
- }
- return undefined;
- };
- /**
- * Remove a child node node from this node
- * This method is equal to Node.removeChild, except that _remove firex an
- * onChange event.
- * @param {JSONEditor.Node} node
- * @private
- */
- JSONEditor.Node.prototype._remove = function(node) {
- this.removeChild(node);
- };
- /**
- * Change the type of the value of this Node
- * @param {String} newType
- */
- JSONEditor.Node.prototype.changeType = function(newType) {
- var oldType = this.type;
- if (
- (newType == "string" || newType == "auto") &&
- (oldType == "string" || oldType == "auto")
- ) {
- // this is an easy change
- this.type = newType;
- } else {
- // change from array to object, or from string/auto to object/array
- var table = this.dom.tr ? this.dom.tr.parentNode : undefined;
- var lastTr;
- if (this.expanded) {
- lastTr = this.getAppend();
- } else {
- lastTr = this.getDom();
- }
- var nextTr = lastTr && lastTr.parentNode ? lastTr.nextSibling : undefined;
- // hide current field and all its childs
- this.hide();
- this.clearDom();
- // adjust the field and the value
- this.type = newType;
- // adjust childs
- if (newType == "object") {
- if (!this.childs) {
- this.childs = [];
- }
- this.childs.forEach(function(child, index) {
- child.clearDom();
- delete child.index;
- child.fieldEditable = true;
- if (child.field == undefined) {
- child.field = index;
- }
- });
- if (oldType == "string" || oldType == "auto") {
- this.expanded = true;
- }
- } else if (newType == "array") {
- if (!this.childs) {
- this.childs = [];
- }
- this.childs.forEach(function(child, index) {
- child.clearDom();
- child.fieldEditable = false;
- child.index = index;
- });
- if (oldType == "string" || oldType == "auto") {
- this.expanded = true;
- }
- } else {
- this.expanded = false;
- }
- // create new DOM
- if (table) {
- if (nextTr) {
- table.insertBefore(this.getDom(), nextTr);
- } else {
- table.appendChild(this.getDom());
- }
- }
- this.showChilds();
- }
- if (newType == "auto" || newType == "string") {
- // cast value to the correct type
- if (newType == "string") {
- this.value = String(this.value);
- } else {
- this.value = this._stringCast(String(this.value));
- }
- this.focus();
- }
- this.updateDom({
- updateIndexes: true
- });
- };
- /**
- * Retrieve value from DOM
- * @param {boolean} silent. If true (default), no errors will be thrown in
- * case of invalid data
- * @private
- */
- JSONEditor.Node.prototype._getDomValue = function(silent) {
- if (this.dom.value && this.type != "array" && this.type != "object") {
- this.valueInnerText = JSONEditor.getInnerText(this.dom.value);
- }
- if (this.valueInnerText != undefined) {
- try {
- // retrieve the value
- var value;
- if (this.type == "string") {
- value = this._unescapeHTML(this.valueInnerText);
- } else {
- var str = this._unescapeHTML(this.valueInnerText);
- value = this._stringCast(str);
- }
- if (value !== this.value) {
- var oldValue = this.value;
- this.value = value;
- this.editor.onAction("editValue", {
- node: this,
- oldValue: oldValue,
- newValue: value
- });
- }
- } catch (err) {
- this.value = undefined;
- // TODO: sent an action with the new, invalid value?
- if (silent != true) {
- throw err;
- }
- }
- }
- };
- /**
- * Update dom value:
- * - the text color of the value, depending on the type of the value
- * - the height of the field, depending on the width
- * - background color in case it is empty
- * @private
- */
- JSONEditor.Node.prototype._updateDomValue = function() {
- var domValue = this.dom.value;
- if (domValue) {
- // set text color depending on value type
- var v = this.value;
- var t = this.type == "auto" ? typeof v : this.type;
- var color = "";
- if (t == "string") {
- color = "green";
- } else if (t == "number") {
- color = "red";
- } else if (t == "boolean") {
- color = "blue";
- } else if (this.type == "object" || this.type == "array") {
- // note: typeof(null)=="object", therefore check this.type instead of t
- color = "";
- } else if (v === null) {
- color = "purple";
- } else if (v === undefined) {
- // invalid value
- color = "green";
- }
- domValue.style.color = color;
- // make backgound color lightgray when empty
- var isEmpty =
- String(this.value) == "" && this.type != "array" && this.type != "object";
- if (isEmpty) {
- JSONEditor.addClassName(domValue, "jsoneditor-empty");
- } else {
- JSONEditor.removeClassName(domValue, "jsoneditor-empty");
- }
- // highlight when there is a search result
- if (this.searchValueActive) {
- JSONEditor.addClassName(domValue, "jsoneditor-search-highlight-active");
- } else {
- JSONEditor.removeClassName(
- domValue,
- "jsoneditor-search-highlight-active"
- );
- }
- if (this.searchValue) {
- JSONEditor.addClassName(domValue, "jsoneditor-search-highlight");
- } else {
- JSONEditor.removeClassName(domValue, "jsoneditor-search-highlight");
- }
- // strip formatting from the contents of the editable div
- JSONEditor.stripFormatting(domValue);
- }
- };
- /**
- * Update dom field:
- * - the text color of the field, depending on the text
- * - the height of the field, depending on the width
- * - background color in case it is empty
- * @private
- */
- JSONEditor.Node.prototype._updateDomField = function() {
- var domField = this.dom.field;
- if (domField) {
- // make backgound color lightgray when empty
- var isEmpty = String(this.field) == "";
- if (isEmpty) {
- JSONEditor.addClassName(domField, "jsoneditor-empty");
- } else {
- JSONEditor.removeClassName(domField, "jsoneditor-empty");
- }
- // highlight when there is a search result
- if (this.searchFieldActive) {
- JSONEditor.addClassName(domField, "jsoneditor-search-highlight-active");
- } else {
- JSONEditor.removeClassName(
- domField,
- "jsoneditor-search-highlight-active"
- );
- }
- if (this.searchField) {
- JSONEditor.addClassName(domField, "jsoneditor-search-highlight");
- } else {
- JSONEditor.removeClassName(domField, "jsoneditor-search-highlight");
- }
- // strip formatting from the contents of the editable div
- JSONEditor.stripFormatting(domField);
- }
- };
- /**
- * Retrieve field from DOM
- * @param {boolean} silent. If true (default), no errors will be thrown in
- * case of invalid data
- * @private
- */
- JSONEditor.Node.prototype._getDomField = function(silent) {
- if (this.dom.field && this.fieldEditable) {
- this.fieldInnerText = JSONEditor.getInnerText(this.dom.field);
- }
- if (this.fieldInnerText != undefined) {
- try {
- var field = this._unescapeHTML(this.fieldInnerText);
- if (field !== this.field) {
- var oldField = this.field;
- this.field = field;
- this.editor.onAction("editField", {
- node: this,
- oldValue: oldField,
- newValue: field
- });
- }
- } catch (err) {
- this.field = undefined;
- // TODO: sent an action here, with the new, invalid value?
- if (silent != true) {
- throw err;
- }
- }
- }
- };
- /**
- * Clear the dom of the node
- */
- JSONEditor.Node.prototype.clearDom = function() {
- // TODO: hide the node first?
- //this.hide();
- // TOOD: recursively clear dom?
- this.dom = {};
- };
- /**
- * Get the HTML DOM TR element of the node.
- * The dom will be generated when not yet created
- * @return {Element} tr HTML DOM TR Element
- */
- JSONEditor.Node.prototype.getDom = function() {
- var dom = this.dom;
- if (dom.tr) {
- return dom.tr;
- }
- // create row
- dom.tr = document.createElement("tr");
- dom.tr.className = "jsoneditor-tr";
- dom.tr.node = this;
- if (this.editor.editable) {
- // create draggable area
- var tdDrag = document.createElement("td");
- tdDrag.className = "jsoneditor-td";
- dom.drag = this._createDomDragArea();
- if (dom.drag) {
- tdDrag.appendChild(dom.drag);
- }
- dom.tr.appendChild(tdDrag);
- }
- // create tree and field
- var tdField = document.createElement("td");
- tdField.className = "jsoneditor-td";
- dom.tr.appendChild(tdField);
- dom.expand = this._createDomExpandButton();
- dom.field = this._createDomField();
- dom.value = this._createDomValue();
- dom.tree = this._createDomTree(dom.expand, dom.field, dom.value);
- tdField.appendChild(dom.tree);
- if (this.editor.editable) {
- // create type select box
- var tdType = document.createElement("td");
- tdType.className = "jsoneditor-td jsoneditor-td-edit";
- dom.tr.appendChild(tdType);
- dom.type = this._createDomTypeButton();
- tdType.appendChild(dom.type);
- // create duplicate button
- var tdDuplicate = document.createElement("td");
- tdDuplicate.className = "jsoneditor-td jsoneditor-td-edit";
- dom.tr.appendChild(tdDuplicate);
- dom.duplicate = this._createDomDuplicateButton();
- if (dom.duplicate) {
- tdDuplicate.appendChild(dom.duplicate);
- }
- // create remove button
- var tdRemove = document.createElement("td");
- tdRemove.className = "jsoneditor-td jsoneditor-td-edit";
- dom.tr.appendChild(tdRemove);
- dom.remove = this._createDomRemoveButton();
- if (dom.remove) {
- tdRemove.appendChild(dom.remove);
- }
- }
- this.updateDom(); // TODO: recurse here?
- return dom.tr;
- };
- /**
- * DragStart event, fired on mousedown on the dragarea at the left side of a Node
- * @param {Event} event
- * @private
- */
- JSONEditor.Node.prototype._onDragStart = function(event) {
- event = event || window.event;
- var node = this;
- if (!this.mousemove) {
- this.mousemove = JSONEditor.Events.addEventListener(
- document,
- "mousemove",
- function(event) {
- node._onDrag(event);
- }
- );
- }
- if (!this.mouseup) {
- this.mouseup = JSONEditor.Events.addEventListener(
- document,
- "mouseup",
- function(event) {
- node._onDragEnd(event);
- }
- );
- }
- /* TODO: correct highlighting when the TypeDropDown is visible (And has highlighting locked)
- if (JSONEditor.freezeHighlight) {
- console.log('heee');
- JSONEditor.freezeHighlight = false;
- this.setHighlight(true);
- }
- */
- JSONEditor.freezeHighlight = true;
- this.drag = {
- oldCursor: document.body.style.cursor,
- startParent: this.parent,
- startIndex: this.parent.childs.indexOf(this)
- };
- document.body.style.cursor = "move";
- JSONEditor.Events.preventDefault(event);
- };
- /**
- * Drag event, fired when moving the mouse while dragging a Node
- * @param {Event} event
- * @private
- */
- JSONEditor.Node.prototype._onDrag = function(event) {
- event = event || window.event;
- var trThis = this.dom.tr;
- // TODO: add an ESC option, which resets to the original position
- var topThis = JSONEditor.getAbsoluteTop(trThis);
- var heightThis = trThis.offsetHeight;
- var mouseY = event.pageY || event.clientY + document.body.scrollTop;
- if (mouseY < topThis) {
- // move up
- var trPrev = trThis.previousSibling;
- var topPrev = JSONEditor.getAbsoluteTop(trPrev);
- var nodePrev = JSONEditor.getNodeFromTarget(trPrev);
- while (trPrev && mouseY < topPrev) {
- nodePrev = JSONEditor.getNodeFromTarget(trPrev);
- trPrev = trPrev.previousSibling;
- topPrev = JSONEditor.getAbsoluteTop(trPrev);
- }
- if (nodePrev) {
- trPrev = nodePrev.dom.tr;
- topPrev = JSONEditor.getAbsoluteTop(trPrev);
- if (mouseY > topPrev + heightThis) {
- nodePrev = undefined;
- }
- }
- if (nodePrev && nodePrev.parent) {
- nodePrev.parent.moveBefore(this, nodePrev);
- }
- } else {
- // move down
- var trLast =
- this.expanded && this.append ? this.append.getDom() : this.dom.tr;
- var trFirst = trLast ? trLast.nextSibling : undefined;
- if (trFirst) {
- var topFirst = JSONEditor.getAbsoluteTop(trFirst);
- var nodeNext = undefined;
- var trNext = trFirst.nextSibling;
- var topNext = JSONEditor.getAbsoluteTop(trNext);
- var heightNext = trNext ? topNext - topFirst : 0;
- while (trNext && mouseY > topThis + heightNext) {
- nodeNext = JSONEditor.getNodeFromTarget(trNext);
- trNext = trNext.nextSibling;
- topNext = JSONEditor.getAbsoluteTop(trNext);
- heightNext = trNext ? topNext - topFirst : 0;
- }
- if (nodeNext && nodeNext.parent) {
- nodeNext.parent.moveBefore(this, nodeNext);
- }
- }
- }
- JSONEditor.Events.preventDefault(event);
- };
- /**
- * Drag event, fired on mouseup after having dragged a node
- * @param {Event} event
- * @private
- */
- JSONEditor.Node.prototype._onDragEnd = function(event) {
- event = event || window.event;
- var params = {
- node: this,
- startParent: this.drag.startParent,
- startIndex: this.drag.startIndex,
- endParent: this.parent,
- endIndex: this.parent.childs.indexOf(this)
- };
- if (
- params.startParent != params.endParent ||
- params.startIndex != params.endIndex
- ) {
- // only register this action if the node is actually moved to another place
- this.editor.onAction("moveNode", params);
- }
- document.body.style.cursor = this.drag.oldCursor;
- delete JSONEditor.freezeHighlight;
- delete this.drag;
- this.setHighlight(false);
- if (this.mousemove) {
- JSONEditor.Events.removeEventListener(
- document,
- "mousemove",
- this.mousemove
- );
- delete this.mousemove;
- }
- if (this.mouseup) {
- JSONEditor.Events.removeEventListener(document, "mouseup", this.mouseup);
- delete this.mouseup;
- }
- JSONEditor.Events.preventDefault(event);
- };
- /**
- * Create a drag area, displayed at the left side of the node
- * @return {Element | undefined} domDrag
- * @private
- */
- JSONEditor.Node.prototype._createDomDragArea = function() {
- if (!this.parent) {
- return undefined;
- }
- var domDrag = document.createElement("button");
- domDrag.className = "jsoneditor-dragarea";
- domDrag.title = "Move field (drag and drop)";
- return domDrag;
- };
- /**
- * Create an editable field
- * @return {Element} domField
- * @private
- */
- JSONEditor.Node.prototype._createDomField = function() {
- return document.createElement("div");
- };
- /**
- * Set highlighting for this node and all its childs.
- * Only applied to the currently visible (expanded childs)
- * @param {boolean} highlight
- */
- JSONEditor.Node.prototype.setHighlight = function(highlight) {
- if (JSONEditor.freezeHighlight) {
- return;
- }
- if (this.dom.tr) {
- this.dom.tr.className =
- "jsoneditor-tr" + (highlight ? " jsoneditor-tr-highlight" : "");
- if (this.append) {
- this.append.setHighlight(highlight);
- }
- if (this.childs) {
- this.childs.forEach(function(child) {
- child.setHighlight(highlight);
- });
- }
- }
- };
- /**
- * Update the value of the node. Only primitive types are allowed, no Object
- * or Array is allowed.
- * @param {String | Number | Boolean | null} value
- */
- JSONEditor.Node.prototype.updateValue = function(value) {
- this.value = value;
- this.updateDom();
- };
- /**
- * Update the field of the node.
- * @param {String} field
- */
- JSONEditor.Node.prototype.updateField = function(field) {
- this.field = field;
- this.updateDom();
- };
- /**
- * Update the HTML DOM, optionally recursing through the childs
- * @param {Object} [options] Available parameters:
- * {boolean} [recurse] If true, the
- * DOM of the childs will be updated recursively.
- * False by default.
- * {boolean} [updateIndexes] If true, the childs
- * indexes of the node will be updated too. False by
- * default.
- */
- JSONEditor.Node.prototype.updateDom = function(options) {
- // update level indentation
- var domTree = this.dom.tree;
- if (domTree) {
- domTree.style.marginLeft = this.getLevel() * 24 + "px";
- }
- // update field
- var domField = this.dom.field;
- if (domField) {
- if (this.fieldEditable == true) {
- // parent is an object
- domField.contentEditable = this.editor.editable;
- domField.spellcheck = false;
- domField.className = "jsoneditor-field";
- } else {
- // parent is an array this is the root node
- domField.className = "jsoneditor-readonly";
- }
- var field;
- if (this.index != undefined) {
- field = this.index;
- } else if (this.field != undefined) {
- field = this.field;
- } else if (this.type == "array" || this.type == "object") {
- field = this.type;
- } else {
- field = "field";
- }
- domField.innerHTML = this._escapeHTML(field);
- }
- // update value
- var domValue = this.dom.value;
- if (domValue) {
- var count = this.childs ? this.childs.length : 0;
- if (this.type == "array") {
- domValue.innerHTML = "[" + count + "]";
- domValue.title = this.type + " containing " + count + " items";
- } else if (this.type == "object") {
- domValue.innerHTML = "{" + count + "}";
- domValue.title = this.type + " containing " + count + " items";
- } else {
- domValue.innerHTML = this._escapeHTML(this.value);
- delete domValue.title;
- }
- }
- // update field and value
- this._updateDomField();
- this._updateDomValue();
- // update childs indexes
- if (options && options.updateIndexes == true) {
- // updateIndexes is true or undefined
- this._updateDomIndexes();
- }
- if (options && options.recurse == true) {
- // recurse is true or undefined. update childs recursively
- if (this.childs) {
- this.childs.forEach(function(child) {
- child.updateDom(options);
- });
- }
- // update row with append button
- if (this.append) {
- this.append.updateDom();
- }
- }
- };
- /**
- * Update the DOM of the childs of a node: update indexes and undefined field
- * names.
- * Only applicable when structure is an array or object
- * @private
- */
- JSONEditor.Node.prototype._updateDomIndexes = function() {
- var domValue = this.dom.value;
- var childs = this.childs;
- if (domValue && childs) {
- if (this.type == "array") {
- childs.forEach(function(child, index) {
- child.index = index;
- var childField = child.dom.field;
- if (childField) {
- childField.innerHTML = index;
- }
- });
- } else if (this.type == "object") {
- childs.forEach(function(child) {
- if (child.index != undefined) {
- delete child.index;
- if (child.field == undefined) {
- child.field = "field";
- }
- }
- });
- }
- }
- };
- /**
- * Create an editable value
- * @private
- */
- JSONEditor.Node.prototype._createDomValue = function() {
- var domValue;
- if (this.type == "array") {
- domValue = document.createElement("div");
- domValue.className = "jsoneditor-readonly";
- domValue.innerHTML = "[...]";
- } else if (this.type == "object") {
- domValue = document.createElement("div");
- domValue.className = "jsoneditor-readonly";
- domValue.innerHTML = "{...}";
- } else if (this.type == "string") {
- domValue = document.createElement("div");
- domValue.contentEditable = this.editor.editable;
- domValue.spellcheck = false;
- domValue.className = "jsoneditor-value";
- domValue.innerHTML = this._escapeHTML(this.value);
- } else {
- domValue = document.createElement("div");
- domValue.contentEditable = this.editor.editable;
- domValue.spellcheck = false;
- domValue.className = "jsoneditor-value";
- domValue.innerHTML = this._escapeHTML(this.value);
- }
- // TODO: in FF spel/check of editable divs is done via the body. quite ugly
- // document.body.spellcheck = false;
- return domValue;
- };
- /**
- * Create an expand/collapse button
- * @return {Element} expand
- * @private
- */
- JSONEditor.Node.prototype._createDomExpandButton = function() {
- // create expand button
- var expand = document.createElement("button");
- var expandable = this.type == "array" || this.type == "object";
- if (expandable) {
- expand.className = this.expanded
- ? "jsoneditor-expanded"
- : "jsoneditor-collapsed";
- expand.title =
- "Click to expand/collapse this field. \n" +
- "Ctrl+Click to expand/collapse including all childs.";
- } else {
- expand.className = "jsoneditor-invisible";
- expand.title = "";
- }
- return expand;
- };
- /**
- * Create a DOM tree element, containing the expand/collapse button
- * @param {Element} domExpand
- * @param {Element} domField
- * @param {Element} domValue
- * @return {Element} domTree
- * @private
- */
- JSONEditor.Node.prototype._createDomTree = function(
- domExpand,
- domField,
- domValue
- ) {
- var dom = this.dom;
- var domTree = document.createElement("table");
- var tbody = document.createElement("tbody");
- domTree.style.borderCollapse = "collapse"; // TODO: put in css
- domTree.appendChild(tbody);
- var tr = document.createElement("tr");
- tbody.appendChild(tr);
- // create expand button
- var tdExpand = document.createElement("td");
- tdExpand.className = "jsoneditor-td-tree";
- tr.appendChild(tdExpand);
- tdExpand.appendChild(domExpand);
- dom.tdExpand = tdExpand;
- // add the field
- var tdField = document.createElement("td");
- tdField.className = "jsoneditor-td-tree";
- tr.appendChild(tdField);
- tdField.appendChild(domField);
- dom.tdField = tdField;
- // add a separator
- var tdSeparator = document.createElement("td");
- tdSeparator.className = "jsoneditor-td-tree";
- tr.appendChild(tdSeparator);
- if (this.type != "object" && this.type != "array") {
- tdSeparator.appendChild(document.createTextNode(":"));
- tdSeparator.className = "jsoneditor-separator";
- }
- dom.tdSeparator = tdSeparator;
- // add the value
- var tdValue = document.createElement("td");
- tdValue.className = "jsoneditor-td-tree";
- tr.appendChild(tdValue);
- tdValue.appendChild(domValue);
- dom.tdValue = tdValue;
- return domTree;
- };
- /**
- * Handle an event. The event is catched centrally by the editor
- * @param {Event} event
- */
- JSONEditor.Node.prototype.onEvent = function(event) {
- var type = event.type;
- var target = event.target || event.srcElement;
- var dom = this.dom;
- var node = this;
- var expandable = this.type == "array" || this.type == "object";
- // value events
- var domValue = dom.value;
- if (target == domValue) {
- switch (type) {
- case "focus":
- JSONEditor.focusNode = this;
- break;
- case "blur":
- case "change":
- this._getDomValue(true);
- this._updateDomValue();
- if (this.value) {
- domValue.innerHTML = this._escapeHTML(this.value);
- }
- break;
- case "keyup":
- this._getDomValue(true);
- this._updateDomValue();
- break;
- case "cut":
- case "paste":
- setTimeout(function() {
- node._getDomValue(true);
- node._updateDomValue();
- }, 1);
- break;
- }
- }
- // field events
- var domField = dom.field;
- if (target == domField) {
- switch (type) {
- case "focus":
- JSONEditor.focusNode = this;
- break;
- case "change":
- case "blur":
- this._getDomField(true);
- this._updateDomField();
- if (this.field) {
- domField.innerHTML = this._escapeHTML(this.field);
- }
- break;
- case "keyup":
- this._getDomField(true);
- this._updateDomField();
- break;
- case "cut":
- case "paste":
- setTimeout(function() {
- node._getDomField(true);
- node._updateDomField();
- }, 1);
- break;
- }
- }
- // drag events
- var domDrag = dom.drag;
- if (target == domDrag) {
- switch (type) {
- case "mousedown":
- this._onDragStart(event);
- break;
- case "mouseover":
- this.setHighlight(true);
- break;
- case "mouseout":
- this.setHighlight(false);
- break;
- }
- }
- // expand events
- var domExpand = dom.expand;
- if (target == domExpand) {
- if (type == "click") {
- if (expandable) {
- this._onExpand(event);
- }
- }
- }
- // duplicate button
- var domDuplicate = dom.duplicate;
- if (target == domDuplicate) {
- switch (type) {
- case "click":
- var clone = this.parent._duplicate(this);
- this.editor.onAction("duplicateNode", {
- node: this,
- clone: clone,
- parent: this.parent
- });
- break;
- case "mouseover":
- this.setHighlight(true);
- break;
- case "mouseout":
- this.setHighlight(false);
- break;
- }
- }
- // remove button
- var domRemove = dom.remove;
- if (target == domRemove) {
- switch (type) {
- case "click":
- this._onRemove();
- break;
- case "mouseover":
- this.setHighlight(true);
- break;
- case "mouseout":
- this.setHighlight(false);
- break;
- }
- }
- // type button
- var domType = dom.type;
- if (target == domType) {
- switch (type) {
- case "click":
- this._onChangeType(event);
- break;
- case "mouseover":
- this.setHighlight(true);
- break;
- case "mouseout":
- this.setHighlight(false);
- break;
- }
- }
- // focus
- // when clicked in whitespace left or right from the field or value, set focus
- var domTree = dom.tree;
- if (target == domTree.parentNode) {
- switch (type) {
- case "click":
- var left =
- event.offsetX != undefined
- ? event.offsetX < (this.getLevel() + 1) * 24
- : event.clientX < JSONEditor.getAbsoluteLeft(dom.tdSeparator); // for FF
- if (left || expandable) {
- // node is expandable when it is an object or array
- if (domField) {
- JSONEditor.setEndOfContentEditable(domField);
- domField.focus();
- }
- } else {
- if (domValue) {
- JSONEditor.setEndOfContentEditable(domValue);
- domValue.focus();
- }
- }
- break;
- }
- }
- if (
- (target == dom.tdExpand && !expandable) ||
- target == dom.tdField ||
- target == dom.tdSeparator
- ) {
- switch (type) {
- case "click":
- if (domField) {
- JSONEditor.setEndOfContentEditable(domField);
- domField.focus();
- }
- break;
- }
- }
- };
- /**
- * Handle the expand event, when clicked on the expand button
- * @param {Event} event
- * @private
- */
- JSONEditor.Node.prototype._onExpand = function(event) {
- event = event || window.event;
- var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all
- if (recurse) {
- // Take the table offline
- var table = this.dom.tr.parentNode; // TODO: not nice to access the main table like this
- var frame = table.parentNode;
- var scrollTop = frame.scrollTop;
- frame.removeChild(table);
- }
- if (this.expanded) {
- this.collapse(recurse);
- } else {
- this.expand(recurse);
- }
- if (recurse) {
- // Put the table online again
- frame.appendChild(table);
- frame.scrollTop = scrollTop;
- }
- };
- JSONEditor.Node.types = [
- {
- value: "array",
- className: "jsoneditor-option-array",
- title: '"array" 类型: 包含了有序值集合的数组.'
- },
- {
- value: "auto",
- className: "jsoneditor-option-auto",
- title:
- '"auto" 类型: 节点类型将自动从值中获取, 可以是: string, number, boolean, 或 null.'
- },
- {
- value: "object",
- className: "jsoneditor-option-object",
- title: '"object" 类型: 对象包含了一些无序的键/值对.'
- },
- {
- value: "string",
- className: "jsoneditor-option-string",
- title: '"string" 类型: 节点类型不从值中自动获取, 但永远返回string.'
- }
- ];
- /**
- * Create a DOM select box containing the node type
- * @return {Element} domType
- * @private
- */
- JSONEditor.Node.prototype._createDomTypeButton = function() {
- var node = this;
- var domType = document.createElement("button");
- domType.className = "jsoneditor-type-" + node.type;
- domType.title = "改变节点类型";
- return domType;
- };
- /**
- * Remove this node
- * @private
- */
- JSONEditor.Node.prototype._onRemove = function() {
- this.setHighlight(false);
- var index = this.parent.childs.indexOf(this);
- this.parent._remove(this);
- this.editor.onAction("removeNode", {
- node: this,
- parent: this.parent,
- index: index
- });
- };
- /**
- * Handle a click on the Type-button
- * @param {Event} event
- * @private
- */
- JSONEditor.Node.prototype._onChangeType = function(event) {
- JSONEditor.Events.stopPropagation(event);
- var domType = this.dom.type;
- var node = this;
- var x = JSONEditor.getAbsoluteLeft(domType);
- var y = JSONEditor.getAbsoluteTop(domType) + domType.clientHeight;
- var callback = function(newType) {
- var oldType = node.type;
- node.changeType(newType);
- node.editor.onAction("changeType", {
- node: node,
- oldType: oldType,
- newType: newType
- });
- domType.className = "jsoneditor-type-" + node.type;
- };
- JSONEditor.showDropDownList({
- x: x,
- y: y,
- node: node,
- value: node.type,
- values: JSONEditor.Node.types,
- className: "jsoneditor-select",
- optionSelectedClassName: "jsoneditor-option-selected",
- optionClassName: "jsoneditor-option",
- callback: callback
- });
- };
- /**
- * Show a dropdown list
- * @param {Object} params Available parameters:
- * {Number} x The absolute horizontal position
- * {Number} y The absolute vertical position
- * {JSONEditor.Node} node node used for highlighting
- * {String} value current selected value
- * {Object[]} values the available values. Each object
- * contains a value, title, and
- * className
- * {String} optionSelectedClassName
- * {String} optionClassName
- * {function} callback Callback method, called when
- * the selected value changed.
- */
- JSONEditor.showDropDownList = function(params) {
- var select = document.createElement("div");
- select.className = params.className || "";
- select.style.position = "absolute";
- select.style.left = (params.x || 0) + "px";
- select.style.top = (params.y || 0) + "px";
- params.values.forEach(function(v) {
- var text = v.value || String(v);
- var className = "jsoneditor-option";
- var selected = text == params.value;
- if (selected) {
- className += " " + params.optionSelectedClassName;
- }
- var option = document.createElement("div");
- option.className = className;
- if (v.title) {
- option.title = v.title;
- }
- var divIcon = document.createElement("div");
- divIcon.className = v.className || "";
- option.appendChild(divIcon);
- var divText = document.createElement("div");
- divText.className = "jsoneditor-option-text";
- divText.innerHTML = "<div>" + text + "</div>";
- option.appendChild(divText);
- option.onmousedown = (function(value) {
- return function() {
- params.callback(value);
- };
- })(v.value);
- select.appendChild(option);
- });
- document.body.appendChild(select);
- params.node.setHighlight(true);
- JSONEditor.freezeHighlight = true;
- // TODO: change to onclick? -> but be sure to remove existing dropdown first
- var onmousedown = JSONEditor.Events.addEventListener(
- document,
- "mousedown",
- function() {
- JSONEditor.freezeHighlight = false;
- params.node.setHighlight(false);
- if (select && select.parentNode) {
- select.parentNode.removeChild(select);
- }
- JSONEditor.Events.removeEventListener(document, "mousedown", onmousedown);
- }
- );
- var onmousewheel = JSONEditor.Events.addEventListener(
- document,
- "mousewheel",
- function() {
- JSONEditor.freezeHighlight = false;
- params.node.setHighlight(false);
- if (select && select.parentNode) {
- select.parentNode.removeChild(select);
- }
- JSONEditor.Events.removeEventListener(
- document,
- "mousewheel",
- onmousewheel
- );
- }
- );
- };
- /**
- * Create a table row with an append button.
- * @return {Element | undefined} buttonAppend or undefined when inapplicable
- */
- JSONEditor.Node.prototype.getAppend = function() {
- if (!this.append) {
- this.append = new JSONEditor.AppendNode(this.editor);
- this.append.setParent(this);
- }
- return this.append.getDom();
- };
- /**
- * Create a remove button. Returns undefined when the structure cannot
- * be removed
- * @return {Element | undefined} removeButton, or undefined when inapplicable
- * @private
- */
- JSONEditor.Node.prototype._createDomRemoveButton = function() {
- if (
- this.parent &&
- (this.parent.type == "array" || this.parent.type == "object")
- ) {
- var buttonRemove = document.createElement("button");
- buttonRemove.className = "jsoneditor-remove";
- buttonRemove.title = "删除节点 (包括所有子节点)";
- return buttonRemove;
- } else {
- return undefined;
- }
- };
- /**
- * Create a duplicate button.
- * If the Node is the root node, no duplicate button is available and undefined
- * will be returned
- * @return {Element | undefined} buttonDuplicate
- * @private
- */
- JSONEditor.Node.prototype._createDomDuplicateButton = function() {
- if (
- this.parent &&
- (this.parent.type == "array" || this.parent.type == "object")
- ) {
- var buttonDupliate = document.createElement("button");
- buttonDupliate.className = "jsoneditor-duplicate";
- buttonDupliate.title = "复制节点 (包括所有子节点)";
- return buttonDupliate;
- } else {
- return undefined;
- }
- };
- /**
- * get the type of a value
- * @param {*} value
- * @return {String} type Can be 'object', 'array', 'string', 'auto'
- * @private
- */
- JSONEditor.Node.prototype._getType = function(value) {
- if (value instanceof Array) {
- return "array";
- }
- if (value instanceof Object) {
- return "object";
- }
- if (typeof value == "string" && typeof this._stringCast(value) != "string") {
- return "string";
- }
- return "auto";
- };
- /**
- * cast contents of a string to the correct type. This can be a string,
- * a number, a boolean, etc
- * @param {String} str
- * @return {*} castedStr
- * @private
- */
- JSONEditor.Node.prototype._stringCast = function(str) {
- var lower = str.toLowerCase(),
- num = Number(str), // will nicely fail with '123ab'
- numFloat = parseFloat(str); // will nicely fail with ' '
- if (str == "") {
- return "";
- } else if (lower == "null") {
- return null;
- } else if (lower == "true") {
- return true;
- } else if (lower == "false") {
- return false;
- } else if (!isNaN(num) && !isNaN(numFloat)) {
- return num;
- } else {
- return str;
- }
- };
- /**
- * escape a text, such that it can be displayed safely in an HTML element
- * @param {String} text
- * @return {String} escapedText
- * @private
- */
- JSONEditor.Node.prototype._escapeHTML = function(text) {
- var htmlEscaped = String(text)
- .replace(/</g, "<")
- .replace(/>/g, ">")
- .replace(/ /g, " ") // replace double space with an nbsp and space
- .replace(/^ /, " ") // space at start
- .replace(/ $/, " "); // space at end
- var json = JSON.stringify(htmlEscaped);
- return json.substring(1, json.length - 1);
- };
- /**
- * unescape a string.
- * @param {String} escapedText
- * @return {String} text
- * @private
- */
- JSONEditor.Node.prototype._unescapeHTML = function(escapedText) {
- var json = '"' + this._escapeJSON(escapedText) + '"';
- var htmlEscaped = JSONEditor.parse(json);
- return htmlEscaped
- .replace(/</g, "<")
- .replace(/>/g, ">")
- .replace(/ /g, " ");
- };
- /**
- * escape a text to make it a valid JSON string. The method will:
- * - replace unescaped double quotes with '\"'
- * - replace unescaped backslash with '\\'
- * - replace returns with '\n'
- * @param {String} text
- * @return {String} escapedText
- * @private
- */
- JSONEditor.Node.prototype._escapeJSON = function(text) {
- // TODO: replace with some smart regex (only when a new solution is faster!)
- var escaped = "";
- var i = 0,
- iMax = text.length;
- while (i < iMax) {
- var c = text.charAt(i);
- if (c == "\n") {
- escaped += "\\n";
- } else if (c == "\\") {
- escaped += c;
- i++;
- c = text.charAt(i);
- if ('"\\/bfnrtu'.indexOf(c) == -1) {
- escaped += "\\"; // no valid escape character
- }
- escaped += c;
- } else if (c == '"') {
- escaped += '\\"';
- } else {
- escaped += c;
- }
- i++;
- }
- return escaped;
- };
- /**
- * @constructor JSONEditor.AppendNode
- * @extends JSONEditor.Node
- * @param {JSONEditor} editor
- * Create a new AppendNode. This is a special node which is created at the
- * end of the list with childs for an object or array
- */
- JSONEditor.AppendNode = function(editor) {
- this.editor = editor;
- this.dom = {};
- };
- JSONEditor.AppendNode.prototype = new JSONEditor.Node();
- /**
- * Return a table row with an append button.
- * @return {Element} dom TR element
- */
- JSONEditor.AppendNode.prototype.getDom = function() {
- if (this.dom.tr) {
- return this.dom.tr;
- }
- /**
- * Create a TD element, and give it the provided class name (if any)
- * @param {String} [className]
- * @return {Element} td
- */
- function newTd(className) {
- var td = document.createElement("td");
- td.className = className || "";
- return td;
- }
- // a row for the append button
- var trAppend = document.createElement("tr");
- trAppend.node = this;
- // TODO: do not create an appendNode at all when in viewer mode
- if (!this.editor.editable) {
- return trAppend;
- }
- // a cell for the drag area column
- trAppend.appendChild(newTd("jsoneditor-td"));
- // a cell for the append button
- var tdAppend = document.createElement("td");
- trAppend.appendChild(tdAppend);
- tdAppend.className = "jsoneditor-td";
- // create the append button
- var buttonAppend = document.createElement("button");
- buttonAppend.className = "jsoneditor-append";
- buttonAppend.title = "添加";
- this.dom.append = buttonAppend;
- tdAppend.appendChild(buttonAppend);
- trAppend.appendChild(newTd("jsoneditor-td jsoneditor-td-edit"));
- trAppend.appendChild(newTd("jsoneditor-td jsoneditor-td-edit"));
- trAppend.appendChild(newTd("jsoneditor-td jsoneditor-td-edit"));
- this.dom.tr = trAppend;
- this.dom.td = tdAppend;
- this.updateDom();
- return trAppend;
- };
- /**
- * Update the HTML dom of the Node
- */
- JSONEditor.AppendNode.prototype.updateDom = function() {
- var tdAppend = this.dom.td;
- if (tdAppend) {
- tdAppend.style.paddingLeft = this.getLevel() * 24 + 26 + "px";
- // TODO: not so nice hard coded offset
- }
- };
- /**
- * Handle an event. The event is catched centrally by the editor
- * @param {Event} event
- */
- JSONEditor.AppendNode.prototype.onEvent = function(event) {
- var type = event.type;
- var target = event.target || event.srcElement;
- var dom = this.dom;
- var domAppend = dom.append;
- if (target == domAppend) {
- switch (type) {
- case "click":
- this._onAppend();
- break;
- case "mouseover":
- this.parent.setHighlight(true);
- break;
- case "mouseout":
- this.parent.setHighlight(false);
- }
- }
- };
- /**
- * Handle append event
- * @private
- */
- JSONEditor.AppendNode.prototype._onAppend = function() {
- var newNode = new JSONEditor.Node(this.editor, {
- field: "field",
- value: "value"
- });
- this.parent.appendChild(newNode);
- this.parent.setHighlight(false);
- newNode.focus();
- this.editor.onAction("appendNode", {
- node: newNode,
- parent: this.parent
- });
- };
- /**
- * Create main frame
- * @private
- */
- JSONEditor.prototype._createFrame = function() {
- // create the frame
- this.container.innerHTML = "";
- this.frame = document.createElement("div");
- this.frame.className = "jsoneditor-frame";
- this.container.appendChild(this.frame);
- // create one global event listener to handle all events from all nodes
- var editor = this;
- // TODO: move this onEvent to JSONEditor.prototype.onEvent
- var onEvent = function(event) {
- event = event || window.event;
- var target = event.target || event.srcElement;
- /* TODO: Enable quickkeys Ctrl+F and F3.
- // Requires knowing whether the JSONEditor has focus or not
- // (use a global event listener for that?)
- // Check for search quickkeys, Ctrl+F and F3
- if (editor.options.search) {
- if (event.type == 'keydown') {
- var keynum = event.which || event.keyCode;
- if (keynum == 70 && event.ctrlKey) { // Ctrl+F
- if (editor.searchBox) {
- editor.searchBox.dom.search.focus();
- editor.searchBox.dom.search.select();
- JSONEditor.Events.preventDefault(event);
- JSONEditor.Events.stopPropagation(event);
- }
- }
- else if (keynum == 114) { // F3
- if (!event.shiftKey) {
- // select next search result
- editor.searchBox.next();
- }
- else {
- // select previous search result
- editor.searchBox.previous();
- }
- editor.searchBox.focusActiveResult();
- // set selection to the current
- JSONEditor.Events.preventDefault(event);
- JSONEditor.Events.stopPropagation(event);
- }
- }
- }
- */
- var node = JSONEditor.getNodeFromTarget(target);
- if (node) {
- node.onEvent(event);
- }
- };
- this.frame.onclick = function(event) {
- onEvent(event);
- // prevent default submit action when JSONEditor is located inside a form
- JSONEditor.Events.preventDefault(event);
- };
- this.frame.onchange = onEvent;
- this.frame.onkeydown = onEvent;
- this.frame.onkeyup = onEvent;
- this.frame.oncut = onEvent;
- this.frame.onpaste = onEvent;
- this.frame.onmousedown = onEvent;
- this.frame.onmouseup = onEvent;
- this.frame.onmouseover = onEvent;
- this.frame.onmouseout = onEvent;
- // Note: focus and blur events do not propagate, therefore they defined
- // using an eventListener with useCapture=true
- // see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
- JSONEditor.Events.addEventListener(this.frame, "focus", onEvent, true);
- JSONEditor.Events.addEventListener(this.frame, "blur", onEvent, true);
- this.frame.onfocusin = onEvent; // for IE
- this.frame.onfocusout = onEvent; // for IE
- // create menu
- this.menu = document.createElement("div");
- this.menu.className = "jsoneditor-menu";
- this.frame.appendChild(this.menu);
- // create expand all button
- var expandAll = document.createElement("button");
- expandAll.className = "jsoneditor-menu jsoneditor-expand-all";
- expandAll.title = "展开";
- expandAll.onclick = function() {
- editor.expandAll();
- };
- this.menu.appendChild(expandAll);
- // create expand all button
- var collapseAll = document.createElement("button");
- collapseAll.title = "折叠";
- collapseAll.className = "jsoneditor-menu jsoneditor-collapse-all";
- collapseAll.onclick = function() {
- editor.collapseAll();
- };
- this.menu.appendChild(collapseAll);
- // create expand/collapse buttons
- if (this.history) {
- // create separator
- var separator = document.createElement("span");
- separator.innerHTML = " ";
- this.menu.appendChild(separator);
- // create undo button
- var undo = document.createElement("button");
- undo.className = "jsoneditor-menu jsoneditor-undo";
- undo.title = "撤销";
- undo.onclick = function() {
- // undo last action
- editor.history.undo();
- // trigger change callback
- if (editor.options.change) {
- editor.options.change();
- }
- };
- this.menu.appendChild(undo);
- this.dom.undo = undo;
- // create redo button
- var redo = document.createElement("button");
- redo.className = "jsoneditor-menu jsoneditor-redo";
- redo.title = "重做";
- redo.onclick = function() {
- // redo last action
- editor.history.redo();
- // trigger change callback
- if (editor.options.change) {
- editor.options.change();
- }
- };
- this.menu.appendChild(redo);
- this.dom.redo = redo;
- // register handler for onchange of history
- this.history.onChange = function() {
- undo.disabled = !editor.history.canUndo();
- redo.disabled = !editor.history.canRedo();
- };
- this.history.onChange();
- }
- // create search box
- if (this.options.search) {
- this.searchBox = new JSONEditor.SearchBox(this, this.menu);
- }
- };
- /**
- * Create main table
- * @private
- */
- JSONEditor.prototype._createTable = function() {
- var contentOuter = document.createElement("div");
- contentOuter.className = "jsoneditor-content-outer";
- this.contentOuter = contentOuter;
- this.content = document.createElement("div");
- this.content.className = "jsoneditor-content";
- contentOuter.appendChild(this.content);
- this.table = document.createElement("table");
- this.table.className = "jsoneditor-table";
- this.content.appendChild(this.table);
- // IE8 does not handle overflow='auto' correctly.
- // Therefore, set overflow to 'scroll'
- var ieVersion = JSONEditor.getInternetExplorerVersion();
- if (ieVersion == 8) {
- this.content.style.overflow = "scroll";
- }
- // create colgroup where the first two columns don't have a fixed
- // width, and the edit columns do have a fixed width
- var col;
- this.colgroupContent = document.createElement("colgroup");
- col = document.createElement("col");
- col.width = "24px";
- this.colgroupContent.appendChild(col);
- col = document.createElement("col");
- this.colgroupContent.appendChild(col);
- col = document.createElement("col");
- col.width = "24px";
- this.colgroupContent.appendChild(col);
- col = document.createElement("col");
- col.width = "24px";
- this.colgroupContent.appendChild(col);
- col = document.createElement("col");
- col.width = "24px";
- this.colgroupContent.appendChild(col);
- this.table.appendChild(this.colgroupContent);
- this.tbody = document.createElement("tbody");
- this.table.appendChild(this.tbody);
- this.frame.appendChild(contentOuter);
- };
- /**
- * Find the node from an event target
- * @param {Element} target
- * @return {JSONEditor.Node | undefined} node or undefined when not found
- */
- JSONEditor.getNodeFromTarget = function(target) {
- while (target) {
- if (target.node) {
- return target.node;
- }
- target = target.parentNode;
- }
- return undefined;
- };
- /**
- * Create a JSONFormatter and attach it to given container
- * @constructor JSONFormatter
- * @param {Element} container
- * @param {Object} [options] Object with options. available options:
- * {Number} indentation Number of indentation
- * spaces. 4 by default.
- * {function} change Callback method
- * triggered on change
- * @param {JSON | String} [json] initial contents of the formatter
- */
- JSONFormatter = function(container, options, json) {
- // check availability of JSON parser (not available in IE7 and older)
- if (!JSON) {
- throw new Error(
- "您当前使用的浏览器不支持 JSON. \n\n" +
- "请下载安装最新版本的浏览, 本站推荐Google Chrome.\n" +
- "(PS: 当前主流浏览器都支持JSON)."
- );
- }
- this.container = container;
- this.indentation = 4; // number of spaces
- this.width = container.clientWidth;
- this.height = container.clientHeight;
- this.frame = document.createElement("div");
- this.frame.className = "jsoneditor-frame";
- this.frame.onclick = function(event) {
- // prevent default submit action when JSONFormatter is located inside a form
- JSONEditor.Events.preventDefault(event);
- };
- // create menu
- this.menu = document.createElement("div");
- this.menu.className = "jsoneditor-menu";
- this.frame.appendChild(this.menu);
- // create format button
- var buttonFormat = document.createElement("button");
- //buttonFormat.innerHTML = 'Format';
- buttonFormat.className = "jsoneditor-menu jsoneditor-format";
- buttonFormat.title = "格式化JSON数据";
- //buttonFormat.className = 'jsoneditor-button';
- this.menu.appendChild(buttonFormat);
- // create compact button
- var buttonCompact = document.createElement("button");
- //buttonCompact.innerHTML = 'Compact';
- buttonCompact.className = "jsoneditor-menu jsoneditor-compact";
- buttonCompact.title = "压缩JSON数据, 清除所有空白字符";
- //buttonCompact.className = 'jsoneditor-button';
- this.menu.appendChild(buttonCompact);
- this.content = document.createElement("div");
- this.content.className = "jsonformatter-content";
- this.frame.appendChild(this.content);
- this.textarea = document.createElement("textarea");
- this.textarea.className = "jsonformatter-textarea";
- this.textarea.spellcheck = false;
- this.content.appendChild(this.textarea);
- var textarea = this.textarea;
- // read the options
- if (options) {
- if (options.change) {
- // register on change event
- if (this.textarea.oninput === null) {
- this.textarea.oninput = function() {
- options.change();
- };
- } else {
- // oninput is undefined. For IE8-
- this.textarea.onchange = function() {
- options.change();
- };
- }
- }
- if (options.indentation) {
- this.indentation = Number(options.indentation);
- }
- }
- var me = this;
- buttonFormat.onclick = function() {
- try {
- var json = JSONEditor.parse(textarea.value);
- textarea.value = JSON.stringify(json, null, me.indentation);
- } catch (err) {
- me.onError(err);
- }
- };
- buttonCompact.onclick = function() {
- try {
- var json = JSONEditor.parse(textarea.value);
- textarea.value = JSON.stringify(json);
- } catch (err) {
- me.onError(err);
- }
- };
- this.container.appendChild(this.frame);
- // load initial json object or string
- if (typeof json == "string") {
- this.setText(json);
- } else {
- this.set(json);
- }
- };
- /**
- * This method is executed on error.
- * It can be overwritten for each instance of the JSONFormatter
- * @param {String} err
- */
- JSONFormatter.prototype.onError = function(err) {
- // action should be implemented for the instance
- };
- /**
- * Set json data in the formatter
- * @param {Object} json
- */
- JSONFormatter.prototype.set = function(json) {
- this.textarea.value = JSON.stringify(json, null, this.indentation);
- };
- /**
- * Get json data from the formatter
- * @return {Object} json
- */
- JSONFormatter.prototype.get = function() {
- return JSONEditor.parse(this.textarea.value);
- };
- /**
- * Get the text contents of the JSONFormatter
- * @return {String} text
- */
- JSONFormatter.prototype.getText = function() {
- return this.textarea.value;
- };
- /**
- * Set the text contents of the JSONFormatter
- * @param {String} text
- */
- JSONFormatter.prototype.setText = function(text) {
- this.textarea.value = text;
- };
- /**
- * @constructor JSONEditor.SearchBox
- * Create a search box in given HTML container
- * @param {JSONEditor} editor The JSON Editor to attach to
- * @param {Element} container HTML container element of where to create the
- * search box
- */
- JSONEditor.SearchBox = function(editor, container) {
- var searchBox = this;
- this.editor = editor;
- this.timeout = undefined;
- this.delay = 200; // ms
- this.lastText = undefined;
- this.dom = {};
- this.dom.container = container;
- var table = document.createElement("table");
- this.dom.table = table;
- table.className = "jsoneditor-search";
- container.appendChild(table);
- var tbody = document.createElement("tbody");
- this.dom.tbody = tbody;
- table.appendChild(tbody);
- var tr = document.createElement("tr");
- tbody.appendChild(tr);
- var td = document.createElement("td");
- td.className = "jsoneditor-search";
- tr.appendChild(td);
- var results = document.createElement("div");
- this.dom.results = results;
- results.className = "jsoneditor-search-results";
- td.appendChild(results);
- td = document.createElement("td");
- td.className = "jsoneditor-search";
- tr.appendChild(td);
- var divInput = document.createElement("div");
- this.dom.input = divInput;
- divInput.className = "jsoneditor-search";
- divInput.title = "查找区块";
- td.appendChild(divInput);
- // table to contain the text input and search button
- var tableInput = document.createElement("table");
- tableInput.className = "jsoneditor-search-input";
- divInput.appendChild(tableInput);
- var tbodySearch = document.createElement("tbody");
- tableInput.appendChild(tbodySearch);
- tr = document.createElement("tr");
- tbodySearch.appendChild(tr);
- var refreshSearch = document.createElement("button");
- refreshSearch.className = "jsoneditor-search-refresh";
- td = document.createElement("td");
- td.appendChild(refreshSearch);
- tr.appendChild(td);
- var search = document.createElement("input");
- this.dom.search = search;
- search.className = "jsoneditor-search";
- search.oninput = function(event) {
- searchBox.onDelayedSearch(event);
- };
- search.onchange = function(event) {
- // For IE 8
- searchBox.onSearch(event);
- };
- search.onkeydown = function(event) {
- searchBox.onKeyDown(event);
- };
- search.onkeyup = function(event) {
- searchBox.onKeyUp(event);
- };
- refreshSearch.onclick = function(event) {
- search.select();
- };
- // TODO: ESC in FF restores the last input, is a FF bug, https://bugzilla.mozilla.org/show_bug.cgi?id=598819
- td = document.createElement("td");
- td.appendChild(search);
- tr.appendChild(td);
- var searchNext = document.createElement("button");
- searchNext.title = "下一个 (Enter)";
- searchNext.className = "jsoneditor-search-next";
- searchNext.onclick = function() {
- searchBox.next();
- };
- td = document.createElement("td");
- td.appendChild(searchNext);
- tr.appendChild(td);
- var searchPrevious = document.createElement("button");
- searchPrevious.title = "上一个 (Shift+Enter)";
- searchPrevious.className = "jsoneditor-search-previous";
- searchPrevious.onclick = function() {
- searchBox.previous();
- };
- td = document.createElement("td");
- td.appendChild(searchPrevious);
- tr.appendChild(td);
- };
- /**
- * Go to the next search result
- */
- JSONEditor.SearchBox.prototype.next = function() {
- if (this.results != undefined) {
- var index = this.resultIndex != undefined ? this.resultIndex + 1 : 0;
- if (index > this.results.length - 1) {
- index = 0;
- }
- this.setActiveResult(index);
- }
- };
- /**
- * Go to the prevous search result
- */
- JSONEditor.SearchBox.prototype.previous = function() {
- if (this.results != undefined) {
- var max = this.results.length - 1;
- var index = this.resultIndex != undefined ? this.resultIndex - 1 : max;
- if (index < 0) {
- index = max;
- }
- this.setActiveResult(index);
- }
- };
- /**
- * Set new value for the current active result
- * @param {Number} index
- */
- JSONEditor.SearchBox.prototype.setActiveResult = function(index) {
- // de-activate current active result
- if (this.activeResult) {
- var prevNode = this.activeResult.node;
- var prevElem = this.activeResult.elem;
- if (prevElem == "field") {
- delete prevNode.searchFieldActive;
- } else {
- delete prevNode.searchValueActive;
- }
- prevNode.updateDom();
- }
- if (!this.results || !this.results[index]) {
- // out of range, set to undefined
- this.resultIndex = undefined;
- this.activeResult = undefined;
- return;
- }
- this.resultIndex = index;
- // set new node active
- var node = this.results[this.resultIndex].node;
- var elem = this.results[this.resultIndex].elem;
- if (elem == "field") {
- node.searchFieldActive = true;
- } else {
- node.searchValueActive = true;
- }
- this.activeResult = this.results[this.resultIndex];
- node.updateDom();
- node.scrollTo();
- };
- /**
- * Set the focus to the currently active result. If there is no currently
- * active result, the next search result will get focus
- */
- JSONEditor.SearchBox.prototype.focusActiveResult = function() {
- if (!this.activeResult) {
- this.next();
- }
- if (this.activeResult) {
- this.activeResult.node.focus(this.activeResult.elem);
- }
- };
- /**
- * Cancel any running onDelayedSearch.
- */
- JSONEditor.SearchBox.prototype.clearDelay = function() {
- if (this.timeout != undefined) {
- clearTimeout(this.timeout);
- delete this.timeout;
- }
- };
- /**
- * Start a timer to execute a search after a short delay.
- * Used for reducing the number of searches while typing.
- * @param {Event} event
- */
- JSONEditor.SearchBox.prototype.onDelayedSearch = function(event) {
- // execute the search after a short delay (reduces the number of
- // search actions while typing in the search text box)
- this.clearDelay();
- var searchBox = this;
- this.timeout = setTimeout(function(event) {
- searchBox.onSearch(event);
- }, this.delay);
- };
- /**
- * Handle onSearch event
- * @param {Event} event
- * @param {boolean} [forceSearch] If true, search will be executed again even
- * when the search text is not changed.
- * Default is false.
- */
- JSONEditor.SearchBox.prototype.onSearch = function(event, forceSearch) {
- this.clearDelay();
- var value = this.dom.search.value;
- var text = value.length > 0 ? value : undefined;
- if (text != this.lastText || forceSearch) {
- // only search again when changed
- this.lastText = text;
- this.results = this.editor.search(text);
- this.setActiveResult(undefined);
- // display search results
- if (text != undefined) {
- var resultCount = this.results.length;
- switch (resultCount) {
- case 0:
- this.dom.results.innerHTML = "区块/值未找到";
- break;
- default:
- this.dom.results.innerHTML =
- "找到 " + resultCount + " 个节点";
- break;
- }
- } else {
- this.dom.results.innerHTML = "";
- }
- }
- };
- /**
- * Handle onKeyDown event in the input box
- * @param {Event} event
- */
- JSONEditor.SearchBox.prototype.onKeyDown = function(event) {
- event = event || window.event;
- var keynum = event.which || event.keyCode;
- if (keynum == 27) {
- // ESC
- this.dom.search.value = ""; // clear search
- this.onSearch(event);
- JSONEditor.Events.preventDefault(event);
- JSONEditor.Events.stopPropagation(event);
- } else if (keynum == 13) {
- // Enter
- if (event.ctrlKey) {
- // force to search again
- this.onSearch(event, true);
- } else if (event.shiftKey) {
- // move to the previous search result
- this.previous();
- } else {
- // move to the next search result
- this.next();
- }
- JSONEditor.Events.preventDefault(event);
- JSONEditor.Events.stopPropagation(event);
- }
- };
- /**
- * Handle onKeyUp event in the input box
- * @param {Event} event
- */
- JSONEditor.SearchBox.prototype.onKeyUp = function(event) {
- event = event || window.event;
- var keynum = event.which || event.keyCode;
- if (keynum != 27 && keynum != 13) {
- // !ESC and !Enter
- this.onDelayedSearch(event); // For IE 8
- }
- };
- // create namespace for event methods
- JSONEditor.Events = {};
- /**
- * Add and event listener. Works for all browsers
- * @param {Element} element An html element
- * @param {string} action The action, for example "click",
- * without the prefix "on"
- * @param {function} listener The callback function to be executed
- * @param {boolean} useCapture
- * @return {function} the created event listener
- */
- JSONEditor.Events.addEventListener = function(
- element,
- action,
- listener,
- useCapture
- ) {
- if (element.addEventListener) {
- if (useCapture === undefined) useCapture = false;
- if (
- action === "mousewheel" &&
- navigator.userAgent.indexOf("Firefox") >= 0
- ) {
- action = "DOMMouseScroll"; // For Firefox
- }
- element.addEventListener(action, listener, useCapture);
- return listener;
- } else {
- // IE browsers
- var f = function() {
- return listener.call(element, window.event);
- };
- element.attachEvent("on" + action, f);
- return f;
- }
- };
- /**
- * Remove an event listener from an element
- * @param {Element} element An html dom element
- * @param {string} action The name of the event, for example "mousedown"
- * @param {function} listener The listener function
- * @param {boolean} useCapture
- */
- JSONEditor.Events.removeEventListener = function(
- element,
- action,
- listener,
- useCapture
- ) {
- if (element.removeEventListener) {
- // non-IE browsers
- if (useCapture === undefined) useCapture = false;
- if (
- action === "mousewheel" &&
- navigator.userAgent.indexOf("Firefox") >= 0
- ) {
- action = "DOMMouseScroll"; // For Firefox
- }
- element.removeEventListener(action, listener, useCapture);
- } else {
- // IE browsers
- element.detachEvent("on" + action, listener);
- }
- };
- /**
- * Stop event propagation
- * @param {Event} event
- */
- JSONEditor.Events.stopPropagation = function(event) {
- if (!event) event = window.event;
- if (event.stopPropagation) {
- event.stopPropagation(); // non-IE browsers
- } else {
- event.cancelBubble = true; // IE browsers
- }
- };
- /**
- * Cancels the event if it is cancelable, without stopping further propagation of the event.
- * @param {Event} event
- */
- JSONEditor.Events.preventDefault = function(event) {
- if (!event) event = window.event;
- if (event.preventDefault) {
- event.preventDefault(); // non-IE browsers
- } else {
- event.returnValue = false; // IE browsers
- }
- };
- /**
- * Retrieve the absolute left value of a DOM element
- * @param {Element} elem A dom element, for example a div
- * @return {Number} left The absolute left position of this element
- * in the browser page.
- */
- JSONEditor.getAbsoluteLeft = function(elem) {
- var left = 0;
- var body = document.body;
- while (elem != null && elem != body) {
- left += elem.offsetLeft;
- left -= elem.scrollLeft;
- elem = elem.offsetParent;
- }
- return left;
- };
- /**
- * Retrieve the absolute top value of a DOM element
- * @param {Element} elem A dom element, for example a div
- * @return {Number} top The absolute top position of this element
- * in the browser page.
- */
- JSONEditor.getAbsoluteTop = function(elem) {
- var top = 0;
- var body = document.body;
- while (elem != null && elem != body) {
- top += elem.offsetTop;
- top -= elem.scrollTop;
- elem = elem.offsetParent;
- }
- return top;
- };
- /**
- * add a className to the given elements style
- * @param {Element} elem
- * @param {String} className
- */
- JSONEditor.addClassName = function(elem, className) {
- var classes = elem.className.split(" ");
- if (classes.indexOf(className) == -1) {
- classes.push(className); // add the class to the array
- elem.className = classes.join(" ");
- }
- };
- /**
- * add a className to the given elements style
- * @param {Element} elem
- * @param {String} className
- */
- JSONEditor.removeClassName = function(elem, className) {
- var classes = elem.className.split(" ");
- var index = classes.indexOf(className);
- if (index != -1) {
- classes.splice(index, 1); // remove the class from the array
- elem.className = classes.join(" ");
- }
- };
- /**
- * Strip the formatting from the contents of a div
- * the formatting from the div itself is not stripped, only from its childs.
- * @param {Element} divElement
- */
- JSONEditor.stripFormatting = function(divElement) {
- var childs = divElement.childNodes;
- for (var i = 0, iMax = childs.length; i < iMax; i++) {
- var child = childs[i];
- // remove the style
- if (child.style) {
- // TODO: test if child.attributes does contain style
- child.removeAttribute("style");
- }
- // remove all attributes
- var attributes = child.attributes;
- if (attributes) {
- for (var j = attributes.length - 1; j >= 0; j--) {
- var attribute = attributes[j];
- if (attribute.specified == true) {
- child.removeAttribute(attribute.name);
- }
- }
- }
- // recursively strip childs
- JSONEditor.stripFormatting(child);
- }
- };
- /**
- * Set focus to the end of an editable div
- * code from Nico Burns
- * http://stackoverflow.com/users/140293/nico-burns
- * http://stackoverflow.com/questions/1125292/how-to-move-cursor-to-end-of-contenteditable-entity
- * @param {Element} contentEditableElement
- */
- JSONEditor.setEndOfContentEditable = function(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
- }
- };
- /**
- * Get the inner text of an HTML element (for example a div element)
- * @param {Element} element
- * @param {Object} [buffer]
- * @return {String} innerText
- */
- JSONEditor.getInnerText = function(element, buffer) {
- var first = buffer == undefined;
- if (first) {
- buffer = {
- text: "",
- flush: function() {
- var text = this.text;
- this.text = "";
- return text;
- },
- set: function(text) {
- this.text = text;
- }
- };
- }
- // text node
- if (element.nodeValue) {
- return buffer.flush() + element.nodeValue;
- }
- // divs or other HTML elements
- if (element.hasChildNodes()) {
- var childNodes = element.childNodes;
- var innerText = "";
- for (var i = 0, iMax = childNodes.length; i < iMax; i++) {
- var child = childNodes[i];
- if (child.nodeName == "DIV" || child.nodeName == "P") {
- var prevChild = childNodes[i - 1];
- var prevName = prevChild ? prevChild.nodeName : undefined;
- if (
- prevName &&
- prevName != "DIV" &&
- prevName != "P" &&
- prevName != "BR"
- ) {
- innerText += "\n";
- buffer.flush();
- }
- innerText += JSONEditor.getInnerText(child, buffer);
- buffer.set("\n");
- } else if (child.nodeName == "BR") {
- innerText += buffer.flush();
- buffer.set("\n");
- } else {
- innerText += JSONEditor.getInnerText(child, buffer);
- }
- }
- return innerText;
- } else {
- if (
- element.nodeName == "P" &&
- JSONEditor.getInternetExplorerVersion() != -1
- ) {
- // On Internet Explorer, a <p> with hasChildNodes()==false is
- // rendered with a new line. Note that a <p> with
- // hasChildNodes()==true is rendered without a new line
- // Other browsers always ensure there is a <br> inside the <p>,
- // and if not, the <p> does not render a new line
- return buffer.flush();
- }
- }
- // br or unknown
- return "";
- };
- /**
- * Returns the version of Internet Explorer or a -1
- * (indicating the use of another browser).
- * Source: http://msdn.microsoft.com/en-us/library/ms537509(v=vs.85).aspx
- * @return {Number} Internet Explorer version, or -1 in case of an other browser
- */
- JSONEditor._ieVersion = undefined;
- JSONEditor.getInternetExplorerVersion = function() {
- if (JSONEditor._ieVersion == undefined) {
- var rv = -1; // Return value assumes failure.
- if (navigator.appName == "Microsoft Internet Explorer") {
- var ua = navigator.userAgent;
- var re = new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");
- if (re.exec(ua) != null) {
- rv = parseFloat(RegExp.$1);
- }
- }
- JSONEditor._ieVersion = rv;
- }
- return JSONEditor._ieVersion;
- };
- JSONEditor.ieVersion = JSONEditor.getInternetExplorerVersion();
- /**
- * Parse JSON using the parser built-in in the browser.
- * On exception, the jsonString is validated and a detailed error is thrown.
- * @param {String} jsonString
- */
- JSONEditor.parse = function(jsonString) {
- try {
- return JSON.parse(jsonString);
- } catch (err) {
- // get a detailed error message using validate
- var message = JSONEditor.validate(jsonString) || err;
- throw new Error(message);
- }
- };
- /**
- * Validate a string containing a JSON object
- * This method uses JSONLint to validate the String. If JSONLint is not
- * available, the built-in JSON parser of the browser is used.
- * @param {String} jsonString String with an (invalid) JSON object
- * @return {String | undefined} Returns undefined when the string is valid JSON,
- * returns a string with an error message when
- * the data is invalid
- */
- JSONEditor.validate = function(jsonString) {
- var message = undefined;
- try {
- if (window.jsonlint) {
- window.jsonlint.parse(jsonString);
- } else {
- JSON.parse(jsonString);
- }
- } catch (err) {
- message = '<pre class="error">' + err.toString() + "</pre>";
- if (window.jsonlint) {
- message +=
- '<div id="by-jsonlint"> <a class="error" href="http://zaach.github.com/jsonlint/" target="_blank">' +
- "JSONLint" +
- "</a> 提供验证.</div>";
- }
- }
- return message;
- };
- function jsonArea(ob) {
- let inputEle = ob.el;
- let insert = ob.insert;
- let nth = ob.nth;
- let change = ob.change;
- let thisare = new Object();
- if (!inputEle) {
- throw new Error("没有提供数据.");
- }
- // if target to multi dom
- if (nth) {
- thisare.data = document.querySelectorAll(inputEle)[nth - 1];
- } else {
- let all = document.querySelectorAll(inputEle);
- for (var i = 0; i < all.length; i++) {
- jsonArea({ el: inputEle, insert: insert, nth: i + 1, change: change });
- }
- // thisare.data = document.querySelector(inputEle);
- return;
- }
- // create container
- thisare.container = document.createElement("div");
- thisare.container.style.width = "100%";
- thisare.container.style.height = "auto";
- thisare.data.parentElement.insertBefore(thisare.container, thisare.data);
- // check wether is a json
- thisare.isJson = function(str) {
- if (!isNaN(str)) return false;
- if (str == "") return false;
- if (str == '""') return false;
- if (typeof str == "string") {
- try {
- JSON.parse(str);
- return true;
- } catch (e) {
- // console.log(e);
- return false;
- }
- }
- console.log("不是丢�个stringify");
- };
- // json data
- thisare.jsonval = "{}";
- // callback when change
- thisare.syn = function() {};
- // whether is textarea or input
- if (thisare.data.tagName == "TEXTAREA") {
- thisare.jsonval = thisare.data.innerHTML;
- thisare.syn = function() {
- thisare.data.innerHTML = JSON.stringify(thisare.jsonEditor.get());
- change(thisare.jsonEditor.get());
- };
- } else if (thisare.data.tagName == "INPUT") {
- thisare.jsonval = thisare.data.value;
- thisare.syn = function() {
- thisare.data.value = JSON.stringify(thisare.jsonEditor.get());
- change(thisare.jsonEditor.get());
- };
- }
- // if not json, and not insert ,then json editer
- thisare.hashJson = thisare.isJson(thisare.jsonval);
- if (!thisare.hashJson && !insert) return;
- thisare.data.style.display = "none";
- // create jsoneditor for father
- thisare.jsonEditor = new JSONEditor(thisare.container, {
- change: function() {
- thisare.lastChanged = thisare.jsonEditor;
- thisare.syn();
- }
- });
- if (thisare.hashJson) {
- thisare.jsonEditor.set(JSON.parse(thisare.jsonval));
- }
- console.log("in jsonArea");
- return thisare;
- }
|