var silkMutations = xb.core.object.extend( Array, { ctor: function( node ) { this.node = node; this.domNodes = []; this.mtime = null; }, isEditable: function( domNode ) { return ( ( typeof( domNode.getAttribute ) === "function" ) && ( domNode.getAttribute( "data-simply-field" ) !== null || domNode.getAttribute( "data-simply-list" ) !== null || domNode.getAttribute( "data-simply-list-item" ) !== null ) ); }, log: function( mutations ) { this.mtime = ( new Date() ).getTime(); /* FIXME: bouw een data structuur */ for ( var i = 0, il = mutations.length; i < il; i++ ) { var m = mutations[ i ]; //if ( this.isEditable( m.target ) ) { var p = this.domNodes.indexOf( m.target ); if ( p < 0 ) { p = this.domNodes.length; this.domNodes[ p ] = m.target; } this.push( { domNode: p, mutation: m } ); //} } return this; } } ); var silkMutationObserver = xb.core.object.extend( { defaultProperties: { childList: true, attributes: true, characterData: true, subtree: true, attributeFilter: [ "src", "href" ] }, defaultMutationDelay: 250, defaultInterval: 125, ctor: function( node, properties ) { var self = this; this.running = false; this.node = node; this.properties = this.defaultProperties; if ( typeof( properties ) === "object" ) { this.properties = xb.core.object.prototype.copy.call( this.defaultProperties ); var keys = Object.getOwnPropertyNames( properties ); for ( var i = 0, il = keys.length; i < il; i++ ) { this.properties[ keys[ i ] ] = properties[ keys[ i ] ]; } } this.mutations = null; this.mutationDelay = this.defaultMutationDelay; this.interval = this.defaultInterval; this.observer = new MutationObserver( function( mutations ) { self.onMutation( mutations ); } ); this.__watchMethod = function( evt ) { self.watchMethod( false, evt ); }; this.__intervalListener = null; }, onMutation: function( mutations ) { this.mutations.log( mutations ); }, watchMethod: function( flush, evt ) { if ( this.mutations.mtime !== null ) { var dt = ( new Date() ).getTime() - this.mutations.mtime; if ( dt >= this.mutationDelay ) { if ( typeof( this.node.onDOMChange ) === "function" ) { var node = this.node; var mutations = this.mutations; if ( !flush ) this.stop(); //console.log( "DT", dt, this.mutations ); if ( flush ) { console.warn( "Flushing", mutations ); } this.node.onDOMChange.call( node, mutations, dt, this ); if ( !flush ) this.start(); } this.mutations = new silkMutations( this.node ); } } }, start: function() { if ( !this.running ) { // console.log( "Start watching" ); this.running = true; var self = this; var timestamp = ( new Date() ).getTime(); this.mutations = new silkMutations( this.node ); this.observer.observe( this.node.domNode, this.properties ); this.__intervalListener = window.setInterval( this.__watchMethod, this.interval ); } return this; }, stop: function() { if ( this.running ) { // console.log( "Stop watching" ); this.observer.disconnect(); window.clearInterval( this.__intervalListener ); this.__intervalListener = null; //this.watchMethod( true, null ); this.running = false; } return this; } } ); var silkNode = xb.core.object.extend( { factory: function( domNode ) { var mount = domNode.getAttribute( "data-simply-data" ); var list = domNode.getAttribute( "data-simply-list" ); var listItem = domNode.getAttribute( "data-simply-list-item" ); var field = domNode.getAttribute( "data-simply-field" ); if ( mount !== null ) { return new silkNodeMount( domNode, list ); } else if ( listItem !== null ) { return new silkNodeStruct( domNode ); } else if ( field !== null ) { return silkNodeField( domNode, field ); } else if ( list !== null ) { return new silkNodeList( domNode, list ); } else { console.error( "Node", domNode, "is not a `simply` node" ); return new silkNodeStruct( domNode ); } }, ctor: function( domNode ) { this.domNode = domNode; this.parent = null; }, setParent: function( node ) { this.parent = node; return this; }, getParentDOMNodeIn: function( stack ) { //debugger var parentElm = this.domNode.parentElement; while ( parentElm !== null ) { for ( var i = stack.length - 1; i >= 0; i-- ) { if ( stack[ i ].domNode === parentElm ) { return i; } } parentElm = parentElm.parentElement; } return null; }, parseDOM: function() { var selector = "[data-simply-list], [data-simply-field], [data-simply-list-item]"; var stack = []; if ( this instanceof silkNodeStruct ) { stack = [ new silkNodeStruct( this.domNode ) ]; } else { stack = [ new silkNodeList( this.domNode ) ]; } $( this.domNode ).find( selector ).each( function() { var node = silkNode( this ); var sp = node.getParentDOMNodeIn( stack ); if ( sp < ( stack.length - 1 ) ) { //console.log( "resetting stack to", sp ); stack = stack.slice( 0, sp + 1 ); } //console.log( "adding", node, "to", sp ); stack[ sp ].add( node ); if ( ( node instanceof silkNodeList ) || ( node instanceof silkNodeStruct ) ) { stack.push( node ); } } ); return stack[ 0 ]; } } ); var silkNodeStruct = xb.core.object.extend( silkNode, { ctor: function( domNode ) { silkNode.prototype.ctor.call( this, domNode ); this.childNodes = {}; this.template = domNode.getAttribute( "data-simply-template" ); }, add: function( node ) { this.childNodes[ node.name ] = node.setParent( this ); return this; }, get: function( offset ) { var node = this.childNodes[ offset ]; if ( node instanceof silkNode ) { return node; } return null; }, getValue: function( options ) { var result = {}; for ( var name in this.childNodes ) { result[ name ] = this.childNodes[ name ].getValue( options ); } var tmpl = this.domNode.getAttribute( "data-simply-template" ); if ( tmpl ) { result[ "data-simply-template" ] = tmpl; } return result; }, setValue: function( data ) { var result = {}; for ( var name in this.childNodes ) { result[ name ] = this.childNodes[ name ].setValue( data[ name ] ); } return result; }, each: function( func ) { var result = []; if ( typeof( func ) === "function" ) { for ( var name in this.childNodes ) { var r = func.call( this.childNodes[ name ], name ); if ( typeof( r ) !== "undefined" && r !== null ) { result.push( r ); } } } return result; }, } ); var silkNodeField = xb.core.object.extend( silkNode, { factory: function( domNode, name ) { var mapping = { img: [ "src" ], a: [ "href" ] }; var attribs = mapping[ domNode.tagName.toLowerCase() ]; if ( typeof( attribs ) !== "undefined" ) { return new silkNodeFieldAttribs( domNode, name, attribs ); } return new silkNodeField( domNode, name ); }, ctor: function( domNode, name ) { silkNode.prototype.ctor.call( this, domNode ); this.name = name; }, getValue: function( options ) { if ( options === true ) { return this.domNode.innerText; } return this.domNode.innerHTML; }, setValue: function( data ) { var data = ( ( typeof( data ) !== "undefined" ) ? data : "" ); this.domNode.innerHTML = data; return this.domNode.innerHTML; } } ); var silkNodeFieldAttribs = xb.core.object.extend( silkNodeField, { ctor: function( domNode, name, attribs ) { silkNodeField.prototype.ctor.call( this, domNode, name ); this.attribs = ( attribs instanceof Array ) ? attribs : []; }, getValue: function( options ) { var result = {}; for ( var i = 0, il = this.attribs.length; i < il; i++ ) { var name = this.attribs[ i ]; result[ name ] = this.domNode.getAttribute( name ); } result.innerHTML = silkNodeField.prototype.getValue.call( this, options ); return result; }, setValue: function( data ) { silkNodeField.prototype.setValue.call( this, data.innerHTML ); for ( var i = 0, il = this.attribs.length; i < il; i++ ) { var name = this.attribs[ i ]; this.domNode.setAttribute( name, data[ name ] ); } return this.getValue(); } } ); var silkNodeList = xb.core.object.extend( silkNodeField, { ctor: function( domNode, name ) { silkNodeField.prototype.ctor.call( this, domNode, name ); this.childNodes = []; }, add: function( node ) { this.childNodes.push( node.setParent( this ) ); return this; }, getValue: function( options ) { var result = []; for ( var i = 0, il = this.childNodes.length; i < il; i++ ) { result[ i ] = this.childNodes[ i ].getValue( options ); } return result; }, setValue: function( data ) { var result = []; console.warn( "FIXME: silkNodeList.setValue> hier moet wat intelligenter mee omgegaan worden; lijst lengtes kunnen bijvoorbeeld niet kloppen." ); for ( var i = 0, il = this.childNodes.length; i < il; i++ ) { result[ i ] = this.childNodes[ i ].setValue( data[ i ] ); } return result; }, each: function( func ) { var result = []; if ( typeof( func ) === "function" ) { for ( var i = 0, il = this.childNodes.length; i < il; i++ ) { var r = func.call( this.childNodes[ i ], i ); if ( typeof( r ) !== "undefined" && r !== null ) { result.push( r ); } } } return result; }, find: function( cmp, context, offset ) { var result = []; var f = function( context, offset ) { //console.log( "=> checking", offset ); if ( cmp.call( this, context, offset ) === true ) { result.push( this ); } if ( typeof( this.each ) === "function" ) { this.each( f ); } }; this.each( f ); return result; } } ); var silkMountDOMNode = xb.core.object.extend( silkNodeList, { factory: function( mount, domNode ) { return new silkMountDOMNode( mount, domNode ); }, ctor: function( mount, domNode ) { silkNodeList.prototype.ctor.call( this, domNode, mount.name ); this.mount = mount; this.childNodes = this.parseDOM().childNodes; this.mObserver = silkMutationObserver( this ); this.mObserver.start(); console.log( "observing", this.domNode ); }, onDOMChange: function( mutations, dt, mObserver ) { var before = new silkNodeList( this.domNode, this.mount.name ); before.childNodes = this.childNodes; this.childNodes = this.parseDOM().childNodes; /* console.log( "onDOMChange", this.mount.name ); console.log( "> before", before.getValue() ); console.log( "> after", this.getValue() ); */ this.mount.onDOMChange( this, before ); }, display: function( data, reset ) { return this.render( data, reset ); }, render: function( data, reset ) { var self = this; var copy = []; for ( var i = 0, il = data.length; i < il; i++ ) { copy[ copy.length ] = xb.core.object.prototype.copy.call( data[ i ], true ); } var flipObserver = this.mObserver.running; if ( flipObserver ) { this.mObserver.stop(); } if ( reset === true ) { while ( this.domNode.firstChild ) { this.domNode.removeChild( this.domNode.firstChild ); } } var s = Date.now(); //console.log( "start", s ); editor.data.list.applyTemplates( this.domNode, copy ); if ( typeof( hope ) !== "undefined" && document.body.getAttribute( "data-simply-edit" ) ) { editor.editmode.makeEditable( this.domNode ); } var e = Date.now(); //console.log( "end", e ); //console.log( ( e - s ) / 1000 ); this.childNodes = this.parseDOM().childNodes; if ( flipObserver ) { this.mObserver.start(); } } } ); var silkNodeMount = xb.core.object.extend( silkNodeList, { DOMNodeHandler: silkMountDOMNode, ctor: function( domNode, name, resource ) { silkNode.prototype.ctor.call( this, null ); this.name = name; this.nodes = []; this.setResource( resource ); this.addDOMNode( domNode ); }, addDOMNode: function( domNode ) { for ( var i = 0, il = this.nodes.length; i < il; i++ ) { var node = this.nodes[ i ]; if ( node.domNode === domNode ) { return this; } } var node = this.DOMNodeHandler( this, domNode ); // new silkMountDOMNode( this, domNode ); this.nodes[ this.nodes.length ] = node; return this; }, getDOMNodes: function() { var result = []; for ( var i = 0, il = this.nodes.length; i < il; i++ ) { result[ result.length ] = this.nodes[ i ].domNode; } return result; }, onLoad: function() { console.warn( "silkNodeMount::onLoad", this.name, "still a stub" ); }, setResource: function( resource ) { this.resource = ( ( resource instanceof silkResource ) ? resource : null ); return this; }, getResource: function( name ) { return this.resource.getResource( name ); }, getMount: function( name, resourceName ) { if ( arguments.length > 0 ) { return this.resource.getMount.apply( this.resource, arguments ); } return this; }, display: function() { var data = this.resource[ data ]; if ( ! ( data instanceof Array ) ) { data = []; } this.render( data, true ); }, render: function( data, reset ) { if ( !( data instanceof Array ) ) { data = []; } for ( var i = 0, il = this.nodes.length; i < il; i++ ) { this.nodes[ i ].display( data, reset ); } return this; }, onDOMChange: function( handler ) { console.warn( "Mount(", this.name, ")::onDOMChange => no custom implementation", handler ); } } ); var silkResource = xb.core.object.extend( { handlers: { }, ctor: function( config ) { this.config = config; this.mounts = {}; this.data = null; this.init(); }, init: function() { }, save: function() { console.warn( "No save handler for", this.config.name ); return false; }, getResource: function( name ) { return this.config.dispatcher.getResource( name ); }, getMount: function( name, resourceName ) { if ( arguments.length > 1 ) { return this.config.dispatcher.getMount.apply( this.config.dispatcher, arguments ); } var mount = this.mounts[ name ]; return ( ( typeof( mount ) !== "undefined" ) ? mount : null ); }, add: function( name, domNode ) { var handler = this.handlers[ name ]; if ( typeof( handler ) !== "function" ) { handler = this.handlers[ "*" ]; } if ( typeof( handler ) === "function" ) { if ( typeof( this.mounts[ name ] ) === "undefined" ) { this.mounts[ name ] = new handler( domNode, name, this ); } else { this.mounts[ name ].addDOMNode( domNode ); } return this.mounts[ name ]; } return null; }, load: function( data ) { this.data = data; console.log( "load data", this.mounts ); for ( var name in this.mounts ) { console.log( "Resource loaded, notifying 'mount'", name ); this.mounts[ name ].onLoad(); } return this; }, loadURL: function( url, params, callback ) { var params = ( ( typeof( params ) === "string" ) ? params : "" ); var http = new XMLHttpRequest(); http.open( "post", url, true ); http.setRequestHeader( "Content-type", "application/x-www-form-urlencoded" ); http.onreadystatechange = function() { if ( http.readyState == 4 ) { if ( http.status == 200 ) { var result = null; try { result = JSON.parse( http.responseText ); } catch( e ) { console.error( "Could not parse", url ); }; if ( typeof( callback ) === "function" ) { return callback.call( null, result ); } return result; } } }; return http.send( params ); }, putURL: function( url, params, callback ) { var params = ( ( typeof( params ) === "string" ) ? params : "" ); var http = new XMLHttpRequest(); http.open( "put", url, true ); // http.setRequestHeader( "Content-type", "application/x-www-form-urlencoded" ); http.onreadystatechange = function() { if ( http.readyState == 4 ) { var result = null; try { result = JSON.parse( http.responseText ); } catch( e ) {}; if ( typeof( callback ) === "function" ) { return callback.call( null, http.status, result ); } return result; } }; return http.send( params ); } } ); var silkDispatcher = xb.core.object.extend( { ctor: function( mappings ) { this.mappings = ( ( typeof( mappings ) !== "undefined" ) ? mappings : {} ); this.resources = {}; for ( var name in this.mappings ) { console.log( "addDataSource", name ); this.resources[ name ] = null; editor.addDataSource( name, this ); } }, getResource: function( name ) { var config = this.resources[ name ]; if ( typeof( config ) !== "undefined" && config !== null ) { return config.resource; } return null; }, getMount: function( name, resourceName ) { var resource = this.getResource( resourceName ); if ( resource instanceof silkResource ) { return resource.getMount( name ); } return null; }, load: function( elm ) { var resource = elm.getAttribute( "data-simply-data" ); var name = elm.getAttribute( "data-simply-list" ); console.log( "silkDispatcher::load(", resource, name, ")" ); var config = this.resources[ resource ]; if ( config === null ) { config = { dispatcher: this, elm: elm, resource: null, name: resource }; config.resource = this.mappings[ config.name ]( config ); } this.resources[ resource ] = config; config.resource.add( name, elm ); }, delayedSave: function( counter ) { var self = this; if ( counter === 0 || counter < this.saveCounter ) { window.setTimeout( function() { self.delayedSave( this.saveCounter ); }, 10 ); } else { console.warn( "Going to save @", this.saveCounter, "!" ); for( var r in this.resources ) { var resource = this.getResource( r ); if ( resource !== null ) { resource.save(); } } this.saveCounter = 0; } }, get: function( elm ) { this.stash = [null]; var resourceName = elm.getAttribute( "data-simply-data" ); var mountName = elm.getAttribute( "data-simply-list" ); // console.log( "silkDispatcher::get(", resource, name, ")", this.stash ); var resource = this.getResource( resourceName ); if ( resource !== null && typeof( resource.get ) === "function" ) { return resource.get( mountName ); } return null; }, save: function( info ) { console.log( "silkDispatcher::save(", info, ")" ); if ( typeof( this.saveCounter ) === "undefined" || this.saveCounter === 0 ) { this.saveCounter = 0; this.delayedSave( this.saveCounter ); } this.saveCounter += 1; return true; } } );