statixite 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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;