12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757 |
- /*
- The MIT License (MIT)
- Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
- Virtual World Framework Apache 2.0 license (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
- */
- /// vwf/model/javascript.js is a placeholder for the JavaScript object interface to the
- /// simulation.
- ///
- /// @module vwf/model/javascript
- /// @requires vwf/model
- /// @requires vwf/kernel/utility
- /// @requires vwf/utility
- /// @requires vwf/configuration
- import { Utility } from "/core/vwf/utility/utility.js";
- import { KUtility } from "/core/vwf/utility/kutility.js";
- import { Fabric } from "/core/vwf/fabric.js";
- class VWFJavaScript extends Fabric {
- constructor(module) {
- console.log("JavaScript constructor");
- super(module, "Model")
- }
- factory() {
- let _self_ = this;
- return this.load(
- this.module,
- {
- // This is a placeholder for providing a natural integration between simulation and the
- // browser's JavaScript environment.
- //
- // Within the JavaScript environment, component instances appear as JavaScript objects.
- //
- // - Properties appear in the "properties" field. Each property contains a getter and
- // setter callback to notify the object of property manipulation.
- // - Methods appear in "methods".
- // - Events appear in "events".
- // - "parent" refers to the parent node and "children" is an array of the child nodes.
- //
- // - Node prototypes use the JavaScript prototype chain.
- // - Properties, methods, events, and children may be referenced directly on the node or
- // within their respective collections by name when there is no conflict with another
- // attribute.
- // - Properties support getters and setters that invoke a handler that may influence the
- // property access.
- // == Module Definition ====================================================================
- // -- initialize ---------------------------------------------------------------------------
- initialize: function() {
- this.nodes = {}; // maps id => new type()
- this.protoNode = undefined; // this.nodes[kutility.protoNodeURI] once it exists
- this.creatingNode( undefined, 0 ); // global root // TODO: to allow vwf.children( 0 ), vwf.getNode( 0 ); is this the best way, or should the kernel createNode( global-root-id /* 0 */ )?
- },
- // == Model API ============================================================================
- // -- creatingNode -------------------------------------------------------------------------
- creatingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
- childSource, childType, childIndex, childName, callback /* ( ready ) */ ) {
- var self = this;
- // Get the prototype node.
- var prototype = this.nodes[childExtendsID] || Object.prototype;
- // Get the behavior nodes.
- var behaviors = ( childImplementsIDs || [] ).map( function( childImplementsID ) {
- return self.nodes[childImplementsID];
- } );
- // For each behavior, create a proxy for this node to the behavior and attach it above
- // the prototype, or above the most recently-attached behavior.
- behaviors.forEach( function( behavior ) {
- prototype = VWFJavaScript.proxiedBehavior.call( self, prototype, behavior );
- } );
- // Create the node. Its prototype is the most recently-attached behavior, or the
- // specific prototype if no behaviors are attached.
- var node = this.nodes[childID] = Object.create( prototype );
- if ( childID === VWFJavaScript.kutility.protoNodeURI ) {
- this.protoNode = node;
- }
- Object.defineProperty( node, "private", {
- value: {} // for bookkeeping, not visible to scripts on the node // TODO: well, ideally not visible; hide this better ("_private", "vwf_private", ?)
- } );
- Object.defineProperty( node, "id", {
- value: childID,
- enumerable: true,
- } );
- Object.defineProperty( node, "uri", { // "this" is node
- get: function() {
- return self.kernel.uri( this.id );
- },
- enumerable: true,
- } );
- node.name = childName;
- node.parent = undefined;
- Object.defineProperty( node, "parent_", { // "this" is node in get/set
- get: function() {
- return this.parent;
- },
- set: function( value ) {
- var childIndex;
- if ( this.parent ) {
- var oldParent = this.parent;
- self.kernel.removeChild( this.parent.id, this.id );
- childIndex = oldParent.children.indexOf( this );
- if ( childIndex != -1 )
- oldParent.children.splice( childIndex, 1 );
- }
- self.kernel.addChild( value.id, this.id, this.name );
- this.parent = value;
- childIndex = this.parent.children.indexOf( this );
- if ( childIndex == -1 )
- this.parent.children.push( this );
- },
- } );
- node.source = childSource;
- node.type = childType;
- Object.defineProperty( node, "logger", {
- value: this.logger.for( "#" + ( childName || childIndex || childID ), node ),
- enumerable: true,
- } );
- // Properties.
- node.properties = Object.create( prototype.properties || Object.prototype, {
- node: { value: node } // for node.properties accessors (non-enumerable) // TODO: hide this better
- } );
- Object.defineProperty( node.properties, "create", {
- value: function( name, value, get, set ) { // "this" is node.properties
- return self.kernel.createProperty( this.node.id, name, value, get, set );
- }
- } );
- // Attach the property meta events to `node.properties.{created,initialized,deleted}`.
- VWFJavaScript.createEventAccessor.call( this, node.properties, "created", "properties" );
- VWFJavaScript.createEventAccessor.call( this, node.properties, "initialized", "properties" );
- VWFJavaScript.createEventAccessor.call( this, node.properties, "deleted", "properties" );
- node.private.getters = Object.create( prototype.private ?
- prototype.private.getters : Object.prototype
- );
- node.private.setters = Object.create( prototype.private ?
- prototype.private.setters : Object.prototype
- );
- // Methods.
- node.methods = Object.create( prototype.methods || Object.prototype, {
- node: { value: node } // for node.methods accessors (non-enumerable) // TODO: hide this better
- } );
- Object.defineProperty( node.methods, "create", {
- value: function( name, parameters, body ) { // "this" is node.methods // TODO: also accept create( name, body )
- return self.kernel.createMethod( this.node.id, name, parameters, body );
- }
- } );
- // Attach the method meta events to `node.methods.{created,deleted}`.
- VWFJavaScript.createEventAccessor.call( this, node.methods, "created", "methods" );
- VWFJavaScript.createEventAccessor.call( this, node.methods, "deleted", "methods" );
- node.private.bodies = Object.create( prototype.private ?
- prototype.private.bodies : Object.prototype
- );
- // Events.
- node.events = Object.create( prototype.events || Object.prototype, {
- node: { value: node }, // for node.events accessors (non-enumerable) // TODO: hide this better
- } );
- // TODO: these only need to be on the base node's events object
- Object.defineProperty( node.events, "create", {
- value: function( name, parameters ) { // "this" is node.events
- return self.kernel.createEvent( this.node.id, name, parameters );
- }
- } );
- // Attach the event meta events to `node.events.{created,deleted}`.
- VWFJavaScript.createEventAccessor.call( this, node.events, "created", "events" );
- VWFJavaScript.createEventAccessor.call( this, node.events, "deleted", "events" );
- // Provide helper functions to create the directives for adding, removing and flushing
- // event handlers.
- // Add: `node.events.*eventName* =
- // node.events.add( handler [, phases ] [, context ] [, callback( listenerID ) ] )`
- Object.defineProperty( node.events, "add", {
- value: function( handler, phases, context, callback /* listenerID */ ) {
- // Interpret `add( handler [, phases|context ], callback )` as
- // `add( handler, phases|context|undefined, undefined, callback )`.
- if ( typeof context === "function" || context instanceof Function ) {
- callback = context;
- context = undefined;
- } else if ( typeof phases === "function" || phases instanceof Function ) {
- callback = phases;
- context = phases = undefined;
- }
- // Interpret `add( handler, context, ... )` as `add( handler, undefined, context, ... )`.
- if ( VWFJavaScript.valueIsNode.call( self, phases ) ) {
- context = phases;
- phases = undefined;
- }
- return { add: true, handler: handler, phases: phases, context: context, callback: callback };
- }
- } );
- // Remove: `node.events.*eventName* = node.events.remove( listenerID|handler )`
- Object.defineProperty( node.events, "remove", {
- value: function( listenerID ) {
- // For 0.6.23 and earlier, listeners were removed using a direct reference to
- // the handler. For 0.6.24 and later a `listenerID` is used. Accept a function
- // for compatability with components written for 0.6.23 or earlier. The event
- // setter will translate the function to an id.
- if ( typeof listenerID === "function" || listenerID instanceof Function ) {
- return { remove: true, handler: listenerID };
- } else {
- return { remove: true, id: listenerID };
- }
- }
- } );
- // Flush: `node.events.*eventName* = node.events.flush( context )`
- Object.defineProperty( node.events, "flush", {
- value: function( context ) {
- return { flush: true, context: context };
- }
- } );
- node.private.listeners = {}; // not delegated to the prototype as with getters, setters, and bodies; findListeners() filters recursion
- // Children.
- node.children = []; // TODO: connect children's prototype like properties, methods and events do? how, since it's an array? drop the ordered list support and just use an object?
- Object.defineProperty( node.children, "node", {
- value: node // for node.children accessors (non-enumerable) // TODO: hide this better
- } );
- Object.defineProperty( node.children, "create", {
- value: function( name, component, callback /* ( child ) */ ) { // "this" is node.children
- // Interpret `node.children.create( name, callback )` as
- // `node.children.create( name, undefined, callback )`.
- if ( typeof component === "function" || component instanceof Function ) {
- callback = component;
- component = undefined;
- }
- // Accept `node.children.create( name )` and treat it as
- // `node.children.create( name, {} )`.
- component = component || {};
- // Make the call. If a callback is provided, wrap it and translate the ID to a
- // node reference.
- if ( callback ) {
- self.kernel.createChild( this.node.id, name, VWFJavaScript.componentKernelFromJS.call( self, component ), undefined, undefined, function( childID ) {
- callback.call( node, self.nodes[childID] );
- } );
- } else {
- return self.kernel.createChild( this.node.id, name, VWFJavaScript.componentKernelFromJS.call( self, component ) );
- }
- }
- } );
- Object.defineProperty( node.children, "delete", {
- value: function( child ) {
- if ( typeof child === "string" ) {
- child = this.node.children[ child ];
- }
- return self.kernel.deleteNode( child.id );
- }
- } );
- // Attach the child meta events to `node.children.{added,removed}`.
- VWFJavaScript.createEventAccessor.call( this, node.children, "added", "children" );
- VWFJavaScript.createEventAccessor.call( this, node.children, "removed", "children" );
- // Define the "random" and "seed" functions.
- Object.defineProperty( node, "random", { // "this" is node
- value: function() {
- return self.kernel.random( this.id );
- }
- } );
- Object.defineProperty( node, "seed", { // "this" is node
- value: function( seed ) {
- return self.kernel.seed( this.id, seed );
- }
- } );
- // Define the "time", "client", and "moniker" properties.
- Object.defineProperty( node, "time", { // TODO: only define on shared "node" prototype?
- get: function() {
- return self.kernel.time();
- },
- enumerable: true,
- } );
- Object.defineProperty( node, "client", { // TODO: only define on shared "node" prototype?
- get: function() {
- return self.kernel.client();
- },
- enumerable: true,
- } );
- Object.defineProperty( node, "moniker", { // TODO: only define on shared "node" prototype?
- get: function() {
- return self.kernel.moniker();
- },
- enumerable: true,
- } );
- Object.defineProperty( node, "find", {
- value: function( matchPattern, callback /* ( match ) */ ) { // "this" is node
- if ( callback ) {
- self.kernel.find( this.id, matchPattern, true, function( matchID ) {
- callback.call( node, self.nodes[matchID] );
- } );
- } else { // TODO: future iterator proxy
- var findResults = self.kernel.find( this.id, matchPattern, true );
- if ( findResults )
- return findResults.map( function( matchID ) {
- return self.nodes[matchID];
- } );
- }
- }
- } );
- Object.defineProperty( node, "test", {
- value: function( matchPattern, testNode ) { // "this" is node
- return self.kernel.test( this.id, matchPattern, testNode.id, true );
- }
- } );
- // Define a "future" proxy so that for any this.property, this.method, or this.event, we
- // can reference this.future( when, callback ).property/method/event and have the
- // expression evaluated at the future time.
- Object.defineProperty( node, "in", { // TODO: only define on shared "node" prototype?
- value: function( when, callback ) { // "this" is node
- return VWFJavaScript.refreshedFuture.call( self, this, -when, callback ); // relative time
- },
- enumerable: true,
- } );
- Object.defineProperty( node, "at", { // TODO: only define on shared "node" prototype?
- value: function( when, callback ) { // "this" is node
- return VWFJavaScript.refreshedFuture.call( self, this, when, callback ); // absolute time
- },
- enumerable: true,
- } );
- Object.defineProperty( node, "future", { // same as "in" // TODO: only define on shared "node" prototype?
- get: function() {
- return this.in;
- },
- enumerable: true,
- } );
- node.private.future = Object.create( prototype.private ?
- prototype.private.future : Object.prototype
- );
- Object.defineProperty( node.private.future, "private", {
- value: {
- when: 0,
- callback: undefined,
- change: 0,
- }
- } );
- node.private.change = 1; // incremented whenever "future"-related changes occur
- },
- // -- initializingNode ---------------------------------------------------------------------
- // Invoke an initialize() function if one exists.
- initializingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
- childSource, childType, childIndex, childName ) {
- var node = this.nodes[nodeID];
- var child = this.nodes[childID];
- var scriptText =
- "this.hasOwnProperty( 'initialize' ) && " +
- "( typeof this.initialize === 'function' || this.initialize instanceof Function ) && " +
- "this.initialize()";
- // Call the child's initializer.
- try {
- ( function( scriptText ) { return eval( scriptText ) } ).call( child, scriptText );
- } catch ( e ) {
- this.logger.warnx( "initializingNode", childID,
- "exception in initialize:", VWFJavaScript.utility.exceptionMessage( e ) );
- }
- // The node is fully initialized at this point
- // Link to the parent.
- //
- // The parent reference is only defined once the node is fully initialized.
- // It is not defined earlier since components should be able to stand alone
- // without depending on external nodes.
- //
- // Additionally, since parts of the application may become ready in a different
- // order on other clients, referring to properties in other parts of the
- // application may lead to consistency errors.
- child.parent = node;
- if ( node ) {
- node.children[childIndex] = child;
- if ( parseInt( childName ).toString() !== childName ) {
- node.children[childName] = child;
- }
- node.hasOwnProperty( childName ) || // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
- ( node[childName] = child );
- }
- return undefined;
- },
- // -- initializingNodeFromPrototype --------------------------------------------------------
- // Invoke an initialize() function from `childInitializingNodeID` on `childID` if one exists.
- initializingNodeFromPrototype: function( nodeID, childID, childInitializingNodeID ) {
- var child = this.nodes[childID];
- var initializer = this.nodes[childInitializingNodeID];
- // Call the prototype's initializer on the child.
- try {
- var prototypeHasInitialize = ( initializer.hasOwnProperty( 'initialize' ) &&
- ( typeof initializer.initialize === 'function' ||
- initializer.initialize instanceof Function ) );
- if ( prototypeHasInitialize ) {
- return initializer.initialize.call( child );
- }
- } catch ( e ) {
- this.logger.warnx( "initializingNodeFromPrototype", childID,
- "exception in initialize:", VWFJavaScript.utility.exceptionMessage( e ) );
- }
- return undefined;
- },
- // -- deletingNode -------------------------------------------------------------------------
- deletingNode: function( nodeID ) {
- var child = this.nodes[nodeID];
- var node = child.parent;
- if ( node ) {
- var index = node.children.indexOf( child );
- if ( index >= 0 ) {
- node.children.splice( index, 1 );
- }
- delete node.children[child.name]; // TODO: conflict if childName is parseable as a number
- if ( node[child.name] === child ) {
- delete node[child.name]; // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
- }
- child.parent = undefined;
- }
- delete this.nodes[nodeID];
- },
- // -- addingChild --------------------------------------------------------------------------
- addingChild: function( nodeID, childID, childName ) {
- },
- // -- removingChild ------------------------------------------------------------------------
- removingChild: function( nodeID, childID ) {
- var node = this.nodes[nodeID];
- var child = this.nodes[childID];
- child.parent = undefined;
- if ( node ) {
- node.children.splice( node.children.indexOf( child ), 1 );
- delete node.children[child.name]; // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
- delete node[child.name]; // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
- }
- },
- // -- creatingProperty ---------------------------------------------------------------------
- creatingProperty: function( nodeID, propertyName, propertyValue, propertyGet, propertySet ) {
- var node = this.nodes[nodeID];
- var self = this;
- var getter = propertyGet && // TODO: assuming javascript here; how to specify script type?
- VWFJavaScript.functionFromHandler( { body: propertyGet, type: VWFJavaScript.scriptMediaType },
- logGetterException );
- if ( getter ) {
- node.private.getters[ propertyName ] = getter;
- } else {
- node.private.getters[ propertyName ] = true; // set a guard value so that we don't call prototype getters on value properties
- }
-
- var setter = propertySet && // TODO: assuming javascript here; how to specify script type?
- VWFJavaScript.functionFromHandler( { parameters: [ "value" ], body: propertySet, type: VWFJavaScript.scriptMediaType },
- logSetterException );
- if ( setter ) {
- node.private.setters[ propertyName ] = setter;
- } else {
- node.private.setters[ propertyName ] = true; // set a guard value so that we don't call prototype setters on value properties
- }
- function logGetterException( exception ) {
- self.logger.warnx( "creatingProperty", nodeID, propertyName, propertyValue,
- "exception evaluating getter:", VWFJavaScript.utility.exceptionMessage( exception ) );
- }
- function logSetterException( exception ) {
- self.logger.warnx( "creatingProperty", nodeID, propertyName, propertyValue,
- "exception evaluating setter:", VWFJavaScript.utility.exceptionMessage( exception ) );
- }
- return this.initializingProperty( nodeID, propertyName, propertyValue );
- },
- // -- initializingProperty -----------------------------------------------------------------
- initializingProperty: function( nodeID, propertyName, propertyValue ) {
- var node = this.nodes[nodeID];
- VWFJavaScript.createPropertyAccessor.call( this, node.properties, propertyName );
- node.hasOwnProperty( propertyName ) || // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
- VWFJavaScript.createPropertyAccessor.call( this, node, propertyName );
- // Invalidate the "future" cache.
- node.private.change++;
- return propertyValue !== undefined ?
- this.settingProperty( nodeID, propertyName, propertyValue ) : undefined;
- },
- // TODO: deletingProperty
- // -- settingProperty ----------------------------------------------------------------------
- settingProperty: function( nodeID, propertyName, propertyValue ) {
- var node = this.nodes[nodeID];
- if ( ! node ) return; // TODO: patch until full-graph sync is working; drivers should be able to assume that nodeIDs refer to valid objects
- var setter = node.private.setters && node.private.setters[propertyName];
- if ( setter && setter !== true ) { // is there is a setter (and not just a guard value)
- try {
- var valueJS = VWFJavaScript.valueJSFromKernel.call( this, propertyValue );
- var resultJS = setter.call( node, valueJS );
- return VWFJavaScript.valueKernelFromJS.call( this, resultJS );
- } catch ( e ) {
- this.logger.warnx( "settingProperty", nodeID, propertyName, propertyValue,
- "exception in setter:", VWFJavaScript.utility.exceptionMessage( e ) );
- }
- }
- return undefined;
- },
- // -- gettingProperty ----------------------------------------------------------------------
- gettingProperty: function( nodeID, propertyName, propertyValue ) {
- var node = this.nodes[nodeID];
- //if ( ! node ) return;
- var getter = node.private.getters && node.private.getters[propertyName];
- if ( getter && getter !== true ) { // is there is a getter (and not just a guard value)
- try {
- var resultJS = getter.call( node );
- return VWFJavaScript.valueKernelFromJS.call( this, resultJS );
- } catch ( e ) {
- this.logger.warnx( "gettingProperty", nodeID, propertyName, propertyValue,
- "exception in getter:", VWFJavaScript.utility.exceptionMessage( e ) );
- }
- }
-
- return undefined;
- },
- // -- creatingMethod -----------------------------------------------------------------------
- creatingMethod: function( nodeID, methodName, methodParameters, methodBody ) {
- var node = this.nodes[nodeID];
- VWFJavaScript.createMethodAccessor.call( this, node.methods, methodName );
- node.hasOwnProperty( methodName ) || // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
- VWFJavaScript.createMethodAccessor.call( this, node, methodName );
- // Invalidate the "future" cache.
- node.private.change++;
- // Delegate to `settingMethod`.
- return this.settingMethod( nodeID, methodName, {
- parameters: methodParameters,
- body: methodBody,
- type: typeof methodBody === "string" || methodBody instanceof String ?
- VWFJavaScript.scriptMediaType : undefined, // TODO: heuristic duplicated in vwf.js `normalizedHandler`.
- } );
- },
- // TODO: deletingMethod
- // -- settingMethod ------------------------------------------------------------------------
- settingMethod: function( nodeID, methodName, methodHandler ) {
- var node = this.nodes[nodeID];
- var self = this;
- var body = VWFJavaScript.functionFromHandler( methodHandler, logException,
- vwf.configuration[ "preserve-script-closures" ] );
- if ( body ) {
- node.private.bodies[ methodName ] = body;
- return VWFJavaScript.handlerFromFunction( body ); // TODO: shortcut to avoid retrieving this?
- } else {
- delete node.private.bodies[ methodName ];
- }
- function logException( exception ) {
- self.logger.warnx( "settingMethod", nodeID, methodName, methodHandler.parameters,
- "exception evaluating body:", VWFJavaScript.utility.exceptionMessage( exception ) );
- }
- return undefined;
- },
- // -- gettingMethod ------------------------------------------------------------------------
- gettingMethod: function( nodeID, methodName ) {
- var node = this.nodes[nodeID];
- var body = node.private.bodies && node.private.bodies[methodName];
- if ( body ) {
- return VWFJavaScript.handlerFromFunction( body );
- }
- return undefined;
- },
- // -- callingMethod ------------------------------------------------------------------------
- callingMethod: function( nodeID, methodName, methodParameters ) {
- var node = this.nodes[nodeID];
- var body = node.private.bodies && node.private.bodies[methodName];
- if ( body ) {
- try {
- var parametersJS = VWFJavaScript.parametersJSFromKernel.call( this, methodParameters );
- var resultJS = body.apply( node, parametersJS );
- return VWFJavaScript.valueKernelFromJS.call( this, resultJS );
- } catch ( e ) {
- this.logger.warnx( "callingMethod", nodeID, methodName, methodParameters, // TODO: limit methodParameters for log
- "exception:", VWFJavaScript.utility.exceptionMessage( e ) );
- }
- }
- return undefined;
- },
- // -- creatingEvent ------------------------------------------------------------------------
- creatingEvent: function( nodeID, eventName, eventParameters ) {
- var node = this.nodes[nodeID];
- VWFJavaScript.createEventAccessor.call( this, node.events, eventName );
- node.hasOwnProperty( eventName ) || // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
- VWFJavaScript.createEventAccessor.call( this, node, eventName );
- // Invalidate the "future" cache.
- node.private.change++;
- },
- // -- addingEventListener ------------------------------------------------------------------
- addingEventListener: function( nodeID, eventName, eventListenerID, eventHandler, eventContextID, eventPhases ) {
- var node = this.nodes[nodeID];
- // Create the listeners collection if this is the first listener added for an event on a
- // prototype.
- if ( ! node.private.listeners[eventName] ) {
- node.private.listeners[eventName] = [];
- }
- // Build a `Listener` from the `Handler` and the context and phases.
- var eventListener = VWFJavaScript.utility.merge( eventHandler, {
- context: eventContextID,
- phases: eventPhases,
- } );
- // Delegate to `settingEventListener`.
- return this.settingEventListener( nodeID, eventName, eventListenerID, eventListener ) ?
- true : undefined;
- },
- // -- removingEventListener ----------------------------------------------------------------
- removingEventListener: function( nodeID, eventName, eventListenerID ) {
- var node = this.nodes[nodeID];
- var listeners = node.private.listeners[eventName];
- if ( listeners && listeners[ eventListenerID ] ) {
- delete listeners[ eventListenerID ];
- return true;
- }
- return undefined;
- },
- // -- settingEventListener -----------------------------------------------------------------
- settingEventListener: function( nodeID, eventName, eventListenerID, eventListener ) {
- var node = this.nodes[nodeID];
- var self = this;
- var listeners = node.private.listeners[eventName];
- var handler = VWFJavaScript.functionFromHandler( eventListener, logException,
- vwf.configuration[ "preserve-script-closures" ] );
- if ( handler ) {
- listeners[ eventListenerID ] = {
- handler: handler,
- context: eventListener.context,
- phases: eventListener.phases,
- };
- // Kernel actions that set a value allow the driver to modify the value assigned.
- // The result of the action is the actually-assigned value reported by the driver.
- // Here, we should return a `Listener` derived from the function we just rendered so
- // that `kernel.setEvent` will return the same result that a following
- // `kernel.getEvent` would. However, since `Function.toString` is relatively heavy,
- // we'll just return the incoming value until there is a demonstrated need for the
- // precise result.
- // return utility.merge( handlerFromFunction( listener.handler ), {
- // context: listener.context,
- // phases: listener.phases,
- // } );
- return eventListener;
- } else {
- delete listeners[ eventListenerID ];
- }
- function logException( exception ) {
- self.logger.warnx( "settingEventListener", nodeID, eventName, eventListenerID,
- "exception evaluating listener:", VWFJavaScript.utility.exceptionMessage( exception ) );
- }
- return undefined;
- },
- // -- gettingEventListener -----------------------------------------------------------------
- gettingEventListener: function( nodeID, eventName, eventListenerID ) {
- var node = this.nodes[nodeID];
- var listeners = node.private.listeners[eventName];
- if ( listeners ) {
- var listener = listeners[ eventListenerID ];
- return VWFJavaScript.utility.merge( VWFJavaScript.handlerFromFunction( listener.handler ), {
- context: listener.context,
- phases: listener.phases,
- } );
- }
- return undefined;
- },
- // -- flushingEventListeners ---------------------------------------------------------------
- flushingEventListeners: function( nodeID, eventName, eventContextID ) {
- // Prepare the return value
- var removedListenerIDs = [];
- // Extract the listeners of the specified node
- var node = this.nodes[ nodeID ];
- var listeners = node.private.listeners[ eventName ];
- // If listeners exist for the given eventName, loop through them, removing any for which
- // the context is the node specified by the parameter eventContextID
- if ( listeners ) {
- listeners.forEach( function( listener, listenerID ) {
- if ( listener.context === eventContextID ) {
- delete listeners[ listenerID ];
- removedListenerIDs.push( listenerID );
- }
- } );
- }
- return removedListenerIDs;
- },
- // -- firingEvent --------------------------------------------------------------------------
- firingEvent: function( nodeID, eventName, eventParameters ) {
- var phase = eventParameters && eventParameters.phase; // the phase is smuggled across on the parameters array // TODO: add "phase" as a fireEvent() parameter? it isn't currently needed in the kernel public API (not queueable, not called by the drivers), so avoid if possible
- var node = this.nodes[nodeID];
- var listeners = VWFJavaScript.findListeners( node, eventName );
- var parametersJS = VWFJavaScript.parametersJSFromKernel.call( this, eventParameters );
- var self = this;
- // Call the handlers registered for the event, and calculate the logical OR of each
- // result. Normally, callers to fireEvent() ignore the handler result, but dispatched
- // events use the return value to determine when an event has been handled as it bubbles
- // up from its target.
- var handled = listeners && listeners.reduce( function( handled, listener ) {
- // Call the handler. If a phase is provided, only call handlers tagged for that
- // phase.
- try {
- if ( ! phase || listener.phases && listener.phases.indexOf( phase ) >= 0 ) {
- var contextNode = self.nodes[ listener.context ] || self.nodes[ 0 ]; // default context is the global root // TODO: this presumes this.creatingNode( undefined, 0 ) is retained above
- var resultJS = listener.handler.apply( contextNode, parametersJS );
- var result = VWFJavaScript.valueKernelFromJS.call( self, resultJS );
- return handled || result || result === undefined; // interpret no return as "return true"
- }
- } catch ( e ) {
- self.logger.warnx( "firingEvent", nodeID, eventName, eventParameters, // TODO: limit eventParameters for log
- "exception:", VWFJavaScript.utility.exceptionMessage( e ) );
- }
- return handled;
- }, false );
- return handled;
- },
- // -- executing ----------------------------------------------------------------------------
- executing: function( nodeID, scriptText, scriptType ) {
- var node = this.nodes[nodeID];
- if ( scriptType == VWFJavaScript.scriptMediaType ) {
- try {
- var resultJS = ( function( scriptText ) { return eval( scriptText ) } ).call( node, scriptText || "" );
- return VWFJavaScript.valueKernelFromJS.call( this, resultJS );
- } catch ( e ) {
- this.logger.warnx( "executing", nodeID,
- ( scriptText || "" ).replace( /\s+/g, " " ).substring( 0, 100 ), scriptType, "exception:", VWFJavaScript.utility.exceptionMessage( e ) );
- }
- }
- return undefined;
- },
- } );
- }
- // == Private functions ========================================================================
- // -- proxiedBehavior --------------------------------------------------------------------------
- static proxiedBehavior( prototype, behavior ) { // invoke with the model as "this" // TODO: this is a lot like createProperty()/createMethod()/createEvent(), and refreshedFuture(). Find a way to merge. // TODO: nodes need to keep a list of proxies on them and callback here to refresh after changes
- var self = this;
- var proxy = Object.create( prototype );
- Object.defineProperty( proxy, "private", {
- value: {}
- } );
- proxy.private.origin = behavior; // the node we're the proxy for
- Object.defineProperty( proxy, "id", {
- value: behavior.id,
- enumerable: true,
- } );
- proxy.name = behavior.name;
- proxy.parent = behavior.parent;
- proxy.source = behavior.source;
- proxy.type = behavior.type;
- proxy.initialize = behavior.initialize;
- proxy.properties = Object.create( prototype.properties || Object.prototype, {
- node: { value: proxy } // for proxy.properties accessors (non-enumerable) // TODO: hide this better
- } );
- proxy.private.getters = Object.create( prototype.private ?
- prototype.private.getters : Object.prototype
- );
- proxy.private.setters = Object.create( prototype.private ?
- prototype.private.setters : Object.prototype
- );
- for ( var propertyName in behavior.properties ) {
- if ( behavior.properties.hasOwnProperty( propertyName ) ) {
- ( function( propertyName ) {
- VWFJavaScript.createPropertyAccessor.call( self, proxy.properties, propertyName );
- proxy.hasOwnProperty( propertyName ) || // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
- VWFJavaScript.createPropertyAccessor.call( self, proxy, propertyName );
- } )( propertyName );
-
- if ( behavior.private.getters.hasOwnProperty( propertyName ) ) {
- proxy.private.getters[propertyName] = behavior.private.getters[propertyName];
- }
- if ( behavior.private.setters.hasOwnProperty( propertyName ) ) {
- proxy.private.setters[propertyName] = behavior.private.setters[propertyName];
- }
- }
- }
- proxy.methods = Object.create( prototype.methods || Object.prototype, {
- node: { value: proxy } // for proxy.methods accessors (non-enumerable) // TODO: hide this better
- } );
- proxy.private.bodies = Object.create( prototype.private ?
- prototype.private.bodies : Object.prototype
- );
- for ( var methodName in behavior.methods ) {
- if ( behavior.methods.hasOwnProperty( methodName ) ) {
- ( function( methodName ) {
- VWFJavaScript.createMethodAccessor.call( self, proxy.methods, methodName );
- proxy.hasOwnProperty( methodName ) || // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
- VWFJavaScript.createMethodAccessor.call( self, proxy, methodName );
- } )( methodName );
-
- if ( behavior.private.bodies.hasOwnProperty( methodName ) ) {
- proxy.private.bodies[methodName] = behavior.private.bodies[methodName];
- }
- }
- }
- proxy.events = Object.create( prototype.events || Object.prototype, {
- node: { value: proxy } // for proxy.events accessors (non-enumerable) // TODO: hide this better
- } );
- proxy.private.listeners = {}; // not delegated to the prototype as with getters, setters, and bodies; findListeners() filters recursion
- for ( var eventName in behavior.events ) {
- if ( behavior.events.hasOwnProperty( eventName ) ) {
- ( function( eventName ) {
- VWFJavaScript.createEventAccessor.call( self, proxy.events, eventName );
- proxy.hasOwnProperty( eventName ) || // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
- VWFJavaScript.createEventAccessor.call( self, proxy, eventName );
- } )( eventName );
- }
- }
- for ( var eventName in behavior.private.listeners ) { // outside of the behavior.events loop as with getters, setters, and bodies; listeners may appear above the event definition
- if ( behavior.private.listeners.hasOwnProperty( eventName ) ) {
- proxy.private.listeners[eventName] = behavior.private.listeners[eventName];
- }
- }
- proxy.private.future = Object.create( prototype.private ?
- prototype.private.future : Object.prototype
- );
- Object.defineProperty( proxy.private.future, "private", {
- value: {
- when: 0,
- callback: undefined,
- change: 0,
- }
- } );
- proxy.private.change = behavior.private.change;
- return proxy;
- }
- // -- refreshedFuture --------------------------------------------------------------------------
- static refreshedFuture( node, when, callback ) { // invoke with the model as "this"
- var self = this;
- if ( Object.getPrototypeOf( node ).private ) {
- VWFJavaScript.refreshedFuture.call( this, Object.getPrototypeOf( node ) );
- }
- var future = node.private.future;
- future.private.when = when;
- future.private.callback = callback; // TODO: would like to be able to remove this reference after the future call has completed
- if ( future.private.change < node.private.change ) { // only if out of date
- Object.defineProperty( future, "id", {
- value: node.id,
- enumerable: true,
- } );
- future.properties = Object.create( Object.getPrototypeOf( future ).properties || Object.prototype, {
- node: { value: future } // for future.properties accessors (non-enumerable) // TODO: hide this better
- } );
- for ( var propertyName in node.properties ) {
- if ( node.properties.hasOwnProperty( propertyName ) ) {
- ( function( propertyName ) {
- VWFJavaScript.createPropertyAccessor.call( self, future.properties, propertyName );
- future.hasOwnProperty( propertyName ) || // TODO: calculate so that properties take precedence over methods over events, for example
- VWFJavaScript.createPropertyAccessor.call( self, future, propertyName );
- } )( propertyName );
-
- }
-
- }
- future.methods = Object.create( Object.getPrototypeOf( future ).methods || Object.prototype, {
- node: { value: future } // for future.methods accessors (non-enumerable) // TODO: hide this better
- } );
- for ( var methodName in node.methods ) {
- if ( node.methods.hasOwnProperty( methodName ) ) {
- ( function( methodName ) {
- VWFJavaScript.createMethodAccessor.call( self, future.methods, methodName, true );
- future.hasOwnProperty( methodName ) || // TODO: calculate so that properties take precedence over methods over events, for example
- VWFJavaScript.createMethodAccessor.call( self, future, methodName, true );
- } )( methodName );
- }
- }
- future.events = Object.create( Object.getPrototypeOf( future ).events || Object.prototype, {
- node: { value: future } // for future.events accessors (non-enumerable) // TODO: hide this better
- } );
- for ( var eventName in node.events ) {
- if ( node.events.hasOwnProperty( eventName ) ) {
- ( function( eventName ) {
- VWFJavaScript.createEventAccessor.call( self, future.events, eventName, undefined, true );
- future.hasOwnProperty( eventName ) || // TODO: calculate so that properties take precedence over methods over events, for example
- VWFJavaScript.createEventAccessor.call( self, future, eventName, undefined, true );
- } )( eventName );
- }
- }
- future.private.change = node.private.change;
- }
- return future;
- }
- /// Define a (JavaScript) accessor property on a node or a node's `properties` collection that
- /// will manipulate a (VWF) property on the node.
- ///
- /// Reading `node.properties.name` invokes the `get` accessor, which calls `kernel.getProperty`
- /// to return the value for the property on the node. Writing `node.properties.name` invokes
- /// the `set` accessor, which calls `kernel.setProperty` to assign a new value to the property
- /// on its node.
- ///
- /// This function must run as a method of the driver. Invoke it as:
- /// `createPropertyAccessor.call( driver, container, propertyName )`.
- ///
- /// @param {Object} container
- /// A `node` or `node.properties` object to receive the property.
- /// @param {String} propertyName
- /// The name of the property to create on `container`.
- static createPropertyAccessor( container, propertyName ) {
- var self = this;
- Object.defineProperty( container, propertyName, {
- // On read, call `kernel.getProperty` and return the result.
- get: function() { // `this` is the container
- var node = this.node || this; // the node via node.properties.node, or just node
- var resultKernel = self.kernel.getProperty( node.id, propertyName,
- node.private.when, node.private.callback );
- return VWFJavaScript.valueJSFromKernel.call( self, resultKernel );
- },
- // On write, pass the assigned value to `kernel.setProperty`.
- set: function( value ) { // `this` is the container
- var node = this.node || this; // the node via node.properties.node, or just node
- var valueKernel = VWFJavaScript.valueKernelFromJS.call( self, value );
- self.kernel.setProperty( node.id, propertyName, valueKernel,
- node.private.when, node.private.callback );
- },
- enumerable: true,
- } );
- }
- /// Define an accessor property on a node or a node's `methods` collection that manipulates a
- /// method on the node.
- ///
- /// Reading `node.methods.name` returns a function that when called calls `kernel.callMethod` to
- /// invoke the method on the node. Writing a function object to `node.methods.name` will set the
- /// method body to the assigned function.
- ///
- /// This function must run as a method of the driver. Invoke it as:
- /// `createMethodAccessor.call( driver, container, methodName )`.
- ///
- /// @param {Object} container
- /// A `node` or `node.methods` object to receive the property.
- /// @param {String} methodName
- /// The name of the property to create on `container`.
- /// @param {Boolean} [unsettable]
- /// When truthy, don't create the `set` accessor. An unsettable method property doesn't allow
- /// the method body to be changed.
- static createMethodAccessor( container, methodName, unsettable ) {
- var self = this;
- Object.defineProperty( container, methodName, {
- // On read, return a function that calls `kernel.callMethod` when invoked.
- get: function() { // `this` is the container
- var node = this.node || this; // the node via node.methods.node, or just node
- return function( /* parameter1, parameter2, ... */ ) { // `this` is the container
- var argumentsKernel = VWFJavaScript.parametersKernelFromJS.call( self, arguments );
- var resultKernel = self.kernel.callMethod( node.id, methodName, argumentsKernel,
- node.private.when, node.private.callback );
- return VWFJavaScript.valueJSFromKernel.call( self, resultKernel );
- };
- },
- // On write, update the method body. `unsettable` methods don't accept writes.
- set: unsettable ? undefined : function( value ) { // `this` is the container
- var node = this.node || this; // the node via node.methods.node, or just node
- self.kernel.setMethod( node.id, methodName,
- VWFJavaScript.handlerFromFunction( value, vwf.configuration[ "preserve-script-closures" ] ) );
- },
- enumerable: true,
- } );
- }
- /// Define an accessor property on a node or a node's `events` collection that manipulates an
- /// event on the node. `createEventAccessor` is also used to define accessor properties at other
- /// locations on the node to expose the node's meta events.
- ///
- /// Reading `node.events.name` returns a function that when called calls `kernel.fireEvent` to
- /// fire the event from the node. Writing a function object to `node.events.name` will add the
- /// assigned function as a new listener using default parameters. To add a listener with
- /// specified paramters, call the `node.events.add` helper function and assign the result to
- /// `node.events.name`. Remove a listener by calling the `node.events.remove` helper and
- /// assigning the result. Flush a set of listeners with the `node.events.flush` helper.
- ///
- /// This function must run as a method of the driver. Invoke it as:
- /// `createEventAccessor.call( driver, container, eventName [, eventNamespace ] [, unsettable ] )`.
- ///
- /// @param {Object} container
- /// A `node` or `node.events` object to receive the property. Meta events will attach to
- /// `node.properties`, `node.methods`, `node.events`, and `node.children` as well.
- /// @param {String} eventName
- /// The name of the property to create on `container`.
- /// @param {String} [eventNamespace]
- /// For meta events, the namespace associated with the event.
- /// @param {Boolean} [unsettable]
- /// When truthy, don't create the `set` accessor. An unsettable event property can't add or
- /// remove listeners.
- static createEventAccessor( container, eventName, eventNamespace, unsettable ) {
- var self = this;
- Object.defineProperty( container, eventName, {
- // On read, return a function that calls `kernel.fireEvent` when invoked. Namespaced
- // events (which are meta events and controlled by the kernel) are ungettable and can't
- // be fired by the application.
- get: eventNamespace ? undefined : function() { // `this` is the container
- var node = this.node || this; // the node via node.*collection*.node, or just node
- return function( /* parameter1, parameter2, ... */ ) { // `this` is the container
- var argumentsKernel = VWFJavaScript.parametersKernelFromJS.call( self, arguments );
- var resultKernel = self.kernel.fireEvent( node.id, eventName, argumentsKernel,
- node.private.when, node.private.callback );
- return VWFJavaScript.valueJSFromKernel.call( self, resultKernel );
- };
- },
- // On write, update the listeners. `unsettable` events don't accept writes.
- set: unsettable ? undefined : function( value ) { // `this` is the container
- var node = this.node || this; // the node via node.*collection*.node, or just node
- var namespacedName = eventNamespace ? [ eventNamespace, eventName ] : eventName;
- if ( typeof value === "function" || value instanceof Function ) {
- // `container.*eventName* = handler` (context is the target node).
- addListener( value, node );
- } else if ( value.add ) {
- // `container.*eventName* = node.events.add( handler, phases, context, callback /* listenerID */ )`.
- if ( ! value.phases || value.phases instanceof Array ) {
- addListener( value.handler, value.context, value.phases, value.callback );
- } else {
- addListener( value.handler, value.context, [ value.phases ], value.callback );
- }
- } else if ( value.remove ) {
- // `container.*eventName* = node.events.remove( listenerID|handler )`.
- // For `node.events.remove( listenerID )`, remove using the direct parameter.
- // For `node.events.remove( handler )`, use the id that `addListener` attached
- // to the handler.
- self.kernel.removeEventListener( node.id, namespacedName,
- value.handler ? value.handler.listenerID : value.id );
- } else if ( value.flush ) {
- // `container.*eventName* = node.events.flush( context )`.
- self.kernel.flushEventListeners( node.id, namespacedName,
- value.context && value.context.id );
- }
- function addListener( handler, context, phases, callback ) {
- var listenerID = self.kernel.addEventListener( node.id, namespacedName,
- VWFJavaScript.handlerFromFunction( handler, vwf.configuration[ "preserve-script-closures" ] ),
- context && context.id, phases );
- // For 0.6.23 and earlier, listeners were removed using a direct reference to
- // the handler. For backward compatability, tag the handler with the listener id
- // so that we can retrieve the id if the listener is removed by handler.
- handler.listenerID = listenerID;
- callback && callback.call( node, listenerID );
- }
- },
- // Meta events--including the `properties`, `methods`, and `events` `created` and
- // `deleted` events, and the `children` `added` and `removed` events--are not
- // enumerable.
- enumerable: ! eventNamespace,
- } );
- }
- /// Convert a `Handler` to a JavaScript function.
- ///
- /// @param {Handler} handler
- /// A `Handler` to convert to a function.
- /// @param {function} [errback]
- /// If `errback` is provided, any exception that occurs during the conversion will be passed
- /// to `errback` as `errback( exception )`.
- /// @param {boolean} [bypass]
- /// Expect that `handler.body` is a function object instead of the string representation of
- /// the function body. Return the function without any conversion. This parameter should only
- /// be used in support of the backwards-compatability `preserve-script-closures` configuration
- /// option.
- ///
- /// @returns {function|undefined}
- /// The function generated from `handler`, or `undefined` if `handler` does not describe a
- /// JavaScript function or if the function could not be converted.
- static functionFromHandler( handler, errback /* exception */, bypass ) {
- if ( bypass && ( typeof handler.body === "function" || handler.body instanceof Function ) ) {
- return handler.body;
- } else if ( handler.type === VWFJavaScript.scriptMediaType ) {
- var name = handler.name, parameters = handler.parameters, body = handler.body;
- var parameterString = parameters && parameters.length ?
- " " + parameters.join( ", " ) + " " :
- "";
- var prefix = "function(" + parameterString + ") {";
- var suffix = "}";
- var functionString, indentedBody;
- if ( body && body.length ) {
- if ( body.charAt( body.length-1 ) === "\n" ) {
- indentedBody = body.match( /^[^\S\n]/ ) ? body : body.replace( /^./gm, " $&" );
- functionString = prefix + "\n" + indentedBody + suffix + "\n";
- } else {
- functionString = prefix + " " + body + " " + suffix;
- }
- } else {
- functionString = prefix + suffix;
- }
- try {
- return eval( "( " + functionString + ")" );
- } catch( exception ) {
- errback && errback( exception );
- }
- }
- return undefined;
- }
- /// Convert a JavaScript `function` to a `Handler`.
- ///
- /// @param {function} funcshun
- /// A function to convert to a `Handler`.
- /// @param {boolean} [bypass]
- /// Create an object having the form of a `Handler`, but with the `body` field set to the
- /// function object instead of the string representation of the function body. The `name`,
- /// `parameters`, and `type` fields will not be set. This parameter should only be used in
- /// support of the backwards-compatability `preserve-script-closures` configuration option.
- ///
- /// @returns {Handler|undefined}
- /// The `Handler` generated from `funcshun`, or `undefined` if the function's `toString` could
- /// not be parsed.
- static handlerFromFunction( funcshun, bypass ) {
- var name, parameters, body, type = VWFJavaScript.scriptMediaType;
- var match, leadingMatch, trailingMatch, indention = "";
- if ( bypass ) {
- return {
- body: funcshun,
- };
- } else if ( match = /* assignment! */ VWFJavaScript.functionRegex.exec( funcshun.toString() ) ) {
- name = match[1];
- // Trim the parameter string. Also remove the `/**/` that Chrome adds to the parameter
- // list for functions created using `Function( parameter, ..., body )`. See
- // `NewFunctionString` in http://code.google.com/p/v8/source/browse/trunk/src/v8natives.js.
- var parameterString = match[2].replace( /\/\*.*\*\//, "" ).trim();
- parameters = parameterString.length ? parameterString.split( "," ).map( function( parameter ) {
- return parameter.trim();
- } ) : undefined;
- // Trim the body string. Recognize block vs. inline formatting where possible and retain
- // the existing spacing.
- body = match[3];
- leadingMatch = // leading spacing, if the leading brace is on its own line
- body.match( /^([^\S\n]*\n)([^\S\n]*)/ );
- trailingMatch = // trailing spacing, if the trailing brace is on own line
- body.match( /\n([^\S\n]*)$/ );
- // Trim the leading spaces. If the leading brace was on its own line, delete the empty
- // first line and take the body indention to be the next line's spacing. Otherwise, just
- // trim the beginning of the body.
- if ( leadingMatch ) {
- body = body.substr( leadingMatch[1].length );
- indention = leadingMatch[2];
- } else {
- body = body.replace( /^\s*/, "" );
- }
- // Trim the trailing spaces. If the trailing brace was on its own line, delete its
- // indention and take that as the body indention. The trailing brace indention takes
- // priority over indention taken from the leading line. If the trailing brace was not on
- // its own line, just trim the end of the body.
- if ( trailingMatch ) {
- body = body.substr( -trailingMatch[1] );
- indention = trailingMatch[1];
- } else {
- body = body.replace( /\s*$/, "" );
- }
- // If we recognized the body as an block (not inline with the braces), unindent it and
- // ensure that the last line ends with a newline.
- if ( leadingMatch || trailingMatch ) {
- body = body.replace( new RegExp( "^" + indention, "gm" ), "" );
- body = body.replace( /\n?$/, "\n" );
- }
- return {
- name: name,
- parameters: parameters,
- body: body,
- type: type,
- };
- }
- return undefined;
- }
- // -- findListeners ----------------------------------------------------------------------------
- // TODO: this walks the full prototype chain and is probably horribly inefficient.
- static findListeners( node, eventName, targetOnly ) {
- var prototypeListeners = Object.getPrototypeOf( node ).private ? // get any self-targeted listeners from the prototypes
- VWFJavaScript.findListeners( Object.getPrototypeOf( node ), eventName, true ) : [];
- var nodeListeners = node.private.listeners && node.private.listeners[eventName] || [];
- if ( targetOnly ) {
- return prototypeListeners.concat( nodeListeners.filter( function( listener ) {
- return listener.context === node.id || // in the prototypes, select self-targeted listeners only
- ( node.private.origin && listener.context === node.private.origin.id );
- } ) );
- } else {
- return prototypeListeners.map( function( listener ) { // remap the prototype listeners to target the node
- return { handler: listener.handler, context: node.id, phases: listener.phases };
- } ).concat( nodeListeners );
- }
- }
- /// Transform node references in a component descriptor into kernel-style node references. The
- /// resulting object will be suitable for passing to `kernel.createNode`.
- ///
- /// This function must run as a method of the driver. Invoke it as:
- /// `componentKernelFromJS.call( driver, component )`.
- ///
- /// @param {String|Object} component
- /// A component URI or descriptor. A URI will pass through unchanged (as will all descriptor
- /// fields that aren't node references.)
- ///
- /// @returns {String|Object}
- /// `component` with node references replaced with kernel-style node references.
- static componentKernelFromJS( component ) {
- return VWFJavaScript.valueKernelFromJS.call( this, component );
- }
- /// Convert a parameter array of values using `valueKernelFromJS`.
- ///
- /// This function must run as a method of the driver. Invoke it as:
- /// `parametersKernelFromJS.call( driver, parameters )`.
- ///
- /// @param {Object[]} parameters
- ///
- /// @returns {Object}
- static parametersKernelFromJS( parameters ) {
- return VWFJavaScript.valueKernelFromJS.call( this, parameters );
- }
- /// Convert a parameter array of values using `valueJSFromKernel`.
- ///
- /// This function must run as a method of the driver. Invoke it as:
- /// `parametersJSFromKernel.call( driver, parameters )`.
- ///
- /// @param {Object[]} parameters
- ///
- /// @returns {Object}
- static parametersJSFromKernel( parameters ) {
- return VWFJavaScript.valueJSFromKernel.call( this, parameters );
- }
- /// Convert node references into special values that can pass through the kernel. These values
- /// are wrapped in such a way that they won't be confused with any other application value, and
- /// they will be replicated correctly by the kernel.
- ///
- /// Other values are returned unchanged. Use `valueJSFromKernel` to retrieve the original.
- ///
- /// This function must run as a method of the driver. Invoke it as:
- /// `valueKernelFromJS.call( driver, value )`.
- ///
- /// @param {Object} value
- ///
- /// @returns {Object}
- static valueKernelFromJS( value ) {
- var self = this;
- return VWFJavaScript.utility.transform( value, function( object, names, depth, finished ) {
- if ( VWFJavaScript.valueIsNode.call( self, object ) ) {
- finished();
- return VWFJavaScript.kutility.nodeReference( object.id );
- } else if ( object && object.buffer && object.buffer.toString() === "[object ArrayBuffer]" ) {
- finished();
- return object;
- } else if ( VWFJavaScript.kutility.valueIsNodeReference( object ) ) {
- finished();
- self.logger.warnx( "valueKernelFromJS", "javascript-format value contains a kernel-format node reference" );
- return object;
- } else {
- return object;
- }
- } );
- }
- /// Convert values wrapped by `valueKernelFromJS` into their original form for use in the
- /// JavaScript driver's execution environment.
- ///
- /// This function must run as a method of the driver. Invoke it as:
- /// `valueJSFromKernel.call( driver, value )`.
- ///
- /// @param {Object} value
- ///
- /// @returns {Object}
- static valueJSFromKernel( value ) {
- var self = this;
- return VWFJavaScript.utility.transform( value, function( object, names, depth, finished ) {
- if ( VWFJavaScript.kutility.valueIsNodeReference( object ) ) {
- finished();
- return self.nodes[ object.id ];
- } else if ( object && object.buffer && object.buffer.toString() === "[object ArrayBuffer]" ) {
- finished();
- return object;
- } else if ( VWFJavaScript.valueIsNode.call( self, object ) ) {
- finished();
- self.logger.warnx( "valueJSFromKernel", "kernel-format value contains a javascript-format node reference" );
- return object;
- } else {
- return object;
- }
- } );
- }
- /// Determine if a value is a `model/javascript` node.
- ///
- /// This function must run as a method of the driver. Invoke it as:
- /// `valueIsNode.call( driver, value )`.
- ///
- /// @param {Object} value
- ///
- /// @returns {Boolean}
- static valueIsNode( value ) {
- return this.protoNode && // our proxy for the node.vwf prototype
- ( this.protoNode.isPrototypeOf( value ) || value === this.protoNode );
- }
- }
- /// The `application/javascript` media type for scripts that this driver recognizes.
- ///
- /// @field
- VWFJavaScript.scriptMediaType = "application/javascript";
- /// Regex to crack a `Function.toString()` result.
- ///
- /// @field
- VWFJavaScript.functionRegex = new RegExp(
- "function" + // `function`
- "\\s*" +
- "([a-zA-Z_$][0-9a-zA-Z_$]*)?" + // optional name; capture #1
- "\\s*" +
- "\\(([^)]*)\\)" + // `(...)`; capture #2 inside `()`
- "\\s*" +
- "\\{([^]*)\\}" // `{...}`; capture #3 inside `{}`
- );
- VWFJavaScript.kutility = new KUtility();
- VWFJavaScript.utility = new Utility();
- export { VWFJavaScript as default }
|