jsoneditor.js 114 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898
  1. /**
  2. * @file jsoneditor.js
  3. *
  4. * @brief
  5. * JSONEditor is a web-based tool to view, edit, and format JSON.
  6. * It shows data a clear, editable treeview.
  7. *
  8. * Supported browsers: Chrome, Firefox, Safari, Opera, Internet Explorer 8+
  9. *
  10. * @license
  11. * This json editor is open sourced with the intention to use the editor as
  12. * a component in your own application. Not to just copy and monetize the editor
  13. * as it is.
  14. *
  15. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  16. * use this file except in compliance with the License. You may obtain a copy
  17. * of the License at
  18. *
  19. * http://www.apache.org/licenses/LICENSE-2.0
  20. *
  21. * Unless required by applicable law or agreed to in writing, software
  22. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  23. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  24. * License for the specific language governing permissions and limitations under
  25. * the License.
  26. *
  27. * Copyright (c) 2011-2012 Jos de Jong, http://jsoneditoronline.org
  28. *
  29. * @author Jos de Jong, <wjosdejong@gmail.com>
  30. * @date 2012-12-08
  31. */
  32. // Internet Explorer 8 and older does not support Array.indexOf,
  33. // so we define it here in that case
  34. // http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/
  35. if (!Array.prototype.indexOf) {
  36. Array.prototype.indexOf = function(obj) {
  37. for (var i = 0; i < this.length; i++) {
  38. if (this[i] == obj) {
  39. return i;
  40. }
  41. }
  42. return -1;
  43. };
  44. }
  45. // Internet Explorer 8 and older does not support Array.forEach,
  46. // so we define it here in that case
  47. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach
  48. if (!Array.prototype.forEach) {
  49. Array.prototype.forEach = function(fn, scope) {
  50. for (var i = 0, len = this.length; i < len; ++i) {
  51. fn.call(scope || this, this[i], i, this);
  52. }
  53. };
  54. }
  55. // define variable JSON, needed for correct error handling on IE7 and older
  56. var JSON;
  57. /**
  58. * JSONEditor
  59. * @param {Element} container Container element
  60. * @param {Object} [options] Object with options. available options:
  61. * {String} mode Editor mode. Available values:
  62. * 'editor' (default), 'viewer'.
  63. * {Boolean} search Enable search box.
  64. * True by default
  65. * {Boolean} history Enable history (undo/redo).
  66. * True by default
  67. * {function} change Callback method, triggered
  68. * on change of contents
  69. * {String} name Field name for the root node.
  70. * @param {Object | undefined} json JSON object
  71. */
  72. JSONEditor = function(container, options, json) {
  73. // check availability of JSON parser (not available in IE7 and older)
  74. if (!JSON) {
  75. throw new Error(
  76. "您当前使用的浏览器不支持 JSON. \n\n" +
  77. "请下载安装最新版本的浏览, 本站推荐Google Chrome.\n" +
  78. "(PS: 当前主流浏览器都支持JSON)."
  79. );
  80. }
  81. if (!container) {
  82. throw new Error("没有提供容器元素.");
  83. }
  84. this.container = container;
  85. this.dom = {};
  86. this._setOptions(options);
  87. if (this.options.history && this.editable) {
  88. this.history = new JSONEditor.History(this);
  89. }
  90. this._createFrame();
  91. this._createTable();
  92. this.set(json || {});
  93. };
  94. /**
  95. * Initialize and set default options
  96. * @param {Object} [options] Object with options. available options:
  97. * {String} mode Editor mode. Available values:
  98. * 'editor' (default), 'viewer'.
  99. * {Boolean} search Enable search box.
  100. * True by default.
  101. * {Boolean} history Enable history (undo/redo).
  102. * True by default.
  103. * {function} change Callback method, triggered
  104. * on change of contents.
  105. * {String} name Field name for the root node.
  106. * @private
  107. */
  108. JSONEditor.prototype._setOptions = function(options) {
  109. this.options = {
  110. search: true,
  111. history: true,
  112. mode: "editor",
  113. name: undefined // field name of root node
  114. };
  115. // copy all options
  116. if (options) {
  117. for (var prop in options) {
  118. if (options.hasOwnProperty(prop)) {
  119. this.options[prop] = options[prop];
  120. }
  121. }
  122. // check for deprecated options
  123. if (options.enableSearch) {
  124. // deprecated since version 1.6.0, 2012-11-03
  125. this.options.search = options.enableSearch;
  126. // console.log('WARNING: Option "enableSearch" is deprecated. Use "search" instead.');
  127. }
  128. if (options.enableHistory) {
  129. // deprecated since version 1.6.0, 2012-11-03
  130. this.options.search = options.enableHistory;
  131. // console.log('WARNING: Option "enableHistory" is deprecated. Use "history" instead.');
  132. }
  133. }
  134. // interpret the options
  135. this.editable = this.options.mode != "viewer";
  136. };
  137. // node currently being edited
  138. JSONEditor.focusNode = undefined;
  139. /**
  140. * Set JSON object in editor
  141. * @param {Object | undefined} json JSON data
  142. * @param {String} [name] Optional field name for the root node.
  143. * Can also be set using setName(name).
  144. */
  145. JSONEditor.prototype.set = function(json, name) {
  146. // adjust field name for root node
  147. if (name) {
  148. this.options.name = name;
  149. }
  150. // verify if json is valid JSON, ignore when a function
  151. if (json instanceof Function || json === undefined) {
  152. this.clear();
  153. } else {
  154. this.content.removeChild(this.table); // Take the table offline
  155. // replace the root node
  156. var params = {
  157. field: this.options.name,
  158. value: json
  159. };
  160. var node = new JSONEditor.Node(this, params);
  161. this._setRoot(node);
  162. // expand
  163. var recurse = false;
  164. this.node.expand(recurse);
  165. this.content.appendChild(this.table); // Put the table online again
  166. }
  167. // TODO: maintain history, store last state and previous document
  168. if (this.history) {
  169. this.history.clear();
  170. }
  171. };
  172. /**
  173. * Get JSON object from editor
  174. * @return {Object | undefined} json
  175. */
  176. JSONEditor.prototype.get = function() {
  177. // remove focus from currently edited node
  178. if (JSONEditor.focusNode) {
  179. JSONEditor.focusNode.blur();
  180. }
  181. if (this.node) {
  182. return this.node.getValue();
  183. } else {
  184. return undefined;
  185. }
  186. };
  187. /**
  188. * Set a field name for the root node.
  189. * @param {String | undefined} name
  190. */
  191. JSONEditor.prototype.setName = function(name) {
  192. this.options.name = name;
  193. if (this.node) {
  194. this.node.updateField(this.options.name);
  195. }
  196. };
  197. /**
  198. * Get the field name for the root node.
  199. * @return {String | undefined} name
  200. */
  201. JSONEditor.prototype.getName = function() {
  202. return this.options.name;
  203. };
  204. /**
  205. * Remove the root node from the editor
  206. */
  207. JSONEditor.prototype.clear = function() {
  208. if (this.node) {
  209. this.node.collapse();
  210. this.tbody.removeChild(this.node.getDom());
  211. delete this.node;
  212. }
  213. };
  214. /**
  215. * Set the root node for the json editor
  216. * @param {JSONEditor.Node} node
  217. * @private
  218. */
  219. JSONEditor.prototype._setRoot = function(node) {
  220. this.clear();
  221. this.node = node;
  222. // append to the dom
  223. this.tbody.appendChild(node.getDom());
  224. };
  225. /**
  226. * Search text in all nodes
  227. * The nodes will be expanded when the text is found one of its childs,
  228. * else it will be collapsed. Searches are case insensitive.
  229. * @param {String} text
  230. * @return {Object[]} results Array with nodes containing the search results
  231. * The result objects contains fields:
  232. * - {JSONEditor.Node} node,
  233. * - {String} elem the dom element name where
  234. * the result is found ('field' or
  235. * 'value')
  236. */
  237. JSONEditor.prototype.search = function(text) {
  238. var results;
  239. if (this.node) {
  240. this.content.removeChild(this.table); // Take the table offline
  241. results = this.node.search(text);
  242. this.content.appendChild(this.table); // Put the table online again
  243. } else {
  244. results = [];
  245. }
  246. return results;
  247. };
  248. /**
  249. * Expand all nodes
  250. */
  251. JSONEditor.prototype.expandAll = function() {
  252. if (this.node) {
  253. this.content.removeChild(this.table); // Take the table offline
  254. this.node.expand();
  255. this.content.appendChild(this.table); // Put the table online again
  256. }
  257. };
  258. /**
  259. * Collapse all nodes
  260. */
  261. JSONEditor.prototype.collapseAll = function() {
  262. if (this.node) {
  263. this.content.removeChild(this.table); // Take the table offline
  264. this.node.collapse();
  265. this.content.appendChild(this.table); // Put the table online again
  266. }
  267. };
  268. /**
  269. * The method onChange is called whenever a field or value is changed, created,
  270. * deleted, duplicated, etc.
  271. * @param {String} action Change action. Available values: "editField",
  272. * "editValue", "changeType", "appendNode",
  273. * "removeNode", "duplicateNode", "moveNode", "expand",
  274. * "collapse".
  275. * @param {Object} params Object containing parameters describing the change.
  276. * The parameters in params depend on the action (for
  277. * example for "editValue" the Node, old value, and new
  278. * value are provided). params contains all information
  279. * needed to undo or redo the action.
  280. */
  281. JSONEditor.prototype.onAction = function(action, params) {
  282. // add an action to the history
  283. if (this.history) {
  284. this.history.add(action, params);
  285. }
  286. // trigger the onChange callback
  287. if (this.options.change) {
  288. try {
  289. this.options.change();
  290. } catch (err) {
  291. //console.log('Error in change callback: ', err);
  292. }
  293. }
  294. };
  295. /**
  296. * Set the focus to the JSONEditor. A hidden input field will be created
  297. * which captures key events
  298. */
  299. // TODO: use the focus method?
  300. JSONEditor.prototype.focus = function() {
  301. /*
  302. if (!this.dom.focus) {
  303. this.dom.focus = document.createElement('input');
  304. this.dom.focus.className = 'jsoneditor-hidden-focus';
  305. var editor = this;
  306. this.dom.focus.onblur = function () {
  307. // remove itself
  308. if (editor.dom.focus) {
  309. var focus = editor.dom.focus;
  310. delete editor.dom.focus;
  311. editor.frame.removeChild(focus);
  312. }
  313. };
  314. // attach the hidden input box to the DOM
  315. if (this.frame.firstChild) {
  316. this.frame.insertBefore(this.dom.focus, this.frame.firstChild);
  317. }
  318. else {
  319. this.frame.appendChild(this.dom.focus);
  320. }
  321. }
  322. this.dom.focus.focus();
  323. */
  324. };
  325. /**
  326. * Adjust the scroll position such that given top position is shown at 1/4
  327. * of the window height.
  328. * @param {Number} top
  329. */
  330. JSONEditor.prototype.scrollTo = function(top) {
  331. var content = this.content;
  332. if (content) {
  333. // cancel any running animation
  334. var editor = this;
  335. if (editor.animateTimeout) {
  336. clearTimeout(editor.animateTimeout);
  337. delete editor.animateTimeout;
  338. }
  339. // calculate final scroll position
  340. var height = content.clientHeight;
  341. var bottom = content.scrollHeight - height;
  342. var finalScrollTop = Math.min(Math.max(top - height / 4, 0), bottom);
  343. // animate towards the new scroll position
  344. var animate = function() {
  345. var scrollTop = content.scrollTop;
  346. var diff = finalScrollTop - scrollTop;
  347. if (Math.abs(diff) > 3) {
  348. content.scrollTop += diff / 3;
  349. editor.animateTimeout = setTimeout(animate, 50);
  350. }
  351. };
  352. animate();
  353. }
  354. };
  355. /**
  356. * @constructor JSONEditor.History
  357. * Store action history, enables undo and redo
  358. * @param {JSONEditor} editor
  359. */
  360. JSONEditor.History = function(editor) {
  361. this.editor = editor;
  362. this.clear();
  363. // map with all supported actions
  364. this.actions = {
  365. editField: {
  366. undo: function(obj) {
  367. obj.params.node.updateField(obj.params.oldValue);
  368. },
  369. redo: function(obj) {
  370. obj.params.node.updateField(obj.params.newValue);
  371. }
  372. },
  373. editValue: {
  374. undo: function(obj) {
  375. obj.params.node.updateValue(obj.params.oldValue);
  376. },
  377. redo: function(obj) {
  378. obj.params.node.updateValue(obj.params.newValue);
  379. }
  380. },
  381. appendNode: {
  382. undo: function(obj) {
  383. obj.params.parent.removeChild(obj.params.node);
  384. },
  385. redo: function(obj) {
  386. obj.params.parent.appendChild(obj.params.node);
  387. }
  388. },
  389. removeNode: {
  390. undo: function(obj) {
  391. var parent = obj.params.parent;
  392. var beforeNode = parent.childs[obj.params.index] || parent.append;
  393. parent.insertBefore(obj.params.node, beforeNode);
  394. },
  395. redo: function(obj) {
  396. obj.params.parent.removeChild(obj.params.node);
  397. }
  398. },
  399. duplicateNode: {
  400. undo: function(obj) {
  401. obj.params.parent.removeChild(obj.params.clone);
  402. },
  403. redo: function(obj) {
  404. // TODO: insert after instead of insert before
  405. obj.params.parent.insertBefore(obj.params.clone, obj.params.node);
  406. }
  407. },
  408. changeType: {
  409. undo: function(obj) {
  410. obj.params.node.changeType(obj.params.oldType);
  411. },
  412. redo: function(obj) {
  413. obj.params.node.changeType(obj.params.newType);
  414. }
  415. },
  416. moveNode: {
  417. undo: function(obj) {
  418. obj.params.startParent.moveTo(obj.params.node, obj.params.startIndex);
  419. },
  420. redo: function(obj) {
  421. obj.params.endParent.moveTo(obj.params.node, obj.params.endIndex);
  422. }
  423. }
  424. // TODO: restore the original caret position and selection with each undo
  425. // TODO: implement history for actions "expand", "collapse", "scroll", "setDocument"
  426. };
  427. };
  428. /**
  429. * The method onChange is executed when the History is changed, and can
  430. * be overloaded.
  431. */
  432. JSONEditor.History.prototype.onChange = function() {};
  433. /**
  434. * Add a new action to the history
  435. * @param {String} action The executed action. Available actions: "editField",
  436. * "editValue", "changeType", "appendNode",
  437. * "removeNode", "duplicateNode", "moveNode"
  438. * @param {Object} params Object containing parameters describing the change.
  439. * The parameters in params depend on the action (for
  440. * example for "editValue" the Node, old value, and new
  441. * value are provided). params contains all information
  442. * needed to undo or redo the action.
  443. */
  444. JSONEditor.History.prototype.add = function(action, params) {
  445. this.index++;
  446. this.history[this.index] = {
  447. action: action,
  448. params: params,
  449. timestamp: new Date()
  450. };
  451. // remove redo actions which are invalid now
  452. if (this.index < this.history.length - 1) {
  453. this.history.splice(this.index + 1, this.history.length - this.index - 1);
  454. }
  455. // fire onchange event
  456. this.onChange();
  457. };
  458. /**
  459. * Clear history
  460. */
  461. JSONEditor.History.prototype.clear = function() {
  462. this.history = [];
  463. this.index = -1;
  464. // fire onchange event
  465. this.onChange();
  466. };
  467. /**
  468. * Check if there is an action available for undo
  469. * @return {Boolean} canUndo
  470. */
  471. JSONEditor.History.prototype.canUndo = function() {
  472. return this.index >= 0;
  473. };
  474. /**
  475. * Check if there is an action available for redo
  476. * @return {Boolean} canRedo
  477. */
  478. JSONEditor.History.prototype.canRedo = function() {
  479. return this.index < this.history.length - 1;
  480. };
  481. /**
  482. * Undo the last action
  483. */
  484. JSONEditor.History.prototype.undo = function() {
  485. if (this.canUndo()) {
  486. var obj = this.history[this.index];
  487. if (obj) {
  488. var action = this.actions[obj.action];
  489. if (action && action.undo) {
  490. action.undo(obj);
  491. } else {
  492. //console.log('Error: unknown action "' + obj.action + '"');
  493. }
  494. }
  495. this.index--;
  496. // fire onchange event
  497. this.onChange();
  498. }
  499. };
  500. /**
  501. * Redo the last action
  502. */
  503. JSONEditor.History.prototype.redo = function() {
  504. if (this.canRedo()) {
  505. this.index++;
  506. var obj = this.history[this.index];
  507. if (obj) {
  508. if (obj) {
  509. var action = this.actions[obj.action];
  510. if (action && action.redo) {
  511. action.redo(obj);
  512. } else {
  513. //console.log('Error: unknown action "' + obj.action + '"');
  514. }
  515. }
  516. }
  517. // fire onchange event
  518. this.onChange();
  519. }
  520. };
  521. /**
  522. * @constructor JSONEditor.Node
  523. * Create a new Node
  524. * @param {JSONEditor} editor
  525. * @param {Object} params Can contain parameters: field, fieldEditable, value.
  526. */
  527. JSONEditor.Node = function(editor, params) {
  528. this.editor = editor;
  529. this.dom = {};
  530. this.expanded = false;
  531. if (params && params instanceof Object) {
  532. this.setField(params.field, params.fieldEditable);
  533. this.setValue(params.value);
  534. } else {
  535. this.setField();
  536. this.setValue();
  537. }
  538. };
  539. /**
  540. * Set parent node
  541. * @param {JSONEditor.Node} parent
  542. */
  543. JSONEditor.Node.prototype.setParent = function(parent) {
  544. this.parent = parent;
  545. };
  546. /**
  547. * Get parent node. Returns undefined when no parent node is set.
  548. * @return {JSONEditor.Node} parent
  549. */
  550. JSONEditor.Node.prototype.getParent = function() {
  551. return this.parent;
  552. };
  553. /**
  554. * Set field
  555. * @param {String} field
  556. * @param {boolean} fieldEditable
  557. */
  558. JSONEditor.Node.prototype.setField = function(field, fieldEditable) {
  559. this.field = field;
  560. this.fieldEditable = fieldEditable == true;
  561. };
  562. /**
  563. * Get field
  564. * @return {String}
  565. */
  566. JSONEditor.Node.prototype.getField = function() {
  567. if (this.field === undefined) {
  568. this._getDomField();
  569. }
  570. return this.field;
  571. };
  572. /**
  573. * Set value. Value is a JSON structure or an element String, Boolean, etc.
  574. * @param {*} value
  575. */
  576. JSONEditor.Node.prototype.setValue = function(value) {
  577. var childValue, child;
  578. // first clear all current childs (if any)
  579. var childs = this.childs;
  580. if (childs) {
  581. while (childs.length) {
  582. this.removeChild(childs[0]);
  583. }
  584. }
  585. // TODO: remove the DOM of this Node
  586. this.type = this._getType(value);
  587. if (this.type == "array") {
  588. // array
  589. this.childs = [];
  590. for (var i = 0, iMax = value.length; i < iMax; i++) {
  591. childValue = value[i];
  592. if (childValue !== undefined && !(childValue instanceof Function)) {
  593. // ignore undefined and functions
  594. child = new JSONEditor.Node(this.editor, {
  595. value: childValue
  596. });
  597. this.appendChild(child);
  598. }
  599. }
  600. this.value = "";
  601. } else if (this.type == "object") {
  602. // object
  603. this.childs = [];
  604. for (var childField in value) {
  605. if (value.hasOwnProperty(childField)) {
  606. childValue = value[childField];
  607. if (childValue !== undefined && !(childValue instanceof Function)) {
  608. // ignore undefined and functions
  609. child = new JSONEditor.Node(this.editor, {
  610. field: childField,
  611. value: childValue
  612. });
  613. this.appendChild(child);
  614. }
  615. }
  616. }
  617. this.value = "";
  618. } else {
  619. // value
  620. this.childs = undefined;
  621. this.value = value;
  622. /* TODO
  623. if (typeof(value) == 'string') {
  624. var escValue = JSON.stringify(value);
  625. this.value = escValue.substring(1, escValue.length - 1);
  626. console.log('check', value, this.value);
  627. }
  628. else {
  629. this.value = value;
  630. }
  631. */
  632. }
  633. };
  634. /**
  635. * Get value. Value is a JSON structure
  636. * @return {*} value
  637. */
  638. JSONEditor.Node.prototype.getValue = function() {
  639. //var childs, i, iMax;
  640. if (this.type == "array") {
  641. var arr = [];
  642. this.childs.forEach(function(child) {
  643. arr.push(child.getValue());
  644. });
  645. return arr;
  646. } else if (this.type == "object") {
  647. var obj = {};
  648. this.childs.forEach(function(child) {
  649. obj[child.getField()] = child.getValue();
  650. });
  651. return obj;
  652. } else {
  653. if (this.value === undefined) {
  654. this._getDomValue();
  655. }
  656. return this.value;
  657. }
  658. };
  659. /**
  660. * Get the nesting level of this node
  661. * @return {Number} level
  662. */
  663. JSONEditor.Node.prototype.getLevel = function() {
  664. return this.parent ? this.parent.getLevel() + 1 : 0;
  665. };
  666. /**
  667. * Create a clone of a node
  668. * The complete state of a clone is copied, including whether it is expanded or
  669. * not. The DOM elements are not cloned.
  670. * @return {JSONEditor.Node} clone
  671. */
  672. JSONEditor.Node.prototype.clone = function() {
  673. var clone = new JSONEditor.Node(this.editor);
  674. clone.type = this.type;
  675. clone.field = this.field;
  676. clone.fieldInnerText = this.fieldInnerText;
  677. clone.fieldEditable = this.fieldEditable;
  678. clone.value = this.value;
  679. clone.valueInnerText = this.valueInnerText;
  680. clone.expanded = this.expanded;
  681. if (this.childs) {
  682. // an object or array
  683. var cloneChilds = [];
  684. this.childs.forEach(function(child) {
  685. var childClone = child.clone();
  686. childClone.setParent(clone);
  687. cloneChilds.push(childClone);
  688. });
  689. clone.childs = cloneChilds;
  690. } else {
  691. // a value
  692. clone.childs = undefined;
  693. }
  694. return clone;
  695. };
  696. /**
  697. * Expand this node and optionally its childs.
  698. * @param {boolean} recurse Optional recursion, true by default. When
  699. * true, all childs will be expanded recursively
  700. */
  701. JSONEditor.Node.prototype.expand = function(recurse) {
  702. if (!this.childs) {
  703. return;
  704. }
  705. // set this node expanded
  706. this.expanded = true;
  707. if (this.dom.expand) {
  708. this.dom.expand.className = "jsoneditor-expanded";
  709. }
  710. this.showChilds();
  711. if (recurse != false) {
  712. this.childs.forEach(function(child) {
  713. child.expand(recurse);
  714. });
  715. }
  716. };
  717. /**
  718. * Collapse this node and optionally its childs.
  719. * @param {Number} recurse Optional recursion, true by default. When
  720. * true, all childs will be collapsed recursively
  721. */
  722. JSONEditor.Node.prototype.collapse = function(recurse) {
  723. if (!this.childs) {
  724. return;
  725. }
  726. this.hideChilds();
  727. // collapse childs in case of recurse
  728. if (recurse != false) {
  729. this.childs.forEach(function(child) {
  730. child.collapse(recurse);
  731. });
  732. }
  733. // make this node collapsed
  734. if (this.dom.expand) {
  735. this.dom.expand.className = "jsoneditor-collapsed";
  736. }
  737. this.expanded = false;
  738. };
  739. /**
  740. * Recursively show all childs when they are expanded
  741. */
  742. JSONEditor.Node.prototype.showChilds = function() {
  743. var childs = this.childs;
  744. if (!childs) {
  745. return;
  746. }
  747. if (!this.expanded) {
  748. return;
  749. }
  750. var tr = this.dom.tr;
  751. var table = tr ? tr.parentNode : undefined;
  752. if (table) {
  753. // show row with append button
  754. var append = this.getAppend();
  755. var nextTr = tr.nextSibling;
  756. if (nextTr) {
  757. table.insertBefore(append, nextTr);
  758. } else {
  759. table.appendChild(append);
  760. }
  761. // show childs
  762. this.childs.forEach(function(child) {
  763. table.insertBefore(child.getDom(), append);
  764. child.showChilds();
  765. });
  766. }
  767. };
  768. /**
  769. * Hide the node with all its childs
  770. */
  771. JSONEditor.Node.prototype.hide = function() {
  772. var tr = this.dom.tr;
  773. var table = tr ? tr.parentNode : undefined;
  774. if (table) {
  775. table.removeChild(tr);
  776. }
  777. this.hideChilds();
  778. };
  779. /**
  780. * Recursively hide all childs
  781. */
  782. JSONEditor.Node.prototype.hideChilds = function() {
  783. var childs = this.childs;
  784. if (!childs) {
  785. return;
  786. }
  787. if (!this.expanded) {
  788. return;
  789. }
  790. // hide append row
  791. var append = this.getAppend();
  792. if (append.parentNode) {
  793. append.parentNode.removeChild(append);
  794. }
  795. // hide childs
  796. this.childs.forEach(function(child) {
  797. child.hide();
  798. });
  799. };
  800. /**
  801. * Add a new child to the node.
  802. * Only applicable when Node value is of type array or object
  803. * @param {JSONEditor.Node} node
  804. */
  805. JSONEditor.Node.prototype.appendChild = function(node) {
  806. if (this.type == "array" || this.type == "object") {
  807. // adjust the link to the parent
  808. node.setParent(this);
  809. node.fieldEditable = this.type == "object";
  810. if (this.type == "array") {
  811. node.index = this.childs.length;
  812. }
  813. this.childs.push(node);
  814. if (this.expanded) {
  815. // insert into the DOM, before the appendRow
  816. var newtr = node.getDom();
  817. var appendTr = this.getAppend();
  818. var table = appendTr ? appendTr.parentNode : undefined;
  819. if (appendTr && table) {
  820. table.insertBefore(newtr, appendTr);
  821. }
  822. node.showChilds();
  823. }
  824. this.updateDom({
  825. updateIndexes: true
  826. });
  827. node.updateDom({
  828. recurse: true
  829. });
  830. }
  831. };
  832. /**
  833. * Move a node from its current parent to this node
  834. * Only applicable when Node value is of type array or object
  835. * @param {JSONEditor.Node} node
  836. * @param {JSONEditor.Node} beforeNode
  837. */
  838. JSONEditor.Node.prototype.moveBefore = function(node, beforeNode) {
  839. if (this.type == "array" || this.type == "object") {
  840. // create a temporary row, to prevent the scroll position from jumping
  841. // when removing the node
  842. var tbody = this.dom.tr ? this.dom.tr.parentNode : undefined;
  843. if (tbody) {
  844. var trTemp = document.createElement("tr");
  845. trTemp.style.height = tbody.clientHeight + "px";
  846. tbody.appendChild(trTemp);
  847. }
  848. var parent = node.getParent();
  849. if (parent) {
  850. parent.removeChild(node);
  851. }
  852. if (beforeNode instanceof JSONEditor.AppendNode) {
  853. this.appendChild(node);
  854. } else {
  855. this.insertBefore(node, beforeNode);
  856. }
  857. if (tbody) {
  858. tbody.removeChild(trTemp);
  859. }
  860. }
  861. };
  862. /**
  863. * Move a node from its current parent to this node
  864. * Only applicable when Node value is of type array or object.
  865. * If index is out of range, the node will be appended to the end
  866. * @param {JSONEditor.Node} node
  867. * @param {Number} index
  868. */
  869. JSONEditor.Node.prototype.moveTo = function(node, index) {
  870. if (node.parent == this) {
  871. // same parent
  872. var currentIndex = this.childs.indexOf(node);
  873. if (currentIndex < index) {
  874. // compensate the index for removal of the node itself
  875. index++;
  876. }
  877. }
  878. var beforeNode = this.childs[index] || this.append;
  879. this.moveBefore(node, beforeNode);
  880. };
  881. /**
  882. * Insert a new child before a given node
  883. * Only applicable when Node value is of type array or object
  884. * @param {JSONEditor.Node} node
  885. * @param {JSONEditor.Node} beforeNode
  886. */
  887. JSONEditor.Node.prototype.insertBefore = function(node, beforeNode) {
  888. if (this.type == "array" || this.type == "object") {
  889. if (beforeNode == this.append) {
  890. // append to the child nodes
  891. // adjust the link to the parent
  892. node.setParent(this);
  893. node.fieldEditable = this.type == "object";
  894. this.childs.push(node);
  895. } else {
  896. // insert before a child node
  897. var index = this.childs.indexOf(beforeNode);
  898. if (index == -1) {
  899. throw new Error("节点未找到.");
  900. }
  901. // adjust the link to the parent
  902. node.setParent(this);
  903. node.fieldEditable = this.type == "object";
  904. this.childs.splice(index, 0, node);
  905. }
  906. if (this.expanded) {
  907. // insert into the DOM
  908. var newTr = node.getDom();
  909. var nextTr = beforeNode.getDom();
  910. var table = nextTr ? nextTr.parentNode : undefined;
  911. if (nextTr && table) {
  912. table.insertBefore(newTr, nextTr);
  913. }
  914. node.showChilds();
  915. }
  916. this.updateDom({
  917. updateIndexes: true
  918. });
  919. node.updateDom({
  920. recurse: true
  921. });
  922. }
  923. };
  924. /**
  925. * Search in this node
  926. * The node will be expanded when the text is found one of its childs, else
  927. * it will be collapsed. Searches are case insensitive.
  928. * @param {String} text
  929. * @return {JSONEditor.Node[]} results Array with nodes containing the search text
  930. */
  931. JSONEditor.Node.prototype.search = function(text) {
  932. var results = [];
  933. var index;
  934. var search = text ? text.toLowerCase() : undefined;
  935. // delete old search data
  936. delete this.searchField;
  937. delete this.searchValue;
  938. // search in field
  939. if (this.field != undefined) {
  940. var field = String(this.field).toLowerCase();
  941. index = field.indexOf(search);
  942. if (index != -1) {
  943. this.searchField = true;
  944. results.push({
  945. node: this,
  946. elem: "field"
  947. });
  948. }
  949. // update dom
  950. this._updateDomField();
  951. }
  952. // search in value
  953. if (this.type == "array" || this.type == "object") {
  954. // array, object
  955. // search the nodes childs
  956. if (this.childs) {
  957. var childResults = [];
  958. this.childs.forEach(function(child) {
  959. childResults = childResults.concat(child.search(text));
  960. });
  961. results = results.concat(childResults);
  962. }
  963. // update dom
  964. if (search != undefined) {
  965. var recurse = false;
  966. if (childResults.length == 0) {
  967. this.collapse(recurse);
  968. } else {
  969. this.expand(recurse);
  970. }
  971. }
  972. } else {
  973. // string, auto
  974. if (this.value != undefined) {
  975. var value = String(this.value).toLowerCase();
  976. index = value.indexOf(search);
  977. if (index != -1) {
  978. this.searchValue = true;
  979. results.push({
  980. node: this,
  981. elem: "value"
  982. });
  983. }
  984. }
  985. // update dom
  986. this._updateDomValue();
  987. }
  988. return results;
  989. };
  990. /**
  991. * Move the scroll position such that this node is in the visible area.
  992. * The node will not get the focus
  993. */
  994. JSONEditor.Node.prototype.scrollTo = function() {
  995. if (!this.dom.tr || !this.dom.tr.parentNode) {
  996. // if the node is not visible, expand its parents
  997. var parent = this.parent;
  998. var recurse = false;
  999. while (parent) {
  1000. parent.expand(recurse);
  1001. parent = parent.parent;
  1002. }
  1003. }
  1004. if (this.dom.tr && this.dom.tr.parentNode) {
  1005. this.editor.scrollTo(this.dom.tr.offsetTop);
  1006. }
  1007. };
  1008. /**
  1009. * Set focus to the value of this node
  1010. * @param {String} [field] The field name of the element to get the focus
  1011. * available values: 'field', 'value'
  1012. */
  1013. JSONEditor.Node.prototype.focus = function(field) {
  1014. if (this.dom.tr && this.dom.tr.parentNode) {
  1015. if (field != "value" && this.fieldEditable) {
  1016. var domField = this.dom.field;
  1017. if (domField) {
  1018. domField.focus();
  1019. }
  1020. } else {
  1021. var domValue = this.dom.value;
  1022. if (domValue) {
  1023. domValue.focus();
  1024. }
  1025. }
  1026. }
  1027. };
  1028. /**
  1029. * Update the values from the DOM field and value of this node
  1030. */
  1031. JSONEditor.Node.prototype.blur = function() {
  1032. // retrieve the actual field and value from the DOM.
  1033. this._getDomValue(false);
  1034. this._getDomField(false);
  1035. };
  1036. /**
  1037. * Duplicate given child node
  1038. * new structure will be added right before the cloned node
  1039. * @param {JSONEditor.Node} node the childNode to be duplicated
  1040. * @return {JSONEditor.Node} clone the clone of the node
  1041. * @private
  1042. */
  1043. JSONEditor.Node.prototype._duplicate = function(node) {
  1044. var clone = node.clone();
  1045. /* TODO: adjust the field name (to prevent equal field names)
  1046. if (this.type == 'object') {
  1047. }
  1048. */
  1049. // TODO: insert after instead of insert before
  1050. this.insertBefore(clone, node);
  1051. return clone;
  1052. };
  1053. /**
  1054. * Check if given node is a child. The method will check recursively to find
  1055. * this node.
  1056. * @param {JSONEditor.Node} node
  1057. * @return {boolean} containsNode
  1058. */
  1059. JSONEditor.Node.prototype.containsNode = function(node) {
  1060. if (this == node) {
  1061. return true;
  1062. }
  1063. var childs = this.childs;
  1064. if (childs) {
  1065. // TOOD: use the js5 Array.some() here?
  1066. for (var i = 0, iMax = childs.length; i < iMax; i++) {
  1067. if (childs[i].containsNode(node)) {
  1068. return true;
  1069. }
  1070. }
  1071. }
  1072. return false;
  1073. };
  1074. /**
  1075. * Move given node into this node
  1076. * @param {JSONEditor.Node} node the childNode to be moved
  1077. * @param {JSONEditor.Node} beforeNode node will be inserted before given
  1078. * node. If no beforeNode is given,
  1079. * the node is appended at the end
  1080. * @private
  1081. */
  1082. JSONEditor.Node.prototype._move = function(node, beforeNode) {
  1083. if (node == beforeNode) {
  1084. // nothing to do...
  1085. return;
  1086. }
  1087. // check if this node is not a child of the node to be moved here
  1088. if (node.containsNode(this)) {
  1089. throw new Error("不能把区域移动到自身的子节点.");
  1090. }
  1091. // remove the original node
  1092. if (node.parent) {
  1093. node.parent.removeChild(node);
  1094. }
  1095. // create a clone of the node
  1096. var clone = node.clone();
  1097. node.clearDom();
  1098. // insert or append the node
  1099. if (beforeNode) {
  1100. this.insertBefore(clone, beforeNode);
  1101. } else {
  1102. this.appendChild(clone);
  1103. }
  1104. /* TODO: adjust the field name (to prevent equal field names)
  1105. if (this.type == 'object') {
  1106. }
  1107. */
  1108. };
  1109. /**
  1110. * Remove a child from the node.
  1111. * Only applicable when Node value is of type array or object
  1112. * @param {JSONEditor.Node} node The child node to be removed;
  1113. * @return {JSONEditor.Node | undefined} node The removed node on success,
  1114. * else undefined
  1115. */
  1116. JSONEditor.Node.prototype.removeChild = function(node) {
  1117. if (this.childs) {
  1118. var index = this.childs.indexOf(node);
  1119. if (index != -1) {
  1120. node.hide();
  1121. // delete old search results
  1122. delete node.searchField;
  1123. delete node.searchValue;
  1124. var removedNode = this.childs.splice(index, 1)[0];
  1125. this.updateDom({
  1126. updateIndexes: true
  1127. });
  1128. return removedNode;
  1129. }
  1130. }
  1131. return undefined;
  1132. };
  1133. /**
  1134. * Remove a child node node from this node
  1135. * This method is equal to Node.removeChild, except that _remove firex an
  1136. * onChange event.
  1137. * @param {JSONEditor.Node} node
  1138. * @private
  1139. */
  1140. JSONEditor.Node.prototype._remove = function(node) {
  1141. this.removeChild(node);
  1142. };
  1143. /**
  1144. * Change the type of the value of this Node
  1145. * @param {String} newType
  1146. */
  1147. JSONEditor.Node.prototype.changeType = function(newType) {
  1148. var oldType = this.type;
  1149. if (
  1150. (newType == "string" || newType == "auto") &&
  1151. (oldType == "string" || oldType == "auto")
  1152. ) {
  1153. // this is an easy change
  1154. this.type = newType;
  1155. } else {
  1156. // change from array to object, or from string/auto to object/array
  1157. var table = this.dom.tr ? this.dom.tr.parentNode : undefined;
  1158. var lastTr;
  1159. if (this.expanded) {
  1160. lastTr = this.getAppend();
  1161. } else {
  1162. lastTr = this.getDom();
  1163. }
  1164. var nextTr = lastTr && lastTr.parentNode ? lastTr.nextSibling : undefined;
  1165. // hide current field and all its childs
  1166. this.hide();
  1167. this.clearDom();
  1168. // adjust the field and the value
  1169. this.type = newType;
  1170. // adjust childs
  1171. if (newType == "object") {
  1172. if (!this.childs) {
  1173. this.childs = [];
  1174. }
  1175. this.childs.forEach(function(child, index) {
  1176. child.clearDom();
  1177. delete child.index;
  1178. child.fieldEditable = true;
  1179. if (child.field == undefined) {
  1180. child.field = index;
  1181. }
  1182. });
  1183. if (oldType == "string" || oldType == "auto") {
  1184. this.expanded = true;
  1185. }
  1186. } else if (newType == "array") {
  1187. if (!this.childs) {
  1188. this.childs = [];
  1189. }
  1190. this.childs.forEach(function(child, index) {
  1191. child.clearDom();
  1192. child.fieldEditable = false;
  1193. child.index = index;
  1194. });
  1195. if (oldType == "string" || oldType == "auto") {
  1196. this.expanded = true;
  1197. }
  1198. } else {
  1199. this.expanded = false;
  1200. }
  1201. // create new DOM
  1202. if (table) {
  1203. if (nextTr) {
  1204. table.insertBefore(this.getDom(), nextTr);
  1205. } else {
  1206. table.appendChild(this.getDom());
  1207. }
  1208. }
  1209. this.showChilds();
  1210. }
  1211. if (newType == "auto" || newType == "string") {
  1212. // cast value to the correct type
  1213. if (newType == "string") {
  1214. this.value = String(this.value);
  1215. } else {
  1216. this.value = this._stringCast(String(this.value));
  1217. }
  1218. this.focus();
  1219. }
  1220. this.updateDom({
  1221. updateIndexes: true
  1222. });
  1223. };
  1224. /**
  1225. * Retrieve value from DOM
  1226. * @param {boolean} silent. If true (default), no errors will be thrown in
  1227. * case of invalid data
  1228. * @private
  1229. */
  1230. JSONEditor.Node.prototype._getDomValue = function(silent) {
  1231. if (this.dom.value && this.type != "array" && this.type != "object") {
  1232. this.valueInnerText = JSONEditor.getInnerText(this.dom.value);
  1233. }
  1234. if (this.valueInnerText != undefined) {
  1235. try {
  1236. // retrieve the value
  1237. var value;
  1238. if (this.type == "string") {
  1239. value = this._unescapeHTML(this.valueInnerText);
  1240. } else {
  1241. var str = this._unescapeHTML(this.valueInnerText);
  1242. value = this._stringCast(str);
  1243. }
  1244. if (value !== this.value) {
  1245. var oldValue = this.value;
  1246. this.value = value;
  1247. this.editor.onAction("editValue", {
  1248. node: this,
  1249. oldValue: oldValue,
  1250. newValue: value
  1251. });
  1252. }
  1253. } catch (err) {
  1254. this.value = undefined;
  1255. // TODO: sent an action with the new, invalid value?
  1256. if (silent != true) {
  1257. throw err;
  1258. }
  1259. }
  1260. }
  1261. };
  1262. /**
  1263. * Update dom value:
  1264. * - the text color of the value, depending on the type of the value
  1265. * - the height of the field, depending on the width
  1266. * - background color in case it is empty
  1267. * @private
  1268. */
  1269. JSONEditor.Node.prototype._updateDomValue = function() {
  1270. var domValue = this.dom.value;
  1271. if (domValue) {
  1272. // set text color depending on value type
  1273. var v = this.value;
  1274. var t = this.type == "auto" ? typeof v : this.type;
  1275. var color = "";
  1276. if (t == "string") {
  1277. color = "green";
  1278. } else if (t == "number") {
  1279. color = "red";
  1280. } else if (t == "boolean") {
  1281. color = "blue";
  1282. } else if (this.type == "object" || this.type == "array") {
  1283. // note: typeof(null)=="object", therefore check this.type instead of t
  1284. color = "";
  1285. } else if (v === null) {
  1286. color = "purple";
  1287. } else if (v === undefined) {
  1288. // invalid value
  1289. color = "green";
  1290. }
  1291. domValue.style.color = color;
  1292. // make backgound color lightgray when empty
  1293. var isEmpty =
  1294. String(this.value) == "" && this.type != "array" && this.type != "object";
  1295. if (isEmpty) {
  1296. JSONEditor.addClassName(domValue, "jsoneditor-empty");
  1297. } else {
  1298. JSONEditor.removeClassName(domValue, "jsoneditor-empty");
  1299. }
  1300. // highlight when there is a search result
  1301. if (this.searchValueActive) {
  1302. JSONEditor.addClassName(domValue, "jsoneditor-search-highlight-active");
  1303. } else {
  1304. JSONEditor.removeClassName(
  1305. domValue,
  1306. "jsoneditor-search-highlight-active"
  1307. );
  1308. }
  1309. if (this.searchValue) {
  1310. JSONEditor.addClassName(domValue, "jsoneditor-search-highlight");
  1311. } else {
  1312. JSONEditor.removeClassName(domValue, "jsoneditor-search-highlight");
  1313. }
  1314. // strip formatting from the contents of the editable div
  1315. JSONEditor.stripFormatting(domValue);
  1316. }
  1317. };
  1318. /**
  1319. * Update dom field:
  1320. * - the text color of the field, depending on the text
  1321. * - the height of the field, depending on the width
  1322. * - background color in case it is empty
  1323. * @private
  1324. */
  1325. JSONEditor.Node.prototype._updateDomField = function() {
  1326. var domField = this.dom.field;
  1327. if (domField) {
  1328. // make backgound color lightgray when empty
  1329. var isEmpty = String(this.field) == "";
  1330. if (isEmpty) {
  1331. JSONEditor.addClassName(domField, "jsoneditor-empty");
  1332. } else {
  1333. JSONEditor.removeClassName(domField, "jsoneditor-empty");
  1334. }
  1335. // highlight when there is a search result
  1336. if (this.searchFieldActive) {
  1337. JSONEditor.addClassName(domField, "jsoneditor-search-highlight-active");
  1338. } else {
  1339. JSONEditor.removeClassName(
  1340. domField,
  1341. "jsoneditor-search-highlight-active"
  1342. );
  1343. }
  1344. if (this.searchField) {
  1345. JSONEditor.addClassName(domField, "jsoneditor-search-highlight");
  1346. } else {
  1347. JSONEditor.removeClassName(domField, "jsoneditor-search-highlight");
  1348. }
  1349. // strip formatting from the contents of the editable div
  1350. JSONEditor.stripFormatting(domField);
  1351. }
  1352. };
  1353. /**
  1354. * Retrieve field from DOM
  1355. * @param {boolean} silent. If true (default), no errors will be thrown in
  1356. * case of invalid data
  1357. * @private
  1358. */
  1359. JSONEditor.Node.prototype._getDomField = function(silent) {
  1360. if (this.dom.field && this.fieldEditable) {
  1361. this.fieldInnerText = JSONEditor.getInnerText(this.dom.field);
  1362. }
  1363. if (this.fieldInnerText != undefined) {
  1364. try {
  1365. var field = this._unescapeHTML(this.fieldInnerText);
  1366. if (field !== this.field) {
  1367. var oldField = this.field;
  1368. this.field = field;
  1369. this.editor.onAction("editField", {
  1370. node: this,
  1371. oldValue: oldField,
  1372. newValue: field
  1373. });
  1374. }
  1375. } catch (err) {
  1376. this.field = undefined;
  1377. // TODO: sent an action here, with the new, invalid value?
  1378. if (silent != true) {
  1379. throw err;
  1380. }
  1381. }
  1382. }
  1383. };
  1384. /**
  1385. * Clear the dom of the node
  1386. */
  1387. JSONEditor.Node.prototype.clearDom = function() {
  1388. // TODO: hide the node first?
  1389. //this.hide();
  1390. // TOOD: recursively clear dom?
  1391. this.dom = {};
  1392. };
  1393. /**
  1394. * Get the HTML DOM TR element of the node.
  1395. * The dom will be generated when not yet created
  1396. * @return {Element} tr HTML DOM TR Element
  1397. */
  1398. JSONEditor.Node.prototype.getDom = function() {
  1399. var dom = this.dom;
  1400. if (dom.tr) {
  1401. return dom.tr;
  1402. }
  1403. // create row
  1404. dom.tr = document.createElement("tr");
  1405. dom.tr.className = "jsoneditor-tr";
  1406. dom.tr.node = this;
  1407. if (this.editor.editable) {
  1408. // create draggable area
  1409. var tdDrag = document.createElement("td");
  1410. tdDrag.className = "jsoneditor-td";
  1411. dom.drag = this._createDomDragArea();
  1412. if (dom.drag) {
  1413. tdDrag.appendChild(dom.drag);
  1414. }
  1415. dom.tr.appendChild(tdDrag);
  1416. }
  1417. // create tree and field
  1418. var tdField = document.createElement("td");
  1419. tdField.className = "jsoneditor-td";
  1420. dom.tr.appendChild(tdField);
  1421. dom.expand = this._createDomExpandButton();
  1422. dom.field = this._createDomField();
  1423. dom.value = this._createDomValue();
  1424. dom.tree = this._createDomTree(dom.expand, dom.field, dom.value);
  1425. tdField.appendChild(dom.tree);
  1426. if (this.editor.editable) {
  1427. // create type select box
  1428. var tdType = document.createElement("td");
  1429. tdType.className = "jsoneditor-td jsoneditor-td-edit";
  1430. dom.tr.appendChild(tdType);
  1431. dom.type = this._createDomTypeButton();
  1432. tdType.appendChild(dom.type);
  1433. // create duplicate button
  1434. var tdDuplicate = document.createElement("td");
  1435. tdDuplicate.className = "jsoneditor-td jsoneditor-td-edit";
  1436. dom.tr.appendChild(tdDuplicate);
  1437. dom.duplicate = this._createDomDuplicateButton();
  1438. if (dom.duplicate) {
  1439. tdDuplicate.appendChild(dom.duplicate);
  1440. }
  1441. // create remove button
  1442. var tdRemove = document.createElement("td");
  1443. tdRemove.className = "jsoneditor-td jsoneditor-td-edit";
  1444. dom.tr.appendChild(tdRemove);
  1445. dom.remove = this._createDomRemoveButton();
  1446. if (dom.remove) {
  1447. tdRemove.appendChild(dom.remove);
  1448. }
  1449. }
  1450. this.updateDom(); // TODO: recurse here?
  1451. return dom.tr;
  1452. };
  1453. /**
  1454. * DragStart event, fired on mousedown on the dragarea at the left side of a Node
  1455. * @param {Event} event
  1456. * @private
  1457. */
  1458. JSONEditor.Node.prototype._onDragStart = function(event) {
  1459. event = event || window.event;
  1460. var node = this;
  1461. if (!this.mousemove) {
  1462. this.mousemove = JSONEditor.Events.addEventListener(
  1463. document,
  1464. "mousemove",
  1465. function(event) {
  1466. node._onDrag(event);
  1467. }
  1468. );
  1469. }
  1470. if (!this.mouseup) {
  1471. this.mouseup = JSONEditor.Events.addEventListener(
  1472. document,
  1473. "mouseup",
  1474. function(event) {
  1475. node._onDragEnd(event);
  1476. }
  1477. );
  1478. }
  1479. /* TODO: correct highlighting when the TypeDropDown is visible (And has highlighting locked)
  1480. if (JSONEditor.freezeHighlight) {
  1481. console.log('heee');
  1482. JSONEditor.freezeHighlight = false;
  1483. this.setHighlight(true);
  1484. }
  1485. */
  1486. JSONEditor.freezeHighlight = true;
  1487. this.drag = {
  1488. oldCursor: document.body.style.cursor,
  1489. startParent: this.parent,
  1490. startIndex: this.parent.childs.indexOf(this)
  1491. };
  1492. document.body.style.cursor = "move";
  1493. JSONEditor.Events.preventDefault(event);
  1494. };
  1495. /**
  1496. * Drag event, fired when moving the mouse while dragging a Node
  1497. * @param {Event} event
  1498. * @private
  1499. */
  1500. JSONEditor.Node.prototype._onDrag = function(event) {
  1501. event = event || window.event;
  1502. var trThis = this.dom.tr;
  1503. // TODO: add an ESC option, which resets to the original position
  1504. var topThis = JSONEditor.getAbsoluteTop(trThis);
  1505. var heightThis = trThis.offsetHeight;
  1506. var mouseY = event.pageY || event.clientY + document.body.scrollTop;
  1507. if (mouseY < topThis) {
  1508. // move up
  1509. var trPrev = trThis.previousSibling;
  1510. var topPrev = JSONEditor.getAbsoluteTop(trPrev);
  1511. var nodePrev = JSONEditor.getNodeFromTarget(trPrev);
  1512. while (trPrev && mouseY < topPrev) {
  1513. nodePrev = JSONEditor.getNodeFromTarget(trPrev);
  1514. trPrev = trPrev.previousSibling;
  1515. topPrev = JSONEditor.getAbsoluteTop(trPrev);
  1516. }
  1517. if (nodePrev) {
  1518. trPrev = nodePrev.dom.tr;
  1519. topPrev = JSONEditor.getAbsoluteTop(trPrev);
  1520. if (mouseY > topPrev + heightThis) {
  1521. nodePrev = undefined;
  1522. }
  1523. }
  1524. if (nodePrev && nodePrev.parent) {
  1525. nodePrev.parent.moveBefore(this, nodePrev);
  1526. }
  1527. } else {
  1528. // move down
  1529. var trLast =
  1530. this.expanded && this.append ? this.append.getDom() : this.dom.tr;
  1531. var trFirst = trLast ? trLast.nextSibling : undefined;
  1532. if (trFirst) {
  1533. var topFirst = JSONEditor.getAbsoluteTop(trFirst);
  1534. var nodeNext = undefined;
  1535. var trNext = trFirst.nextSibling;
  1536. var topNext = JSONEditor.getAbsoluteTop(trNext);
  1537. var heightNext = trNext ? topNext - topFirst : 0;
  1538. while (trNext && mouseY > topThis + heightNext) {
  1539. nodeNext = JSONEditor.getNodeFromTarget(trNext);
  1540. trNext = trNext.nextSibling;
  1541. topNext = JSONEditor.getAbsoluteTop(trNext);
  1542. heightNext = trNext ? topNext - topFirst : 0;
  1543. }
  1544. if (nodeNext && nodeNext.parent) {
  1545. nodeNext.parent.moveBefore(this, nodeNext);
  1546. }
  1547. }
  1548. }
  1549. JSONEditor.Events.preventDefault(event);
  1550. };
  1551. /**
  1552. * Drag event, fired on mouseup after having dragged a node
  1553. * @param {Event} event
  1554. * @private
  1555. */
  1556. JSONEditor.Node.prototype._onDragEnd = function(event) {
  1557. event = event || window.event;
  1558. var params = {
  1559. node: this,
  1560. startParent: this.drag.startParent,
  1561. startIndex: this.drag.startIndex,
  1562. endParent: this.parent,
  1563. endIndex: this.parent.childs.indexOf(this)
  1564. };
  1565. if (
  1566. params.startParent != params.endParent ||
  1567. params.startIndex != params.endIndex
  1568. ) {
  1569. // only register this action if the node is actually moved to another place
  1570. this.editor.onAction("moveNode", params);
  1571. }
  1572. document.body.style.cursor = this.drag.oldCursor;
  1573. delete JSONEditor.freezeHighlight;
  1574. delete this.drag;
  1575. this.setHighlight(false);
  1576. if (this.mousemove) {
  1577. JSONEditor.Events.removeEventListener(
  1578. document,
  1579. "mousemove",
  1580. this.mousemove
  1581. );
  1582. delete this.mousemove;
  1583. }
  1584. if (this.mouseup) {
  1585. JSONEditor.Events.removeEventListener(document, "mouseup", this.mouseup);
  1586. delete this.mouseup;
  1587. }
  1588. JSONEditor.Events.preventDefault(event);
  1589. };
  1590. /**
  1591. * Create a drag area, displayed at the left side of the node
  1592. * @return {Element | undefined} domDrag
  1593. * @private
  1594. */
  1595. JSONEditor.Node.prototype._createDomDragArea = function() {
  1596. if (!this.parent) {
  1597. return undefined;
  1598. }
  1599. var domDrag = document.createElement("button");
  1600. domDrag.className = "jsoneditor-dragarea";
  1601. domDrag.title = "Move field (drag and drop)";
  1602. return domDrag;
  1603. };
  1604. /**
  1605. * Create an editable field
  1606. * @return {Element} domField
  1607. * @private
  1608. */
  1609. JSONEditor.Node.prototype._createDomField = function() {
  1610. return document.createElement("div");
  1611. };
  1612. /**
  1613. * Set highlighting for this node and all its childs.
  1614. * Only applied to the currently visible (expanded childs)
  1615. * @param {boolean} highlight
  1616. */
  1617. JSONEditor.Node.prototype.setHighlight = function(highlight) {
  1618. if (JSONEditor.freezeHighlight) {
  1619. return;
  1620. }
  1621. if (this.dom.tr) {
  1622. this.dom.tr.className =
  1623. "jsoneditor-tr" + (highlight ? " jsoneditor-tr-highlight" : "");
  1624. if (this.append) {
  1625. this.append.setHighlight(highlight);
  1626. }
  1627. if (this.childs) {
  1628. this.childs.forEach(function(child) {
  1629. child.setHighlight(highlight);
  1630. });
  1631. }
  1632. }
  1633. };
  1634. /**
  1635. * Update the value of the node. Only primitive types are allowed, no Object
  1636. * or Array is allowed.
  1637. * @param {String | Number | Boolean | null} value
  1638. */
  1639. JSONEditor.Node.prototype.updateValue = function(value) {
  1640. this.value = value;
  1641. this.updateDom();
  1642. };
  1643. /**
  1644. * Update the field of the node.
  1645. * @param {String} field
  1646. */
  1647. JSONEditor.Node.prototype.updateField = function(field) {
  1648. this.field = field;
  1649. this.updateDom();
  1650. };
  1651. /**
  1652. * Update the HTML DOM, optionally recursing through the childs
  1653. * @param {Object} [options] Available parameters:
  1654. * {boolean} [recurse] If true, the
  1655. * DOM of the childs will be updated recursively.
  1656. * False by default.
  1657. * {boolean} [updateIndexes] If true, the childs
  1658. * indexes of the node will be updated too. False by
  1659. * default.
  1660. */
  1661. JSONEditor.Node.prototype.updateDom = function(options) {
  1662. // update level indentation
  1663. var domTree = this.dom.tree;
  1664. if (domTree) {
  1665. domTree.style.marginLeft = this.getLevel() * 24 + "px";
  1666. }
  1667. // update field
  1668. var domField = this.dom.field;
  1669. if (domField) {
  1670. if (this.fieldEditable == true) {
  1671. // parent is an object
  1672. domField.contentEditable = this.editor.editable;
  1673. domField.spellcheck = false;
  1674. domField.className = "jsoneditor-field";
  1675. } else {
  1676. // parent is an array this is the root node
  1677. domField.className = "jsoneditor-readonly";
  1678. }
  1679. var field;
  1680. if (this.index != undefined) {
  1681. field = this.index;
  1682. } else if (this.field != undefined) {
  1683. field = this.field;
  1684. } else if (this.type == "array" || this.type == "object") {
  1685. field = this.type;
  1686. } else {
  1687. field = "field";
  1688. }
  1689. domField.innerHTML = this._escapeHTML(field);
  1690. }
  1691. // update value
  1692. var domValue = this.dom.value;
  1693. if (domValue) {
  1694. var count = this.childs ? this.childs.length : 0;
  1695. if (this.type == "array") {
  1696. domValue.innerHTML = "[" + count + "]";
  1697. domValue.title = this.type + " containing " + count + " items";
  1698. } else if (this.type == "object") {
  1699. domValue.innerHTML = "{" + count + "}";
  1700. domValue.title = this.type + " containing " + count + " items";
  1701. } else {
  1702. domValue.innerHTML = this._escapeHTML(this.value);
  1703. delete domValue.title;
  1704. }
  1705. }
  1706. // update field and value
  1707. this._updateDomField();
  1708. this._updateDomValue();
  1709. // update childs indexes
  1710. if (options && options.updateIndexes == true) {
  1711. // updateIndexes is true or undefined
  1712. this._updateDomIndexes();
  1713. }
  1714. if (options && options.recurse == true) {
  1715. // recurse is true or undefined. update childs recursively
  1716. if (this.childs) {
  1717. this.childs.forEach(function(child) {
  1718. child.updateDom(options);
  1719. });
  1720. }
  1721. // update row with append button
  1722. if (this.append) {
  1723. this.append.updateDom();
  1724. }
  1725. }
  1726. };
  1727. /**
  1728. * Update the DOM of the childs of a node: update indexes and undefined field
  1729. * names.
  1730. * Only applicable when structure is an array or object
  1731. * @private
  1732. */
  1733. JSONEditor.Node.prototype._updateDomIndexes = function() {
  1734. var domValue = this.dom.value;
  1735. var childs = this.childs;
  1736. if (domValue && childs) {
  1737. if (this.type == "array") {
  1738. childs.forEach(function(child, index) {
  1739. child.index = index;
  1740. var childField = child.dom.field;
  1741. if (childField) {
  1742. childField.innerHTML = index;
  1743. }
  1744. });
  1745. } else if (this.type == "object") {
  1746. childs.forEach(function(child) {
  1747. if (child.index != undefined) {
  1748. delete child.index;
  1749. if (child.field == undefined) {
  1750. child.field = "field";
  1751. }
  1752. }
  1753. });
  1754. }
  1755. }
  1756. };
  1757. /**
  1758. * Create an editable value
  1759. * @private
  1760. */
  1761. JSONEditor.Node.prototype._createDomValue = function() {
  1762. var domValue;
  1763. if (this.type == "array") {
  1764. domValue = document.createElement("div");
  1765. domValue.className = "jsoneditor-readonly";
  1766. domValue.innerHTML = "[...]";
  1767. } else if (this.type == "object") {
  1768. domValue = document.createElement("div");
  1769. domValue.className = "jsoneditor-readonly";
  1770. domValue.innerHTML = "{...}";
  1771. } else if (this.type == "string") {
  1772. domValue = document.createElement("div");
  1773. domValue.contentEditable = this.editor.editable;
  1774. domValue.spellcheck = false;
  1775. domValue.className = "jsoneditor-value";
  1776. domValue.innerHTML = this._escapeHTML(this.value);
  1777. } else {
  1778. domValue = document.createElement("div");
  1779. domValue.contentEditable = this.editor.editable;
  1780. domValue.spellcheck = false;
  1781. domValue.className = "jsoneditor-value";
  1782. domValue.innerHTML = this._escapeHTML(this.value);
  1783. }
  1784. // TODO: in FF spel/check of editable divs is done via the body. quite ugly
  1785. // document.body.spellcheck = false;
  1786. return domValue;
  1787. };
  1788. /**
  1789. * Create an expand/collapse button
  1790. * @return {Element} expand
  1791. * @private
  1792. */
  1793. JSONEditor.Node.prototype._createDomExpandButton = function() {
  1794. // create expand button
  1795. var expand = document.createElement("button");
  1796. var expandable = this.type == "array" || this.type == "object";
  1797. if (expandable) {
  1798. expand.className = this.expanded
  1799. ? "jsoneditor-expanded"
  1800. : "jsoneditor-collapsed";
  1801. expand.title =
  1802. "Click to expand/collapse this field. \n" +
  1803. "Ctrl+Click to expand/collapse including all childs.";
  1804. } else {
  1805. expand.className = "jsoneditor-invisible";
  1806. expand.title = "";
  1807. }
  1808. return expand;
  1809. };
  1810. /**
  1811. * Create a DOM tree element, containing the expand/collapse button
  1812. * @param {Element} domExpand
  1813. * @param {Element} domField
  1814. * @param {Element} domValue
  1815. * @return {Element} domTree
  1816. * @private
  1817. */
  1818. JSONEditor.Node.prototype._createDomTree = function(
  1819. domExpand,
  1820. domField,
  1821. domValue
  1822. ) {
  1823. var dom = this.dom;
  1824. var domTree = document.createElement("table");
  1825. var tbody = document.createElement("tbody");
  1826. domTree.style.borderCollapse = "collapse"; // TODO: put in css
  1827. domTree.appendChild(tbody);
  1828. var tr = document.createElement("tr");
  1829. tbody.appendChild(tr);
  1830. // create expand button
  1831. var tdExpand = document.createElement("td");
  1832. tdExpand.className = "jsoneditor-td-tree";
  1833. tr.appendChild(tdExpand);
  1834. tdExpand.appendChild(domExpand);
  1835. dom.tdExpand = tdExpand;
  1836. // add the field
  1837. var tdField = document.createElement("td");
  1838. tdField.className = "jsoneditor-td-tree";
  1839. tr.appendChild(tdField);
  1840. tdField.appendChild(domField);
  1841. dom.tdField = tdField;
  1842. // add a separator
  1843. var tdSeparator = document.createElement("td");
  1844. tdSeparator.className = "jsoneditor-td-tree";
  1845. tr.appendChild(tdSeparator);
  1846. if (this.type != "object" && this.type != "array") {
  1847. tdSeparator.appendChild(document.createTextNode(":"));
  1848. tdSeparator.className = "jsoneditor-separator";
  1849. }
  1850. dom.tdSeparator = tdSeparator;
  1851. // add the value
  1852. var tdValue = document.createElement("td");
  1853. tdValue.className = "jsoneditor-td-tree";
  1854. tr.appendChild(tdValue);
  1855. tdValue.appendChild(domValue);
  1856. dom.tdValue = tdValue;
  1857. return domTree;
  1858. };
  1859. /**
  1860. * Handle an event. The event is catched centrally by the editor
  1861. * @param {Event} event
  1862. */
  1863. JSONEditor.Node.prototype.onEvent = function(event) {
  1864. var type = event.type;
  1865. var target = event.target || event.srcElement;
  1866. var dom = this.dom;
  1867. var node = this;
  1868. var expandable = this.type == "array" || this.type == "object";
  1869. // value events
  1870. var domValue = dom.value;
  1871. if (target == domValue) {
  1872. switch (type) {
  1873. case "focus":
  1874. JSONEditor.focusNode = this;
  1875. break;
  1876. case "blur":
  1877. case "change":
  1878. this._getDomValue(true);
  1879. this._updateDomValue();
  1880. if (this.value) {
  1881. domValue.innerHTML = this._escapeHTML(this.value);
  1882. }
  1883. break;
  1884. case "keyup":
  1885. this._getDomValue(true);
  1886. this._updateDomValue();
  1887. break;
  1888. case "cut":
  1889. case "paste":
  1890. setTimeout(function() {
  1891. node._getDomValue(true);
  1892. node._updateDomValue();
  1893. }, 1);
  1894. break;
  1895. }
  1896. }
  1897. // field events
  1898. var domField = dom.field;
  1899. if (target == domField) {
  1900. switch (type) {
  1901. case "focus":
  1902. JSONEditor.focusNode = this;
  1903. break;
  1904. case "change":
  1905. case "blur":
  1906. this._getDomField(true);
  1907. this._updateDomField();
  1908. if (this.field) {
  1909. domField.innerHTML = this._escapeHTML(this.field);
  1910. }
  1911. break;
  1912. case "keyup":
  1913. this._getDomField(true);
  1914. this._updateDomField();
  1915. break;
  1916. case "cut":
  1917. case "paste":
  1918. setTimeout(function() {
  1919. node._getDomField(true);
  1920. node._updateDomField();
  1921. }, 1);
  1922. break;
  1923. }
  1924. }
  1925. // drag events
  1926. var domDrag = dom.drag;
  1927. if (target == domDrag) {
  1928. switch (type) {
  1929. case "mousedown":
  1930. this._onDragStart(event);
  1931. break;
  1932. case "mouseover":
  1933. this.setHighlight(true);
  1934. break;
  1935. case "mouseout":
  1936. this.setHighlight(false);
  1937. break;
  1938. }
  1939. }
  1940. // expand events
  1941. var domExpand = dom.expand;
  1942. if (target == domExpand) {
  1943. if (type == "click") {
  1944. if (expandable) {
  1945. this._onExpand(event);
  1946. }
  1947. }
  1948. }
  1949. // duplicate button
  1950. var domDuplicate = dom.duplicate;
  1951. if (target == domDuplicate) {
  1952. switch (type) {
  1953. case "click":
  1954. var clone = this.parent._duplicate(this);
  1955. this.editor.onAction("duplicateNode", {
  1956. node: this,
  1957. clone: clone,
  1958. parent: this.parent
  1959. });
  1960. break;
  1961. case "mouseover":
  1962. this.setHighlight(true);
  1963. break;
  1964. case "mouseout":
  1965. this.setHighlight(false);
  1966. break;
  1967. }
  1968. }
  1969. // remove button
  1970. var domRemove = dom.remove;
  1971. if (target == domRemove) {
  1972. switch (type) {
  1973. case "click":
  1974. this._onRemove();
  1975. break;
  1976. case "mouseover":
  1977. this.setHighlight(true);
  1978. break;
  1979. case "mouseout":
  1980. this.setHighlight(false);
  1981. break;
  1982. }
  1983. }
  1984. // type button
  1985. var domType = dom.type;
  1986. if (target == domType) {
  1987. switch (type) {
  1988. case "click":
  1989. this._onChangeType(event);
  1990. break;
  1991. case "mouseover":
  1992. this.setHighlight(true);
  1993. break;
  1994. case "mouseout":
  1995. this.setHighlight(false);
  1996. break;
  1997. }
  1998. }
  1999. // focus
  2000. // when clicked in whitespace left or right from the field or value, set focus
  2001. var domTree = dom.tree;
  2002. if (target == domTree.parentNode) {
  2003. switch (type) {
  2004. case "click":
  2005. var left =
  2006. event.offsetX != undefined
  2007. ? event.offsetX < (this.getLevel() + 1) * 24
  2008. : event.clientX < JSONEditor.getAbsoluteLeft(dom.tdSeparator); // for FF
  2009. if (left || expandable) {
  2010. // node is expandable when it is an object or array
  2011. if (domField) {
  2012. JSONEditor.setEndOfContentEditable(domField);
  2013. domField.focus();
  2014. }
  2015. } else {
  2016. if (domValue) {
  2017. JSONEditor.setEndOfContentEditable(domValue);
  2018. domValue.focus();
  2019. }
  2020. }
  2021. break;
  2022. }
  2023. }
  2024. if (
  2025. (target == dom.tdExpand && !expandable) ||
  2026. target == dom.tdField ||
  2027. target == dom.tdSeparator
  2028. ) {
  2029. switch (type) {
  2030. case "click":
  2031. if (domField) {
  2032. JSONEditor.setEndOfContentEditable(domField);
  2033. domField.focus();
  2034. }
  2035. break;
  2036. }
  2037. }
  2038. };
  2039. /**
  2040. * Handle the expand event, when clicked on the expand button
  2041. * @param {Event} event
  2042. * @private
  2043. */
  2044. JSONEditor.Node.prototype._onExpand = function(event) {
  2045. event = event || window.event;
  2046. var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all
  2047. if (recurse) {
  2048. // Take the table offline
  2049. var table = this.dom.tr.parentNode; // TODO: not nice to access the main table like this
  2050. var frame = table.parentNode;
  2051. var scrollTop = frame.scrollTop;
  2052. frame.removeChild(table);
  2053. }
  2054. if (this.expanded) {
  2055. this.collapse(recurse);
  2056. } else {
  2057. this.expand(recurse);
  2058. }
  2059. if (recurse) {
  2060. // Put the table online again
  2061. frame.appendChild(table);
  2062. frame.scrollTop = scrollTop;
  2063. }
  2064. };
  2065. JSONEditor.Node.types = [
  2066. {
  2067. value: "array",
  2068. className: "jsoneditor-option-array",
  2069. title: '"array" 类型: 包含了有序值集合的数组.'
  2070. },
  2071. {
  2072. value: "auto",
  2073. className: "jsoneditor-option-auto",
  2074. title:
  2075. '"auto" 类型: 节点类型将自动从值中获取, 可以是: string, number, boolean, 或 null.'
  2076. },
  2077. {
  2078. value: "object",
  2079. className: "jsoneditor-option-object",
  2080. title: '"object" 类型: 对象包含了一些无序的键/值对.'
  2081. },
  2082. {
  2083. value: "string",
  2084. className: "jsoneditor-option-string",
  2085. title: '"string" 类型: 节点类型不从值中自动获取, 但永远返回string.'
  2086. }
  2087. ];
  2088. /**
  2089. * Create a DOM select box containing the node type
  2090. * @return {Element} domType
  2091. * @private
  2092. */
  2093. JSONEditor.Node.prototype._createDomTypeButton = function() {
  2094. var node = this;
  2095. var domType = document.createElement("button");
  2096. domType.className = "jsoneditor-type-" + node.type;
  2097. domType.title = "改变节点类型";
  2098. return domType;
  2099. };
  2100. /**
  2101. * Remove this node
  2102. * @private
  2103. */
  2104. JSONEditor.Node.prototype._onRemove = function() {
  2105. this.setHighlight(false);
  2106. var index = this.parent.childs.indexOf(this);
  2107. this.parent._remove(this);
  2108. this.editor.onAction("removeNode", {
  2109. node: this,
  2110. parent: this.parent,
  2111. index: index
  2112. });
  2113. };
  2114. /**
  2115. * Handle a click on the Type-button
  2116. * @param {Event} event
  2117. * @private
  2118. */
  2119. JSONEditor.Node.prototype._onChangeType = function(event) {
  2120. JSONEditor.Events.stopPropagation(event);
  2121. var domType = this.dom.type;
  2122. var node = this;
  2123. var x = JSONEditor.getAbsoluteLeft(domType);
  2124. var y = JSONEditor.getAbsoluteTop(domType) + domType.clientHeight;
  2125. var callback = function(newType) {
  2126. var oldType = node.type;
  2127. node.changeType(newType);
  2128. node.editor.onAction("changeType", {
  2129. node: node,
  2130. oldType: oldType,
  2131. newType: newType
  2132. });
  2133. domType.className = "jsoneditor-type-" + node.type;
  2134. };
  2135. JSONEditor.showDropDownList({
  2136. x: x,
  2137. y: y,
  2138. node: node,
  2139. value: node.type,
  2140. values: JSONEditor.Node.types,
  2141. className: "jsoneditor-select",
  2142. optionSelectedClassName: "jsoneditor-option-selected",
  2143. optionClassName: "jsoneditor-option",
  2144. callback: callback
  2145. });
  2146. };
  2147. /**
  2148. * Show a dropdown list
  2149. * @param {Object} params Available parameters:
  2150. * {Number} x The absolute horizontal position
  2151. * {Number} y The absolute vertical position
  2152. * {JSONEditor.Node} node node used for highlighting
  2153. * {String} value current selected value
  2154. * {Object[]} values the available values. Each object
  2155. * contains a value, title, and
  2156. * className
  2157. * {String} optionSelectedClassName
  2158. * {String} optionClassName
  2159. * {function} callback Callback method, called when
  2160. * the selected value changed.
  2161. */
  2162. JSONEditor.showDropDownList = function(params) {
  2163. var select = document.createElement("div");
  2164. select.className = params.className || "";
  2165. select.style.position = "absolute";
  2166. select.style.left = (params.x || 0) + "px";
  2167. select.style.top = (params.y || 0) + "px";
  2168. params.values.forEach(function(v) {
  2169. var text = v.value || String(v);
  2170. var className = "jsoneditor-option";
  2171. var selected = text == params.value;
  2172. if (selected) {
  2173. className += " " + params.optionSelectedClassName;
  2174. }
  2175. var option = document.createElement("div");
  2176. option.className = className;
  2177. if (v.title) {
  2178. option.title = v.title;
  2179. }
  2180. var divIcon = document.createElement("div");
  2181. divIcon.className = v.className || "";
  2182. option.appendChild(divIcon);
  2183. var divText = document.createElement("div");
  2184. divText.className = "jsoneditor-option-text";
  2185. divText.innerHTML = "<div>" + text + "</div>";
  2186. option.appendChild(divText);
  2187. option.onmousedown = (function(value) {
  2188. return function() {
  2189. params.callback(value);
  2190. };
  2191. })(v.value);
  2192. select.appendChild(option);
  2193. });
  2194. document.body.appendChild(select);
  2195. params.node.setHighlight(true);
  2196. JSONEditor.freezeHighlight = true;
  2197. // TODO: change to onclick? -> but be sure to remove existing dropdown first
  2198. var onmousedown = JSONEditor.Events.addEventListener(
  2199. document,
  2200. "mousedown",
  2201. function() {
  2202. JSONEditor.freezeHighlight = false;
  2203. params.node.setHighlight(false);
  2204. if (select && select.parentNode) {
  2205. select.parentNode.removeChild(select);
  2206. }
  2207. JSONEditor.Events.removeEventListener(document, "mousedown", onmousedown);
  2208. }
  2209. );
  2210. var onmousewheel = JSONEditor.Events.addEventListener(
  2211. document,
  2212. "mousewheel",
  2213. function() {
  2214. JSONEditor.freezeHighlight = false;
  2215. params.node.setHighlight(false);
  2216. if (select && select.parentNode) {
  2217. select.parentNode.removeChild(select);
  2218. }
  2219. JSONEditor.Events.removeEventListener(
  2220. document,
  2221. "mousewheel",
  2222. onmousewheel
  2223. );
  2224. }
  2225. );
  2226. };
  2227. /**
  2228. * Create a table row with an append button.
  2229. * @return {Element | undefined} buttonAppend or undefined when inapplicable
  2230. */
  2231. JSONEditor.Node.prototype.getAppend = function() {
  2232. if (!this.append) {
  2233. this.append = new JSONEditor.AppendNode(this.editor);
  2234. this.append.setParent(this);
  2235. }
  2236. return this.append.getDom();
  2237. };
  2238. /**
  2239. * Create a remove button. Returns undefined when the structure cannot
  2240. * be removed
  2241. * @return {Element | undefined} removeButton, or undefined when inapplicable
  2242. * @private
  2243. */
  2244. JSONEditor.Node.prototype._createDomRemoveButton = function() {
  2245. if (
  2246. this.parent &&
  2247. (this.parent.type == "array" || this.parent.type == "object")
  2248. ) {
  2249. var buttonRemove = document.createElement("button");
  2250. buttonRemove.className = "jsoneditor-remove";
  2251. buttonRemove.title = "删除节点 (包括所有子节点)";
  2252. return buttonRemove;
  2253. } else {
  2254. return undefined;
  2255. }
  2256. };
  2257. /**
  2258. * Create a duplicate button.
  2259. * If the Node is the root node, no duplicate button is available and undefined
  2260. * will be returned
  2261. * @return {Element | undefined} buttonDuplicate
  2262. * @private
  2263. */
  2264. JSONEditor.Node.prototype._createDomDuplicateButton = function() {
  2265. if (
  2266. this.parent &&
  2267. (this.parent.type == "array" || this.parent.type == "object")
  2268. ) {
  2269. var buttonDupliate = document.createElement("button");
  2270. buttonDupliate.className = "jsoneditor-duplicate";
  2271. buttonDupliate.title = "复制节点 (包括所有子节点)";
  2272. return buttonDupliate;
  2273. } else {
  2274. return undefined;
  2275. }
  2276. };
  2277. /**
  2278. * get the type of a value
  2279. * @param {*} value
  2280. * @return {String} type Can be 'object', 'array', 'string', 'auto'
  2281. * @private
  2282. */
  2283. JSONEditor.Node.prototype._getType = function(value) {
  2284. if (value instanceof Array) {
  2285. return "array";
  2286. }
  2287. if (value instanceof Object) {
  2288. return "object";
  2289. }
  2290. if (typeof value == "string" && typeof this._stringCast(value) != "string") {
  2291. return "string";
  2292. }
  2293. return "auto";
  2294. };
  2295. /**
  2296. * cast contents of a string to the correct type. This can be a string,
  2297. * a number, a boolean, etc
  2298. * @param {String} str
  2299. * @return {*} castedStr
  2300. * @private
  2301. */
  2302. JSONEditor.Node.prototype._stringCast = function(str) {
  2303. var lower = str.toLowerCase(),
  2304. num = Number(str), // will nicely fail with '123ab'
  2305. numFloat = parseFloat(str); // will nicely fail with ' '
  2306. if (str == "") {
  2307. return "";
  2308. } else if (lower == "null") {
  2309. return null;
  2310. } else if (lower == "true") {
  2311. return true;
  2312. } else if (lower == "false") {
  2313. return false;
  2314. } else if (!isNaN(num) && !isNaN(numFloat)) {
  2315. return num;
  2316. } else {
  2317. return str;
  2318. }
  2319. };
  2320. /**
  2321. * escape a text, such that it can be displayed safely in an HTML element
  2322. * @param {String} text
  2323. * @return {String} escapedText
  2324. * @private
  2325. */
  2326. JSONEditor.Node.prototype._escapeHTML = function(text) {
  2327. var htmlEscaped = String(text)
  2328. .replace(/</g, "&lt;")
  2329. .replace(/>/g, "&gt;")
  2330. .replace(/ /g, " &nbsp;") // replace double space with an nbsp and space
  2331. .replace(/^ /, "&nbsp;") // space at start
  2332. .replace(/ $/, "&nbsp;"); // space at end
  2333. var json = JSON.stringify(htmlEscaped);
  2334. return json.substring(1, json.length - 1);
  2335. };
  2336. /**
  2337. * unescape a string.
  2338. * @param {String} escapedText
  2339. * @return {String} text
  2340. * @private
  2341. */
  2342. JSONEditor.Node.prototype._unescapeHTML = function(escapedText) {
  2343. var json = '"' + this._escapeJSON(escapedText) + '"';
  2344. var htmlEscaped = JSONEditor.parse(json);
  2345. return htmlEscaped
  2346. .replace(/&lt;/g, "<")
  2347. .replace(/&gt;/g, ">")
  2348. .replace(/&nbsp;/g, " ");
  2349. };
  2350. /**
  2351. * escape a text to make it a valid JSON string. The method will:
  2352. * - replace unescaped double quotes with '\"'
  2353. * - replace unescaped backslash with '\\'
  2354. * - replace returns with '\n'
  2355. * @param {String} text
  2356. * @return {String} escapedText
  2357. * @private
  2358. */
  2359. JSONEditor.Node.prototype._escapeJSON = function(text) {
  2360. // TODO: replace with some smart regex (only when a new solution is faster!)
  2361. var escaped = "";
  2362. var i = 0,
  2363. iMax = text.length;
  2364. while (i < iMax) {
  2365. var c = text.charAt(i);
  2366. if (c == "\n") {
  2367. escaped += "\\n";
  2368. } else if (c == "\\") {
  2369. escaped += c;
  2370. i++;
  2371. c = text.charAt(i);
  2372. if ('"\\/bfnrtu'.indexOf(c) == -1) {
  2373. escaped += "\\"; // no valid escape character
  2374. }
  2375. escaped += c;
  2376. } else if (c == '"') {
  2377. escaped += '\\"';
  2378. } else {
  2379. escaped += c;
  2380. }
  2381. i++;
  2382. }
  2383. return escaped;
  2384. };
  2385. /**
  2386. * @constructor JSONEditor.AppendNode
  2387. * @extends JSONEditor.Node
  2388. * @param {JSONEditor} editor
  2389. * Create a new AppendNode. This is a special node which is created at the
  2390. * end of the list with childs for an object or array
  2391. */
  2392. JSONEditor.AppendNode = function(editor) {
  2393. this.editor = editor;
  2394. this.dom = {};
  2395. };
  2396. JSONEditor.AppendNode.prototype = new JSONEditor.Node();
  2397. /**
  2398. * Return a table row with an append button.
  2399. * @return {Element} dom TR element
  2400. */
  2401. JSONEditor.AppendNode.prototype.getDom = function() {
  2402. if (this.dom.tr) {
  2403. return this.dom.tr;
  2404. }
  2405. /**
  2406. * Create a TD element, and give it the provided class name (if any)
  2407. * @param {String} [className]
  2408. * @return {Element} td
  2409. */
  2410. function newTd(className) {
  2411. var td = document.createElement("td");
  2412. td.className = className || "";
  2413. return td;
  2414. }
  2415. // a row for the append button
  2416. var trAppend = document.createElement("tr");
  2417. trAppend.node = this;
  2418. // TODO: do not create an appendNode at all when in viewer mode
  2419. if (!this.editor.editable) {
  2420. return trAppend;
  2421. }
  2422. // a cell for the drag area column
  2423. trAppend.appendChild(newTd("jsoneditor-td"));
  2424. // a cell for the append button
  2425. var tdAppend = document.createElement("td");
  2426. trAppend.appendChild(tdAppend);
  2427. tdAppend.className = "jsoneditor-td";
  2428. // create the append button
  2429. var buttonAppend = document.createElement("button");
  2430. buttonAppend.className = "jsoneditor-append";
  2431. buttonAppend.title = "添加";
  2432. this.dom.append = buttonAppend;
  2433. tdAppend.appendChild(buttonAppend);
  2434. trAppend.appendChild(newTd("jsoneditor-td jsoneditor-td-edit"));
  2435. trAppend.appendChild(newTd("jsoneditor-td jsoneditor-td-edit"));
  2436. trAppend.appendChild(newTd("jsoneditor-td jsoneditor-td-edit"));
  2437. this.dom.tr = trAppend;
  2438. this.dom.td = tdAppend;
  2439. this.updateDom();
  2440. return trAppend;
  2441. };
  2442. /**
  2443. * Update the HTML dom of the Node
  2444. */
  2445. JSONEditor.AppendNode.prototype.updateDom = function() {
  2446. var tdAppend = this.dom.td;
  2447. if (tdAppend) {
  2448. tdAppend.style.paddingLeft = this.getLevel() * 24 + 26 + "px";
  2449. // TODO: not so nice hard coded offset
  2450. }
  2451. };
  2452. /**
  2453. * Handle an event. The event is catched centrally by the editor
  2454. * @param {Event} event
  2455. */
  2456. JSONEditor.AppendNode.prototype.onEvent = function(event) {
  2457. var type = event.type;
  2458. var target = event.target || event.srcElement;
  2459. var dom = this.dom;
  2460. var domAppend = dom.append;
  2461. if (target == domAppend) {
  2462. switch (type) {
  2463. case "click":
  2464. this._onAppend();
  2465. break;
  2466. case "mouseover":
  2467. this.parent.setHighlight(true);
  2468. break;
  2469. case "mouseout":
  2470. this.parent.setHighlight(false);
  2471. }
  2472. }
  2473. };
  2474. /**
  2475. * Handle append event
  2476. * @private
  2477. */
  2478. JSONEditor.AppendNode.prototype._onAppend = function() {
  2479. var newNode = new JSONEditor.Node(this.editor, {
  2480. field: "field",
  2481. value: "value"
  2482. });
  2483. this.parent.appendChild(newNode);
  2484. this.parent.setHighlight(false);
  2485. newNode.focus();
  2486. this.editor.onAction("appendNode", {
  2487. node: newNode,
  2488. parent: this.parent
  2489. });
  2490. };
  2491. /**
  2492. * Create main frame
  2493. * @private
  2494. */
  2495. JSONEditor.prototype._createFrame = function() {
  2496. // create the frame
  2497. this.container.innerHTML = "";
  2498. this.frame = document.createElement("div");
  2499. this.frame.className = "jsoneditor-frame";
  2500. this.container.appendChild(this.frame);
  2501. // create one global event listener to handle all events from all nodes
  2502. var editor = this;
  2503. // TODO: move this onEvent to JSONEditor.prototype.onEvent
  2504. var onEvent = function(event) {
  2505. event = event || window.event;
  2506. var target = event.target || event.srcElement;
  2507. /* TODO: Enable quickkeys Ctrl+F and F3.
  2508. // Requires knowing whether the JSONEditor has focus or not
  2509. // (use a global event listener for that?)
  2510. // Check for search quickkeys, Ctrl+F and F3
  2511. if (editor.options.search) {
  2512. if (event.type == 'keydown') {
  2513. var keynum = event.which || event.keyCode;
  2514. if (keynum == 70 && event.ctrlKey) { // Ctrl+F
  2515. if (editor.searchBox) {
  2516. editor.searchBox.dom.search.focus();
  2517. editor.searchBox.dom.search.select();
  2518. JSONEditor.Events.preventDefault(event);
  2519. JSONEditor.Events.stopPropagation(event);
  2520. }
  2521. }
  2522. else if (keynum == 114) { // F3
  2523. if (!event.shiftKey) {
  2524. // select next search result
  2525. editor.searchBox.next();
  2526. }
  2527. else {
  2528. // select previous search result
  2529. editor.searchBox.previous();
  2530. }
  2531. editor.searchBox.focusActiveResult();
  2532. // set selection to the current
  2533. JSONEditor.Events.preventDefault(event);
  2534. JSONEditor.Events.stopPropagation(event);
  2535. }
  2536. }
  2537. }
  2538. */
  2539. var node = JSONEditor.getNodeFromTarget(target);
  2540. if (node) {
  2541. node.onEvent(event);
  2542. }
  2543. };
  2544. this.frame.onclick = function(event) {
  2545. onEvent(event);
  2546. // prevent default submit action when JSONEditor is located inside a form
  2547. JSONEditor.Events.preventDefault(event);
  2548. };
  2549. this.frame.onchange = onEvent;
  2550. this.frame.onkeydown = onEvent;
  2551. this.frame.onkeyup = onEvent;
  2552. this.frame.oncut = onEvent;
  2553. this.frame.onpaste = onEvent;
  2554. this.frame.onmousedown = onEvent;
  2555. this.frame.onmouseup = onEvent;
  2556. this.frame.onmouseover = onEvent;
  2557. this.frame.onmouseout = onEvent;
  2558. // Note: focus and blur events do not propagate, therefore they defined
  2559. // using an eventListener with useCapture=true
  2560. // see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
  2561. JSONEditor.Events.addEventListener(this.frame, "focus", onEvent, true);
  2562. JSONEditor.Events.addEventListener(this.frame, "blur", onEvent, true);
  2563. this.frame.onfocusin = onEvent; // for IE
  2564. this.frame.onfocusout = onEvent; // for IE
  2565. // create menu
  2566. this.menu = document.createElement("div");
  2567. this.menu.className = "jsoneditor-menu";
  2568. this.frame.appendChild(this.menu);
  2569. // create expand all button
  2570. var expandAll = document.createElement("button");
  2571. expandAll.className = "jsoneditor-menu jsoneditor-expand-all";
  2572. expandAll.title = "展开";
  2573. expandAll.onclick = function() {
  2574. editor.expandAll();
  2575. };
  2576. this.menu.appendChild(expandAll);
  2577. // create expand all button
  2578. var collapseAll = document.createElement("button");
  2579. collapseAll.title = "折叠";
  2580. collapseAll.className = "jsoneditor-menu jsoneditor-collapse-all";
  2581. collapseAll.onclick = function() {
  2582. editor.collapseAll();
  2583. };
  2584. this.menu.appendChild(collapseAll);
  2585. // create expand/collapse buttons
  2586. if (this.history) {
  2587. // create separator
  2588. var separator = document.createElement("span");
  2589. separator.innerHTML = "&nbsp;";
  2590. this.menu.appendChild(separator);
  2591. // create undo button
  2592. var undo = document.createElement("button");
  2593. undo.className = "jsoneditor-menu jsoneditor-undo";
  2594. undo.title = "撤销";
  2595. undo.onclick = function() {
  2596. // undo last action
  2597. editor.history.undo();
  2598. // trigger change callback
  2599. if (editor.options.change) {
  2600. editor.options.change();
  2601. }
  2602. };
  2603. this.menu.appendChild(undo);
  2604. this.dom.undo = undo;
  2605. // create redo button
  2606. var redo = document.createElement("button");
  2607. redo.className = "jsoneditor-menu jsoneditor-redo";
  2608. redo.title = "重做";
  2609. redo.onclick = function() {
  2610. // redo last action
  2611. editor.history.redo();
  2612. // trigger change callback
  2613. if (editor.options.change) {
  2614. editor.options.change();
  2615. }
  2616. };
  2617. this.menu.appendChild(redo);
  2618. this.dom.redo = redo;
  2619. // register handler for onchange of history
  2620. this.history.onChange = function() {
  2621. undo.disabled = !editor.history.canUndo();
  2622. redo.disabled = !editor.history.canRedo();
  2623. };
  2624. this.history.onChange();
  2625. }
  2626. // create search box
  2627. if (this.options.search) {
  2628. this.searchBox = new JSONEditor.SearchBox(this, this.menu);
  2629. }
  2630. };
  2631. /**
  2632. * Create main table
  2633. * @private
  2634. */
  2635. JSONEditor.prototype._createTable = function() {
  2636. var contentOuter = document.createElement("div");
  2637. contentOuter.className = "jsoneditor-content-outer";
  2638. this.contentOuter = contentOuter;
  2639. this.content = document.createElement("div");
  2640. this.content.className = "jsoneditor-content";
  2641. contentOuter.appendChild(this.content);
  2642. this.table = document.createElement("table");
  2643. this.table.className = "jsoneditor-table";
  2644. this.content.appendChild(this.table);
  2645. // IE8 does not handle overflow='auto' correctly.
  2646. // Therefore, set overflow to 'scroll'
  2647. var ieVersion = JSONEditor.getInternetExplorerVersion();
  2648. if (ieVersion == 8) {
  2649. this.content.style.overflow = "scroll";
  2650. }
  2651. // create colgroup where the first two columns don't have a fixed
  2652. // width, and the edit columns do have a fixed width
  2653. var col;
  2654. this.colgroupContent = document.createElement("colgroup");
  2655. col = document.createElement("col");
  2656. col.width = "24px";
  2657. this.colgroupContent.appendChild(col);
  2658. col = document.createElement("col");
  2659. this.colgroupContent.appendChild(col);
  2660. col = document.createElement("col");
  2661. col.width = "24px";
  2662. this.colgroupContent.appendChild(col);
  2663. col = document.createElement("col");
  2664. col.width = "24px";
  2665. this.colgroupContent.appendChild(col);
  2666. col = document.createElement("col");
  2667. col.width = "24px";
  2668. this.colgroupContent.appendChild(col);
  2669. this.table.appendChild(this.colgroupContent);
  2670. this.tbody = document.createElement("tbody");
  2671. this.table.appendChild(this.tbody);
  2672. this.frame.appendChild(contentOuter);
  2673. };
  2674. /**
  2675. * Find the node from an event target
  2676. * @param {Element} target
  2677. * @return {JSONEditor.Node | undefined} node or undefined when not found
  2678. */
  2679. JSONEditor.getNodeFromTarget = function(target) {
  2680. while (target) {
  2681. if (target.node) {
  2682. return target.node;
  2683. }
  2684. target = target.parentNode;
  2685. }
  2686. return undefined;
  2687. };
  2688. /**
  2689. * Create a JSONFormatter and attach it to given container
  2690. * @constructor JSONFormatter
  2691. * @param {Element} container
  2692. * @param {Object} [options] Object with options. available options:
  2693. * {Number} indentation Number of indentation
  2694. * spaces. 4 by default.
  2695. * {function} change Callback method
  2696. * triggered on change
  2697. * @param {JSON | String} [json] initial contents of the formatter
  2698. */
  2699. JSONFormatter = function(container, options, json) {
  2700. // check availability of JSON parser (not available in IE7 and older)
  2701. if (!JSON) {
  2702. throw new Error(
  2703. "您当前使用的浏览器不支持 JSON. \n\n" +
  2704. "请下载安装最新版本的浏览, 本站推荐Google Chrome.\n" +
  2705. "(PS: 当前主流浏览器都支持JSON)."
  2706. );
  2707. }
  2708. this.container = container;
  2709. this.indentation = 4; // number of spaces
  2710. this.width = container.clientWidth;
  2711. this.height = container.clientHeight;
  2712. this.frame = document.createElement("div");
  2713. this.frame.className = "jsoneditor-frame";
  2714. this.frame.onclick = function(event) {
  2715. // prevent default submit action when JSONFormatter is located inside a form
  2716. JSONEditor.Events.preventDefault(event);
  2717. };
  2718. // create menu
  2719. this.menu = document.createElement("div");
  2720. this.menu.className = "jsoneditor-menu";
  2721. this.frame.appendChild(this.menu);
  2722. // create format button
  2723. var buttonFormat = document.createElement("button");
  2724. //buttonFormat.innerHTML = 'Format';
  2725. buttonFormat.className = "jsoneditor-menu jsoneditor-format";
  2726. buttonFormat.title = "格式化JSON数据";
  2727. //buttonFormat.className = 'jsoneditor-button';
  2728. this.menu.appendChild(buttonFormat);
  2729. // create compact button
  2730. var buttonCompact = document.createElement("button");
  2731. //buttonCompact.innerHTML = 'Compact';
  2732. buttonCompact.className = "jsoneditor-menu jsoneditor-compact";
  2733. buttonCompact.title = "压缩JSON数据, 清除所有空白字符";
  2734. //buttonCompact.className = 'jsoneditor-button';
  2735. this.menu.appendChild(buttonCompact);
  2736. this.content = document.createElement("div");
  2737. this.content.className = "jsonformatter-content";
  2738. this.frame.appendChild(this.content);
  2739. this.textarea = document.createElement("textarea");
  2740. this.textarea.className = "jsonformatter-textarea";
  2741. this.textarea.spellcheck = false;
  2742. this.content.appendChild(this.textarea);
  2743. var textarea = this.textarea;
  2744. // read the options
  2745. if (options) {
  2746. if (options.change) {
  2747. // register on change event
  2748. if (this.textarea.oninput === null) {
  2749. this.textarea.oninput = function() {
  2750. options.change();
  2751. };
  2752. } else {
  2753. // oninput is undefined. For IE8-
  2754. this.textarea.onchange = function() {
  2755. options.change();
  2756. };
  2757. }
  2758. }
  2759. if (options.indentation) {
  2760. this.indentation = Number(options.indentation);
  2761. }
  2762. }
  2763. var me = this;
  2764. buttonFormat.onclick = function() {
  2765. try {
  2766. var json = JSONEditor.parse(textarea.value);
  2767. textarea.value = JSON.stringify(json, null, me.indentation);
  2768. } catch (err) {
  2769. me.onError(err);
  2770. }
  2771. };
  2772. buttonCompact.onclick = function() {
  2773. try {
  2774. var json = JSONEditor.parse(textarea.value);
  2775. textarea.value = JSON.stringify(json);
  2776. } catch (err) {
  2777. me.onError(err);
  2778. }
  2779. };
  2780. this.container.appendChild(this.frame);
  2781. // load initial json object or string
  2782. if (typeof json == "string") {
  2783. this.setText(json);
  2784. } else {
  2785. this.set(json);
  2786. }
  2787. };
  2788. /**
  2789. * This method is executed on error.
  2790. * It can be overwritten for each instance of the JSONFormatter
  2791. * @param {String} err
  2792. */
  2793. JSONFormatter.prototype.onError = function(err) {
  2794. // action should be implemented for the instance
  2795. };
  2796. /**
  2797. * Set json data in the formatter
  2798. * @param {Object} json
  2799. */
  2800. JSONFormatter.prototype.set = function(json) {
  2801. this.textarea.value = JSON.stringify(json, null, this.indentation);
  2802. };
  2803. /**
  2804. * Get json data from the formatter
  2805. * @return {Object} json
  2806. */
  2807. JSONFormatter.prototype.get = function() {
  2808. return JSONEditor.parse(this.textarea.value);
  2809. };
  2810. /**
  2811. * Get the text contents of the JSONFormatter
  2812. * @return {String} text
  2813. */
  2814. JSONFormatter.prototype.getText = function() {
  2815. return this.textarea.value;
  2816. };
  2817. /**
  2818. * Set the text contents of the JSONFormatter
  2819. * @param {String} text
  2820. */
  2821. JSONFormatter.prototype.setText = function(text) {
  2822. this.textarea.value = text;
  2823. };
  2824. /**
  2825. * @constructor JSONEditor.SearchBox
  2826. * Create a search box in given HTML container
  2827. * @param {JSONEditor} editor The JSON Editor to attach to
  2828. * @param {Element} container HTML container element of where to create the
  2829. * search box
  2830. */
  2831. JSONEditor.SearchBox = function(editor, container) {
  2832. var searchBox = this;
  2833. this.editor = editor;
  2834. this.timeout = undefined;
  2835. this.delay = 200; // ms
  2836. this.lastText = undefined;
  2837. this.dom = {};
  2838. this.dom.container = container;
  2839. var table = document.createElement("table");
  2840. this.dom.table = table;
  2841. table.className = "jsoneditor-search";
  2842. container.appendChild(table);
  2843. var tbody = document.createElement("tbody");
  2844. this.dom.tbody = tbody;
  2845. table.appendChild(tbody);
  2846. var tr = document.createElement("tr");
  2847. tbody.appendChild(tr);
  2848. var td = document.createElement("td");
  2849. td.className = "jsoneditor-search";
  2850. tr.appendChild(td);
  2851. var results = document.createElement("div");
  2852. this.dom.results = results;
  2853. results.className = "jsoneditor-search-results";
  2854. td.appendChild(results);
  2855. td = document.createElement("td");
  2856. td.className = "jsoneditor-search";
  2857. tr.appendChild(td);
  2858. var divInput = document.createElement("div");
  2859. this.dom.input = divInput;
  2860. divInput.className = "jsoneditor-search";
  2861. divInput.title = "查找区块";
  2862. td.appendChild(divInput);
  2863. // table to contain the text input and search button
  2864. var tableInput = document.createElement("table");
  2865. tableInput.className = "jsoneditor-search-input";
  2866. divInput.appendChild(tableInput);
  2867. var tbodySearch = document.createElement("tbody");
  2868. tableInput.appendChild(tbodySearch);
  2869. tr = document.createElement("tr");
  2870. tbodySearch.appendChild(tr);
  2871. var refreshSearch = document.createElement("button");
  2872. refreshSearch.className = "jsoneditor-search-refresh";
  2873. td = document.createElement("td");
  2874. td.appendChild(refreshSearch);
  2875. tr.appendChild(td);
  2876. var search = document.createElement("input");
  2877. this.dom.search = search;
  2878. search.className = "jsoneditor-search";
  2879. search.oninput = function(event) {
  2880. searchBox.onDelayedSearch(event);
  2881. };
  2882. search.onchange = function(event) {
  2883. // For IE 8
  2884. searchBox.onSearch(event);
  2885. };
  2886. search.onkeydown = function(event) {
  2887. searchBox.onKeyDown(event);
  2888. };
  2889. search.onkeyup = function(event) {
  2890. searchBox.onKeyUp(event);
  2891. };
  2892. refreshSearch.onclick = function(event) {
  2893. search.select();
  2894. };
  2895. // TODO: ESC in FF restores the last input, is a FF bug, https://bugzilla.mozilla.org/show_bug.cgi?id=598819
  2896. td = document.createElement("td");
  2897. td.appendChild(search);
  2898. tr.appendChild(td);
  2899. var searchNext = document.createElement("button");
  2900. searchNext.title = "下一个 (Enter)";
  2901. searchNext.className = "jsoneditor-search-next";
  2902. searchNext.onclick = function() {
  2903. searchBox.next();
  2904. };
  2905. td = document.createElement("td");
  2906. td.appendChild(searchNext);
  2907. tr.appendChild(td);
  2908. var searchPrevious = document.createElement("button");
  2909. searchPrevious.title = "上一个 (Shift+Enter)";
  2910. searchPrevious.className = "jsoneditor-search-previous";
  2911. searchPrevious.onclick = function() {
  2912. searchBox.previous();
  2913. };
  2914. td = document.createElement("td");
  2915. td.appendChild(searchPrevious);
  2916. tr.appendChild(td);
  2917. };
  2918. /**
  2919. * Go to the next search result
  2920. */
  2921. JSONEditor.SearchBox.prototype.next = function() {
  2922. if (this.results != undefined) {
  2923. var index = this.resultIndex != undefined ? this.resultIndex + 1 : 0;
  2924. if (index > this.results.length - 1) {
  2925. index = 0;
  2926. }
  2927. this.setActiveResult(index);
  2928. }
  2929. };
  2930. /**
  2931. * Go to the prevous search result
  2932. */
  2933. JSONEditor.SearchBox.prototype.previous = function() {
  2934. if (this.results != undefined) {
  2935. var max = this.results.length - 1;
  2936. var index = this.resultIndex != undefined ? this.resultIndex - 1 : max;
  2937. if (index < 0) {
  2938. index = max;
  2939. }
  2940. this.setActiveResult(index);
  2941. }
  2942. };
  2943. /**
  2944. * Set new value for the current active result
  2945. * @param {Number} index
  2946. */
  2947. JSONEditor.SearchBox.prototype.setActiveResult = function(index) {
  2948. // de-activate current active result
  2949. if (this.activeResult) {
  2950. var prevNode = this.activeResult.node;
  2951. var prevElem = this.activeResult.elem;
  2952. if (prevElem == "field") {
  2953. delete prevNode.searchFieldActive;
  2954. } else {
  2955. delete prevNode.searchValueActive;
  2956. }
  2957. prevNode.updateDom();
  2958. }
  2959. if (!this.results || !this.results[index]) {
  2960. // out of range, set to undefined
  2961. this.resultIndex = undefined;
  2962. this.activeResult = undefined;
  2963. return;
  2964. }
  2965. this.resultIndex = index;
  2966. // set new node active
  2967. var node = this.results[this.resultIndex].node;
  2968. var elem = this.results[this.resultIndex].elem;
  2969. if (elem == "field") {
  2970. node.searchFieldActive = true;
  2971. } else {
  2972. node.searchValueActive = true;
  2973. }
  2974. this.activeResult = this.results[this.resultIndex];
  2975. node.updateDom();
  2976. node.scrollTo();
  2977. };
  2978. /**
  2979. * Set the focus to the currently active result. If there is no currently
  2980. * active result, the next search result will get focus
  2981. */
  2982. JSONEditor.SearchBox.prototype.focusActiveResult = function() {
  2983. if (!this.activeResult) {
  2984. this.next();
  2985. }
  2986. if (this.activeResult) {
  2987. this.activeResult.node.focus(this.activeResult.elem);
  2988. }
  2989. };
  2990. /**
  2991. * Cancel any running onDelayedSearch.
  2992. */
  2993. JSONEditor.SearchBox.prototype.clearDelay = function() {
  2994. if (this.timeout != undefined) {
  2995. clearTimeout(this.timeout);
  2996. delete this.timeout;
  2997. }
  2998. };
  2999. /**
  3000. * Start a timer to execute a search after a short delay.
  3001. * Used for reducing the number of searches while typing.
  3002. * @param {Event} event
  3003. */
  3004. JSONEditor.SearchBox.prototype.onDelayedSearch = function(event) {
  3005. // execute the search after a short delay (reduces the number of
  3006. // search actions while typing in the search text box)
  3007. this.clearDelay();
  3008. var searchBox = this;
  3009. this.timeout = setTimeout(function(event) {
  3010. searchBox.onSearch(event);
  3011. }, this.delay);
  3012. };
  3013. /**
  3014. * Handle onSearch event
  3015. * @param {Event} event
  3016. * @param {boolean} [forceSearch] If true, search will be executed again even
  3017. * when the search text is not changed.
  3018. * Default is false.
  3019. */
  3020. JSONEditor.SearchBox.prototype.onSearch = function(event, forceSearch) {
  3021. this.clearDelay();
  3022. var value = this.dom.search.value;
  3023. var text = value.length > 0 ? value : undefined;
  3024. if (text != this.lastText || forceSearch) {
  3025. // only search again when changed
  3026. this.lastText = text;
  3027. this.results = this.editor.search(text);
  3028. this.setActiveResult(undefined);
  3029. // display search results
  3030. if (text != undefined) {
  3031. var resultCount = this.results.length;
  3032. switch (resultCount) {
  3033. case 0:
  3034. this.dom.results.innerHTML = "区块/值未找到";
  3035. break;
  3036. default:
  3037. this.dom.results.innerHTML =
  3038. "找到&nbsp;" + resultCount + "&nbsp;个节点";
  3039. break;
  3040. }
  3041. } else {
  3042. this.dom.results.innerHTML = "";
  3043. }
  3044. }
  3045. };
  3046. /**
  3047. * Handle onKeyDown event in the input box
  3048. * @param {Event} event
  3049. */
  3050. JSONEditor.SearchBox.prototype.onKeyDown = function(event) {
  3051. event = event || window.event;
  3052. var keynum = event.which || event.keyCode;
  3053. if (keynum == 27) {
  3054. // ESC
  3055. this.dom.search.value = ""; // clear search
  3056. this.onSearch(event);
  3057. JSONEditor.Events.preventDefault(event);
  3058. JSONEditor.Events.stopPropagation(event);
  3059. } else if (keynum == 13) {
  3060. // Enter
  3061. if (event.ctrlKey) {
  3062. // force to search again
  3063. this.onSearch(event, true);
  3064. } else if (event.shiftKey) {
  3065. // move to the previous search result
  3066. this.previous();
  3067. } else {
  3068. // move to the next search result
  3069. this.next();
  3070. }
  3071. JSONEditor.Events.preventDefault(event);
  3072. JSONEditor.Events.stopPropagation(event);
  3073. }
  3074. };
  3075. /**
  3076. * Handle onKeyUp event in the input box
  3077. * @param {Event} event
  3078. */
  3079. JSONEditor.SearchBox.prototype.onKeyUp = function(event) {
  3080. event = event || window.event;
  3081. var keynum = event.which || event.keyCode;
  3082. if (keynum != 27 && keynum != 13) {
  3083. // !ESC and !Enter
  3084. this.onDelayedSearch(event); // For IE 8
  3085. }
  3086. };
  3087. // create namespace for event methods
  3088. JSONEditor.Events = {};
  3089. /**
  3090. * Add and event listener. Works for all browsers
  3091. * @param {Element} element An html element
  3092. * @param {string} action The action, for example "click",
  3093. * without the prefix "on"
  3094. * @param {function} listener The callback function to be executed
  3095. * @param {boolean} useCapture
  3096. * @return {function} the created event listener
  3097. */
  3098. JSONEditor.Events.addEventListener = function(
  3099. element,
  3100. action,
  3101. listener,
  3102. useCapture
  3103. ) {
  3104. if (element.addEventListener) {
  3105. if (useCapture === undefined) useCapture = false;
  3106. if (
  3107. action === "mousewheel" &&
  3108. navigator.userAgent.indexOf("Firefox") >= 0
  3109. ) {
  3110. action = "DOMMouseScroll"; // For Firefox
  3111. }
  3112. element.addEventListener(action, listener, useCapture);
  3113. return listener;
  3114. } else {
  3115. // IE browsers
  3116. var f = function() {
  3117. return listener.call(element, window.event);
  3118. };
  3119. element.attachEvent("on" + action, f);
  3120. return f;
  3121. }
  3122. };
  3123. /**
  3124. * Remove an event listener from an element
  3125. * @param {Element} element An html dom element
  3126. * @param {string} action The name of the event, for example "mousedown"
  3127. * @param {function} listener The listener function
  3128. * @param {boolean} useCapture
  3129. */
  3130. JSONEditor.Events.removeEventListener = function(
  3131. element,
  3132. action,
  3133. listener,
  3134. useCapture
  3135. ) {
  3136. if (element.removeEventListener) {
  3137. // non-IE browsers
  3138. if (useCapture === undefined) useCapture = false;
  3139. if (
  3140. action === "mousewheel" &&
  3141. navigator.userAgent.indexOf("Firefox") >= 0
  3142. ) {
  3143. action = "DOMMouseScroll"; // For Firefox
  3144. }
  3145. element.removeEventListener(action, listener, useCapture);
  3146. } else {
  3147. // IE browsers
  3148. element.detachEvent("on" + action, listener);
  3149. }
  3150. };
  3151. /**
  3152. * Stop event propagation
  3153. * @param {Event} event
  3154. */
  3155. JSONEditor.Events.stopPropagation = function(event) {
  3156. if (!event) event = window.event;
  3157. if (event.stopPropagation) {
  3158. event.stopPropagation(); // non-IE browsers
  3159. } else {
  3160. event.cancelBubble = true; // IE browsers
  3161. }
  3162. };
  3163. /**
  3164. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  3165. * @param {Event} event
  3166. */
  3167. JSONEditor.Events.preventDefault = function(event) {
  3168. if (!event) event = window.event;
  3169. if (event.preventDefault) {
  3170. event.preventDefault(); // non-IE browsers
  3171. } else {
  3172. event.returnValue = false; // IE browsers
  3173. }
  3174. };
  3175. /**
  3176. * Retrieve the absolute left value of a DOM element
  3177. * @param {Element} elem A dom element, for example a div
  3178. * @return {Number} left The absolute left position of this element
  3179. * in the browser page.
  3180. */
  3181. JSONEditor.getAbsoluteLeft = function(elem) {
  3182. var left = 0;
  3183. var body = document.body;
  3184. while (elem != null && elem != body) {
  3185. left += elem.offsetLeft;
  3186. left -= elem.scrollLeft;
  3187. elem = elem.offsetParent;
  3188. }
  3189. return left;
  3190. };
  3191. /**
  3192. * Retrieve the absolute top value of a DOM element
  3193. * @param {Element} elem A dom element, for example a div
  3194. * @return {Number} top The absolute top position of this element
  3195. * in the browser page.
  3196. */
  3197. JSONEditor.getAbsoluteTop = function(elem) {
  3198. var top = 0;
  3199. var body = document.body;
  3200. while (elem != null && elem != body) {
  3201. top += elem.offsetTop;
  3202. top -= elem.scrollTop;
  3203. elem = elem.offsetParent;
  3204. }
  3205. return top;
  3206. };
  3207. /**
  3208. * add a className to the given elements style
  3209. * @param {Element} elem
  3210. * @param {String} className
  3211. */
  3212. JSONEditor.addClassName = function(elem, className) {
  3213. var classes = elem.className.split(" ");
  3214. if (classes.indexOf(className) == -1) {
  3215. classes.push(className); // add the class to the array
  3216. elem.className = classes.join(" ");
  3217. }
  3218. };
  3219. /**
  3220. * add a className to the given elements style
  3221. * @param {Element} elem
  3222. * @param {String} className
  3223. */
  3224. JSONEditor.removeClassName = function(elem, className) {
  3225. var classes = elem.className.split(" ");
  3226. var index = classes.indexOf(className);
  3227. if (index != -1) {
  3228. classes.splice(index, 1); // remove the class from the array
  3229. elem.className = classes.join(" ");
  3230. }
  3231. };
  3232. /**
  3233. * Strip the formatting from the contents of a div
  3234. * the formatting from the div itself is not stripped, only from its childs.
  3235. * @param {Element} divElement
  3236. */
  3237. JSONEditor.stripFormatting = function(divElement) {
  3238. var childs = divElement.childNodes;
  3239. for (var i = 0, iMax = childs.length; i < iMax; i++) {
  3240. var child = childs[i];
  3241. // remove the style
  3242. if (child.style) {
  3243. // TODO: test if child.attributes does contain style
  3244. child.removeAttribute("style");
  3245. }
  3246. // remove all attributes
  3247. var attributes = child.attributes;
  3248. if (attributes) {
  3249. for (var j = attributes.length - 1; j >= 0; j--) {
  3250. var attribute = attributes[j];
  3251. if (attribute.specified == true) {
  3252. child.removeAttribute(attribute.name);
  3253. }
  3254. }
  3255. }
  3256. // recursively strip childs
  3257. JSONEditor.stripFormatting(child);
  3258. }
  3259. };
  3260. /**
  3261. * Set focus to the end of an editable div
  3262. * code from Nico Burns
  3263. * http://stackoverflow.com/users/140293/nico-burns
  3264. * http://stackoverflow.com/questions/1125292/how-to-move-cursor-to-end-of-contenteditable-entity
  3265. * @param {Element} contentEditableElement
  3266. */
  3267. JSONEditor.setEndOfContentEditable = function(contentEditableElement) {
  3268. var range, selection;
  3269. if (document.createRange) {
  3270. //Firefox, Chrome, Opera, Safari, IE 9+
  3271. range = document.createRange(); //Create a range (a range is a like the selection but invisible)
  3272. range.selectNodeContents(contentEditableElement); //Select the entire contents of the element with the range
  3273. range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start
  3274. selection = window.getSelection(); //get the selection object (allows you to change selection)
  3275. selection.removeAllRanges(); //remove any selections already made
  3276. selection.addRange(range); //make the range you have just created the visible selection
  3277. } else if (document.selection) {
  3278. //IE 8 and lower
  3279. range = document.body.createTextRange(); //Create a range (a range is a like the selection but invisible)
  3280. range.moveToElementText(contentEditableElement); //Select the entire contents of the element with the range
  3281. range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start
  3282. range.select(); //Select the range (make it the visible selection
  3283. }
  3284. };
  3285. /**
  3286. * Get the inner text of an HTML element (for example a div element)
  3287. * @param {Element} element
  3288. * @param {Object} [buffer]
  3289. * @return {String} innerText
  3290. */
  3291. JSONEditor.getInnerText = function(element, buffer) {
  3292. var first = buffer == undefined;
  3293. if (first) {
  3294. buffer = {
  3295. text: "",
  3296. flush: function() {
  3297. var text = this.text;
  3298. this.text = "";
  3299. return text;
  3300. },
  3301. set: function(text) {
  3302. this.text = text;
  3303. }
  3304. };
  3305. }
  3306. // text node
  3307. if (element.nodeValue) {
  3308. return buffer.flush() + element.nodeValue;
  3309. }
  3310. // divs or other HTML elements
  3311. if (element.hasChildNodes()) {
  3312. var childNodes = element.childNodes;
  3313. var innerText = "";
  3314. for (var i = 0, iMax = childNodes.length; i < iMax; i++) {
  3315. var child = childNodes[i];
  3316. if (child.nodeName == "DIV" || child.nodeName == "P") {
  3317. var prevChild = childNodes[i - 1];
  3318. var prevName = prevChild ? prevChild.nodeName : undefined;
  3319. if (
  3320. prevName &&
  3321. prevName != "DIV" &&
  3322. prevName != "P" &&
  3323. prevName != "BR"
  3324. ) {
  3325. innerText += "\n";
  3326. buffer.flush();
  3327. }
  3328. innerText += JSONEditor.getInnerText(child, buffer);
  3329. buffer.set("\n");
  3330. } else if (child.nodeName == "BR") {
  3331. innerText += buffer.flush();
  3332. buffer.set("\n");
  3333. } else {
  3334. innerText += JSONEditor.getInnerText(child, buffer);
  3335. }
  3336. }
  3337. return innerText;
  3338. } else {
  3339. if (
  3340. element.nodeName == "P" &&
  3341. JSONEditor.getInternetExplorerVersion() != -1
  3342. ) {
  3343. // On Internet Explorer, a <p> with hasChildNodes()==false is
  3344. // rendered with a new line. Note that a <p> with
  3345. // hasChildNodes()==true is rendered without a new line
  3346. // Other browsers always ensure there is a <br> inside the <p>,
  3347. // and if not, the <p> does not render a new line
  3348. return buffer.flush();
  3349. }
  3350. }
  3351. // br or unknown
  3352. return "";
  3353. };
  3354. /**
  3355. * Returns the version of Internet Explorer or a -1
  3356. * (indicating the use of another browser).
  3357. * Source: http://msdn.microsoft.com/en-us/library/ms537509(v=vs.85).aspx
  3358. * @return {Number} Internet Explorer version, or -1 in case of an other browser
  3359. */
  3360. JSONEditor._ieVersion = undefined;
  3361. JSONEditor.getInternetExplorerVersion = function() {
  3362. if (JSONEditor._ieVersion == undefined) {
  3363. var rv = -1; // Return value assumes failure.
  3364. if (navigator.appName == "Microsoft Internet Explorer") {
  3365. var ua = navigator.userAgent;
  3366. var re = new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");
  3367. if (re.exec(ua) != null) {
  3368. rv = parseFloat(RegExp.$1);
  3369. }
  3370. }
  3371. JSONEditor._ieVersion = rv;
  3372. }
  3373. return JSONEditor._ieVersion;
  3374. };
  3375. JSONEditor.ieVersion = JSONEditor.getInternetExplorerVersion();
  3376. /**
  3377. * Parse JSON using the parser built-in in the browser.
  3378. * On exception, the jsonString is validated and a detailed error is thrown.
  3379. * @param {String} jsonString
  3380. */
  3381. JSONEditor.parse = function(jsonString) {
  3382. try {
  3383. return JSON.parse(jsonString);
  3384. } catch (err) {
  3385. // get a detailed error message using validate
  3386. var message = JSONEditor.validate(jsonString) || err;
  3387. throw new Error(message);
  3388. }
  3389. };
  3390. /**
  3391. * Validate a string containing a JSON object
  3392. * This method uses JSONLint to validate the String. If JSONLint is not
  3393. * available, the built-in JSON parser of the browser is used.
  3394. * @param {String} jsonString String with an (invalid) JSON object
  3395. * @return {String | undefined} Returns undefined when the string is valid JSON,
  3396. * returns a string with an error message when
  3397. * the data is invalid
  3398. */
  3399. JSONEditor.validate = function(jsonString) {
  3400. var message = undefined;
  3401. try {
  3402. if (window.jsonlint) {
  3403. window.jsonlint.parse(jsonString);
  3404. } else {
  3405. JSON.parse(jsonString);
  3406. }
  3407. } catch (err) {
  3408. message = '<pre class="error">' + err.toString() + "</pre>";
  3409. if (window.jsonlint) {
  3410. message +=
  3411. '<div id="by-jsonlint"> <a class="error" href="http://zaach.github.com/jsonlint/" target="_blank">' +
  3412. "JSONLint" +
  3413. "</a> 提供验证.</div>";
  3414. }
  3415. }
  3416. return message;
  3417. };
  3418. function jsonArea(ob) {
  3419. let inputEle = ob.el;
  3420. let insert = ob.insert;
  3421. let nth = ob.nth;
  3422. let change = ob.change;
  3423. let thisare = new Object();
  3424. if (!inputEle) {
  3425. throw new Error("没有提供数据.");
  3426. }
  3427. // if target to multi dom
  3428. if (nth) {
  3429. thisare.data = document.querySelectorAll(inputEle)[nth - 1];
  3430. } else {
  3431. let all = document.querySelectorAll(inputEle);
  3432. for (var i = 0; i < all.length; i++) {
  3433. jsonArea({ el: inputEle, insert: insert, nth: i + 1, change: change });
  3434. }
  3435. // thisare.data = document.querySelector(inputEle);
  3436. return;
  3437. }
  3438. // create container
  3439. thisare.container = document.createElement("div");
  3440. thisare.container.style.width = "100%";
  3441. thisare.container.style.height = "auto";
  3442. thisare.data.parentElement.insertBefore(thisare.container, thisare.data);
  3443. // check wether is a json
  3444. thisare.isJson = function(str) {
  3445. if (!isNaN(str)) return false;
  3446. if (str == "") return false;
  3447. if (str == '""') return false;
  3448. if (typeof str == "string") {
  3449. try {
  3450. JSON.parse(str);
  3451. return true;
  3452. } catch (e) {
  3453. // console.log(e);
  3454. return false;
  3455. }
  3456. }
  3457. console.log("不是丢�个stringify");
  3458. };
  3459. // json data
  3460. thisare.jsonval = "{}";
  3461. // callback when change
  3462. thisare.syn = function() {};
  3463. // whether is textarea or input
  3464. if (thisare.data.tagName == "TEXTAREA") {
  3465. thisare.jsonval = thisare.data.innerHTML;
  3466. thisare.syn = function() {
  3467. thisare.data.innerHTML = JSON.stringify(thisare.jsonEditor.get());
  3468. change(thisare.jsonEditor.get());
  3469. };
  3470. } else if (thisare.data.tagName == "INPUT") {
  3471. thisare.jsonval = thisare.data.value;
  3472. thisare.syn = function() {
  3473. thisare.data.value = JSON.stringify(thisare.jsonEditor.get());
  3474. change(thisare.jsonEditor.get());
  3475. };
  3476. }
  3477. // if not json, and not insert ,then json editer
  3478. thisare.hashJson = thisare.isJson(thisare.jsonval);
  3479. if (!thisare.hashJson && !insert) return;
  3480. thisare.data.style.display = "none";
  3481. // create jsoneditor for father
  3482. thisare.jsonEditor = new JSONEditor(thisare.container, {
  3483. change: function() {
  3484. thisare.lastChanged = thisare.jsonEditor;
  3485. thisare.syn();
  3486. }
  3487. });
  3488. if (thisare.hashJson) {
  3489. thisare.jsonEditor.set(JSON.parse(thisare.jsonval));
  3490. }
  3491. console.log("in jsonArea");
  3492. return thisare;
  3493. }