statixite 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (462) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +145 -0
  4. data/Rakefile +26 -0
  5. data/app/assets/images/statixite/STATIXITE-logo.png +0 -0
  6. data/app/assets/images/statixite/favicon.ico +0 -0
  7. data/app/assets/images/statixite/jekyll.png +0 -0
  8. data/app/assets/images/statixite/md-editor.png +0 -0
  9. data/app/assets/images/statixite/ring-alt.gif +0 -0
  10. data/app/assets/javascripts/controllers/statixite/deployments_create.js +13 -0
  11. data/app/assets/javascripts/controllers/statixite/deployments_index.js +13 -0
  12. data/app/assets/javascripts/controllers/statixite/sites_create.js +27 -0
  13. data/app/assets/javascripts/controllers/statixite/sites_new.js +27 -0
  14. data/app/assets/javascripts/controllers/statixite/templates_edit.js +208 -0
  15. data/app/assets/javascripts/statixite/application.js +8 -0
  16. data/app/assets/javascripts/statixite/array-helpers.js +13 -0
  17. data/app/assets/javascripts/statixite/editor.js +244 -0
  18. data/app/assets/javascripts/statixite/loader.js +14 -0
  19. data/app/assets/javascripts/statixite/notify-defaults.js +5 -0
  20. data/app/assets/stylesheets/statixite/application.less +271 -0
  21. data/app/assets/stylesheets/statixite/bootstrap-glyphicons.less +305 -0
  22. data/app/assets/stylesheets/statixite/bootstrap.less +58 -0
  23. data/app/assets/stylesheets/statixite/bootstrap_and_overrides.less +2 -0
  24. data/app/assets/stylesheets/statixite/landing-page.less +156 -0
  25. data/app/controllers/statixite/application_controller.rb +22 -0
  26. data/app/controllers/statixite/deployments_controller.rb +61 -0
  27. data/app/controllers/statixite/media_controller.rb +61 -0
  28. data/app/controllers/statixite/posts_controller.rb +68 -0
  29. data/app/controllers/statixite/sites_controller.rb +141 -0
  30. data/app/controllers/statixite/templates_controller.rb +233 -0
  31. data/app/helpers/statixite/application_helper.rb +63 -0
  32. data/app/models/statixite/deployment.rb +5 -0
  33. data/app/models/statixite/media.rb +7 -0
  34. data/app/models/statixite/post.rb +76 -0
  35. data/app/models/statixite/site.rb +76 -0
  36. data/app/services/statixite/deployment_service.rb +73 -0
  37. data/app/services/statixite/git_service.rb +102 -0
  38. data/app/services/statixite/site_deactivation_service.rb +52 -0
  39. data/app/services/statixite/site_operation_service.rb +251 -0
  40. data/app/uploaders/statixite/file_uploader.rb +14 -0
  41. data/app/validators/statixite/jekyll_template_validator.rb +16 -0
  42. data/app/validators/statixite/liquid_validator.rb +11 -0
  43. data/app/views/layouts/statixite/_dash_nav.html.haml +20 -0
  44. data/app/views/layouts/statixite/_flash.html.haml +13 -0
  45. data/app/views/layouts/statixite/_head.html.haml +12 -0
  46. data/app/views/layouts/statixite/_header.html.haml +30 -0
  47. data/app/views/layouts/statixite/_site_sub_items.html.haml +38 -0
  48. data/app/views/layouts/statixite/dashboard.html.haml +27 -0
  49. data/app/views/statixite/deployments/_modal.html.haml +18 -0
  50. data/app/views/statixite/deployments/index.html.haml +39 -0
  51. data/app/views/statixite/kaminari/_first_page.html.haml +2 -0
  52. data/app/views/statixite/kaminari/_gap.html.haml +2 -0
  53. data/app/views/statixite/kaminari/_last_page.html.haml +2 -0
  54. data/app/views/statixite/kaminari/_next_page.html.haml +2 -0
  55. data/app/views/statixite/kaminari/_page.html.haml +6 -0
  56. data/app/views/statixite/kaminari/_paginator.html.haml +11 -0
  57. data/app/views/statixite/kaminari/_prev_page.html.haml +2 -0
  58. data/app/views/statixite/media/_index.html.haml +13 -0
  59. data/app/views/statixite/media/_paginate.html.haml +1 -0
  60. data/app/views/statixite/media/index.html.haml +39 -0
  61. data/app/views/statixite/media/index.js.haml +2 -0
  62. data/app/views/statixite/posts/_form.html.haml +65 -0
  63. data/app/views/statixite/posts/_image_modal.html.haml +21 -0
  64. data/app/views/statixite/posts/_modals.html.haml +111 -0
  65. data/app/views/statixite/posts/_posts.html.haml +15 -0
  66. data/app/views/statixite/posts/edit.html.haml +6 -0
  67. data/app/views/statixite/posts/index.html.haml +4 -0
  68. data/app/views/statixite/posts/index.js.haml +1 -0
  69. data/app/views/statixite/posts/new.html.haml +5 -0
  70. data/app/views/statixite/sites/_sites.html.haml +27 -0
  71. data/app/views/statixite/sites/_templates.html.haml +26 -0
  72. data/app/views/statixite/sites/build_and_preview.html.haml +24 -0
  73. data/app/views/statixite/sites/index.html.haml +4 -0
  74. data/app/views/statixite/sites/index.js.haml +1 -0
  75. data/app/views/statixite/sites/new.html.haml +45 -0
  76. data/app/views/statixite/sites/settings.html.haml +77 -0
  77. data/app/views/statixite/templates/edit.html.haml +93 -0
  78. data/app/views/statixite/templates/upload_files.js.haml +2 -0
  79. data/config/routes.rb +24 -0
  80. data/db/migrate/20150525171543_create_statixite_sites.rb +15 -0
  81. data/db/migrate/20150530041055_create_statixite_posts.rb +14 -0
  82. data/db/migrate/20150610024047_create_statixite_deployments.rb +11 -0
  83. data/db/migrate/20151013131224_create_statixite_media.rb +10 -0
  84. data/lib/assets/templates.yaml +734 -0
  85. data/lib/generators/statixite/statixite_generator.rb +49 -0
  86. data/lib/statixite.rb +14 -0
  87. data/lib/statixite/cloud_sync.rb +104 -0
  88. data/lib/statixite/engine.rb +49 -0
  89. data/lib/statixite/version.rb +3 -0
  90. data/lib/tasks/statixite_tasks.rake +4 -0
  91. data/vendor/assets/bower_components/bootstrap/CHANGELOG.md +5 -0
  92. data/vendor/assets/bower_components/bootstrap/Gruntfile.js +533 -0
  93. data/vendor/assets/bower_components/bootstrap/LICENSE +21 -0
  94. data/vendor/assets/bower_components/bootstrap/README.md +139 -0
  95. data/vendor/assets/bower_components/bootstrap/bower.json +34 -0
  96. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap-theme.css +587 -0
  97. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap-theme.css.map +1 -0
  98. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap-theme.min.css +6 -0
  99. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap-theme.min.css.map +1 -0
  100. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap.css +6760 -0
  101. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap.css.map +1 -0
  102. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap.min.css +6 -0
  103. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap.min.css.map +1 -0
  104. data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.eot +0 -0
  105. data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.svg +288 -0
  106. data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf +0 -0
  107. data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff +0 -0
  108. data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 +0 -0
  109. data/vendor/assets/bower_components/bootstrap/dist/js/bootstrap.js +2363 -0
  110. data/vendor/assets/bower_components/bootstrap/dist/js/bootstrap.min.js +7 -0
  111. data/vendor/assets/bower_components/bootstrap/dist/js/npm.js +13 -0
  112. data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  113. data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
  114. data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  115. data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  116. data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
  117. data/vendor/assets/bower_components/bootstrap/grunt/bs-commonjs-generator.js +30 -0
  118. data/vendor/assets/bower_components/bootstrap/grunt/bs-glyphicons-data-generator.js +42 -0
  119. data/vendor/assets/bower_components/bootstrap/grunt/bs-lessdoc-parser.js +237 -0
  120. data/vendor/assets/bower_components/bootstrap/grunt/bs-raw-files-generator.js +44 -0
  121. data/vendor/assets/bower_components/bootstrap/grunt/configBridge.json +46 -0
  122. data/vendor/assets/bower_components/bootstrap/grunt/sauce_browsers.yml +82 -0
  123. data/vendor/assets/bower_components/bootstrap/js/affix.js +162 -0
  124. data/vendor/assets/bower_components/bootstrap/js/alert.js +94 -0
  125. data/vendor/assets/bower_components/bootstrap/js/button.js +120 -0
  126. data/vendor/assets/bower_components/bootstrap/js/carousel.js +237 -0
  127. data/vendor/assets/bower_components/bootstrap/js/collapse.js +211 -0
  128. data/vendor/assets/bower_components/bootstrap/js/dropdown.js +165 -0
  129. data/vendor/assets/bower_components/bootstrap/js/modal.js +337 -0
  130. data/vendor/assets/bower_components/bootstrap/js/popover.js +108 -0
  131. data/vendor/assets/bower_components/bootstrap/js/scrollspy.js +172 -0
  132. data/vendor/assets/bower_components/bootstrap/js/tab.js +155 -0
  133. data/vendor/assets/bower_components/bootstrap/js/tooltip.js +514 -0
  134. data/vendor/assets/bower_components/bootstrap/js/transition.js +59 -0
  135. data/vendor/assets/bower_components/bootstrap/less/alerts.less +73 -0
  136. data/vendor/assets/bower_components/bootstrap/less/badges.less +66 -0
  137. data/vendor/assets/bower_components/bootstrap/less/bootstrap.less +56 -0
  138. data/vendor/assets/bower_components/bootstrap/less/breadcrumbs.less +26 -0
  139. data/vendor/assets/bower_components/bootstrap/less/button-groups.less +244 -0
  140. data/vendor/assets/bower_components/bootstrap/less/buttons.less +166 -0
  141. data/vendor/assets/bower_components/bootstrap/less/carousel.less +270 -0
  142. data/vendor/assets/bower_components/bootstrap/less/close.less +34 -0
  143. data/vendor/assets/bower_components/bootstrap/less/code.less +69 -0
  144. data/vendor/assets/bower_components/bootstrap/less/component-animations.less +33 -0
  145. data/vendor/assets/bower_components/bootstrap/less/dropdowns.less +216 -0
  146. data/vendor/assets/bower_components/bootstrap/less/forms.less +613 -0
  147. data/vendor/assets/bower_components/bootstrap/less/glyphicons.less +305 -0
  148. data/vendor/assets/bower_components/bootstrap/less/grid.less +84 -0
  149. data/vendor/assets/bower_components/bootstrap/less/input-groups.less +171 -0
  150. data/vendor/assets/bower_components/bootstrap/less/jumbotron.less +54 -0
  151. data/vendor/assets/bower_components/bootstrap/less/labels.less +64 -0
  152. data/vendor/assets/bower_components/bootstrap/less/list-group.less +130 -0
  153. data/vendor/assets/bower_components/bootstrap/less/media.less +66 -0
  154. data/vendor/assets/bower_components/bootstrap/less/mixins.less +40 -0
  155. data/vendor/assets/bower_components/bootstrap/less/mixins/alerts.less +14 -0
  156. data/vendor/assets/bower_components/bootstrap/less/mixins/background-variant.less +9 -0
  157. data/vendor/assets/bower_components/bootstrap/less/mixins/border-radius.less +18 -0
  158. data/vendor/assets/bower_components/bootstrap/less/mixins/buttons.less +65 -0
  159. data/vendor/assets/bower_components/bootstrap/less/mixins/center-block.less +7 -0
  160. data/vendor/assets/bower_components/bootstrap/less/mixins/clearfix.less +22 -0
  161. data/vendor/assets/bower_components/bootstrap/less/mixins/forms.less +85 -0
  162. data/vendor/assets/bower_components/bootstrap/less/mixins/gradients.less +59 -0
  163. data/vendor/assets/bower_components/bootstrap/less/mixins/grid-framework.less +91 -0
  164. data/vendor/assets/bower_components/bootstrap/less/mixins/grid.less +122 -0
  165. data/vendor/assets/bower_components/bootstrap/less/mixins/hide-text.less +21 -0
  166. data/vendor/assets/bower_components/bootstrap/less/mixins/image.less +33 -0
  167. data/vendor/assets/bower_components/bootstrap/less/mixins/labels.less +12 -0
  168. data/vendor/assets/bower_components/bootstrap/less/mixins/list-group.less +30 -0
  169. data/vendor/assets/bower_components/bootstrap/less/mixins/nav-divider.less +10 -0
  170. data/vendor/assets/bower_components/bootstrap/less/mixins/nav-vertical-align.less +9 -0
  171. data/vendor/assets/bower_components/bootstrap/less/mixins/opacity.less +8 -0
  172. data/vendor/assets/bower_components/bootstrap/less/mixins/pagination.less +24 -0
  173. data/vendor/assets/bower_components/bootstrap/less/mixins/panels.less +24 -0
  174. data/vendor/assets/bower_components/bootstrap/less/mixins/progress-bar.less +10 -0
  175. data/vendor/assets/bower_components/bootstrap/less/mixins/reset-filter.less +8 -0
  176. data/vendor/assets/bower_components/bootstrap/less/mixins/reset-text.less +18 -0
  177. data/vendor/assets/bower_components/bootstrap/less/mixins/resize.less +6 -0
  178. data/vendor/assets/bower_components/bootstrap/less/mixins/responsive-visibility.less +15 -0
  179. data/vendor/assets/bower_components/bootstrap/less/mixins/size.less +10 -0
  180. data/vendor/assets/bower_components/bootstrap/less/mixins/tab-focus.less +9 -0
  181. data/vendor/assets/bower_components/bootstrap/less/mixins/table-row.less +28 -0
  182. data/vendor/assets/bower_components/bootstrap/less/mixins/text-emphasis.less +9 -0
  183. data/vendor/assets/bower_components/bootstrap/less/mixins/text-overflow.less +8 -0
  184. data/vendor/assets/bower_components/bootstrap/less/mixins/vendor-prefixes.less +227 -0
  185. data/vendor/assets/bower_components/bootstrap/less/modals.less +150 -0
  186. data/vendor/assets/bower_components/bootstrap/less/navbar.less +660 -0
  187. data/vendor/assets/bower_components/bootstrap/less/navs.less +242 -0
  188. data/vendor/assets/bower_components/bootstrap/less/normalize.less +424 -0
  189. data/vendor/assets/bower_components/bootstrap/less/pager.less +54 -0
  190. data/vendor/assets/bower_components/bootstrap/less/pagination.less +89 -0
  191. data/vendor/assets/bower_components/bootstrap/less/panels.less +271 -0
  192. data/vendor/assets/bower_components/bootstrap/less/popovers.less +131 -0
  193. data/vendor/assets/bower_components/bootstrap/less/print.less +101 -0
  194. data/vendor/assets/bower_components/bootstrap/less/progress-bars.less +87 -0
  195. data/vendor/assets/bower_components/bootstrap/less/responsive-embed.less +35 -0
  196. data/vendor/assets/bower_components/bootstrap/less/responsive-utilities.less +194 -0
  197. data/vendor/assets/bower_components/bootstrap/less/scaffolding.less +161 -0
  198. data/vendor/assets/bower_components/bootstrap/less/tables.less +234 -0
  199. data/vendor/assets/bower_components/bootstrap/less/theme.less +291 -0
  200. data/vendor/assets/bower_components/bootstrap/less/thumbnails.less +36 -0
  201. data/vendor/assets/bower_components/bootstrap/less/tooltip.less +101 -0
  202. data/vendor/assets/bower_components/bootstrap/less/type.less +302 -0
  203. data/vendor/assets/bower_components/bootstrap/less/utilities.less +55 -0
  204. data/vendor/assets/bower_components/bootstrap/less/variables.less +869 -0
  205. data/vendor/assets/bower_components/bootstrap/less/wells.less +29 -0
  206. data/vendor/assets/bower_components/bootstrap/nuget/MyGet.ps1 +8 -0
  207. data/vendor/assets/bower_components/bootstrap/nuget/bootstrap.less.nuspec +28 -0
  208. data/vendor/assets/bower_components/bootstrap/nuget/bootstrap.nuspec +28 -0
  209. data/vendor/assets/bower_components/bootstrap/package.js +32 -0
  210. data/vendor/assets/bower_components/bootstrap/package.json +87 -0
  211. data/vendor/assets/bower_components/jquery/AUTHORS.txt +275 -0
  212. data/vendor/assets/bower_components/jquery/LICENSE.txt +36 -0
  213. data/vendor/assets/bower_components/jquery/README.md +5 -0
  214. data/vendor/assets/bower_components/jquery/bower.json +14 -0
  215. data/vendor/assets/bower_components/jquery/dist/jquery.js +9831 -0
  216. data/vendor/assets/bower_components/jquery/dist/jquery.min.js +4 -0
  217. data/vendor/assets/bower_components/jquery/dist/jquery.min.map +1 -0
  218. data/vendor/assets/bower_components/jquery/src/ajax.js +845 -0
  219. data/vendor/assets/bower_components/jquery/src/ajax/jsonp.js +100 -0
  220. data/vendor/assets/bower_components/jquery/src/ajax/load.js +83 -0
  221. data/vendor/assets/bower_components/jquery/src/ajax/parseJSON.js +13 -0
  222. data/vendor/assets/bower_components/jquery/src/ajax/parseXML.js +27 -0
  223. data/vendor/assets/bower_components/jquery/src/ajax/script.js +68 -0
  224. data/vendor/assets/bower_components/jquery/src/ajax/var/location.js +3 -0
  225. data/vendor/assets/bower_components/jquery/src/ajax/var/nonce.js +5 -0
  226. data/vendor/assets/bower_components/jquery/src/ajax/var/rquery.js +3 -0
  227. data/vendor/assets/bower_components/jquery/src/ajax/xhr.js +167 -0
  228. data/vendor/assets/bower_components/jquery/src/attributes.js +11 -0
  229. data/vendor/assets/bower_components/jquery/src/attributes/attr.js +142 -0
  230. data/vendor/assets/bower_components/jquery/src/attributes/classes.js +177 -0
  231. data/vendor/assets/bower_components/jquery/src/attributes/prop.js +109 -0
  232. data/vendor/assets/bower_components/jquery/src/attributes/support.js +36 -0
  233. data/vendor/assets/bower_components/jquery/src/attributes/val.js +170 -0
  234. data/vendor/assets/bower_components/jquery/src/callbacks.js +232 -0
  235. data/vendor/assets/bower_components/jquery/src/core.js +489 -0
  236. data/vendor/assets/bower_components/jquery/src/core/access.js +65 -0
  237. data/vendor/assets/bower_components/jquery/src/core/init.js +134 -0
  238. data/vendor/assets/bower_components/jquery/src/core/parseHTML.js +49 -0
  239. data/vendor/assets/bower_components/jquery/src/core/ready.js +103 -0
  240. data/vendor/assets/bower_components/jquery/src/core/support.js +18 -0
  241. data/vendor/assets/bower_components/jquery/src/core/var/rsingleTag.js +5 -0
  242. data/vendor/assets/bower_components/jquery/src/css.js +515 -0
  243. data/vendor/assets/bower_components/jquery/src/css/addGetHookIf.js +24 -0
  244. data/vendor/assets/bower_components/jquery/src/css/adjustCSS.js +65 -0
  245. data/vendor/assets/bower_components/jquery/src/css/curCSS.js +57 -0
  246. data/vendor/assets/bower_components/jquery/src/css/defaultDisplay.js +72 -0
  247. data/vendor/assets/bower_components/jquery/src/css/hiddenVisibleSelectors.js +18 -0
  248. data/vendor/assets/bower_components/jquery/src/css/showHide.js +48 -0
  249. data/vendor/assets/bower_components/jquery/src/css/support.js +121 -0
  250. data/vendor/assets/bower_components/jquery/src/css/var/cssExpand.js +3 -0
  251. data/vendor/assets/bower_components/jquery/src/css/var/getStyles.js +15 -0
  252. data/vendor/assets/bower_components/jquery/src/css/var/isHidden.js +16 -0
  253. data/vendor/assets/bower_components/jquery/src/css/var/rmargin.js +3 -0
  254. data/vendor/assets/bower_components/jquery/src/css/var/rnumnonpx.js +5 -0
  255. data/vendor/assets/bower_components/jquery/src/css/var/swap.js +24 -0
  256. data/vendor/assets/bower_components/jquery/src/data.js +187 -0
  257. data/vendor/assets/bower_components/jquery/src/data/Data.js +200 -0
  258. data/vendor/assets/bower_components/jquery/src/data/accepts.js +20 -0
  259. data/vendor/assets/bower_components/jquery/src/data/support.js +23 -0
  260. data/vendor/assets/bower_components/jquery/src/data/var/acceptData.js +18 -0
  261. data/vendor/assets/bower_components/jquery/src/data/var/dataPriv.js +5 -0
  262. data/vendor/assets/bower_components/jquery/src/data/var/dataUser.js +5 -0
  263. data/vendor/assets/bower_components/jquery/src/deferred.js +158 -0
  264. data/vendor/assets/bower_components/jquery/src/deprecated.js +32 -0
  265. data/vendor/assets/bower_components/jquery/src/dimensions.js +54 -0
  266. data/vendor/assets/bower_components/jquery/src/effects.js +629 -0
  267. data/vendor/assets/bower_components/jquery/src/effects/Tween.js +121 -0
  268. data/vendor/assets/bower_components/jquery/src/effects/animatedSelector.js +13 -0
  269. data/vendor/assets/bower_components/jquery/src/effects/support.js +58 -0
  270. data/vendor/assets/bower_components/jquery/src/event.js +710 -0
  271. data/vendor/assets/bower_components/jquery/src/event/ajax.js +20 -0
  272. data/vendor/assets/bower_components/jquery/src/event/alias.js +27 -0
  273. data/vendor/assets/bower_components/jquery/src/event/focusin.js +53 -0
  274. data/vendor/assets/bower_components/jquery/src/event/support.js +9 -0
  275. data/vendor/assets/bower_components/jquery/src/event/trigger.js +199 -0
  276. data/vendor/assets/bower_components/jquery/src/exports/amd.js +24 -0
  277. data/vendor/assets/bower_components/jquery/src/exports/global.js +26 -0
  278. data/vendor/assets/bower_components/jquery/src/intro.js +44 -0
  279. data/vendor/assets/bower_components/jquery/src/jquery.js +37 -0
  280. data/vendor/assets/bower_components/jquery/src/manipulation.js +481 -0
  281. data/vendor/assets/bower_components/jquery/src/manipulation/_evalUrl.js +20 -0
  282. data/vendor/assets/bower_components/jquery/src/manipulation/buildFragment.js +102 -0
  283. data/vendor/assets/bower_components/jquery/src/manipulation/createSafeFragment.js +20 -0
  284. data/vendor/assets/bower_components/jquery/src/manipulation/getAll.js +21 -0
  285. data/vendor/assets/bower_components/jquery/src/manipulation/setGlobalEval.js +20 -0
  286. data/vendor/assets/bower_components/jquery/src/manipulation/support.js +33 -0
  287. data/vendor/assets/bower_components/jquery/src/manipulation/var/nodeNames.js +5 -0
  288. data/vendor/assets/bower_components/jquery/src/manipulation/var/rcheckableType.js +3 -0
  289. data/vendor/assets/bower_components/jquery/src/manipulation/var/rleadingWhitespace.js +3 -0
  290. data/vendor/assets/bower_components/jquery/src/manipulation/var/rscriptType.js +3 -0
  291. data/vendor/assets/bower_components/jquery/src/manipulation/var/rtagName.js +3 -0
  292. data/vendor/assets/bower_components/jquery/src/manipulation/wrapMap.js +27 -0
  293. data/vendor/assets/bower_components/jquery/src/offset.js +221 -0
  294. data/vendor/assets/bower_components/jquery/src/outro.js +2 -0
  295. data/vendor/assets/bower_components/jquery/src/queue.js +143 -0
  296. data/vendor/assets/bower_components/jquery/src/queue/delay.js +22 -0
  297. data/vendor/assets/bower_components/jquery/src/selector-native.js +211 -0
  298. data/vendor/assets/bower_components/jquery/src/selector-sizzle.js +14 -0
  299. data/vendor/assets/bower_components/jquery/src/selector.js +1 -0
  300. data/vendor/assets/bower_components/jquery/src/serialize.js +125 -0
  301. data/vendor/assets/bower_components/jquery/src/support.js +63 -0
  302. data/vendor/assets/bower_components/jquery/src/traversing.js +175 -0
  303. data/vendor/assets/bower_components/jquery/src/traversing/findFilter.js +100 -0
  304. data/vendor/assets/bower_components/jquery/src/traversing/var/dir.js +20 -0
  305. data/vendor/assets/bower_components/jquery/src/traversing/var/rneedsContext.js +6 -0
  306. data/vendor/assets/bower_components/jquery/src/traversing/var/siblings.js +15 -0
  307. data/vendor/assets/bower_components/jquery/src/var/arr.js +3 -0
  308. data/vendor/assets/bower_components/jquery/src/var/class2type.js +5 -0
  309. data/vendor/assets/bower_components/jquery/src/var/concat.js +5 -0
  310. data/vendor/assets/bower_components/jquery/src/var/deletedIds.js +3 -0
  311. data/vendor/assets/bower_components/jquery/src/var/document.js +3 -0
  312. data/vendor/assets/bower_components/jquery/src/var/documentElement.js +5 -0
  313. data/vendor/assets/bower_components/jquery/src/var/hasOwn.js +5 -0
  314. data/vendor/assets/bower_components/jquery/src/var/indexOf.js +5 -0
  315. data/vendor/assets/bower_components/jquery/src/var/pnum.js +3 -0
  316. data/vendor/assets/bower_components/jquery/src/var/push.js +5 -0
  317. data/vendor/assets/bower_components/jquery/src/var/rcssNum.js +7 -0
  318. data/vendor/assets/bower_components/jquery/src/var/rnotwhite.js +3 -0
  319. data/vendor/assets/bower_components/jquery/src/var/slice.js +5 -0
  320. data/vendor/assets/bower_components/jquery/src/var/support.js +5 -0
  321. data/vendor/assets/bower_components/jquery/src/var/toString.js +5 -0
  322. data/vendor/assets/bower_components/jquery/src/wrap.js +79 -0
  323. data/vendor/assets/bower_components/jsoneditor/CONTRIBUTING.md +15 -0
  324. data/vendor/assets/bower_components/jsoneditor/HISTORY.md +457 -0
  325. data/vendor/assets/bower_components/jsoneditor/LICENSE +176 -0
  326. data/vendor/assets/bower_components/jsoneditor/NOTICE +17 -0
  327. data/vendor/assets/bower_components/jsoneditor/README.md +155 -0
  328. data/vendor/assets/bower_components/jsoneditor/bower.json +32 -0
  329. data/vendor/assets/bower_components/jsoneditor/dist/img/jsoneditor-icons.svg +893 -0
  330. data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor-minimalist.js +9418 -0
  331. data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor-minimalist.map +1 -0
  332. data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor-minimalist.min.js +35 -0
  333. data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor.css.less +879 -0
  334. data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor.js +35095 -0
  335. data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor.map +1 -0
  336. data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor.min.css +1 -0
  337. data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor.min.js +48 -0
  338. data/vendor/assets/bower_components/jsoneditor/dist/which files do I need.md +41 -0
  339. data/vendor/assets/bower_components/jsoneditor/docs/api.md +286 -0
  340. data/vendor/assets/bower_components/jsoneditor/docs/shortcut_keys.md +38 -0
  341. data/vendor/assets/bower_components/jsoneditor/docs/usage.md +117 -0
  342. data/vendor/assets/bower_components/jsoneditor/examples/01_basic_usage.html +49 -0
  343. data/vendor/assets/bower_components/jsoneditor/examples/02_viewer.html +44 -0
  344. data/vendor/assets/bower_components/jsoneditor/examples/03_switch_mode.html +66 -0
  345. data/vendor/assets/bower_components/jsoneditor/examples/04_load_and_save.html +62 -0
  346. data/vendor/assets/bower_components/jsoneditor/examples/05_custom_fields_editable.html +63 -0
  347. data/vendor/assets/bower_components/jsoneditor/examples/06_custom_styling.html +51 -0
  348. data/vendor/assets/bower_components/jsoneditor/examples/07_json_schema_validation.html +71 -0
  349. data/vendor/assets/bower_components/jsoneditor/examples/css/darktheme.css +76 -0
  350. data/vendor/assets/bower_components/jsoneditor/examples/requirejs_demo/requirejs_demo.html +21 -0
  351. data/vendor/assets/bower_components/jsoneditor/examples/requirejs_demo/scripts/main.js +25 -0
  352. data/vendor/assets/bower_components/jsoneditor/examples/requirejs_demo/scripts/require.js +36 -0
  353. data/vendor/assets/bower_components/jsoneditor/index.js +1 -0
  354. data/vendor/assets/bower_components/jsoneditor/package.json +41 -0
  355. data/vendor/assets/bower_components/jsoneditor/src/css/contextmenu.css +233 -0
  356. data/vendor/assets/bower_components/jsoneditor/src/css/img/description.txt +14 -0
  357. data/vendor/assets/bower_components/jsoneditor/src/css/img/jsoneditor-icons.svg +893 -0
  358. data/vendor/assets/bower_components/jsoneditor/src/css/jsoneditor.css +433 -0
  359. data/vendor/assets/bower_components/jsoneditor/src/css/menu.css +110 -0
  360. data/vendor/assets/bower_components/jsoneditor/src/css/searchbox.css +75 -0
  361. data/vendor/assets/bower_components/jsoneditor/src/docs/which files do I need.md +41 -0
  362. data/vendor/assets/bower_components/jsoneditor/src/js/ContextMenu.js +455 -0
  363. data/vendor/assets/bower_components/jsoneditor/src/js/Highlighter.js +84 -0
  364. data/vendor/assets/bower_components/jsoneditor/src/js/History.js +252 -0
  365. data/vendor/assets/bower_components/jsoneditor/src/js/JSONEditor.js +371 -0
  366. data/vendor/assets/bower_components/jsoneditor/src/js/Node.js +3400 -0
  367. data/vendor/assets/bower_components/jsoneditor/src/js/SearchBox.js +295 -0
  368. data/vendor/assets/bower_components/jsoneditor/src/js/ace/index.js +9 -0
  369. data/vendor/assets/bower_components/jsoneditor/src/js/ace/theme-jsoneditor.js +144 -0
  370. data/vendor/assets/bower_components/jsoneditor/src/js/appendNodeFactory.js +226 -0
  371. data/vendor/assets/bower_components/jsoneditor/src/js/assets/jsonlint/README.md +15 -0
  372. data/vendor/assets/bower_components/jsoneditor/src/js/assets/jsonlint/jsonlint.js +418 -0
  373. data/vendor/assets/bower_components/jsoneditor/src/js/header.js +29 -0
  374. data/vendor/assets/bower_components/jsoneditor/src/js/modeswitcher.js +105 -0
  375. data/vendor/assets/bower_components/jsoneditor/src/js/textmode.js +474 -0
  376. data/vendor/assets/bower_components/jsoneditor/src/js/treemode.js +1150 -0
  377. data/vendor/assets/bower_components/jsoneditor/src/js/util.js +765 -0
  378. data/vendor/assets/bower_components/notifyjs/CHANGES.md +7 -0
  379. data/vendor/assets/bower_components/notifyjs/README.md +31 -0
  380. data/vendor/assets/bower_components/notifyjs/bower.json +14 -0
  381. data/vendor/assets/bower_components/notifyjs/dist/notify.js +586 -0
  382. data/vendor/assets/bower_components/notifyjs/dist/styles/metro/notify-metro.css +40 -0
  383. data/vendor/assets/bower_components/notifyjs/dist/styles/metro/notify-metro.js +39 -0
  384. data/vendor/assets/bower_components/notifyjs/examples/classes.html +49 -0
  385. data/vendor/assets/bower_components/notifyjs/examples/images/Mail.png +0 -0
  386. data/vendor/assets/bower_components/notifyjs/examples/inlines.html +111 -0
  387. data/vendor/assets/bower_components/notifyjs/examples/metro.html +37 -0
  388. data/vendor/assets/bower_components/notifyjs/examples/multi-text.html +55 -0
  389. data/vendor/assets/bower_components/notifyjs/examples/position.html +42 -0
  390. data/vendor/assets/bower_components/notifyjs/notify.jquery.json +28 -0
  391. data/vendor/assets/bower_components/notifyjs/package.json +18 -0
  392. data/vendor/assets/bower_components/vue/LICENSE +21 -0
  393. data/vendor/assets/bower_components/vue/bower.json +19 -0
  394. data/vendor/assets/bower_components/vue/dist/vue.js +10198 -0
  395. data/vendor/assets/bower_components/vue/dist/vue.min.js +8 -0
  396. data/vendor/assets/bower_components/vue/src/api/child.js +49 -0
  397. data/vendor/assets/bower_components/vue/src/api/data.js +159 -0
  398. data/vendor/assets/bower_components/vue/src/api/dom.js +226 -0
  399. data/vendor/assets/bower_components/vue/src/api/events.js +174 -0
  400. data/vendor/assets/bower_components/vue/src/api/global.js +129 -0
  401. data/vendor/assets/bower_components/vue/src/api/lifecycle.js +68 -0
  402. data/vendor/assets/bower_components/vue/src/batcher.js +98 -0
  403. data/vendor/assets/bower_components/vue/src/cache.js +112 -0
  404. data/vendor/assets/bower_components/vue/src/compiler/compile-props.js +183 -0
  405. data/vendor/assets/bower_components/vue/src/compiler/compile.js +630 -0
  406. data/vendor/assets/bower_components/vue/src/compiler/index.js +4 -0
  407. data/vendor/assets/bower_components/vue/src/compiler/transclude.js +144 -0
  408. data/vendor/assets/bower_components/vue/src/config.js +124 -0
  409. data/vendor/assets/bower_components/vue/src/directive.js +254 -0
  410. data/vendor/assets/bower_components/vue/src/directives/attr.js +59 -0
  411. data/vendor/assets/bower_components/vue/src/directives/class.js +70 -0
  412. data/vendor/assets/bower_components/vue/src/directives/cloak.js +10 -0
  413. data/vendor/assets/bower_components/vue/src/directives/component.js +344 -0
  414. data/vendor/assets/bower_components/vue/src/directives/el.js +12 -0
  415. data/vendor/assets/bower_components/vue/src/directives/html.js +40 -0
  416. data/vendor/assets/bower_components/vue/src/directives/if.js +125 -0
  417. data/vendor/assets/bower_components/vue/src/directives/index.js +24 -0
  418. data/vendor/assets/bower_components/vue/src/directives/model/checkbox.js +42 -0
  419. data/vendor/assets/bower_components/vue/src/directives/model/index.js +82 -0
  420. data/vendor/assets/bower_components/vue/src/directives/model/radio.js +33 -0
  421. data/vendor/assets/bower_components/vue/src/directives/model/select.js +236 -0
  422. data/vendor/assets/bower_components/vue/src/directives/model/text.js +132 -0
  423. data/vendor/assets/bower_components/vue/src/directives/on.js +59 -0
  424. data/vendor/assets/bower_components/vue/src/directives/prop.js +62 -0
  425. data/vendor/assets/bower_components/vue/src/directives/ref.js +22 -0
  426. data/vendor/assets/bower_components/vue/src/directives/repeat.js +770 -0
  427. data/vendor/assets/bower_components/vue/src/directives/show.js +8 -0
  428. data/vendor/assets/bower_components/vue/src/directives/style.js +110 -0
  429. data/vendor/assets/bower_components/vue/src/directives/text.js +14 -0
  430. data/vendor/assets/bower_components/vue/src/directives/transition.js +26 -0
  431. data/vendor/assets/bower_components/vue/src/element-directives/content.js +111 -0
  432. data/vendor/assets/bower_components/vue/src/element-directives/index.js +2 -0
  433. data/vendor/assets/bower_components/vue/src/element-directives/partial.js +73 -0
  434. data/vendor/assets/bower_components/vue/src/filters/array-filters.js +97 -0
  435. data/vendor/assets/bower_components/vue/src/filters/index.js +146 -0
  436. data/vendor/assets/bower_components/vue/src/instance/compile.js +200 -0
  437. data/vendor/assets/bower_components/vue/src/instance/events.js +139 -0
  438. data/vendor/assets/bower_components/vue/src/instance/init.js +89 -0
  439. data/vendor/assets/bower_components/vue/src/instance/misc.js +93 -0
  440. data/vendor/assets/bower_components/vue/src/instance/scope.js +282 -0
  441. data/vendor/assets/bower_components/vue/src/observer/array.js +98 -0
  442. data/vendor/assets/bower_components/vue/src/observer/dep.js +61 -0
  443. data/vendor/assets/bower_components/vue/src/observer/index.js +234 -0
  444. data/vendor/assets/bower_components/vue/src/observer/object.js +82 -0
  445. data/vendor/assets/bower_components/vue/src/parsers/directive.js +180 -0
  446. data/vendor/assets/bower_components/vue/src/parsers/expression.js +264 -0
  447. data/vendor/assets/bower_components/vue/src/parsers/path.js +348 -0
  448. data/vendor/assets/bower_components/vue/src/parsers/template.js +288 -0
  449. data/vendor/assets/bower_components/vue/src/parsers/text.js +178 -0
  450. data/vendor/assets/bower_components/vue/src/transition/index.js +128 -0
  451. data/vendor/assets/bower_components/vue/src/transition/queue.js +35 -0
  452. data/vendor/assets/bower_components/vue/src/transition/transition.js +357 -0
  453. data/vendor/assets/bower_components/vue/src/util/component.js +124 -0
  454. data/vendor/assets/bower_components/vue/src/util/debug.js +64 -0
  455. data/vendor/assets/bower_components/vue/src/util/dom.js +272 -0
  456. data/vendor/assets/bower_components/vue/src/util/env.js +85 -0
  457. data/vendor/assets/bower_components/vue/src/util/index.js +9 -0
  458. data/vendor/assets/bower_components/vue/src/util/lang.js +310 -0
  459. data/vendor/assets/bower_components/vue/src/util/options.js +357 -0
  460. data/vendor/assets/bower_components/vue/src/vue.js +89 -0
  461. data/vendor/assets/bower_components/vue/src/watcher.js +312 -0
  462. metadata +870 -0
@@ -0,0 +1,3400 @@
1
+ var ContextMenu = require('./ContextMenu');
2
+ var appendNodeFactory = require('./appendNodeFactory');
3
+ var util = require('./util');
4
+
5
+ /**
6
+ * @constructor Node
7
+ * Create a new Node
8
+ * @param {./treemode} editor
9
+ * @param {Object} [params] Can contain parameters:
10
+ * {string} field
11
+ * {boolean} fieldEditable
12
+ * {*} value
13
+ * {String} type Can have values 'auto', 'array',
14
+ * 'object', or 'string'.
15
+ */
16
+ function Node (editor, params) {
17
+ /** @type {./treemode} */
18
+ this.editor = editor;
19
+ this.dom = {};
20
+ this.expanded = false;
21
+
22
+ if(params && (params instanceof Object)) {
23
+ this.setField(params.field, params.fieldEditable);
24
+ this.setValue(params.value, params.type);
25
+ }
26
+ else {
27
+ this.setField('');
28
+ this.setValue(null);
29
+ }
30
+
31
+ this._debouncedOnChangeValue = util.debounce(this._onChangeValue.bind(this), Node.prototype.DEBOUNCE_INTERVAL);
32
+ this._debouncedOnChangeField = util.debounce(this._onChangeField.bind(this), Node.prototype.DEBOUNCE_INTERVAL);
33
+ }
34
+
35
+ // debounce interval for keyboard input in milliseconds
36
+ Node.prototype.DEBOUNCE_INTERVAL = 150;
37
+
38
+ /**
39
+ * Determine whether the field and/or value of this node are editable
40
+ * @private
41
+ */
42
+ Node.prototype._updateEditability = function () {
43
+ this.editable = {
44
+ field: true,
45
+ value: true
46
+ };
47
+
48
+ if (this.editor) {
49
+ this.editable.field = this.editor.options.mode === 'tree';
50
+ this.editable.value = this.editor.options.mode !== 'view';
51
+
52
+ if ((this.editor.options.mode === 'tree' || this.editor.options.mode === 'form') &&
53
+ (typeof this.editor.options.onEditable === 'function')) {
54
+ var editable = this.editor.options.onEditable({
55
+ field: this.field,
56
+ value: this.value,
57
+ path: this.getFieldsPath()
58
+ });
59
+
60
+ if (typeof editable === 'boolean') {
61
+ this.editable.field = editable;
62
+ this.editable.value = editable;
63
+ }
64
+ else {
65
+ if (typeof editable.field === 'boolean') this.editable.field = editable.field;
66
+ if (typeof editable.value === 'boolean') this.editable.value = editable.value;
67
+ }
68
+ }
69
+ }
70
+ };
71
+
72
+ /**
73
+ * Get the path of this node
74
+ * @return {String[]} Array containing the path to this node
75
+ */
76
+ Node.prototype.getFieldsPath = function () {
77
+ var node = this;
78
+ var path = [];
79
+ while (node) {
80
+ var field = node.field != undefined ? node.field : node.index;
81
+ if (field !== undefined) {
82
+ path.unshift(field);
83
+ }
84
+ node = node.parent;
85
+ }
86
+ return path;
87
+ };
88
+
89
+ /**
90
+ * Find a Node from a JSON path like '.items[3].name'
91
+ * @param {string} jsonPath
92
+ * @return {Node | null} Returns the Node when found, returns null if not found
93
+ */
94
+ Node.prototype.findNode = function (jsonPath) {
95
+ var path = util.parsePath(jsonPath);
96
+ var node = this;
97
+ while (node && path.length > 0) {
98
+ var prop = path.shift();
99
+ if (typeof prop === 'number') {
100
+ if (node.type !== 'array') {
101
+ throw new Error('Cannot get child node at index ' + prop + ': node is no array');
102
+ }
103
+ node = node.childs[prop];
104
+ }
105
+ else { // string
106
+ if (node.type !== 'object') {
107
+ throw new Error('Cannot get child node ' + prop + ': node is no object');
108
+ }
109
+ node = node.childs.filter(function (child) {
110
+ return child.field === prop;
111
+ })[0];
112
+ }
113
+ }
114
+
115
+ return node;
116
+ };
117
+
118
+ /**
119
+ * Find all parents of this node. The parents are ordered from root node towards
120
+ * the original node.
121
+ * @return {Array.<Node>}
122
+ */
123
+ Node.prototype.findParents = function () {
124
+ var parents = [];
125
+ var parent = this.parent;
126
+ while (parent) {
127
+ parents.unshift(parent);
128
+ parent = parent.parent;
129
+ }
130
+ return parents;
131
+ };
132
+
133
+ /**
134
+ *
135
+ * @param {{dataPath: string, keyword: string, message: string, params: Object, schemaPath: string} | null} error
136
+ * @param {Node} [child] When this is the error of a parent node, pointing
137
+ * to an invalid child node, the child node itself
138
+ * can be provided. If provided, clicking the error
139
+ * icon will set focus to the invalid child node.
140
+ */
141
+ Node.prototype.setError = function (error, child) {
142
+ // ensure the dom exists
143
+ this.getDom();
144
+
145
+ this.error = error;
146
+ var tdError = this.dom.tdError;
147
+ if (error) {
148
+ if (!tdError) {
149
+ tdError = document.createElement('td');
150
+ this.dom.tdError = tdError;
151
+ this.dom.tdValue.parentNode.appendChild(tdError);
152
+ }
153
+
154
+ var popover = document.createElement('div');
155
+ popover.className = 'jsoneditor-popover jsoneditor-right';
156
+ popover.appendChild(document.createTextNode(error.message));
157
+
158
+ var button = document.createElement('button');
159
+ button.className = 'jsoneditor-schema-error';
160
+ button.appendChild(popover);
161
+
162
+ // update the direction of the popover
163
+ button.onmouseover = button.onfocus = function updateDirection() {
164
+ var directions = ['right', 'above', 'below', 'left'];
165
+ for (var i = 0; i < directions.length; i++) {
166
+ var direction = directions[i];
167
+ popover.className = 'jsoneditor-popover jsoneditor-' + direction;
168
+
169
+ var contentRect = this.editor.content.getBoundingClientRect();
170
+ var popoverRect = popover.getBoundingClientRect();
171
+ var margin = 20; // account for a scroll bar
172
+ var fit = util.insideRect(contentRect, popoverRect, margin);
173
+
174
+ if (fit) {
175
+ break;
176
+ }
177
+ }
178
+ }.bind(this);
179
+
180
+ // when clicking the error icon, expand all nodes towards the invalid
181
+ // child node, and set focus to the child node
182
+ if (child) {
183
+ button.onclick = function showInvalidNode() {
184
+ child.findParents().forEach(function (parent) {
185
+ parent.expand(false);
186
+ });
187
+
188
+ child.scrollTo(function () {
189
+ child.focus();
190
+ });
191
+ };
192
+ }
193
+
194
+ // apply the error message to the node
195
+ while (tdError.firstChild) {
196
+ tdError.removeChild(tdError.firstChild);
197
+ }
198
+ tdError.appendChild(button);
199
+ }
200
+ else {
201
+ if (tdError) {
202
+ this.dom.tdError.parentNode.removeChild(this.dom.tdError);
203
+ delete this.dom.tdError;
204
+ }
205
+ }
206
+ };
207
+
208
+ /**
209
+ * Get the index of this node: the index in the list of childs where this
210
+ * node is part of
211
+ * @return {number} Returns the index, or -1 if this is the root node
212
+ */
213
+ Node.prototype.getIndex = function () {
214
+ return this.parent ? this.parent.childs.indexOf(this) : -1;
215
+ };
216
+
217
+ /**
218
+ * Set parent node
219
+ * @param {Node} parent
220
+ */
221
+ Node.prototype.setParent = function(parent) {
222
+ this.parent = parent;
223
+ };
224
+
225
+ /**
226
+ * Set field
227
+ * @param {String} field
228
+ * @param {boolean} [fieldEditable]
229
+ */
230
+ Node.prototype.setField = function(field, fieldEditable) {
231
+ this.field = field;
232
+ this.previousField = field;
233
+ this.fieldEditable = (fieldEditable === true);
234
+ };
235
+
236
+ /**
237
+ * Get field
238
+ * @return {String}
239
+ */
240
+ Node.prototype.getField = function() {
241
+ if (this.field === undefined) {
242
+ this._getDomField();
243
+ }
244
+
245
+ return this.field;
246
+ };
247
+
248
+ /**
249
+ * Set value. Value is a JSON structure or an element String, Boolean, etc.
250
+ * @param {*} value
251
+ * @param {String} [type] Specify the type of the value. Can be 'auto',
252
+ * 'array', 'object', or 'string'
253
+ */
254
+ Node.prototype.setValue = function(value, type) {
255
+ var childValue, child;
256
+
257
+ // first clear all current childs (if any)
258
+ var childs = this.childs;
259
+ if (childs) {
260
+ while (childs.length) {
261
+ this.removeChild(childs[0]);
262
+ }
263
+ }
264
+
265
+ // TODO: remove the DOM of this Node
266
+
267
+ this.type = this._getType(value);
268
+
269
+ // check if type corresponds with the provided type
270
+ if (type && type != this.type) {
271
+ if (type == 'string' && this.type == 'auto') {
272
+ this.type = type;
273
+ }
274
+ else {
275
+ throw new Error('Type mismatch: ' +
276
+ 'cannot cast value of type "' + this.type +
277
+ ' to the specified type "' + type + '"');
278
+ }
279
+ }
280
+
281
+ if (this.type == 'array') {
282
+ // array
283
+ this.childs = [];
284
+ for (var i = 0, iMax = value.length; i < iMax; i++) {
285
+ childValue = value[i];
286
+ if (childValue !== undefined && !(childValue instanceof Function)) {
287
+ // ignore undefined and functions
288
+ child = new Node(this.editor, {
289
+ value: childValue
290
+ });
291
+ this.appendChild(child);
292
+ }
293
+ }
294
+ this.value = '';
295
+ }
296
+ else if (this.type == 'object') {
297
+ // object
298
+ this.childs = [];
299
+ for (var childField in value) {
300
+ if (value.hasOwnProperty(childField)) {
301
+ childValue = value[childField];
302
+ if (childValue !== undefined && !(childValue instanceof Function)) {
303
+ // ignore undefined and functions
304
+ child = new Node(this.editor, {
305
+ field: childField,
306
+ value: childValue
307
+ });
308
+ this.appendChild(child);
309
+ }
310
+ }
311
+ }
312
+ this.value = '';
313
+ }
314
+ else {
315
+ // value
316
+ this.childs = undefined;
317
+ this.value = value;
318
+ /* TODO
319
+ if (typeof(value) == 'string') {
320
+ var escValue = JSON.stringify(value);
321
+ this.value = escValue.substring(1, escValue.length - 1);
322
+ console.log('check', value, this.value);
323
+ }
324
+ else {
325
+ this.value = value;
326
+ }
327
+ */
328
+ }
329
+
330
+ this.previousValue = this.value;
331
+ };
332
+
333
+ /**
334
+ * Get value. Value is a JSON structure
335
+ * @return {*} value
336
+ */
337
+ Node.prototype.getValue = function() {
338
+ //var childs, i, iMax;
339
+
340
+ if (this.type == 'array') {
341
+ var arr = [];
342
+ this.childs.forEach (function (child) {
343
+ arr.push(child.getValue());
344
+ });
345
+ return arr;
346
+ }
347
+ else if (this.type == 'object') {
348
+ var obj = {};
349
+ this.childs.forEach (function (child) {
350
+ obj[child.getField()] = child.getValue();
351
+ });
352
+ return obj;
353
+ }
354
+ else {
355
+ if (this.value === undefined) {
356
+ this._getDomValue();
357
+ }
358
+
359
+ return this.value;
360
+ }
361
+ };
362
+
363
+ /**
364
+ * Get the nesting level of this node
365
+ * @return {Number} level
366
+ */
367
+ Node.prototype.getLevel = function() {
368
+ return (this.parent ? this.parent.getLevel() + 1 : 0);
369
+ };
370
+
371
+ /**
372
+ * Get path of the root node till the current node
373
+ * @return {Node[]} Returns an array with nodes
374
+ */
375
+ Node.prototype.getPath = function() {
376
+ var path = this.parent ? this.parent.getPath() : [];
377
+ path.push(this);
378
+ return path;
379
+ };
380
+
381
+ /**
382
+ * Create a clone of a node
383
+ * The complete state of a clone is copied, including whether it is expanded or
384
+ * not. The DOM elements are not cloned.
385
+ * @return {Node} clone
386
+ */
387
+ Node.prototype.clone = function() {
388
+ var clone = new Node(this.editor);
389
+ clone.type = this.type;
390
+ clone.field = this.field;
391
+ clone.fieldInnerText = this.fieldInnerText;
392
+ clone.fieldEditable = this.fieldEditable;
393
+ clone.value = this.value;
394
+ clone.valueInnerText = this.valueInnerText;
395
+ clone.expanded = this.expanded;
396
+
397
+ if (this.childs) {
398
+ // an object or array
399
+ var cloneChilds = [];
400
+ this.childs.forEach(function (child) {
401
+ var childClone = child.clone();
402
+ childClone.setParent(clone);
403
+ cloneChilds.push(childClone);
404
+ });
405
+ clone.childs = cloneChilds;
406
+ }
407
+ else {
408
+ // a value
409
+ clone.childs = undefined;
410
+ }
411
+
412
+ return clone;
413
+ };
414
+
415
+ /**
416
+ * Expand this node and optionally its childs.
417
+ * @param {boolean} [recurse] Optional recursion, true by default. When
418
+ * true, all childs will be expanded recursively
419
+ */
420
+ Node.prototype.expand = function(recurse) {
421
+ if (!this.childs) {
422
+ return;
423
+ }
424
+
425
+ // set this node expanded
426
+ this.expanded = true;
427
+ if (this.dom.expand) {
428
+ this.dom.expand.className = 'jsoneditor-expanded';
429
+ }
430
+
431
+ this.showChilds();
432
+
433
+ if (recurse !== false) {
434
+ this.childs.forEach(function (child) {
435
+ child.expand(recurse);
436
+ });
437
+ }
438
+ };
439
+
440
+ /**
441
+ * Collapse this node and optionally its childs.
442
+ * @param {boolean} [recurse] Optional recursion, true by default. When
443
+ * true, all childs will be collapsed recursively
444
+ */
445
+ Node.prototype.collapse = function(recurse) {
446
+ if (!this.childs) {
447
+ return;
448
+ }
449
+
450
+ this.hideChilds();
451
+
452
+ // collapse childs in case of recurse
453
+ if (recurse !== false) {
454
+ this.childs.forEach(function (child) {
455
+ child.collapse(recurse);
456
+ });
457
+
458
+ }
459
+
460
+ // make this node collapsed
461
+ if (this.dom.expand) {
462
+ this.dom.expand.className = 'jsoneditor-collapsed';
463
+ }
464
+ this.expanded = false;
465
+ };
466
+
467
+ /**
468
+ * Recursively show all childs when they are expanded
469
+ */
470
+ Node.prototype.showChilds = function() {
471
+ var childs = this.childs;
472
+ if (!childs) {
473
+ return;
474
+ }
475
+ if (!this.expanded) {
476
+ return;
477
+ }
478
+
479
+ var tr = this.dom.tr;
480
+ var table = tr ? tr.parentNode : undefined;
481
+ if (table) {
482
+ // show row with append button
483
+ var append = this.getAppend();
484
+ var nextTr = tr.nextSibling;
485
+ if (nextTr) {
486
+ table.insertBefore(append, nextTr);
487
+ }
488
+ else {
489
+ table.appendChild(append);
490
+ }
491
+
492
+ // show childs
493
+ this.childs.forEach(function (child) {
494
+ table.insertBefore(child.getDom(), append);
495
+ child.showChilds();
496
+ });
497
+ }
498
+ };
499
+
500
+ /**
501
+ * Hide the node with all its childs
502
+ */
503
+ Node.prototype.hide = function() {
504
+ var tr = this.dom.tr;
505
+ var table = tr ? tr.parentNode : undefined;
506
+ if (table) {
507
+ table.removeChild(tr);
508
+ }
509
+ this.hideChilds();
510
+ };
511
+
512
+
513
+ /**
514
+ * Recursively hide all childs
515
+ */
516
+ Node.prototype.hideChilds = function() {
517
+ var childs = this.childs;
518
+ if (!childs) {
519
+ return;
520
+ }
521
+ if (!this.expanded) {
522
+ return;
523
+ }
524
+
525
+ // hide append row
526
+ var append = this.getAppend();
527
+ if (append.parentNode) {
528
+ append.parentNode.removeChild(append);
529
+ }
530
+
531
+ // hide childs
532
+ this.childs.forEach(function (child) {
533
+ child.hide();
534
+ });
535
+ };
536
+
537
+
538
+ /**
539
+ * Add a new child to the node.
540
+ * Only applicable when Node value is of type array or object
541
+ * @param {Node} node
542
+ */
543
+ Node.prototype.appendChild = function(node) {
544
+ if (this._hasChilds()) {
545
+ // adjust the link to the parent
546
+ node.setParent(this);
547
+ node.fieldEditable = (this.type == 'object');
548
+ if (this.type == 'array') {
549
+ node.index = this.childs.length;
550
+ }
551
+ this.childs.push(node);
552
+
553
+ if (this.expanded) {
554
+ // insert into the DOM, before the appendRow
555
+ var newTr = node.getDom();
556
+ var appendTr = this.getAppend();
557
+ var table = appendTr ? appendTr.parentNode : undefined;
558
+ if (appendTr && table) {
559
+ table.insertBefore(newTr, appendTr);
560
+ }
561
+
562
+ node.showChilds();
563
+ }
564
+
565
+ this.updateDom({'updateIndexes': true});
566
+ node.updateDom({'recurse': true});
567
+ }
568
+ };
569
+
570
+
571
+ /**
572
+ * Move a node from its current parent to this node
573
+ * Only applicable when Node value is of type array or object
574
+ * @param {Node} node
575
+ * @param {Node} beforeNode
576
+ */
577
+ Node.prototype.moveBefore = function(node, beforeNode) {
578
+ if (this._hasChilds()) {
579
+ // create a temporary row, to prevent the scroll position from jumping
580
+ // when removing the node
581
+ var tbody = (this.dom.tr) ? this.dom.tr.parentNode : undefined;
582
+ if (tbody) {
583
+ var trTemp = document.createElement('tr');
584
+ trTemp.style.height = tbody.clientHeight + 'px';
585
+ tbody.appendChild(trTemp);
586
+ }
587
+
588
+ if (node.parent) {
589
+ node.parent.removeChild(node);
590
+ }
591
+
592
+ if (beforeNode instanceof AppendNode) {
593
+ this.appendChild(node);
594
+ }
595
+ else {
596
+ this.insertBefore(node, beforeNode);
597
+ }
598
+
599
+ if (tbody) {
600
+ tbody.removeChild(trTemp);
601
+ }
602
+ }
603
+ };
604
+
605
+ /**
606
+ * Move a node from its current parent to this node
607
+ * Only applicable when Node value is of type array or object.
608
+ * If index is out of range, the node will be appended to the end
609
+ * @param {Node} node
610
+ * @param {Number} index
611
+ */
612
+ Node.prototype.moveTo = function (node, index) {
613
+ if (node.parent == this) {
614
+ // same parent
615
+ var currentIndex = this.childs.indexOf(node);
616
+ if (currentIndex < index) {
617
+ // compensate the index for removal of the node itself
618
+ index++;
619
+ }
620
+ }
621
+
622
+ var beforeNode = this.childs[index] || this.append;
623
+ this.moveBefore(node, beforeNode);
624
+ };
625
+
626
+ /**
627
+ * Insert a new child before a given node
628
+ * Only applicable when Node value is of type array or object
629
+ * @param {Node} node
630
+ * @param {Node} beforeNode
631
+ */
632
+ Node.prototype.insertBefore = function(node, beforeNode) {
633
+ if (this._hasChilds()) {
634
+ if (beforeNode == this.append) {
635
+ // append to the child nodes
636
+
637
+ // adjust the link to the parent
638
+ node.setParent(this);
639
+ node.fieldEditable = (this.type == 'object');
640
+ this.childs.push(node);
641
+ }
642
+ else {
643
+ // insert before a child node
644
+ var index = this.childs.indexOf(beforeNode);
645
+ if (index == -1) {
646
+ throw new Error('Node not found');
647
+ }
648
+
649
+ // adjust the link to the parent
650
+ node.setParent(this);
651
+ node.fieldEditable = (this.type == 'object');
652
+ this.childs.splice(index, 0, node);
653
+ }
654
+
655
+ if (this.expanded) {
656
+ // insert into the DOM
657
+ var newTr = node.getDom();
658
+ var nextTr = beforeNode.getDom();
659
+ var table = nextTr ? nextTr.parentNode : undefined;
660
+ if (nextTr && table) {
661
+ table.insertBefore(newTr, nextTr);
662
+ }
663
+
664
+ node.showChilds();
665
+ }
666
+
667
+ this.updateDom({'updateIndexes': true});
668
+ node.updateDom({'recurse': true});
669
+ }
670
+ };
671
+
672
+ /**
673
+ * Insert a new child before a given node
674
+ * Only applicable when Node value is of type array or object
675
+ * @param {Node} node
676
+ * @param {Node} afterNode
677
+ */
678
+ Node.prototype.insertAfter = function(node, afterNode) {
679
+ if (this._hasChilds()) {
680
+ var index = this.childs.indexOf(afterNode);
681
+ var beforeNode = this.childs[index + 1];
682
+ if (beforeNode) {
683
+ this.insertBefore(node, beforeNode);
684
+ }
685
+ else {
686
+ this.appendChild(node);
687
+ }
688
+ }
689
+ };
690
+
691
+ /**
692
+ * Search in this node
693
+ * The node will be expanded when the text is found one of its childs, else
694
+ * it will be collapsed. Searches are case insensitive.
695
+ * @param {String} text
696
+ * @return {Node[]} results Array with nodes containing the search text
697
+ */
698
+ Node.prototype.search = function(text) {
699
+ var results = [];
700
+ var index;
701
+ var search = text ? text.toLowerCase() : undefined;
702
+
703
+ // delete old search data
704
+ delete this.searchField;
705
+ delete this.searchValue;
706
+
707
+ // search in field
708
+ if (this.field != undefined) {
709
+ var field = String(this.field).toLowerCase();
710
+ index = field.indexOf(search);
711
+ if (index != -1) {
712
+ this.searchField = true;
713
+ results.push({
714
+ 'node': this,
715
+ 'elem': 'field'
716
+ });
717
+ }
718
+
719
+ // update dom
720
+ this._updateDomField();
721
+ }
722
+
723
+ // search in value
724
+ if (this._hasChilds()) {
725
+ // array, object
726
+
727
+ // search the nodes childs
728
+ if (this.childs) {
729
+ var childResults = [];
730
+ this.childs.forEach(function (child) {
731
+ childResults = childResults.concat(child.search(text));
732
+ });
733
+ results = results.concat(childResults);
734
+ }
735
+
736
+ // update dom
737
+ if (search != undefined) {
738
+ var recurse = false;
739
+ if (childResults.length == 0) {
740
+ this.collapse(recurse);
741
+ }
742
+ else {
743
+ this.expand(recurse);
744
+ }
745
+ }
746
+ }
747
+ else {
748
+ // string, auto
749
+ if (this.value != undefined ) {
750
+ var value = String(this.value).toLowerCase();
751
+ index = value.indexOf(search);
752
+ if (index != -1) {
753
+ this.searchValue = true;
754
+ results.push({
755
+ 'node': this,
756
+ 'elem': 'value'
757
+ });
758
+ }
759
+ }
760
+
761
+ // update dom
762
+ this._updateDomValue();
763
+ }
764
+
765
+ return results;
766
+ };
767
+
768
+ /**
769
+ * Move the scroll position such that this node is in the visible area.
770
+ * The node will not get the focus
771
+ * @param {function(boolean)} [callback]
772
+ */
773
+ Node.prototype.scrollTo = function(callback) {
774
+ if (!this.dom.tr || !this.dom.tr.parentNode) {
775
+ // if the node is not visible, expand its parents
776
+ var parent = this.parent;
777
+ var recurse = false;
778
+ while (parent) {
779
+ parent.expand(recurse);
780
+ parent = parent.parent;
781
+ }
782
+ }
783
+
784
+ if (this.dom.tr && this.dom.tr.parentNode) {
785
+ this.editor.scrollTo(this.dom.tr.offsetTop, callback);
786
+ }
787
+ };
788
+
789
+
790
+ // stores the element name currently having the focus
791
+ Node.focusElement = undefined;
792
+
793
+ /**
794
+ * Set focus to this node
795
+ * @param {String} [elementName] The field name of the element to get the
796
+ * focus available values: 'drag', 'menu',
797
+ * 'expand', 'field', 'value' (default)
798
+ */
799
+ Node.prototype.focus = function(elementName) {
800
+ Node.focusElement = elementName;
801
+
802
+ if (this.dom.tr && this.dom.tr.parentNode) {
803
+ var dom = this.dom;
804
+
805
+ switch (elementName) {
806
+ case 'drag':
807
+ if (dom.drag) {
808
+ dom.drag.focus();
809
+ }
810
+ else {
811
+ dom.menu.focus();
812
+ }
813
+ break;
814
+
815
+ case 'menu':
816
+ dom.menu.focus();
817
+ break;
818
+
819
+ case 'expand':
820
+ if (this._hasChilds()) {
821
+ dom.expand.focus();
822
+ }
823
+ else if (dom.field && this.fieldEditable) {
824
+ dom.field.focus();
825
+ util.selectContentEditable(dom.field);
826
+ }
827
+ else if (dom.value && !this._hasChilds()) {
828
+ dom.value.focus();
829
+ util.selectContentEditable(dom.value);
830
+ }
831
+ else {
832
+ dom.menu.focus();
833
+ }
834
+ break;
835
+
836
+ case 'field':
837
+ if (dom.field && this.fieldEditable) {
838
+ dom.field.focus();
839
+ util.selectContentEditable(dom.field);
840
+ }
841
+ else if (dom.value && !this._hasChilds()) {
842
+ dom.value.focus();
843
+ util.selectContentEditable(dom.value);
844
+ }
845
+ else if (this._hasChilds()) {
846
+ dom.expand.focus();
847
+ }
848
+ else {
849
+ dom.menu.focus();
850
+ }
851
+ break;
852
+
853
+ case 'value':
854
+ default:
855
+ if (dom.value && !this._hasChilds()) {
856
+ dom.value.focus();
857
+ util.selectContentEditable(dom.value);
858
+ }
859
+ else if (dom.field && this.fieldEditable) {
860
+ dom.field.focus();
861
+ util.selectContentEditable(dom.field);
862
+ }
863
+ else if (this._hasChilds()) {
864
+ dom.expand.focus();
865
+ }
866
+ else {
867
+ dom.menu.focus();
868
+ }
869
+ break;
870
+ }
871
+ }
872
+ };
873
+
874
+ /**
875
+ * Select all text in an editable div after a delay of 0 ms
876
+ * @param {Element} editableDiv
877
+ */
878
+ Node.select = function(editableDiv) {
879
+ setTimeout(function () {
880
+ util.selectContentEditable(editableDiv);
881
+ }, 0);
882
+ };
883
+
884
+ /**
885
+ * Update the values from the DOM field and value of this node
886
+ */
887
+ Node.prototype.blur = function() {
888
+ // retrieve the actual field and value from the DOM.
889
+ this._getDomValue(false);
890
+ this._getDomField(false);
891
+ };
892
+
893
+ /**
894
+ * Check if given node is a child. The method will check recursively to find
895
+ * this node.
896
+ * @param {Node} node
897
+ * @return {boolean} containsNode
898
+ */
899
+ Node.prototype.containsNode = function(node) {
900
+ if (this == node) {
901
+ return true;
902
+ }
903
+
904
+ var childs = this.childs;
905
+ if (childs) {
906
+ // TODO: use the js5 Array.some() here?
907
+ for (var i = 0, iMax = childs.length; i < iMax; i++) {
908
+ if (childs[i].containsNode(node)) {
909
+ return true;
910
+ }
911
+ }
912
+ }
913
+
914
+ return false;
915
+ };
916
+
917
+ /**
918
+ * Move given node into this node
919
+ * @param {Node} node the childNode to be moved
920
+ * @param {Node} beforeNode node will be inserted before given
921
+ * node. If no beforeNode is given,
922
+ * the node is appended at the end
923
+ * @private
924
+ */
925
+ Node.prototype._move = function(node, beforeNode) {
926
+ if (node == beforeNode) {
927
+ // nothing to do...
928
+ return;
929
+ }
930
+
931
+ // check if this node is not a child of the node to be moved here
932
+ if (node.containsNode(this)) {
933
+ throw new Error('Cannot move a field into a child of itself');
934
+ }
935
+
936
+ // remove the original node
937
+ if (node.parent) {
938
+ node.parent.removeChild(node);
939
+ }
940
+
941
+ // create a clone of the node
942
+ var clone = node.clone();
943
+ node.clearDom();
944
+
945
+ // insert or append the node
946
+ if (beforeNode) {
947
+ this.insertBefore(clone, beforeNode);
948
+ }
949
+ else {
950
+ this.appendChild(clone);
951
+ }
952
+
953
+ /* TODO: adjust the field name (to prevent equal field names)
954
+ if (this.type == 'object') {
955
+ }
956
+ */
957
+ };
958
+
959
+ /**
960
+ * Remove a child from the node.
961
+ * Only applicable when Node value is of type array or object
962
+ * @param {Node} node The child node to be removed;
963
+ * @return {Node | undefined} node The removed node on success,
964
+ * else undefined
965
+ */
966
+ Node.prototype.removeChild = function(node) {
967
+ if (this.childs) {
968
+ var index = this.childs.indexOf(node);
969
+
970
+ if (index != -1) {
971
+ node.hide();
972
+
973
+ // delete old search results
974
+ delete node.searchField;
975
+ delete node.searchValue;
976
+
977
+ var removedNode = this.childs.splice(index, 1)[0];
978
+ removedNode.parent = null;
979
+
980
+ this.updateDom({'updateIndexes': true});
981
+
982
+ return removedNode;
983
+ }
984
+ }
985
+
986
+ return undefined;
987
+ };
988
+
989
+ /**
990
+ * Remove a child node node from this node
991
+ * This method is equal to Node.removeChild, except that _remove fire an
992
+ * onChange event.
993
+ * @param {Node} node
994
+ * @private
995
+ */
996
+ Node.prototype._remove = function (node) {
997
+ this.removeChild(node);
998
+ };
999
+
1000
+ /**
1001
+ * Change the type of the value of this Node
1002
+ * @param {String} newType
1003
+ */
1004
+ Node.prototype.changeType = function (newType) {
1005
+ var oldType = this.type;
1006
+
1007
+ if (oldType == newType) {
1008
+ // type is not changed
1009
+ return;
1010
+ }
1011
+
1012
+ if ((newType == 'string' || newType == 'auto') &&
1013
+ (oldType == 'string' || oldType == 'auto')) {
1014
+ // this is an easy change
1015
+ this.type = newType;
1016
+ }
1017
+ else {
1018
+ // change from array to object, or from string/auto to object/array
1019
+ var table = this.dom.tr ? this.dom.tr.parentNode : undefined;
1020
+ var lastTr;
1021
+ if (this.expanded) {
1022
+ lastTr = this.getAppend();
1023
+ }
1024
+ else {
1025
+ lastTr = this.getDom();
1026
+ }
1027
+ var nextTr = (lastTr && lastTr.parentNode) ? lastTr.nextSibling : undefined;
1028
+
1029
+ // hide current field and all its childs
1030
+ this.hide();
1031
+ this.clearDom();
1032
+
1033
+ // adjust the field and the value
1034
+ this.type = newType;
1035
+
1036
+ // adjust childs
1037
+ if (newType == 'object') {
1038
+ if (!this.childs) {
1039
+ this.childs = [];
1040
+ }
1041
+
1042
+ this.childs.forEach(function (child, index) {
1043
+ child.clearDom();
1044
+ delete child.index;
1045
+ child.fieldEditable = true;
1046
+ if (child.field == undefined) {
1047
+ child.field = '';
1048
+ }
1049
+ });
1050
+
1051
+ if (oldType == 'string' || oldType == 'auto') {
1052
+ this.expanded = true;
1053
+ }
1054
+ }
1055
+ else if (newType == 'array') {
1056
+ if (!this.childs) {
1057
+ this.childs = [];
1058
+ }
1059
+
1060
+ this.childs.forEach(function (child, index) {
1061
+ child.clearDom();
1062
+ child.fieldEditable = false;
1063
+ child.index = index;
1064
+ });
1065
+
1066
+ if (oldType == 'string' || oldType == 'auto') {
1067
+ this.expanded = true;
1068
+ }
1069
+ }
1070
+ else {
1071
+ this.expanded = false;
1072
+ }
1073
+
1074
+ // create new DOM
1075
+ if (table) {
1076
+ if (nextTr) {
1077
+ table.insertBefore(this.getDom(), nextTr);
1078
+ }
1079
+ else {
1080
+ table.appendChild(this.getDom());
1081
+ }
1082
+ }
1083
+ this.showChilds();
1084
+ }
1085
+
1086
+ if (newType == 'auto' || newType == 'string') {
1087
+ // cast value to the correct type
1088
+ if (newType == 'string') {
1089
+ this.value = String(this.value);
1090
+ }
1091
+ else {
1092
+ this.value = this._stringCast(String(this.value));
1093
+ }
1094
+
1095
+ this.focus();
1096
+ }
1097
+
1098
+ this.updateDom({'updateIndexes': true});
1099
+ };
1100
+
1101
+ /**
1102
+ * Retrieve value from DOM
1103
+ * @param {boolean} [silent] If true (default), no errors will be thrown in
1104
+ * case of invalid data
1105
+ * @private
1106
+ */
1107
+ Node.prototype._getDomValue = function(silent) {
1108
+ if (this.dom.value && this.type != 'array' && this.type != 'object') {
1109
+ this.valueInnerText = util.getInnerText(this.dom.value);
1110
+ }
1111
+
1112
+ if (this.valueInnerText != undefined) {
1113
+ try {
1114
+ // retrieve the value
1115
+ var value;
1116
+ if (this.type == 'string') {
1117
+ value = this._unescapeHTML(this.valueInnerText);
1118
+ }
1119
+ else {
1120
+ var str = this._unescapeHTML(this.valueInnerText);
1121
+ value = this._stringCast(str);
1122
+ }
1123
+ if (value !== this.value) {
1124
+ this.value = value;
1125
+ this._debouncedOnChangeValue();
1126
+ }
1127
+ }
1128
+ catch (err) {
1129
+ this.value = undefined;
1130
+ // TODO: sent an action with the new, invalid value?
1131
+ if (silent !== true) {
1132
+ throw err;
1133
+ }
1134
+ }
1135
+ }
1136
+ };
1137
+
1138
+ /**
1139
+ * Handle a changed value
1140
+ * @private
1141
+ */
1142
+ Node.prototype._onChangeValue = function () {
1143
+ // get current selection, then override the range such that we can select
1144
+ // the added/removed text on undo/redo
1145
+ var oldSelection = this.editor.getSelection();
1146
+ if (oldSelection.range) {
1147
+ var undoDiff = util.textDiff(String(this.value), String(this.previousValue));
1148
+ oldSelection.range.startOffset = undoDiff.start;
1149
+ oldSelection.range.endOffset = undoDiff.end;
1150
+ }
1151
+ var newSelection = this.editor.getSelection();
1152
+ if (newSelection.range) {
1153
+ var redoDiff = util.textDiff(String(this.previousValue), String(this.value));
1154
+ newSelection.range.startOffset = redoDiff.start;
1155
+ newSelection.range.endOffset = redoDiff.end;
1156
+ }
1157
+
1158
+ this.editor._onAction('editValue', {
1159
+ node: this,
1160
+ oldValue: this.previousValue,
1161
+ newValue: this.value,
1162
+ oldSelection: oldSelection,
1163
+ newSelection: newSelection
1164
+ });
1165
+
1166
+ this.previousValue = this.value;
1167
+ };
1168
+
1169
+ /**
1170
+ * Handle a changed field
1171
+ * @private
1172
+ */
1173
+ Node.prototype._onChangeField = function () {
1174
+ // get current selection, then override the range such that we can select
1175
+ // the added/removed text on undo/redo
1176
+ var oldSelection = this.editor.getSelection();
1177
+ if (oldSelection.range) {
1178
+ var undoDiff = util.textDiff(this.field, this.previousField);
1179
+ oldSelection.range.startOffset = undoDiff.start;
1180
+ oldSelection.range.endOffset = undoDiff.end;
1181
+ }
1182
+ var newSelection = this.editor.getSelection();
1183
+ if (newSelection.range) {
1184
+ var redoDiff = util.textDiff(this.previousField, this.field);
1185
+ newSelection.range.startOffset = redoDiff.start;
1186
+ newSelection.range.endOffset = redoDiff.end;
1187
+ }
1188
+
1189
+ this.editor._onAction('editField', {
1190
+ node: this,
1191
+ oldValue: this.previousField,
1192
+ newValue: this.field,
1193
+ oldSelection: oldSelection,
1194
+ newSelection: newSelection
1195
+ });
1196
+
1197
+ this.previousField = this.field;
1198
+ };
1199
+
1200
+ /**
1201
+ * Update dom value:
1202
+ * - the text color of the value, depending on the type of the value
1203
+ * - the height of the field, depending on the width
1204
+ * - background color in case it is empty
1205
+ * @private
1206
+ */
1207
+ Node.prototype._updateDomValue = function () {
1208
+ var domValue = this.dom.value;
1209
+ if (domValue) {
1210
+ var classNames = ['jsoneditor-value'];
1211
+
1212
+
1213
+ // set text color depending on value type
1214
+ var value = this.value;
1215
+ var type = (this.type == 'auto') ? util.type(value) : this.type;
1216
+ var isUrl = type == 'string' && util.isUrl(value);
1217
+ classNames.push('jsoneditor-' + type);
1218
+ if (isUrl) {
1219
+ classNames.push('jsoneditor-url');
1220
+ }
1221
+
1222
+ // visual styling when empty
1223
+ var isEmpty = (String(this.value) == '' && this.type != 'array' && this.type != 'object');
1224
+ if (isEmpty) {
1225
+ classNames.push('jsoneditor-empty');
1226
+ }
1227
+
1228
+ // highlight when there is a search result
1229
+ if (this.searchValueActive) {
1230
+ classNames.push('jsoneditor-highlight-active');
1231
+ }
1232
+ if (this.searchValue) {
1233
+ classNames.push('jsoneditor-highlight');
1234
+ }
1235
+
1236
+ domValue.className = classNames.join(' ');
1237
+
1238
+ // update title
1239
+ if (type == 'array' || type == 'object') {
1240
+ var count = this.childs ? this.childs.length : 0;
1241
+ domValue.title = this.type + ' containing ' + count + ' items';
1242
+ }
1243
+ else if (isUrl && this.editable.value) {
1244
+ domValue.title = 'Ctrl+Click or Ctrl+Enter to open url in new window';
1245
+ }
1246
+ else {
1247
+ domValue.title = '';
1248
+ }
1249
+
1250
+ // show checkbox when the value is a boolean
1251
+ if (type === 'boolean') {
1252
+ if (!this.dom.checkbox) {
1253
+ this.dom.checkbox = document.createElement('input');
1254
+ this.dom.checkbox.type = 'checkbox';
1255
+ this.dom.tdCheckbox = document.createElement('td');
1256
+ this.dom.tdCheckbox.className = 'jsoneditor-tree';
1257
+ this.dom.tdCheckbox.appendChild(this.dom.checkbox);
1258
+
1259
+ this.dom.tdValue.parentNode.insertBefore(this.dom.tdCheckbox, this.dom.tdValue);
1260
+ }
1261
+
1262
+ this.dom.checkbox.checked = this.value;
1263
+ }
1264
+ else {
1265
+ // cleanup checkbox when displayed
1266
+ if (this.dom.tdCheckbox) {
1267
+ this.dom.tdCheckbox.parentNode.removeChild(this.dom.tdCheckbox);
1268
+ delete this.dom.tdCheckbox;
1269
+ delete this.dom.checkbox;
1270
+ }
1271
+ }
1272
+
1273
+ // strip formatting from the contents of the editable div
1274
+ util.stripFormatting(domValue);
1275
+ }
1276
+ };
1277
+
1278
+ /**
1279
+ * Update dom field:
1280
+ * - the text color of the field, depending on the text
1281
+ * - the height of the field, depending on the width
1282
+ * - background color in case it is empty
1283
+ * @private
1284
+ */
1285
+ Node.prototype._updateDomField = function () {
1286
+ var domField = this.dom.field;
1287
+ if (domField) {
1288
+ // make backgound color lightgray when empty
1289
+ var isEmpty = (String(this.field) == '' && this.parent.type != 'array');
1290
+ if (isEmpty) {
1291
+ util.addClassName(domField, 'jsoneditor-empty');
1292
+ }
1293
+ else {
1294
+ util.removeClassName(domField, 'jsoneditor-empty');
1295
+ }
1296
+
1297
+ // highlight when there is a search result
1298
+ if (this.searchFieldActive) {
1299
+ util.addClassName(domField, 'jsoneditor-highlight-active');
1300
+ }
1301
+ else {
1302
+ util.removeClassName(domField, 'jsoneditor-highlight-active');
1303
+ }
1304
+ if (this.searchField) {
1305
+ util.addClassName(domField, 'jsoneditor-highlight');
1306
+ }
1307
+ else {
1308
+ util.removeClassName(domField, 'jsoneditor-highlight');
1309
+ }
1310
+
1311
+ // strip formatting from the contents of the editable div
1312
+ util.stripFormatting(domField);
1313
+ }
1314
+ };
1315
+
1316
+ /**
1317
+ * Retrieve field from DOM
1318
+ * @param {boolean} [silent] If true (default), no errors will be thrown in
1319
+ * case of invalid data
1320
+ * @private
1321
+ */
1322
+ Node.prototype._getDomField = function(silent) {
1323
+ if (this.dom.field && this.fieldEditable) {
1324
+ this.fieldInnerText = util.getInnerText(this.dom.field);
1325
+ }
1326
+
1327
+ if (this.fieldInnerText != undefined) {
1328
+ try {
1329
+ var field = this._unescapeHTML(this.fieldInnerText);
1330
+
1331
+ if (field !== this.field) {
1332
+ this.field = field;
1333
+ this._debouncedOnChangeField();
1334
+ }
1335
+ }
1336
+ catch (err) {
1337
+ this.field = undefined;
1338
+ // TODO: sent an action here, with the new, invalid value?
1339
+ if (silent !== true) {
1340
+ throw err;
1341
+ }
1342
+ }
1343
+ }
1344
+ };
1345
+
1346
+ /**
1347
+ * Validate this node and all it's childs
1348
+ * @return {Array.<{node: Node, error: {message: string}}>} Returns a list with duplicates
1349
+ */
1350
+ Node.prototype.validate = function () {
1351
+ var errors = [];
1352
+
1353
+ // find duplicate keys
1354
+ if (this.type === 'object') {
1355
+ var keys = {};
1356
+ var duplicateKeys = [];
1357
+ for (var i = 0; i < this.childs.length; i++) {
1358
+ var child = this.childs[i];
1359
+ if (keys[child.field]) {
1360
+ duplicateKeys.push(child.field);
1361
+ }
1362
+ keys[child.field] = true;
1363
+ }
1364
+
1365
+ if (duplicateKeys.length > 0) {
1366
+ errors = this.childs
1367
+ .filter(function (node) {
1368
+ return duplicateKeys.indexOf(node.field) !== -1;
1369
+ })
1370
+ .map(function (node) {
1371
+ return {
1372
+ node: node,
1373
+ error: {
1374
+ message: 'duplicate key "' + node.field + '"'
1375
+ }
1376
+ }
1377
+ });
1378
+ }
1379
+ }
1380
+
1381
+ // recurse over the childs
1382
+ if (this.childs) {
1383
+ for (var i = 0; i < this.childs.length; i++) {
1384
+ var e = this.childs[i].validate();
1385
+ if (e.length > 0) {
1386
+ errors = errors.concat(e);
1387
+ }
1388
+ }
1389
+ }
1390
+
1391
+ return errors;
1392
+ };
1393
+
1394
+ /**
1395
+ * Clear the dom of the node
1396
+ */
1397
+ Node.prototype.clearDom = function() {
1398
+ // TODO: hide the node first?
1399
+ //this.hide();
1400
+ // TODO: recursively clear dom?
1401
+
1402
+ this.dom = {};
1403
+ };
1404
+
1405
+ /**
1406
+ * Get the HTML DOM TR element of the node.
1407
+ * The dom will be generated when not yet created
1408
+ * @return {Element} tr HTML DOM TR Element
1409
+ */
1410
+ Node.prototype.getDom = function() {
1411
+ var dom = this.dom;
1412
+ if (dom.tr) {
1413
+ return dom.tr;
1414
+ }
1415
+
1416
+ this._updateEditability();
1417
+
1418
+ // create row
1419
+ dom.tr = document.createElement('tr');
1420
+ dom.tr.node = this;
1421
+
1422
+ if (this.editor.options.mode === 'tree') { // note: we take here the global setting
1423
+ var tdDrag = document.createElement('td');
1424
+ if (this.editable.field) {
1425
+ // create draggable area
1426
+ if (this.parent) {
1427
+ var domDrag = document.createElement('button');
1428
+ dom.drag = domDrag;
1429
+ domDrag.className = 'jsoneditor-dragarea';
1430
+ domDrag.title = 'Drag to move this field (Alt+Shift+Arrows)';
1431
+ tdDrag.appendChild(domDrag);
1432
+ }
1433
+ }
1434
+ dom.tr.appendChild(tdDrag);
1435
+
1436
+ // create context menu
1437
+ var tdMenu = document.createElement('td');
1438
+ var menu = document.createElement('button');
1439
+ dom.menu = menu;
1440
+ menu.className = 'jsoneditor-contextmenu';
1441
+ menu.title = 'Click to open the actions menu (Ctrl+M)';
1442
+ tdMenu.appendChild(dom.menu);
1443
+ dom.tr.appendChild(tdMenu);
1444
+ }
1445
+
1446
+ // create tree and field
1447
+ var tdField = document.createElement('td');
1448
+ dom.tr.appendChild(tdField);
1449
+ dom.tree = this._createDomTree();
1450
+ tdField.appendChild(dom.tree);
1451
+
1452
+ this.updateDom({'updateIndexes': true});
1453
+
1454
+ return dom.tr;
1455
+ };
1456
+
1457
+ /**
1458
+ * DragStart event, fired on mousedown on the dragarea at the left side of a Node
1459
+ * @param {Node[] | Node} nodes
1460
+ * @param {Event} event
1461
+ */
1462
+ Node.onDragStart = function (nodes, event) {
1463
+ if (!Array.isArray(nodes)) {
1464
+ return Node.onDragStart([nodes], event);
1465
+ }
1466
+ if (nodes.length === 0) {
1467
+ return;
1468
+ }
1469
+
1470
+ var firstNode = nodes[0];
1471
+ var lastNode = nodes[nodes.length - 1];
1472
+ var draggedNode = Node.getNodeFromTarget(event.target);
1473
+ var beforeNode = lastNode._nextSibling();
1474
+ var editor = firstNode.editor;
1475
+
1476
+ // in case of multiple selected nodes, offsetY prevents the selection from
1477
+ // jumping when you start dragging one of the lower down nodes in the selection
1478
+ var offsetY = util.getAbsoluteTop(draggedNode.dom.tr) - util.getAbsoluteTop(firstNode.dom.tr);
1479
+
1480
+ if (!editor.mousemove) {
1481
+ editor.mousemove = util.addEventListener(window, 'mousemove', function (event) {
1482
+ Node.onDrag(nodes, event);
1483
+ });
1484
+ }
1485
+
1486
+ if (!editor.mouseup) {
1487
+ editor.mouseup = util.addEventListener(window, 'mouseup',function (event ) {
1488
+ Node.onDragEnd(nodes, event);
1489
+ });
1490
+ }
1491
+
1492
+ editor.highlighter.lock();
1493
+ editor.drag = {
1494
+ oldCursor: document.body.style.cursor,
1495
+ oldSelection: editor.getSelection(),
1496
+ oldBeforeNode: beforeNode,
1497
+ mouseX: event.pageX,
1498
+ offsetY: offsetY,
1499
+ level: firstNode.getLevel()
1500
+ };
1501
+ document.body.style.cursor = 'move';
1502
+
1503
+ event.preventDefault();
1504
+ };
1505
+
1506
+ /**
1507
+ * Drag event, fired when moving the mouse while dragging a Node
1508
+ * @param {Node[] | Node} nodes
1509
+ * @param {Event} event
1510
+ */
1511
+ Node.onDrag = function (nodes, event) {
1512
+ if (!Array.isArray(nodes)) {
1513
+ return Node.onDrag([nodes], event);
1514
+ }
1515
+ if (nodes.length === 0) {
1516
+ return;
1517
+ }
1518
+
1519
+ // TODO: this method has grown too large. Split it in a number of methods
1520
+ var editor = nodes[0].editor;
1521
+ var mouseY = event.pageY - editor.drag.offsetY;
1522
+ var mouseX = event.pageX;
1523
+ var trThis, trPrev, trNext, trFirst, trLast, trRoot;
1524
+ var nodePrev, nodeNext;
1525
+ var topThis, topPrev, topFirst, heightThis, bottomNext, heightNext;
1526
+ var moved = false;
1527
+
1528
+ // TODO: add an ESC option, which resets to the original position
1529
+
1530
+ // move up/down
1531
+ var firstNode = nodes[0];
1532
+ trThis = firstNode.dom.tr;
1533
+ topThis = util.getAbsoluteTop(trThis);
1534
+ heightThis = trThis.offsetHeight;
1535
+ if (mouseY < topThis) {
1536
+ // move up
1537
+ trPrev = trThis;
1538
+ do {
1539
+ trPrev = trPrev.previousSibling;
1540
+ nodePrev = Node.getNodeFromTarget(trPrev);
1541
+ topPrev = trPrev ? util.getAbsoluteTop(trPrev) : 0;
1542
+ }
1543
+ while (trPrev && mouseY < topPrev);
1544
+
1545
+ if (nodePrev && !nodePrev.parent) {
1546
+ nodePrev = undefined;
1547
+ }
1548
+
1549
+ if (!nodePrev) {
1550
+ // move to the first node
1551
+ trRoot = trThis.parentNode.firstChild;
1552
+ trPrev = trRoot ? trRoot.nextSibling : undefined;
1553
+ nodePrev = Node.getNodeFromTarget(trPrev);
1554
+ if (nodePrev == firstNode) {
1555
+ nodePrev = undefined;
1556
+ }
1557
+ }
1558
+
1559
+ if (nodePrev) {
1560
+ // check if mouseY is really inside the found node
1561
+ trPrev = nodePrev.dom.tr;
1562
+ topPrev = trPrev ? util.getAbsoluteTop(trPrev) : 0;
1563
+ if (mouseY > topPrev + heightThis) {
1564
+ nodePrev = undefined;
1565
+ }
1566
+ }
1567
+
1568
+ if (nodePrev) {
1569
+ nodes.forEach(function (node) {
1570
+ nodePrev.parent.moveBefore(node, nodePrev);
1571
+ });
1572
+ moved = true;
1573
+ }
1574
+ }
1575
+ else {
1576
+ // move down
1577
+ var lastNode = nodes[nodes.length - 1];
1578
+ trLast = (lastNode.expanded && lastNode.append) ? lastNode.append.getDom() : lastNode.dom.tr;
1579
+ trFirst = trLast ? trLast.nextSibling : undefined;
1580
+ if (trFirst) {
1581
+ topFirst = util.getAbsoluteTop(trFirst);
1582
+ trNext = trFirst;
1583
+ do {
1584
+ nodeNext = Node.getNodeFromTarget(trNext);
1585
+ if (trNext) {
1586
+ bottomNext = trNext.nextSibling ?
1587
+ util.getAbsoluteTop(trNext.nextSibling) : 0;
1588
+ heightNext = trNext ? (bottomNext - topFirst) : 0;
1589
+
1590
+ if (nodeNext.parent.childs.length == nodes.length &&
1591
+ nodeNext.parent.childs[nodes.length - 1] == lastNode) {
1592
+ // We are about to remove the last child of this parent,
1593
+ // which will make the parents appendNode visible.
1594
+ topThis += 27;
1595
+ // TODO: dangerous to suppose the height of the appendNode a constant of 27 px.
1596
+ }
1597
+ }
1598
+
1599
+ trNext = trNext.nextSibling;
1600
+ }
1601
+ while (trNext && mouseY > topThis + heightNext);
1602
+
1603
+ if (nodeNext && nodeNext.parent) {
1604
+ // calculate the desired level
1605
+ var diffX = (mouseX - editor.drag.mouseX);
1606
+ var diffLevel = Math.round(diffX / 24 / 2);
1607
+ var level = editor.drag.level + diffLevel; // desired level
1608
+ var levelNext = nodeNext.getLevel(); // level to be
1609
+
1610
+ // find the best fitting level (move upwards over the append nodes)
1611
+ trPrev = nodeNext.dom.tr.previousSibling;
1612
+ while (levelNext < level && trPrev) {
1613
+ nodePrev = Node.getNodeFromTarget(trPrev);
1614
+
1615
+ var isDraggedNode = nodes.some(function (node) {
1616
+ return node === nodePrev || nodePrev._isChildOf(node);
1617
+ });
1618
+
1619
+ if (isDraggedNode) {
1620
+ // neglect the dragged nodes themselves and their childs
1621
+ }
1622
+ else if (nodePrev instanceof AppendNode) {
1623
+ var childs = nodePrev.parent.childs;
1624
+ if (childs.length != nodes.length || childs[nodes.length - 1] != lastNode) {
1625
+ // non-visible append node of a list of childs
1626
+ // consisting of not only this node (else the
1627
+ // append node will change into a visible "empty"
1628
+ // text when removing this node).
1629
+ nodeNext = Node.getNodeFromTarget(trPrev);
1630
+ levelNext = nodeNext.getLevel();
1631
+ }
1632
+ else {
1633
+ break;
1634
+ }
1635
+ }
1636
+ else {
1637
+ break;
1638
+ }
1639
+
1640
+ trPrev = trPrev.previousSibling;
1641
+ }
1642
+
1643
+ // move the node when its position is changed
1644
+ if (trLast.nextSibling != nodeNext.dom.tr) {
1645
+ nodes.forEach(function (node) {
1646
+ nodeNext.parent.moveBefore(node, nodeNext);
1647
+ });
1648
+ moved = true;
1649
+ }
1650
+ }
1651
+ }
1652
+ }
1653
+
1654
+ if (moved) {
1655
+ // update the dragging parameters when moved
1656
+ editor.drag.mouseX = mouseX;
1657
+ editor.drag.level = firstNode.getLevel();
1658
+ }
1659
+
1660
+ // auto scroll when hovering around the top of the editor
1661
+ editor.startAutoScroll(mouseY);
1662
+
1663
+ event.preventDefault();
1664
+ };
1665
+
1666
+ /**
1667
+ * Drag event, fired on mouseup after having dragged a node
1668
+ * @param {Node[] | Node} nodes
1669
+ * @param {Event} event
1670
+ */
1671
+ Node.onDragEnd = function (nodes, event) {
1672
+ if (!Array.isArray(nodes)) {
1673
+ return Node.onDrag([nodes], event);
1674
+ }
1675
+ if (nodes.length === 0) {
1676
+ return;
1677
+ }
1678
+
1679
+ var firstNode = nodes[0];
1680
+ var editor = firstNode.editor;
1681
+ var parent = firstNode.parent;
1682
+ var firstIndex = parent.childs.indexOf(firstNode);
1683
+ var beforeNode = parent.childs[firstIndex + nodes.length] || parent.append;
1684
+
1685
+ // set focus to the context menu button of the first node
1686
+ if (nodes[0]) {
1687
+ nodes[0].dom.menu.focus();
1688
+ }
1689
+
1690
+ var params = {
1691
+ nodes: nodes,
1692
+ oldSelection: editor.drag.oldSelection,
1693
+ newSelection: editor.getSelection(),
1694
+ oldBeforeNode: editor.drag.oldBeforeNode,
1695
+ newBeforeNode: beforeNode
1696
+ };
1697
+
1698
+ if (params.oldBeforeNode != params.newBeforeNode) {
1699
+ // only register this action if the node is actually moved to another place
1700
+ editor._onAction('moveNodes', params);
1701
+ }
1702
+
1703
+ document.body.style.cursor = editor.drag.oldCursor;
1704
+ editor.highlighter.unlock();
1705
+ nodes.forEach(function (node) {
1706
+ if (event.target !== node.dom.drag && event.target !== node.dom.menu) {
1707
+ editor.highlighter.unhighlight();
1708
+ }
1709
+ });
1710
+ delete editor.drag;
1711
+
1712
+ if (editor.mousemove) {
1713
+ util.removeEventListener(window, 'mousemove', editor.mousemove);
1714
+ delete editor.mousemove;
1715
+ }
1716
+ if (editor.mouseup) {
1717
+ util.removeEventListener(window, 'mouseup', editor.mouseup);
1718
+ delete editor.mouseup;
1719
+ }
1720
+
1721
+ // Stop any running auto scroll
1722
+ editor.stopAutoScroll();
1723
+
1724
+ event.preventDefault();
1725
+ };
1726
+
1727
+ /**
1728
+ * Test if this node is a child of an other node
1729
+ * @param {Node} node
1730
+ * @return {boolean} isChild
1731
+ * @private
1732
+ */
1733
+ Node.prototype._isChildOf = function (node) {
1734
+ var n = this.parent;
1735
+ while (n) {
1736
+ if (n == node) {
1737
+ return true;
1738
+ }
1739
+ n = n.parent;
1740
+ }
1741
+
1742
+ return false;
1743
+ };
1744
+
1745
+ /**
1746
+ * Create an editable field
1747
+ * @return {Element} domField
1748
+ * @private
1749
+ */
1750
+ Node.prototype._createDomField = function () {
1751
+ return document.createElement('div');
1752
+ };
1753
+
1754
+ /**
1755
+ * Set highlighting for this node and all its childs.
1756
+ * Only applied to the currently visible (expanded childs)
1757
+ * @param {boolean} highlight
1758
+ */
1759
+ Node.prototype.setHighlight = function (highlight) {
1760
+ if (this.dom.tr) {
1761
+ if (highlight) {
1762
+ util.addClassName(this.dom.tr, 'jsoneditor-highlight');
1763
+ }
1764
+ else {
1765
+ util.removeClassName(this.dom.tr, 'jsoneditor-highlight');
1766
+ }
1767
+
1768
+ if (this.append) {
1769
+ this.append.setHighlight(highlight);
1770
+ }
1771
+
1772
+ if (this.childs) {
1773
+ this.childs.forEach(function (child) {
1774
+ child.setHighlight(highlight);
1775
+ });
1776
+ }
1777
+ }
1778
+ };
1779
+
1780
+ /**
1781
+ * Select or deselect a node
1782
+ * @param {boolean} selected
1783
+ * @param {boolean} [isFirst]
1784
+ */
1785
+ Node.prototype.setSelected = function (selected, isFirst) {
1786
+ this.selected = selected;
1787
+
1788
+ if (this.dom.tr) {
1789
+ if (selected) {
1790
+ util.addClassName(this.dom.tr, 'jsoneditor-selected');
1791
+ }
1792
+ else {
1793
+ util.removeClassName(this.dom.tr, 'jsoneditor-selected');
1794
+ }
1795
+
1796
+ if (isFirst) {
1797
+ util.addClassName(this.dom.tr, 'jsoneditor-first');
1798
+ }
1799
+ else {
1800
+ util.removeClassName(this.dom.tr, 'jsoneditor-first');
1801
+ }
1802
+
1803
+ if (this.append) {
1804
+ this.append.setSelected(selected);
1805
+ }
1806
+
1807
+ if (this.childs) {
1808
+ this.childs.forEach(function (child) {
1809
+ child.setSelected(selected);
1810
+ });
1811
+ }
1812
+ }
1813
+ };
1814
+
1815
+ /**
1816
+ * Update the value of the node. Only primitive types are allowed, no Object
1817
+ * or Array is allowed.
1818
+ * @param {String | Number | Boolean | null} value
1819
+ */
1820
+ Node.prototype.updateValue = function (value) {
1821
+ this.value = value;
1822
+ this.updateDom();
1823
+ };
1824
+
1825
+ /**
1826
+ * Update the field of the node.
1827
+ * @param {String} field
1828
+ */
1829
+ Node.prototype.updateField = function (field) {
1830
+ this.field = field;
1831
+ this.updateDom();
1832
+ };
1833
+
1834
+ /**
1835
+ * Update the HTML DOM, optionally recursing through the childs
1836
+ * @param {Object} [options] Available parameters:
1837
+ * {boolean} [recurse] If true, the
1838
+ * DOM of the childs will be updated recursively.
1839
+ * False by default.
1840
+ * {boolean} [updateIndexes] If true, the childs
1841
+ * indexes of the node will be updated too. False by
1842
+ * default.
1843
+ */
1844
+ Node.prototype.updateDom = function (options) {
1845
+ // update level indentation
1846
+ var domTree = this.dom.tree;
1847
+ if (domTree) {
1848
+ domTree.style.marginLeft = this.getLevel() * 24 + 'px';
1849
+ }
1850
+
1851
+ // apply field to DOM
1852
+ var domField = this.dom.field;
1853
+ if (domField) {
1854
+ if (this.fieldEditable) {
1855
+ // parent is an object
1856
+ domField.contentEditable = this.editable.field;
1857
+ domField.spellcheck = false;
1858
+ domField.className = 'jsoneditor-field';
1859
+ }
1860
+ else {
1861
+ // parent is an array this is the root node
1862
+ domField.className = 'jsoneditor-readonly';
1863
+ }
1864
+
1865
+ var field;
1866
+ if (this.index != undefined) {
1867
+ field = this.index;
1868
+ }
1869
+ else if (this.field != undefined) {
1870
+ field = this.field;
1871
+ }
1872
+ else if (this._hasChilds()) {
1873
+ field = this.type;
1874
+ }
1875
+ else {
1876
+ field = '';
1877
+ }
1878
+ domField.innerHTML = this._escapeHTML(field);
1879
+ }
1880
+
1881
+ // apply value to DOM
1882
+ var domValue = this.dom.value;
1883
+ if (domValue) {
1884
+ var count = this.childs ? this.childs.length : 0;
1885
+ if (this.type == 'array') {
1886
+ domValue.innerHTML = '[' + count + ']';
1887
+ util.addClassName(this.dom.tr, 'jsoneditor-expandable');
1888
+ }
1889
+ else if (this.type == 'object') {
1890
+ domValue.innerHTML = '{' + count + '}';
1891
+ util.addClassName(this.dom.tr, 'jsoneditor-expandable');
1892
+ }
1893
+ else {
1894
+ domValue.innerHTML = this._escapeHTML(this.value);
1895
+ util.removeClassName(this.dom.tr, 'jsoneditor-expandable');
1896
+ }
1897
+ }
1898
+
1899
+ // update field and value
1900
+ this._updateDomField();
1901
+ this._updateDomValue();
1902
+
1903
+ // update childs indexes
1904
+ if (options && options.updateIndexes === true) {
1905
+ // updateIndexes is true or undefined
1906
+ this._updateDomIndexes();
1907
+ }
1908
+
1909
+ if (options && options.recurse === true) {
1910
+ // recurse is true or undefined. update childs recursively
1911
+ if (this.childs) {
1912
+ this.childs.forEach(function (child) {
1913
+ child.updateDom(options);
1914
+ });
1915
+ }
1916
+ }
1917
+
1918
+ // update row with append button
1919
+ if (this.append) {
1920
+ this.append.updateDom();
1921
+ }
1922
+ };
1923
+
1924
+ /**
1925
+ * Update the DOM of the childs of a node: update indexes and undefined field
1926
+ * names.
1927
+ * Only applicable when structure is an array or object
1928
+ * @private
1929
+ */
1930
+ Node.prototype._updateDomIndexes = function () {
1931
+ var domValue = this.dom.value;
1932
+ var childs = this.childs;
1933
+ if (domValue && childs) {
1934
+ if (this.type == 'array') {
1935
+ childs.forEach(function (child, index) {
1936
+ child.index = index;
1937
+ var childField = child.dom.field;
1938
+ if (childField) {
1939
+ childField.innerHTML = index;
1940
+ }
1941
+ });
1942
+ }
1943
+ else if (this.type == 'object') {
1944
+ childs.forEach(function (child) {
1945
+ if (child.index != undefined) {
1946
+ delete child.index;
1947
+
1948
+ if (child.field == undefined) {
1949
+ child.field = '';
1950
+ }
1951
+ }
1952
+ });
1953
+ }
1954
+ }
1955
+ };
1956
+
1957
+ /**
1958
+ * Create an editable value
1959
+ * @private
1960
+ */
1961
+ Node.prototype._createDomValue = function () {
1962
+ var domValue;
1963
+
1964
+ if (this.type == 'array') {
1965
+ domValue = document.createElement('div');
1966
+ domValue.innerHTML = '[...]';
1967
+ }
1968
+ else if (this.type == 'object') {
1969
+ domValue = document.createElement('div');
1970
+ domValue.innerHTML = '{...}';
1971
+ }
1972
+ else {
1973
+ if (!this.editable.value && util.isUrl(this.value)) {
1974
+ // create a link in case of read-only editor and value containing an url
1975
+ domValue = document.createElement('a');
1976
+ domValue.href = this.value;
1977
+ domValue.target = '_blank';
1978
+ domValue.innerHTML = this._escapeHTML(this.value);
1979
+ }
1980
+ else {
1981
+ // create an editable or read-only div
1982
+ domValue = document.createElement('div');
1983
+ domValue.contentEditable = this.editable.value;
1984
+ domValue.spellcheck = false;
1985
+ domValue.innerHTML = this._escapeHTML(this.value);
1986
+ }
1987
+ }
1988
+
1989
+ return domValue;
1990
+ };
1991
+
1992
+ /**
1993
+ * Create an expand/collapse button
1994
+ * @return {Element} expand
1995
+ * @private
1996
+ */
1997
+ Node.prototype._createDomExpandButton = function () {
1998
+ // create expand button
1999
+ var expand = document.createElement('button');
2000
+ if (this._hasChilds()) {
2001
+ expand.className = this.expanded ? 'jsoneditor-expanded' : 'jsoneditor-collapsed';
2002
+ expand.title =
2003
+ 'Click to expand/collapse this field (Ctrl+E). \n' +
2004
+ 'Ctrl+Click to expand/collapse including all childs.';
2005
+ }
2006
+ else {
2007
+ expand.className = 'jsoneditor-invisible';
2008
+ expand.title = '';
2009
+ }
2010
+
2011
+ return expand;
2012
+ };
2013
+
2014
+
2015
+ /**
2016
+ * Create a DOM tree element, containing the expand/collapse button
2017
+ * @return {Element} domTree
2018
+ * @private
2019
+ */
2020
+ Node.prototype._createDomTree = function () {
2021
+ var dom = this.dom;
2022
+ var domTree = document.createElement('table');
2023
+ var tbody = document.createElement('tbody');
2024
+ domTree.style.borderCollapse = 'collapse'; // TODO: put in css
2025
+ domTree.className = 'jsoneditor-values';
2026
+ domTree.appendChild(tbody);
2027
+ var tr = document.createElement('tr');
2028
+ tbody.appendChild(tr);
2029
+
2030
+ // create expand button
2031
+ var tdExpand = document.createElement('td');
2032
+ tdExpand.className = 'jsoneditor-tree';
2033
+ tr.appendChild(tdExpand);
2034
+ dom.expand = this._createDomExpandButton();
2035
+ tdExpand.appendChild(dom.expand);
2036
+ dom.tdExpand = tdExpand;
2037
+
2038
+ // create the field
2039
+ var tdField = document.createElement('td');
2040
+ tdField.className = 'jsoneditor-tree';
2041
+ tr.appendChild(tdField);
2042
+ dom.field = this._createDomField();
2043
+ tdField.appendChild(dom.field);
2044
+ dom.tdField = tdField;
2045
+
2046
+ // create a separator
2047
+ var tdSeparator = document.createElement('td');
2048
+ tdSeparator.className = 'jsoneditor-tree';
2049
+ tr.appendChild(tdSeparator);
2050
+ if (this.type != 'object' && this.type != 'array') {
2051
+ tdSeparator.appendChild(document.createTextNode(':'));
2052
+ tdSeparator.className = 'jsoneditor-separator';
2053
+ }
2054
+ dom.tdSeparator = tdSeparator;
2055
+
2056
+ // create the value
2057
+ var tdValue = document.createElement('td');
2058
+ tdValue.className = 'jsoneditor-tree';
2059
+ tr.appendChild(tdValue);
2060
+ dom.value = this._createDomValue();
2061
+ tdValue.appendChild(dom.value);
2062
+ dom.tdValue = tdValue;
2063
+
2064
+ return domTree;
2065
+ };
2066
+
2067
+ /**
2068
+ * Handle an event. The event is caught centrally by the editor
2069
+ * @param {Event} event
2070
+ */
2071
+ Node.prototype.onEvent = function (event) {
2072
+ var type = event.type,
2073
+ target = event.target || event.srcElement,
2074
+ dom = this.dom,
2075
+ node = this,
2076
+ focusNode,
2077
+ expandable = this._hasChilds();
2078
+
2079
+ // check if mouse is on menu or on dragarea.
2080
+ // If so, highlight current row and its childs
2081
+ if (target == dom.drag || target == dom.menu) {
2082
+ if (type == 'mouseover') {
2083
+ this.editor.highlighter.highlight(this);
2084
+ }
2085
+ else if (type == 'mouseout') {
2086
+ this.editor.highlighter.unhighlight();
2087
+ }
2088
+ }
2089
+
2090
+ // context menu events
2091
+ if (type == 'click' && target == dom.menu) {
2092
+ var highlighter = node.editor.highlighter;
2093
+ highlighter.highlight(node);
2094
+ highlighter.lock();
2095
+ util.addClassName(dom.menu, 'jsoneditor-selected');
2096
+ this.showContextMenu(dom.menu, function () {
2097
+ util.removeClassName(dom.menu, 'jsoneditor-selected');
2098
+ highlighter.unlock();
2099
+ highlighter.unhighlight();
2100
+ });
2101
+ }
2102
+
2103
+ // expand events
2104
+ if (type == 'click') {
2105
+ if (target == dom.expand ||
2106
+ ((node.editor.options.mode === 'view' || node.editor.options.mode === 'form') && target.nodeName === 'DIV')) {
2107
+ if (expandable) {
2108
+ var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all
2109
+ this._onExpand(recurse);
2110
+ }
2111
+ }
2112
+ }
2113
+
2114
+ // swap the value of a boolean when the checkbox displayed left is clicked
2115
+ if (type == 'change' && target == dom.checkbox) {
2116
+ this.dom.value.innerHTML = !this.value;
2117
+ this._getDomValue();
2118
+ }
2119
+
2120
+ // value events
2121
+ var domValue = dom.value;
2122
+ if (target == domValue) {
2123
+ //noinspection FallthroughInSwitchStatementJS
2124
+ switch (type) {
2125
+ case 'focus':
2126
+ focusNode = this;
2127
+ break;
2128
+
2129
+ case 'blur':
2130
+ case 'change':
2131
+ this._getDomValue(true);
2132
+ this._updateDomValue();
2133
+ if (this.value) {
2134
+ domValue.innerHTML = this._escapeHTML(this.value);
2135
+ }
2136
+ break;
2137
+
2138
+ case 'input':
2139
+ //this._debouncedGetDomValue(true); // TODO
2140
+ this._getDomValue(true);
2141
+ this._updateDomValue();
2142
+ break;
2143
+
2144
+ case 'keydown':
2145
+ case 'mousedown':
2146
+ // TODO: cleanup
2147
+ this.editor.selection = this.editor.getSelection();
2148
+ break;
2149
+
2150
+ case 'click':
2151
+ if (event.ctrlKey || !this.editable.value) {
2152
+ if (util.isUrl(this.value)) {
2153
+ window.open(this.value, '_blank');
2154
+ }
2155
+ }
2156
+ break;
2157
+
2158
+ case 'keyup':
2159
+ //this._debouncedGetDomValue(true); // TODO
2160
+ this._getDomValue(true);
2161
+ this._updateDomValue();
2162
+ break;
2163
+
2164
+ case 'cut':
2165
+ case 'paste':
2166
+ setTimeout(function () {
2167
+ node._getDomValue(true);
2168
+ node._updateDomValue();
2169
+ }, 1);
2170
+ break;
2171
+ }
2172
+ }
2173
+
2174
+ // field events
2175
+ var domField = dom.field;
2176
+ if (target == domField) {
2177
+ switch (type) {
2178
+ case 'focus':
2179
+ focusNode = this;
2180
+ break;
2181
+
2182
+ case 'blur':
2183
+ case 'change':
2184
+ this._getDomField(true);
2185
+ this._updateDomField();
2186
+ if (this.field) {
2187
+ domField.innerHTML = this._escapeHTML(this.field);
2188
+ }
2189
+ break;
2190
+
2191
+ case 'input':
2192
+ this._getDomField(true);
2193
+ this._updateDomField();
2194
+ break;
2195
+
2196
+ case 'keydown':
2197
+ case 'mousedown':
2198
+ this.editor.selection = this.editor.getSelection();
2199
+ break;
2200
+
2201
+ case 'keyup':
2202
+ this._getDomField(true);
2203
+ this._updateDomField();
2204
+ break;
2205
+
2206
+ case 'cut':
2207
+ case 'paste':
2208
+ setTimeout(function () {
2209
+ node._getDomField(true);
2210
+ node._updateDomField();
2211
+ }, 1);
2212
+ break;
2213
+ }
2214
+ }
2215
+
2216
+ // focus
2217
+ // when clicked in whitespace left or right from the field or value, set focus
2218
+ var domTree = dom.tree;
2219
+ if (target == domTree.parentNode && type == 'click' && !event.hasMoved) {
2220
+ var left = (event.offsetX != undefined) ?
2221
+ (event.offsetX < (this.getLevel() + 1) * 24) :
2222
+ (event.pageX < util.getAbsoluteLeft(dom.tdSeparator));// for FF
2223
+ if (left || expandable) {
2224
+ // node is expandable when it is an object or array
2225
+ if (domField) {
2226
+ util.setEndOfContentEditable(domField);
2227
+ domField.focus();
2228
+ }
2229
+ }
2230
+ else {
2231
+ if (domValue) {
2232
+ util.setEndOfContentEditable(domValue);
2233
+ domValue.focus();
2234
+ }
2235
+ }
2236
+ }
2237
+ if (((target == dom.tdExpand && !expandable) || target == dom.tdField || target == dom.tdSeparator) &&
2238
+ (type == 'click' && !event.hasMoved)) {
2239
+ if (domField) {
2240
+ util.setEndOfContentEditable(domField);
2241
+ domField.focus();
2242
+ }
2243
+ }
2244
+
2245
+ if (type == 'keydown') {
2246
+ this.onKeyDown(event);
2247
+ }
2248
+ };
2249
+
2250
+ /**
2251
+ * Key down event handler
2252
+ * @param {Event} event
2253
+ */
2254
+ Node.prototype.onKeyDown = function (event) {
2255
+ var keynum = event.which || event.keyCode;
2256
+ var target = event.target || event.srcElement;
2257
+ var ctrlKey = event.ctrlKey;
2258
+ var shiftKey = event.shiftKey;
2259
+ var altKey = event.altKey;
2260
+ var handled = false;
2261
+ var prevNode, nextNode, nextDom, nextDom2;
2262
+ var editable = this.editor.options.mode === 'tree';
2263
+ var oldSelection;
2264
+ var oldBeforeNode;
2265
+ var nodes;
2266
+ var multiselection;
2267
+ var selectedNodes = this.editor.multiselection.nodes.length > 0
2268
+ ? this.editor.multiselection.nodes
2269
+ : [this];
2270
+ var firstNode = selectedNodes[0];
2271
+ var lastNode = selectedNodes[selectedNodes.length - 1];
2272
+
2273
+ // console.log(ctrlKey, keynum, event.charCode); // TODO: cleanup
2274
+ if (keynum == 13) { // Enter
2275
+ if (target == this.dom.value) {
2276
+ if (!this.editable.value || event.ctrlKey) {
2277
+ if (util.isUrl(this.value)) {
2278
+ window.open(this.value, '_blank');
2279
+ handled = true;
2280
+ }
2281
+ }
2282
+ }
2283
+ else if (target == this.dom.expand) {
2284
+ var expandable = this._hasChilds();
2285
+ if (expandable) {
2286
+ var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all
2287
+ this._onExpand(recurse);
2288
+ target.focus();
2289
+ handled = true;
2290
+ }
2291
+ }
2292
+ }
2293
+ else if (keynum == 68) { // D
2294
+ if (ctrlKey && editable) { // Ctrl+D
2295
+ Node.onDuplicate(selectedNodes);
2296
+ handled = true;
2297
+ }
2298
+ }
2299
+ else if (keynum == 69) { // E
2300
+ if (ctrlKey) { // Ctrl+E and Ctrl+Shift+E
2301
+ this._onExpand(shiftKey); // recurse = shiftKey
2302
+ target.focus(); // TODO: should restore focus in case of recursing expand (which takes DOM offline)
2303
+ handled = true;
2304
+ }
2305
+ }
2306
+ else if (keynum == 77 && editable) { // M
2307
+ if (ctrlKey) { // Ctrl+M
2308
+ this.showContextMenu(target);
2309
+ handled = true;
2310
+ }
2311
+ }
2312
+ else if (keynum == 46 && editable) { // Del
2313
+ if (ctrlKey) { // Ctrl+Del
2314
+ Node.onRemove(selectedNodes);
2315
+ handled = true;
2316
+ }
2317
+ }
2318
+ else if (keynum == 45 && editable) { // Ins
2319
+ if (ctrlKey && !shiftKey) { // Ctrl+Ins
2320
+ this._onInsertBefore();
2321
+ handled = true;
2322
+ }
2323
+ else if (ctrlKey && shiftKey) { // Ctrl+Shift+Ins
2324
+ this._onInsertAfter();
2325
+ handled = true;
2326
+ }
2327
+ }
2328
+ else if (keynum == 35) { // End
2329
+ if (altKey) { // Alt+End
2330
+ // find the last node
2331
+ var endNode = this._lastNode();
2332
+ if (endNode) {
2333
+ endNode.focus(Node.focusElement || this._getElementName(target));
2334
+ }
2335
+ handled = true;
2336
+ }
2337
+ }
2338
+ else if (keynum == 36) { // Home
2339
+ if (altKey) { // Alt+Home
2340
+ // find the first node
2341
+ var homeNode = this._firstNode();
2342
+ if (homeNode) {
2343
+ homeNode.focus(Node.focusElement || this._getElementName(target));
2344
+ }
2345
+ handled = true;
2346
+ }
2347
+ }
2348
+ else if (keynum == 37) { // Arrow Left
2349
+ if (altKey && !shiftKey) { // Alt + Arrow Left
2350
+ // move to left element
2351
+ var prevElement = this._previousElement(target);
2352
+ if (prevElement) {
2353
+ this.focus(this._getElementName(prevElement));
2354
+ }
2355
+ handled = true;
2356
+ }
2357
+ else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow left
2358
+ if (lastNode.expanded) {
2359
+ var appendDom = lastNode.getAppend();
2360
+ nextDom = appendDom ? appendDom.nextSibling : undefined;
2361
+ }
2362
+ else {
2363
+ var dom = lastNode.getDom();
2364
+ nextDom = dom.nextSibling;
2365
+ }
2366
+ if (nextDom) {
2367
+ nextNode = Node.getNodeFromTarget(nextDom);
2368
+ nextDom2 = nextDom.nextSibling;
2369
+ nextNode2 = Node.getNodeFromTarget(nextDom2);
2370
+ if (nextNode && nextNode instanceof AppendNode &&
2371
+ !(lastNode.parent.childs.length == 1) &&
2372
+ nextNode2 && nextNode2.parent) {
2373
+ oldSelection = this.editor.getSelection();
2374
+ oldBeforeNode = lastNode._nextSibling();
2375
+
2376
+ selectedNodes.forEach(function (node) {
2377
+ nextNode2.parent.moveBefore(node, nextNode2);
2378
+ });
2379
+ this.focus(Node.focusElement || this._getElementName(target));
2380
+
2381
+ this.editor._onAction('moveNodes', {
2382
+ nodes: selectedNodes,
2383
+ oldBeforeNode: oldBeforeNode,
2384
+ newBeforeNode: nextNode2,
2385
+ oldSelection: oldSelection,
2386
+ newSelection: this.editor.getSelection()
2387
+ });
2388
+ }
2389
+ }
2390
+ }
2391
+ }
2392
+ else if (keynum == 38) { // Arrow Up
2393
+ if (altKey && !shiftKey) { // Alt + Arrow Up
2394
+ // find the previous node
2395
+ prevNode = this._previousNode();
2396
+ if (prevNode) {
2397
+ this.editor.deselect(true);
2398
+ prevNode.focus(Node.focusElement || this._getElementName(target));
2399
+ }
2400
+ handled = true;
2401
+ }
2402
+ else if (!altKey && ctrlKey && shiftKey && editable) { // Ctrl + Shift + Arrow Up
2403
+ // select multiple nodes
2404
+ prevNode = this._previousNode();
2405
+ if (prevNode) {
2406
+ multiselection = this.editor.multiselection;
2407
+ multiselection.start = multiselection.start || this;
2408
+ multiselection.end = prevNode;
2409
+ nodes = this.editor._findTopLevelNodes(multiselection.start, multiselection.end);
2410
+
2411
+ this.editor.select(nodes);
2412
+ prevNode.focus('field'); // select field as we know this always exists
2413
+ }
2414
+ handled = true;
2415
+ }
2416
+ else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Up
2417
+ // find the previous node
2418
+ prevNode = firstNode._previousNode();
2419
+ if (prevNode && prevNode.parent) {
2420
+ oldSelection = this.editor.getSelection();
2421
+ oldBeforeNode = lastNode._nextSibling();
2422
+
2423
+ selectedNodes.forEach(function (node) {
2424
+ prevNode.parent.moveBefore(node, prevNode);
2425
+ });
2426
+ this.focus(Node.focusElement || this._getElementName(target));
2427
+
2428
+ this.editor._onAction('moveNodes', {
2429
+ nodes: selectedNodes,
2430
+ oldBeforeNode: oldBeforeNode,
2431
+ newBeforeNode: prevNode,
2432
+ oldSelection: oldSelection,
2433
+ newSelection: this.editor.getSelection()
2434
+ });
2435
+ }
2436
+ handled = true;
2437
+ }
2438
+ }
2439
+ else if (keynum == 39) { // Arrow Right
2440
+ if (altKey && !shiftKey) { // Alt + Arrow Right
2441
+ // move to right element
2442
+ var nextElement = this._nextElement(target);
2443
+ if (nextElement) {
2444
+ this.focus(this._getElementName(nextElement));
2445
+ }
2446
+ handled = true;
2447
+ }
2448
+ else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Right
2449
+ dom = firstNode.getDom();
2450
+ var prevDom = dom.previousSibling;
2451
+ if (prevDom) {
2452
+ prevNode = Node.getNodeFromTarget(prevDom);
2453
+ if (prevNode && prevNode.parent &&
2454
+ (prevNode instanceof AppendNode)
2455
+ && !prevNode.isVisible()) {
2456
+ oldSelection = this.editor.getSelection();
2457
+ oldBeforeNode = lastNode._nextSibling();
2458
+
2459
+ selectedNodes.forEach(function (node) {
2460
+ prevNode.parent.moveBefore(node, prevNode);
2461
+ });
2462
+ this.focus(Node.focusElement || this._getElementName(target));
2463
+
2464
+ this.editor._onAction('moveNodes', {
2465
+ nodes: selectedNodes,
2466
+ oldBeforeNode: oldBeforeNode,
2467
+ newBeforeNode: prevNode,
2468
+ oldSelection: oldSelection,
2469
+ newSelection: this.editor.getSelection()
2470
+ });
2471
+ }
2472
+ }
2473
+ }
2474
+ }
2475
+ else if (keynum == 40) { // Arrow Down
2476
+ if (altKey && !shiftKey) { // Alt + Arrow Down
2477
+ // find the next node
2478
+ nextNode = this._nextNode();
2479
+ if (nextNode) {
2480
+ this.editor.deselect(true);
2481
+ nextNode.focus(Node.focusElement || this._getElementName(target));
2482
+ }
2483
+ handled = true;
2484
+ }
2485
+ else if (!altKey && ctrlKey && shiftKey && editable) { // Ctrl + Shift + Arrow Down
2486
+ // select multiple nodes
2487
+ nextNode = this._nextNode();
2488
+ if (nextNode) {
2489
+ multiselection = this.editor.multiselection;
2490
+ multiselection.start = multiselection.start || this;
2491
+ multiselection.end = nextNode;
2492
+ nodes = this.editor._findTopLevelNodes(multiselection.start, multiselection.end);
2493
+
2494
+ this.editor.select(nodes);
2495
+ nextNode.focus('field'); // select field as we know this always exists
2496
+ }
2497
+ handled = true;
2498
+ }
2499
+ else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Down
2500
+ // find the 2nd next node and move before that one
2501
+ if (lastNode.expanded) {
2502
+ nextNode = lastNode.append ? lastNode.append._nextNode() : undefined;
2503
+ }
2504
+ else {
2505
+ nextNode = lastNode._nextNode();
2506
+ }
2507
+ var nextNode2 = nextNode && (nextNode._nextNode() || nextNode.parent.append);
2508
+ if (nextNode2 && nextNode2.parent) {
2509
+ oldSelection = this.editor.getSelection();
2510
+ oldBeforeNode = lastNode._nextSibling();
2511
+
2512
+ selectedNodes.forEach(function (node) {
2513
+ nextNode2.parent.moveBefore(node, nextNode2);
2514
+ });
2515
+ this.focus(Node.focusElement || this._getElementName(target));
2516
+
2517
+ this.editor._onAction('moveNodes', {
2518
+ nodes: selectedNodes,
2519
+ oldBeforeNode: oldBeforeNode,
2520
+ newBeforeNode: nextNode2,
2521
+ oldSelection: oldSelection,
2522
+ newSelection: this.editor.getSelection()
2523
+ });
2524
+ }
2525
+ handled = true;
2526
+ }
2527
+ }
2528
+
2529
+ if (handled) {
2530
+ event.preventDefault();
2531
+ event.stopPropagation();
2532
+ }
2533
+ };
2534
+
2535
+ /**
2536
+ * Handle the expand event, when clicked on the expand button
2537
+ * @param {boolean} recurse If true, child nodes will be expanded too
2538
+ * @private
2539
+ */
2540
+ Node.prototype._onExpand = function (recurse) {
2541
+ if (recurse) {
2542
+ // Take the table offline
2543
+ var table = this.dom.tr.parentNode; // TODO: not nice to access the main table like this
2544
+ var frame = table.parentNode;
2545
+ var scrollTop = frame.scrollTop;
2546
+ frame.removeChild(table);
2547
+ }
2548
+
2549
+ if (this.expanded) {
2550
+ this.collapse(recurse);
2551
+ }
2552
+ else {
2553
+ this.expand(recurse);
2554
+ }
2555
+
2556
+ if (recurse) {
2557
+ // Put the table online again
2558
+ frame.appendChild(table);
2559
+ frame.scrollTop = scrollTop;
2560
+ }
2561
+ };
2562
+
2563
+ /**
2564
+ * Remove nodes
2565
+ * @param {Node[] | Node} nodes
2566
+ */
2567
+ Node.onRemove = function(nodes) {
2568
+ if (!Array.isArray(nodes)) {
2569
+ return Node.onRemove([nodes]);
2570
+ }
2571
+
2572
+ if (nodes && nodes.length > 0) {
2573
+ var firstNode = nodes[0];
2574
+ var parent = firstNode.parent;
2575
+ var editor = firstNode.editor;
2576
+ var firstIndex = firstNode.getIndex();
2577
+ editor.highlighter.unhighlight();
2578
+
2579
+ // adjust the focus
2580
+ var oldSelection = editor.getSelection();
2581
+ Node.blurNodes(nodes);
2582
+ var newSelection = editor.getSelection();
2583
+
2584
+ // remove the nodes
2585
+ nodes.forEach(function (node) {
2586
+ node.parent._remove(node);
2587
+ });
2588
+
2589
+ // store history action
2590
+ editor._onAction('removeNodes', {
2591
+ nodes: nodes.slice(0), // store a copy of the array!
2592
+ parent: parent,
2593
+ index: firstIndex,
2594
+ oldSelection: oldSelection,
2595
+ newSelection: newSelection
2596
+ });
2597
+ }
2598
+ };
2599
+
2600
+
2601
+ /**
2602
+ * Duplicate nodes
2603
+ * duplicated nodes will be added right after the original nodes
2604
+ * @param {Node[] | Node} nodes
2605
+ */
2606
+ Node.onDuplicate = function(nodes) {
2607
+ if (!Array.isArray(nodes)) {
2608
+ return Node.onDuplicate([nodes]);
2609
+ }
2610
+
2611
+ if (nodes && nodes.length > 0) {
2612
+ var lastNode = nodes[nodes.length - 1];
2613
+ var parent = lastNode.parent;
2614
+ var editor = lastNode.editor;
2615
+
2616
+ editor.deselect(editor.multiselection.nodes);
2617
+
2618
+ // duplicate the nodes
2619
+ var oldSelection = editor.getSelection();
2620
+ var afterNode = lastNode;
2621
+ var clones = nodes.map(function (node) {
2622
+ var clone = node.clone();
2623
+ parent.insertAfter(clone, afterNode);
2624
+ afterNode = clone;
2625
+ return clone;
2626
+ });
2627
+
2628
+ // set selection to the duplicated nodes
2629
+ if (nodes.length === 1) {
2630
+ clones[0].focus();
2631
+ }
2632
+ else {
2633
+ editor.select(clones);
2634
+ }
2635
+ var newSelection = editor.getSelection();
2636
+
2637
+ editor._onAction('duplicateNodes', {
2638
+ afterNode: lastNode,
2639
+ nodes: clones,
2640
+ parent: parent,
2641
+ oldSelection: oldSelection,
2642
+ newSelection: newSelection
2643
+ });
2644
+ }
2645
+ };
2646
+
2647
+ /**
2648
+ * Handle insert before event
2649
+ * @param {String} [field]
2650
+ * @param {*} [value]
2651
+ * @param {String} [type] Can be 'auto', 'array', 'object', or 'string'
2652
+ * @private
2653
+ */
2654
+ Node.prototype._onInsertBefore = function (field, value, type) {
2655
+ var oldSelection = this.editor.getSelection();
2656
+
2657
+ var newNode = new Node(this.editor, {
2658
+ field: (field != undefined) ? field : '',
2659
+ value: (value != undefined) ? value : '',
2660
+ type: type
2661
+ });
2662
+ newNode.expand(true);
2663
+ this.parent.insertBefore(newNode, this);
2664
+ this.editor.highlighter.unhighlight();
2665
+ newNode.focus('field');
2666
+ var newSelection = this.editor.getSelection();
2667
+
2668
+ this.editor._onAction('insertBeforeNodes', {
2669
+ nodes: [newNode],
2670
+ beforeNode: this,
2671
+ parent: this.parent,
2672
+ oldSelection: oldSelection,
2673
+ newSelection: newSelection
2674
+ });
2675
+ };
2676
+
2677
+ /**
2678
+ * Handle insert after event
2679
+ * @param {String} [field]
2680
+ * @param {*} [value]
2681
+ * @param {String} [type] Can be 'auto', 'array', 'object', or 'string'
2682
+ * @private
2683
+ */
2684
+ Node.prototype._onInsertAfter = function (field, value, type) {
2685
+ var oldSelection = this.editor.getSelection();
2686
+
2687
+ var newNode = new Node(this.editor, {
2688
+ field: (field != undefined) ? field : '',
2689
+ value: (value != undefined) ? value : '',
2690
+ type: type
2691
+ });
2692
+ newNode.expand(true);
2693
+ this.parent.insertAfter(newNode, this);
2694
+ this.editor.highlighter.unhighlight();
2695
+ newNode.focus('field');
2696
+ var newSelection = this.editor.getSelection();
2697
+
2698
+ this.editor._onAction('insertAfterNodes', {
2699
+ nodes: [newNode],
2700
+ afterNode: this,
2701
+ parent: this.parent,
2702
+ oldSelection: oldSelection,
2703
+ newSelection: newSelection
2704
+ });
2705
+ };
2706
+
2707
+ /**
2708
+ * Handle append event
2709
+ * @param {String} [field]
2710
+ * @param {*} [value]
2711
+ * @param {String} [type] Can be 'auto', 'array', 'object', or 'string'
2712
+ * @private
2713
+ */
2714
+ Node.prototype._onAppend = function (field, value, type) {
2715
+ var oldSelection = this.editor.getSelection();
2716
+
2717
+ var newNode = new Node(this.editor, {
2718
+ field: (field != undefined) ? field : '',
2719
+ value: (value != undefined) ? value : '',
2720
+ type: type
2721
+ });
2722
+ newNode.expand(true);
2723
+ this.parent.appendChild(newNode);
2724
+ this.editor.highlighter.unhighlight();
2725
+ newNode.focus('field');
2726
+ var newSelection = this.editor.getSelection();
2727
+
2728
+ this.editor._onAction('appendNodes', {
2729
+ nodes: [newNode],
2730
+ parent: this.parent,
2731
+ oldSelection: oldSelection,
2732
+ newSelection: newSelection
2733
+ });
2734
+ };
2735
+
2736
+ /**
2737
+ * Change the type of the node's value
2738
+ * @param {String} newType
2739
+ * @private
2740
+ */
2741
+ Node.prototype._onChangeType = function (newType) {
2742
+ var oldType = this.type;
2743
+ if (newType != oldType) {
2744
+ var oldSelection = this.editor.getSelection();
2745
+ this.changeType(newType);
2746
+ var newSelection = this.editor.getSelection();
2747
+
2748
+ this.editor._onAction('changeType', {
2749
+ node: this,
2750
+ oldType: oldType,
2751
+ newType: newType,
2752
+ oldSelection: oldSelection,
2753
+ newSelection: newSelection
2754
+ });
2755
+ }
2756
+ };
2757
+
2758
+ /**
2759
+ * Sort the childs of the node. Only applicable when the node has type 'object'
2760
+ * or 'array'.
2761
+ * @param {String} direction Sorting direction. Available values: "asc", "desc"
2762
+ * @private
2763
+ */
2764
+ Node.prototype._onSort = function (direction) {
2765
+ if (this._hasChilds()) {
2766
+ var order = (direction == 'desc') ? -1 : 1;
2767
+ var prop = (this.type == 'array') ? 'value': 'field';
2768
+ this.hideChilds();
2769
+
2770
+ var oldChilds = this.childs;
2771
+ var oldSort = this.sort;
2772
+
2773
+ // copy the array (the old one will be kept for an undo action
2774
+ this.childs = this.childs.concat();
2775
+
2776
+ // sort the arrays
2777
+ this.childs.sort(function (a, b) {
2778
+ if (a[prop] > b[prop]) return order;
2779
+ if (a[prop] < b[prop]) return -order;
2780
+ return 0;
2781
+ });
2782
+ this.sort = (order == 1) ? 'asc' : 'desc';
2783
+
2784
+ this.editor._onAction('sort', {
2785
+ node: this,
2786
+ oldChilds: oldChilds,
2787
+ oldSort: oldSort,
2788
+ newChilds: this.childs,
2789
+ newSort: this.sort
2790
+ });
2791
+
2792
+ this.showChilds();
2793
+ }
2794
+ };
2795
+
2796
+ /**
2797
+ * Create a table row with an append button.
2798
+ * @return {HTMLElement | undefined} buttonAppend or undefined when inapplicable
2799
+ */
2800
+ Node.prototype.getAppend = function () {
2801
+ if (!this.append) {
2802
+ this.append = new AppendNode(this.editor);
2803
+ this.append.setParent(this);
2804
+ }
2805
+ return this.append.getDom();
2806
+ };
2807
+
2808
+ /**
2809
+ * Find the node from an event target
2810
+ * @param {Node} target
2811
+ * @return {Node | undefined} node or undefined when not found
2812
+ * @static
2813
+ */
2814
+ Node.getNodeFromTarget = function (target) {
2815
+ while (target) {
2816
+ if (target.node) {
2817
+ return target.node;
2818
+ }
2819
+ target = target.parentNode;
2820
+ }
2821
+
2822
+ return undefined;
2823
+ };
2824
+
2825
+ /**
2826
+ * Remove the focus of given nodes, and move the focus to the (a) node before,
2827
+ * (b) the node after, or (c) the parent node.
2828
+ * @param {Array.<Node> | Node} nodes
2829
+ */
2830
+ Node.blurNodes = function (nodes) {
2831
+ if (!Array.isArray(nodes)) {
2832
+ Node.blurNodes([nodes]);
2833
+ return;
2834
+ }
2835
+
2836
+ var firstNode = nodes[0];
2837
+ var parent = firstNode.parent;
2838
+ var firstIndex = firstNode.getIndex();
2839
+
2840
+ if (parent.childs[firstIndex + nodes.length]) {
2841
+ parent.childs[firstIndex + nodes.length].focus();
2842
+ }
2843
+ else if (parent.childs[firstIndex - 1]) {
2844
+ parent.childs[firstIndex - 1].focus();
2845
+ }
2846
+ else {
2847
+ parent.focus();
2848
+ }
2849
+ };
2850
+
2851
+ /**
2852
+ * Get the next sibling of current node
2853
+ * @return {Node} nextSibling
2854
+ * @private
2855
+ */
2856
+ Node.prototype._nextSibling = function () {
2857
+ var index = this.parent.childs.indexOf(this);
2858
+ return this.parent.childs[index + 1] || this.parent.append;
2859
+ };
2860
+
2861
+ /**
2862
+ * Get the previously rendered node
2863
+ * @return {Node | null} previousNode
2864
+ * @private
2865
+ */
2866
+ Node.prototype._previousNode = function () {
2867
+ var prevNode = null;
2868
+ var dom = this.getDom();
2869
+ if (dom && dom.parentNode) {
2870
+ // find the previous field
2871
+ var prevDom = dom;
2872
+ do {
2873
+ prevDom = prevDom.previousSibling;
2874
+ prevNode = Node.getNodeFromTarget(prevDom);
2875
+ }
2876
+ while (prevDom && (prevNode instanceof AppendNode && !prevNode.isVisible()));
2877
+ }
2878
+ return prevNode;
2879
+ };
2880
+
2881
+ /**
2882
+ * Get the next rendered node
2883
+ * @return {Node | null} nextNode
2884
+ * @private
2885
+ */
2886
+ Node.prototype._nextNode = function () {
2887
+ var nextNode = null;
2888
+ var dom = this.getDom();
2889
+ if (dom && dom.parentNode) {
2890
+ // find the previous field
2891
+ var nextDom = dom;
2892
+ do {
2893
+ nextDom = nextDom.nextSibling;
2894
+ nextNode = Node.getNodeFromTarget(nextDom);
2895
+ }
2896
+ while (nextDom && (nextNode instanceof AppendNode && !nextNode.isVisible()));
2897
+ }
2898
+
2899
+ return nextNode;
2900
+ };
2901
+
2902
+ /**
2903
+ * Get the first rendered node
2904
+ * @return {Node | null} firstNode
2905
+ * @private
2906
+ */
2907
+ Node.prototype._firstNode = function () {
2908
+ var firstNode = null;
2909
+ var dom = this.getDom();
2910
+ if (dom && dom.parentNode) {
2911
+ var firstDom = dom.parentNode.firstChild;
2912
+ firstNode = Node.getNodeFromTarget(firstDom);
2913
+ }
2914
+
2915
+ return firstNode;
2916
+ };
2917
+
2918
+ /**
2919
+ * Get the last rendered node
2920
+ * @return {Node | null} lastNode
2921
+ * @private
2922
+ */
2923
+ Node.prototype._lastNode = function () {
2924
+ var lastNode = null;
2925
+ var dom = this.getDom();
2926
+ if (dom && dom.parentNode) {
2927
+ var lastDom = dom.parentNode.lastChild;
2928
+ lastNode = Node.getNodeFromTarget(lastDom);
2929
+ while (lastDom && (lastNode instanceof AppendNode && !lastNode.isVisible())) {
2930
+ lastDom = lastDom.previousSibling;
2931
+ lastNode = Node.getNodeFromTarget(lastDom);
2932
+ }
2933
+ }
2934
+ return lastNode;
2935
+ };
2936
+
2937
+ /**
2938
+ * Get the next element which can have focus.
2939
+ * @param {Element} elem
2940
+ * @return {Element | null} nextElem
2941
+ * @private
2942
+ */
2943
+ Node.prototype._previousElement = function (elem) {
2944
+ var dom = this.dom;
2945
+ // noinspection FallthroughInSwitchStatementJS
2946
+ switch (elem) {
2947
+ case dom.value:
2948
+ if (this.fieldEditable) {
2949
+ return dom.field;
2950
+ }
2951
+ // intentional fall through
2952
+ case dom.field:
2953
+ if (this._hasChilds()) {
2954
+ return dom.expand;
2955
+ }
2956
+ // intentional fall through
2957
+ case dom.expand:
2958
+ return dom.menu;
2959
+ case dom.menu:
2960
+ if (dom.drag) {
2961
+ return dom.drag;
2962
+ }
2963
+ // intentional fall through
2964
+ default:
2965
+ return null;
2966
+ }
2967
+ };
2968
+
2969
+ /**
2970
+ * Get the next element which can have focus.
2971
+ * @param {Element} elem
2972
+ * @return {Element | null} nextElem
2973
+ * @private
2974
+ */
2975
+ Node.prototype._nextElement = function (elem) {
2976
+ var dom = this.dom;
2977
+ // noinspection FallthroughInSwitchStatementJS
2978
+ switch (elem) {
2979
+ case dom.drag:
2980
+ return dom.menu;
2981
+ case dom.menu:
2982
+ if (this._hasChilds()) {
2983
+ return dom.expand;
2984
+ }
2985
+ // intentional fall through
2986
+ case dom.expand:
2987
+ if (this.fieldEditable) {
2988
+ return dom.field;
2989
+ }
2990
+ // intentional fall through
2991
+ case dom.field:
2992
+ if (!this._hasChilds()) {
2993
+ return dom.value;
2994
+ }
2995
+ default:
2996
+ return null;
2997
+ }
2998
+ };
2999
+
3000
+ /**
3001
+ * Get the dom name of given element. returns null if not found.
3002
+ * For example when element == dom.field, "field" is returned.
3003
+ * @param {Element} element
3004
+ * @return {String | null} elementName Available elements with name: 'drag',
3005
+ * 'menu', 'expand', 'field', 'value'
3006
+ * @private
3007
+ */
3008
+ Node.prototype._getElementName = function (element) {
3009
+ var dom = this.dom;
3010
+ for (var name in dom) {
3011
+ if (dom.hasOwnProperty(name)) {
3012
+ if (dom[name] == element) {
3013
+ return name;
3014
+ }
3015
+ }
3016
+ }
3017
+ return null;
3018
+ };
3019
+
3020
+ /**
3021
+ * Test if this node has childs. This is the case when the node is an object
3022
+ * or array.
3023
+ * @return {boolean} hasChilds
3024
+ * @private
3025
+ */
3026
+ Node.prototype._hasChilds = function () {
3027
+ return this.type == 'array' || this.type == 'object';
3028
+ };
3029
+
3030
+ // titles with explanation for the different types
3031
+ Node.TYPE_TITLES = {
3032
+ 'auto': 'Field type "auto". ' +
3033
+ 'The field type is automatically determined from the value ' +
3034
+ 'and can be a string, number, boolean, or null.',
3035
+ 'object': 'Field type "object". ' +
3036
+ 'An object contains an unordered set of key/value pairs.',
3037
+ 'array': 'Field type "array". ' +
3038
+ 'An array contains an ordered collection of values.',
3039
+ 'string': 'Field type "string". ' +
3040
+ 'Field type is not determined from the value, ' +
3041
+ 'but always returned as string.'
3042
+ };
3043
+
3044
+ /**
3045
+ * Show a contextmenu for this node
3046
+ * @param {HTMLElement} anchor Anchor element to attach the context menu to
3047
+ * as sibling.
3048
+ * @param {function} [onClose] Callback method called when the context menu
3049
+ * is being closed.
3050
+ */
3051
+ Node.prototype.showContextMenu = function (anchor, onClose) {
3052
+ var node = this;
3053
+ var titles = Node.TYPE_TITLES;
3054
+ var items = [];
3055
+
3056
+ if (this.editable.value) {
3057
+ items.push({
3058
+ text: 'Type',
3059
+ title: 'Change the type of this field',
3060
+ className: 'jsoneditor-type-' + this.type,
3061
+ submenu: [
3062
+ {
3063
+ text: 'Auto',
3064
+ className: 'jsoneditor-type-auto' +
3065
+ (this.type == 'auto' ? ' jsoneditor-selected' : ''),
3066
+ title: titles.auto,
3067
+ click: function () {
3068
+ node._onChangeType('auto');
3069
+ }
3070
+ },
3071
+ {
3072
+ text: 'Array',
3073
+ className: 'jsoneditor-type-array' +
3074
+ (this.type == 'array' ? ' jsoneditor-selected' : ''),
3075
+ title: titles.array,
3076
+ click: function () {
3077
+ node._onChangeType('array');
3078
+ }
3079
+ },
3080
+ {
3081
+ text: 'Object',
3082
+ className: 'jsoneditor-type-object' +
3083
+ (this.type == 'object' ? ' jsoneditor-selected' : ''),
3084
+ title: titles.object,
3085
+ click: function () {
3086
+ node._onChangeType('object');
3087
+ }
3088
+ },
3089
+ {
3090
+ text: 'String',
3091
+ className: 'jsoneditor-type-string' +
3092
+ (this.type == 'string' ? ' jsoneditor-selected' : ''),
3093
+ title: titles.string,
3094
+ click: function () {
3095
+ node._onChangeType('string');
3096
+ }
3097
+ }
3098
+ ]
3099
+ });
3100
+ }
3101
+
3102
+ if (this._hasChilds()) {
3103
+ var direction = ((this.sort == 'asc') ? 'desc': 'asc');
3104
+ items.push({
3105
+ text: 'Sort',
3106
+ title: 'Sort the childs of this ' + this.type,
3107
+ className: 'jsoneditor-sort-' + direction,
3108
+ click: function () {
3109
+ node._onSort(direction);
3110
+ },
3111
+ submenu: [
3112
+ {
3113
+ text: 'Ascending',
3114
+ className: 'jsoneditor-sort-asc',
3115
+ title: 'Sort the childs of this ' + this.type + ' in ascending order',
3116
+ click: function () {
3117
+ node._onSort('asc');
3118
+ }
3119
+ },
3120
+ {
3121
+ text: 'Descending',
3122
+ className: 'jsoneditor-sort-desc',
3123
+ title: 'Sort the childs of this ' + this.type +' in descending order',
3124
+ click: function () {
3125
+ node._onSort('desc');
3126
+ }
3127
+ }
3128
+ ]
3129
+ });
3130
+ }
3131
+
3132
+ if (this.parent && this.parent._hasChilds()) {
3133
+ if (items.length) {
3134
+ // create a separator
3135
+ items.push({
3136
+ 'type': 'separator'
3137
+ });
3138
+ }
3139
+
3140
+ // create append button (for last child node only)
3141
+ var childs = node.parent.childs;
3142
+ if (node == childs[childs.length - 1]) {
3143
+ items.push({
3144
+ text: 'Append',
3145
+ title: 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)',
3146
+ submenuTitle: 'Select the type of the field to be appended',
3147
+ className: 'jsoneditor-append',
3148
+ click: function () {
3149
+ node._onAppend('', '', 'auto');
3150
+ },
3151
+ submenu: [
3152
+ {
3153
+ text: 'Auto',
3154
+ className: 'jsoneditor-type-auto',
3155
+ title: titles.auto,
3156
+ click: function () {
3157
+ node._onAppend('', '', 'auto');
3158
+ }
3159
+ },
3160
+ {
3161
+ text: 'Array',
3162
+ className: 'jsoneditor-type-array',
3163
+ title: titles.array,
3164
+ click: function () {
3165
+ node._onAppend('', []);
3166
+ }
3167
+ },
3168
+ {
3169
+ text: 'Object',
3170
+ className: 'jsoneditor-type-object',
3171
+ title: titles.object,
3172
+ click: function () {
3173
+ node._onAppend('', {});
3174
+ }
3175
+ },
3176
+ {
3177
+ text: 'String',
3178
+ className: 'jsoneditor-type-string',
3179
+ title: titles.string,
3180
+ click: function () {
3181
+ node._onAppend('', '', 'string');
3182
+ }
3183
+ }
3184
+ ]
3185
+ });
3186
+ }
3187
+
3188
+ // create insert button
3189
+ items.push({
3190
+ text: 'Insert',
3191
+ title: 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)',
3192
+ submenuTitle: 'Select the type of the field to be inserted',
3193
+ className: 'jsoneditor-insert',
3194
+ click: function () {
3195
+ node._onInsertBefore('', '', 'auto');
3196
+ },
3197
+ submenu: [
3198
+ {
3199
+ text: 'Auto',
3200
+ className: 'jsoneditor-type-auto',
3201
+ title: titles.auto,
3202
+ click: function () {
3203
+ node._onInsertBefore('', '', 'auto');
3204
+ }
3205
+ },
3206
+ {
3207
+ text: 'Array',
3208
+ className: 'jsoneditor-type-array',
3209
+ title: titles.array,
3210
+ click: function () {
3211
+ node._onInsertBefore('', []);
3212
+ }
3213
+ },
3214
+ {
3215
+ text: 'Object',
3216
+ className: 'jsoneditor-type-object',
3217
+ title: titles.object,
3218
+ click: function () {
3219
+ node._onInsertBefore('', {});
3220
+ }
3221
+ },
3222
+ {
3223
+ text: 'String',
3224
+ className: 'jsoneditor-type-string',
3225
+ title: titles.string,
3226
+ click: function () {
3227
+ node._onInsertBefore('', '', 'string');
3228
+ }
3229
+ }
3230
+ ]
3231
+ });
3232
+
3233
+ if (this.editable.field) {
3234
+ // create duplicate button
3235
+ items.push({
3236
+ text: 'Duplicate',
3237
+ title: 'Duplicate this field (Ctrl+D)',
3238
+ className: 'jsoneditor-duplicate',
3239
+ click: function () {
3240
+ Node.onDuplicate(node);
3241
+ }
3242
+ });
3243
+
3244
+ // create remove button
3245
+ items.push({
3246
+ text: 'Remove',
3247
+ title: 'Remove this field (Ctrl+Del)',
3248
+ className: 'jsoneditor-remove',
3249
+ click: function () {
3250
+ Node.onRemove(node);
3251
+ }
3252
+ });
3253
+ }
3254
+ }
3255
+
3256
+ var menu = new ContextMenu(items, {close: onClose});
3257
+ menu.show(anchor, this.editor.content);
3258
+ };
3259
+
3260
+ /**
3261
+ * get the type of a value
3262
+ * @param {*} value
3263
+ * @return {String} type Can be 'object', 'array', 'string', 'auto'
3264
+ * @private
3265
+ */
3266
+ Node.prototype._getType = function(value) {
3267
+ if (value instanceof Array) {
3268
+ return 'array';
3269
+ }
3270
+ if (value instanceof Object) {
3271
+ return 'object';
3272
+ }
3273
+ if (typeof(value) == 'string' && typeof(this._stringCast(value)) != 'string') {
3274
+ return 'string';
3275
+ }
3276
+
3277
+ return 'auto';
3278
+ };
3279
+
3280
+ /**
3281
+ * cast contents of a string to the correct type. This can be a string,
3282
+ * a number, a boolean, etc
3283
+ * @param {String} str
3284
+ * @return {*} castedStr
3285
+ * @private
3286
+ */
3287
+ Node.prototype._stringCast = function(str) {
3288
+ var lower = str.toLowerCase(),
3289
+ num = Number(str), // will nicely fail with '123ab'
3290
+ numFloat = parseFloat(str); // will nicely fail with ' '
3291
+
3292
+ if (str == '') {
3293
+ return '';
3294
+ }
3295
+ else if (lower == 'null') {
3296
+ return null;
3297
+ }
3298
+ else if (lower == 'true') {
3299
+ return true;
3300
+ }
3301
+ else if (lower == 'false') {
3302
+ return false;
3303
+ }
3304
+ else if (!isNaN(num) && !isNaN(numFloat)) {
3305
+ return num;
3306
+ }
3307
+ else {
3308
+ return str;
3309
+ }
3310
+ };
3311
+
3312
+ /**
3313
+ * escape a text, such that it can be displayed safely in an HTML element
3314
+ * @param {String} text
3315
+ * @return {String} escapedText
3316
+ * @private
3317
+ */
3318
+ Node.prototype._escapeHTML = function (text) {
3319
+ if (typeof text !== 'string') {
3320
+ return String(text);
3321
+ }
3322
+ else {
3323
+ var htmlEscaped = String(text)
3324
+ .replace(/&/g, '&amp;') // must be replaced first!
3325
+ .replace(/</g, '&lt;')
3326
+ .replace(/>/g, '&gt;')
3327
+ .replace(/ /g, ' &nbsp;') // replace double space with an nbsp and space
3328
+ .replace(/^ /, '&nbsp;') // space at start
3329
+ .replace(/ $/, '&nbsp;'); // space at end
3330
+
3331
+ var json = JSON.stringify(htmlEscaped);
3332
+ var html = json.substring(1, json.length - 1);
3333
+ if (this.editor.options.escapeUnicode === true) {
3334
+ html = util.escapeUnicodeChars(html);
3335
+ }
3336
+ return html;
3337
+ }
3338
+ };
3339
+
3340
+ /**
3341
+ * unescape a string.
3342
+ * @param {String} escapedText
3343
+ * @return {String} text
3344
+ * @private
3345
+ */
3346
+ Node.prototype._unescapeHTML = function (escapedText) {
3347
+ var json = '"' + this._escapeJSON(escapedText) + '"';
3348
+ var htmlEscaped = util.parse(json);
3349
+
3350
+ return htmlEscaped
3351
+ .replace(/&lt;/g, '<')
3352
+ .replace(/&gt;/g, '>')
3353
+ .replace(/&nbsp;|\u00A0/g, ' ')
3354
+ .replace(/&amp;/g, '&'); // must be replaced last
3355
+ };
3356
+
3357
+ /**
3358
+ * escape a text to make it a valid JSON string. The method will:
3359
+ * - replace unescaped double quotes with '\"'
3360
+ * - replace unescaped backslash with '\\'
3361
+ * - replace returns with '\n'
3362
+ * @param {String} text
3363
+ * @return {String} escapedText
3364
+ * @private
3365
+ */
3366
+ Node.prototype._escapeJSON = function (text) {
3367
+ // TODO: replace with some smart regex (only when a new solution is faster!)
3368
+ var escaped = '';
3369
+ var i = 0;
3370
+ while (i < text.length) {
3371
+ var c = text.charAt(i);
3372
+ if (c == '\n') {
3373
+ escaped += '\\n';
3374
+ }
3375
+ else if (c == '\\') {
3376
+ escaped += c;
3377
+ i++;
3378
+
3379
+ c = text.charAt(i);
3380
+ if (c === '' || '"\\/bfnrtu'.indexOf(c) == -1) {
3381
+ escaped += '\\'; // no valid escape character
3382
+ }
3383
+ escaped += c;
3384
+ }
3385
+ else if (c == '"') {
3386
+ escaped += '\\"';
3387
+ }
3388
+ else {
3389
+ escaped += c;
3390
+ }
3391
+ i++;
3392
+ }
3393
+
3394
+ return escaped;
3395
+ };
3396
+
3397
+ // TODO: find a nicer solution to resolve this circular dependency between Node and AppendNode
3398
+ var AppendNode = appendNodeFactory(Node);
3399
+
3400
+ module.exports = Node;