2016-01-15 04:26:39 +08:00
/ * !
* jsoneditor . js
*
* @ brief
* JSONEditor is a web - based tool to view , edit , format , and validate JSON .
* It has various modes such as a tree editor , a code editor , and a plain text
* editor .
*
* Supported browsers : Chrome , Firefox , Safari , Opera , Internet Explorer 8 +
*
* @ license
* Licensed under the Apache License , Version 2.0 ( the "License" ) ; you may not
* use this file except in compliance with the License . You may obtain a copy
* of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS , WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied . See the
* License for the specific language governing permissions and limitations under
* the License .
*
* Copyright ( c ) 2011 - 2016 Jos de Jong , http : //jsoneditoronline.org
*
* @ author Jos de Jong , < wjosdejong @ gmail . com >
2016-03-21 01:19:13 +08:00
* @ version 5.2 . 0
* @ date 2016 - 03 - 20
2016-01-15 04:26:39 +08:00
* /
( function webpackUniversalModuleDefinition ( root , factory ) {
if ( typeof exports === 'object' && typeof module === 'object' )
module . exports = factory ( ) ;
else if ( typeof define === 'function' && define . amd )
define ( factory ) ;
else if ( typeof exports === 'object' )
exports [ "JSONEditor" ] = factory ( ) ;
else
root [ "JSONEditor" ] = factory ( ) ;
} ) ( this , function ( ) {
return /******/ ( function ( modules ) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = { } ;
/******/ // The require function
/******/ function _ _webpack _require _ _ ( moduleId ) {
/******/ // Check if module is in cache
/******/ if ( installedModules [ moduleId ] )
/******/ return installedModules [ moduleId ] . exports ;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules [ moduleId ] = {
/******/ exports : { } ,
/******/ id : moduleId ,
/******/ loaded : false
/******/ } ;
/******/ // Execute the module function
/******/ modules [ moduleId ] . call ( module . exports , module , module . exports , _ _webpack _require _ _ ) ;
/******/ // Flag the module as loaded
/******/ module . loaded = true ;
/******/ // Return the exports of the module
/******/ return module . exports ;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ _ _webpack _require _ _ . m = modules ;
/******/ // expose the module cache
/******/ _ _webpack _require _ _ . c = installedModules ;
/******/ // __webpack_public_path__
/******/ _ _webpack _require _ _ . p = "" ;
/******/ // Load entry module and return exports
/******/ return _ _webpack _require _ _ ( 0 ) ;
/******/ } )
/************************************************************************/
/******/ ( [
/* 0 */
/***/ function ( module , exports , _ _webpack _require _ _ ) {
var Ajv ;
try {
2016-01-22 03:01:49 +08:00
Ajv = _ _webpack _require _ _ ( ! ( function webpackMissingModule ( ) { var e = new Error ( "Cannot find module \"ajv\"" ) ; e . code = 'MODULE_NOT_FOUND' ; throw e ; } ( ) ) ) ;
2016-01-15 04:26:39 +08:00
}
catch ( err ) {
// no problem... when we need Ajv we will throw a neat exception
}
var treemode = _ _webpack _require _ _ ( 1 ) ;
var textmode = _ _webpack _require _ _ ( 2 ) ;
var util = _ _webpack _require _ _ ( 3 ) ;
/ * *
* @ constructor JSONEditor
* @ param { Element } container Container element
* @ param { Object } [ options ] Object with options . available options :
* { String } mode Editor mode . Available values :
* 'tree' ( default ) , 'view' ,
* 'form' , 'text' , and 'code' .
* { function } onChange Callback method , triggered
* on change of contents
* { function } onError Callback method , triggered
* when an error occurs
* { Boolean } search Enable search box .
* True by default
* Only applicable for modes
* 'tree' , 'view' , and 'form'
* { Boolean } history Enable history ( undo / redo ) .
* True by default
* Only applicable for modes
* 'tree' , 'view' , and 'form'
* { String } name Field name for the root node .
* Only applicable for modes
* 'tree' , 'view' , and 'form'
* { Number } indentation Number of indentation
* spaces . 4 by default .
* Only applicable for
* modes 'text' and 'code'
* { boolean } escapeUnicode If true , unicode
* characters are escaped .
* false by default .
* @ param { Object | undefined } json JSON object
* /
function JSONEditor ( container , options , json ) {
if ( ! ( this instanceof JSONEditor ) ) {
throw new Error ( 'JSONEditor constructor called without "new".' ) ;
}
// check for unsupported browser (IE8 and older)
var ieVersion = util . getInternetExplorerVersion ( ) ;
if ( ieVersion != - 1 && ieVersion < 9 ) {
throw new Error ( 'Unsupported browser, IE9 or newer required. ' +
'Please install the newest version of your browser.' ) ;
}
if ( options ) {
// check for deprecated options
if ( options . error ) {
console . warn ( 'Option "error" has been renamed to "onError"' ) ;
options . onError = options . error ;
delete options . error ;
}
if ( options . change ) {
console . warn ( 'Option "change" has been renamed to "onChange"' ) ;
options . onChange = options . change ;
delete options . change ;
}
if ( options . editable ) {
console . warn ( 'Option "editable" has been renamed to "onEditable"' ) ;
options . onEditable = options . editable ;
delete options . editable ;
}
// validate options
if ( options ) {
var VALID _OPTIONS = [
'ace' , 'theme' ,
'ajv' , 'schema' ,
'onChange' , 'onEditable' , 'onError' , 'onModeChange' ,
2016-01-16 17:55:45 +08:00
'escapeUnicode' , 'history' , 'search' , 'mode' , 'modes' , 'name' , 'indentation'
2016-01-15 04:26:39 +08:00
] ;
Object . keys ( options ) . forEach ( function ( option ) {
if ( VALID _OPTIONS . indexOf ( option ) === - 1 ) {
console . warn ( 'Unknown option "' + option + '". This option will be ignored' ) ;
}
} ) ;
}
}
if ( arguments . length ) {
this . _create ( container , options , json ) ;
}
}
/ * *
* Configuration for all registered modes . Example :
* {
* tree : {
* mixin : TreeEditor ,
* data : 'json'
* } ,
* text : {
* mixin : TextEditor ,
* data : 'text'
* }
* }
*
* @ type { Object . < String , { mixin : Object , data : String } > }
* /
JSONEditor . modes = { } ;
// debounce interval for JSON schema vaidation in milliseconds
JSONEditor . prototype . DEBOUNCE _INTERVAL = 150 ;
/ * *
* Create the JSONEditor
* @ param { Element } container Container element
* @ param { Object } [ options ] See description in constructor
* @ param { Object | undefined } json JSON object
* @ private
* /
JSONEditor . prototype . _create = function ( container , options , json ) {
this . container = container ;
this . options = options || { } ;
this . json = json || { } ;
var mode = this . options . mode || 'tree' ;
this . setMode ( mode ) ;
} ;
/ * *
2016-03-21 01:19:13 +08:00
* Destroy the editor . Clean up DOM , event listeners , and web workers .
2016-01-15 04:26:39 +08:00
* /
2016-03-21 01:19:13 +08:00
JSONEditor . prototype . destroy = function ( ) { } ;
2016-01-15 04:26:39 +08:00
/ * *
* Set JSON object in editor
* @ param { Object | undefined } json JSON data
* /
JSONEditor . prototype . set = function ( json ) {
this . json = json ;
} ;
/ * *
* Get JSON from the editor
* @ returns { Object } json
* /
JSONEditor . prototype . get = function ( ) {
return this . json ;
} ;
/ * *
* Set string containing JSON for the editor
* @ param { String | undefined } jsonText
* /
JSONEditor . prototype . setText = function ( jsonText ) {
this . json = util . parse ( jsonText ) ;
} ;
/ * *
* Get stringified JSON contents from the editor
* @ returns { String } jsonText
* /
JSONEditor . prototype . getText = function ( ) {
return JSON . stringify ( this . json ) ;
} ;
/ * *
* Set a field name for the root node .
* @ param { String | undefined } name
* /
JSONEditor . prototype . setName = function ( name ) {
if ( ! this . options ) {
this . options = { } ;
}
this . options . name = name ;
} ;
/ * *
* Get the field name for the root node .
* @ return { String | undefined } name
* /
JSONEditor . prototype . getName = function ( ) {
return this . options && this . options . name ;
} ;
/ * *
* Change the mode of the editor .
* JSONEditor will be extended with all methods needed for the chosen mode .
* @ param { String } mode Available modes : 'tree' ( default ) , 'view' , 'form' ,
* 'text' , and 'code' .
* /
JSONEditor . prototype . setMode = function ( mode ) {
var container = this . container ;
var options = util . extend ( { } , this . options ) ;
var oldMode = options . mode ;
var data ;
var name ;
options . mode = mode ;
var config = JSONEditor . modes [ mode ] ;
if ( config ) {
try {
var asText = ( config . data == 'text' ) ;
name = this . getName ( ) ;
data = this [ asText ? 'getText' : 'get' ] ( ) ; // get text or json
2016-03-21 01:19:13 +08:00
this . destroy ( ) ;
2016-01-15 04:26:39 +08:00
util . clear ( this ) ;
util . extend ( this , config . mixin ) ;
this . create ( container , options ) ;
this . setName ( name ) ;
this [ asText ? 'setText' : 'set' ] ( data ) ; // set text or json
if ( typeof config . load === 'function' ) {
try {
config . load . call ( this ) ;
}
catch ( err ) {
console . error ( err ) ;
}
}
if ( typeof options . onModeChange === 'function' && mode !== oldMode ) {
try {
options . onModeChange ( mode , oldMode ) ;
}
catch ( err ) {
console . error ( err ) ;
}
}
}
catch ( err ) {
this . _onError ( err ) ;
}
}
else {
throw new Error ( 'Unknown mode "' + options . mode + '"' ) ;
}
} ;
/ * *
* Get the current mode
* @ return { string }
* /
JSONEditor . prototype . getMode = function ( ) {
return this . options . mode ;
} ;
/ * *
* Throw an error . If an error callback is configured in options . error , this
* callback will be invoked . Else , a regular error is thrown .
* @ param { Error } err
* @ private
* /
JSONEditor . prototype . _onError = function ( err ) {
if ( this . options && typeof this . options . onError === 'function' ) {
this . options . onError ( err ) ;
}
else {
throw err ;
}
} ;
/ * *
* Set a JSON schema for validation of the JSON object .
* To remove the schema , call JSONEditor . setSchema ( null )
* @ param { Object | null } schema
* /
JSONEditor . prototype . setSchema = function ( schema ) {
// compile a JSON schema validator if a JSON schema is provided
if ( schema ) {
var ajv ;
try {
// grab ajv from options if provided, else create a new instance
2016-01-16 17:55:45 +08:00
ajv = this . options . ajv || Ajv ( { allErrors : true , verbose : true } ) ;
2016-01-15 04:26:39 +08:00
}
catch ( err ) {
console . warn ( 'Failed to create an instance of Ajv, JSON Schema validation is not available. Please use a JSONEditor bundle including Ajv, or pass an instance of Ajv as via the configuration option `ajv`.' ) ;
}
if ( ajv ) {
this . validateSchema = ajv . compile ( schema ) ;
// add schema to the options, so that when switching to an other mode,
// the set schema is not lost
this . options . schema = schema ;
// validate now
this . validate ( ) ;
}
}
else {
// remove current schema
this . validateSchema = null ;
this . options . schema = null ;
this . validate ( ) ; // to clear current error messages
}
} ;
/ * *
* Validate current JSON object against the configured JSON schema
* Throws an exception when no JSON schema is configured
* /
JSONEditor . prototype . validate = function ( ) {
// must be implemented by treemode and textmode
} ;
/ * *
* Register a plugin with one ore multiple modes for the JSON Editor .
*
* A mode is described as an object with properties :
*
* - ` mode: String ` The name of the mode .
* - ` mixin: Object ` An object containing the mixin functions which
* will be added to the JSONEditor . Must contain functions
* create , get , getText , set , and setText . May have
* additional functions .
* When the JSONEditor switches to a mixin , all mixin
* functions are added to the JSONEditor , and then
* the function ` create(container, options) ` is executed .
* - ` data: 'text' | 'json' ` The type of data that will be used to load the mixin .
* - ` [load: function] ` An optional function called after the mixin
* has been loaded .
*
* @ param { Object | Array } mode A mode object or an array with multiple mode objects .
* /
JSONEditor . registerMode = function ( mode ) {
var i , prop ;
if ( util . isArray ( mode ) ) {
// multiple modes
for ( i = 0 ; i < mode . length ; i ++ ) {
JSONEditor . registerMode ( mode [ i ] ) ;
}
}
else {
// validate the new mode
if ( ! ( 'mode' in mode ) ) throw new Error ( 'Property "mode" missing' ) ;
if ( ! ( 'mixin' in mode ) ) throw new Error ( 'Property "mixin" missing' ) ;
if ( ! ( 'data' in mode ) ) throw new Error ( 'Property "data" missing' ) ;
var name = mode . mode ;
if ( name in JSONEditor . modes ) {
throw new Error ( 'Mode "' + name + '" already registered' ) ;
}
// validate the mixin
if ( typeof mode . mixin . create !== 'function' ) {
throw new Error ( 'Required function "create" missing on mixin' ) ;
}
var reserved = [ 'setMode' , 'registerMode' , 'modes' ] ;
for ( i = 0 ; i < reserved . length ; i ++ ) {
prop = reserved [ i ] ;
if ( prop in mode . mixin ) {
throw new Error ( 'Reserved property "' + prop + '" not allowed in mixin' ) ;
}
}
JSONEditor . modes [ name ] = mode ;
}
} ;
// register tree and text modes
JSONEditor . registerMode ( treemode ) ;
JSONEditor . registerMode ( textmode ) ;
module . exports = JSONEditor ;
/***/ } ,
/* 1 */
/***/ function ( module , exports , _ _webpack _require _ _ ) {
2016-02-13 18:47:51 +08:00
var Highlighter = _ _webpack _require _ _ ( 4 ) ;
var History = _ _webpack _require _ _ ( 5 ) ;
var SearchBox = _ _webpack _require _ _ ( 6 ) ;
var ContextMenu = _ _webpack _require _ _ ( 7 ) ;
var Node = _ _webpack _require _ _ ( 8 ) ;
2016-03-21 01:19:13 +08:00
var ModeSwitcher = _ _webpack _require _ _ ( 9 ) ;
2016-01-15 04:26:39 +08:00
var util = _ _webpack _require _ _ ( 3 ) ;
// create a mixin with the functions for tree mode
var treemode = { } ;
/ * *
* Create a tree editor
* @ param { Element } container Container element
* @ param { Object } [ options ] Object with options . available options :
* { String } mode Editor mode . Available values :
* 'tree' ( default ) , 'view' ,
* and 'form' .
* { Boolean } search Enable search box .
* True by default
* { Boolean } history Enable history ( undo / redo ) .
* True by default
* { function } onChange Callback method , triggered
* on change of contents
* { String } name Field name for the root node .
* { boolean } escapeUnicode If true , unicode
* characters are escaped .
* false by default .
* { Object } schema A JSON Schema for validation
* @ private
* /
treemode . create = function ( container , options ) {
if ( ! container ) {
throw new Error ( 'No container element provided.' ) ;
}
this . container = container ;
this . dom = { } ;
this . highlighter = new Highlighter ( ) ;
this . selection = undefined ; // will hold the last input selection
this . multiselection = {
nodes : [ ]
} ;
this . validateSchema = null ; // will be set in .setSchema(schema)
this . errorNodes = [ ] ;
2016-03-21 01:19:13 +08:00
this . node = null ;
this . focusTarget = null ;
2016-01-15 04:26:39 +08:00
this . _setOptions ( options ) ;
if ( this . options . history && this . options . mode !== 'view' ) {
this . history = new History ( this ) ;
}
this . _createFrame ( ) ;
this . _createTable ( ) ;
} ;
/ * *
2016-03-21 01:19:13 +08:00
* Destroy the editor . Clean up DOM , event listeners , and web workers .
2016-01-15 04:26:39 +08:00
* /
2016-03-21 01:19:13 +08:00
treemode . destroy = function ( ) {
2016-01-15 04:26:39 +08:00
if ( this . frame && this . container && this . frame . parentNode == this . container ) {
this . container . removeChild ( this . frame ) ;
2016-03-21 01:19:13 +08:00
this . frame = null ;
}
this . container = null ;
this . dom = null ;
this . clear ( ) ;
this . node = null ;
this . focusTarget = null ;
this . selection = null ;
this . multiselection = null ;
this . errorNodes = null ;
this . validateSchema = null ;
this . _debouncedValidate = null ;
if ( this . history ) {
this . history . destroy ( ) ;
this . history = null ;
}
if ( this . searchBox ) {
this . searchBox . destroy ( ) ;
this . searchBox = null ;
}
if ( this . modeSwitcher ) {
this . modeSwitcher . destroy ( ) ;
this . modeSwitcher = null ;
2016-01-15 04:26:39 +08:00
}
} ;
/ * *
* Initialize and set default options
* @ param { Object } [ options ] See description in constructor
* @ private
* /
treemode . _setOptions = function ( options ) {
this . options = {
search : true ,
history : true ,
mode : 'tree' ,
name : undefined , // field name of root node
schema : null
} ;
// copy all options
if ( options ) {
for ( var prop in options ) {
if ( options . hasOwnProperty ( prop ) ) {
this . options [ prop ] = options [ prop ] ;
}
}
}
// compile a JSON schema validator if a JSON schema is provided
this . setSchema ( this . options . schema ) ;
// create a debounced validate function
this . _debouncedValidate = util . debounce ( this . validate . bind ( this ) , this . DEBOUNCE _INTERVAL ) ;
} ;
/ * *
* Set JSON object in editor
* @ param { Object | undefined } json JSON data
* @ param { String } [ name ] Optional field name for the root node .
* Can also be set using setName ( name ) .
* /
treemode . set = function ( json , name ) {
// adjust field name for root node
if ( name ) {
// TODO: deprecated since version 2.2.0. Cleanup some day.
console . warn ( 'Second parameter "name" is deprecated. Use setName(name) instead.' ) ;
this . options . name = name ;
}
// verify if json is valid JSON, ignore when a function
if ( json instanceof Function || ( json === undefined ) ) {
this . clear ( ) ;
}
else {
this . content . removeChild ( this . table ) ; // Take the table offline
// replace the root node
var params = {
field : this . options . name ,
value : json
} ;
var node = new Node ( this , params ) ;
this . _setRoot ( node ) ;
// validate JSON schema (if configured)
this . validate ( ) ;
// expand
var recurse = false ;
this . node . expand ( recurse ) ;
this . content . appendChild ( this . table ) ; // Put the table online again
}
// TODO: maintain history, store last state and previous document
if ( this . history ) {
this . history . clear ( ) ;
}
// clear search
2016-01-16 17:55:45 +08:00
if ( this . searchBox ) {
this . searchBox . clear ( ) ;
}
2016-01-15 04:26:39 +08:00
} ;
/ * *
* Get JSON object from editor
* @ return { Object | undefined } json
* /
treemode . get = function ( ) {
// remove focus from currently edited node
2016-03-21 01:19:13 +08:00
if ( this . focusTarget ) {
this . focusTarget . blur ( ) ;
2016-01-15 04:26:39 +08:00
}
if ( this . node ) {
return this . node . getValue ( ) ;
}
else {
return undefined ;
}
} ;
/ * *
* Get the text contents of the editor
* @ return { String } jsonText
* /
treemode . getText = function ( ) {
return JSON . stringify ( this . get ( ) ) ;
} ;
/ * *
* Set the text contents of the editor
* @ param { String } jsonText
* /
treemode . setText = function ( jsonText ) {
this . set ( util . parse ( jsonText ) ) ;
} ;
/ * *
* Set a field name for the root node .
* @ param { String | undefined } name
* /
treemode . setName = function ( name ) {
this . options . name = name ;
if ( this . node ) {
this . node . updateField ( this . options . name ) ;
}
} ;
/ * *
* Get the field name for the root node .
* @ return { String | undefined } name
* /
treemode . getName = function ( ) {
return this . options . name ;
} ;
/ * *
* Set focus to the editor . Focus will be set to :
* - the first editable field or value , or else
* - to the expand button of the root node , or else
* - to the context menu button of the root node , or else
* - to the first button in the top menu
* /
treemode . focus = function ( ) {
var input = this . content . querySelector ( '[contenteditable=true]' ) ;
if ( input ) {
input . focus ( ) ;
}
else if ( this . node . dom . expand ) {
this . node . dom . expand . focus ( ) ;
}
else if ( this . node . dom . menu ) {
this . node . dom . menu . focus ( ) ;
}
else {
// focus to the first button in the menu
input = this . frame . querySelector ( 'button' ) ;
if ( input ) {
input . focus ( ) ;
}
}
} ;
/ * *
* Remove the root node from the editor
* /
treemode . clear = function ( ) {
if ( this . node ) {
this . node . collapse ( ) ;
this . tbody . removeChild ( this . node . getDom ( ) ) ;
delete this . node ;
}
} ;
/ * *
* Set the root node for the json editor
* @ param { Node } node
* @ private
* /
treemode . _setRoot = function ( node ) {
this . clear ( ) ;
this . node = node ;
// append to the dom
this . tbody . appendChild ( node . getDom ( ) ) ;
} ;
/ * *
* Search text in all nodes
* The nodes will be expanded when the text is found one of its childs ,
* else it will be collapsed . Searches are case insensitive .
* @ param { String } text
* @ return { Object [ ] } results Array with nodes containing the search results
* The result objects contains fields :
* - { Node } node ,
* - { String } elem the dom element name where
* the result is found ( 'field' or
* 'value' )
* /
treemode . search = function ( text ) {
var results ;
if ( this . node ) {
this . content . removeChild ( this . table ) ; // Take the table offline
results = this . node . search ( text ) ;
this . content . appendChild ( this . table ) ; // Put the table online again
}
else {
results = [ ] ;
}
return results ;
} ;
/ * *
* Expand all nodes
* /
treemode . expandAll = function ( ) {
if ( this . node ) {
this . content . removeChild ( this . table ) ; // Take the table offline
this . node . expand ( ) ;
this . content . appendChild ( this . table ) ; // Put the table online again
}
} ;
/ * *
* Collapse all nodes
* /
treemode . collapseAll = function ( ) {
if ( this . node ) {
this . content . removeChild ( this . table ) ; // Take the table offline
this . node . collapse ( ) ;
this . content . appendChild ( this . table ) ; // Put the table online again
}
} ;
/ * *
* The method onChange is called whenever a field or value is changed , created ,
* deleted , duplicated , etc .
* @ param { String } action Change action . Available values : "editField" ,
* "editValue" , "changeType" , "appendNode" ,
* "removeNode" , "duplicateNode" , "moveNode" , "expand" ,
* "collapse" .
* @ param { Object } params Object containing parameters describing the change .
* The parameters in params depend on the action ( for
* example for "editValue" the Node , old value , and new
* value are provided ) . params contains all information
* needed to undo or redo the action .
* @ private
* /
treemode . _onAction = function ( action , params ) {
// add an action to the history
if ( this . history ) {
this . history . add ( action , params ) ;
}
this . _onChange ( ) ;
} ;
/ * *
* Handle a change :
* - Validate JSON schema
* - Send a callback to the onChange listener if provided
* @ private
* /
treemode . _onChange = function ( ) {
// validate JSON schema (if configured)
this . _debouncedValidate ( ) ;
// trigger the onChange callback
if ( this . options . onChange ) {
try {
this . options . onChange ( ) ;
}
catch ( err ) {
console . error ( 'Error in onChange callback: ' , err ) ;
}
}
} ;
/ * *
* Validate current JSON object against the configured JSON schema
* Throws an exception when no JSON schema is configured
* /
treemode . validate = function ( ) {
// clear all current errors
if ( this . errorNodes ) {
this . errorNodes . forEach ( function ( node ) {
node . setError ( null ) ;
} ) ;
}
var root = this . node ;
if ( ! root ) { // TODO: this should be redundant but is needed on mode switch
return ;
}
// check for duplicate keys
var duplicateErrors = root . validate ( ) ;
// validate the JSON
var schemaErrors = [ ] ;
if ( this . validateSchema ) {
var valid = this . validateSchema ( root . getValue ( ) ) ;
if ( ! valid ) {
// apply all new errors
schemaErrors = this . validateSchema . errors
. map ( function ( error ) {
2016-01-16 17:55:45 +08:00
return util . improveSchemaError ( error ) ;
2016-01-15 04:26:39 +08:00
} )
. map ( function findNode ( error ) {
return {
node : root . findNode ( error . dataPath ) ,
error : error
}
} )
. filter ( function hasNode ( entry ) {
return entry . node != null
} ) ;
}
}
// display the error in the nodes with a problem
this . errorNodes = duplicateErrors
. concat ( schemaErrors )
. reduce ( function expandParents ( all , entry ) {
// expand parents, then merge such that parents come first and
// original entries last
return entry . node
. findParents ( )
. map ( function ( parent ) {
return {
node : parent ,
child : entry . node ,
error : {
message : parent . type === 'object'
? 'Contains invalid properties' // object
: 'Contains invalid items' // array
}
} ;
} )
. concat ( all , [ entry ] ) ;
} , [ ] )
// TODO: dedupe the parent nodes
. map ( function setError ( entry ) {
entry . node . setError ( entry . error , entry . child ) ;
return entry . node ;
} ) ;
} ;
/ * *
* Start autoscrolling when given mouse position is above the top of the
* editor contents , or below the bottom .
* @ param { Number } mouseY Absolute mouse position in pixels
* /
treemode . startAutoScroll = function ( mouseY ) {
var me = this ;
var content = this . content ;
var top = util . getAbsoluteTop ( content ) ;
var height = content . clientHeight ;
var bottom = top + height ;
var margin = 24 ;
var interval = 50 ; // ms
if ( ( mouseY < top + margin ) && content . scrollTop > 0 ) {
this . autoScrollStep = ( ( top + margin ) - mouseY ) / 3 ;
}
else if ( mouseY > bottom - margin &&
height + content . scrollTop < content . scrollHeight ) {
this . autoScrollStep = ( ( bottom - margin ) - mouseY ) / 3 ;
}
else {
this . autoScrollStep = undefined ;
}
if ( this . autoScrollStep ) {
if ( ! this . autoScrollTimer ) {
this . autoScrollTimer = setInterval ( function ( ) {
if ( me . autoScrollStep ) {
content . scrollTop -= me . autoScrollStep ;
}
else {
me . stopAutoScroll ( ) ;
}
} , interval ) ;
}
}
else {
this . stopAutoScroll ( ) ;
}
} ;
/ * *
* Stop auto scrolling . Only applicable when scrolling
* /
treemode . stopAutoScroll = function ( ) {
if ( this . autoScrollTimer ) {
clearTimeout ( this . autoScrollTimer ) ;
delete this . autoScrollTimer ;
}
if ( this . autoScrollStep ) {
delete this . autoScrollStep ;
}
} ;
/ * *
* Set the focus to an element in the editor , set text selection , and
* set scroll position .
* @ param { Object } selection An object containing fields :
* { Element | undefined } dom The dom element
* which has focus
* { Range | TextRange } range A text selection
* { Node [ ] } nodes Nodes in case of multi selection
* { Number } scrollTop Scroll position
* /
treemode . setSelection = function ( selection ) {
if ( ! selection ) {
return ;
}
if ( 'scrollTop' in selection && this . content ) {
// TODO: animated scroll
this . content . scrollTop = selection . scrollTop ;
}
if ( selection . nodes ) {
// multi-select
this . select ( selection . nodes ) ;
}
if ( selection . range ) {
util . setSelectionOffset ( selection . range ) ;
}
if ( selection . dom ) {
selection . dom . focus ( ) ;
}
} ;
/ * *
* Get the current focus
* @ return { Object } selection An object containing fields :
* { Element | undefined } dom The dom element
* which has focus
* { Range | TextRange } range A text selection
* { Node [ ] } nodes Nodes in case of multi selection
* { Number } scrollTop Scroll position
* /
treemode . getSelection = function ( ) {
var range = util . getSelectionOffset ( ) ;
if ( range && range . container . nodeName !== 'DIV' ) { // filter on (editable) divs)
range = null ;
}
return {
2016-03-21 01:19:13 +08:00
dom : this . focusTarget ,
2016-01-15 04:26:39 +08:00
range : range ,
nodes : this . multiselection . nodes . slice ( 0 ) ,
scrollTop : this . content ? this . content . scrollTop : 0
} ;
} ;
/ * *
* Adjust the scroll position such that given top position is shown at 1 / 4
* of the window height .
* @ param { Number } top
* @ param { function ( boolean ) } [ callback ] Callback , executed when animation is
* finished . The callback returns true
* when animation is finished , or false
* when not .
* /
treemode . scrollTo = function ( top , callback ) {
var content = this . content ;
if ( content ) {
var editor = this ;
// cancel any running animation
if ( editor . animateTimeout ) {
clearTimeout ( editor . animateTimeout ) ;
delete editor . animateTimeout ;
}
if ( editor . animateCallback ) {
editor . animateCallback ( false ) ;
delete editor . animateCallback ;
}
// calculate final scroll position
var height = content . clientHeight ;
var bottom = content . scrollHeight - height ;
var finalScrollTop = Math . min ( Math . max ( top - height / 4 , 0 ) , bottom ) ;
// animate towards the new scroll position
var animate = function ( ) {
var scrollTop = content . scrollTop ;
var diff = ( finalScrollTop - scrollTop ) ;
if ( Math . abs ( diff ) > 3 ) {
content . scrollTop += diff / 3 ;
editor . animateCallback = callback ;
editor . animateTimeout = setTimeout ( animate , 50 ) ;
}
else {
// finished
if ( callback ) {
callback ( true ) ;
}
content . scrollTop = finalScrollTop ;
delete editor . animateTimeout ;
delete editor . animateCallback ;
}
} ;
animate ( ) ;
}
else {
if ( callback ) {
callback ( false ) ;
}
}
} ;
/ * *
* Create main frame
* @ private
* /
treemode . _createFrame = function ( ) {
// create the frame
this . frame = document . createElement ( 'div' ) ;
this . frame . className = 'jsoneditor jsoneditor-mode-' + this . options . mode ;
this . container . appendChild ( this . frame ) ;
// create one global event listener to handle all events from all nodes
var editor = this ;
function onEvent ( event ) {
// when switching to mode "code" or "text" via the menu, some events
// are still fired whilst the _onEvent methods is already removed.
if ( editor . _onEvent ) {
editor . _onEvent ( event ) ;
}
}
this . frame . onclick = function ( event ) {
var target = event . target ; // || event.srcElement;
onEvent ( event ) ;
// prevent default submit action of buttons when editor is located
// inside a form
if ( target . nodeName == 'BUTTON' ) {
event . preventDefault ( ) ;
}
} ;
this . frame . oninput = onEvent ;
this . frame . onchange = onEvent ;
this . frame . onkeydown = onEvent ;
this . frame . onkeyup = onEvent ;
this . frame . oncut = onEvent ;
this . frame . onpaste = onEvent ;
this . frame . onmousedown = onEvent ;
this . frame . onmouseup = onEvent ;
this . frame . onmouseover = onEvent ;
this . frame . onmouseout = onEvent ;
// Note: focus and blur events do not propagate, therefore they defined
// using an eventListener with useCapture=true
// see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
util . addEventListener ( this . frame , 'focus' , onEvent , true ) ;
util . addEventListener ( this . frame , 'blur' , onEvent , true ) ;
this . frame . onfocusin = onEvent ; // for IE
this . frame . onfocusout = onEvent ; // for IE
// create menu
this . menu = document . createElement ( 'div' ) ;
this . menu . className = 'jsoneditor-menu' ;
this . frame . appendChild ( this . menu ) ;
// create expand all button
var expandAll = document . createElement ( 'button' ) ;
expandAll . className = 'jsoneditor-expand-all' ;
expandAll . title = 'Expand all fields' ;
expandAll . onclick = function ( ) {
editor . expandAll ( ) ;
} ;
this . menu . appendChild ( expandAll ) ;
// create expand all button
var collapseAll = document . createElement ( 'button' ) ;
collapseAll . title = 'Collapse all fields' ;
collapseAll . className = 'jsoneditor-collapse-all' ;
collapseAll . onclick = function ( ) {
editor . collapseAll ( ) ;
} ;
this . menu . appendChild ( collapseAll ) ;
// create undo/redo buttons
if ( this . history ) {
// create undo button
var undo = document . createElement ( 'button' ) ;
undo . className = 'jsoneditor-undo jsoneditor-separator' ;
undo . title = 'Undo last action (Ctrl+Z)' ;
undo . onclick = function ( ) {
editor . _onUndo ( ) ;
} ;
this . menu . appendChild ( undo ) ;
this . dom . undo = undo ;
// create redo button
var redo = document . createElement ( 'button' ) ;
redo . className = 'jsoneditor-redo' ;
redo . title = 'Redo (Ctrl+Shift+Z)' ;
redo . onclick = function ( ) {
editor . _onRedo ( ) ;
} ;
this . menu . appendChild ( redo ) ;
this . dom . redo = redo ;
// register handler for onchange of history
this . history . onChange = function ( ) {
undo . disabled = ! editor . history . canUndo ( ) ;
redo . disabled = ! editor . history . canRedo ( ) ;
} ;
this . history . onChange ( ) ;
}
// create mode box
if ( this . options && this . options . modes && this . options . modes . length ) {
2016-03-21 01:19:13 +08:00
var me = this ;
this . modeSwitcher = new ModeSwitcher ( this . menu , this . options . modes , this . options . mode , function onSwitch ( mode ) {
me . modeSwitcher . destroy ( ) ;
// switch mode and restore focus
me . setMode ( mode ) ;
me . modeSwitcher . focus ( ) ;
} ) ;
2016-01-15 04:26:39 +08:00
}
// create search box
if ( this . options . search ) {
this . searchBox = new SearchBox ( this , this . menu ) ;
}
} ;
/ * *
* Perform an undo action
* @ private
* /
treemode . _onUndo = function ( ) {
if ( this . history ) {
// undo last action
this . history . undo ( ) ;
// fire change event
this . _onChange ( ) ;
}
} ;
/ * *
* Perform a redo action
* @ private
* /
treemode . _onRedo = function ( ) {
if ( this . history ) {
// redo last action
this . history . redo ( ) ;
// fire change event
this . _onChange ( ) ;
}
} ;
/ * *
* Event handler
* @ param event
* @ private
* /
treemode . _onEvent = function ( event ) {
if ( event . type == 'keydown' ) {
this . _onKeyDown ( event ) ;
}
if ( event . type == 'focus' ) {
2016-03-21 01:19:13 +08:00
this . focusTarget = event . target ;
2016-01-15 04:26:39 +08:00
}
if ( event . type == 'mousedown' ) {
this . _startDragDistance ( event ) ;
}
if ( event . type == 'mousemove' || event . type == 'mouseup' || event . type == 'click' ) {
this . _updateDragDistance ( event ) ;
}
var node = Node . getNodeFromTarget ( event . target ) ;
if ( node && node . selected ) {
if ( event . type == 'click' ) {
if ( event . target == node . dom . menu ) {
this . showContextMenu ( event . target ) ;
// stop propagation (else we will open the context menu of a single node)
return ;
}
// deselect a multi selection
if ( ! event . hasMoved ) {
this . deselect ( ) ;
}
}
if ( event . type == 'mousedown' ) {
// drag multiple nodes
Node . onDragStart ( this . multiselection . nodes , event ) ;
}
}
else {
if ( event . type == 'mousedown' ) {
this . deselect ( ) ;
if ( node && event . target == node . dom . drag ) {
// drag a singe node
Node . onDragStart ( node , event ) ;
}
else if ( ! node || ( event . target != node . dom . field && event . target != node . dom . value ) ) {
// select multiple nodes
this . _onMultiSelectStart ( event ) ;
}
}
}
if ( node ) {
node . onEvent ( event ) ;
}
} ;
treemode . _startDragDistance = function ( event ) {
this . dragDistanceEvent = {
initialTarget : event . target ,
initialPageX : event . pageX ,
initialPageY : event . pageY ,
dragDistance : 0 ,
hasMoved : false
} ;
} ;
treemode . _updateDragDistance = function ( event ) {
if ( ! this . dragDistanceEvent ) {
this . _startDragDistance ( event ) ;
}
var diffX = event . pageX - this . dragDistanceEvent . initialPageX ;
var diffY = event . pageY - this . dragDistanceEvent . initialPageY ;
this . dragDistanceEvent . dragDistance = Math . sqrt ( diffX * diffX + diffY * diffY ) ;
this . dragDistanceEvent . hasMoved =
this . dragDistanceEvent . hasMoved || this . dragDistanceEvent . dragDistance > 10 ;
event . dragDistance = this . dragDistanceEvent . dragDistance ;
event . hasMoved = this . dragDistanceEvent . hasMoved ;
return event . dragDistance ;
} ;
/ * *
* Start multi selection of nodes by dragging the mouse
* @ param event
* @ private
* /
treemode . _onMultiSelectStart = function ( event ) {
var node = Node . getNodeFromTarget ( event . target ) ;
if ( this . options . mode !== 'tree' || this . options . onEditable !== undefined ) {
// dragging not allowed in modes 'view' and 'form'
// TODO: allow multiselection of items when option onEditable is specified
return ;
}
this . multiselection = {
start : node || null ,
end : null ,
nodes : [ ]
} ;
this . _startDragDistance ( event ) ;
var editor = this ;
if ( ! this . mousemove ) {
this . mousemove = util . addEventListener ( window , 'mousemove' , function ( event ) {
editor . _onMultiSelect ( event ) ;
} ) ;
}
if ( ! this . mouseup ) {
this . mouseup = util . addEventListener ( window , 'mouseup' , function ( event ) {
editor . _onMultiSelectEnd ( event ) ;
} ) ;
}
} ;
/ * *
* Multiselect nodes by dragging
* @ param event
* @ private
* /
treemode . _onMultiSelect = function ( event ) {
event . preventDefault ( ) ;
this . _updateDragDistance ( event ) ;
if ( ! event . hasMoved ) {
return ;
}
var node = Node . getNodeFromTarget ( event . target ) ;
if ( node ) {
if ( this . multiselection . start == null ) {
this . multiselection . start = node ;
}
this . multiselection . end = node ;
}
// deselect previous selection
this . deselect ( ) ;
// find the selected nodes in the range from first to last
var start = this . multiselection . start ;
var end = this . multiselection . end || this . multiselection . start ;
if ( start && end ) {
// find the top level childs, all having the same parent
this . multiselection . nodes = this . _findTopLevelNodes ( start , end ) ;
this . select ( this . multiselection . nodes ) ;
}
} ;
/ * *
* End of multiselect nodes by dragging
* @ param event
* @ private
* /
treemode . _onMultiSelectEnd = function ( event ) {
// set focus to the context menu button of the first node
if ( this . multiselection . nodes [ 0 ] ) {
this . multiselection . nodes [ 0 ] . dom . menu . focus ( ) ;
}
this . multiselection . start = null ;
this . multiselection . end = null ;
// cleanup global event listeners
if ( this . mousemove ) {
util . removeEventListener ( window , 'mousemove' , this . mousemove ) ;
delete this . mousemove ;
}
if ( this . mouseup ) {
util . removeEventListener ( window , 'mouseup' , this . mouseup ) ;
delete this . mouseup ;
}
} ;
/ * *
* deselect currently selected nodes
* @ param { boolean } [ clearStartAndEnd = false ] If true , the ` start ` and ` end `
* state is cleared too .
* /
treemode . deselect = function ( clearStartAndEnd ) {
this . multiselection . nodes . forEach ( function ( node ) {
node . setSelected ( false ) ;
} ) ;
this . multiselection . nodes = [ ] ;
if ( clearStartAndEnd ) {
this . multiselection . start = null ;
this . multiselection . end = null ;
}
} ;
/ * *
* select nodes
* @ param { Node [ ] | Node } nodes
* /
treemode . select = function ( nodes ) {
if ( ! Array . isArray ( nodes ) ) {
return this . select ( [ nodes ] ) ;
}
if ( nodes ) {
this . deselect ( ) ;
this . multiselection . nodes = nodes . slice ( 0 ) ;
var first = nodes [ 0 ] ;
nodes . forEach ( function ( node ) {
node . setSelected ( true , node === first ) ;
} ) ;
}
} ;
/ * *
* From two arbitrary selected nodes , find their shared parent node .
* From that parent node , select the two child nodes in the brances going to
* nodes ` start ` and ` end ` , and select all childs in between .
* @ param { Node } start
* @ param { Node } end
* @ return { Array . < Node > } Returns an ordered list with child nodes
* @ private
* /
treemode . _findTopLevelNodes = function ( start , end ) {
var startPath = start . getPath ( ) ;
var endPath = end . getPath ( ) ;
var i = 0 ;
while ( i < startPath . length && startPath [ i ] === endPath [ i ] ) {
i ++ ;
}
var root = startPath [ i - 1 ] ;
var startChild = startPath [ i ] ;
var endChild = endPath [ i ] ;
if ( ! startChild || ! endChild ) {
if ( root . parent ) {
// startChild is a parent of endChild or vice versa
startChild = root ;
endChild = root ;
root = root . parent
}
else {
// we have selected the root node (which doesn't have a parent)
startChild = root . childs [ 0 ] ;
endChild = root . childs [ root . childs . length - 1 ] ;
}
}
if ( root && startChild && endChild ) {
var startIndex = root . childs . indexOf ( startChild ) ;
var endIndex = root . childs . indexOf ( endChild ) ;
var firstIndex = Math . min ( startIndex , endIndex ) ;
var lastIndex = Math . max ( startIndex , endIndex ) ;
return root . childs . slice ( firstIndex , lastIndex + 1 ) ;
}
else {
return [ ] ;
}
} ;
/ * *
* Event handler for keydown . Handles shortcut keys
* @ param { Event } event
* @ private
* /
treemode . _onKeyDown = function ( event ) {
var keynum = event . which || event . keyCode ;
var ctrlKey = event . ctrlKey ;
var shiftKey = event . shiftKey ;
var handled = false ;
if ( keynum == 9 ) { // Tab or Shift+Tab
2016-03-21 01:19:13 +08:00
var me = this ;
2016-01-15 04:26:39 +08:00
setTimeout ( function ( ) {
// select all text when moving focus to an editable div
2016-03-21 01:19:13 +08:00
util . selectContentEditable ( me . focusTarget ) ;
2016-01-15 04:26:39 +08:00
} , 0 ) ;
}
if ( this . searchBox ) {
if ( ctrlKey && keynum == 70 ) { // Ctrl+F
this . searchBox . dom . search . focus ( ) ;
this . searchBox . dom . search . select ( ) ;
handled = true ;
}
else if ( keynum == 114 || ( ctrlKey && keynum == 71 ) ) { // F3 or Ctrl+G
var focus = true ;
if ( ! shiftKey ) {
// select next search result (F3 or Ctrl+G)
this . searchBox . next ( focus ) ;
}
else {
// select previous search result (Shift+F3 or Ctrl+Shift+G)
this . searchBox . previous ( focus ) ;
}
handled = true ;
}
}
if ( this . history ) {
if ( ctrlKey && ! shiftKey && keynum == 90 ) { // Ctrl+Z
// undo
this . _onUndo ( ) ;
handled = true ;
}
else if ( ctrlKey && shiftKey && keynum == 90 ) { // Ctrl+Shift+Z
// redo
this . _onRedo ( ) ;
handled = true ;
}
}
if ( handled ) {
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
}
} ;
/ * *
* Create main table
* @ private
* /
treemode . _createTable = function ( ) {
var contentOuter = document . createElement ( 'div' ) ;
contentOuter . className = 'jsoneditor-outer' ;
this . contentOuter = contentOuter ;
this . content = document . createElement ( 'div' ) ;
this . content . className = 'jsoneditor-tree' ;
contentOuter . appendChild ( this . content ) ;
this . table = document . createElement ( 'table' ) ;
this . table . className = 'jsoneditor-tree' ;
this . content . appendChild ( this . table ) ;
// create colgroup where the first two columns don't have a fixed
// width, and the edit columns do have a fixed width
var col ;
this . colgroupContent = document . createElement ( 'colgroup' ) ;
if ( this . options . mode === 'tree' ) {
col = document . createElement ( 'col' ) ;
col . width = "24px" ;
this . colgroupContent . appendChild ( col ) ;
}
col = document . createElement ( 'col' ) ;
col . width = "24px" ;
this . colgroupContent . appendChild ( col ) ;
col = document . createElement ( 'col' ) ;
this . colgroupContent . appendChild ( col ) ;
this . table . appendChild ( this . colgroupContent ) ;
this . tbody = document . createElement ( 'tbody' ) ;
this . table . appendChild ( this . tbody ) ;
this . frame . appendChild ( contentOuter ) ;
} ;
/ * *
* Show a contextmenu for this node .
* Used for multiselection
* @ param { HTMLElement } anchor Anchor element to attache the context menu to .
* @ param { function } [ onClose ] Callback method called when the context menu
* is being closed .
* /
treemode . showContextMenu = function ( anchor , onClose ) {
var items = [ ] ;
var editor = this ;
// create duplicate button
items . push ( {
text : 'Duplicate' ,
title : 'Duplicate selected fields (Ctrl+D)' ,
className : 'jsoneditor-duplicate' ,
click : function ( ) {
Node . onDuplicate ( editor . multiselection . nodes ) ;
}
} ) ;
// create remove button
items . push ( {
text : 'Remove' ,
title : 'Remove selected fields (Ctrl+Del)' ,
className : 'jsoneditor-remove' ,
click : function ( ) {
Node . onRemove ( editor . multiselection . nodes ) ;
}
} ) ;
var menu = new ContextMenu ( items , { close : onClose } ) ;
menu . show ( anchor , this . content ) ;
} ;
// define modes
module . exports = [
{
mode : 'tree' ,
mixin : treemode ,
data : 'json'
} ,
{
mode : 'view' ,
mixin : treemode ,
data : 'json'
} ,
{
mode : 'form' ,
mixin : treemode ,
data : 'json'
}
] ;
2016-01-16 17:55:45 +08:00
2016-01-15 04:26:39 +08:00
/***/ } ,
/* 2 */
/***/ function ( module , exports , _ _webpack _require _ _ ) {
var ace ;
try {
ace = _ _webpack _require _ _ ( 11 ) ;
}
catch ( err ) {
// failed to load ace, no problem, we will fall back to plain text
}
2016-03-21 01:19:13 +08:00
var ModeSwitcher = _ _webpack _require _ _ ( 9 ) ;
2016-01-15 04:26:39 +08:00
var util = _ _webpack _require _ _ ( 3 ) ;
// create a mixin with the functions for text mode
var textmode = { } ;
var MAX _ERRORS = 3 ; // maximum number of displayed errors at the bottom
/ * *
* Create a text editor
* @ param { Element } container
* @ param { Object } [ options ] Object with options . available options :
* { String } mode Available values :
* "text" ( default )
* or "code" .
* { Number } indentation Number of indentation
* spaces . 2 by default .
* { function } onChange Callback method
* triggered on change
* { function } onModeChange Callback method
* triggered after setMode
* { Object } ace A custom instance of
* Ace editor .
* { boolean } escapeUnicode If true , unicode
* characters are escaped .
* false by default .
* @ private
* /
textmode . create = function ( container , options ) {
// read options
options = options || { } ;
this . options = options ;
// indentation
if ( options . indentation ) {
this . indentation = Number ( options . indentation ) ;
}
else {
this . indentation = 2 ; // number of spaces
}
// grab ace from options if provided
var _ace = options . ace ? options . ace : ace ;
// determine mode
this . mode = ( options . mode == 'code' ) ? 'code' : 'text' ;
if ( this . mode == 'code' ) {
// verify whether Ace editor is available and supported
if ( typeof _ace === 'undefined' ) {
this . mode = 'text' ;
console . warn ( 'Failed to load Ace editor, falling back to plain text mode. Please use a JSONEditor bundle including Ace, or pass Ace as via the configuration option `ace`.' ) ;
}
}
// determine theme
this . theme = options . theme || 'ace/theme/jsoneditor' ;
var me = this ;
this . container = container ;
this . dom = { } ;
this . aceEditor = undefined ; // ace code editor
this . textarea = undefined ; // plain text editor (fallback when Ace is not available)
this . validateSchema = null ;
// create a debounced validate function
this . _debouncedValidate = util . debounce ( this . validate . bind ( this ) , this . DEBOUNCE _INTERVAL ) ;
this . width = container . clientWidth ;
this . height = container . clientHeight ;
this . frame = document . createElement ( 'div' ) ;
this . frame . className = 'jsoneditor jsoneditor-mode-' + this . options . mode ;
this . frame . onclick = function ( event ) {
// prevent default submit action when the editor is located inside a form
event . preventDefault ( ) ;
} ;
this . frame . onkeydown = function ( event ) {
me . _onKeyDown ( event ) ;
} ;
// create menu
this . menu = document . createElement ( 'div' ) ;
this . menu . className = 'jsoneditor-menu' ;
this . frame . appendChild ( this . menu ) ;
// create format button
var buttonFormat = document . createElement ( 'button' ) ;
buttonFormat . className = 'jsoneditor-format' ;
buttonFormat . title = 'Format JSON data, with proper indentation and line feeds (Ctrl+\\)' ;
this . menu . appendChild ( buttonFormat ) ;
buttonFormat . onclick = function ( ) {
try {
me . format ( ) ;
2016-01-22 03:01:49 +08:00
me . _onChange ( ) ;
2016-01-15 04:26:39 +08:00
}
catch ( err ) {
me . _onError ( err ) ;
}
} ;
// create compact button
var buttonCompact = document . createElement ( 'button' ) ;
buttonCompact . className = 'jsoneditor-compact' ;
buttonCompact . title = 'Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)' ;
this . menu . appendChild ( buttonCompact ) ;
buttonCompact . onclick = function ( ) {
try {
me . compact ( ) ;
2016-01-22 03:01:49 +08:00
me . _onChange ( ) ;
2016-01-15 04:26:39 +08:00
}
catch ( err ) {
me . _onError ( err ) ;
}
} ;
// create mode box
if ( this . options && this . options . modes && this . options . modes . length ) {
2016-03-21 01:19:13 +08:00
this . modeSwitcher = new ModeSwitcher ( this . menu , this . options . modes , this . options . mode , function onSwitch ( mode ) {
me . modeSwitcher . destroy ( ) ;
// switch mode and restore focus
me . setMode ( mode ) ;
me . modeSwitcher . focus ( ) ;
} ) ;
2016-01-15 04:26:39 +08:00
}
this . content = document . createElement ( 'div' ) ;
this . content . className = 'jsoneditor-outer' ;
this . frame . appendChild ( this . content ) ;
this . container . appendChild ( this . frame ) ;
if ( this . mode == 'code' ) {
this . editorDom = document . createElement ( 'div' ) ;
this . editorDom . style . height = '100%' ; // TODO: move to css
this . editorDom . style . width = '100%' ; // TODO: move to css
this . content . appendChild ( this . editorDom ) ;
var aceEditor = _ace . edit ( this . editorDom ) ;
aceEditor . $blockScrolling = Infinity ;
aceEditor . setTheme ( this . theme ) ;
aceEditor . setShowPrintMargin ( false ) ;
aceEditor . setFontSize ( 13 ) ;
aceEditor . getSession ( ) . setMode ( 'ace/mode/json' ) ;
aceEditor . getSession ( ) . setTabSize ( this . indentation ) ;
aceEditor . getSession ( ) . setUseSoftTabs ( true ) ;
aceEditor . getSession ( ) . setUseWrapMode ( true ) ;
aceEditor . commands . bindKey ( 'Ctrl-L' , null ) ; // disable Ctrl+L (is used by the browser to select the address bar)
aceEditor . commands . bindKey ( 'Command-L' , null ) ; // disable Ctrl+L (is used by the browser to select the address bar)
this . aceEditor = aceEditor ;
// TODO: deprecated since v5.0.0. Cleanup backward compatibility some day
if ( ! this . hasOwnProperty ( 'editor' ) ) {
Object . defineProperty ( this , 'editor' , {
get : function ( ) {
console . warn ( 'Property "editor" has been renamed to "aceEditor".' ) ;
return me . aceEditor ;
} ,
set : function ( aceEditor ) {
console . warn ( 'Property "editor" has been renamed to "aceEditor".' ) ;
me . aceEditor = aceEditor ;
}
} ) ;
}
var poweredBy = document . createElement ( 'a' ) ;
poweredBy . appendChild ( document . createTextNode ( 'powered by ace' ) ) ;
poweredBy . href = 'http://ace.ajax.org' ;
poweredBy . target = '_blank' ;
poweredBy . className = 'jsoneditor-poweredBy' ;
poweredBy . onclick = function ( ) {
// TODO: this anchor falls below the margin of the content,
// therefore the normal a.href does not work. We use a click event
// for now, but this should be fixed.
window . open ( poweredBy . href , poweredBy . target ) ;
} ;
this . menu . appendChild ( poweredBy ) ;
// register onchange event
aceEditor . on ( 'change' , this . _onChange . bind ( this ) ) ;
}
else {
// load a plain text textarea
var textarea = document . createElement ( 'textarea' ) ;
textarea . className = 'jsoneditor-text' ;
textarea . spellcheck = false ;
this . content . appendChild ( textarea ) ;
this . textarea = textarea ;
// register onchange event
if ( this . textarea . oninput === null ) {
this . textarea . oninput = this . _onChange . bind ( this ) ;
}
else {
// oninput is undefined. For IE8-
this . textarea . onchange = this . _onChange . bind ( this ) ;
}
}
this . setSchema ( this . options . schema ) ;
} ;
/ * *
* Handle a change :
* - Validate JSON schema
* - Send a callback to the onChange listener if provided
* @ private
* /
textmode . _onChange = function ( ) {
// validate JSON schema (if configured)
this . _debouncedValidate ( ) ;
// trigger the onChange callback
if ( this . options . onChange ) {
try {
this . options . onChange ( ) ;
}
catch ( err ) {
console . error ( 'Error in onChange callback: ' , err ) ;
}
}
} ;
/ * *
* Event handler for keydown . Handles shortcut keys
* @ param { Event } event
* @ private
* /
textmode . _onKeyDown = function ( event ) {
var keynum = event . which || event . keyCode ;
var handled = false ;
if ( keynum == 220 && event . ctrlKey ) {
if ( event . shiftKey ) { // Ctrl+Shift+\
this . compact ( ) ;
2016-01-22 03:01:49 +08:00
this . _onChange ( ) ;
2016-01-15 04:26:39 +08:00
}
else { // Ctrl+\
this . format ( ) ;
2016-01-22 03:01:49 +08:00
this . _onChange ( ) ;
2016-01-15 04:26:39 +08:00
}
handled = true ;
}
if ( handled ) {
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
}
} ;
/ * *
2016-03-21 01:19:13 +08:00
* Destroy the editor . Clean up DOM , event listeners , and web workers .
2016-01-15 04:26:39 +08:00
* /
2016-03-21 01:19:13 +08:00
textmode . destroy = function ( ) {
2016-01-15 04:26:39 +08:00
// remove old ace editor
if ( this . aceEditor ) {
this . aceEditor . destroy ( ) ;
2016-03-21 01:19:13 +08:00
this . aceEditor = null ;
2016-01-15 04:26:39 +08:00
}
if ( this . frame && this . container && this . frame . parentNode == this . container ) {
this . container . removeChild ( this . frame ) ;
}
2016-03-21 01:19:13 +08:00
if ( this . modeSwitcher ) {
this . modeSwitcher . destroy ( ) ;
this . modeSwitcher = null ;
}
this . textarea = null ;
this . _debouncedValidate = null ;
2016-01-15 04:26:39 +08:00
} ;
/ * *
* Compact the code in the formatter
* /
textmode . compact = function ( ) {
var json = this . get ( ) ;
var text = JSON . stringify ( json ) ;
this . setText ( text ) ;
} ;
/ * *
* Format the code in the formatter
* /
textmode . format = function ( ) {
var json = this . get ( ) ;
var text = JSON . stringify ( json , null , this . indentation ) ;
this . setText ( text ) ;
} ;
/ * *
* Set focus to the formatter
* /
textmode . focus = function ( ) {
if ( this . textarea ) {
this . textarea . focus ( ) ;
}
if ( this . aceEditor ) {
this . aceEditor . focus ( ) ;
}
} ;
/ * *
* Resize the formatter
* /
textmode . resize = function ( ) {
if ( this . aceEditor ) {
var force = false ;
this . aceEditor . resize ( force ) ;
}
} ;
/ * *
* Set json data in the formatter
* @ param { Object } json
* /
textmode . set = function ( json ) {
this . setText ( JSON . stringify ( json , null , this . indentation ) ) ;
} ;
/ * *
* Get json data from the formatter
* @ return { Object } json
* /
textmode . get = function ( ) {
var text = this . getText ( ) ;
var json ;
try {
json = util . parse ( text ) ; // this can throw an error
}
catch ( err ) {
// try to sanitize json, replace JavaScript notation with JSON notation
text = util . sanitize ( text ) ;
// try to parse again
json = util . parse ( text ) ; // this can throw an error
}
return json ;
} ;
/ * *
* Get the text contents of the editor
* @ return { String } jsonText
* /
textmode . getText = function ( ) {
if ( this . textarea ) {
return this . textarea . value ;
}
if ( this . aceEditor ) {
return this . aceEditor . getValue ( ) ;
}
return '' ;
} ;
/ * *
* Set the text contents of the editor
* @ param { String } jsonText
* /
textmode . setText = function ( jsonText ) {
if ( this . options . escapeUnicode === true ) {
text = util . escapeUnicodeChars ( jsonText ) ;
}
else {
text = jsonText ;
}
if ( this . textarea ) {
this . textarea . value = text ;
}
if ( this . aceEditor ) {
// prevent emitting onChange events while setting new text
var originalOnChange = this . options . onChange ;
this . options . onChange = null ;
this . aceEditor . setValue ( text , - 1 ) ;
this . options . onChange = originalOnChange ;
}
// validate JSON schema
this . validate ( ) ;
} ;
/ * *
* Validate current JSON object against the configured JSON schema
* Throws an exception when no JSON schema is configured
* /
textmode . validate = function ( ) {
// clear all current errors
if ( this . dom . validationErrors ) {
this . dom . validationErrors . parentNode . removeChild ( this . dom . validationErrors ) ;
this . dom . validationErrors = null ;
this . content . style . marginBottom = '' ;
this . content . style . paddingBottom = '' ;
}
var doValidate = false ;
var errors = [ ] ;
var json ;
try {
json = this . get ( ) ; // this can fail when there is no valid json
doValidate = true ;
}
catch ( err ) {
// no valid JSON, don't validate
}
// only validate the JSON when parsing the JSON succeeded
if ( doValidate && this . validateSchema ) {
var valid = this . validateSchema ( json ) ;
if ( ! valid ) {
2016-01-16 17:55:45 +08:00
errors = this . validateSchema . errors . map ( function ( error ) {
return util . improveSchemaError ( error ) ;
} ) ;
2016-01-15 04:26:39 +08:00
}
}
if ( errors . length > 0 ) {
// limit the number of displayed errors
var limit = errors . length > MAX _ERRORS ;
if ( limit ) {
errors = errors . slice ( 0 , MAX _ERRORS ) ;
var hidden = this . validateSchema . errors . length - MAX _ERRORS ;
errors . push ( '(' + hidden + ' more errors...)' )
}
var validationErrors = document . createElement ( 'div' ) ;
validationErrors . innerHTML = '<table class="jsoneditor-text-errors">' +
'<tbody>' +
errors . map ( function ( error ) {
var message ;
if ( typeof error === 'string' ) {
message = '<td colspan="2"><pre>' + error + '</pre></td>' ;
}
else {
message = '<td>' + error . dataPath + '</td>' +
'<td>' + error . message + '</td>' ;
}
return '<tr><td><button class="jsoneditor-schema-error"></button></td>' + message + '</tr>'
} ) . join ( '' ) +
'</tbody>' +
'</table>' ;
this . dom . validationErrors = validationErrors ;
this . frame . appendChild ( validationErrors ) ;
var height = validationErrors . clientHeight ;
this . content . style . marginBottom = ( - height ) + 'px' ;
this . content . style . paddingBottom = height + 'px' ;
}
// update the height of the ace editor
if ( this . aceEditor ) {
var force = false ;
this . aceEditor . resize ( force ) ;
}
} ;
// define modes
module . exports = [
{
mode : 'text' ,
mixin : textmode ,
data : 'text' ,
load : textmode . format
} ,
{
mode : 'code' ,
mixin : textmode ,
data : 'text' ,
load : textmode . format
}
] ;
/***/ } ,
/* 3 */
/***/ function ( module , exports , _ _webpack _require _ _ ) {
var jsonlint = _ _webpack _require _ _ ( 10 ) ;
/ * *
* Parse JSON using the parser built - in in the browser .
* On exception , the jsonString is validated and a detailed error is thrown .
* @ param { String } jsonString
* @ return { JSON } json
* /
exports . parse = function parse ( jsonString ) {
try {
return JSON . parse ( jsonString ) ;
}
catch ( err ) {
// try to throw a more detailed error message using validate
exports . validate ( jsonString ) ;
// rethrow the original error
throw err ;
}
} ;
/ * *
* Sanitize a JSON - like string containing . For example changes JavaScript
* notation into JSON notation .
* This function for example changes a string like "{a: 2, 'b': {c: 'd'}"
* into '{"a": 2, "b": {"c": "d"}'
* @ param { string } jsString
* @ returns { string } json
* /
exports . sanitize = function ( jsString ) {
// escape all single and double quotes inside strings
var chars = [ ] ;
var i = 0 ;
//If JSON starts with a function (characters/digits/"_-"), remove this function.
//This is useful for "stripping" JSONP objects to become JSON
//For example: /* some comment */ function_12321321 ( [{"a":"b"}] ); => [{"a":"b"}]
var match = jsString . match ( /^\s*(\/\*(.|[\r\n])*?\*\/)?\s*[\da-zA-Z_$]+\s*\(([\s\S]*)\)\s*;?\s*$/ ) ;
if ( match ) {
jsString = match [ 3 ] ;
}
// helper functions to get the current/prev/next character
function curr ( ) { return jsString . charAt ( i ) ; }
function next ( ) { return jsString . charAt ( i + 1 ) ; }
function prev ( ) { return jsString . charAt ( i - 1 ) ; }
2016-01-22 03:01:49 +08:00
// get the last parsed non-whitespace character
function lastNonWhitespace ( ) {
var p = chars . length - 1 ;
while ( p >= 0 ) {
var pp = chars [ p ] ;
if ( pp !== ' ' && pp !== '\n' && pp !== '\r' && pp !== '\t' ) { // non whitespace
return pp ;
2016-01-15 04:26:39 +08:00
}
2016-01-22 03:01:49 +08:00
p -- ;
2016-01-15 04:26:39 +08:00
}
2016-01-22 03:01:49 +08:00
return '' ;
2016-01-15 04:26:39 +08:00
}
// skip a block comment '/* ... */'
2016-01-22 03:01:49 +08:00
function skipBlockComment ( ) {
2016-01-15 04:26:39 +08:00
i += 2 ;
while ( i < jsString . length && ( curr ( ) !== '*' || next ( ) !== '/' ) ) {
i ++ ;
}
i += 2 ;
}
2016-01-22 03:01:49 +08:00
// skip a comment '// ...'
function skipComment ( ) {
i += 2 ;
while ( i < jsString . length && ( curr ( ) !== '\n' ) ) {
i ++ ;
}
}
2016-01-15 04:26:39 +08:00
// parse single or double quoted string
function parseString ( quote ) {
chars . push ( '"' ) ;
i ++ ;
var c = curr ( ) ;
while ( i < jsString . length && c !== quote ) {
if ( c === '"' && prev ( ) !== '\\' ) {
// unescaped double quote, escape it
chars . push ( '\\' ) ;
}
// handle escape character
if ( c === '\\' ) {
i ++ ;
c = curr ( ) ;
// remove the escape character when followed by a single quote ', not needed
if ( c !== '\'' ) {
chars . push ( '\\' ) ;
}
}
chars . push ( c ) ;
i ++ ;
c = curr ( ) ;
}
if ( c === quote ) {
chars . push ( '"' ) ;
i ++ ;
}
}
// parse an unquoted key
function parseKey ( ) {
var specialValues = [ 'null' , 'true' , 'false' ] ;
var key = '' ;
var c = curr ( ) ;
var regexp = /[a-zA-Z_$\d]/ ; // letter, number, underscore, dollar character
while ( regexp . test ( c ) ) {
key += c ;
i ++ ;
c = curr ( ) ;
}
if ( specialValues . indexOf ( key ) === - 1 ) {
chars . push ( '"' + key + '"' ) ;
}
else {
chars . push ( key ) ;
}
}
while ( i < jsString . length ) {
var c = curr ( ) ;
if ( c === '/' && next ( ) === '*' ) {
2016-01-22 03:01:49 +08:00
skipBlockComment ( ) ;
}
else if ( c === '/' && next ( ) === '/' ) {
2016-01-15 04:26:39 +08:00
skipComment ( ) ;
}
else if ( c === '\'' || c === '"' ) {
parseString ( c ) ;
}
2016-01-22 03:01:49 +08:00
else if ( /[a-zA-Z_$]/ . test ( c ) && [ '{' , ',' ] . indexOf ( lastNonWhitespace ( ) ) !== - 1 ) {
2016-01-15 04:26:39 +08:00
// an unquoted object key (like a in '{a:2}')
parseKey ( ) ;
}
else {
chars . push ( c ) ;
i ++ ;
}
}
return chars . join ( '' ) ;
} ;
/ * *
* Escape unicode characters .
* For example input '\u2661' ( length 1 ) will output '\\u2661' ( length 5 ) .
* @ param { string } text
* @ return { string }
* /
exports . escapeUnicodeChars = function ( text ) {
// see https://www.wikiwand.com/en/UTF-16
// note: we leave surrogate pairs as two individual chars,
// as JSON doesn't interpret them as a single unicode char.
return text . replace ( /[\u007F-\uFFFF]/g , function ( c ) {
return '\\u' + ( '0000' + c . charCodeAt ( 0 ) . toString ( 16 ) ) . slice ( - 4 ) ;
} )
} ;
/ * *
* Validate a string containing a JSON object
* This method uses JSONLint to validate the String . If JSONLint is not
* available , the built - in JSON parser of the browser is used .
* @ param { String } jsonString String with an ( invalid ) JSON object
* @ throws Error
* /
exports . validate = function validate ( jsonString ) {
if ( typeof ( jsonlint ) != 'undefined' ) {
jsonlint . parse ( jsonString ) ;
}
else {
JSON . parse ( jsonString ) ;
}
} ;
/ * *
* Extend object a with the properties of object b
* @ param { Object } a
* @ param { Object } b
* @ return { Object } a
* /
exports . extend = function extend ( a , b ) {
for ( var prop in b ) {
if ( b . hasOwnProperty ( prop ) ) {
a [ prop ] = b [ prop ] ;
}
}
return a ;
} ;
/ * *
* Remove all properties from object a
* @ param { Object } a
* @ return { Object } a
* /
exports . clear = function clear ( a ) {
for ( var prop in a ) {
if ( a . hasOwnProperty ( prop ) ) {
delete a [ prop ] ;
}
}
return a ;
} ;
/ * *
* Get the type of an object
* @ param { * } object
* @ return { String } type
* /
exports . type = function type ( object ) {
if ( object === null ) {
return 'null' ;
}
if ( object === undefined ) {
return 'undefined' ;
}
if ( ( object instanceof Number ) || ( typeof object === 'number' ) ) {
return 'number' ;
}
if ( ( object instanceof String ) || ( typeof object === 'string' ) ) {
return 'string' ;
}
if ( ( object instanceof Boolean ) || ( typeof object === 'boolean' ) ) {
return 'boolean' ;
}
if ( ( object instanceof RegExp ) || ( typeof object === 'regexp' ) ) {
return 'regexp' ;
}
if ( exports . isArray ( object ) ) {
return 'array' ;
}
return 'object' ;
} ;
/ * *
* Test whether a text contains a url ( matches when a string starts
* with 'http://*' or 'https://*' and has no whitespace characters )
* @ param { String } text
* /
var isUrlRegex = /^https?:\/\/\S+$/ ;
exports . isUrl = function isUrl ( text ) {
return ( typeof text == 'string' || text instanceof String ) &&
isUrlRegex . test ( text ) ;
} ;
/ * *
* Tes whether given object is an Array
* @ param { * } obj
* @ returns { boolean } returns true when obj is an array
* /
exports . isArray = function ( obj ) {
return Object . prototype . toString . call ( obj ) === '[object Array]' ;
} ;
/ * *
* Retrieve the absolute left value of a DOM element
* @ param { Element } elem A dom element , for example a div
* @ return { Number } left The absolute left position of this element
* in the browser page .
* /
exports . getAbsoluteLeft = function getAbsoluteLeft ( elem ) {
var rect = elem . getBoundingClientRect ( ) ;
return rect . left + window . pageXOffset || document . scrollLeft || 0 ;
} ;
/ * *
* Retrieve the absolute top value of a DOM element
* @ param { Element } elem A dom element , for example a div
* @ return { Number } top The absolute top position of this element
* in the browser page .
* /
exports . getAbsoluteTop = function getAbsoluteTop ( elem ) {
var rect = elem . getBoundingClientRect ( ) ;
return rect . top + window . pageYOffset || document . scrollTop || 0 ;
} ;
/ * *
* add a className to the given elements style
* @ param { Element } elem
* @ param { String } className
* /
exports . addClassName = function addClassName ( elem , className ) {
var classes = elem . className . split ( ' ' ) ;
if ( classes . indexOf ( className ) == - 1 ) {
classes . push ( className ) ; // add the class to the array
elem . className = classes . join ( ' ' ) ;
}
} ;
/ * *
* add a className to the given elements style
* @ param { Element } elem
* @ param { String } className
* /
exports . removeClassName = function removeClassName ( elem , className ) {
var classes = elem . className . split ( ' ' ) ;
var index = classes . indexOf ( className ) ;
if ( index != - 1 ) {
classes . splice ( index , 1 ) ; // remove the class from the array
elem . className = classes . join ( ' ' ) ;
}
} ;
/ * *
* Strip the formatting from the contents of a div
* the formatting from the div itself is not stripped , only from its childs .
* @ param { Element } divElement
* /
exports . stripFormatting = function stripFormatting ( divElement ) {
var childs = divElement . childNodes ;
for ( var i = 0 , iMax = childs . length ; i < iMax ; i ++ ) {
var child = childs [ i ] ;
// remove the style
if ( child . style ) {
// TODO: test if child.attributes does contain style
child . removeAttribute ( 'style' ) ;
}
// remove all attributes
var attributes = child . attributes ;
if ( attributes ) {
for ( var j = attributes . length - 1 ; j >= 0 ; j -- ) {
var attribute = attributes [ j ] ;
if ( attribute . specified === true ) {
child . removeAttribute ( attribute . name ) ;
}
}
}
// recursively strip childs
exports . stripFormatting ( child ) ;
}
} ;
/ * *
* Set focus to the end of an editable div
* code from Nico Burns
* http : //stackoverflow.com/users/140293/nico-burns
* http : //stackoverflow.com/questions/1125292/how-to-move-cursor-to-end-of-contenteditable-entity
* @ param { Element } contentEditableElement A content editable div
* /
exports . setEndOfContentEditable = function setEndOfContentEditable ( contentEditableElement ) {
var range , selection ;
if ( document . createRange ) {
range = document . createRange ( ) ; //Create a range (a range is a like the selection but invisible)
range . selectNodeContents ( contentEditableElement ) ; //Select the entire contents of the element with the range
range . collapse ( false ) ; //collapse the range to the end point. false means collapse to end rather than the start
selection = window . getSelection ( ) ; //get the selection object (allows you to change selection)
selection . removeAllRanges ( ) ; //remove any selections already made
selection . addRange ( range ) ; //make the range you have just created the visible selection
}
} ;
/ * *
* Select all text of a content editable div .
* http : //stackoverflow.com/a/3806004/1262753
* @ param { Element } contentEditableElement A content editable div
* /
exports . selectContentEditable = function selectContentEditable ( contentEditableElement ) {
if ( ! contentEditableElement || contentEditableElement . nodeName != 'DIV' ) {
return ;
}
var sel , range ;
if ( window . getSelection && document . createRange ) {
range = document . createRange ( ) ;
range . selectNodeContents ( contentEditableElement ) ;
sel = window . getSelection ( ) ;
sel . removeAllRanges ( ) ;
sel . addRange ( range ) ;
}
} ;
/ * *
* Get text selection
* http : //stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore
* @ return { Range | TextRange | null } range
* /
exports . getSelection = function getSelection ( ) {
if ( window . getSelection ) {
var sel = window . getSelection ( ) ;
if ( sel . getRangeAt && sel . rangeCount ) {
return sel . getRangeAt ( 0 ) ;
}
}
return null ;
} ;
/ * *
* Set text selection
* http : //stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore
* @ param { Range | TextRange | null } range
* /
exports . setSelection = function setSelection ( range ) {
if ( range ) {
if ( window . getSelection ) {
var sel = window . getSelection ( ) ;
sel . removeAllRanges ( ) ;
sel . addRange ( range ) ;
}
}
} ;
/ * *
* Get selected text range
* @ return { Object } params object containing parameters :
* { Number } startOffset
* { Number } endOffset
* { Element } container HTML element holding the
* selected text element
* Returns null if no text selection is found
* /
exports . getSelectionOffset = function getSelectionOffset ( ) {
var range = exports . getSelection ( ) ;
if ( range && 'startOffset' in range && 'endOffset' in range &&
range . startContainer && ( range . startContainer == range . endContainer ) ) {
return {
startOffset : range . startOffset ,
endOffset : range . endOffset ,
container : range . startContainer . parentNode
} ;
}
return null ;
} ;
/ * *
* Set selected text range in given element
* @ param { Object } params An object containing :
* { Element } container
* { Number } startOffset
* { Number } endOffset
* /
exports . setSelectionOffset = function setSelectionOffset ( params ) {
if ( document . createRange && window . getSelection ) {
var selection = window . getSelection ( ) ;
if ( selection ) {
var range = document . createRange ( ) ;
if ( ! params . container . firstChild ) {
params . container . appendChild ( document . createTextNode ( '' ) ) ;
}
// TODO: do not suppose that the first child of the container is a textnode,
// but recursively find the textnodes
range . setStart ( params . container . firstChild , params . startOffset ) ;
range . setEnd ( params . container . firstChild , params . endOffset ) ;
exports . setSelection ( range ) ;
}
}
} ;
/ * *
* Get the inner text of an HTML element ( for example a div element )
* @ param { Element } element
* @ param { Object } [ buffer ]
* @ return { String } innerText
* /
exports . getInnerText = function getInnerText ( element , buffer ) {
var first = ( buffer == undefined ) ;
if ( first ) {
buffer = {
'text' : '' ,
'flush' : function ( ) {
var text = this . text ;
this . text = '' ;
return text ;
} ,
'set' : function ( text ) {
this . text = text ;
}
} ;
}
// text node
if ( element . nodeValue ) {
return buffer . flush ( ) + element . nodeValue ;
}
// divs or other HTML elements
if ( element . hasChildNodes ( ) ) {
var childNodes = element . childNodes ;
var innerText = '' ;
for ( var i = 0 , iMax = childNodes . length ; i < iMax ; i ++ ) {
var child = childNodes [ i ] ;
if ( child . nodeName == 'DIV' || child . nodeName == 'P' ) {
var prevChild = childNodes [ i - 1 ] ;
var prevName = prevChild ? prevChild . nodeName : undefined ;
if ( prevName && prevName != 'DIV' && prevName != 'P' && prevName != 'BR' ) {
innerText += '\n' ;
buffer . flush ( ) ;
}
innerText += exports . getInnerText ( child , buffer ) ;
buffer . set ( '\n' ) ;
}
else if ( child . nodeName == 'BR' ) {
innerText += buffer . flush ( ) ;
buffer . set ( '\n' ) ;
}
else {
innerText += exports . getInnerText ( child , buffer ) ;
}
}
return innerText ;
}
else {
if ( element . nodeName == 'P' && exports . getInternetExplorerVersion ( ) != - 1 ) {
// On Internet Explorer, a <p> with hasChildNodes()==false is
// rendered with a new line. Note that a <p> with
// hasChildNodes()==true is rendered without a new line
// Other browsers always ensure there is a <br> inside the <p>,
// and if not, the <p> does not render a new line
return buffer . flush ( ) ;
}
}
// br or unknown
return '' ;
} ;
/ * *
* Returns the version of Internet Explorer or a - 1
* ( indicating the use of another browser ) .
* Source : http : //msdn.microsoft.com/en-us/library/ms537509(v=vs.85).aspx
* @ return { Number } Internet Explorer version , or - 1 in case of an other browser
* /
exports . getInternetExplorerVersion = function getInternetExplorerVersion ( ) {
if ( _ieVersion == - 1 ) {
var rv = - 1 ; // Return value assumes failure.
if ( navigator . appName == 'Microsoft Internet Explorer' )
{
var ua = navigator . userAgent ;
var re = new RegExp ( "MSIE ([0-9]{1,}[\.0-9]{0,})" ) ;
if ( re . exec ( ua ) != null ) {
rv = parseFloat ( RegExp . $1 ) ;
}
}
_ieVersion = rv ;
}
return _ieVersion ;
} ;
/ * *
* Test whether the current browser is Firefox
* @ returns { boolean } isFirefox
* /
exports . isFirefox = function isFirefox ( ) {
return ( navigator . userAgent . indexOf ( "Firefox" ) != - 1 ) ;
} ;
/ * *
* cached internet explorer version
* @ type { Number }
* @ private
* /
var _ieVersion = - 1 ;
/ * *
* Add and event listener . Works for all browsers
* @ param { Element } element An html element
* @ param { string } action The action , for example "click" ,
* without the prefix "on"
* @ param { function } listener The callback function to be executed
* @ param { boolean } [ useCapture ] false by default
* @ return { function } the created event listener
* /
exports . addEventListener = function addEventListener ( element , action , listener , useCapture ) {
if ( element . addEventListener ) {
if ( useCapture === undefined )
useCapture = false ;
if ( action === "mousewheel" && exports . isFirefox ( ) ) {
action = "DOMMouseScroll" ; // For Firefox
}
element . addEventListener ( action , listener , useCapture ) ;
return listener ;
} else if ( element . attachEvent ) {
// Old IE browsers
var f = function ( ) {
return listener . call ( element , window . event ) ;
} ;
element . attachEvent ( "on" + action , f ) ;
return f ;
}
} ;
/ * *
* Remove an event listener from an element
* @ param { Element } element An html dom element
* @ param { string } action The name of the event , for example "mousedown"
* @ param { function } listener The listener function
* @ param { boolean } [ useCapture ] false by default
* /
exports . removeEventListener = function removeEventListener ( element , action , listener , useCapture ) {
if ( element . removeEventListener ) {
if ( useCapture === undefined )
useCapture = false ;
if ( action === "mousewheel" && exports . isFirefox ( ) ) {
action = "DOMMouseScroll" ; // For Firefox
}
element . removeEventListener ( action , listener , useCapture ) ;
} else if ( element . detachEvent ) {
// Old IE browsers
element . detachEvent ( "on" + action , listener ) ;
}
} ;
/ * *
* Parse a JSON path like '.items[3].name' into an array
* @ param { string } jsonPath
* @ return { Array }
* /
exports . parsePath = function parsePath ( jsonPath ) {
var prop , remainder ;
if ( jsonPath . length === 0 ) {
return [ ] ;
}
// find a match like '.prop'
var match = jsonPath . match ( /^\.(\w+)/ ) ;
if ( match ) {
prop = match [ 1 ] ;
remainder = jsonPath . substr ( prop . length + 1 ) ;
}
else if ( jsonPath [ 0 ] === '[' ) {
// find a match like
var end = jsonPath . indexOf ( ']' ) ;
if ( end === - 1 ) {
throw new SyntaxError ( 'Character ] expected in path' ) ;
}
if ( end === 1 ) {
throw new SyntaxError ( 'Index expected after [' ) ;
}
prop = JSON . parse ( jsonPath . substring ( 1 , end ) ) ;
remainder = jsonPath . substr ( end + 1 ) ;
}
else {
throw new SyntaxError ( 'Failed to parse path' ) ;
}
return [ prop ] . concat ( parsePath ( remainder ) )
} ;
/ * *
* Improve the error message of a JSON schema error
* @ param { Object } error
* @ return { Object } The error
* /
2016-01-16 17:55:45 +08:00
exports . improveSchemaError = function ( error ) {
if ( error . keyword === 'enum' && Array . isArray ( error . schema ) ) {
var enums = error . schema ;
2016-01-15 04:26:39 +08:00
if ( enums ) {
enums = enums . map ( function ( value ) {
return JSON . stringify ( value ) ;
} ) ;
if ( enums . length > 5 ) {
var more = [ '(' + ( enums . length - 5 ) + ' more...)' ] ;
enums = enums . slice ( 0 , 5 ) ;
enums . push ( more ) ;
}
error . message = 'should be equal to one of: ' + enums . join ( ', ' ) ;
}
}
return error ;
} ;
/ * *
* Test whether the child rect fits completely inside the parent rect .
* @ param { ClientRect } parent
* @ param { ClientRect } child
* @ param { number } margin
* /
exports . insideRect = function ( parent , child , margin ) {
var _margin = margin !== undefined ? margin : 0 ;
return child . left - _margin >= parent . left
&& child . right + _margin <= parent . right
&& child . top - _margin >= parent . top
&& child . bottom + _margin <= parent . bottom ;
} ;
/ * *
* Returns a function , that , as long as it continues to be invoked , will not
* be triggered . The function will be called after it stops being called for
* N milliseconds .
*
* Source : https : //davidwalsh.name/javascript-debounce-function
*
* @ param { function } func
* @ param { number } wait Number in milliseconds
* @ param { boolean } [ immediate = false ] If ` immediate ` is passed , trigger the
* function on the leading edge , instead
* of the trailing .
* @ return { function } Return the debounced function
* /
exports . debounce = function debounce ( func , wait , immediate ) {
var timeout ;
return function ( ) {
var context = this , args = arguments ;
var later = function ( ) {
timeout = null ;
if ( ! immediate ) func . apply ( context , args ) ;
} ;
var callNow = immediate && ! timeout ;
clearTimeout ( timeout ) ;
timeout = setTimeout ( later , wait ) ;
if ( callNow ) func . apply ( context , args ) ;
} ;
} ;
/ * *
* Determines the difference between two texts .
* Can only detect one removed or inserted block of characters .
* @ param { string } oldText
* @ param { string } newText
* @ return { { start : number , end : number } } Returns the start and end
* of the changed part in newText .
* /
exports . textDiff = function textDiff ( oldText , newText ) {
var len = newText . length ;
var start = 0 ;
var oldEnd = oldText . length ;
var newEnd = newText . length ;
while ( newText . charAt ( start ) === oldText . charAt ( start )
&& start < len ) {
start ++ ;
}
while ( newText . charAt ( newEnd - 1 ) === oldText . charAt ( oldEnd - 1 )
&& newEnd > start && oldEnd > 0 ) {
newEnd -- ;
oldEnd -- ;
}
return { start : start , end : newEnd } ;
} ;
/***/ } ,
/* 4 */
/***/ function ( module , exports , _ _webpack _require _ _ ) {
/ * *
* The highlighter can highlight / unhighlight a node , and
* animate the visibility of a context menu .
* @ constructor Highlighter
* /
function Highlighter ( ) {
this . locked = false ;
}
/ * *
* Hightlight given node and its childs
* @ param { Node } node
* /
Highlighter . prototype . highlight = function ( node ) {
if ( this . locked ) {
return ;
}
if ( this . node != node ) {
// unhighlight current node
if ( this . node ) {
this . node . setHighlight ( false ) ;
}
// highlight new node
this . node = node ;
this . node . setHighlight ( true ) ;
}
// cancel any current timeout
this . _cancelUnhighlight ( ) ;
} ;
/ * *
* Unhighlight currently highlighted node .
* Will be done after a delay
* /
Highlighter . prototype . unhighlight = function ( ) {
if ( this . locked ) {
return ;
}
var me = this ;
if ( this . node ) {
this . _cancelUnhighlight ( ) ;
// do the unhighlighting after a small delay, to prevent re-highlighting
// the same node when moving from the drag-icon to the contextmenu-icon
// or vice versa.
this . unhighlightTimer = setTimeout ( function ( ) {
me . node . setHighlight ( false ) ;
me . node = undefined ;
me . unhighlightTimer = undefined ;
} , 0 ) ;
}
} ;
/ * *
* Cancel an unhighlight action ( if before the timeout of the unhighlight action )
* @ private
* /
Highlighter . prototype . _cancelUnhighlight = function ( ) {
if ( this . unhighlightTimer ) {
clearTimeout ( this . unhighlightTimer ) ;
this . unhighlightTimer = undefined ;
}
} ;
/ * *
* Lock highlighting or unhighlighting nodes .
* methods highlight and unhighlight do not work while locked .
* /
Highlighter . prototype . lock = function ( ) {
this . locked = true ;
} ;
/ * *
* Unlock highlighting or unhighlighting nodes
* /
Highlighter . prototype . unlock = function ( ) {
this . locked = false ;
} ;
module . exports = Highlighter ;
/***/ } ,
2016-02-13 18:47:51 +08:00
/* 5 */
2016-01-15 04:26:39 +08:00
/***/ function ( module , exports , _ _webpack _require _ _ ) {
var util = _ _webpack _require _ _ ( 3 ) ;
/ * *
* @ constructor History
* Store action history , enables undo and redo
* @ param { JSONEditor } editor
* /
function History ( editor ) {
this . editor = editor ;
2016-03-21 01:19:13 +08:00
this . history = [ ] ;
this . index = - 1 ;
2016-01-15 04:26:39 +08:00
this . clear ( ) ;
// map with all supported actions
this . actions = {
'editField' : {
'undo' : function ( params ) {
params . node . updateField ( params . oldValue ) ;
} ,
'redo' : function ( params ) {
params . node . updateField ( params . newValue ) ;
}
} ,
'editValue' : {
'undo' : function ( params ) {
params . node . updateValue ( params . oldValue ) ;
} ,
'redo' : function ( params ) {
params . node . updateValue ( params . newValue ) ;
}
} ,
'changeType' : {
'undo' : function ( params ) {
params . node . changeType ( params . oldType ) ;
} ,
'redo' : function ( params ) {
params . node . changeType ( params . newType ) ;
}
} ,
'appendNodes' : {
'undo' : function ( params ) {
params . nodes . forEach ( function ( node ) {
params . parent . removeChild ( node ) ;
} ) ;
} ,
'redo' : function ( params ) {
params . nodes . forEach ( function ( node ) {
params . parent . appendChild ( node ) ;
} ) ;
}
} ,
'insertBeforeNodes' : {
'undo' : function ( params ) {
params . nodes . forEach ( function ( node ) {
params . parent . removeChild ( node ) ;
} ) ;
} ,
'redo' : function ( params ) {
params . nodes . forEach ( function ( node ) {
params . parent . insertBefore ( node , params . beforeNode ) ;
} ) ;
}
} ,
'insertAfterNodes' : {
'undo' : function ( params ) {
params . nodes . forEach ( function ( node ) {
params . parent . removeChild ( node ) ;
} ) ;
} ,
'redo' : function ( params ) {
var afterNode = params . afterNode ;
params . nodes . forEach ( function ( node ) {
params . parent . insertAfter ( params . node , afterNode ) ;
afterNode = node ;
} ) ;
}
} ,
'removeNodes' : {
'undo' : function ( params ) {
var parent = params . parent ;
var beforeNode = parent . childs [ params . index ] || parent . append ;
params . nodes . forEach ( function ( node ) {
parent . insertBefore ( node , beforeNode ) ;
} ) ;
} ,
'redo' : function ( params ) {
params . nodes . forEach ( function ( node ) {
params . parent . removeChild ( node ) ;
} ) ;
}
} ,
'duplicateNodes' : {
'undo' : function ( params ) {
params . nodes . forEach ( function ( node ) {
params . parent . removeChild ( node ) ;
} ) ;
} ,
'redo' : function ( params ) {
var afterNode = params . afterNode ;
params . nodes . forEach ( function ( node ) {
params . parent . insertAfter ( node , afterNode ) ;
afterNode = node ;
} ) ;
}
} ,
'moveNodes' : {
'undo' : function ( params ) {
params . nodes . forEach ( function ( node ) {
params . oldBeforeNode . parent . moveBefore ( node , params . oldBeforeNode ) ;
} ) ;
} ,
'redo' : function ( params ) {
params . nodes . forEach ( function ( node ) {
params . newBeforeNode . parent . moveBefore ( node , params . newBeforeNode ) ;
} ) ;
}
} ,
'sort' : {
'undo' : function ( params ) {
var node = params . node ;
node . hideChilds ( ) ;
node . sort = params . oldSort ;
node . childs = params . oldChilds ;
node . showChilds ( ) ;
} ,
'redo' : function ( params ) {
var node = params . node ;
node . hideChilds ( ) ;
node . sort = params . newSort ;
node . childs = params . newChilds ;
node . showChilds ( ) ;
}
}
// TODO: restore the original caret position and selection with each undo
// TODO: implement history for actions "expand", "collapse", "scroll", "setDocument"
} ;
}
/ * *
* The method onChange is executed when the History is changed , and can
* be overloaded .
* /
History . prototype . onChange = function ( ) { } ;
/ * *
* Add a new action to the history
* @ param { String } action The executed action . Available actions : "editField" ,
* "editValue" , "changeType" , "appendNode" ,
* "removeNode" , "duplicateNode" , "moveNode"
* @ param { Object } params Object containing parameters describing the change .
* The parameters in params depend on the action ( for
* example for "editValue" the Node , old value , and new
* value are provided ) . params contains all information
* needed to undo or redo the action .
* /
History . prototype . add = function ( action , params ) {
this . index ++ ;
this . history [ this . index ] = {
'action' : action ,
'params' : params ,
'timestamp' : new Date ( )
} ;
// remove redo actions which are invalid now
if ( this . index < this . history . length - 1 ) {
this . history . splice ( this . index + 1 , this . history . length - this . index - 1 ) ;
}
// fire onchange event
this . onChange ( ) ;
} ;
/ * *
* Clear history
* /
History . prototype . clear = function ( ) {
this . history = [ ] ;
this . index = - 1 ;
// fire onchange event
this . onChange ( ) ;
} ;
/ * *
* Check if there is an action available for undo
* @ return { Boolean } canUndo
* /
History . prototype . canUndo = function ( ) {
return ( this . index >= 0 ) ;
} ;
/ * *
* Check if there is an action available for redo
* @ return { Boolean } canRedo
* /
History . prototype . canRedo = function ( ) {
return ( this . index < this . history . length - 1 ) ;
} ;
/ * *
* Undo the last action
* /
History . prototype . undo = function ( ) {
if ( this . canUndo ( ) ) {
var obj = this . history [ this . index ] ;
if ( obj ) {
var action = this . actions [ obj . action ] ;
if ( action && action . undo ) {
action . undo ( obj . params ) ;
if ( obj . params . oldSelection ) {
this . editor . setSelection ( obj . params . oldSelection ) ;
}
}
else {
console . error ( new Error ( 'unknown action "' + obj . action + '"' ) ) ;
}
}
this . index -- ;
// fire onchange event
this . onChange ( ) ;
}
} ;
/ * *
* Redo the last action
* /
History . prototype . redo = function ( ) {
if ( this . canRedo ( ) ) {
this . index ++ ;
var obj = this . history [ this . index ] ;
if ( obj ) {
var action = this . actions [ obj . action ] ;
if ( action && action . redo ) {
action . redo ( obj . params ) ;
if ( obj . params . newSelection ) {
this . editor . setSelection ( obj . params . newSelection ) ;
}
}
else {
console . error ( new Error ( 'unknown action "' + obj . action + '"' ) ) ;
}
}
// fire onchange event
this . onChange ( ) ;
}
} ;
2016-03-21 01:19:13 +08:00
/ * *
* Destroy history
* /
History . prototype . destroy = function ( ) {
this . editor = null ;
this . history = [ ] ;
this . index = - 1 ;
} ;
2016-01-15 04:26:39 +08:00
module . exports = History ;
/***/ } ,
2016-02-13 18:47:51 +08:00
/* 6 */
2016-01-15 04:26:39 +08:00
/***/ function ( module , exports , _ _webpack _require _ _ ) {
/ * *
* @ constructor SearchBox
* Create a search box in given HTML container
* @ param { JSONEditor } editor The JSON Editor to attach to
* @ param { Element } container HTML container element of where to
* create the search box
* /
function SearchBox ( editor , container ) {
var searchBox = this ;
this . editor = editor ;
this . timeout = undefined ;
this . delay = 200 ; // ms
this . lastText = undefined ;
this . dom = { } ;
this . dom . container = container ;
var table = document . createElement ( 'table' ) ;
this . dom . table = table ;
table . className = 'jsoneditor-search' ;
container . appendChild ( table ) ;
var tbody = document . createElement ( 'tbody' ) ;
this . dom . tbody = tbody ;
table . appendChild ( tbody ) ;
var tr = document . createElement ( 'tr' ) ;
tbody . appendChild ( tr ) ;
var td = document . createElement ( 'td' ) ;
tr . appendChild ( td ) ;
var results = document . createElement ( 'div' ) ;
this . dom . results = results ;
results . className = 'jsoneditor-results' ;
td . appendChild ( results ) ;
td = document . createElement ( 'td' ) ;
tr . appendChild ( td ) ;
var divInput = document . createElement ( 'div' ) ;
this . dom . input = divInput ;
divInput . className = 'jsoneditor-frame' ;
divInput . title = 'Search fields and values' ;
td . appendChild ( divInput ) ;
// table to contain the text input and search button
var tableInput = document . createElement ( 'table' ) ;
divInput . appendChild ( tableInput ) ;
var tbodySearch = document . createElement ( 'tbody' ) ;
tableInput . appendChild ( tbodySearch ) ;
tr = document . createElement ( 'tr' ) ;
tbodySearch . appendChild ( tr ) ;
var refreshSearch = document . createElement ( 'button' ) ;
refreshSearch . className = 'jsoneditor-refresh' ;
td = document . createElement ( 'td' ) ;
td . appendChild ( refreshSearch ) ;
tr . appendChild ( td ) ;
var search = document . createElement ( 'input' ) ;
this . dom . search = search ;
search . oninput = function ( event ) {
searchBox . _onDelayedSearch ( event ) ;
} ;
search . onchange = function ( event ) { // For IE 9
searchBox . _onSearch ( ) ;
} ;
search . onkeydown = function ( event ) {
searchBox . _onKeyDown ( event ) ;
} ;
search . onkeyup = function ( event ) {
searchBox . _onKeyUp ( event ) ;
} ;
refreshSearch . onclick = function ( event ) {
search . select ( ) ;
} ;
// TODO: ESC in FF restores the last input, is a FF bug, https://bugzilla.mozilla.org/show_bug.cgi?id=598819
td = document . createElement ( 'td' ) ;
td . appendChild ( search ) ;
tr . appendChild ( td ) ;
var searchNext = document . createElement ( 'button' ) ;
searchNext . title = 'Next result (Enter)' ;
searchNext . className = 'jsoneditor-next' ;
searchNext . onclick = function ( ) {
searchBox . next ( ) ;
} ;
td = document . createElement ( 'td' ) ;
td . appendChild ( searchNext ) ;
tr . appendChild ( td ) ;
var searchPrevious = document . createElement ( 'button' ) ;
searchPrevious . title = 'Previous result (Shift+Enter)' ;
searchPrevious . className = 'jsoneditor-previous' ;
searchPrevious . onclick = function ( ) {
searchBox . previous ( ) ;
} ;
td = document . createElement ( 'td' ) ;
td . appendChild ( searchPrevious ) ;
tr . appendChild ( td ) ;
}
/ * *
* Go to the next search result
* @ param { boolean } [ focus ] If true , focus will be set to the next result
* focus is false by default .
* /
SearchBox . prototype . next = function ( focus ) {
if ( this . results != undefined ) {
var index = ( this . resultIndex != undefined ) ? this . resultIndex + 1 : 0 ;
if ( index > this . results . length - 1 ) {
index = 0 ;
}
this . _setActiveResult ( index , focus ) ;
}
} ;
/ * *
* Go to the prevous search result
* @ param { boolean } [ focus ] If true , focus will be set to the next result
* focus is false by default .
* /
SearchBox . prototype . previous = function ( focus ) {
if ( this . results != undefined ) {
var max = this . results . length - 1 ;
var index = ( this . resultIndex != undefined ) ? this . resultIndex - 1 : max ;
if ( index < 0 ) {
index = max ;
}
this . _setActiveResult ( index , focus ) ;
}
} ;
/ * *
* Set new value for the current active result
* @ param { Number } index
* @ param { boolean } [ focus ] If true , focus will be set to the next result .
* focus is false by default .
* @ private
* /
SearchBox . prototype . _setActiveResult = function ( index , focus ) {
// de-activate current active result
if ( this . activeResult ) {
var prevNode = this . activeResult . node ;
var prevElem = this . activeResult . elem ;
if ( prevElem == 'field' ) {
delete prevNode . searchFieldActive ;
}
else {
delete prevNode . searchValueActive ;
}
prevNode . updateDom ( ) ;
}
if ( ! this . results || ! this . results [ index ] ) {
// out of range, set to undefined
this . resultIndex = undefined ;
this . activeResult = undefined ;
return ;
}
this . resultIndex = index ;
// set new node active
var node = this . results [ this . resultIndex ] . node ;
var elem = this . results [ this . resultIndex ] . elem ;
if ( elem == 'field' ) {
node . searchFieldActive = true ;
}
else {
node . searchValueActive = true ;
}
this . activeResult = this . results [ this . resultIndex ] ;
node . updateDom ( ) ;
// TODO: not so nice that the focus is only set after the animation is finished
node . scrollTo ( function ( ) {
if ( focus ) {
node . focus ( elem ) ;
}
} ) ;
} ;
/ * *
* Cancel any running onDelayedSearch .
* @ private
* /
SearchBox . prototype . _clearDelay = function ( ) {
if ( this . timeout != undefined ) {
clearTimeout ( this . timeout ) ;
delete this . timeout ;
}
} ;
/ * *
* Start a timer to execute a search after a short delay .
* Used for reducing the number of searches while typing .
* @ param { Event } event
* @ private
* /
SearchBox . prototype . _onDelayedSearch = function ( event ) {
// execute the search after a short delay (reduces the number of
// search actions while typing in the search text box)
this . _clearDelay ( ) ;
var searchBox = this ;
this . timeout = setTimeout ( function ( event ) {
searchBox . _onSearch ( ) ;
} ,
this . delay ) ;
} ;
/ * *
* Handle onSearch event
* @ param { boolean } [ forceSearch ] If true , search will be executed again even
* when the search text is not changed .
* Default is false .
* @ private
* /
SearchBox . prototype . _onSearch = function ( forceSearch ) {
this . _clearDelay ( ) ;
var value = this . dom . search . value ;
var text = ( value . length > 0 ) ? value : undefined ;
if ( text != this . lastText || forceSearch ) {
// only search again when changed
this . lastText = text ;
this . results = this . editor . search ( text ) ;
this . _setActiveResult ( undefined ) ;
// display search results
if ( text != undefined ) {
var resultCount = this . results . length ;
switch ( resultCount ) {
case 0 : this . dom . results . innerHTML = 'no results' ; break ;
case 1 : this . dom . results . innerHTML = '1 result' ; break ;
default : this . dom . results . innerHTML = resultCount + ' results' ; break ;
}
}
else {
this . dom . results . innerHTML = '' ;
}
}
} ;
/ * *
* Handle onKeyDown event in the input box
* @ param { Event } event
* @ private
* /
SearchBox . prototype . _onKeyDown = function ( event ) {
var keynum = event . which ;
if ( keynum == 27 ) { // ESC
this . dom . search . value = '' ; // clear search
this . _onSearch ( ) ;
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
}
else if ( keynum == 13 ) { // Enter
if ( event . ctrlKey ) {
// force to search again
this . _onSearch ( true ) ;
}
else if ( event . shiftKey ) {
// move to the previous search result
this . previous ( ) ;
}
else {
// move to the next search result
this . next ( ) ;
}
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
}
} ;
/ * *
* Handle onKeyUp event in the input box
* @ param { Event } event
* @ private
* /
SearchBox . prototype . _onKeyUp = function ( event ) {
var keynum = event . keyCode ;
if ( keynum != 27 && keynum != 13 ) { // !show and !Enter
this . _onDelayedSearch ( event ) ; // For IE 9
}
} ;
/ * *
* Clear the search results
* /
SearchBox . prototype . clear = function ( ) {
this . dom . search . value = '' ;
this . _onSearch ( ) ;
} ;
2016-03-21 01:19:13 +08:00
/ * *
* Destroy the search box
* /
SearchBox . prototype . destroy = function ( ) {
this . editor = null ;
this . dom . container . removeChild ( this . dom . table ) ;
this . dom = null ;
this . results = null ;
this . activeResult = null ;
this . _clearDelay ( ) ;
} ;
2016-01-15 04:26:39 +08:00
module . exports = SearchBox ;
/***/ } ,
2016-02-13 18:47:51 +08:00
/* 7 */
2016-01-15 04:26:39 +08:00
/***/ function ( module , exports , _ _webpack _require _ _ ) {
var util = _ _webpack _require _ _ ( 3 ) ;
/ * *
* A context menu
* @ param { Object [ ] } items Array containing the menu structure
* TODO : describe structure
* @ param { Object } [ options ] Object with options . Available options :
* { function } close Callback called when the
* context menu is being closed .
* @ constructor
* /
function ContextMenu ( items , options ) {
this . dom = { } ;
var me = this ;
var dom = this . dom ;
this . anchor = undefined ;
this . items = items ;
this . eventListeners = { } ;
this . selection = undefined ; // holds the selection before the menu was opened
this . onClose = options ? options . close : undefined ;
// create root element
var root = document . createElement ( 'div' ) ;
root . className = 'jsoneditor-contextmenu-root' ;
dom . root = root ;
// create a container element
var menu = document . createElement ( 'div' ) ;
menu . className = 'jsoneditor-contextmenu' ;
dom . menu = menu ;
root . appendChild ( menu ) ;
// create a list to hold the menu items
var list = document . createElement ( 'ul' ) ;
list . className = 'jsoneditor-menu' ;
menu . appendChild ( list ) ;
dom . list = list ;
dom . items = [ ] ; // list with all buttons
// create a (non-visible) button to set the focus to the menu
var focusButton = document . createElement ( 'button' ) ;
dom . focusButton = focusButton ;
var li = document . createElement ( 'li' ) ;
li . style . overflow = 'hidden' ;
li . style . height = '0' ;
li . appendChild ( focusButton ) ;
list . appendChild ( li ) ;
function createMenuItems ( list , domItems , items ) {
items . forEach ( function ( item ) {
if ( item . type == 'separator' ) {
// create a separator
var separator = document . createElement ( 'div' ) ;
separator . className = 'jsoneditor-separator' ;
li = document . createElement ( 'li' ) ;
li . appendChild ( separator ) ;
list . appendChild ( li ) ;
}
else {
var domItem = { } ;
// create a menu item
var li = document . createElement ( 'li' ) ;
list . appendChild ( li ) ;
// create a button in the menu item
var button = document . createElement ( 'button' ) ;
button . className = item . className ;
domItem . button = button ;
if ( item . title ) {
button . title = item . title ;
}
if ( item . click ) {
2016-02-03 15:14:46 +08:00
button . onclick = function ( event ) {
2016-01-22 03:01:49 +08:00
event . preventDefault ( ) ;
2016-01-15 04:26:39 +08:00
me . hide ( ) ;
item . click ( ) ;
} ;
}
li . appendChild ( button ) ;
// create the contents of the button
if ( item . submenu ) {
// add the icon to the button
var divIcon = document . createElement ( 'div' ) ;
divIcon . className = 'jsoneditor-icon' ;
button . appendChild ( divIcon ) ;
button . appendChild ( document . createTextNode ( item . text ) ) ;
var buttonSubmenu ;
if ( item . click ) {
// submenu and a button with a click handler
button . className += ' jsoneditor-default' ;
var buttonExpand = document . createElement ( 'button' ) ;
domItem . buttonExpand = buttonExpand ;
buttonExpand . className = 'jsoneditor-expand' ;
buttonExpand . innerHTML = '<div class="jsoneditor-expand"></div>' ;
li . appendChild ( buttonExpand ) ;
if ( item . submenuTitle ) {
buttonExpand . title = item . submenuTitle ;
}
buttonSubmenu = buttonExpand ;
}
else {
// submenu and a button without a click handler
var divExpand = document . createElement ( 'div' ) ;
divExpand . className = 'jsoneditor-expand' ;
button . appendChild ( divExpand ) ;
buttonSubmenu = button ;
}
// attach a handler to expand/collapse the submenu
2016-01-22 03:01:49 +08:00
buttonSubmenu . onclick = function ( event ) {
event . preventDefault ( ) ;
2016-01-15 04:26:39 +08:00
me . _onExpandItem ( domItem ) ;
buttonSubmenu . focus ( ) ;
} ;
// create the submenu
var domSubItems = [ ] ;
domItem . subItems = domSubItems ;
var ul = document . createElement ( 'ul' ) ;
domItem . ul = ul ;
ul . className = 'jsoneditor-menu' ;
ul . style . height = '0' ;
li . appendChild ( ul ) ;
createMenuItems ( ul , domSubItems , item . submenu ) ;
}
else {
// no submenu, just a button with clickhandler
button . innerHTML = '<div class="jsoneditor-icon"></div>' + item . text ;
}
domItems . push ( domItem ) ;
}
} ) ;
}
createMenuItems ( list , this . dom . items , items ) ;
// TODO: when the editor is small, show the submenu on the right instead of inline?
// calculate the max height of the menu with one submenu expanded
this . maxHeight = 0 ; // height in pixels
items . forEach ( function ( item ) {
var height = ( items . length + ( item . submenu ? item . submenu . length : 0 ) ) * 24 ;
me . maxHeight = Math . max ( me . maxHeight , height ) ;
} ) ;
}
/ * *
* Get the currently visible buttons
* @ return { Array . < HTMLElement > } buttons
* @ private
* /
ContextMenu . prototype . _getVisibleButtons = function ( ) {
var buttons = [ ] ;
var me = this ;
this . dom . items . forEach ( function ( item ) {
buttons . push ( item . button ) ;
if ( item . buttonExpand ) {
buttons . push ( item . buttonExpand ) ;
}
if ( item . subItems && item == me . expandedItem ) {
item . subItems . forEach ( function ( subItem ) {
buttons . push ( subItem . button ) ;
if ( subItem . buttonExpand ) {
buttons . push ( subItem . buttonExpand ) ;
}
// TODO: change to fully recursive method
} ) ;
}
} ) ;
return buttons ;
} ;
// currently displayed context menu, a singleton. We may only have one visible context menu
ContextMenu . visibleMenu = undefined ;
/ * *
* Attach the menu to an anchor
* @ param { HTMLElement } anchor Anchor where the menu will be attached
* as sibling .
* @ param { HTMLElement } [ contentWindow ] The DIV with with the ( scrollable ) contents
* /
ContextMenu . prototype . show = function ( anchor , contentWindow ) {
this . hide ( ) ;
// determine whether to display the menu below or above the anchor
var showBelow = true ;
if ( contentWindow ) {
var anchorRect = anchor . getBoundingClientRect ( ) ;
var contentRect = contentWindow . getBoundingClientRect ( ) ;
if ( anchorRect . bottom + this . maxHeight < contentRect . bottom ) {
// fits below -> show below
}
else if ( anchorRect . top - this . maxHeight > contentRect . top ) {
// fits above -> show above
showBelow = false ;
}
else {
// doesn't fit above nor below -> show below
}
}
// position the menu
if ( showBelow ) {
// display the menu below the anchor
var anchorHeight = anchor . offsetHeight ;
this . dom . menu . style . left = '0px' ;
this . dom . menu . style . top = anchorHeight + 'px' ;
this . dom . menu . style . bottom = '' ;
}
else {
// display the menu above the anchor
this . dom . menu . style . left = '0px' ;
this . dom . menu . style . top = '' ;
this . dom . menu . style . bottom = '0px' ;
}
// attach the menu to the parent of the anchor
var parent = anchor . parentNode ;
parent . insertBefore ( this . dom . root , parent . firstChild ) ;
// create and attach event listeners
var me = this ;
var list = this . dom . list ;
this . eventListeners . mousedown = util . addEventListener ( window , 'mousedown' , function ( event ) {
// hide menu on click outside of the menu
var target = event . target ;
if ( ( target != list ) && ! me . _isChildOf ( target , list ) ) {
me . hide ( ) ;
event . stopPropagation ( ) ;
event . preventDefault ( ) ;
}
} ) ;
this . eventListeners . keydown = util . addEventListener ( window , 'keydown' , function ( event ) {
me . _onKeyDown ( event ) ;
} ) ;
// move focus to the first button in the context menu
this . selection = util . getSelection ( ) ;
this . anchor = anchor ;
setTimeout ( function ( ) {
me . dom . focusButton . focus ( ) ;
} , 0 ) ;
if ( ContextMenu . visibleMenu ) {
ContextMenu . visibleMenu . hide ( ) ;
}
ContextMenu . visibleMenu = this ;
} ;
/ * *
* Hide the context menu if visible
* /
ContextMenu . prototype . hide = function ( ) {
// remove the menu from the DOM
if ( this . dom . root . parentNode ) {
this . dom . root . parentNode . removeChild ( this . dom . root ) ;
if ( this . onClose ) {
this . onClose ( ) ;
}
}
// remove all event listeners
// all event listeners are supposed to be attached to document.
for ( var name in this . eventListeners ) {
if ( this . eventListeners . hasOwnProperty ( name ) ) {
var fn = this . eventListeners [ name ] ;
if ( fn ) {
util . removeEventListener ( window , name , fn ) ;
}
delete this . eventListeners [ name ] ;
}
}
if ( ContextMenu . visibleMenu == this ) {
ContextMenu . visibleMenu = undefined ;
}
} ;
/ * *
* Expand a submenu
* Any currently expanded submenu will be hided .
* @ param { Object } domItem
* @ private
* /
ContextMenu . prototype . _onExpandItem = function ( domItem ) {
var me = this ;
var alreadyVisible = ( domItem == this . expandedItem ) ;
// hide the currently visible submenu
var expandedItem = this . expandedItem ;
if ( expandedItem ) {
//var ul = expandedItem.ul;
expandedItem . ul . style . height = '0' ;
expandedItem . ul . style . padding = '' ;
setTimeout ( function ( ) {
if ( me . expandedItem != expandedItem ) {
expandedItem . ul . style . display = '' ;
util . removeClassName ( expandedItem . ul . parentNode , 'jsoneditor-selected' ) ;
}
} , 300 ) ; // timeout duration must match the css transition duration
this . expandedItem = undefined ;
}
if ( ! alreadyVisible ) {
var ul = domItem . ul ;
ul . style . display = 'block' ;
var height = ul . clientHeight ; // force a reflow in Firefox
setTimeout ( function ( ) {
if ( me . expandedItem == domItem ) {
ul . style . height = ( ul . childNodes . length * 24 ) + 'px' ;
ul . style . padding = '5px 10px' ;
}
} , 0 ) ;
util . addClassName ( ul . parentNode , 'jsoneditor-selected' ) ;
this . expandedItem = domItem ;
}
} ;
/ * *
* Handle onkeydown event
* @ param { Event } event
* @ private
* /
ContextMenu . prototype . _onKeyDown = function ( event ) {
var target = event . target ;
var keynum = event . which ;
var handled = false ;
var buttons , targetIndex , prevButton , nextButton ;
if ( keynum == 27 ) { // ESC
// hide the menu on ESC key
// restore previous selection and focus
if ( this . selection ) {
util . setSelection ( this . selection ) ;
}
if ( this . anchor ) {
this . anchor . focus ( ) ;
}
this . hide ( ) ;
handled = true ;
}
else if ( keynum == 9 ) { // Tab
if ( ! event . shiftKey ) { // Tab
buttons = this . _getVisibleButtons ( ) ;
targetIndex = buttons . indexOf ( target ) ;
if ( targetIndex == buttons . length - 1 ) {
// move to first button
buttons [ 0 ] . focus ( ) ;
handled = true ;
}
}
else { // Shift+Tab
buttons = this . _getVisibleButtons ( ) ;
targetIndex = buttons . indexOf ( target ) ;
if ( targetIndex == 0 ) {
// move to last button
buttons [ buttons . length - 1 ] . focus ( ) ;
handled = true ;
}
}
}
else if ( keynum == 37 ) { // Arrow Left
if ( target . className == 'jsoneditor-expand' ) {
buttons = this . _getVisibleButtons ( ) ;
targetIndex = buttons . indexOf ( target ) ;
prevButton = buttons [ targetIndex - 1 ] ;
if ( prevButton ) {
prevButton . focus ( ) ;
}
}
handled = true ;
}
else if ( keynum == 38 ) { // Arrow Up
buttons = this . _getVisibleButtons ( ) ;
targetIndex = buttons . indexOf ( target ) ;
prevButton = buttons [ targetIndex - 1 ] ;
if ( prevButton && prevButton . className == 'jsoneditor-expand' ) {
// skip expand button
prevButton = buttons [ targetIndex - 2 ] ;
}
if ( ! prevButton ) {
// move to last button
prevButton = buttons [ buttons . length - 1 ] ;
}
if ( prevButton ) {
prevButton . focus ( ) ;
}
handled = true ;
}
else if ( keynum == 39 ) { // Arrow Right
buttons = this . _getVisibleButtons ( ) ;
targetIndex = buttons . indexOf ( target ) ;
nextButton = buttons [ targetIndex + 1 ] ;
if ( nextButton && nextButton . className == 'jsoneditor-expand' ) {
nextButton . focus ( ) ;
}
handled = true ;
}
else if ( keynum == 40 ) { // Arrow Down
buttons = this . _getVisibleButtons ( ) ;
targetIndex = buttons . indexOf ( target ) ;
nextButton = buttons [ targetIndex + 1 ] ;
if ( nextButton && nextButton . className == 'jsoneditor-expand' ) {
// skip expand button
nextButton = buttons [ targetIndex + 2 ] ;
}
if ( ! nextButton ) {
// move to first button
nextButton = buttons [ 0 ] ;
}
if ( nextButton ) {
nextButton . focus ( ) ;
handled = true ;
}
handled = true ;
}
// TODO: arrow left and right
if ( handled ) {
event . stopPropagation ( ) ;
event . preventDefault ( ) ;
}
} ;
/ * *
* Test if an element is a child of a parent element .
* @ param { Element } child
* @ param { Element } parent
* @ return { boolean } isChild
* /
ContextMenu . prototype . _isChildOf = function ( child , parent ) {
var e = child . parentNode ;
while ( e ) {
if ( e == parent ) {
return true ;
}
e = e . parentNode ;
}
return false ;
} ;
module . exports = ContextMenu ;
/***/ } ,
2016-02-13 18:47:51 +08:00
/* 8 */
2016-01-15 04:26:39 +08:00
/***/ function ( module , exports , _ _webpack _require _ _ ) {
2016-02-13 18:47:51 +08:00
var ContextMenu = _ _webpack _require _ _ ( 7 ) ;
2016-01-15 04:26:39 +08:00
var appendNodeFactory = _ _webpack _require _ _ ( 12 ) ;
var util = _ _webpack _require _ _ ( 3 ) ;
/ * *
* @ constructor Node
* Create a new Node
* @ param { . / treemode } editor
* @ param { Object } [ params ] Can contain parameters :
* { string } field
* { boolean } fieldEditable
* { * } value
* { String } type Can have values 'auto' , 'array' ,
* 'object' , or 'string' .
* /
function Node ( editor , params ) {
/** @type {./treemode} */
this . editor = editor ;
this . dom = { } ;
this . expanded = false ;
if ( params && ( params instanceof Object ) ) {
this . setField ( params . field , params . fieldEditable ) ;
this . setValue ( params . value , params . type ) ;
}
else {
this . setField ( '' ) ;
this . setValue ( null ) ;
}
this . _debouncedOnChangeValue = util . debounce ( this . _onChangeValue . bind ( this ) , Node . prototype . DEBOUNCE _INTERVAL ) ;
this . _debouncedOnChangeField = util . debounce ( this . _onChangeField . bind ( this ) , Node . prototype . DEBOUNCE _INTERVAL ) ;
}
// debounce interval for keyboard input in milliseconds
Node . prototype . DEBOUNCE _INTERVAL = 150 ;
/ * *
* Determine whether the field and / or value of this node are editable
* @ private
* /
Node . prototype . _updateEditability = function ( ) {
this . editable = {
field : true ,
value : true
} ;
if ( this . editor ) {
this . editable . field = this . editor . options . mode === 'tree' ;
this . editable . value = this . editor . options . mode !== 'view' ;
if ( ( this . editor . options . mode === 'tree' || this . editor . options . mode === 'form' ) &&
( typeof this . editor . options . onEditable === 'function' ) ) {
var editable = this . editor . options . onEditable ( {
field : this . field ,
value : this . value ,
path : this . getFieldsPath ( )
} ) ;
if ( typeof editable === 'boolean' ) {
this . editable . field = editable ;
this . editable . value = editable ;
}
else {
if ( typeof editable . field === 'boolean' ) this . editable . field = editable . field ;
if ( typeof editable . value === 'boolean' ) this . editable . value = editable . value ;
}
}
}
} ;
/ * *
* Get the path of this node
* @ return { String [ ] } Array containing the path to this node
* /
Node . prototype . getFieldsPath = function ( ) {
var node = this ;
var path = [ ] ;
while ( node ) {
var field = node . field != undefined ? node . field : node . index ;
if ( field !== undefined ) {
path . unshift ( field ) ;
}
node = node . parent ;
}
return path ;
} ;
/ * *
* Find a Node from a JSON path like '.items[3].name'
* @ param { string } jsonPath
* @ return { Node | null } Returns the Node when found , returns null if not found
* /
Node . prototype . findNode = function ( jsonPath ) {
var path = util . parsePath ( jsonPath ) ;
var node = this ;
while ( node && path . length > 0 ) {
var prop = path . shift ( ) ;
if ( typeof prop === 'number' ) {
if ( node . type !== 'array' ) {
throw new Error ( 'Cannot get child node at index ' + prop + ': node is no array' ) ;
}
node = node . childs [ prop ] ;
}
else { // string
if ( node . type !== 'object' ) {
throw new Error ( 'Cannot get child node ' + prop + ': node is no object' ) ;
}
node = node . childs . filter ( function ( child ) {
return child . field === prop ;
} ) [ 0 ] ;
}
}
return node ;
} ;
/ * *
* Find all parents of this node . The parents are ordered from root node towards
* the original node .
* @ return { Array . < Node > }
* /
Node . prototype . findParents = function ( ) {
var parents = [ ] ;
var parent = this . parent ;
while ( parent ) {
parents . unshift ( parent ) ;
parent = parent . parent ;
}
return parents ;
} ;
/ * *
*
* @ param { { dataPath : string , keyword : string , message : string , params : Object , schemaPath : string } | null } error
* @ param { Node } [ child ] When this is the error of a parent node , pointing
* to an invalid child node , the child node itself
* can be provided . If provided , clicking the error
* icon will set focus to the invalid child node .
* /
Node . prototype . setError = function ( error , child ) {
// ensure the dom exists
this . getDom ( ) ;
this . error = error ;
var tdError = this . dom . tdError ;
if ( error ) {
if ( ! tdError ) {
tdError = document . createElement ( 'td' ) ;
this . dom . tdError = tdError ;
this . dom . tdValue . parentNode . appendChild ( tdError ) ;
}
var popover = document . createElement ( 'div' ) ;
popover . className = 'jsoneditor-popover jsoneditor-right' ;
popover . appendChild ( document . createTextNode ( error . message ) ) ;
var button = document . createElement ( 'button' ) ;
button . className = 'jsoneditor-schema-error' ;
button . appendChild ( popover ) ;
// update the direction of the popover
button . onmouseover = button . onfocus = function updateDirection ( ) {
var directions = [ 'right' , 'above' , 'below' , 'left' ] ;
for ( var i = 0 ; i < directions . length ; i ++ ) {
var direction = directions [ i ] ;
popover . className = 'jsoneditor-popover jsoneditor-' + direction ;
var contentRect = this . editor . content . getBoundingClientRect ( ) ;
var popoverRect = popover . getBoundingClientRect ( ) ;
var margin = 20 ; // account for a scroll bar
var fit = util . insideRect ( contentRect , popoverRect , margin ) ;
if ( fit ) {
break ;
}
}
} . bind ( this ) ;
// when clicking the error icon, expand all nodes towards the invalid
// child node, and set focus to the child node
if ( child ) {
button . onclick = function showInvalidNode ( ) {
child . findParents ( ) . forEach ( function ( parent ) {
parent . expand ( false ) ;
} ) ;
child . scrollTo ( function ( ) {
child . focus ( ) ;
} ) ;
} ;
}
// apply the error message to the node
while ( tdError . firstChild ) {
tdError . removeChild ( tdError . firstChild ) ;
}
tdError . appendChild ( button ) ;
}
else {
if ( tdError ) {
this . dom . tdError . parentNode . removeChild ( this . dom . tdError ) ;
delete this . dom . tdError ;
}
}
} ;
/ * *
* Get the index of this node : the index in the list of childs where this
* node is part of
* @ return { number } Returns the index , or - 1 if this is the root node
* /
Node . prototype . getIndex = function ( ) {
return this . parent ? this . parent . childs . indexOf ( this ) : - 1 ;
} ;
/ * *
* Set parent node
* @ param { Node } parent
* /
Node . prototype . setParent = function ( parent ) {
this . parent = parent ;
} ;
/ * *
* Set field
* @ param { String } field
* @ param { boolean } [ fieldEditable ]
* /
Node . prototype . setField = function ( field , fieldEditable ) {
this . field = field ;
this . previousField = field ;
this . fieldEditable = ( fieldEditable === true ) ;
} ;
/ * *
* Get field
* @ return { String }
* /
Node . prototype . getField = function ( ) {
if ( this . field === undefined ) {
this . _getDomField ( ) ;
}
return this . field ;
} ;
/ * *
* Set value . Value is a JSON structure or an element String , Boolean , etc .
* @ param { * } value
* @ param { String } [ type ] Specify the type of the value . Can be 'auto' ,
* 'array' , 'object' , or 'string'
* /
Node . prototype . setValue = function ( value , type ) {
var childValue , child ;
// first clear all current childs (if any)
var childs = this . childs ;
if ( childs ) {
while ( childs . length ) {
this . removeChild ( childs [ 0 ] ) ;
}
}
// TODO: remove the DOM of this Node
this . type = this . _getType ( value ) ;
// check if type corresponds with the provided type
if ( type && type != this . type ) {
if ( type == 'string' && this . type == 'auto' ) {
this . type = type ;
}
else {
throw new Error ( 'Type mismatch: ' +
'cannot cast value of type "' + this . type +
' to the specified type "' + type + '"' ) ;
}
}
if ( this . type == 'array' ) {
// array
this . childs = [ ] ;
for ( var i = 0 , iMax = value . length ; i < iMax ; i ++ ) {
childValue = value [ i ] ;
if ( childValue !== undefined && ! ( childValue instanceof Function ) ) {
// ignore undefined and functions
child = new Node ( this . editor , {
value : childValue
} ) ;
this . appendChild ( child ) ;
}
}
this . value = '' ;
}
else if ( this . type == 'object' ) {
// object
this . childs = [ ] ;
for ( var childField in value ) {
if ( value . hasOwnProperty ( childField ) ) {
childValue = value [ childField ] ;
if ( childValue !== undefined && ! ( childValue instanceof Function ) ) {
// ignore undefined and functions
child = new Node ( this . editor , {
field : childField ,
value : childValue
} ) ;
this . appendChild ( child ) ;
}
}
}
this . value = '' ;
}
else {
// value
this . childs = undefined ;
this . value = value ;
/ * T O D O
if ( typeof ( value ) == 'string' ) {
var escValue = JSON . stringify ( value ) ;
this . value = escValue . substring ( 1 , escValue . length - 1 ) ;
console . log ( 'check' , value , this . value ) ;
}
else {
this . value = value ;
}
* /
}
this . previousValue = this . value ;
} ;
/ * *
* Get value . Value is a JSON structure
* @ return { * } value
* /
Node . prototype . getValue = function ( ) {
//var childs, i, iMax;
if ( this . type == 'array' ) {
var arr = [ ] ;
this . childs . forEach ( function ( child ) {
arr . push ( child . getValue ( ) ) ;
} ) ;
return arr ;
}
else if ( this . type == 'object' ) {
var obj = { } ;
this . childs . forEach ( function ( child ) {
obj [ child . getField ( ) ] = child . getValue ( ) ;
} ) ;
return obj ;
}
else {
if ( this . value === undefined ) {
this . _getDomValue ( ) ;
}
return this . value ;
}
} ;
/ * *
* Get the nesting level of this node
* @ return { Number } level
* /
Node . prototype . getLevel = function ( ) {
return ( this . parent ? this . parent . getLevel ( ) + 1 : 0 ) ;
} ;
/ * *
* Get path of the root node till the current node
* @ return { Node [ ] } Returns an array with nodes
* /
Node . prototype . getPath = function ( ) {
var path = this . parent ? this . parent . getPath ( ) : [ ] ;
path . push ( this ) ;
return path ;
} ;
/ * *
* Create a clone of a node
* The complete state of a clone is copied , including whether it is expanded or
* not . The DOM elements are not cloned .
* @ return { Node } clone
* /
Node . prototype . clone = function ( ) {
var clone = new Node ( this . editor ) ;
clone . type = this . type ;
clone . field = this . field ;
clone . fieldInnerText = this . fieldInnerText ;
clone . fieldEditable = this . fieldEditable ;
clone . value = this . value ;
clone . valueInnerText = this . valueInnerText ;
clone . expanded = this . expanded ;
if ( this . childs ) {
// an object or array
var cloneChilds = [ ] ;
this . childs . forEach ( function ( child ) {
var childClone = child . clone ( ) ;
childClone . setParent ( clone ) ;
cloneChilds . push ( childClone ) ;
} ) ;
clone . childs = cloneChilds ;
}
else {
// a value
clone . childs = undefined ;
}
return clone ;
} ;
/ * *
* Expand this node and optionally its childs .
* @ param { boolean } [ recurse ] Optional recursion , true by default . When
* true , all childs will be expanded recursively
* /
Node . prototype . expand = function ( recurse ) {
if ( ! this . childs ) {
return ;
}
// set this node expanded
this . expanded = true ;
if ( this . dom . expand ) {
this . dom . expand . className = 'jsoneditor-expanded' ;
}
this . showChilds ( ) ;
if ( recurse !== false ) {
this . childs . forEach ( function ( child ) {
child . expand ( recurse ) ;
} ) ;
}
} ;
/ * *
* Collapse this node and optionally its childs .
* @ param { boolean } [ recurse ] Optional recursion , true by default . When
* true , all childs will be collapsed recursively
* /
Node . prototype . collapse = function ( recurse ) {
if ( ! this . childs ) {
return ;
}
this . hideChilds ( ) ;
// collapse childs in case of recurse
if ( recurse !== false ) {
this . childs . forEach ( function ( child ) {
child . collapse ( recurse ) ;
} ) ;
}
// make this node collapsed
if ( this . dom . expand ) {
this . dom . expand . className = 'jsoneditor-collapsed' ;
}
this . expanded = false ;
} ;
/ * *
* Recursively show all childs when they are expanded
* /
Node . prototype . showChilds = function ( ) {
var childs = this . childs ;
if ( ! childs ) {
return ;
}
if ( ! this . expanded ) {
return ;
}
var tr = this . dom . tr ;
var table = tr ? tr . parentNode : undefined ;
if ( table ) {
// show row with append button
var append = this . getAppend ( ) ;
var nextTr = tr . nextSibling ;
if ( nextTr ) {
table . insertBefore ( append , nextTr ) ;
}
else {
table . appendChild ( append ) ;
}
// show childs
this . childs . forEach ( function ( child ) {
table . insertBefore ( child . getDom ( ) , append ) ;
child . showChilds ( ) ;
} ) ;
}
} ;
/ * *
* Hide the node with all its childs
* /
Node . prototype . hide = function ( ) {
var tr = this . dom . tr ;
var table = tr ? tr . parentNode : undefined ;
if ( table ) {
table . removeChild ( tr ) ;
}
this . hideChilds ( ) ;
} ;
/ * *
* Recursively hide all childs
* /
Node . prototype . hideChilds = function ( ) {
var childs = this . childs ;
if ( ! childs ) {
return ;
}
if ( ! this . expanded ) {
return ;
}
// hide append row
var append = this . getAppend ( ) ;
if ( append . parentNode ) {
append . parentNode . removeChild ( append ) ;
}
// hide childs
this . childs . forEach ( function ( child ) {
child . hide ( ) ;
} ) ;
} ;
/ * *
* Add a new child to the node .
* Only applicable when Node value is of type array or object
* @ param { Node } node
* /
Node . prototype . appendChild = function ( node ) {
if ( this . _hasChilds ( ) ) {
// adjust the link to the parent
node . setParent ( this ) ;
node . fieldEditable = ( this . type == 'object' ) ;
if ( this . type == 'array' ) {
node . index = this . childs . length ;
}
this . childs . push ( node ) ;
if ( this . expanded ) {
// insert into the DOM, before the appendRow
var newTr = node . getDom ( ) ;
var appendTr = this . getAppend ( ) ;
var table = appendTr ? appendTr . parentNode : undefined ;
if ( appendTr && table ) {
table . insertBefore ( newTr , appendTr ) ;
}
node . showChilds ( ) ;
}
this . updateDom ( { 'updateIndexes' : true } ) ;
node . updateDom ( { 'recurse' : true } ) ;
}
} ;
/ * *
* Move a node from its current parent to this node
* Only applicable when Node value is of type array or object
* @ param { Node } node
* @ param { Node } beforeNode
* /
Node . prototype . moveBefore = function ( node , beforeNode ) {
if ( this . _hasChilds ( ) ) {
// create a temporary row, to prevent the scroll position from jumping
// when removing the node
var tbody = ( this . dom . tr ) ? this . dom . tr . parentNode : undefined ;
if ( tbody ) {
var trTemp = document . createElement ( 'tr' ) ;
trTemp . style . height = tbody . clientHeight + 'px' ;
tbody . appendChild ( trTemp ) ;
}
if ( node . parent ) {
node . parent . removeChild ( node ) ;
}
if ( beforeNode instanceof AppendNode ) {
this . appendChild ( node ) ;
}
else {
this . insertBefore ( node , beforeNode ) ;
}
if ( tbody ) {
tbody . removeChild ( trTemp ) ;
}
}
} ;
/ * *
* Move a node from its current parent to this node
* Only applicable when Node value is of type array or object .
* If index is out of range , the node will be appended to the end
* @ param { Node } node
* @ param { Number } index
* /
Node . prototype . moveTo = function ( node , index ) {
if ( node . parent == this ) {
// same parent
var currentIndex = this . childs . indexOf ( node ) ;
if ( currentIndex < index ) {
// compensate the index for removal of the node itself
index ++ ;
}
}
var beforeNode = this . childs [ index ] || this . append ;
this . moveBefore ( node , beforeNode ) ;
} ;
/ * *
* Insert a new child before a given node
* Only applicable when Node value is of type array or object
* @ param { Node } node
* @ param { Node } beforeNode
* /
Node . prototype . insertBefore = function ( node , beforeNode ) {
if ( this . _hasChilds ( ) ) {
if ( beforeNode == this . append ) {
// append to the child nodes
// adjust the link to the parent
node . setParent ( this ) ;
node . fieldEditable = ( this . type == 'object' ) ;
this . childs . push ( node ) ;
}
else {
// insert before a child node
var index = this . childs . indexOf ( beforeNode ) ;
if ( index == - 1 ) {
throw new Error ( 'Node not found' ) ;
}
// adjust the link to the parent
node . setParent ( this ) ;
node . fieldEditable = ( this . type == 'object' ) ;
this . childs . splice ( index , 0 , node ) ;
}
if ( this . expanded ) {
// insert into the DOM
var newTr = node . getDom ( ) ;
var nextTr = beforeNode . getDom ( ) ;
var table = nextTr ? nextTr . parentNode : undefined ;
if ( nextTr && table ) {
table . insertBefore ( newTr , nextTr ) ;
}
node . showChilds ( ) ;
}
this . updateDom ( { 'updateIndexes' : true } ) ;
node . updateDom ( { 'recurse' : true } ) ;
}
} ;
/ * *
* Insert a new child before a given node
* Only applicable when Node value is of type array or object
* @ param { Node } node
* @ param { Node } afterNode
* /
Node . prototype . insertAfter = function ( node , afterNode ) {
if ( this . _hasChilds ( ) ) {
var index = this . childs . indexOf ( afterNode ) ;
var beforeNode = this . childs [ index + 1 ] ;
if ( beforeNode ) {
this . insertBefore ( node , beforeNode ) ;
}
else {
this . appendChild ( node ) ;
}
}
} ;
/ * *
* Search in this node
* The node will be expanded when the text is found one of its childs , else
* it will be collapsed . Searches are case insensitive .
* @ param { String } text
* @ return { Node [ ] } results Array with nodes containing the search text
* /
Node . prototype . search = function ( text ) {
var results = [ ] ;
var index ;
var search = text ? text . toLowerCase ( ) : undefined ;
// delete old search data
delete this . searchField ;
delete this . searchValue ;
// search in field
if ( this . field != undefined ) {
var field = String ( this . field ) . toLowerCase ( ) ;
index = field . indexOf ( search ) ;
if ( index != - 1 ) {
this . searchField = true ;
results . push ( {
'node' : this ,
'elem' : 'field'
} ) ;
}
// update dom
this . _updateDomField ( ) ;
}
// search in value
if ( this . _hasChilds ( ) ) {
// array, object
// search the nodes childs
if ( this . childs ) {
var childResults = [ ] ;
this . childs . forEach ( function ( child ) {
childResults = childResults . concat ( child . search ( text ) ) ;
} ) ;
results = results . concat ( childResults ) ;
}
// update dom
if ( search != undefined ) {
var recurse = false ;
if ( childResults . length == 0 ) {
this . collapse ( recurse ) ;
}
else {
this . expand ( recurse ) ;
}
}
}
else {
// string, auto
if ( this . value != undefined ) {
var value = String ( this . value ) . toLowerCase ( ) ;
index = value . indexOf ( search ) ;
if ( index != - 1 ) {
this . searchValue = true ;
results . push ( {
'node' : this ,
'elem' : 'value'
} ) ;
}
}
// update dom
this . _updateDomValue ( ) ;
}
return results ;
} ;
/ * *
* Move the scroll position such that this node is in the visible area .
* The node will not get the focus
* @ param { function ( boolean ) } [ callback ]
* /
Node . prototype . scrollTo = function ( callback ) {
if ( ! this . dom . tr || ! this . dom . tr . parentNode ) {
// if the node is not visible, expand its parents
var parent = this . parent ;
var recurse = false ;
while ( parent ) {
parent . expand ( recurse ) ;
parent = parent . parent ;
}
}
if ( this . dom . tr && this . dom . tr . parentNode ) {
this . editor . scrollTo ( this . dom . tr . offsetTop , callback ) ;
}
} ;
// stores the element name currently having the focus
Node . focusElement = undefined ;
/ * *
* Set focus to this node
* @ param { String } [ elementName ] The field name of the element to get the
* focus available values : 'drag' , 'menu' ,
* 'expand' , 'field' , 'value' ( default )
* /
Node . prototype . focus = function ( elementName ) {
Node . focusElement = elementName ;
if ( this . dom . tr && this . dom . tr . parentNode ) {
var dom = this . dom ;
switch ( elementName ) {
case 'drag' :
if ( dom . drag ) {
dom . drag . focus ( ) ;
}
else {
dom . menu . focus ( ) ;
}
break ;
case 'menu' :
dom . menu . focus ( ) ;
break ;
case 'expand' :
if ( this . _hasChilds ( ) ) {
dom . expand . focus ( ) ;
}
else if ( dom . field && this . fieldEditable ) {
dom . field . focus ( ) ;
util . selectContentEditable ( dom . field ) ;
}
else if ( dom . value && ! this . _hasChilds ( ) ) {
dom . value . focus ( ) ;
util . selectContentEditable ( dom . value ) ;
}
else {
dom . menu . focus ( ) ;
}
break ;
case 'field' :
if ( dom . field && this . fieldEditable ) {
dom . field . focus ( ) ;
util . selectContentEditable ( dom . field ) ;
}
else if ( dom . value && ! this . _hasChilds ( ) ) {
dom . value . focus ( ) ;
util . selectContentEditable ( dom . value ) ;
}
else if ( this . _hasChilds ( ) ) {
dom . expand . focus ( ) ;
}
else {
dom . menu . focus ( ) ;
}
break ;
case 'value' :
default :
if ( dom . value && ! this . _hasChilds ( ) ) {
dom . value . focus ( ) ;
util . selectContentEditable ( dom . value ) ;
}
else if ( dom . field && this . fieldEditable ) {
dom . field . focus ( ) ;
util . selectContentEditable ( dom . field ) ;
}
else if ( this . _hasChilds ( ) ) {
dom . expand . focus ( ) ;
}
else {
dom . menu . focus ( ) ;
}
break ;
}
}
} ;
/ * *
* Select all text in an editable div after a delay of 0 ms
* @ param { Element } editableDiv
* /
Node . select = function ( editableDiv ) {
setTimeout ( function ( ) {
util . selectContentEditable ( editableDiv ) ;
} , 0 ) ;
} ;
/ * *
* Update the values from the DOM field and value of this node
* /
Node . prototype . blur = function ( ) {
// retrieve the actual field and value from the DOM.
this . _getDomValue ( false ) ;
this . _getDomField ( false ) ;
} ;
/ * *
* Check if given node is a child . The method will check recursively to find
* this node .
* @ param { Node } node
* @ return { boolean } containsNode
* /
Node . prototype . containsNode = function ( node ) {
if ( this == node ) {
return true ;
}
var childs = this . childs ;
if ( childs ) {
// TODO: use the js5 Array.some() here?
for ( var i = 0 , iMax = childs . length ; i < iMax ; i ++ ) {
if ( childs [ i ] . containsNode ( node ) ) {
return true ;
}
}
}
return false ;
} ;
/ * *
* Move given node into this node
* @ param { Node } node the childNode to be moved
* @ param { Node } beforeNode node will be inserted before given
* node . If no beforeNode is given ,
* the node is appended at the end
* @ private
* /
Node . prototype . _move = function ( node , beforeNode ) {
if ( node == beforeNode ) {
// nothing to do...
return ;
}
// check if this node is not a child of the node to be moved here
if ( node . containsNode ( this ) ) {
throw new Error ( 'Cannot move a field into a child of itself' ) ;
}
// remove the original node
if ( node . parent ) {
node . parent . removeChild ( node ) ;
}
// create a clone of the node
var clone = node . clone ( ) ;
node . clearDom ( ) ;
// insert or append the node
if ( beforeNode ) {
this . insertBefore ( clone , beforeNode ) ;
}
else {
this . appendChild ( clone ) ;
}
/ * T O D O : a d j u s t t h e f i e l d n a m e ( t o p r e v e n t e q u a l f i e l d n a m e s )
if ( this . type == 'object' ) {
}
* /
} ;
/ * *
* Remove a child from the node .
* Only applicable when Node value is of type array or object
* @ param { Node } node The child node to be removed ;
* @ return { Node | undefined } node The removed node on success ,
* else undefined
* /
Node . prototype . removeChild = function ( node ) {
if ( this . childs ) {
var index = this . childs . indexOf ( node ) ;
if ( index != - 1 ) {
node . hide ( ) ;
// delete old search results
delete node . searchField ;
delete node . searchValue ;
var removedNode = this . childs . splice ( index , 1 ) [ 0 ] ;
removedNode . parent = null ;
this . updateDom ( { 'updateIndexes' : true } ) ;
return removedNode ;
}
}
return undefined ;
} ;
/ * *
* Remove a child node node from this node
* This method is equal to Node . removeChild , except that _remove fire an
* onChange event .
* @ param { Node } node
* @ private
* /
Node . prototype . _remove = function ( node ) {
this . removeChild ( node ) ;
} ;
/ * *
* Change the type of the value of this Node
* @ param { String } newType
* /
Node . prototype . changeType = function ( newType ) {
var oldType = this . type ;
if ( oldType == newType ) {
// type is not changed
return ;
}
if ( ( newType == 'string' || newType == 'auto' ) &&
( oldType == 'string' || oldType == 'auto' ) ) {
// this is an easy change
this . type = newType ;
}
else {
// change from array to object, or from string/auto to object/array
var table = this . dom . tr ? this . dom . tr . parentNode : undefined ;
var lastTr ;
if ( this . expanded ) {
lastTr = this . getAppend ( ) ;
}
else {
lastTr = this . getDom ( ) ;
}
var nextTr = ( lastTr && lastTr . parentNode ) ? lastTr . nextSibling : undefined ;
// hide current field and all its childs
this . hide ( ) ;
this . clearDom ( ) ;
// adjust the field and the value
this . type = newType ;
// adjust childs
if ( newType == 'object' ) {
if ( ! this . childs ) {
this . childs = [ ] ;
}
this . childs . forEach ( function ( child , index ) {
child . clearDom ( ) ;
delete child . index ;
child . fieldEditable = true ;
if ( child . field == undefined ) {
child . field = '' ;
}
} ) ;
if ( oldType == 'string' || oldType == 'auto' ) {
this . expanded = true ;
}
}
else if ( newType == 'array' ) {
if ( ! this . childs ) {
this . childs = [ ] ;
}
this . childs . forEach ( function ( child , index ) {
child . clearDom ( ) ;
child . fieldEditable = false ;
child . index = index ;
} ) ;
if ( oldType == 'string' || oldType == 'auto' ) {
this . expanded = true ;
}
}
else {
this . expanded = false ;
}
// create new DOM
if ( table ) {
if ( nextTr ) {
table . insertBefore ( this . getDom ( ) , nextTr ) ;
}
else {
table . appendChild ( this . getDom ( ) ) ;
}
}
this . showChilds ( ) ;
}
if ( newType == 'auto' || newType == 'string' ) {
// cast value to the correct type
if ( newType == 'string' ) {
this . value = String ( this . value ) ;
}
else {
this . value = this . _stringCast ( String ( this . value ) ) ;
}
this . focus ( ) ;
}
this . updateDom ( { 'updateIndexes' : true } ) ;
} ;
/ * *
* Retrieve value from DOM
* @ param { boolean } [ silent ] If true ( default ) , no errors will be thrown in
* case of invalid data
* @ private
* /
Node . prototype . _getDomValue = function ( silent ) {
if ( this . dom . value && this . type != 'array' && this . type != 'object' ) {
this . valueInnerText = util . getInnerText ( this . dom . value ) ;
}
if ( this . valueInnerText != undefined ) {
try {
// retrieve the value
var value ;
if ( this . type == 'string' ) {
value = this . _unescapeHTML ( this . valueInnerText ) ;
}
else {
var str = this . _unescapeHTML ( this . valueInnerText ) ;
value = this . _stringCast ( str ) ;
}
if ( value !== this . value ) {
this . value = value ;
this . _debouncedOnChangeValue ( ) ;
}
}
catch ( err ) {
this . value = undefined ;
// TODO: sent an action with the new, invalid value?
if ( silent !== true ) {
throw err ;
}
}
}
} ;
/ * *
* Handle a changed value
* @ private
* /
Node . prototype . _onChangeValue = function ( ) {
// get current selection, then override the range such that we can select
// the added/removed text on undo/redo
var oldSelection = this . editor . getSelection ( ) ;
if ( oldSelection . range ) {
var undoDiff = util . textDiff ( String ( this . value ) , String ( this . previousValue ) ) ;
oldSelection . range . startOffset = undoDiff . start ;
oldSelection . range . endOffset = undoDiff . end ;
}
var newSelection = this . editor . getSelection ( ) ;
if ( newSelection . range ) {
var redoDiff = util . textDiff ( String ( this . previousValue ) , String ( this . value ) ) ;
newSelection . range . startOffset = redoDiff . start ;
newSelection . range . endOffset = redoDiff . end ;
}
this . editor . _onAction ( 'editValue' , {
node : this ,
oldValue : this . previousValue ,
newValue : this . value ,
oldSelection : oldSelection ,
newSelection : newSelection
} ) ;
this . previousValue = this . value ;
} ;
/ * *
* Handle a changed field
* @ private
* /
Node . prototype . _onChangeField = function ( ) {
// get current selection, then override the range such that we can select
// the added/removed text on undo/redo
var oldSelection = this . editor . getSelection ( ) ;
if ( oldSelection . range ) {
var undoDiff = util . textDiff ( this . field , this . previousField ) ;
oldSelection . range . startOffset = undoDiff . start ;
oldSelection . range . endOffset = undoDiff . end ;
}
var newSelection = this . editor . getSelection ( ) ;
if ( newSelection . range ) {
var redoDiff = util . textDiff ( this . previousField , this . field ) ;
newSelection . range . startOffset = redoDiff . start ;
newSelection . range . endOffset = redoDiff . end ;
}
this . editor . _onAction ( 'editField' , {
node : this ,
oldValue : this . previousField ,
newValue : this . field ,
oldSelection : oldSelection ,
newSelection : newSelection
} ) ;
this . previousField = this . field ;
} ;
/ * *
* Update dom value :
* - the text color of the value , depending on the type of the value
* - the height of the field , depending on the width
* - background color in case it is empty
* @ private
* /
Node . prototype . _updateDomValue = function ( ) {
var domValue = this . dom . value ;
if ( domValue ) {
var classNames = [ 'jsoneditor-value' ] ;
// set text color depending on value type
var value = this . value ;
var type = ( this . type == 'auto' ) ? util . type ( value ) : this . type ;
var isUrl = type == 'string' && util . isUrl ( value ) ;
classNames . push ( 'jsoneditor-' + type ) ;
if ( isUrl ) {
classNames . push ( 'jsoneditor-url' ) ;
}
// visual styling when empty
var isEmpty = ( String ( this . value ) == '' && this . type != 'array' && this . type != 'object' ) ;
if ( isEmpty ) {
classNames . push ( 'jsoneditor-empty' ) ;
}
// highlight when there is a search result
if ( this . searchValueActive ) {
classNames . push ( 'jsoneditor-highlight-active' ) ;
}
if ( this . searchValue ) {
classNames . push ( 'jsoneditor-highlight' ) ;
}
domValue . className = classNames . join ( ' ' ) ;
// update title
if ( type == 'array' || type == 'object' ) {
var count = this . childs ? this . childs . length : 0 ;
domValue . title = this . type + ' containing ' + count + ' items' ;
}
else if ( isUrl && this . editable . value ) {
domValue . title = 'Ctrl+Click or Ctrl+Enter to open url in new window' ;
}
else {
domValue . title = '' ;
}
// show checkbox when the value is a boolean
2016-02-16 03:36:39 +08:00
if ( type === 'boolean' && this . editable . value ) {
2016-01-15 04:26:39 +08:00
if ( ! this . dom . checkbox ) {
this . dom . checkbox = document . createElement ( 'input' ) ;
this . dom . checkbox . type = 'checkbox' ;
this . dom . tdCheckbox = document . createElement ( 'td' ) ;
this . dom . tdCheckbox . className = 'jsoneditor-tree' ;
this . dom . tdCheckbox . appendChild ( this . dom . checkbox ) ;
this . dom . tdValue . parentNode . insertBefore ( this . dom . tdCheckbox , this . dom . tdValue ) ;
}
this . dom . checkbox . checked = this . value ;
}
else {
// cleanup checkbox when displayed
if ( this . dom . tdCheckbox ) {
this . dom . tdCheckbox . parentNode . removeChild ( this . dom . tdCheckbox ) ;
delete this . dom . tdCheckbox ;
delete this . dom . checkbox ;
}
}
// strip formatting from the contents of the editable div
util . stripFormatting ( domValue ) ;
}
} ;
/ * *
* Update dom field :
* - the text color of the field , depending on the text
* - the height of the field , depending on the width
* - background color in case it is empty
* @ private
* /
Node . prototype . _updateDomField = function ( ) {
var domField = this . dom . field ;
if ( domField ) {
// make backgound color lightgray when empty
var isEmpty = ( String ( this . field ) == '' && this . parent . type != 'array' ) ;
if ( isEmpty ) {
util . addClassName ( domField , 'jsoneditor-empty' ) ;
}
else {
util . removeClassName ( domField , 'jsoneditor-empty' ) ;
}
// highlight when there is a search result
if ( this . searchFieldActive ) {
util . addClassName ( domField , 'jsoneditor-highlight-active' ) ;
}
else {
util . removeClassName ( domField , 'jsoneditor-highlight-active' ) ;
}
if ( this . searchField ) {
util . addClassName ( domField , 'jsoneditor-highlight' ) ;
}
else {
util . removeClassName ( domField , 'jsoneditor-highlight' ) ;
}
// strip formatting from the contents of the editable div
util . stripFormatting ( domField ) ;
}
} ;
/ * *
* Retrieve field from DOM
* @ param { boolean } [ silent ] If true ( default ) , no errors will be thrown in
* case of invalid data
* @ private
* /
Node . prototype . _getDomField = function ( silent ) {
if ( this . dom . field && this . fieldEditable ) {
this . fieldInnerText = util . getInnerText ( this . dom . field ) ;
}
if ( this . fieldInnerText != undefined ) {
try {
var field = this . _unescapeHTML ( this . fieldInnerText ) ;
if ( field !== this . field ) {
this . field = field ;
this . _debouncedOnChangeField ( ) ;
}
}
catch ( err ) {
this . field = undefined ;
// TODO: sent an action here, with the new, invalid value?
if ( silent !== true ) {
throw err ;
}
}
}
} ;
/ * *
* Validate this node and all it ' s childs
* @ return { Array . < { node : Node , error : { message : string } } > } Returns a list with duplicates
* /
Node . prototype . validate = function ( ) {
var errors = [ ] ;
// find duplicate keys
if ( this . type === 'object' ) {
var keys = { } ;
var duplicateKeys = [ ] ;
for ( var i = 0 ; i < this . childs . length ; i ++ ) {
var child = this . childs [ i ] ;
if ( keys [ child . field ] ) {
duplicateKeys . push ( child . field ) ;
}
keys [ child . field ] = true ;
}
if ( duplicateKeys . length > 0 ) {
errors = this . childs
. filter ( function ( node ) {
return duplicateKeys . indexOf ( node . field ) !== - 1 ;
} )
. map ( function ( node ) {
return {
node : node ,
error : {
message : 'duplicate key "' + node . field + '"'
}
}
} ) ;
}
}
// recurse over the childs
if ( this . childs ) {
for ( var i = 0 ; i < this . childs . length ; i ++ ) {
var e = this . childs [ i ] . validate ( ) ;
if ( e . length > 0 ) {
errors = errors . concat ( e ) ;
}
}
}
return errors ;
} ;
/ * *
* Clear the dom of the node
* /
Node . prototype . clearDom = function ( ) {
// TODO: hide the node first?
//this.hide();
// TODO: recursively clear dom?
this . dom = { } ;
} ;
/ * *
* Get the HTML DOM TR element of the node .
* The dom will be generated when not yet created
* @ return { Element } tr HTML DOM TR Element
* /
Node . prototype . getDom = function ( ) {
var dom = this . dom ;
if ( dom . tr ) {
return dom . tr ;
}
this . _updateEditability ( ) ;
// create row
dom . tr = document . createElement ( 'tr' ) ;
dom . tr . node = this ;
if ( this . editor . options . mode === 'tree' ) { // note: we take here the global setting
var tdDrag = document . createElement ( 'td' ) ;
if ( this . editable . field ) {
// create draggable area
if ( this . parent ) {
var domDrag = document . createElement ( 'button' ) ;
dom . drag = domDrag ;
domDrag . className = 'jsoneditor-dragarea' ;
domDrag . title = 'Drag to move this field (Alt+Shift+Arrows)' ;
tdDrag . appendChild ( domDrag ) ;
}
}
dom . tr . appendChild ( tdDrag ) ;
// create context menu
var tdMenu = document . createElement ( 'td' ) ;
var menu = document . createElement ( 'button' ) ;
dom . menu = menu ;
menu . className = 'jsoneditor-contextmenu' ;
menu . title = 'Click to open the actions menu (Ctrl+M)' ;
tdMenu . appendChild ( dom . menu ) ;
dom . tr . appendChild ( tdMenu ) ;
}
// create tree and field
var tdField = document . createElement ( 'td' ) ;
dom . tr . appendChild ( tdField ) ;
dom . tree = this . _createDomTree ( ) ;
tdField . appendChild ( dom . tree ) ;
this . updateDom ( { 'updateIndexes' : true } ) ;
return dom . tr ;
} ;
/ * *
* DragStart event , fired on mousedown on the dragarea at the left side of a Node
* @ param { Node [ ] | Node } nodes
* @ param { Event } event
* /
Node . onDragStart = function ( nodes , event ) {
if ( ! Array . isArray ( nodes ) ) {
return Node . onDragStart ( [ nodes ] , event ) ;
}
if ( nodes . length === 0 ) {
return ;
}
var firstNode = nodes [ 0 ] ;
var lastNode = nodes [ nodes . length - 1 ] ;
var draggedNode = Node . getNodeFromTarget ( event . target ) ;
var beforeNode = lastNode . _nextSibling ( ) ;
var editor = firstNode . editor ;
// in case of multiple selected nodes, offsetY prevents the selection from
// jumping when you start dragging one of the lower down nodes in the selection
var offsetY = util . getAbsoluteTop ( draggedNode . dom . tr ) - util . getAbsoluteTop ( firstNode . dom . tr ) ;
if ( ! editor . mousemove ) {
editor . mousemove = util . addEventListener ( window , 'mousemove' , function ( event ) {
Node . onDrag ( nodes , event ) ;
} ) ;
}
if ( ! editor . mouseup ) {
editor . mouseup = util . addEventListener ( window , 'mouseup' , function ( event ) {
Node . onDragEnd ( nodes , event ) ;
} ) ;
}
editor . highlighter . lock ( ) ;
editor . drag = {
oldCursor : document . body . style . cursor ,
oldSelection : editor . getSelection ( ) ,
oldBeforeNode : beforeNode ,
mouseX : event . pageX ,
offsetY : offsetY ,
level : firstNode . getLevel ( )
} ;
document . body . style . cursor = 'move' ;
event . preventDefault ( ) ;
} ;
/ * *
* Drag event , fired when moving the mouse while dragging a Node
* @ param { Node [ ] | Node } nodes
* @ param { Event } event
* /
Node . onDrag = function ( nodes , event ) {
if ( ! Array . isArray ( nodes ) ) {
return Node . onDrag ( [ nodes ] , event ) ;
}
if ( nodes . length === 0 ) {
return ;
}
// TODO: this method has grown too large. Split it in a number of methods
var editor = nodes [ 0 ] . editor ;
var mouseY = event . pageY - editor . drag . offsetY ;
var mouseX = event . pageX ;
var trThis , trPrev , trNext , trFirst , trLast , trRoot ;
var nodePrev , nodeNext ;
var topThis , topPrev , topFirst , heightThis , bottomNext , heightNext ;
var moved = false ;
// TODO: add an ESC option, which resets to the original position
// move up/down
var firstNode = nodes [ 0 ] ;
trThis = firstNode . dom . tr ;
topThis = util . getAbsoluteTop ( trThis ) ;
heightThis = trThis . offsetHeight ;
if ( mouseY < topThis ) {
// move up
trPrev = trThis ;
do {
trPrev = trPrev . previousSibling ;
nodePrev = Node . getNodeFromTarget ( trPrev ) ;
topPrev = trPrev ? util . getAbsoluteTop ( trPrev ) : 0 ;
}
while ( trPrev && mouseY < topPrev ) ;
if ( nodePrev && ! nodePrev . parent ) {
nodePrev = undefined ;
}
if ( ! nodePrev ) {
// move to the first node
trRoot = trThis . parentNode . firstChild ;
trPrev = trRoot ? trRoot . nextSibling : undefined ;
nodePrev = Node . getNodeFromTarget ( trPrev ) ;
if ( nodePrev == firstNode ) {
nodePrev = undefined ;
}
}
if ( nodePrev ) {
// check if mouseY is really inside the found node
trPrev = nodePrev . dom . tr ;
topPrev = trPrev ? util . getAbsoluteTop ( trPrev ) : 0 ;
if ( mouseY > topPrev + heightThis ) {
nodePrev = undefined ;
}
}
if ( nodePrev ) {
nodes . forEach ( function ( node ) {
nodePrev . parent . moveBefore ( node , nodePrev ) ;
} ) ;
moved = true ;
}
}
else {
// move down
var lastNode = nodes [ nodes . length - 1 ] ;
trLast = ( lastNode . expanded && lastNode . append ) ? lastNode . append . getDom ( ) : lastNode . dom . tr ;
trFirst = trLast ? trLast . nextSibling : undefined ;
if ( trFirst ) {
topFirst = util . getAbsoluteTop ( trFirst ) ;
trNext = trFirst ;
do {
nodeNext = Node . getNodeFromTarget ( trNext ) ;
if ( trNext ) {
bottomNext = trNext . nextSibling ?
util . getAbsoluteTop ( trNext . nextSibling ) : 0 ;
heightNext = trNext ? ( bottomNext - topFirst ) : 0 ;
if ( nodeNext . parent . childs . length == nodes . length &&
nodeNext . parent . childs [ nodes . length - 1 ] == lastNode ) {
// We are about to remove the last child of this parent,
// which will make the parents appendNode visible.
topThis += 27 ;
// TODO: dangerous to suppose the height of the appendNode a constant of 27 px.
}
}
trNext = trNext . nextSibling ;
}
while ( trNext && mouseY > topThis + heightNext ) ;
if ( nodeNext && nodeNext . parent ) {
// calculate the desired level
var diffX = ( mouseX - editor . drag . mouseX ) ;
var diffLevel = Math . round ( diffX / 24 / 2 ) ;
var level = editor . drag . level + diffLevel ; // desired level
var levelNext = nodeNext . getLevel ( ) ; // level to be
// find the best fitting level (move upwards over the append nodes)
trPrev = nodeNext . dom . tr . previousSibling ;
while ( levelNext < level && trPrev ) {
nodePrev = Node . getNodeFromTarget ( trPrev ) ;
var isDraggedNode = nodes . some ( function ( node ) {
return node === nodePrev || nodePrev . _isChildOf ( node ) ;
} ) ;
if ( isDraggedNode ) {
// neglect the dragged nodes themselves and their childs
}
else if ( nodePrev instanceof AppendNode ) {
var childs = nodePrev . parent . childs ;
if ( childs . length != nodes . length || childs [ nodes . length - 1 ] != lastNode ) {
// non-visible append node of a list of childs
// consisting of not only this node (else the
// append node will change into a visible "empty"
// text when removing this node).
nodeNext = Node . getNodeFromTarget ( trPrev ) ;
levelNext = nodeNext . getLevel ( ) ;
}
else {
break ;
}
}
else {
break ;
}
trPrev = trPrev . previousSibling ;
}
// move the node when its position is changed
if ( trLast . nextSibling != nodeNext . dom . tr ) {
nodes . forEach ( function ( node ) {
nodeNext . parent . moveBefore ( node , nodeNext ) ;
} ) ;
moved = true ;
}
}
}
}
if ( moved ) {
// update the dragging parameters when moved
editor . drag . mouseX = mouseX ;
editor . drag . level = firstNode . getLevel ( ) ;
}
// auto scroll when hovering around the top of the editor
editor . startAutoScroll ( mouseY ) ;
event . preventDefault ( ) ;
} ;
/ * *
* Drag event , fired on mouseup after having dragged a node
* @ param { Node [ ] | Node } nodes
* @ param { Event } event
* /
Node . onDragEnd = function ( nodes , event ) {
if ( ! Array . isArray ( nodes ) ) {
return Node . onDrag ( [ nodes ] , event ) ;
}
if ( nodes . length === 0 ) {
return ;
}
var firstNode = nodes [ 0 ] ;
var editor = firstNode . editor ;
var parent = firstNode . parent ;
var firstIndex = parent . childs . indexOf ( firstNode ) ;
var beforeNode = parent . childs [ firstIndex + nodes . length ] || parent . append ;
// set focus to the context menu button of the first node
if ( nodes [ 0 ] ) {
nodes [ 0 ] . dom . menu . focus ( ) ;
}
var params = {
nodes : nodes ,
oldSelection : editor . drag . oldSelection ,
newSelection : editor . getSelection ( ) ,
oldBeforeNode : editor . drag . oldBeforeNode ,
newBeforeNode : beforeNode
} ;
if ( params . oldBeforeNode != params . newBeforeNode ) {
// only register this action if the node is actually moved to another place
editor . _onAction ( 'moveNodes' , params ) ;
}
document . body . style . cursor = editor . drag . oldCursor ;
editor . highlighter . unlock ( ) ;
nodes . forEach ( function ( node ) {
if ( event . target !== node . dom . drag && event . target !== node . dom . menu ) {
editor . highlighter . unhighlight ( ) ;
}
} ) ;
delete editor . drag ;
if ( editor . mousemove ) {
util . removeEventListener ( window , 'mousemove' , editor . mousemove ) ;
delete editor . mousemove ;
}
if ( editor . mouseup ) {
util . removeEventListener ( window , 'mouseup' , editor . mouseup ) ;
delete editor . mouseup ;
}
// Stop any running auto scroll
editor . stopAutoScroll ( ) ;
event . preventDefault ( ) ;
} ;
/ * *
* Test if this node is a child of an other node
* @ param { Node } node
* @ return { boolean } isChild
* @ private
* /
Node . prototype . _isChildOf = function ( node ) {
var n = this . parent ;
while ( n ) {
if ( n == node ) {
return true ;
}
n = n . parent ;
}
return false ;
} ;
/ * *
* Create an editable field
* @ return { Element } domField
* @ private
* /
Node . prototype . _createDomField = function ( ) {
return document . createElement ( 'div' ) ;
} ;
/ * *
* Set highlighting for this node and all its childs .
* Only applied to the currently visible ( expanded childs )
* @ param { boolean } highlight
* /
Node . prototype . setHighlight = function ( highlight ) {
if ( this . dom . tr ) {
if ( highlight ) {
util . addClassName ( this . dom . tr , 'jsoneditor-highlight' ) ;
}
else {
util . removeClassName ( this . dom . tr , 'jsoneditor-highlight' ) ;
}
if ( this . append ) {
this . append . setHighlight ( highlight ) ;
}
if ( this . childs ) {
this . childs . forEach ( function ( child ) {
child . setHighlight ( highlight ) ;
} ) ;
}
}
} ;
/ * *
* Select or deselect a node
* @ param { boolean } selected
* @ param { boolean } [ isFirst ]
* /
Node . prototype . setSelected = function ( selected , isFirst ) {
this . selected = selected ;
if ( this . dom . tr ) {
if ( selected ) {
util . addClassName ( this . dom . tr , 'jsoneditor-selected' ) ;
}
else {
util . removeClassName ( this . dom . tr , 'jsoneditor-selected' ) ;
}
if ( isFirst ) {
util . addClassName ( this . dom . tr , 'jsoneditor-first' ) ;
}
else {
util . removeClassName ( this . dom . tr , 'jsoneditor-first' ) ;
}
if ( this . append ) {
this . append . setSelected ( selected ) ;
}
if ( this . childs ) {
this . childs . forEach ( function ( child ) {
child . setSelected ( selected ) ;
} ) ;
}
}
} ;
/ * *
* Update the value of the node . Only primitive types are allowed , no Object
* or Array is allowed .
* @ param { String | Number | Boolean | null } value
* /
Node . prototype . updateValue = function ( value ) {
this . value = value ;
this . updateDom ( ) ;
} ;
/ * *
* Update the field of the node .
* @ param { String } field
* /
Node . prototype . updateField = function ( field ) {
this . field = field ;
this . updateDom ( ) ;
} ;
/ * *
* Update the HTML DOM , optionally recursing through the childs
* @ param { Object } [ options ] Available parameters :
* { boolean } [ recurse ] If true , the
* DOM of the childs will be updated recursively .
* False by default .
* { boolean } [ updateIndexes ] If true , the childs
* indexes of the node will be updated too . False by
* default .
* /
Node . prototype . updateDom = function ( options ) {
// update level indentation
var domTree = this . dom . tree ;
if ( domTree ) {
domTree . style . marginLeft = this . getLevel ( ) * 24 + 'px' ;
}
// apply field to DOM
var domField = this . dom . field ;
if ( domField ) {
if ( this . fieldEditable ) {
// parent is an object
domField . contentEditable = this . editable . field ;
domField . spellcheck = false ;
domField . className = 'jsoneditor-field' ;
}
else {
// parent is an array this is the root node
domField . className = 'jsoneditor-readonly' ;
}
var field ;
if ( this . index != undefined ) {
field = this . index ;
}
else if ( this . field != undefined ) {
field = this . field ;
}
else if ( this . _hasChilds ( ) ) {
field = this . type ;
}
else {
field = '' ;
}
domField . innerHTML = this . _escapeHTML ( field ) ;
}
// apply value to DOM
var domValue = this . dom . value ;
if ( domValue ) {
var count = this . childs ? this . childs . length : 0 ;
if ( this . type == 'array' ) {
domValue . innerHTML = '[' + count + ']' ;
util . addClassName ( this . dom . tr , 'jsoneditor-expandable' ) ;
}
else if ( this . type == 'object' ) {
domValue . innerHTML = '{' + count + '}' ;
util . addClassName ( this . dom . tr , 'jsoneditor-expandable' ) ;
}
else {
domValue . innerHTML = this . _escapeHTML ( this . value ) ;
util . removeClassName ( this . dom . tr , 'jsoneditor-expandable' ) ;
}
}
// update field and value
this . _updateDomField ( ) ;
this . _updateDomValue ( ) ;
// update childs indexes
if ( options && options . updateIndexes === true ) {
// updateIndexes is true or undefined
this . _updateDomIndexes ( ) ;
}
if ( options && options . recurse === true ) {
// recurse is true or undefined. update childs recursively
if ( this . childs ) {
this . childs . forEach ( function ( child ) {
child . updateDom ( options ) ;
} ) ;
}
}
// update row with append button
if ( this . append ) {
this . append . updateDom ( ) ;
}
} ;
/ * *
* Update the DOM of the childs of a node : update indexes and undefined field
* names .
* Only applicable when structure is an array or object
* @ private
* /
Node . prototype . _updateDomIndexes = function ( ) {
var domValue = this . dom . value ;
var childs = this . childs ;
if ( domValue && childs ) {
if ( this . type == 'array' ) {
childs . forEach ( function ( child , index ) {
child . index = index ;
var childField = child . dom . field ;
if ( childField ) {
childField . innerHTML = index ;
}
} ) ;
}
else if ( this . type == 'object' ) {
childs . forEach ( function ( child ) {
if ( child . index != undefined ) {
delete child . index ;
if ( child . field == undefined ) {
child . field = '' ;
}
}
} ) ;
}
}
} ;
/ * *
* Create an editable value
* @ private
* /
Node . prototype . _createDomValue = function ( ) {
var domValue ;
if ( this . type == 'array' ) {
domValue = document . createElement ( 'div' ) ;
domValue . innerHTML = '[...]' ;
}
else if ( this . type == 'object' ) {
domValue = document . createElement ( 'div' ) ;
domValue . innerHTML = '{...}' ;
}
else {
if ( ! this . editable . value && util . isUrl ( this . value ) ) {
// create a link in case of read-only editor and value containing an url
domValue = document . createElement ( 'a' ) ;
domValue . href = this . value ;
domValue . target = '_blank' ;
domValue . innerHTML = this . _escapeHTML ( this . value ) ;
}
else {
// create an editable or read-only div
domValue = document . createElement ( 'div' ) ;
domValue . contentEditable = this . editable . value ;
domValue . spellcheck = false ;
domValue . innerHTML = this . _escapeHTML ( this . value ) ;
}
}
return domValue ;
} ;
/ * *
* Create an expand / collapse button
* @ return { Element } expand
* @ private
* /
Node . prototype . _createDomExpandButton = function ( ) {
// create expand button
var expand = document . createElement ( 'button' ) ;
if ( this . _hasChilds ( ) ) {
expand . className = this . expanded ? 'jsoneditor-expanded' : 'jsoneditor-collapsed' ;
expand . title =
'Click to expand/collapse this field (Ctrl+E). \n' +
'Ctrl+Click to expand/collapse including all childs.' ;
}
else {
expand . className = 'jsoneditor-invisible' ;
expand . title = '' ;
}
return expand ;
} ;
/ * *
* Create a DOM tree element , containing the expand / collapse button
* @ return { Element } domTree
* @ private
* /
Node . prototype . _createDomTree = function ( ) {
var dom = this . dom ;
var domTree = document . createElement ( 'table' ) ;
var tbody = document . createElement ( 'tbody' ) ;
domTree . style . borderCollapse = 'collapse' ; // TODO: put in css
domTree . className = 'jsoneditor-values' ;
domTree . appendChild ( tbody ) ;
var tr = document . createElement ( 'tr' ) ;
tbody . appendChild ( tr ) ;
// create expand button
var tdExpand = document . createElement ( 'td' ) ;
tdExpand . className = 'jsoneditor-tree' ;
tr . appendChild ( tdExpand ) ;
dom . expand = this . _createDomExpandButton ( ) ;
tdExpand . appendChild ( dom . expand ) ;
dom . tdExpand = tdExpand ;
// create the field
var tdField = document . createElement ( 'td' ) ;
tdField . className = 'jsoneditor-tree' ;
tr . appendChild ( tdField ) ;
dom . field = this . _createDomField ( ) ;
tdField . appendChild ( dom . field ) ;
dom . tdField = tdField ;
// create a separator
var tdSeparator = document . createElement ( 'td' ) ;
tdSeparator . className = 'jsoneditor-tree' ;
tr . appendChild ( tdSeparator ) ;
if ( this . type != 'object' && this . type != 'array' ) {
tdSeparator . appendChild ( document . createTextNode ( ':' ) ) ;
tdSeparator . className = 'jsoneditor-separator' ;
}
dom . tdSeparator = tdSeparator ;
// create the value
var tdValue = document . createElement ( 'td' ) ;
tdValue . className = 'jsoneditor-tree' ;
tr . appendChild ( tdValue ) ;
dom . value = this . _createDomValue ( ) ;
tdValue . appendChild ( dom . value ) ;
dom . tdValue = tdValue ;
return domTree ;
} ;
/ * *
* Handle an event . The event is caught centrally by the editor
* @ param { Event } event
* /
Node . prototype . onEvent = function ( event ) {
var type = event . type ,
target = event . target || event . srcElement ,
dom = this . dom ,
node = this ,
expandable = this . _hasChilds ( ) ;
// check if mouse is on menu or on dragarea.
// If so, highlight current row and its childs
if ( target == dom . drag || target == dom . menu ) {
if ( type == 'mouseover' ) {
this . editor . highlighter . highlight ( this ) ;
}
else if ( type == 'mouseout' ) {
this . editor . highlighter . unhighlight ( ) ;
}
}
// context menu events
if ( type == 'click' && target == dom . menu ) {
var highlighter = node . editor . highlighter ;
highlighter . highlight ( node ) ;
highlighter . lock ( ) ;
util . addClassName ( dom . menu , 'jsoneditor-selected' ) ;
this . showContextMenu ( dom . menu , function ( ) {
util . removeClassName ( dom . menu , 'jsoneditor-selected' ) ;
highlighter . unlock ( ) ;
highlighter . unhighlight ( ) ;
} ) ;
}
// expand events
if ( type == 'click' ) {
if ( target == dom . expand ||
( ( node . editor . options . mode === 'view' || node . editor . options . mode === 'form' ) && target . nodeName === 'DIV' ) ) {
if ( expandable ) {
var recurse = event . ctrlKey ; // with ctrl-key, expand/collapse all
this . _onExpand ( recurse ) ;
}
}
}
// swap the value of a boolean when the checkbox displayed left is clicked
if ( type == 'change' && target == dom . checkbox ) {
this . dom . value . innerHTML = ! this . value ;
this . _getDomValue ( ) ;
}
// value events
var domValue = dom . value ;
if ( target == domValue ) {
//noinspection FallthroughInSwitchStatementJS
switch ( type ) {
case 'blur' :
case 'change' :
this . _getDomValue ( true ) ;
this . _updateDomValue ( ) ;
if ( this . value ) {
domValue . innerHTML = this . _escapeHTML ( this . value ) ;
}
break ;
case 'input' :
//this._debouncedGetDomValue(true); // TODO
this . _getDomValue ( true ) ;
this . _updateDomValue ( ) ;
break ;
case 'keydown' :
case 'mousedown' :
// TODO: cleanup
this . editor . selection = this . editor . getSelection ( ) ;
break ;
case 'click' :
if ( event . ctrlKey || ! this . editable . value ) {
if ( util . isUrl ( this . value ) ) {
window . open ( this . value , '_blank' ) ;
}
}
break ;
case 'keyup' :
//this._debouncedGetDomValue(true); // TODO
this . _getDomValue ( true ) ;
this . _updateDomValue ( ) ;
break ;
case 'cut' :
case 'paste' :
setTimeout ( function ( ) {
node . _getDomValue ( true ) ;
node . _updateDomValue ( ) ;
} , 1 ) ;
break ;
}
}
// field events
var domField = dom . field ;
if ( target == domField ) {
switch ( type ) {
case 'blur' :
case 'change' :
this . _getDomField ( true ) ;
this . _updateDomField ( ) ;
if ( this . field ) {
domField . innerHTML = this . _escapeHTML ( this . field ) ;
}
break ;
case 'input' :
this . _getDomField ( true ) ;
this . _updateDomField ( ) ;
break ;
case 'keydown' :
case 'mousedown' :
this . editor . selection = this . editor . getSelection ( ) ;
break ;
case 'keyup' :
this . _getDomField ( true ) ;
this . _updateDomField ( ) ;
break ;
case 'cut' :
case 'paste' :
setTimeout ( function ( ) {
node . _getDomField ( true ) ;
node . _updateDomField ( ) ;
} , 1 ) ;
break ;
}
}
// focus
// when clicked in whitespace left or right from the field or value, set focus
var domTree = dom . tree ;
if ( target == domTree . parentNode && type == 'click' && ! event . hasMoved ) {
var left = ( event . offsetX != undefined ) ?
( event . offsetX < ( this . getLevel ( ) + 1 ) * 24 ) :
( event . pageX < util . getAbsoluteLeft ( dom . tdSeparator ) ) ; // for FF
if ( left || expandable ) {
// node is expandable when it is an object or array
if ( domField ) {
util . setEndOfContentEditable ( domField ) ;
domField . focus ( ) ;
}
}
else {
if ( domValue ) {
util . setEndOfContentEditable ( domValue ) ;
domValue . focus ( ) ;
}
}
}
if ( ( ( target == dom . tdExpand && ! expandable ) || target == dom . tdField || target == dom . tdSeparator ) &&
( type == 'click' && ! event . hasMoved ) ) {
if ( domField ) {
util . setEndOfContentEditable ( domField ) ;
domField . focus ( ) ;
}
}
if ( type == 'keydown' ) {
this . onKeyDown ( event ) ;
}
} ;
/ * *
* Key down event handler
* @ param { Event } event
* /
Node . prototype . onKeyDown = function ( event ) {
var keynum = event . which || event . keyCode ;
var target = event . target || event . srcElement ;
var ctrlKey = event . ctrlKey ;
var shiftKey = event . shiftKey ;
var altKey = event . altKey ;
var handled = false ;
var prevNode , nextNode , nextDom , nextDom2 ;
var editable = this . editor . options . mode === 'tree' ;
var oldSelection ;
var oldBeforeNode ;
var nodes ;
var multiselection ;
var selectedNodes = this . editor . multiselection . nodes . length > 0
? this . editor . multiselection . nodes
: [ this ] ;
var firstNode = selectedNodes [ 0 ] ;
var lastNode = selectedNodes [ selectedNodes . length - 1 ] ;
// console.log(ctrlKey, keynum, event.charCode); // TODO: cleanup
if ( keynum == 13 ) { // Enter
if ( target == this . dom . value ) {
if ( ! this . editable . value || event . ctrlKey ) {
if ( util . isUrl ( this . value ) ) {
window . open ( this . value , '_blank' ) ;
handled = true ;
}
}
}
else if ( target == this . dom . expand ) {
var expandable = this . _hasChilds ( ) ;
if ( expandable ) {
var recurse = event . ctrlKey ; // with ctrl-key, expand/collapse all
this . _onExpand ( recurse ) ;
target . focus ( ) ;
handled = true ;
}
}
}
else if ( keynum == 68 ) { // D
if ( ctrlKey && editable ) { // Ctrl+D
Node . onDuplicate ( selectedNodes ) ;
handled = true ;
}
}
else if ( keynum == 69 ) { // E
if ( ctrlKey ) { // Ctrl+E and Ctrl+Shift+E
this . _onExpand ( shiftKey ) ; // recurse = shiftKey
target . focus ( ) ; // TODO: should restore focus in case of recursing expand (which takes DOM offline)
handled = true ;
}
}
else if ( keynum == 77 && editable ) { // M
if ( ctrlKey ) { // Ctrl+M
this . showContextMenu ( target ) ;
handled = true ;
}
}
else if ( keynum == 46 && editable ) { // Del
if ( ctrlKey ) { // Ctrl+Del
Node . onRemove ( selectedNodes ) ;
handled = true ;
}
}
else if ( keynum == 45 && editable ) { // Ins
if ( ctrlKey && ! shiftKey ) { // Ctrl+Ins
this . _onInsertBefore ( ) ;
handled = true ;
}
else if ( ctrlKey && shiftKey ) { // Ctrl+Shift+Ins
this . _onInsertAfter ( ) ;
handled = true ;
}
}
else if ( keynum == 35 ) { // End
if ( altKey ) { // Alt+End
// find the last node
var endNode = this . _lastNode ( ) ;
if ( endNode ) {
endNode . focus ( Node . focusElement || this . _getElementName ( target ) ) ;
}
handled = true ;
}
}
else if ( keynum == 36 ) { // Home
if ( altKey ) { // Alt+Home
// find the first node
var homeNode = this . _firstNode ( ) ;
if ( homeNode ) {
homeNode . focus ( Node . focusElement || this . _getElementName ( target ) ) ;
}
handled = true ;
}
}
else if ( keynum == 37 ) { // Arrow Left
if ( altKey && ! shiftKey ) { // Alt + Arrow Left
// move to left element
var prevElement = this . _previousElement ( target ) ;
if ( prevElement ) {
this . focus ( this . _getElementName ( prevElement ) ) ;
}
handled = true ;
}
else if ( altKey && shiftKey && editable ) { // Alt + Shift + Arrow left
if ( lastNode . expanded ) {
var appendDom = lastNode . getAppend ( ) ;
nextDom = appendDom ? appendDom . nextSibling : undefined ;
}
else {
var dom = lastNode . getDom ( ) ;
nextDom = dom . nextSibling ;
}
if ( nextDom ) {
nextNode = Node . getNodeFromTarget ( nextDom ) ;
nextDom2 = nextDom . nextSibling ;
nextNode2 = Node . getNodeFromTarget ( nextDom2 ) ;
if ( nextNode && nextNode instanceof AppendNode &&
! ( lastNode . parent . childs . length == 1 ) &&
nextNode2 && nextNode2 . parent ) {
oldSelection = this . editor . getSelection ( ) ;
oldBeforeNode = lastNode . _nextSibling ( ) ;
selectedNodes . forEach ( function ( node ) {
nextNode2 . parent . moveBefore ( node , nextNode2 ) ;
} ) ;
this . focus ( Node . focusElement || this . _getElementName ( target ) ) ;
this . editor . _onAction ( 'moveNodes' , {
nodes : selectedNodes ,
oldBeforeNode : oldBeforeNode ,
newBeforeNode : nextNode2 ,
oldSelection : oldSelection ,
newSelection : this . editor . getSelection ( )
} ) ;
}
}
}
}
else if ( keynum == 38 ) { // Arrow Up
if ( altKey && ! shiftKey ) { // Alt + Arrow Up
// find the previous node
prevNode = this . _previousNode ( ) ;
if ( prevNode ) {
this . editor . deselect ( true ) ;
prevNode . focus ( Node . focusElement || this . _getElementName ( target ) ) ;
}
handled = true ;
}
else if ( ! altKey && ctrlKey && shiftKey && editable ) { // Ctrl + Shift + Arrow Up
// select multiple nodes
prevNode = this . _previousNode ( ) ;
if ( prevNode ) {
multiselection = this . editor . multiselection ;
multiselection . start = multiselection . start || this ;
multiselection . end = prevNode ;
nodes = this . editor . _findTopLevelNodes ( multiselection . start , multiselection . end ) ;
this . editor . select ( nodes ) ;
prevNode . focus ( 'field' ) ; // select field as we know this always exists
}
handled = true ;
}
else if ( altKey && shiftKey && editable ) { // Alt + Shift + Arrow Up
// find the previous node
prevNode = firstNode . _previousNode ( ) ;
if ( prevNode && prevNode . parent ) {
oldSelection = this . editor . getSelection ( ) ;
oldBeforeNode = lastNode . _nextSibling ( ) ;
selectedNodes . forEach ( function ( node ) {
prevNode . parent . moveBefore ( node , prevNode ) ;
} ) ;
this . focus ( Node . focusElement || this . _getElementName ( target ) ) ;
this . editor . _onAction ( 'moveNodes' , {
nodes : selectedNodes ,
oldBeforeNode : oldBeforeNode ,
newBeforeNode : prevNode ,
oldSelection : oldSelection ,
newSelection : this . editor . getSelection ( )
} ) ;
}
handled = true ;
}
}
else if ( keynum == 39 ) { // Arrow Right
if ( altKey && ! shiftKey ) { // Alt + Arrow Right
// move to right element
var nextElement = this . _nextElement ( target ) ;
if ( nextElement ) {
this . focus ( this . _getElementName ( nextElement ) ) ;
}
handled = true ;
}
else if ( altKey && shiftKey && editable ) { // Alt + Shift + Arrow Right
dom = firstNode . getDom ( ) ;
var prevDom = dom . previousSibling ;
if ( prevDom ) {
prevNode = Node . getNodeFromTarget ( prevDom ) ;
if ( prevNode && prevNode . parent &&
( prevNode instanceof AppendNode )
&& ! prevNode . isVisible ( ) ) {
oldSelection = this . editor . getSelection ( ) ;
oldBeforeNode = lastNode . _nextSibling ( ) ;
selectedNodes . forEach ( function ( node ) {
prevNode . parent . moveBefore ( node , prevNode ) ;
} ) ;
this . focus ( Node . focusElement || this . _getElementName ( target ) ) ;
this . editor . _onAction ( 'moveNodes' , {
nodes : selectedNodes ,
oldBeforeNode : oldBeforeNode ,
newBeforeNode : prevNode ,
oldSelection : oldSelection ,
newSelection : this . editor . getSelection ( )
} ) ;
}
}
}
}
else if ( keynum == 40 ) { // Arrow Down
if ( altKey && ! shiftKey ) { // Alt + Arrow Down
// find the next node
nextNode = this . _nextNode ( ) ;
if ( nextNode ) {
this . editor . deselect ( true ) ;
nextNode . focus ( Node . focusElement || this . _getElementName ( target ) ) ;
}
handled = true ;
}
else if ( ! altKey && ctrlKey && shiftKey && editable ) { // Ctrl + Shift + Arrow Down
// select multiple nodes
nextNode = this . _nextNode ( ) ;
if ( nextNode ) {
multiselection = this . editor . multiselection ;
multiselection . start = multiselection . start || this ;
multiselection . end = nextNode ;
nodes = this . editor . _findTopLevelNodes ( multiselection . start , multiselection . end ) ;
this . editor . select ( nodes ) ;
nextNode . focus ( 'field' ) ; // select field as we know this always exists
}
handled = true ;
}
else if ( altKey && shiftKey && editable ) { // Alt + Shift + Arrow Down
// find the 2nd next node and move before that one
if ( lastNode . expanded ) {
nextNode = lastNode . append ? lastNode . append . _nextNode ( ) : undefined ;
}
else {
nextNode = lastNode . _nextNode ( ) ;
}
var nextNode2 = nextNode && ( nextNode . _nextNode ( ) || nextNode . parent . append ) ;
if ( nextNode2 && nextNode2 . parent ) {
oldSelection = this . editor . getSelection ( ) ;
oldBeforeNode = lastNode . _nextSibling ( ) ;
selectedNodes . forEach ( function ( node ) {
nextNode2 . parent . moveBefore ( node , nextNode2 ) ;
} ) ;
this . focus ( Node . focusElement || this . _getElementName ( target ) ) ;
this . editor . _onAction ( 'moveNodes' , {
nodes : selectedNodes ,
oldBeforeNode : oldBeforeNode ,
newBeforeNode : nextNode2 ,
oldSelection : oldSelection ,
newSelection : this . editor . getSelection ( )
} ) ;
}
handled = true ;
}
}
if ( handled ) {
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
}
} ;
/ * *
* Handle the expand event , when clicked on the expand button
* @ param { boolean } recurse If true , child nodes will be expanded too
* @ private
* /
Node . prototype . _onExpand = function ( recurse ) {
if ( recurse ) {
// Take the table offline
var table = this . dom . tr . parentNode ; // TODO: not nice to access the main table like this
var frame = table . parentNode ;
var scrollTop = frame . scrollTop ;
frame . removeChild ( table ) ;
}
if ( this . expanded ) {
this . collapse ( recurse ) ;
}
else {
this . expand ( recurse ) ;
}
if ( recurse ) {
// Put the table online again
frame . appendChild ( table ) ;
frame . scrollTop = scrollTop ;
}
} ;
/ * *
* Remove nodes
* @ param { Node [ ] | Node } nodes
* /
Node . onRemove = function ( nodes ) {
if ( ! Array . isArray ( nodes ) ) {
return Node . onRemove ( [ nodes ] ) ;
}
if ( nodes && nodes . length > 0 ) {
var firstNode = nodes [ 0 ] ;
var parent = firstNode . parent ;
var editor = firstNode . editor ;
var firstIndex = firstNode . getIndex ( ) ;
editor . highlighter . unhighlight ( ) ;
// adjust the focus
var oldSelection = editor . getSelection ( ) ;
Node . blurNodes ( nodes ) ;
var newSelection = editor . getSelection ( ) ;
// remove the nodes
nodes . forEach ( function ( node ) {
node . parent . _remove ( node ) ;
} ) ;
// store history action
editor . _onAction ( 'removeNodes' , {
nodes : nodes . slice ( 0 ) , // store a copy of the array!
parent : parent ,
index : firstIndex ,
oldSelection : oldSelection ,
newSelection : newSelection
} ) ;
}
} ;
/ * *
* Duplicate nodes
* duplicated nodes will be added right after the original nodes
* @ param { Node [ ] | Node } nodes
* /
Node . onDuplicate = function ( nodes ) {
if ( ! Array . isArray ( nodes ) ) {
return Node . onDuplicate ( [ nodes ] ) ;
}
if ( nodes && nodes . length > 0 ) {
var lastNode = nodes [ nodes . length - 1 ] ;
var parent = lastNode . parent ;
var editor = lastNode . editor ;
editor . deselect ( editor . multiselection . nodes ) ;
// duplicate the nodes
var oldSelection = editor . getSelection ( ) ;
var afterNode = lastNode ;
var clones = nodes . map ( function ( node ) {
var clone = node . clone ( ) ;
parent . insertAfter ( clone , afterNode ) ;
afterNode = clone ;
return clone ;
} ) ;
// set selection to the duplicated nodes
if ( nodes . length === 1 ) {
clones [ 0 ] . focus ( ) ;
}
else {
editor . select ( clones ) ;
}
var newSelection = editor . getSelection ( ) ;
editor . _onAction ( 'duplicateNodes' , {
afterNode : lastNode ,
nodes : clones ,
parent : parent ,
oldSelection : oldSelection ,
newSelection : newSelection
} ) ;
}
} ;
/ * *
* Handle insert before event
* @ param { String } [ field ]
* @ param { * } [ value ]
* @ param { String } [ type ] Can be 'auto' , 'array' , 'object' , or 'string'
* @ private
* /
Node . prototype . _onInsertBefore = function ( field , value , type ) {
var oldSelection = this . editor . getSelection ( ) ;
var newNode = new Node ( this . editor , {
field : ( field != undefined ) ? field : '' ,
value : ( value != undefined ) ? value : '' ,
type : type
} ) ;
newNode . expand ( true ) ;
this . parent . insertBefore ( newNode , this ) ;
this . editor . highlighter . unhighlight ( ) ;
newNode . focus ( 'field' ) ;
var newSelection = this . editor . getSelection ( ) ;
this . editor . _onAction ( 'insertBeforeNodes' , {
nodes : [ newNode ] ,
beforeNode : this ,
parent : this . parent ,
oldSelection : oldSelection ,
newSelection : newSelection
} ) ;
} ;
/ * *
* Handle insert after event
* @ param { String } [ field ]
* @ param { * } [ value ]
* @ param { String } [ type ] Can be 'auto' , 'array' , 'object' , or 'string'
* @ private
* /
Node . prototype . _onInsertAfter = function ( field , value , type ) {
var oldSelection = this . editor . getSelection ( ) ;
var newNode = new Node ( this . editor , {
field : ( field != undefined ) ? field : '' ,
value : ( value != undefined ) ? value : '' ,
type : type
} ) ;
newNode . expand ( true ) ;
this . parent . insertAfter ( newNode , this ) ;
this . editor . highlighter . unhighlight ( ) ;
newNode . focus ( 'field' ) ;
var newSelection = this . editor . getSelection ( ) ;
this . editor . _onAction ( 'insertAfterNodes' , {
nodes : [ newNode ] ,
afterNode : this ,
parent : this . parent ,
oldSelection : oldSelection ,
newSelection : newSelection
} ) ;
} ;
/ * *
* Handle append event
* @ param { String } [ field ]
* @ param { * } [ value ]
* @ param { String } [ type ] Can be 'auto' , 'array' , 'object' , or 'string'
* @ private
* /
Node . prototype . _onAppend = function ( field , value , type ) {
var oldSelection = this . editor . getSelection ( ) ;
var newNode = new Node ( this . editor , {
field : ( field != undefined ) ? field : '' ,
value : ( value != undefined ) ? value : '' ,
type : type
} ) ;
newNode . expand ( true ) ;
this . parent . appendChild ( newNode ) ;
this . editor . highlighter . unhighlight ( ) ;
newNode . focus ( 'field' ) ;
var newSelection = this . editor . getSelection ( ) ;
this . editor . _onAction ( 'appendNodes' , {
nodes : [ newNode ] ,
parent : this . parent ,
oldSelection : oldSelection ,
newSelection : newSelection
} ) ;
} ;
/ * *
* Change the type of the node ' s value
* @ param { String } newType
* @ private
* /
Node . prototype . _onChangeType = function ( newType ) {
var oldType = this . type ;
if ( newType != oldType ) {
var oldSelection = this . editor . getSelection ( ) ;
this . changeType ( newType ) ;
var newSelection = this . editor . getSelection ( ) ;
this . editor . _onAction ( 'changeType' , {
node : this ,
oldType : oldType ,
newType : newType ,
oldSelection : oldSelection ,
newSelection : newSelection
} ) ;
}
} ;
/ * *
* Sort the childs of the node . Only applicable when the node has type 'object'
* or 'array' .
* @ param { String } direction Sorting direction . Available values : "asc" , "desc"
* @ private
* /
Node . prototype . _onSort = function ( direction ) {
if ( this . _hasChilds ( ) ) {
var order = ( direction == 'desc' ) ? - 1 : 1 ;
var prop = ( this . type == 'array' ) ? 'value' : 'field' ;
this . hideChilds ( ) ;
var oldChilds = this . childs ;
var oldSort = this . sort ;
// copy the array (the old one will be kept for an undo action
this . childs = this . childs . concat ( ) ;
// sort the arrays
this . childs . sort ( function ( a , b ) {
if ( a [ prop ] > b [ prop ] ) return order ;
if ( a [ prop ] < b [ prop ] ) return - order ;
return 0 ;
} ) ;
this . sort = ( order == 1 ) ? 'asc' : 'desc' ;
this . editor . _onAction ( 'sort' , {
node : this ,
oldChilds : oldChilds ,
oldSort : oldSort ,
newChilds : this . childs ,
newSort : this . sort
} ) ;
this . showChilds ( ) ;
}
} ;
/ * *
* Create a table row with an append button .
* @ return { HTMLElement | undefined } buttonAppend or undefined when inapplicable
* /
Node . prototype . getAppend = function ( ) {
if ( ! this . append ) {
this . append = new AppendNode ( this . editor ) ;
this . append . setParent ( this ) ;
}
return this . append . getDom ( ) ;
} ;
/ * *
* Find the node from an event target
* @ param { Node } target
* @ return { Node | undefined } node or undefined when not found
* @ static
* /
Node . getNodeFromTarget = function ( target ) {
while ( target ) {
if ( target . node ) {
return target . node ;
}
target = target . parentNode ;
}
return undefined ;
} ;
/ * *
* Remove the focus of given nodes , and move the focus to the ( a ) node before ,
* ( b ) the node after , or ( c ) the parent node .
* @ param { Array . < Node > | Node } nodes
* /
Node . blurNodes = function ( nodes ) {
if ( ! Array . isArray ( nodes ) ) {
Node . blurNodes ( [ nodes ] ) ;
return ;
}
var firstNode = nodes [ 0 ] ;
var parent = firstNode . parent ;
var firstIndex = firstNode . getIndex ( ) ;
if ( parent . childs [ firstIndex + nodes . length ] ) {
parent . childs [ firstIndex + nodes . length ] . focus ( ) ;
}
else if ( parent . childs [ firstIndex - 1 ] ) {
parent . childs [ firstIndex - 1 ] . focus ( ) ;
}
else {
parent . focus ( ) ;
}
} ;
/ * *
* Get the next sibling of current node
* @ return { Node } nextSibling
* @ private
* /
Node . prototype . _nextSibling = function ( ) {
var index = this . parent . childs . indexOf ( this ) ;
return this . parent . childs [ index + 1 ] || this . parent . append ;
} ;
/ * *
* Get the previously rendered node
* @ return { Node | null } previousNode
* @ private
* /
Node . prototype . _previousNode = function ( ) {
var prevNode = null ;
var dom = this . getDom ( ) ;
if ( dom && dom . parentNode ) {
// find the previous field
var prevDom = dom ;
do {
prevDom = prevDom . previousSibling ;
prevNode = Node . getNodeFromTarget ( prevDom ) ;
}
while ( prevDom && ( prevNode instanceof AppendNode && ! prevNode . isVisible ( ) ) ) ;
}
return prevNode ;
} ;
/ * *
* Get the next rendered node
* @ return { Node | null } nextNode
* @ private
* /
Node . prototype . _nextNode = function ( ) {
var nextNode = null ;
var dom = this . getDom ( ) ;
if ( dom && dom . parentNode ) {
// find the previous field
var nextDom = dom ;
do {
nextDom = nextDom . nextSibling ;
nextNode = Node . getNodeFromTarget ( nextDom ) ;
}
while ( nextDom && ( nextNode instanceof AppendNode && ! nextNode . isVisible ( ) ) ) ;
}
return nextNode ;
} ;
/ * *
* Get the first rendered node
* @ return { Node | null } firstNode
* @ private
* /
Node . prototype . _firstNode = function ( ) {
var firstNode = null ;
var dom = this . getDom ( ) ;
if ( dom && dom . parentNode ) {
var firstDom = dom . parentNode . firstChild ;
firstNode = Node . getNodeFromTarget ( firstDom ) ;
}
return firstNode ;
} ;
/ * *
* Get the last rendered node
* @ return { Node | null } lastNode
* @ private
* /
Node . prototype . _lastNode = function ( ) {
var lastNode = null ;
var dom = this . getDom ( ) ;
if ( dom && dom . parentNode ) {
var lastDom = dom . parentNode . lastChild ;
lastNode = Node . getNodeFromTarget ( lastDom ) ;
while ( lastDom && ( lastNode instanceof AppendNode && ! lastNode . isVisible ( ) ) ) {
lastDom = lastDom . previousSibling ;
lastNode = Node . getNodeFromTarget ( lastDom ) ;
}
}
return lastNode ;
} ;
/ * *
* Get the next element which can have focus .
* @ param { Element } elem
* @ return { Element | null } nextElem
* @ private
* /
Node . prototype . _previousElement = function ( elem ) {
var dom = this . dom ;
// noinspection FallthroughInSwitchStatementJS
switch ( elem ) {
case dom . value :
if ( this . fieldEditable ) {
return dom . field ;
}
// intentional fall through
case dom . field :
if ( this . _hasChilds ( ) ) {
return dom . expand ;
}
// intentional fall through
case dom . expand :
return dom . menu ;
case dom . menu :
if ( dom . drag ) {
return dom . drag ;
}
// intentional fall through
default :
return null ;
}
} ;
/ * *
* Get the next element which can have focus .
* @ param { Element } elem
* @ return { Element | null } nextElem
* @ private
* /
Node . prototype . _nextElement = function ( elem ) {
var dom = this . dom ;
// noinspection FallthroughInSwitchStatementJS
switch ( elem ) {
case dom . drag :
return dom . menu ;
case dom . menu :
if ( this . _hasChilds ( ) ) {
return dom . expand ;
}
// intentional fall through
case dom . expand :
if ( this . fieldEditable ) {
return dom . field ;
}
// intentional fall through
case dom . field :
if ( ! this . _hasChilds ( ) ) {
return dom . value ;
}
default :
return null ;
}
} ;
/ * *
* Get the dom name of given element . returns null if not found .
* For example when element == dom . field , "field" is returned .
* @ param { Element } element
* @ return { String | null } elementName Available elements with name : 'drag' ,
* 'menu' , 'expand' , 'field' , 'value'
* @ private
* /
Node . prototype . _getElementName = function ( element ) {
var dom = this . dom ;
for ( var name in dom ) {
if ( dom . hasOwnProperty ( name ) ) {
if ( dom [ name ] == element ) {
return name ;
}
}
}
return null ;
} ;
/ * *
* Test if this node has childs . This is the case when the node is an object
* or array .
* @ return { boolean } hasChilds
* @ private
* /
Node . prototype . _hasChilds = function ( ) {
return this . type == 'array' || this . type == 'object' ;
} ;
// titles with explanation for the different types
Node . TYPE _TITLES = {
'auto' : 'Field type "auto". ' +
'The field type is automatically determined from the value ' +
'and can be a string, number, boolean, or null.' ,
'object' : 'Field type "object". ' +
'An object contains an unordered set of key/value pairs.' ,
'array' : 'Field type "array". ' +
'An array contains an ordered collection of values.' ,
'string' : 'Field type "string". ' +
'Field type is not determined from the value, ' +
'but always returned as string.'
} ;
/ * *
* Show a contextmenu for this node
* @ param { HTMLElement } anchor Anchor element to attach the context menu to
* as sibling .
* @ param { function } [ onClose ] Callback method called when the context menu
* is being closed .
* /
Node . prototype . showContextMenu = function ( anchor , onClose ) {
var node = this ;
var titles = Node . TYPE _TITLES ;
var items = [ ] ;
if ( this . editable . value ) {
items . push ( {
text : 'Type' ,
title : 'Change the type of this field' ,
className : 'jsoneditor-type-' + this . type ,
submenu : [
{
text : 'Auto' ,
className : 'jsoneditor-type-auto' +
( this . type == 'auto' ? ' jsoneditor-selected' : '' ) ,
title : titles . auto ,
click : function ( ) {
node . _onChangeType ( 'auto' ) ;
}
} ,
{
text : 'Array' ,
className : 'jsoneditor-type-array' +
( this . type == 'array' ? ' jsoneditor-selected' : '' ) ,
title : titles . array ,
click : function ( ) {
node . _onChangeType ( 'array' ) ;
}
} ,
{
text : 'Object' ,
className : 'jsoneditor-type-object' +
( this . type == 'object' ? ' jsoneditor-selected' : '' ) ,
title : titles . object ,
click : function ( ) {
node . _onChangeType ( 'object' ) ;
}
} ,
{
text : 'String' ,
className : 'jsoneditor-type-string' +
( this . type == 'string' ? ' jsoneditor-selected' : '' ) ,
title : titles . string ,
click : function ( ) {
node . _onChangeType ( 'string' ) ;
}
}
]
} ) ;
}
if ( this . _hasChilds ( ) ) {
var direction = ( ( this . sort == 'asc' ) ? 'desc' : 'asc' ) ;
items . push ( {
text : 'Sort' ,
title : 'Sort the childs of this ' + this . type ,
className : 'jsoneditor-sort-' + direction ,
click : function ( ) {
node . _onSort ( direction ) ;
} ,
submenu : [
{
text : 'Ascending' ,
className : 'jsoneditor-sort-asc' ,
title : 'Sort the childs of this ' + this . type + ' in ascending order' ,
click : function ( ) {
node . _onSort ( 'asc' ) ;
}
} ,
{
text : 'Descending' ,
className : 'jsoneditor-sort-desc' ,
title : 'Sort the childs of this ' + this . type + ' in descending order' ,
click : function ( ) {
node . _onSort ( 'desc' ) ;
}
}
]
} ) ;
}
if ( this . parent && this . parent . _hasChilds ( ) ) {
if ( items . length ) {
// create a separator
items . push ( {
'type' : 'separator'
} ) ;
}
// create append button (for last child node only)
var childs = node . parent . childs ;
if ( node == childs [ childs . length - 1 ] ) {
items . push ( {
text : 'Append' ,
title : 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)' ,
submenuTitle : 'Select the type of the field to be appended' ,
className : 'jsoneditor-append' ,
click : function ( ) {
node . _onAppend ( '' , '' , 'auto' ) ;
} ,
submenu : [
{
text : 'Auto' ,
className : 'jsoneditor-type-auto' ,
title : titles . auto ,
click : function ( ) {
node . _onAppend ( '' , '' , 'auto' ) ;
}
} ,
{
text : 'Array' ,
className : 'jsoneditor-type-array' ,
title : titles . array ,
click : function ( ) {
node . _onAppend ( '' , [ ] ) ;
}
} ,
{
text : 'Object' ,
className : 'jsoneditor-type-object' ,
title : titles . object ,
click : function ( ) {
node . _onAppend ( '' , { } ) ;
}
} ,
{
text : 'String' ,
className : 'jsoneditor-type-string' ,
title : titles . string ,
click : function ( ) {
node . _onAppend ( '' , '' , 'string' ) ;
}
}
]
} ) ;
}
// create insert button
items . push ( {
text : 'Insert' ,
title : 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)' ,
submenuTitle : 'Select the type of the field to be inserted' ,
className : 'jsoneditor-insert' ,
click : function ( ) {
node . _onInsertBefore ( '' , '' , 'auto' ) ;
} ,
submenu : [
{
text : 'Auto' ,
className : 'jsoneditor-type-auto' ,
title : titles . auto ,
click : function ( ) {
node . _onInsertBefore ( '' , '' , 'auto' ) ;
}
} ,
{
text : 'Array' ,
className : 'jsoneditor-type-array' ,
title : titles . array ,
click : function ( ) {
node . _onInsertBefore ( '' , [ ] ) ;
}
} ,
{
text : 'Object' ,
className : 'jsoneditor-type-object' ,
title : titles . object ,
click : function ( ) {
node . _onInsertBefore ( '' , { } ) ;
}
} ,
{
text : 'String' ,
className : 'jsoneditor-type-string' ,
title : titles . string ,
click : function ( ) {
node . _onInsertBefore ( '' , '' , 'string' ) ;
}
}
]
} ) ;
if ( this . editable . field ) {
// create duplicate button
items . push ( {
text : 'Duplicate' ,
title : 'Duplicate this field (Ctrl+D)' ,
className : 'jsoneditor-duplicate' ,
click : function ( ) {
Node . onDuplicate ( node ) ;
}
} ) ;
// create remove button
items . push ( {
text : 'Remove' ,
title : 'Remove this field (Ctrl+Del)' ,
className : 'jsoneditor-remove' ,
click : function ( ) {
Node . onRemove ( node ) ;
}
} ) ;
}
}
var menu = new ContextMenu ( items , { close : onClose } ) ;
menu . show ( anchor , this . editor . content ) ;
} ;
/ * *
* get the type of a value
* @ param { * } value
* @ return { String } type Can be 'object' , 'array' , 'string' , 'auto'
* @ private
* /
Node . prototype . _getType = function ( value ) {
if ( value instanceof Array ) {
return 'array' ;
}
if ( value instanceof Object ) {
return 'object' ;
}
if ( typeof ( value ) == 'string' && typeof ( this . _stringCast ( value ) ) != 'string' ) {
return 'string' ;
}
return 'auto' ;
} ;
/ * *
* cast contents of a string to the correct type . This can be a string ,
* a number , a boolean , etc
* @ param { String } str
* @ return { * } castedStr
* @ private
* /
Node . prototype . _stringCast = function ( str ) {
var lower = str . toLowerCase ( ) ,
num = Number ( str ) , // will nicely fail with '123ab'
numFloat = parseFloat ( str ) ; // will nicely fail with ' '
if ( str == '' ) {
return '' ;
}
else if ( lower == 'null' ) {
return null ;
}
else if ( lower == 'true' ) {
return true ;
}
else if ( lower == 'false' ) {
return false ;
}
else if ( ! isNaN ( num ) && ! isNaN ( numFloat ) ) {
return num ;
}
else {
return str ;
}
} ;
/ * *
* escape a text , such that it can be displayed safely in an HTML element
* @ param { String } text
* @ return { String } escapedText
* @ private
* /
Node . prototype . _escapeHTML = function ( text ) {
if ( typeof text !== 'string' ) {
return String ( text ) ;
}
else {
var htmlEscaped = String ( text )
. replace ( /&/g , '&' ) // must be replaced first!
. replace ( /</g , '<' )
. replace ( />/g , '>' )
. replace ( / /g , ' ' ) // replace double space with an nbsp and space
. replace ( /^ / , ' ' ) // space at start
. replace ( / $/ , ' ' ) ; // space at end
var json = JSON . stringify ( htmlEscaped ) ;
var html = json . substring ( 1 , json . length - 1 ) ;
if ( this . editor . options . escapeUnicode === true ) {
html = util . escapeUnicodeChars ( html ) ;
}
return html ;
}
} ;
/ * *
* unescape a string .
* @ param { String } escapedText
* @ return { String } text
* @ private
* /
Node . prototype . _unescapeHTML = function ( escapedText ) {
2016-03-21 01:19:13 +08:00
var json = '"' + this . _escapeJSON ( escapedText . trim ( ) ) + '"' ;
2016-01-15 04:26:39 +08:00
var htmlEscaped = util . parse ( json ) ;
return htmlEscaped
. replace ( /</g , '<' )
. replace ( />/g , '>' )
. replace ( / |\u00A0/g , ' ' )
. replace ( /&/g , '&' ) ; // must be replaced last
} ;
/ * *
* escape a text to make it a valid JSON string . The method will :
* - replace unescaped double quotes with '\"'
* - replace unescaped backslash with '\\'
* - replace returns with '\n'
* @ param { String } text
* @ return { String } escapedText
* @ private
* /
Node . prototype . _escapeJSON = function ( text ) {
// TODO: replace with some smart regex (only when a new solution is faster!)
var escaped = '' ;
var i = 0 ;
while ( i < text . length ) {
var c = text . charAt ( i ) ;
if ( c == '\n' ) {
escaped += '\\n' ;
}
else if ( c == '\\' ) {
escaped += c ;
i ++ ;
c = text . charAt ( i ) ;
if ( c === '' || '"\\/bfnrtu' . indexOf ( c ) == - 1 ) {
escaped += '\\' ; // no valid escape character
}
escaped += c ;
}
else if ( c == '"' ) {
escaped += '\\"' ;
}
else {
escaped += c ;
}
i ++ ;
}
return escaped ;
} ;
// TODO: find a nicer solution to resolve this circular dependency between Node and AppendNode
var AppendNode = appendNodeFactory ( Node ) ;
module . exports = Node ;
2016-02-13 18:47:51 +08:00
/***/ } ,
/* 9 */
/***/ function ( module , exports , _ _webpack _require _ _ ) {
var ContextMenu = _ _webpack _require _ _ ( 7 ) ;
/ * *
* Create a select box to be used in the editor menu ' s , which allows to switch mode
2016-03-21 01:19:13 +08:00
* @ param { HTMLElement } container
2016-02-13 18:47:51 +08:00
* @ param { String [ ] } modes Available modes : 'code' , 'form' , 'text' , 'tree' , 'view'
* @ param { String } current Available modes : 'code' , 'form' , 'text' , 'tree' , 'view'
2016-03-21 01:19:13 +08:00
* @ param { function ( mode : string ) } onSwitch Callback invoked on switch
* @ constructor
2016-02-13 18:47:51 +08:00
* /
2016-03-21 01:19:13 +08:00
function ModeSwitcher ( container , modes , current , onSwitch ) {
2016-02-13 18:47:51 +08:00
// available modes
var availableModes = {
code : {
'text' : 'Code' ,
'title' : 'Switch to code highlighter' ,
'click' : function ( ) {
2016-03-21 01:19:13 +08:00
onSwitch ( 'code' )
2016-02-13 18:47:51 +08:00
}
} ,
form : {
'text' : 'Form' ,
'title' : 'Switch to form editor' ,
'click' : function ( ) {
2016-03-21 01:19:13 +08:00
onSwitch ( 'form' ) ;
2016-02-13 18:47:51 +08:00
}
} ,
text : {
'text' : 'Text' ,
'title' : 'Switch to plain text editor' ,
'click' : function ( ) {
2016-03-21 01:19:13 +08:00
onSwitch ( 'text' ) ;
2016-02-13 18:47:51 +08:00
}
} ,
tree : {
'text' : 'Tree' ,
'title' : 'Switch to tree editor' ,
'click' : function ( ) {
2016-03-21 01:19:13 +08:00
onSwitch ( 'tree' ) ;
2016-02-13 18:47:51 +08:00
}
} ,
view : {
'text' : 'View' ,
'title' : 'Switch to tree view' ,
'click' : function ( ) {
2016-03-21 01:19:13 +08:00
onSwitch ( 'view' ) ;
2016-02-13 18:47:51 +08:00
}
}
} ;
// list the selected modes
var items = [ ] ;
for ( var i = 0 ; i < modes . length ; i ++ ) {
var mode = modes [ i ] ;
var item = availableModes [ mode ] ;
if ( ! item ) {
throw new Error ( 'Unknown mode "' + mode + '"' ) ;
}
item . className = 'jsoneditor-type-modes' + ( ( current == mode ) ? ' jsoneditor-selected' : '' ) ;
items . push ( item ) ;
}
// retrieve the title of current mode
var currentMode = availableModes [ current ] ;
if ( ! currentMode ) {
throw new Error ( 'Unknown mode "' + current + '"' ) ;
}
var currentTitle = currentMode . text ;
// create the html element
var box = document . createElement ( 'button' ) ;
box . className = 'jsoneditor-modes jsoneditor-separator' ;
box . innerHTML = currentTitle + ' ▾' ;
box . title = 'Switch editor mode' ;
box . onclick = function ( ) {
var menu = new ContextMenu ( items ) ;
menu . show ( box ) ;
} ;
2016-03-21 01:19:13 +08:00
var frame = document . createElement ( 'div' ) ;
frame . className = 'jsoneditor-modes' ;
frame . style . position = 'relative' ;
frame . appendChild ( box ) ;
container . appendChild ( frame ) ;
2016-02-13 18:47:51 +08:00
2016-03-21 01:19:13 +08:00
this . dom = {
container : container ,
box : box ,
frame : frame
} ;
2016-02-13 18:47:51 +08:00
}
2016-03-21 01:19:13 +08:00
/ * *
* Set focus to switcher
* /
ModeSwitcher . prototype . focus = function ( ) {
this . dom . box . focus ( ) ;
} ;
/ * *
* Destroy the ModeSwitcher , remove from DOM
* /
ModeSwitcher . prototype . destroy = function ( ) {
if ( this . dom && this . dom . frame && this . dom . frame . parentNode ) {
this . dom . frame . parentNode . removeChild ( this . dom . frame ) ;
}
this . dom = null ;
} ;
module . exports = ModeSwitcher ;
2016-02-13 18:47:51 +08:00
2016-01-15 04:26:39 +08:00
/***/ } ,
/* 10 */
/***/ function ( module , exports , _ _webpack _require _ _ ) {
/* Jison generated parser */
var jsonlint = ( function ( ) {
var parser = { trace : function trace ( ) { } ,
yy : { } ,
symbols _ : { "error" : 2 , "JSONString" : 3 , "STRING" : 4 , "JSONNumber" : 5 , "NUMBER" : 6 , "JSONNullLiteral" : 7 , "NULL" : 8 , "JSONBooleanLiteral" : 9 , "TRUE" : 10 , "FALSE" : 11 , "JSONText" : 12 , "JSONValue" : 13 , "EOF" : 14 , "JSONObject" : 15 , "JSONArray" : 16 , "{" : 17 , "}" : 18 , "JSONMemberList" : 19 , "JSONMember" : 20 , ":" : 21 , "," : 22 , "[" : 23 , "]" : 24 , "JSONElementList" : 25 , "$accept" : 0 , "$end" : 1 } ,
terminals _ : { 2 : "error" , 4 : "STRING" , 6 : "NUMBER" , 8 : "NULL" , 10 : "TRUE" , 11 : "FALSE" , 14 : "EOF" , 17 : "{" , 18 : "}" , 21 : ":" , 22 : "," , 23 : "[" , 24 : "]" } ,
productions _ : [ 0 , [ 3 , 1 ] , [ 5 , 1 ] , [ 7 , 1 ] , [ 9 , 1 ] , [ 9 , 1 ] , [ 12 , 2 ] , [ 13 , 1 ] , [ 13 , 1 ] , [ 13 , 1 ] , [ 13 , 1 ] , [ 13 , 1 ] , [ 13 , 1 ] , [ 15 , 2 ] , [ 15 , 3 ] , [ 20 , 3 ] , [ 19 , 1 ] , [ 19 , 3 ] , [ 16 , 2 ] , [ 16 , 3 ] , [ 25 , 1 ] , [ 25 , 3 ] ] ,
performAction : function anonymous ( yytext , yyleng , yylineno , yy , yystate , $$ , _$ ) {
var $0 = $$ . length - 1 ;
switch ( yystate ) {
case 1 : // replace escaped characters with actual character
this . $ = yytext . replace ( /\\(\\|")/g , "$" + "1" )
. replace ( /\\n/g , '\n' )
. replace ( /\\r/g , '\r' )
. replace ( /\\t/g , '\t' )
. replace ( /\\v/g , '\v' )
. replace ( /\\f/g , '\f' )
. replace ( /\\b/g , '\b' ) ;
break ;
case 2 : this . $ = Number ( yytext ) ;
break ;
case 3 : this . $ = null ;
break ;
case 4 : this . $ = true ;
break ;
case 5 : this . $ = false ;
break ;
case 6 : return this . $ = $$ [ $0 - 1 ] ;
break ;
case 13 : this . $ = { } ;
break ;
case 14 : this . $ = $$ [ $0 - 1 ] ;
break ;
case 15 : this . $ = [ $$ [ $0 - 2 ] , $$ [ $0 ] ] ;
break ;
case 16 : this . $ = { } ; this . $ [ $$ [ $0 ] [ 0 ] ] = $$ [ $0 ] [ 1 ] ;
break ;
case 17 : this . $ = $$ [ $0 - 2 ] ; $$ [ $0 - 2 ] [ $$ [ $0 ] [ 0 ] ] = $$ [ $0 ] [ 1 ] ;
break ;
case 18 : this . $ = [ ] ;
break ;
case 19 : this . $ = $$ [ $0 - 1 ] ;
break ;
case 20 : this . $ = [ $$ [ $0 ] ] ;
break ;
case 21 : this . $ = $$ [ $0 - 2 ] ; $$ [ $0 - 2 ] . push ( $$ [ $0 ] ) ;
break ;
}
} ,
table : [ { 3 : 5 , 4 : [ 1 , 12 ] , 5 : 6 , 6 : [ 1 , 13 ] , 7 : 3 , 8 : [ 1 , 9 ] , 9 : 4 , 10 : [ 1 , 10 ] , 11 : [ 1 , 11 ] , 12 : 1 , 13 : 2 , 15 : 7 , 16 : 8 , 17 : [ 1 , 14 ] , 23 : [ 1 , 15 ] } , { 1 : [ 3 ] } , { 14 : [ 1 , 16 ] } , { 14 : [ 2 , 7 ] , 18 : [ 2 , 7 ] , 22 : [ 2 , 7 ] , 24 : [ 2 , 7 ] } , { 14 : [ 2 , 8 ] , 18 : [ 2 , 8 ] , 22 : [ 2 , 8 ] , 24 : [ 2 , 8 ] } , { 14 : [ 2 , 9 ] , 18 : [ 2 , 9 ] , 22 : [ 2 , 9 ] , 24 : [ 2 , 9 ] } , { 14 : [ 2 , 10 ] , 18 : [ 2 , 10 ] , 22 : [ 2 , 10 ] , 24 : [ 2 , 10 ] } , { 14 : [ 2 , 11 ] , 18 : [ 2 , 11 ] , 22 : [ 2 , 11 ] , 24 : [ 2 , 11 ] } , { 14 : [ 2 , 12 ] , 18 : [ 2 , 12 ] , 22 : [ 2 , 12 ] , 24 : [ 2 , 12 ] } , { 14 : [ 2 , 3 ] , 18 : [ 2 , 3 ] , 22 : [ 2 , 3 ] , 24 : [ 2 , 3 ] } , { 14 : [ 2 , 4 ] , 18 : [ 2 , 4 ] , 22 : [ 2 , 4 ] , 24 : [ 2 , 4 ] } , { 14 : [ 2 , 5 ] , 18 : [ 2 , 5 ] , 22 : [ 2 , 5 ] , 24 : [ 2 , 5 ] } , { 14 : [ 2 , 1 ] , 18 : [ 2 , 1 ] , 21 : [ 2 , 1 ] , 22 : [ 2 , 1 ] , 24 : [ 2 , 1 ] } , { 14 : [ 2 , 2 ] , 18 : [ 2 , 2 ] , 22 : [ 2 , 2 ] , 24 : [ 2 , 2 ] } , { 3 : 20 , 4 : [ 1 , 12 ] , 18 : [ 1 , 17 ] , 19 : 18 , 20 : 19 } , { 3 : 5 , 4 : [ 1 , 12 ] , 5 : 6 , 6 : [ 1 , 13 ] , 7 : 3 , 8 : [ 1 , 9 ] , 9 : 4 , 10 : [ 1 , 10 ] , 11 : [ 1 , 11 ] , 13 : 23 , 15 : 7 , 16 : 8 , 17 : [ 1 , 14 ] , 23 : [ 1 , 15 ] , 24 : [ 1 , 21 ] , 25 : 22 } , { 1 : [ 2 , 6 ] } , { 14 : [ 2 , 13 ] , 18 : [ 2 , 13 ] , 22 : [ 2 , 13 ] , 24 : [ 2 , 13 ] } , { 18 : [ 1 , 24 ] , 22 : [ 1 , 25 ] } , { 18 : [ 2 , 16 ] , 22 : [ 2 , 16 ] } , { 21 : [ 1 , 26 ] } , { 14 : [ 2 , 18 ] , 18 : [ 2 , 18 ] , 22 : [ 2 , 18 ] , 24 : [ 2 , 18 ] } , { 22 : [ 1 , 28 ] , 24 : [ 1 , 27 ] } , { 22 : [ 2 , 20 ] , 24 : [ 2 , 20 ] } , { 14 : [ 2 , 14 ] , 18 : [ 2 , 14 ] , 22 : [ 2 , 14 ] , 24 : [ 2 , 14 ] } , { 3 : 20 , 4 : [ 1 , 12 ] , 20 : 29 } , { 3 : 5 , 4 : [ 1 , 12 ] , 5 : 6 , 6 : [ 1 , 13 ] , 7 : 3 , 8 : [ 1 , 9 ] , 9 : 4 , 10 : [ 1 , 10 ] , 11 : [ 1 , 11 ] , 13 : 30 , 15 : 7 , 16 : 8 , 17 : [ 1 , 14 ] , 23 : [ 1 , 15 ] } , { 14 : [ 2 , 19 ] , 18 : [ 2 , 19 ] , 22 : [ 2 , 19 ] , 24 : [ 2 , 19 ] } , { 3 : 5 , 4 : [ 1 , 12 ] , 5 : 6 , 6 : [ 1 , 13 ] , 7 : 3 , 8 : [ 1 , 9 ] , 9 : 4 , 10 : [ 1 , 10 ] , 11 : [ 1 , 11 ] , 13 : 31 , 15 : 7 , 16 : 8 , 17 : [ 1 , 14 ] , 23 : [ 1 , 15 ] } , { 18 : [ 2 , 17 ] , 22 : [ 2 , 17 ] } , { 18 : [ 2 , 15 ] , 22 : [ 2 , 15 ] } , { 22 : [ 2 , 21 ] , 24 : [ 2 , 21 ] } ] ,
defaultActions : { 16 : [ 2 , 6 ] } ,
parseError : function parseError ( str , hash ) {
throw new Error ( str ) ;
} ,
parse : function parse ( input ) {
var self = this ,
stack = [ 0 ] ,
vstack = [ null ] , // semantic value stack
lstack = [ ] , // location stack
table = this . table ,
yytext = '' ,
yylineno = 0 ,
yyleng = 0 ,
recovering = 0 ,
TERROR = 2 ,
EOF = 1 ;
//this.reductionCount = this.shiftCount = 0;
this . lexer . setInput ( input ) ;
this . lexer . yy = this . yy ;
this . yy . lexer = this . lexer ;
if ( typeof this . lexer . yylloc == 'undefined' )
this . lexer . yylloc = { } ;
var yyloc = this . lexer . yylloc ;
lstack . push ( yyloc ) ;
if ( typeof this . yy . parseError === 'function' )
this . parseError = this . yy . parseError ;
function popStack ( n ) {
stack . length = stack . length - 2 * n ;
vstack . length = vstack . length - n ;
lstack . length = lstack . length - n ;
}
function lex ( ) {
var token ;
token = self . lexer . lex ( ) || 1 ; // $end = 1
// if token isn't its numeric value, convert
if ( typeof token !== 'number' ) {
token = self . symbols _ [ token ] || token ;
}
return token ;
}
var symbol , preErrorSymbol , state , action , a , r , yyval = { } , p , len , newState , expected ;
while ( true ) {
// retreive state number from top of stack
state = stack [ stack . length - 1 ] ;
// use default actions if available
if ( this . defaultActions [ state ] ) {
action = this . defaultActions [ state ] ;
} else {
if ( symbol == null )
symbol = lex ( ) ;
// read action for current state and first input
action = table [ state ] && table [ state ] [ symbol ] ;
}
// handle parse error
_handle _error :
if ( typeof action === 'undefined' || ! action . length || ! action [ 0 ] ) {
if ( ! recovering ) {
// Report error
expected = [ ] ;
for ( p in table [ state ] ) if ( this . terminals _ [ p ] && p > 2 ) {
expected . push ( "'" + this . terminals _ [ p ] + "'" ) ;
}
var errStr = '' ;
if ( this . lexer . showPosition ) {
errStr = 'Parse error on line ' + ( yylineno + 1 ) + ":\n" + this . lexer . showPosition ( ) + "\nExpecting " + expected . join ( ', ' ) + ", got '" + this . terminals _ [ symbol ] + "'" ;
} else {
errStr = 'Parse error on line ' + ( yylineno + 1 ) + ": Unexpected " +
( symbol == 1 /*EOF*/ ? "end of input" :
( "'" + ( this . terminals _ [ symbol ] || symbol ) + "'" ) ) ;
}
this . parseError ( errStr ,
{ text : this . lexer . match , token : this . terminals _ [ symbol ] || symbol , line : this . lexer . yylineno , loc : yyloc , expected : expected } ) ;
}
// just recovered from another error
if ( recovering == 3 ) {
if ( symbol == EOF ) {
throw new Error ( errStr || 'Parsing halted.' ) ;
}
// discard current lookahead and grab another
yyleng = this . lexer . yyleng ;
yytext = this . lexer . yytext ;
yylineno = this . lexer . yylineno ;
yyloc = this . lexer . yylloc ;
symbol = lex ( ) ;
}
// try to recover from error
while ( 1 ) {
// check for error recovery rule in this state
if ( ( TERROR . toString ( ) ) in table [ state ] ) {
break ;
}
if ( state == 0 ) {
throw new Error ( errStr || 'Parsing halted.' ) ;
}
popStack ( 1 ) ;
state = stack [ stack . length - 1 ] ;
}
preErrorSymbol = symbol ; // save the lookahead token
symbol = TERROR ; // insert generic error symbol as new lookahead
state = stack [ stack . length - 1 ] ;
action = table [ state ] && table [ state ] [ TERROR ] ;
recovering = 3 ; // allow 3 real symbols to be shifted before reporting a new error
}
// this shouldn't happen, unless resolve defaults are off
if ( action [ 0 ] instanceof Array && action . length > 1 ) {
throw new Error ( 'Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol ) ;
}
switch ( action [ 0 ] ) {
case 1 : // shift
//this.shiftCount++;
stack . push ( symbol ) ;
vstack . push ( this . lexer . yytext ) ;
lstack . push ( this . lexer . yylloc ) ;
stack . push ( action [ 1 ] ) ; // push state
symbol = null ;
if ( ! preErrorSymbol ) { // normal execution/no error
yyleng = this . lexer . yyleng ;
yytext = this . lexer . yytext ;
yylineno = this . lexer . yylineno ;
yyloc = this . lexer . yylloc ;
if ( recovering > 0 )
recovering -- ;
} else { // error just occurred, resume old lookahead f/ before error
symbol = preErrorSymbol ;
preErrorSymbol = null ;
}
break ;
case 2 : // reduce
//this.reductionCount++;
len = this . productions _ [ action [ 1 ] ] [ 1 ] ;
// perform semantic action
yyval . $ = vstack [ vstack . length - len ] ; // default to $$ = $1
// default location, uses first token for firsts, last for lasts
yyval . _$ = {
first _line : lstack [ lstack . length - ( len || 1 ) ] . first _line ,
last _line : lstack [ lstack . length - 1 ] . last _line ,
first _column : lstack [ lstack . length - ( len || 1 ) ] . first _column ,
last _column : lstack [ lstack . length - 1 ] . last _column
} ;
r = this . performAction . call ( yyval , yytext , yyleng , yylineno , this . yy , action [ 1 ] , vstack , lstack ) ;
if ( typeof r !== 'undefined' ) {
return r ;
}
// pop off stack
if ( len ) {
stack = stack . slice ( 0 , - 1 * len * 2 ) ;
vstack = vstack . slice ( 0 , - 1 * len ) ;
lstack = lstack . slice ( 0 , - 1 * len ) ;
}
stack . push ( this . productions _ [ action [ 1 ] ] [ 0 ] ) ; // push nonterminal (reduce)
vstack . push ( yyval . $ ) ;
lstack . push ( yyval . _$ ) ;
// goto new state = table[STATE][NONTERMINAL]
newState = table [ stack [ stack . length - 2 ] ] [ stack [ stack . length - 1 ] ] ;
stack . push ( newState ) ;
break ;
case 3 : // accept
return true ;
}
}
return true ;
} } ;
/* Jison generated lexer */
var lexer = ( function ( ) {
var lexer = ( { EOF : 1 ,
parseError : function parseError ( str , hash ) {
if ( this . yy . parseError ) {
this . yy . parseError ( str , hash ) ;
} else {
throw new Error ( str ) ;
}
} ,
setInput : function ( input ) {
this . _input = input ;
this . _more = this . _less = this . done = false ;
this . yylineno = this . yyleng = 0 ;
this . yytext = this . matched = this . match = '' ;
this . conditionStack = [ 'INITIAL' ] ;
this . yylloc = { first _line : 1 , first _column : 0 , last _line : 1 , last _column : 0 } ;
return this ;
} ,
input : function ( ) {
var ch = this . _input [ 0 ] ;
this . yytext += ch ;
this . yyleng ++ ;
this . match += ch ;
this . matched += ch ;
var lines = ch . match ( /\n/ ) ;
if ( lines ) this . yylineno ++ ;
this . _input = this . _input . slice ( 1 ) ;
return ch ;
} ,
unput : function ( ch ) {
this . _input = ch + this . _input ;
return this ;
} ,
more : function ( ) {
this . _more = true ;
return this ;
} ,
less : function ( n ) {
this . _input = this . match . slice ( n ) + this . _input ;
} ,
pastInput : function ( ) {
var past = this . matched . substr ( 0 , this . matched . length - this . match . length ) ;
return ( past . length > 20 ? '...' : '' ) + past . substr ( - 20 ) . replace ( /\n/g , "" ) ;
} ,
upcomingInput : function ( ) {
var next = this . match ;
if ( next . length < 20 ) {
next += this . _input . substr ( 0 , 20 - next . length ) ;
}
return ( next . substr ( 0 , 20 ) + ( next . length > 20 ? '...' : '' ) ) . replace ( /\n/g , "" ) ;
} ,
showPosition : function ( ) {
var pre = this . pastInput ( ) ;
var c = new Array ( pre . length + 1 ) . join ( "-" ) ;
return pre + this . upcomingInput ( ) + "\n" + c + "^" ;
} ,
next : function ( ) {
if ( this . done ) {
return this . EOF ;
}
if ( ! this . _input ) this . done = true ;
var token ,
match ,
tempMatch ,
index ,
col ,
lines ;
if ( ! this . _more ) {
this . yytext = '' ;
this . match = '' ;
}
var rules = this . _currentRules ( ) ;
for ( var i = 0 ; i < rules . length ; i ++ ) {
tempMatch = this . _input . match ( this . rules [ rules [ i ] ] ) ;
if ( tempMatch && ( ! match || tempMatch [ 0 ] . length > match [ 0 ] . length ) ) {
match = tempMatch ;
index = i ;
if ( ! this . options . flex ) break ;
}
}
if ( match ) {
lines = match [ 0 ] . match ( /\n.*/g ) ;
if ( lines ) this . yylineno += lines . length ;
this . yylloc = { first _line : this . yylloc . last _line ,
last _line : this . yylineno + 1 ,
first _column : this . yylloc . last _column ,
last _column : lines ? lines [ lines . length - 1 ] . length - 1 : this . yylloc . last _column + match [ 0 ] . length }
this . yytext += match [ 0 ] ;
this . match += match [ 0 ] ;
this . yyleng = this . yytext . length ;
this . _more = false ;
this . _input = this . _input . slice ( match [ 0 ] . length ) ;
this . matched += match [ 0 ] ;
token = this . performAction . call ( this , this . yy , this , rules [ index ] , this . conditionStack [ this . conditionStack . length - 1 ] ) ;
if ( this . done && this . _input ) this . done = false ;
if ( token ) return token ;
else return ;
}
if ( this . _input === "" ) {
return this . EOF ;
} else {
this . parseError ( 'Lexical error on line ' + ( this . yylineno + 1 ) + '. Unrecognized text.\n' + this . showPosition ( ) ,
{ text : "" , token : null , line : this . yylineno } ) ;
}
} ,
lex : function lex ( ) {
var r = this . next ( ) ;
if ( typeof r !== 'undefined' ) {
return r ;
} else {
return this . lex ( ) ;
}
} ,
begin : function begin ( condition ) {
this . conditionStack . push ( condition ) ;
} ,
popState : function popState ( ) {
return this . conditionStack . pop ( ) ;
} ,
_currentRules : function _currentRules ( ) {
return this . conditions [ this . conditionStack [ this . conditionStack . length - 1 ] ] . rules ;
} ,
topState : function ( ) {
return this . conditionStack [ this . conditionStack . length - 2 ] ;
} ,
pushState : function begin ( condition ) {
this . begin ( condition ) ;
} } ) ;
lexer . options = { } ;
lexer . performAction = function anonymous ( yy , yy _ , $avoiding _name _collisions , YY _START ) {
var YYSTATE = YY _START
switch ( $avoiding _name _collisions ) {
case 0 : /* skip whitespace */
break ;
case 1 : return 6
break ;
case 2 : yy _ . yytext = yy _ . yytext . substr ( 1 , yy _ . yyleng - 2 ) ; return 4
break ;
case 3 : return 17
break ;
case 4 : return 18
break ;
case 5 : return 23
break ;
case 6 : return 24
break ;
case 7 : return 22
break ;
case 8 : return 21
break ;
case 9 : return 10
break ;
case 10 : return 11
break ;
case 11 : return 8
break ;
case 12 : return 14
break ;
case 13 : return 'INVALID'
break ;
}
} ;
lexer . rules = [ /^(?:\s+)/ , /^(?:(-?([0-9]|[1-9][0-9]+))(\.[0-9]+)?([eE][-+]?[0-9]+)?\b)/ , /^(?:"(?:\\[\\"bfnrt/]|\\u[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*")/ , /^(?:\{)/ , /^(?:\})/ , /^(?:\[)/ , /^(?:\])/ , /^(?:,)/ , /^(?::)/ , /^(?:true\b)/ , /^(?:false\b)/ , /^(?:null\b)/ , /^(?:$)/ , /^(?:.)/ ] ;
lexer . conditions = { "INITIAL" : { "rules" : [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 ] , "inclusive" : true } } ;
;
return lexer ; } ) ( )
parser . lexer = lexer ;
return parser ;
} ) ( ) ;
if ( true ) {
exports . parser = jsonlint ;
exports . parse = jsonlint . parse . bind ( jsonlint ) ;
}
/***/ } ,
/* 11 */
/***/ function ( module , exports , _ _webpack _require _ _ ) {
// load brace
var ace = _ _webpack _require _ _ ( ! ( function webpackMissingModule ( ) { var e = new Error ( "Cannot find module \"brace\"" ) ; e . code = 'MODULE_NOT_FOUND' ; throw e ; } ( ) ) ) ;
// load required ace modules
_ _webpack _require _ _ ( 14 ) ;
_ _webpack _require _ _ ( 15 ) ;
_ _webpack _require _ _ ( 13 ) ;
module . exports = ace ;
/***/ } ,
/* 12 */
/***/ function ( module , exports , _ _webpack _require _ _ ) {
var util = _ _webpack _require _ _ ( 3 ) ;
2016-02-13 18:47:51 +08:00
var ContextMenu = _ _webpack _require _ _ ( 7 ) ;
2016-01-15 04:26:39 +08:00
/ * *
* A factory function to create an AppendNode , which depends on a Node
* @ param { Node } Node
* /
function appendNodeFactory ( Node ) {
/ * *
* @ constructor AppendNode
* @ extends Node
* @ param { TreeEditor } editor
* Create a new AppendNode . This is a special node which is created at the
* end of the list with childs for an object or array
* /
function AppendNode ( editor ) {
/** @type {TreeEditor} */
this . editor = editor ;
this . dom = { } ;
}
AppendNode . prototype = new Node ( ) ;
/ * *
* Return a table row with an append button .
* @ return { Element } dom TR element
* /
AppendNode . prototype . getDom = function ( ) {
// TODO: implement a new solution for the append node
var dom = this . dom ;
if ( dom . tr ) {
return dom . tr ;
}
this . _updateEditability ( ) ;
// a row for the append button
var trAppend = document . createElement ( 'tr' ) ;
trAppend . node = this ;
dom . tr = trAppend ;
// TODO: consistent naming
if ( this . editable . field ) {
// a cell for the dragarea column
dom . tdDrag = document . createElement ( 'td' ) ;
// create context menu
var tdMenu = document . createElement ( 'td' ) ;
dom . tdMenu = tdMenu ;
var menu = document . createElement ( 'button' ) ;
menu . className = 'jsoneditor-contextmenu' ;
menu . title = 'Click to open the actions menu (Ctrl+M)' ;
dom . menu = menu ;
tdMenu . appendChild ( dom . menu ) ;
}
// a cell for the contents (showing text 'empty')
var tdAppend = document . createElement ( 'td' ) ;
var domText = document . createElement ( 'div' ) ;
domText . innerHTML = '(empty)' ;
domText . className = 'jsoneditor-readonly' ;
tdAppend . appendChild ( domText ) ;
dom . td = tdAppend ;
dom . text = domText ;
this . updateDom ( ) ;
return trAppend ;
} ;
/ * *
* Update the HTML dom of the Node
* /
AppendNode . prototype . updateDom = function ( ) {
var dom = this . dom ;
var tdAppend = dom . td ;
if ( tdAppend ) {
tdAppend . style . paddingLeft = ( this . getLevel ( ) * 24 + 26 ) + 'px' ;
// TODO: not so nice hard coded offset
}
var domText = dom . text ;
if ( domText ) {
domText . innerHTML = '(empty ' + this . parent . type + ')' ;
}
// attach or detach the contents of the append node:
// hide when the parent has childs, show when the parent has no childs
var trAppend = dom . tr ;
if ( ! this . isVisible ( ) ) {
if ( dom . tr . firstChild ) {
if ( dom . tdDrag ) {
trAppend . removeChild ( dom . tdDrag ) ;
}
if ( dom . tdMenu ) {
trAppend . removeChild ( dom . tdMenu ) ;
}
trAppend . removeChild ( tdAppend ) ;
}
}
else {
if ( ! dom . tr . firstChild ) {
if ( dom . tdDrag ) {
trAppend . appendChild ( dom . tdDrag ) ;
}
if ( dom . tdMenu ) {
trAppend . appendChild ( dom . tdMenu ) ;
}
trAppend . appendChild ( tdAppend ) ;
}
}
} ;
/ * *
* Check whether the AppendNode is currently visible .
* the AppendNode is visible when its parent has no childs ( i . e . is empty ) .
* @ return { boolean } isVisible
* /
AppendNode . prototype . isVisible = function ( ) {
return ( this . parent . childs . length == 0 ) ;
} ;
/ * *
* Show a contextmenu for this node
* @ param { HTMLElement } anchor The element to attach the menu to .
* @ param { function } [ onClose ] Callback method called when the context menu
* is being closed .
* /
AppendNode . prototype . showContextMenu = function ( anchor , onClose ) {
var node = this ;
var titles = Node . TYPE _TITLES ;
var items = [
// create append button
{
'text' : 'Append' ,
'title' : 'Append a new field with type \'auto\' (Ctrl+Shift+Ins)' ,
'submenuTitle' : 'Select the type of the field to be appended' ,
'className' : 'jsoneditor-insert' ,
'click' : function ( ) {
node . _onAppend ( '' , '' , 'auto' ) ;
} ,
'submenu' : [
{
'text' : 'Auto' ,
'className' : 'jsoneditor-type-auto' ,
'title' : titles . auto ,
'click' : function ( ) {
node . _onAppend ( '' , '' , 'auto' ) ;
}
} ,
{
'text' : 'Array' ,
'className' : 'jsoneditor-type-array' ,
'title' : titles . array ,
'click' : function ( ) {
node . _onAppend ( '' , [ ] ) ;
}
} ,
{
'text' : 'Object' ,
'className' : 'jsoneditor-type-object' ,
'title' : titles . object ,
'click' : function ( ) {
node . _onAppend ( '' , { } ) ;
}
} ,
{
'text' : 'String' ,
'className' : 'jsoneditor-type-string' ,
'title' : titles . string ,
'click' : function ( ) {
node . _onAppend ( '' , '' , 'string' ) ;
}
}
]
}
] ;
var menu = new ContextMenu ( items , { close : onClose } ) ;
menu . show ( anchor , this . editor . content ) ;
} ;
/ * *
* Handle an event . The event is catched centrally by the editor
* @ param { Event } event
* /
AppendNode . prototype . onEvent = function ( event ) {
var type = event . type ;
var target = event . target || event . srcElement ;
var dom = this . dom ;
// highlight the append nodes parent
var menu = dom . menu ;
if ( target == menu ) {
if ( type == 'mouseover' ) {
this . editor . highlighter . highlight ( this . parent ) ;
}
else if ( type == 'mouseout' ) {
this . editor . highlighter . unhighlight ( ) ;
}
}
// context menu events
if ( type == 'click' && target == dom . menu ) {
var highlighter = this . editor . highlighter ;
highlighter . highlight ( this . parent ) ;
highlighter . lock ( ) ;
util . addClassName ( dom . menu , 'jsoneditor-selected' ) ;
this . showContextMenu ( dom . menu , function ( ) {
util . removeClassName ( dom . menu , 'jsoneditor-selected' ) ;
highlighter . unlock ( ) ;
highlighter . unhighlight ( ) ;
} ) ;
}
if ( type == 'keydown' ) {
this . onKeyDown ( event ) ;
}
} ;
return AppendNode ;
}
module . exports = appendNodeFactory ;
/***/ } ,
/* 13 */
/***/ function ( module , exports , _ _webpack _require _ _ ) {
/ * * * * * * B E G I N L I C E N S E B L O C K * * * * *
* Distributed under the BSD license :
*
* Copyright ( c ) 2010 , Ajax . org B . V .
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
* * Redistributions of source code must retain the above copyright
* notice , this list of conditions and the following disclaimer .
* * Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution .
* * Neither the name of Ajax . org B . V . nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL AJAX . ORG B . V . BE LIABLE FOR ANY
* DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
* ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ;
* LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*
* * * * * * END LICENSE BLOCK * * * * * * /
ace . define ( 'ace/theme/jsoneditor' , [ 'require' , 'exports' , 'module' , 'ace/lib/dom' ] , function ( acequire , exports , module ) {
exports . isDark = false ;
exports . cssClass = "ace-jsoneditor" ;
exports . cssText = " . ace - jsoneditor . ace _gutter { \
background : # ebebeb ; \
color : # 333 \
} \
\
. ace - jsoneditor . ace _editor { \
font - family : droid sans mono , consolas , monospace , courier new , courier , sans - serif ; \
line - height : 1.3 ; \
} \
. ace - jsoneditor . ace _print - margin { \
width : 1 px ; \
background : # e8e8e8 \
} \
. ace - jsoneditor . ace _scroller { \
background - color : # FFFFFF \
} \
. ace - jsoneditor . ace _text - layer { \
color : gray \
} \
. ace - jsoneditor . ace _variable { \
color : # 1 a1a1a \
} \
. ace - jsoneditor . ace _cursor { \
border - left : 2 px solid # 000000 \
} \
. ace - jsoneditor . ace _overwrite - cursors . ace _cursor { \
border - left : 0 px ; \
border - bottom : 1 px solid # 000000 \
} \
. ace - jsoneditor . ace _marker - layer . ace _selection { \
background : lightgray \
} \
. ace - jsoneditor . ace _multiselect . ace _selection . ace _start { \
box - shadow : 0 0 3 px 0 px # FFFFFF ; \
border - radius : 2 px \
} \
. ace - jsoneditor . ace _marker - layer . ace _step { \
background : rgb ( 255 , 255 , 0 ) \
} \
. ace - jsoneditor . ace _marker - layer . ace _bracket { \
margin : - 1 px 0 0 - 1 px ; \
border : 1 px solid # BFBFBF \
} \
. ace - jsoneditor . ace _marker - layer . ace _active - line { \
background : # FFFBD1 \
} \
. ace - jsoneditor . ace _gutter - active - line { \
background - color : # dcdcdc \
} \
. ace - jsoneditor . ace _marker - layer . ace _selected - word { \
border : 1 px solid lightgray \
} \
. ace - jsoneditor . ace _invisible { \
color : # BFBFBF \
} \
. ace - jsoneditor . ace _keyword , \
. ace - jsoneditor . ace _meta , \
. ace - jsoneditor . ace _support . ace _constant . ace _property - value { \
color : # AF956F \
} \
. ace - jsoneditor . ace _keyword . ace _operator { \
color : # 484848 \
} \
. ace - jsoneditor . ace _keyword . ace _other . ace _unit { \
color : # 96 DC5F \
} \
. ace - jsoneditor . ace _constant . ace _language { \
color : darkorange \
} \
. ace - jsoneditor . ace _constant . ace _numeric { \
color : red \
} \
. ace - jsoneditor . ace _constant . ace _character . ace _entity { \
color : # BF78CC \
} \
. ace - jsoneditor . ace _invalid { \
color : # FFFFFF ; \
background - color : # FF002A ; \
} \
. ace - jsoneditor . ace _fold { \
background - color : # AF956F ; \
border - color : # 000000 \
} \
. ace - jsoneditor . ace _storage , \
. ace - jsoneditor . ace _support . ace _class , \
. ace - jsoneditor . ace _support . ace _function , \
. ace - jsoneditor . ace _support . ace _other , \
. ace - jsoneditor . ace _support . ace _type { \
color : # C52727 \
} \
. ace - jsoneditor . ace _string { \
color : green \
} \
. ace - jsoneditor . ace _comment { \
color : # BCC8BA \
} \
. ace - jsoneditor . ace _entity . ace _name . ace _tag , \
. ace - jsoneditor . ace _entity . ace _other . ace _attribute - name { \
color : # 606060 \
} \
. ace - jsoneditor . ace _markup . ace _underline { \
text - decoration : underline \
} \
. ace - jsoneditor . ace _indent - guide { \
background : url ( \ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==\" ) right repeat - y \
} " ;
var dom = acequire ( "../lib/dom" ) ;
dom . importCssString ( exports . cssText , exports . cssClass ) ;
} ) ;
/***/ } ,
/* 14 */
/***/ function ( module , exports , _ _webpack _require _ _ ) {
ace . define ( "ace/mode/json_highlight_rules" , [ "require" , "exports" , "module" , "ace/lib/oop" , "ace/mode/text_highlight_rules" ] , function ( acequire , exports , module ) {
"use strict" ;
var oop = acequire ( "../lib/oop" ) ;
var TextHighlightRules = acequire ( "./text_highlight_rules" ) . TextHighlightRules ;
var JsonHighlightRules = function ( ) {
this . $rules = {
"start" : [
{
token : "variable" , // single line
regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]\\s*(?=:)'
} , {
token : "string" , // single line
regex : '"' ,
next : "string"
} , {
token : "constant.numeric" , // hex
regex : "0[xX][0-9a-fA-F]+\\b"
} , {
token : "constant.numeric" , // float
regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
} , {
token : "constant.language.boolean" ,
regex : "(?:true|false)\\b"
} , {
token : "invalid.illegal" , // single quoted strings are not allowed
regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"
} , {
token : "invalid.illegal" , // comments are not allowed
regex : "\\/\\/.*$"
} , {
token : "paren.lparen" ,
regex : "[[({]"
} , {
token : "paren.rparen" ,
regex : "[\\])}]"
} , {
token : "text" ,
regex : "\\s+"
}
] ,
"string" : [
{
token : "constant.language.escape" ,
regex : /\\(?:x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|["\\\/bfnrt])/
} , {
token : "string" ,
regex : '[^"\\\\]+'
} , {
token : "string" ,
regex : '"' ,
next : "start"
} , {
token : "string" ,
regex : "" ,
next : "start"
}
]
} ;
} ;
oop . inherits ( JsonHighlightRules , TextHighlightRules ) ;
exports . JsonHighlightRules = JsonHighlightRules ;
} ) ;
ace . define ( "ace/mode/matching_brace_outdent" , [ "require" , "exports" , "module" , "ace/range" ] , function ( acequire , exports , module ) {
"use strict" ;
var Range = acequire ( "../range" ) . Range ;
var MatchingBraceOutdent = function ( ) { } ;
( function ( ) {
this . checkOutdent = function ( line , input ) {
if ( ! /^\s+$/ . test ( line ) )
return false ;
return /^\s*\}/ . test ( input ) ;
} ;
this . autoOutdent = function ( doc , row ) {
var line = doc . getLine ( row ) ;
var match = line . match ( /^(\s*\})/ ) ;
if ( ! match ) return 0 ;
var column = match [ 1 ] . length ;
var openBracePos = doc . findMatchingBracket ( { row : row , column : column } ) ;
if ( ! openBracePos || openBracePos . row == row ) return 0 ;
var indent = this . $getIndent ( doc . getLine ( openBracePos . row ) ) ;
doc . replace ( new Range ( row , 0 , row , column - 1 ) , indent ) ;
} ;
this . $getIndent = function ( line ) {
return line . match ( /^\s*/ ) [ 0 ] ;
} ;
} ) . call ( MatchingBraceOutdent . prototype ) ;
exports . MatchingBraceOutdent = MatchingBraceOutdent ;
} ) ;
ace . define ( "ace/mode/behaviour/cstyle" , [ "require" , "exports" , "module" , "ace/lib/oop" , "ace/mode/behaviour" , "ace/token_iterator" , "ace/lib/lang" ] , function ( acequire , exports , module ) {
"use strict" ;
var oop = acequire ( "../../lib/oop" ) ;
var Behaviour = acequire ( "../behaviour" ) . Behaviour ;
var TokenIterator = acequire ( "../../token_iterator" ) . TokenIterator ;
var lang = acequire ( "../../lib/lang" ) ;
var SAFE _INSERT _IN _TOKENS =
[ "text" , "paren.rparen" , "punctuation.operator" ] ;
var SAFE _INSERT _BEFORE _TOKENS =
[ "text" , "paren.rparen" , "punctuation.operator" , "comment" ] ;
var context ;
var contextCache = { } ;
var initContext = function ( editor ) {
var id = - 1 ;
if ( editor . multiSelect ) {
id = editor . selection . index ;
if ( contextCache . rangeCount != editor . multiSelect . rangeCount )
contextCache = { rangeCount : editor . multiSelect . rangeCount } ;
}
if ( contextCache [ id ] )
return context = contextCache [ id ] ;
context = contextCache [ id ] = {
autoInsertedBrackets : 0 ,
autoInsertedRow : - 1 ,
autoInsertedLineEnd : "" ,
maybeInsertedBrackets : 0 ,
maybeInsertedRow : - 1 ,
maybeInsertedLineStart : "" ,
maybeInsertedLineEnd : ""
} ;
} ;
var getWrapped = function ( selection , selected , opening , closing ) {
var rowDiff = selection . end . row - selection . start . row ;
return {
text : opening + selected + closing ,
selection : [
0 ,
selection . start . column + 1 ,
rowDiff ,
selection . end . column + ( rowDiff ? 0 : 1 )
]
} ;
} ;
var CstyleBehaviour = function ( ) {
this . add ( "braces" , "insertion" , function ( state , action , editor , session , text ) {
var cursor = editor . getCursorPosition ( ) ;
var line = session . doc . getLine ( cursor . row ) ;
if ( text == '{' ) {
initContext ( editor ) ;
var selection = editor . getSelectionRange ( ) ;
var selected = session . doc . getTextRange ( selection ) ;
if ( selected !== "" && selected !== "{" && editor . getWrapBehavioursEnabled ( ) ) {
return getWrapped ( selection , selected , '{' , '}' ) ;
} else if ( CstyleBehaviour . isSaneInsertion ( editor , session ) ) {
if ( /[\]\}\)]/ . test ( line [ cursor . column ] ) || editor . inMultiSelectMode ) {
CstyleBehaviour . recordAutoInsert ( editor , session , "}" ) ;
return {
text : '{}' ,
selection : [ 1 , 1 ]
} ;
} else {
CstyleBehaviour . recordMaybeInsert ( editor , session , "{" ) ;
return {
text : '{' ,
selection : [ 1 , 1 ]
} ;
}
}
} else if ( text == '}' ) {
initContext ( editor ) ;
var rightChar = line . substring ( cursor . column , cursor . column + 1 ) ;
if ( rightChar == '}' ) {
var matching = session . $findOpeningBracket ( '}' , { column : cursor . column + 1 , row : cursor . row } ) ;
if ( matching !== null && CstyleBehaviour . isAutoInsertedClosing ( cursor , line , text ) ) {
CstyleBehaviour . popAutoInsertedClosing ( ) ;
return {
text : '' ,
selection : [ 1 , 1 ]
} ;
}
}
} else if ( text == "\n" || text == "\r\n" ) {
initContext ( editor ) ;
var closing = "" ;
if ( CstyleBehaviour . isMaybeInsertedClosing ( cursor , line ) ) {
closing = lang . stringRepeat ( "}" , context . maybeInsertedBrackets ) ;
CstyleBehaviour . clearMaybeInsertedClosing ( ) ;
}
var rightChar = line . substring ( cursor . column , cursor . column + 1 ) ;
if ( rightChar === '}' ) {
var openBracePos = session . findMatchingBracket ( { row : cursor . row , column : cursor . column + 1 } , '}' ) ;
if ( ! openBracePos )
return null ;
var next _indent = this . $getIndent ( session . getLine ( openBracePos . row ) ) ;
} else if ( closing ) {
var next _indent = this . $getIndent ( line ) ;
} else {
CstyleBehaviour . clearMaybeInsertedClosing ( ) ;
return ;
}
var indent = next _indent + session . getTabString ( ) ;
return {
text : '\n' + indent + '\n' + next _indent + closing ,
selection : [ 1 , indent . length , 1 , indent . length ]
} ;
} else {
CstyleBehaviour . clearMaybeInsertedClosing ( ) ;
}
} ) ;
this . add ( "braces" , "deletion" , function ( state , action , editor , session , range ) {
var selected = session . doc . getTextRange ( range ) ;
if ( ! range . isMultiLine ( ) && selected == '{' ) {
initContext ( editor ) ;
var line = session . doc . getLine ( range . start . row ) ;
var rightChar = line . substring ( range . end . column , range . end . column + 1 ) ;
if ( rightChar == '}' ) {
range . end . column ++ ;
return range ;
} else {
context . maybeInsertedBrackets -- ;
}
}
} ) ;
this . add ( "parens" , "insertion" , function ( state , action , editor , session , text ) {
if ( text == '(' ) {
initContext ( editor ) ;
var selection = editor . getSelectionRange ( ) ;
var selected = session . doc . getTextRange ( selection ) ;
if ( selected !== "" && editor . getWrapBehavioursEnabled ( ) ) {
return getWrapped ( selection , selected , '(' , ')' ) ;
} else if ( CstyleBehaviour . isSaneInsertion ( editor , session ) ) {
CstyleBehaviour . recordAutoInsert ( editor , session , ")" ) ;
return {
text : '()' ,
selection : [ 1 , 1 ]
} ;
}
} else if ( text == ')' ) {
initContext ( editor ) ;
var cursor = editor . getCursorPosition ( ) ;
var line = session . doc . getLine ( cursor . row ) ;
var rightChar = line . substring ( cursor . column , cursor . column + 1 ) ;
if ( rightChar == ')' ) {
var matching = session . $findOpeningBracket ( ')' , { column : cursor . column + 1 , row : cursor . row } ) ;
if ( matching !== null && CstyleBehaviour . isAutoInsertedClosing ( cursor , line , text ) ) {
CstyleBehaviour . popAutoInsertedClosing ( ) ;
return {
text : '' ,
selection : [ 1 , 1 ]
} ;
}
}
}
} ) ;
this . add ( "parens" , "deletion" , function ( state , action , editor , session , range ) {
var selected = session . doc . getTextRange ( range ) ;
if ( ! range . isMultiLine ( ) && selected == '(' ) {
initContext ( editor ) ;
var line = session . doc . getLine ( range . start . row ) ;
var rightChar = line . substring ( range . start . column + 1 , range . start . column + 2 ) ;
if ( rightChar == ')' ) {
range . end . column ++ ;
return range ;
}
}
} ) ;
this . add ( "brackets" , "insertion" , function ( state , action , editor , session , text ) {
if ( text == '[' ) {
initContext ( editor ) ;
var selection = editor . getSelectionRange ( ) ;
var selected = session . doc . getTextRange ( selection ) ;
if ( selected !== "" && editor . getWrapBehavioursEnabled ( ) ) {
return getWrapped ( selection , selected , '[' , ']' ) ;
} else if ( CstyleBehaviour . isSaneInsertion ( editor , session ) ) {
CstyleBehaviour . recordAutoInsert ( editor , session , "]" ) ;
return {
text : '[]' ,
selection : [ 1 , 1 ]
} ;
}
} else if ( text == ']' ) {
initContext ( editor ) ;
var cursor = editor . getCursorPosition ( ) ;
var line = session . doc . getLine ( cursor . row ) ;
var rightChar = line . substring ( cursor . column , cursor . column + 1 ) ;
if ( rightChar == ']' ) {
var matching = session . $findOpeningBracket ( ']' , { column : cursor . column + 1 , row : cursor . row } ) ;
if ( matching !== null && CstyleBehaviour . isAutoInsertedClosing ( cursor , line , text ) ) {
CstyleBehaviour . popAutoInsertedClosing ( ) ;
return {
text : '' ,
selection : [ 1 , 1 ]
} ;
}
}
}
} ) ;
this . add ( "brackets" , "deletion" , function ( state , action , editor , session , range ) {
var selected = session . doc . getTextRange ( range ) ;
if ( ! range . isMultiLine ( ) && selected == '[' ) {
initContext ( editor ) ;
var line = session . doc . getLine ( range . start . row ) ;
var rightChar = line . substring ( range . start . column + 1 , range . start . column + 2 ) ;
if ( rightChar == ']' ) {
range . end . column ++ ;
return range ;
}
}
} ) ;
this . add ( "string_dquotes" , "insertion" , function ( state , action , editor , session , text ) {
if ( text == '"' || text == "'" ) {
initContext ( editor ) ;
var quote = text ;
var selection = editor . getSelectionRange ( ) ;
var selected = session . doc . getTextRange ( selection ) ;
if ( selected !== "" && selected !== "'" && selected != '"' && editor . getWrapBehavioursEnabled ( ) ) {
return getWrapped ( selection , selected , quote , quote ) ;
} else if ( ! selected ) {
var cursor = editor . getCursorPosition ( ) ;
var line = session . doc . getLine ( cursor . row ) ;
var leftChar = line . substring ( cursor . column - 1 , cursor . column ) ;
var rightChar = line . substring ( cursor . column , cursor . column + 1 ) ;
var token = session . getTokenAt ( cursor . row , cursor . column ) ;
var rightToken = session . getTokenAt ( cursor . row , cursor . column + 1 ) ;
if ( leftChar == "\\" && token && /escape/ . test ( token . type ) )
return null ;
var stringBefore = token && /string|escape/ . test ( token . type ) ;
var stringAfter = ! rightToken || /string|escape/ . test ( rightToken . type ) ;
var pair ;
if ( rightChar == quote ) {
pair = stringBefore !== stringAfter ;
} else {
if ( stringBefore && ! stringAfter )
return null ; // wrap string with different quote
if ( stringBefore && stringAfter )
return null ; // do not pair quotes inside strings
var wordRe = session . $mode . tokenRe ;
wordRe . lastIndex = 0 ;
var isWordBefore = wordRe . test ( leftChar ) ;
wordRe . lastIndex = 0 ;
var isWordAfter = wordRe . test ( leftChar ) ;
if ( isWordBefore || isWordAfter )
return null ; // before or after alphanumeric
if ( rightChar && ! /[\s;,.})\]\\]/ . test ( rightChar ) )
return null ; // there is rightChar and it isn't closing
pair = true ;
}
return {
text : pair ? quote + quote : "" ,
selection : [ 1 , 1 ]
} ;
}
}
} ) ;
this . add ( "string_dquotes" , "deletion" , function ( state , action , editor , session , range ) {
var selected = session . doc . getTextRange ( range ) ;
if ( ! range . isMultiLine ( ) && ( selected == '"' || selected == "'" ) ) {
initContext ( editor ) ;
var line = session . doc . getLine ( range . start . row ) ;
var rightChar = line . substring ( range . start . column + 1 , range . start . column + 2 ) ;
if ( rightChar == selected ) {
range . end . column ++ ;
return range ;
}
}
} ) ;
} ;
CstyleBehaviour . isSaneInsertion = function ( editor , session ) {
var cursor = editor . getCursorPosition ( ) ;
var iterator = new TokenIterator ( session , cursor . row , cursor . column ) ;
if ( ! this . $matchTokenType ( iterator . getCurrentToken ( ) || "text" , SAFE _INSERT _IN _TOKENS ) ) {
var iterator2 = new TokenIterator ( session , cursor . row , cursor . column + 1 ) ;
if ( ! this . $matchTokenType ( iterator2 . getCurrentToken ( ) || "text" , SAFE _INSERT _IN _TOKENS ) )
return false ;
}
iterator . stepForward ( ) ;
return iterator . getCurrentTokenRow ( ) !== cursor . row ||
this . $matchTokenType ( iterator . getCurrentToken ( ) || "text" , SAFE _INSERT _BEFORE _TOKENS ) ;
} ;
CstyleBehaviour . $matchTokenType = function ( token , types ) {
return types . indexOf ( token . type || token ) > - 1 ;
} ;
CstyleBehaviour . recordAutoInsert = function ( editor , session , bracket ) {
var cursor = editor . getCursorPosition ( ) ;
var line = session . doc . getLine ( cursor . row ) ;
if ( ! this . isAutoInsertedClosing ( cursor , line , context . autoInsertedLineEnd [ 0 ] ) )
context . autoInsertedBrackets = 0 ;
context . autoInsertedRow = cursor . row ;
context . autoInsertedLineEnd = bracket + line . substr ( cursor . column ) ;
context . autoInsertedBrackets ++ ;
} ;
CstyleBehaviour . recordMaybeInsert = function ( editor , session , bracket ) {
var cursor = editor . getCursorPosition ( ) ;
var line = session . doc . getLine ( cursor . row ) ;
if ( ! this . isMaybeInsertedClosing ( cursor , line ) )
context . maybeInsertedBrackets = 0 ;
context . maybeInsertedRow = cursor . row ;
context . maybeInsertedLineStart = line . substr ( 0 , cursor . column ) + bracket ;
context . maybeInsertedLineEnd = line . substr ( cursor . column ) ;
context . maybeInsertedBrackets ++ ;
} ;
CstyleBehaviour . isAutoInsertedClosing = function ( cursor , line , bracket ) {
return context . autoInsertedBrackets > 0 &&
cursor . row === context . autoInsertedRow &&
bracket === context . autoInsertedLineEnd [ 0 ] &&
line . substr ( cursor . column ) === context . autoInsertedLineEnd ;
} ;
CstyleBehaviour . isMaybeInsertedClosing = function ( cursor , line ) {
return context . maybeInsertedBrackets > 0 &&
cursor . row === context . maybeInsertedRow &&
line . substr ( cursor . column ) === context . maybeInsertedLineEnd &&
line . substr ( 0 , cursor . column ) == context . maybeInsertedLineStart ;
} ;
CstyleBehaviour . popAutoInsertedClosing = function ( ) {
context . autoInsertedLineEnd = context . autoInsertedLineEnd . substr ( 1 ) ;
context . autoInsertedBrackets -- ;
} ;
CstyleBehaviour . clearMaybeInsertedClosing = function ( ) {
if ( context ) {
context . maybeInsertedBrackets = 0 ;
context . maybeInsertedRow = - 1 ;
}
} ;
oop . inherits ( CstyleBehaviour , Behaviour ) ;
exports . CstyleBehaviour = CstyleBehaviour ;
} ) ;
ace . define ( "ace/mode/folding/cstyle" , [ "require" , "exports" , "module" , "ace/lib/oop" , "ace/range" , "ace/mode/folding/fold_mode" ] , function ( acequire , exports , module ) {
"use strict" ;
var oop = acequire ( "../../lib/oop" ) ;
var Range = acequire ( "../../range" ) . Range ;
var BaseFoldMode = acequire ( "./fold_mode" ) . FoldMode ;
var FoldMode = exports . FoldMode = function ( commentRegex ) {
if ( commentRegex ) {
this . foldingStartMarker = new RegExp (
this . foldingStartMarker . source . replace ( /\|[^|]*?$/ , "|" + commentRegex . start )
) ;
this . foldingStopMarker = new RegExp (
this . foldingStopMarker . source . replace ( /\|[^|]*?$/ , "|" + commentRegex . end )
) ;
}
} ;
oop . inherits ( FoldMode , BaseFoldMode ) ;
( function ( ) {
this . foldingStartMarker = /(\{|\[)[^\}\]]*$|^\s*(\/\*)/ ;
this . foldingStopMarker = /^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/ ;
this . singleLineBlockCommentRe = /^\s*(\/\*).*\*\/\s*$/ ;
this . tripleStarBlockCommentRe = /^\s*(\/\*\*\*).*\*\/\s*$/ ;
this . startRegionRe = /^\s*(\/\*|\/\/)#?region\b/ ;
this . _getFoldWidgetBase = this . getFoldWidget ;
this . getFoldWidget = function ( session , foldStyle , row ) {
var line = session . getLine ( row ) ;
if ( this . singleLineBlockCommentRe . test ( line ) ) {
if ( ! this . startRegionRe . test ( line ) && ! this . tripleStarBlockCommentRe . test ( line ) )
return "" ;
}
var fw = this . _getFoldWidgetBase ( session , foldStyle , row ) ;
if ( ! fw && this . startRegionRe . test ( line ) )
return "start" ; // lineCommentRegionStart
return fw ;
} ;
this . getFoldWidgetRange = function ( session , foldStyle , row , forceMultiline ) {
var line = session . getLine ( row ) ;
if ( this . startRegionRe . test ( line ) )
return this . getCommentRegionBlock ( session , line , row ) ;
var match = line . match ( this . foldingStartMarker ) ;
if ( match ) {
var i = match . index ;
if ( match [ 1 ] )
return this . openingBracketBlock ( session , match [ 1 ] , row , i ) ;
var range = session . getCommentFoldRange ( row , i + match [ 0 ] . length , 1 ) ;
if ( range && ! range . isMultiLine ( ) ) {
if ( forceMultiline ) {
range = this . getSectionRange ( session , row ) ;
} else if ( foldStyle != "all" )
range = null ;
}
return range ;
}
if ( foldStyle === "markbegin" )
return ;
var match = line . match ( this . foldingStopMarker ) ;
if ( match ) {
var i = match . index + match [ 0 ] . length ;
if ( match [ 1 ] )
return this . closingBracketBlock ( session , match [ 1 ] , row , i ) ;
return session . getCommentFoldRange ( row , i , - 1 ) ;
}
} ;
this . getSectionRange = function ( session , row ) {
var line = session . getLine ( row ) ;
var startIndent = line . search ( /\S/ ) ;
var startRow = row ;
var startColumn = line . length ;
row = row + 1 ;
var endRow = row ;
var maxRow = session . getLength ( ) ;
while ( ++ row < maxRow ) {
line = session . getLine ( row ) ;
var indent = line . search ( /\S/ ) ;
if ( indent === - 1 )
continue ;
if ( startIndent > indent )
break ;
var subRange = this . getFoldWidgetRange ( session , "all" , row ) ;
if ( subRange ) {
if ( subRange . start . row <= startRow ) {
break ;
} else if ( subRange . isMultiLine ( ) ) {
row = subRange . end . row ;
} else if ( startIndent == indent ) {
break ;
}
}
endRow = row ;
}
return new Range ( startRow , startColumn , endRow , session . getLine ( endRow ) . length ) ;
} ;
this . getCommentRegionBlock = function ( session , line , row ) {
var startColumn = line . search ( /\s*$/ ) ;
var maxRow = session . getLength ( ) ;
var startRow = row ;
var re = /^\s*(?:\/\*|\/\/|--)#?(end)?region\b/ ;
var depth = 1 ;
while ( ++ row < maxRow ) {
line = session . getLine ( row ) ;
var m = re . exec ( line ) ;
if ( ! m ) continue ;
if ( m [ 1 ] ) depth -- ;
else depth ++ ;
if ( ! depth ) break ;
}
var endRow = row ;
if ( endRow > startRow ) {
return new Range ( startRow , startColumn , endRow , line . length ) ;
}
} ;
} ) . call ( FoldMode . prototype ) ;
} ) ;
ace . define ( "ace/mode/json" , [ "require" , "exports" , "module" , "ace/lib/oop" , "ace/mode/text" , "ace/mode/json_highlight_rules" , "ace/mode/matching_brace_outdent" , "ace/mode/behaviour/cstyle" , "ace/mode/folding/cstyle" , "ace/worker/worker_client" ] , function ( acequire , exports , module ) {
"use strict" ;
var oop = acequire ( "../lib/oop" ) ;
var TextMode = acequire ( "./text" ) . Mode ;
var HighlightRules = acequire ( "./json_highlight_rules" ) . JsonHighlightRules ;
var MatchingBraceOutdent = acequire ( "./matching_brace_outdent" ) . MatchingBraceOutdent ;
var CstyleBehaviour = acequire ( "./behaviour/cstyle" ) . CstyleBehaviour ;
var CStyleFoldMode = acequire ( "./folding/cstyle" ) . FoldMode ;
var WorkerClient = acequire ( "../worker/worker_client" ) . WorkerClient ;
var Mode = function ( ) {
this . HighlightRules = HighlightRules ;
this . $outdent = new MatchingBraceOutdent ( ) ;
this . $behaviour = new CstyleBehaviour ( ) ;
this . foldingRules = new CStyleFoldMode ( ) ;
} ;
oop . inherits ( Mode , TextMode ) ;
( function ( ) {
this . getNextLineIndent = function ( state , line , tab ) {
var indent = this . $getIndent ( line ) ;
if ( state == "start" ) {
var match = line . match ( /^.*[\{\(\[]\s*$/ ) ;
if ( match ) {
indent += tab ;
}
}
return indent ;
} ;
this . checkOutdent = function ( state , line , input ) {
return this . $outdent . checkOutdent ( line , input ) ;
} ;
this . autoOutdent = function ( state , doc , row ) {
this . $outdent . autoOutdent ( doc , row ) ;
} ;
this . createWorker = function ( session ) {
var worker = new WorkerClient ( [ "ace" ] , _ _webpack _require _ _ ( 16 ) , "JsonWorker" ) ;
worker . attachToDocument ( session . getDocument ( ) ) ;
worker . on ( "annotate" , function ( e ) {
session . setAnnotations ( e . data ) ;
} ) ;
worker . on ( "terminate" , function ( ) {
session . clearAnnotations ( ) ;
} ) ;
return worker ;
} ;
this . $id = "ace/mode/json" ;
} ) . call ( Mode . prototype ) ;
exports . Mode = Mode ;
} ) ;
/***/ } ,
/* 15 */
/***/ function ( module , exports , _ _webpack _require _ _ ) {
ace . define ( "ace/ext/searchbox" , [ "require" , "exports" , "module" , "ace/lib/dom" , "ace/lib/lang" , "ace/lib/event" , "ace/keyboard/hash_handler" , "ace/lib/keys" ] , function ( acequire , exports , module ) {
"use strict" ;
var dom = acequire ( "../lib/dom" ) ;
var lang = acequire ( "../lib/lang" ) ;
var event = acequire ( "../lib/event" ) ;
var searchboxCss = " \
. ace _search { \
background - color : # ddd ; \
border : 1 px solid # cbcbcb ; \
border - top : 0 none ; \
max - width : 325 px ; \
overflow : hidden ; \
margin : 0 ; \
padding : 4 px ; \
padding - right : 6 px ; \
padding - bottom : 0 ; \
position : absolute ; \
top : 0 px ; \
z - index : 99 ; \
white - space : normal ; \
} \
. ace _search . left { \
border - left : 0 none ; \
border - radius : 0 px 0 px 5 px 0 px ; \
left : 0 ; \
} \
. ace _search . right { \
border - radius : 0 px 0 px 0 px 5 px ; \
border - right : 0 none ; \
right : 0 ; \
} \
. ace _search _form , . ace _replace _form { \
border - radius : 3 px ; \
border : 1 px solid # cbcbcb ; \
float : left ; \
margin - bottom : 4 px ; \
overflow : hidden ; \
} \
. ace _search _form . ace _nomatch { \
outline : 1 px solid red ; \
} \
. ace _search _field { \
background - color : white ; \
border - right : 1 px solid # cbcbcb ; \
border : 0 none ; \
- webkit - box - sizing : border - box ; \
- moz - box - sizing : border - box ; \
box - sizing : border - box ; \
float : left ; \
height : 22 px ; \
outline : 0 ; \
padding : 0 7 px ; \
width : 214 px ; \
margin : 0 ; \
} \
. ace _searchbtn , \
. ace _replacebtn { \
background : # fff ; \
border : 0 none ; \
border - left : 1 px solid # dcdcdc ; \
cursor : pointer ; \
float : left ; \
height : 22 px ; \
margin : 0 ; \
position : relative ; \
} \
. ace _searchbtn : last - child , \
. ace _replacebtn : last - child { \
border - top - right - radius : 3 px ; \
border - bottom - right - radius : 3 px ; \
} \
. ace _searchbtn : disabled { \
background : none ; \
cursor : default ; \
} \
. ace _searchbtn { \
background - position : 50 % 50 % ; \
background - repeat : no - repeat ; \
width : 27 px ; \
} \
. ace _searchbtn . prev { \
background - image : url ( data : image / png ; base64 , iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAYAAAB4ka1VAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADFJREFUeNpiSU1NZUAC / 6E0 I0yACYskCpsJiySKIiY0SUZk40FyTEgCjGgKwTRAgAEAQJUIPCE + qfkAAAAASUVORK5CYII = ) ; \
} \
. ace _searchbtn . next { \
background - image : url ( data : image / png ; base64 , iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAYAAAB4ka1VAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNpiTE1NZQCC / 0 DMyIAKwGJMUAYDEo3M / s + EpvM / mkKwCQxYjIeLMaELoLMBAgwAU7UJObTKsvAAAAAASUVORK5CYII = ) ; \
} \
. ace _searchbtn _close { \
background : url ( data : image / png ; base64 , iVBORw0KGgoAAAANSUhEUgAAAA4AAAAcCAYAAABRVo5BAAAAZ0lEQVR42u2SUQrAMAhDvazn8OjZBilCkYVVxiis8H4CT0VrAJb4WHT3C5xU2a2IQZXJjiQIRMdkEoJ5Q2yMqpfDIo + XY4k6h + YXOyKqTIj5REaxloNAd0xiKmAtsTHqW8sR2W5f7gCu5nWFUpVjZwAAAABJRU5ErkJggg == ) no - repeat 50 % 0 ; \
border - radius : 50 % ; \
border : 0 none ; \
color : # 656565 ; \
cursor : pointer ; \
float : right ; \
font : 16 px / 16 px Arial ; \
height : 14 px ; \
margin : 5 px 1 px 9 px 5 px ; \
padding : 0 ; \
text - align : center ; \
width : 14 px ; \
} \
. ace _searchbtn _close : hover { \
background - color : # 656565 ; \
background - position : 50 % 100 % ; \
color : white ; \
} \
. ace _replacebtn . prev { \
width : 54 px \
} \
. ace _replacebtn . next { \
width : 27 px \
} \
. ace _button { \
margin - left : 2 px ; \
cursor : pointer ; \
- webkit - user - select : none ; \
- moz - user - select : none ; \
- o - user - select : none ; \
- ms - user - select : none ; \
user - select : none ; \
overflow : hidden ; \
opacity : 0.7 ; \
border : 1 px solid rgba ( 100 , 100 , 100 , 0.23 ) ; \
padding : 1 px ; \
- moz - box - sizing : border - box ; \
box - sizing : border - box ; \
color : black ; \
} \
. ace _button : hover { \
background - color : # eee ; \
opacity : 1 ; \
} \
. ace _button : active { \
background - color : # ddd ; \
} \
. ace _button . checked { \
border - color : # 3399 ff ; \
opacity : 1 ; \
} \
. ace _search _options { \
margin - bottom : 3 px ; \
text - align : right ; \
- webkit - user - select : none ; \
- moz - user - select : none ; \
- o - user - select : none ; \
- ms - user - select : none ; \
user - select : none ; \
} " ;
var HashHandler = acequire ( "../keyboard/hash_handler" ) . HashHandler ;
var keyUtil = acequire ( "../lib/keys" ) ;
dom . importCssString ( searchboxCss , "ace_searchbox" ) ;
var html = ' < div class = "ace_search right" > \
< button type = "button" action = "hide" class = "ace_searchbtn_close" > < / b u t t o n > \
< div class = "ace_search_form" > \
< input class = "ace_search_field" placeholder = "Search for" spellcheck = "false" > < / i n p u t > \
< button type = "button" action = "findNext" class = "ace_searchbtn next" > < / b u t t o n > \
< button type = "button" action = "findPrev" class = "ace_searchbtn prev" > < / b u t t o n > \
< button type = "button" action = "findAll" class = "ace_searchbtn" title = "Alt-Enter" > All < / b u t t o n > \
< / d i v > \
< div class = "ace_replace_form" > \
< input class = "ace_search_field" placeholder = "Replace with" spellcheck = "false" > < / i n p u t > \
< button type = "button" action = "replaceAndFindNext" class = "ace_replacebtn" > Replace < / b u t t o n > \
< button type = "button" action = "replaceAll" class = "ace_replacebtn" > All < / b u t t o n > \
< / d i v > \
< div class = "ace_search_options" > \
< span action = "toggleRegexpMode" class = "ace_button" title = "RegExp Search" > . * < / s p a n > \
< span action = "toggleCaseSensitive" class = "ace_button" title = "CaseSensitive Search" > Aa < / s p a n > \
< span action = "toggleWholeWords" class = "ace_button" title = "Whole Word Search" > \ \ b < / s p a n > \
< / d i v > \
< /div>'.replace(/ > \ s + / g , " > " ) ;
var SearchBox = function ( editor , range , showReplaceForm ) {
var div = dom . createElement ( "div" ) ;
div . innerHTML = html ;
this . element = div . firstChild ;
this . $init ( ) ;
this . setEditor ( editor ) ;
} ;
( function ( ) {
this . setEditor = function ( editor ) {
editor . searchBox = this ;
editor . container . appendChild ( this . element ) ;
this . editor = editor ;
} ;
this . $initElements = function ( sb ) {
this . searchBox = sb . querySelector ( ".ace_search_form" ) ;
this . replaceBox = sb . querySelector ( ".ace_replace_form" ) ;
this . searchOptions = sb . querySelector ( ".ace_search_options" ) ;
this . regExpOption = sb . querySelector ( "[action=toggleRegexpMode]" ) ;
this . caseSensitiveOption = sb . querySelector ( "[action=toggleCaseSensitive]" ) ;
this . wholeWordOption = sb . querySelector ( "[action=toggleWholeWords]" ) ;
this . searchInput = this . searchBox . querySelector ( ".ace_search_field" ) ;
this . replaceInput = this . replaceBox . querySelector ( ".ace_search_field" ) ;
} ;
this . $init = function ( ) {
var sb = this . element ;
this . $initElements ( sb ) ;
var _this = this ;
event . addListener ( sb , "mousedown" , function ( e ) {
setTimeout ( function ( ) {
_this . activeInput . focus ( ) ;
} , 0 ) ;
event . stopPropagation ( e ) ;
} ) ;
event . addListener ( sb , "click" , function ( e ) {
var t = e . target || e . srcElement ;
var action = t . getAttribute ( "action" ) ;
if ( action && _this [ action ] )
_this [ action ] ( ) ;
else if ( _this . $searchBarKb . commands [ action ] )
_this . $searchBarKb . commands [ action ] . exec ( _this ) ;
event . stopPropagation ( e ) ;
} ) ;
event . addCommandKeyListener ( sb , function ( e , hashId , keyCode ) {
var keyString = keyUtil . keyCodeToString ( keyCode ) ;
var command = _this . $searchBarKb . findKeyCommand ( hashId , keyString ) ;
if ( command && command . exec ) {
command . exec ( _this ) ;
event . stopEvent ( e ) ;
}
} ) ;
this . $onChange = lang . delayedCall ( function ( ) {
_this . find ( false , false ) ;
} ) ;
event . addListener ( this . searchInput , "input" , function ( ) {
_this . $onChange . schedule ( 20 ) ;
} ) ;
event . addListener ( this . searchInput , "focus" , function ( ) {
_this . activeInput = _this . searchInput ;
_this . searchInput . value && _this . highlight ( ) ;
} ) ;
event . addListener ( this . replaceInput , "focus" , function ( ) {
_this . activeInput = _this . replaceInput ;
_this . searchInput . value && _this . highlight ( ) ;
} ) ;
} ;
this . $closeSearchBarKb = new HashHandler ( [ {
bindKey : "Esc" ,
name : "closeSearchBar" ,
exec : function ( editor ) {
editor . searchBox . hide ( ) ;
}
} ] ) ;
this . $searchBarKb = new HashHandler ( ) ;
this . $searchBarKb . bindKeys ( {
"Ctrl-f|Command-f" : function ( sb ) {
var isReplace = sb . isReplace = ! sb . isReplace ;
sb . replaceBox . style . display = isReplace ? "" : "none" ;
sb . searchInput . focus ( ) ;
} ,
"Ctrl-H|Command-Option-F" : function ( sb ) {
sb . replaceBox . style . display = "" ;
sb . replaceInput . focus ( ) ;
} ,
"Ctrl-G|Command-G" : function ( sb ) {
sb . findNext ( ) ;
} ,
"Ctrl-Shift-G|Command-Shift-G" : function ( sb ) {
sb . findPrev ( ) ;
} ,
"esc" : function ( sb ) {
setTimeout ( function ( ) { sb . hide ( ) ; } ) ;
} ,
"Return" : function ( sb ) {
if ( sb . activeInput == sb . replaceInput )
sb . replace ( ) ;
sb . findNext ( ) ;
} ,
"Shift-Return" : function ( sb ) {
if ( sb . activeInput == sb . replaceInput )
sb . replace ( ) ;
sb . findPrev ( ) ;
} ,
"Alt-Return" : function ( sb ) {
if ( sb . activeInput == sb . replaceInput )
sb . replaceAll ( ) ;
sb . findAll ( ) ;
} ,
"Tab" : function ( sb ) {
( sb . activeInput == sb . replaceInput ? sb . searchInput : sb . replaceInput ) . focus ( ) ;
}
} ) ;
this . $searchBarKb . addCommands ( [ {
name : "toggleRegexpMode" ,
bindKey : { win : "Alt-R|Alt-/" , mac : "Ctrl-Alt-R|Ctrl-Alt-/" } ,
exec : function ( sb ) {
sb . regExpOption . checked = ! sb . regExpOption . checked ;
sb . $syncOptions ( ) ;
}
} , {
name : "toggleCaseSensitive" ,
bindKey : { win : "Alt-C|Alt-I" , mac : "Ctrl-Alt-R|Ctrl-Alt-I" } ,
exec : function ( sb ) {
sb . caseSensitiveOption . checked = ! sb . caseSensitiveOption . checked ;
sb . $syncOptions ( ) ;
}
} , {
name : "toggleWholeWords" ,
bindKey : { win : "Alt-B|Alt-W" , mac : "Ctrl-Alt-B|Ctrl-Alt-W" } ,
exec : function ( sb ) {
sb . wholeWordOption . checked = ! sb . wholeWordOption . checked ;
sb . $syncOptions ( ) ;
}
} ] ) ;
this . $syncOptions = function ( ) {
dom . setCssClass ( this . regExpOption , "checked" , this . regExpOption . checked ) ;
dom . setCssClass ( this . wholeWordOption , "checked" , this . wholeWordOption . checked ) ;
dom . setCssClass ( this . caseSensitiveOption , "checked" , this . caseSensitiveOption . checked ) ;
this . find ( false , false ) ;
} ;
this . highlight = function ( re ) {
this . editor . session . highlight ( re || this . editor . $search . $options . re ) ;
this . editor . renderer . updateBackMarkers ( )
} ;
this . find = function ( skipCurrent , backwards , preventScroll ) {
var range = this . editor . find ( this . searchInput . value , {
skipCurrent : skipCurrent ,
backwards : backwards ,
wrap : true ,
regExp : this . regExpOption . checked ,
caseSensitive : this . caseSensitiveOption . checked ,
wholeWord : this . wholeWordOption . checked ,
preventScroll : preventScroll
} ) ;
var noMatch = ! range && this . searchInput . value ;
dom . setCssClass ( this . searchBox , "ace_nomatch" , noMatch ) ;
this . editor . _emit ( "findSearchBox" , { match : ! noMatch } ) ;
this . highlight ( ) ;
} ;
this . findNext = function ( ) {
this . find ( true , false ) ;
} ;
this . findPrev = function ( ) {
this . find ( true , true ) ;
} ;
this . findAll = function ( ) {
var range = this . editor . findAll ( this . searchInput . value , {
regExp : this . regExpOption . checked ,
caseSensitive : this . caseSensitiveOption . checked ,
wholeWord : this . wholeWordOption . checked
} ) ;
var noMatch = ! range && this . searchInput . value ;
dom . setCssClass ( this . searchBox , "ace_nomatch" , noMatch ) ;
this . editor . _emit ( "findSearchBox" , { match : ! noMatch } ) ;
this . highlight ( ) ;
this . hide ( ) ;
} ;
this . replace = function ( ) {
if ( ! this . editor . getReadOnly ( ) )
this . editor . replace ( this . replaceInput . value ) ;
} ;
this . replaceAndFindNext = function ( ) {
if ( ! this . editor . getReadOnly ( ) ) {
this . editor . replace ( this . replaceInput . value ) ;
this . findNext ( )
}
} ;
this . replaceAll = function ( ) {
if ( ! this . editor . getReadOnly ( ) )
this . editor . replaceAll ( this . replaceInput . value ) ;
} ;
this . hide = function ( ) {
this . element . style . display = "none" ;
this . editor . keyBinding . removeKeyboardHandler ( this . $closeSearchBarKb ) ;
this . editor . focus ( ) ;
} ;
this . show = function ( value , isReplace ) {
this . element . style . display = "" ;
this . replaceBox . style . display = isReplace ? "" : "none" ;
this . isReplace = isReplace ;
if ( value )
this . searchInput . value = value ;
this . find ( false , false , true ) ;
this . searchInput . focus ( ) ;
this . searchInput . select ( ) ;
this . editor . keyBinding . addKeyboardHandler ( this . $closeSearchBarKb ) ;
} ;
this . isFocused = function ( ) {
var el = document . activeElement ;
return el == this . searchInput || el == this . replaceInput ;
}
} ) . call ( SearchBox . prototype ) ;
exports . SearchBox = SearchBox ;
exports . Search = function ( editor , isReplace ) {
var sb = editor . searchBox || new SearchBox ( editor ) ;
sb . show ( editor . session . getTextRange ( ) , isReplace ) ;
} ;
} ) ;
( function ( ) {
ace . acequire ( [ "ace/ext/searchbox" ] , function ( ) { } ) ;
} ) ( ) ;
/***/ } ,
/* 16 */
/***/ function ( module , exports , _ _webpack _require _ _ ) {
module . exports . id = 'ace/mode/json_worker' ;
module . exports . src = "\"no use strict\";(function(window){function resolveModuleId(id,paths){for(var testPath=id,tail=\"\";testPath;){var alias=paths[testPath];if(\"string\"==typeof alias)return alias+tail;if(alias)return alias.location.replace(/\\/*$/,\"/\")+(tail||alias.main||alias.name);if(alias===!1)return\"\";var i=testPath.lastIndexOf(\"/\");if(-1===i)break;tail=testPath.substr(i)+tail,testPath=testPath.slice(0,i)}return id}if(!(void 0!==window.window&&window.document||window.acequire&&window.define)){window.console||(window.console=function(){var msgs=Array.prototype.slice.call(arguments,0);postMessage({type:\"log\",data:msgs})},window.console.error=window.console.warn=window.console.log=window.console.trace=window.console),window.window=window,window.ace=window,window.onerror=function(message,file,line,col,err){postMessage({type:\"error\",data:{message:message,data:err.data,file:file,line:line,col:col,stack:err.stack}})},window.normalizeModule=function(parentId,moduleName){if(-1!==moduleName.indexOf(\"!\")){var chunks=moduleName.split(\"!\");return window.normalizeModule(parentId,chunks[0])+\"!\"+window.normalizeModule(parentId,chunks[1])}if(\".\"==moduleName.charAt(0)){var base=parentId.split(\"/\").slice(0,-1).join(\"/\");for(moduleName=(base?base+\"/\":\"\")+moduleName;-1!==moduleName.indexOf(\".\")&&previous!=moduleName;){var previous=moduleName;moduleName=moduleName.replace(/^\\.\\//,\"\").replace(/\\/\\.\\//,\"/\").replace(/[^\\/]+\\/\\.\\.\\//,\"\")}}return moduleName},window.acequire=function acequire(parentId,id){if(id||(id=parentId,parentId=null),!id.charAt)throw Error(\"worker.js acequire() accepts only (parentId, id) as arguments\");id=window.normalizeModule(parentId,id);var module=window.acequire.modules[id];if(module)return module.initialized||(module.initialized=!0,module.exports=module.factory().exports),module.exports;if(!window.acequire.tlns)return console.log(\"unable to load \"+id);var path=resolveModuleId(id,window.acequire.tlns);return\".js\"!=path.slice(-3)&&(path+=\".js\"),window.acequire.id=id,window.acequire.modules[id]={},importScripts(path),window.acequire(parentId,id)},window.acequire.modules={},window.acequire.tlns={},window.define=function(id,deps,factory){if(2==arguments.length?(factory=deps,\"string\"!=typeof id&&(deps=id,id=window.acequire.id)):1==arguments.length&&(factory=id,deps=[],id=window.acequire.id),\"function\"!=typeof factory)return window.acequire.modules[id]={exports:factory,initialized:!0},void 0;deps.length||(deps=[\"require\",\"exports\",\"module\"]);var req=function(childId){return window.acequire(id,childId)};window.acequire.modules[id]={exports:{},factory:function(){var module=this,returnExports=factory.apply(this,deps.map(function(dep){switch(dep){case\"require\":return req;case\"exports\":return module.exports;case\"module\":return module;default:return req(dep)}}));return returnExports&&(module.exports=returnExports),module}}},window.define.amd={},acequire.tlns={},window.initBaseUrls=function(topLevelNamespaces){for(var i in topLevelNamespaces)acequire.tlns[i]=topLevelNamespaces[i]},window.initSender=function(){var EventEmitter=window.acequire(\"ace/lib/event_emitter\").EventEmitter,oop=window.acequire(\"ace/lib/oop\"),Sender=function(){};return function(){oop.implement(this,EventEmitter),this.callback=function(data,callbackId){postMessage({type:\"call\",id:callbackId,data:data})},this.emit=function(name,data){postMessage({type:\"event\",name:name,data:data})}}.call(Sender.prototype),new Sender};var main=window.main=null,sender=window.sender=null;window.onmessage=function(e){var msg=e.data;if(msg.event&&sender)sender._signal(msg.event,msg.data);else if(msg.command)if(main[msg.command])main[msg.command].apply(main,msg.args);else{if(!window[msg.command])throw Error(\"Unknown command:\"+msg.command);window[msg.command].apply(window,msg.args)}else if(msg.init){window.initBaseUrls(msg.tlns),acequire(\"ace/lib/es5-shim\" ) , sender = window . sender = window . initSender ( ) ; var clazz = acequire ( msg . module ) [ msg . classname ] ; main = window . main = new clazz ( sender ) } } } } ) ( this ) , ace . define
/***/ }
/******/ ] )
} ) ;
;