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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +145 -0
- data/Rakefile +26 -0
- data/app/assets/images/statixite/STATIXITE-logo.png +0 -0
- data/app/assets/images/statixite/favicon.ico +0 -0
- data/app/assets/images/statixite/jekyll.png +0 -0
- data/app/assets/images/statixite/md-editor.png +0 -0
- data/app/assets/images/statixite/ring-alt.gif +0 -0
- data/app/assets/javascripts/controllers/statixite/deployments_create.js +13 -0
- data/app/assets/javascripts/controllers/statixite/deployments_index.js +13 -0
- data/app/assets/javascripts/controllers/statixite/sites_create.js +27 -0
- data/app/assets/javascripts/controllers/statixite/sites_new.js +27 -0
- data/app/assets/javascripts/controllers/statixite/templates_edit.js +208 -0
- data/app/assets/javascripts/statixite/application.js +8 -0
- data/app/assets/javascripts/statixite/array-helpers.js +13 -0
- data/app/assets/javascripts/statixite/editor.js +244 -0
- data/app/assets/javascripts/statixite/loader.js +14 -0
- data/app/assets/javascripts/statixite/notify-defaults.js +5 -0
- data/app/assets/stylesheets/statixite/application.less +271 -0
- data/app/assets/stylesheets/statixite/bootstrap-glyphicons.less +305 -0
- data/app/assets/stylesheets/statixite/bootstrap.less +58 -0
- data/app/assets/stylesheets/statixite/bootstrap_and_overrides.less +2 -0
- data/app/assets/stylesheets/statixite/landing-page.less +156 -0
- data/app/controllers/statixite/application_controller.rb +22 -0
- data/app/controllers/statixite/deployments_controller.rb +61 -0
- data/app/controllers/statixite/media_controller.rb +61 -0
- data/app/controllers/statixite/posts_controller.rb +68 -0
- data/app/controllers/statixite/sites_controller.rb +141 -0
- data/app/controllers/statixite/templates_controller.rb +233 -0
- data/app/helpers/statixite/application_helper.rb +63 -0
- data/app/models/statixite/deployment.rb +5 -0
- data/app/models/statixite/media.rb +7 -0
- data/app/models/statixite/post.rb +76 -0
- data/app/models/statixite/site.rb +76 -0
- data/app/services/statixite/deployment_service.rb +73 -0
- data/app/services/statixite/git_service.rb +102 -0
- data/app/services/statixite/site_deactivation_service.rb +52 -0
- data/app/services/statixite/site_operation_service.rb +251 -0
- data/app/uploaders/statixite/file_uploader.rb +14 -0
- data/app/validators/statixite/jekyll_template_validator.rb +16 -0
- data/app/validators/statixite/liquid_validator.rb +11 -0
- data/app/views/layouts/statixite/_dash_nav.html.haml +20 -0
- data/app/views/layouts/statixite/_flash.html.haml +13 -0
- data/app/views/layouts/statixite/_head.html.haml +12 -0
- data/app/views/layouts/statixite/_header.html.haml +30 -0
- data/app/views/layouts/statixite/_site_sub_items.html.haml +38 -0
- data/app/views/layouts/statixite/dashboard.html.haml +27 -0
- data/app/views/statixite/deployments/_modal.html.haml +18 -0
- data/app/views/statixite/deployments/index.html.haml +39 -0
- data/app/views/statixite/kaminari/_first_page.html.haml +2 -0
- data/app/views/statixite/kaminari/_gap.html.haml +2 -0
- data/app/views/statixite/kaminari/_last_page.html.haml +2 -0
- data/app/views/statixite/kaminari/_next_page.html.haml +2 -0
- data/app/views/statixite/kaminari/_page.html.haml +6 -0
- data/app/views/statixite/kaminari/_paginator.html.haml +11 -0
- data/app/views/statixite/kaminari/_prev_page.html.haml +2 -0
- data/app/views/statixite/media/_index.html.haml +13 -0
- data/app/views/statixite/media/_paginate.html.haml +1 -0
- data/app/views/statixite/media/index.html.haml +39 -0
- data/app/views/statixite/media/index.js.haml +2 -0
- data/app/views/statixite/posts/_form.html.haml +65 -0
- data/app/views/statixite/posts/_image_modal.html.haml +21 -0
- data/app/views/statixite/posts/_modals.html.haml +111 -0
- data/app/views/statixite/posts/_posts.html.haml +15 -0
- data/app/views/statixite/posts/edit.html.haml +6 -0
- data/app/views/statixite/posts/index.html.haml +4 -0
- data/app/views/statixite/posts/index.js.haml +1 -0
- data/app/views/statixite/posts/new.html.haml +5 -0
- data/app/views/statixite/sites/_sites.html.haml +27 -0
- data/app/views/statixite/sites/_templates.html.haml +26 -0
- data/app/views/statixite/sites/build_and_preview.html.haml +24 -0
- data/app/views/statixite/sites/index.html.haml +4 -0
- data/app/views/statixite/sites/index.js.haml +1 -0
- data/app/views/statixite/sites/new.html.haml +45 -0
- data/app/views/statixite/sites/settings.html.haml +77 -0
- data/app/views/statixite/templates/edit.html.haml +93 -0
- data/app/views/statixite/templates/upload_files.js.haml +2 -0
- data/config/routes.rb +24 -0
- data/db/migrate/20150525171543_create_statixite_sites.rb +15 -0
- data/db/migrate/20150530041055_create_statixite_posts.rb +14 -0
- data/db/migrate/20150610024047_create_statixite_deployments.rb +11 -0
- data/db/migrate/20151013131224_create_statixite_media.rb +10 -0
- data/lib/assets/templates.yaml +734 -0
- data/lib/generators/statixite/statixite_generator.rb +49 -0
- data/lib/statixite.rb +14 -0
- data/lib/statixite/cloud_sync.rb +104 -0
- data/lib/statixite/engine.rb +49 -0
- data/lib/statixite/version.rb +3 -0
- data/lib/tasks/statixite_tasks.rake +4 -0
- data/vendor/assets/bower_components/bootstrap/CHANGELOG.md +5 -0
- data/vendor/assets/bower_components/bootstrap/Gruntfile.js +533 -0
- data/vendor/assets/bower_components/bootstrap/LICENSE +21 -0
- data/vendor/assets/bower_components/bootstrap/README.md +139 -0
- data/vendor/assets/bower_components/bootstrap/bower.json +34 -0
- data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap-theme.css +587 -0
- data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap-theme.css.map +1 -0
- data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap-theme.min.css +6 -0
- data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap-theme.min.css.map +1 -0
- data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap.css +6760 -0
- data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap.css.map +1 -0
- data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap.min.css +6 -0
- data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap.min.css.map +1 -0
- data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.eot +0 -0
- data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.svg +288 -0
- data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff +0 -0
- data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/vendor/assets/bower_components/bootstrap/dist/js/bootstrap.js +2363 -0
- data/vendor/assets/bower_components/bootstrap/dist/js/bootstrap.min.js +7 -0
- data/vendor/assets/bower_components/bootstrap/dist/js/npm.js +13 -0
- data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
- data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
- data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
- data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/vendor/assets/bower_components/bootstrap/grunt/bs-commonjs-generator.js +30 -0
- data/vendor/assets/bower_components/bootstrap/grunt/bs-glyphicons-data-generator.js +42 -0
- data/vendor/assets/bower_components/bootstrap/grunt/bs-lessdoc-parser.js +237 -0
- data/vendor/assets/bower_components/bootstrap/grunt/bs-raw-files-generator.js +44 -0
- data/vendor/assets/bower_components/bootstrap/grunt/configBridge.json +46 -0
- data/vendor/assets/bower_components/bootstrap/grunt/sauce_browsers.yml +82 -0
- data/vendor/assets/bower_components/bootstrap/js/affix.js +162 -0
- data/vendor/assets/bower_components/bootstrap/js/alert.js +94 -0
- data/vendor/assets/bower_components/bootstrap/js/button.js +120 -0
- data/vendor/assets/bower_components/bootstrap/js/carousel.js +237 -0
- data/vendor/assets/bower_components/bootstrap/js/collapse.js +211 -0
- data/vendor/assets/bower_components/bootstrap/js/dropdown.js +165 -0
- data/vendor/assets/bower_components/bootstrap/js/modal.js +337 -0
- data/vendor/assets/bower_components/bootstrap/js/popover.js +108 -0
- data/vendor/assets/bower_components/bootstrap/js/scrollspy.js +172 -0
- data/vendor/assets/bower_components/bootstrap/js/tab.js +155 -0
- data/vendor/assets/bower_components/bootstrap/js/tooltip.js +514 -0
- data/vendor/assets/bower_components/bootstrap/js/transition.js +59 -0
- data/vendor/assets/bower_components/bootstrap/less/alerts.less +73 -0
- data/vendor/assets/bower_components/bootstrap/less/badges.less +66 -0
- data/vendor/assets/bower_components/bootstrap/less/bootstrap.less +56 -0
- data/vendor/assets/bower_components/bootstrap/less/breadcrumbs.less +26 -0
- data/vendor/assets/bower_components/bootstrap/less/button-groups.less +244 -0
- data/vendor/assets/bower_components/bootstrap/less/buttons.less +166 -0
- data/vendor/assets/bower_components/bootstrap/less/carousel.less +270 -0
- data/vendor/assets/bower_components/bootstrap/less/close.less +34 -0
- data/vendor/assets/bower_components/bootstrap/less/code.less +69 -0
- data/vendor/assets/bower_components/bootstrap/less/component-animations.less +33 -0
- data/vendor/assets/bower_components/bootstrap/less/dropdowns.less +216 -0
- data/vendor/assets/bower_components/bootstrap/less/forms.less +613 -0
- data/vendor/assets/bower_components/bootstrap/less/glyphicons.less +305 -0
- data/vendor/assets/bower_components/bootstrap/less/grid.less +84 -0
- data/vendor/assets/bower_components/bootstrap/less/input-groups.less +171 -0
- data/vendor/assets/bower_components/bootstrap/less/jumbotron.less +54 -0
- data/vendor/assets/bower_components/bootstrap/less/labels.less +64 -0
- data/vendor/assets/bower_components/bootstrap/less/list-group.less +130 -0
- data/vendor/assets/bower_components/bootstrap/less/media.less +66 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins.less +40 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/alerts.less +14 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/background-variant.less +9 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/border-radius.less +18 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/buttons.less +65 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/center-block.less +7 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/clearfix.less +22 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/forms.less +85 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/gradients.less +59 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/grid-framework.less +91 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/grid.less +122 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/hide-text.less +21 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/image.less +33 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/labels.less +12 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/list-group.less +30 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/nav-divider.less +10 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/nav-vertical-align.less +9 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/opacity.less +8 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/pagination.less +24 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/panels.less +24 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/progress-bar.less +10 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/reset-filter.less +8 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/reset-text.less +18 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/resize.less +6 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/responsive-visibility.less +15 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/size.less +10 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/tab-focus.less +9 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/table-row.less +28 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/text-emphasis.less +9 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/text-overflow.less +8 -0
- data/vendor/assets/bower_components/bootstrap/less/mixins/vendor-prefixes.less +227 -0
- data/vendor/assets/bower_components/bootstrap/less/modals.less +150 -0
- data/vendor/assets/bower_components/bootstrap/less/navbar.less +660 -0
- data/vendor/assets/bower_components/bootstrap/less/navs.less +242 -0
- data/vendor/assets/bower_components/bootstrap/less/normalize.less +424 -0
- data/vendor/assets/bower_components/bootstrap/less/pager.less +54 -0
- data/vendor/assets/bower_components/bootstrap/less/pagination.less +89 -0
- data/vendor/assets/bower_components/bootstrap/less/panels.less +271 -0
- data/vendor/assets/bower_components/bootstrap/less/popovers.less +131 -0
- data/vendor/assets/bower_components/bootstrap/less/print.less +101 -0
- data/vendor/assets/bower_components/bootstrap/less/progress-bars.less +87 -0
- data/vendor/assets/bower_components/bootstrap/less/responsive-embed.less +35 -0
- data/vendor/assets/bower_components/bootstrap/less/responsive-utilities.less +194 -0
- data/vendor/assets/bower_components/bootstrap/less/scaffolding.less +161 -0
- data/vendor/assets/bower_components/bootstrap/less/tables.less +234 -0
- data/vendor/assets/bower_components/bootstrap/less/theme.less +291 -0
- data/vendor/assets/bower_components/bootstrap/less/thumbnails.less +36 -0
- data/vendor/assets/bower_components/bootstrap/less/tooltip.less +101 -0
- data/vendor/assets/bower_components/bootstrap/less/type.less +302 -0
- data/vendor/assets/bower_components/bootstrap/less/utilities.less +55 -0
- data/vendor/assets/bower_components/bootstrap/less/variables.less +869 -0
- data/vendor/assets/bower_components/bootstrap/less/wells.less +29 -0
- data/vendor/assets/bower_components/bootstrap/nuget/MyGet.ps1 +8 -0
- data/vendor/assets/bower_components/bootstrap/nuget/bootstrap.less.nuspec +28 -0
- data/vendor/assets/bower_components/bootstrap/nuget/bootstrap.nuspec +28 -0
- data/vendor/assets/bower_components/bootstrap/package.js +32 -0
- data/vendor/assets/bower_components/bootstrap/package.json +87 -0
- data/vendor/assets/bower_components/jquery/AUTHORS.txt +275 -0
- data/vendor/assets/bower_components/jquery/LICENSE.txt +36 -0
- data/vendor/assets/bower_components/jquery/README.md +5 -0
- data/vendor/assets/bower_components/jquery/bower.json +14 -0
- data/vendor/assets/bower_components/jquery/dist/jquery.js +9831 -0
- data/vendor/assets/bower_components/jquery/dist/jquery.min.js +4 -0
- data/vendor/assets/bower_components/jquery/dist/jquery.min.map +1 -0
- data/vendor/assets/bower_components/jquery/src/ajax.js +845 -0
- data/vendor/assets/bower_components/jquery/src/ajax/jsonp.js +100 -0
- data/vendor/assets/bower_components/jquery/src/ajax/load.js +83 -0
- data/vendor/assets/bower_components/jquery/src/ajax/parseJSON.js +13 -0
- data/vendor/assets/bower_components/jquery/src/ajax/parseXML.js +27 -0
- data/vendor/assets/bower_components/jquery/src/ajax/script.js +68 -0
- data/vendor/assets/bower_components/jquery/src/ajax/var/location.js +3 -0
- data/vendor/assets/bower_components/jquery/src/ajax/var/nonce.js +5 -0
- data/vendor/assets/bower_components/jquery/src/ajax/var/rquery.js +3 -0
- data/vendor/assets/bower_components/jquery/src/ajax/xhr.js +167 -0
- data/vendor/assets/bower_components/jquery/src/attributes.js +11 -0
- data/vendor/assets/bower_components/jquery/src/attributes/attr.js +142 -0
- data/vendor/assets/bower_components/jquery/src/attributes/classes.js +177 -0
- data/vendor/assets/bower_components/jquery/src/attributes/prop.js +109 -0
- data/vendor/assets/bower_components/jquery/src/attributes/support.js +36 -0
- data/vendor/assets/bower_components/jquery/src/attributes/val.js +170 -0
- data/vendor/assets/bower_components/jquery/src/callbacks.js +232 -0
- data/vendor/assets/bower_components/jquery/src/core.js +489 -0
- data/vendor/assets/bower_components/jquery/src/core/access.js +65 -0
- data/vendor/assets/bower_components/jquery/src/core/init.js +134 -0
- data/vendor/assets/bower_components/jquery/src/core/parseHTML.js +49 -0
- data/vendor/assets/bower_components/jquery/src/core/ready.js +103 -0
- data/vendor/assets/bower_components/jquery/src/core/support.js +18 -0
- data/vendor/assets/bower_components/jquery/src/core/var/rsingleTag.js +5 -0
- data/vendor/assets/bower_components/jquery/src/css.js +515 -0
- data/vendor/assets/bower_components/jquery/src/css/addGetHookIf.js +24 -0
- data/vendor/assets/bower_components/jquery/src/css/adjustCSS.js +65 -0
- data/vendor/assets/bower_components/jquery/src/css/curCSS.js +57 -0
- data/vendor/assets/bower_components/jquery/src/css/defaultDisplay.js +72 -0
- data/vendor/assets/bower_components/jquery/src/css/hiddenVisibleSelectors.js +18 -0
- data/vendor/assets/bower_components/jquery/src/css/showHide.js +48 -0
- data/vendor/assets/bower_components/jquery/src/css/support.js +121 -0
- data/vendor/assets/bower_components/jquery/src/css/var/cssExpand.js +3 -0
- data/vendor/assets/bower_components/jquery/src/css/var/getStyles.js +15 -0
- data/vendor/assets/bower_components/jquery/src/css/var/isHidden.js +16 -0
- data/vendor/assets/bower_components/jquery/src/css/var/rmargin.js +3 -0
- data/vendor/assets/bower_components/jquery/src/css/var/rnumnonpx.js +5 -0
- data/vendor/assets/bower_components/jquery/src/css/var/swap.js +24 -0
- data/vendor/assets/bower_components/jquery/src/data.js +187 -0
- data/vendor/assets/bower_components/jquery/src/data/Data.js +200 -0
- data/vendor/assets/bower_components/jquery/src/data/accepts.js +20 -0
- data/vendor/assets/bower_components/jquery/src/data/support.js +23 -0
- data/vendor/assets/bower_components/jquery/src/data/var/acceptData.js +18 -0
- data/vendor/assets/bower_components/jquery/src/data/var/dataPriv.js +5 -0
- data/vendor/assets/bower_components/jquery/src/data/var/dataUser.js +5 -0
- data/vendor/assets/bower_components/jquery/src/deferred.js +158 -0
- data/vendor/assets/bower_components/jquery/src/deprecated.js +32 -0
- data/vendor/assets/bower_components/jquery/src/dimensions.js +54 -0
- data/vendor/assets/bower_components/jquery/src/effects.js +629 -0
- data/vendor/assets/bower_components/jquery/src/effects/Tween.js +121 -0
- data/vendor/assets/bower_components/jquery/src/effects/animatedSelector.js +13 -0
- data/vendor/assets/bower_components/jquery/src/effects/support.js +58 -0
- data/vendor/assets/bower_components/jquery/src/event.js +710 -0
- data/vendor/assets/bower_components/jquery/src/event/ajax.js +20 -0
- data/vendor/assets/bower_components/jquery/src/event/alias.js +27 -0
- data/vendor/assets/bower_components/jquery/src/event/focusin.js +53 -0
- data/vendor/assets/bower_components/jquery/src/event/support.js +9 -0
- data/vendor/assets/bower_components/jquery/src/event/trigger.js +199 -0
- data/vendor/assets/bower_components/jquery/src/exports/amd.js +24 -0
- data/vendor/assets/bower_components/jquery/src/exports/global.js +26 -0
- data/vendor/assets/bower_components/jquery/src/intro.js +44 -0
- data/vendor/assets/bower_components/jquery/src/jquery.js +37 -0
- data/vendor/assets/bower_components/jquery/src/manipulation.js +481 -0
- data/vendor/assets/bower_components/jquery/src/manipulation/_evalUrl.js +20 -0
- data/vendor/assets/bower_components/jquery/src/manipulation/buildFragment.js +102 -0
- data/vendor/assets/bower_components/jquery/src/manipulation/createSafeFragment.js +20 -0
- data/vendor/assets/bower_components/jquery/src/manipulation/getAll.js +21 -0
- data/vendor/assets/bower_components/jquery/src/manipulation/setGlobalEval.js +20 -0
- data/vendor/assets/bower_components/jquery/src/manipulation/support.js +33 -0
- data/vendor/assets/bower_components/jquery/src/manipulation/var/nodeNames.js +5 -0
- data/vendor/assets/bower_components/jquery/src/manipulation/var/rcheckableType.js +3 -0
- data/vendor/assets/bower_components/jquery/src/manipulation/var/rleadingWhitespace.js +3 -0
- data/vendor/assets/bower_components/jquery/src/manipulation/var/rscriptType.js +3 -0
- data/vendor/assets/bower_components/jquery/src/manipulation/var/rtagName.js +3 -0
- data/vendor/assets/bower_components/jquery/src/manipulation/wrapMap.js +27 -0
- data/vendor/assets/bower_components/jquery/src/offset.js +221 -0
- data/vendor/assets/bower_components/jquery/src/outro.js +2 -0
- data/vendor/assets/bower_components/jquery/src/queue.js +143 -0
- data/vendor/assets/bower_components/jquery/src/queue/delay.js +22 -0
- data/vendor/assets/bower_components/jquery/src/selector-native.js +211 -0
- data/vendor/assets/bower_components/jquery/src/selector-sizzle.js +14 -0
- data/vendor/assets/bower_components/jquery/src/selector.js +1 -0
- data/vendor/assets/bower_components/jquery/src/serialize.js +125 -0
- data/vendor/assets/bower_components/jquery/src/support.js +63 -0
- data/vendor/assets/bower_components/jquery/src/traversing.js +175 -0
- data/vendor/assets/bower_components/jquery/src/traversing/findFilter.js +100 -0
- data/vendor/assets/bower_components/jquery/src/traversing/var/dir.js +20 -0
- data/vendor/assets/bower_components/jquery/src/traversing/var/rneedsContext.js +6 -0
- data/vendor/assets/bower_components/jquery/src/traversing/var/siblings.js +15 -0
- data/vendor/assets/bower_components/jquery/src/var/arr.js +3 -0
- data/vendor/assets/bower_components/jquery/src/var/class2type.js +5 -0
- data/vendor/assets/bower_components/jquery/src/var/concat.js +5 -0
- data/vendor/assets/bower_components/jquery/src/var/deletedIds.js +3 -0
- data/vendor/assets/bower_components/jquery/src/var/document.js +3 -0
- data/vendor/assets/bower_components/jquery/src/var/documentElement.js +5 -0
- data/vendor/assets/bower_components/jquery/src/var/hasOwn.js +5 -0
- data/vendor/assets/bower_components/jquery/src/var/indexOf.js +5 -0
- data/vendor/assets/bower_components/jquery/src/var/pnum.js +3 -0
- data/vendor/assets/bower_components/jquery/src/var/push.js +5 -0
- data/vendor/assets/bower_components/jquery/src/var/rcssNum.js +7 -0
- data/vendor/assets/bower_components/jquery/src/var/rnotwhite.js +3 -0
- data/vendor/assets/bower_components/jquery/src/var/slice.js +5 -0
- data/vendor/assets/bower_components/jquery/src/var/support.js +5 -0
- data/vendor/assets/bower_components/jquery/src/var/toString.js +5 -0
- data/vendor/assets/bower_components/jquery/src/wrap.js +79 -0
- data/vendor/assets/bower_components/jsoneditor/CONTRIBUTING.md +15 -0
- data/vendor/assets/bower_components/jsoneditor/HISTORY.md +457 -0
- data/vendor/assets/bower_components/jsoneditor/LICENSE +176 -0
- data/vendor/assets/bower_components/jsoneditor/NOTICE +17 -0
- data/vendor/assets/bower_components/jsoneditor/README.md +155 -0
- data/vendor/assets/bower_components/jsoneditor/bower.json +32 -0
- data/vendor/assets/bower_components/jsoneditor/dist/img/jsoneditor-icons.svg +893 -0
- data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor-minimalist.js +9418 -0
- data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor-minimalist.map +1 -0
- data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor-minimalist.min.js +35 -0
- data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor.css.less +879 -0
- data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor.js +35095 -0
- data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor.map +1 -0
- data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor.min.css +1 -0
- data/vendor/assets/bower_components/jsoneditor/dist/jsoneditor.min.js +48 -0
- data/vendor/assets/bower_components/jsoneditor/dist/which files do I need.md +41 -0
- data/vendor/assets/bower_components/jsoneditor/docs/api.md +286 -0
- data/vendor/assets/bower_components/jsoneditor/docs/shortcut_keys.md +38 -0
- data/vendor/assets/bower_components/jsoneditor/docs/usage.md +117 -0
- data/vendor/assets/bower_components/jsoneditor/examples/01_basic_usage.html +49 -0
- data/vendor/assets/bower_components/jsoneditor/examples/02_viewer.html +44 -0
- data/vendor/assets/bower_components/jsoneditor/examples/03_switch_mode.html +66 -0
- data/vendor/assets/bower_components/jsoneditor/examples/04_load_and_save.html +62 -0
- data/vendor/assets/bower_components/jsoneditor/examples/05_custom_fields_editable.html +63 -0
- data/vendor/assets/bower_components/jsoneditor/examples/06_custom_styling.html +51 -0
- data/vendor/assets/bower_components/jsoneditor/examples/07_json_schema_validation.html +71 -0
- data/vendor/assets/bower_components/jsoneditor/examples/css/darktheme.css +76 -0
- data/vendor/assets/bower_components/jsoneditor/examples/requirejs_demo/requirejs_demo.html +21 -0
- data/vendor/assets/bower_components/jsoneditor/examples/requirejs_demo/scripts/main.js +25 -0
- data/vendor/assets/bower_components/jsoneditor/examples/requirejs_demo/scripts/require.js +36 -0
- data/vendor/assets/bower_components/jsoneditor/index.js +1 -0
- data/vendor/assets/bower_components/jsoneditor/package.json +41 -0
- data/vendor/assets/bower_components/jsoneditor/src/css/contextmenu.css +233 -0
- data/vendor/assets/bower_components/jsoneditor/src/css/img/description.txt +14 -0
- data/vendor/assets/bower_components/jsoneditor/src/css/img/jsoneditor-icons.svg +893 -0
- data/vendor/assets/bower_components/jsoneditor/src/css/jsoneditor.css +433 -0
- data/vendor/assets/bower_components/jsoneditor/src/css/menu.css +110 -0
- data/vendor/assets/bower_components/jsoneditor/src/css/searchbox.css +75 -0
- data/vendor/assets/bower_components/jsoneditor/src/docs/which files do I need.md +41 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/ContextMenu.js +455 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/Highlighter.js +84 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/History.js +252 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/JSONEditor.js +371 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/Node.js +3400 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/SearchBox.js +295 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/ace/index.js +9 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/ace/theme-jsoneditor.js +144 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/appendNodeFactory.js +226 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/assets/jsonlint/README.md +15 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/assets/jsonlint/jsonlint.js +418 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/header.js +29 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/modeswitcher.js +105 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/textmode.js +474 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/treemode.js +1150 -0
- data/vendor/assets/bower_components/jsoneditor/src/js/util.js +765 -0
- data/vendor/assets/bower_components/notifyjs/CHANGES.md +7 -0
- data/vendor/assets/bower_components/notifyjs/README.md +31 -0
- data/vendor/assets/bower_components/notifyjs/bower.json +14 -0
- data/vendor/assets/bower_components/notifyjs/dist/notify.js +586 -0
- data/vendor/assets/bower_components/notifyjs/dist/styles/metro/notify-metro.css +40 -0
- data/vendor/assets/bower_components/notifyjs/dist/styles/metro/notify-metro.js +39 -0
- data/vendor/assets/bower_components/notifyjs/examples/classes.html +49 -0
- data/vendor/assets/bower_components/notifyjs/examples/images/Mail.png +0 -0
- data/vendor/assets/bower_components/notifyjs/examples/inlines.html +111 -0
- data/vendor/assets/bower_components/notifyjs/examples/metro.html +37 -0
- data/vendor/assets/bower_components/notifyjs/examples/multi-text.html +55 -0
- data/vendor/assets/bower_components/notifyjs/examples/position.html +42 -0
- data/vendor/assets/bower_components/notifyjs/notify.jquery.json +28 -0
- data/vendor/assets/bower_components/notifyjs/package.json +18 -0
- data/vendor/assets/bower_components/vue/LICENSE +21 -0
- data/vendor/assets/bower_components/vue/bower.json +19 -0
- data/vendor/assets/bower_components/vue/dist/vue.js +10198 -0
- data/vendor/assets/bower_components/vue/dist/vue.min.js +8 -0
- data/vendor/assets/bower_components/vue/src/api/child.js +49 -0
- data/vendor/assets/bower_components/vue/src/api/data.js +159 -0
- data/vendor/assets/bower_components/vue/src/api/dom.js +226 -0
- data/vendor/assets/bower_components/vue/src/api/events.js +174 -0
- data/vendor/assets/bower_components/vue/src/api/global.js +129 -0
- data/vendor/assets/bower_components/vue/src/api/lifecycle.js +68 -0
- data/vendor/assets/bower_components/vue/src/batcher.js +98 -0
- data/vendor/assets/bower_components/vue/src/cache.js +112 -0
- data/vendor/assets/bower_components/vue/src/compiler/compile-props.js +183 -0
- data/vendor/assets/bower_components/vue/src/compiler/compile.js +630 -0
- data/vendor/assets/bower_components/vue/src/compiler/index.js +4 -0
- data/vendor/assets/bower_components/vue/src/compiler/transclude.js +144 -0
- data/vendor/assets/bower_components/vue/src/config.js +124 -0
- data/vendor/assets/bower_components/vue/src/directive.js +254 -0
- data/vendor/assets/bower_components/vue/src/directives/attr.js +59 -0
- data/vendor/assets/bower_components/vue/src/directives/class.js +70 -0
- data/vendor/assets/bower_components/vue/src/directives/cloak.js +10 -0
- data/vendor/assets/bower_components/vue/src/directives/component.js +344 -0
- data/vendor/assets/bower_components/vue/src/directives/el.js +12 -0
- data/vendor/assets/bower_components/vue/src/directives/html.js +40 -0
- data/vendor/assets/bower_components/vue/src/directives/if.js +125 -0
- data/vendor/assets/bower_components/vue/src/directives/index.js +24 -0
- data/vendor/assets/bower_components/vue/src/directives/model/checkbox.js +42 -0
- data/vendor/assets/bower_components/vue/src/directives/model/index.js +82 -0
- data/vendor/assets/bower_components/vue/src/directives/model/radio.js +33 -0
- data/vendor/assets/bower_components/vue/src/directives/model/select.js +236 -0
- data/vendor/assets/bower_components/vue/src/directives/model/text.js +132 -0
- data/vendor/assets/bower_components/vue/src/directives/on.js +59 -0
- data/vendor/assets/bower_components/vue/src/directives/prop.js +62 -0
- data/vendor/assets/bower_components/vue/src/directives/ref.js +22 -0
- data/vendor/assets/bower_components/vue/src/directives/repeat.js +770 -0
- data/vendor/assets/bower_components/vue/src/directives/show.js +8 -0
- data/vendor/assets/bower_components/vue/src/directives/style.js +110 -0
- data/vendor/assets/bower_components/vue/src/directives/text.js +14 -0
- data/vendor/assets/bower_components/vue/src/directives/transition.js +26 -0
- data/vendor/assets/bower_components/vue/src/element-directives/content.js +111 -0
- data/vendor/assets/bower_components/vue/src/element-directives/index.js +2 -0
- data/vendor/assets/bower_components/vue/src/element-directives/partial.js +73 -0
- data/vendor/assets/bower_components/vue/src/filters/array-filters.js +97 -0
- data/vendor/assets/bower_components/vue/src/filters/index.js +146 -0
- data/vendor/assets/bower_components/vue/src/instance/compile.js +200 -0
- data/vendor/assets/bower_components/vue/src/instance/events.js +139 -0
- data/vendor/assets/bower_components/vue/src/instance/init.js +89 -0
- data/vendor/assets/bower_components/vue/src/instance/misc.js +93 -0
- data/vendor/assets/bower_components/vue/src/instance/scope.js +282 -0
- data/vendor/assets/bower_components/vue/src/observer/array.js +98 -0
- data/vendor/assets/bower_components/vue/src/observer/dep.js +61 -0
- data/vendor/assets/bower_components/vue/src/observer/index.js +234 -0
- data/vendor/assets/bower_components/vue/src/observer/object.js +82 -0
- data/vendor/assets/bower_components/vue/src/parsers/directive.js +180 -0
- data/vendor/assets/bower_components/vue/src/parsers/expression.js +264 -0
- data/vendor/assets/bower_components/vue/src/parsers/path.js +348 -0
- data/vendor/assets/bower_components/vue/src/parsers/template.js +288 -0
- data/vendor/assets/bower_components/vue/src/parsers/text.js +178 -0
- data/vendor/assets/bower_components/vue/src/transition/index.js +128 -0
- data/vendor/assets/bower_components/vue/src/transition/queue.js +35 -0
- data/vendor/assets/bower_components/vue/src/transition/transition.js +357 -0
- data/vendor/assets/bower_components/vue/src/util/component.js +124 -0
- data/vendor/assets/bower_components/vue/src/util/debug.js +64 -0
- data/vendor/assets/bower_components/vue/src/util/dom.js +272 -0
- data/vendor/assets/bower_components/vue/src/util/env.js +85 -0
- data/vendor/assets/bower_components/vue/src/util/index.js +9 -0
- data/vendor/assets/bower_components/vue/src/util/lang.js +310 -0
- data/vendor/assets/bower_components/vue/src/util/options.js +357 -0
- data/vendor/assets/bower_components/vue/src/vue.js +89 -0
- data/vendor/assets/bower_components/vue/src/watcher.js +312 -0
- 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, '&') // must be replaced first!
|
|
3325
|
+
.replace(/</g, '<')
|
|
3326
|
+
.replace(/>/g, '>')
|
|
3327
|
+
.replace(/ /g, ' ') // replace double space with an nbsp and space
|
|
3328
|
+
.replace(/^ /, ' ') // space at start
|
|
3329
|
+
.replace(/ $/, ' '); // 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(/</g, '<')
|
|
3352
|
+
.replace(/>/g, '>')
|
|
3353
|
+
.replace(/ |\u00A0/g, ' ')
|
|
3354
|
+
.replace(/&/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;
|