javascript.js 70 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731
  1. "use strict";
  2. // Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
  3. // Secretary of Defense (Personnel & Readiness).
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  6. // in compliance with the License. You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software distributed under the License
  11. // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  12. // or implied. See the License for the specific language governing permissions and limitations under
  13. // the License.
  14. /// vwf/model/javascript.js is a placeholder for the JavaScript object interface to the
  15. /// simulation.
  16. ///
  17. /// @module vwf/model/javascript
  18. /// @requires vwf/model
  19. /// @requires vwf/kernel/utility
  20. /// @requires vwf/utility
  21. /// @requires vwf/configuration
  22. define( [ "module", "vwf/model", "vwf/kernel/utility", "vwf/utility", "vwf/configuration" ],
  23. function( module, model, kutility, utility, configuration ) {
  24. var exports = model.load( module, {
  25. // This is a placeholder for providing a natural integration between simulation and the
  26. // browser's JavaScript environment.
  27. //
  28. // Within the JavaScript environment, component instances appear as JavaScript objects.
  29. //
  30. // - Properties appear in the "properties" field. Each property contains a getter and
  31. // setter callback to notify the object of property manipulation.
  32. // - Methods appear in "methods".
  33. // - Events appear in "events".
  34. // - "parent" refers to the parent node and "children" is an array of the child nodes.
  35. //
  36. // - Node prototypes use the JavaScript prototype chain.
  37. // - Properties, methods, events, and children may be referenced directly on the node or
  38. // within their respective collections by name when there is no conflict with another
  39. // attribute.
  40. // - Properties support getters and setters that invoke a handler that may influence the
  41. // property access.
  42. // == Module Definition ====================================================================
  43. // -- initialize ---------------------------------------------------------------------------
  44. initialize: function() {
  45. this.nodes = {}; // maps id => new type()
  46. this.protoNode = undefined; // this.nodes[kutility.protoNodeURI] once it exists
  47. 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 */ )?
  48. },
  49. // == Model API ============================================================================
  50. // -- creatingNode -------------------------------------------------------------------------
  51. creatingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
  52. childSource, childType, childIndex, childName, callback /* ( ready ) */ ) {
  53. var self = this;
  54. // Get the prototype node.
  55. var prototype = this.nodes[childExtendsID] || Object.prototype;
  56. // Get the behavior nodes.
  57. var behaviors = ( childImplementsIDs || [] ).map( function( childImplementsID ) {
  58. return self.nodes[childImplementsID];
  59. } );
  60. // For each behavior, create a proxy for this node to the behavior and attach it above
  61. // the prototype, or above the most recently-attached behavior.
  62. behaviors.forEach( function( behavior ) {
  63. prototype = proxiedBehavior.call( self, prototype, behavior );
  64. } );
  65. // Create the node. Its prototype is the most recently-attached behavior, or the
  66. // specific prototype if no behaviors are attached.
  67. var node = this.nodes[childID] = Object.create( prototype );
  68. if ( childID === kutility.protoNodeURI ) {
  69. this.protoNode = node;
  70. }
  71. Object.defineProperty( node, "private", {
  72. value: {} // for bookkeeping, not visible to scripts on the node // TODO: well, ideally not visible; hide this better ("_private", "vwf_private", ?)
  73. } );
  74. Object.defineProperty( node, "id", {
  75. value: childID,
  76. enumerable: true,
  77. } );
  78. Object.defineProperty( node, "uri", { // "this" is node
  79. get: function() {
  80. return self.kernel.uri( this.id );
  81. },
  82. enumerable: true,
  83. } );
  84. node.name = childName;
  85. node.parent = undefined;
  86. Object.defineProperty( node, "parent_", { // "this" is node in get/set
  87. get: function() {
  88. return this.parent;
  89. },
  90. set: function( value ) {
  91. var childIndex;
  92. if ( this.parent ) {
  93. var oldParent = this.parent;
  94. self.kernel.removeChild( this.parent.id, this.id );
  95. childIndex = oldParent.children.indexOf( this );
  96. if ( childIndex != -1 )
  97. oldParent.children.splice( childIndex, 1 );
  98. }
  99. self.kernel.addChild( value.id, this.id, this.name );
  100. this.parent = value;
  101. childIndex = this.parent.children.indexOf( this );
  102. if ( childIndex == -1 )
  103. this.parent.children.push( this );
  104. },
  105. } );
  106. node.source = childSource;
  107. node.type = childType;
  108. Object.defineProperty( node, "logger", {
  109. value: this.logger.for( "#" + ( childName || childIndex || childID ), node ),
  110. enumerable: true,
  111. } );
  112. // Properties.
  113. node.properties = Object.create( prototype.properties || Object.prototype, {
  114. node: { value: node } // for node.properties accessors (non-enumerable) // TODO: hide this better
  115. } );
  116. Object.defineProperty( node.properties, "create", {
  117. value: function( name, value, get, set ) { // "this" is node.properties
  118. return self.kernel.createProperty( this.node.id, name, value, get, set );
  119. }
  120. } );
  121. // Attach the property meta events to `node.properties.{created,initialized,deleted}`.
  122. createEventAccessor.call( this, node.properties, "created", "properties" );
  123. createEventAccessor.call( this, node.properties, "initialized", "properties" );
  124. createEventAccessor.call( this, node.properties, "deleted", "properties" );
  125. node.private.getters = Object.create( prototype.private ?
  126. prototype.private.getters : Object.prototype
  127. );
  128. node.private.setters = Object.create( prototype.private ?
  129. prototype.private.setters : Object.prototype
  130. );
  131. // Methods.
  132. node.methods = Object.create( prototype.methods || Object.prototype, {
  133. node: { value: node } // for node.methods accessors (non-enumerable) // TODO: hide this better
  134. } );
  135. Object.defineProperty( node.methods, "create", {
  136. value: function( name, parameters, body ) { // "this" is node.methods // TODO: also accept create( name, body )
  137. return self.kernel.createMethod( this.node.id, name, parameters, body );
  138. }
  139. } );
  140. // Attach the method meta events to `node.methods.{created,deleted}`.
  141. createEventAccessor.call( this, node.methods, "created", "methods" );
  142. createEventAccessor.call( this, node.methods, "deleted", "methods" );
  143. node.private.bodies = Object.create( prototype.private ?
  144. prototype.private.bodies : Object.prototype
  145. );
  146. // Events.
  147. node.events = Object.create( prototype.events || Object.prototype, {
  148. node: { value: node }, // for node.events accessors (non-enumerable) // TODO: hide this better
  149. } );
  150. // TODO: these only need to be on the base node's events object
  151. Object.defineProperty( node.events, "create", {
  152. value: function( name, parameters ) { // "this" is node.events
  153. return self.kernel.createEvent( this.node.id, name, parameters );
  154. }
  155. } );
  156. // Attach the event meta events to `node.events.{created,deleted}`.
  157. createEventAccessor.call( this, node.events, "created", "events" );
  158. createEventAccessor.call( this, node.events, "deleted", "events" );
  159. // Provide helper functions to create the directives for adding, removing and flushing
  160. // event handlers.
  161. // Add: `node.events.*eventName* =
  162. // node.events.add( handler [, phases ] [, context ] [, callback( listenerID ) ] )`
  163. Object.defineProperty( node.events, "add", {
  164. value: function( handler, phases, context, callback /* listenerID */ ) {
  165. // Interpret `add( handler [, phases|context ], callback )` as
  166. // `add( handler, phases|context|undefined, undefined, callback )`.
  167. if ( typeof context === "function" || context instanceof Function ) {
  168. callback = context;
  169. context = undefined;
  170. } else if ( typeof phases === "function" || phases instanceof Function ) {
  171. callback = phases;
  172. context = phases = undefined;
  173. }
  174. // Interpret `add( handler, context, ... )` as `add( handler, undefined, context, ... )`.
  175. if ( valueIsNode.call( self, phases ) ) {
  176. context = phases;
  177. phases = undefined;
  178. }
  179. return { add: true, handler: handler, phases: phases, context: context, callback: callback };
  180. }
  181. } );
  182. // Remove: `node.events.*eventName* = node.events.remove( listenerID|handler )`
  183. Object.defineProperty( node.events, "remove", {
  184. value: function( listenerID ) {
  185. // For 0.6.23 and earlier, listeners were removed using a direct reference to
  186. // the handler. For 0.6.24 and later a `listenerID` is used. Accept a function
  187. // for compatability with components written for 0.6.23 or earlier. The event
  188. // setter will translate the function to an id.
  189. if ( typeof listenerID === "function" || listenerID instanceof Function ) {
  190. return { remove: true, handler: listenerID };
  191. } else {
  192. return { remove: true, id: listenerID };
  193. }
  194. }
  195. } );
  196. // Flush: `node.events.*eventName* = node.events.flush( context )`
  197. Object.defineProperty( node.events, "flush", {
  198. value: function( context ) {
  199. return { flush: true, context: context };
  200. }
  201. } );
  202. node.private.listeners = {}; // not delegated to the prototype as with getters, setters, and bodies; findListeners() filters recursion
  203. // Children.
  204. 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?
  205. Object.defineProperty( node.children, "node", {
  206. value: node // for node.children accessors (non-enumerable) // TODO: hide this better
  207. } );
  208. Object.defineProperty( node.children, "create", {
  209. value: function( name, component, callback /* ( child ) */ ) { // "this" is node.children
  210. // Interpret `node.children.create( name, callback )` as
  211. // `node.children.create( name, undefined, callback )`.
  212. if ( typeof component === "function" || component instanceof Function ) {
  213. callback = component;
  214. component = undefined;
  215. }
  216. // Accept `node.children.create( name )` and treat it as
  217. // `node.children.create( name, {} )`.
  218. component = component || {};
  219. // Make the call. If a callback is provided, wrap it and translate the ID to a
  220. // node reference.
  221. if ( callback ) {
  222. self.kernel.createChild( this.node.id, name, componentKernelFromJS.call( self, component ), undefined, undefined, function( childID ) {
  223. callback.call( node, self.nodes[childID] );
  224. } );
  225. } else {
  226. return self.kernel.createChild( this.node.id, name, componentKernelFromJS.call( self, component ) );
  227. }
  228. }
  229. } );
  230. Object.defineProperty( node.children, "delete", {
  231. value: function( child ) {
  232. if ( typeof child === "string" ) {
  233. child = this.node.children[ child ];
  234. }
  235. return self.kernel.deleteNode( child.id );
  236. }
  237. } );
  238. // Attach the child meta events to `node.children.{added,removed}`.
  239. createEventAccessor.call( this, node.children, "added", "children" );
  240. createEventAccessor.call( this, node.children, "removed", "children" );
  241. // Define the "random" and "seed" functions.
  242. Object.defineProperty( node, "random", { // "this" is node
  243. value: function() {
  244. return self.kernel.random( this.id );
  245. }
  246. } );
  247. Object.defineProperty( node, "seed", { // "this" is node
  248. value: function( seed ) {
  249. return self.kernel.seed( this.id, seed );
  250. }
  251. } );
  252. // Define the "time", "client", and "moniker" properties.
  253. Object.defineProperty( node, "time", { // TODO: only define on shared "node" prototype?
  254. get: function() {
  255. return self.kernel.time();
  256. },
  257. enumerable: true,
  258. } );
  259. Object.defineProperty( node, "client", { // TODO: only define on shared "node" prototype?
  260. get: function() {
  261. return self.kernel.client();
  262. },
  263. enumerable: true,
  264. } );
  265. Object.defineProperty( node, "moniker", { // TODO: only define on shared "node" prototype?
  266. get: function() {
  267. return self.kernel.moniker();
  268. },
  269. enumerable: true,
  270. } );
  271. Object.defineProperty( node, "find", {
  272. value: function( matchPattern, callback /* ( match ) */ ) { // "this" is node
  273. if ( callback ) {
  274. self.kernel.find( this.id, matchPattern, true, function( matchID ) {
  275. callback.call( node, self.nodes[matchID] );
  276. } );
  277. } else { // TODO: future iterator proxy
  278. var findResults = self.kernel.find( this.id, matchPattern, true );
  279. if ( findResults )
  280. return findResults.map( function( matchID ) {
  281. return self.nodes[matchID];
  282. } );
  283. }
  284. }
  285. } );
  286. Object.defineProperty( node, "test", {
  287. value: function( matchPattern, testNode ) { // "this" is node
  288. return self.kernel.test( this.id, matchPattern, testNode.id, true );
  289. }
  290. } );
  291. // Define a "future" proxy so that for any this.property, this.method, or this.event, we
  292. // can reference this.future( when, callback ).property/method/event and have the
  293. // expression evaluated at the future time.
  294. Object.defineProperty( node, "in", { // TODO: only define on shared "node" prototype?
  295. value: function( when, callback ) { // "this" is node
  296. return refreshedFuture.call( self, this, -when, callback ); // relative time
  297. },
  298. enumerable: true,
  299. } );
  300. Object.defineProperty( node, "at", { // TODO: only define on shared "node" prototype?
  301. value: function( when, callback ) { // "this" is node
  302. return refreshedFuture.call( self, this, when, callback ); // absolute time
  303. },
  304. enumerable: true,
  305. } );
  306. Object.defineProperty( node, "future", { // same as "in" // TODO: only define on shared "node" prototype?
  307. get: function() {
  308. return this.in;
  309. },
  310. enumerable: true,
  311. } );
  312. node.private.future = Object.create( prototype.private ?
  313. prototype.private.future : Object.prototype
  314. );
  315. Object.defineProperty( node.private.future, "private", {
  316. value: {
  317. when: 0,
  318. callback: undefined,
  319. change: 0,
  320. }
  321. } );
  322. node.private.change = 1; // incremented whenever "future"-related changes occur
  323. },
  324. // -- initializingNode ---------------------------------------------------------------------
  325. // Invoke an initialize() function if one exists.
  326. initializingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
  327. childSource, childType, childIndex, childName ) {
  328. var node = this.nodes[nodeID];
  329. var child = this.nodes[childID];
  330. var scriptText =
  331. "this.hasOwnProperty( 'initialize' ) && " +
  332. "( typeof this.initialize === 'function' || this.initialize instanceof Function ) && " +
  333. "this.initialize()";
  334. // Call the child's initializer.
  335. try {
  336. ( function( scriptText ) { return eval( scriptText ) } ).call( child, scriptText );
  337. } catch ( e ) {
  338. this.logger.warnx( "initializingNode", childID,
  339. "exception in initialize:", utility.exceptionMessage( e ) );
  340. }
  341. // The node is fully initialized at this point
  342. // Link to the parent.
  343. //
  344. // The parent reference is only defined once the node is fully initialized.
  345. // It is not defined earlier since components should be able to stand alone
  346. // without depending on external nodes.
  347. //
  348. // Additionally, since parts of the application may become ready in a different
  349. // order on other clients, referring to properties in other parts of the
  350. // application may lead to consistency errors.
  351. child.parent = node;
  352. if ( node ) {
  353. node.children[childIndex] = child;
  354. if ( parseInt( childName ).toString() !== childName ) {
  355. node.children[childName] = child;
  356. }
  357. 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
  358. ( node[childName] = child );
  359. }
  360. return undefined;
  361. },
  362. // -- initializingNodeFromPrototype --------------------------------------------------------
  363. // Invoke an initialize() function from `childInitializingNodeID` on `childID` if one exists.
  364. initializingNodeFromPrototype: function( nodeID, childID, childInitializingNodeID ) {
  365. var child = this.nodes[childID];
  366. var initializer = this.nodes[childInitializingNodeID];
  367. // Call the prototype's initializer on the child.
  368. try {
  369. var prototypeHasInitialize = ( initializer.hasOwnProperty( 'initialize' ) &&
  370. ( typeof initializer.initialize === 'function' ||
  371. initializer.initialize instanceof Function ) );
  372. if ( prototypeHasInitialize ) {
  373. return initializer.initialize.call( child );
  374. }
  375. } catch ( e ) {
  376. this.logger.warnx( "initializingNodeFromPrototype", childID,
  377. "exception in initialize:", utility.exceptionMessage( e ) );
  378. }
  379. return undefined;
  380. },
  381. // -- deletingNode -------------------------------------------------------------------------
  382. deletingNode: function( nodeID ) {
  383. var child = this.nodes[nodeID];
  384. var node = child.parent;
  385. if ( node ) {
  386. var index = node.children.indexOf( child );
  387. if ( index >= 0 ) {
  388. node.children.splice( index, 1 );
  389. }
  390. delete node.children[child.name]; // TODO: conflict if childName is parseable as a number
  391. if ( node[child.name] === child ) {
  392. 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
  393. }
  394. child.parent = undefined;
  395. }
  396. delete this.nodes[nodeID];
  397. },
  398. // -- addingChild --------------------------------------------------------------------------
  399. addingChild: function( nodeID, childID, childName ) {
  400. },
  401. // -- removingChild ------------------------------------------------------------------------
  402. removingChild: function( nodeID, childID ) {
  403. var node = this.nodes[nodeID];
  404. var child = this.nodes[childID];
  405. child.parent = undefined;
  406. if ( node ) {
  407. node.children.splice( node.children.indexOf( child ), 1 );
  408. 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
  409. 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
  410. }
  411. },
  412. // -- creatingProperty ---------------------------------------------------------------------
  413. creatingProperty: function( nodeID, propertyName, propertyValue, propertyGet, propertySet ) {
  414. var node = this.nodes[nodeID];
  415. var self = this;
  416. var getter = propertyGet && // TODO: assuming javascript here; how to specify script type?
  417. functionFromHandler( { body: propertyGet, type: scriptMediaType },
  418. logGetterException );
  419. if ( getter ) {
  420. node.private.getters[ propertyName ] = getter;
  421. } else {
  422. node.private.getters[ propertyName ] = true; // set a guard value so that we don't call prototype getters on value properties
  423. }
  424. var setter = propertySet && // TODO: assuming javascript here; how to specify script type?
  425. functionFromHandler( { parameters: [ "value" ], body: propertySet, type: scriptMediaType },
  426. logSetterException );
  427. if ( setter ) {
  428. node.private.setters[ propertyName ] = setter;
  429. } else {
  430. node.private.setters[ propertyName ] = true; // set a guard value so that we don't call prototype setters on value properties
  431. }
  432. function logGetterException( exception ) {
  433. self.logger.warnx( "creatingProperty", nodeID, propertyName, propertyValue,
  434. "exception evaluating getter:", utility.exceptionMessage( exception ) );
  435. }
  436. function logSetterException( exception ) {
  437. self.logger.warnx( "creatingProperty", nodeID, propertyName, propertyValue,
  438. "exception evaluating setter:", utility.exceptionMessage( exception ) );
  439. }
  440. return this.initializingProperty( nodeID, propertyName, propertyValue );
  441. },
  442. // -- initializingProperty -----------------------------------------------------------------
  443. initializingProperty: function( nodeID, propertyName, propertyValue ) {
  444. var node = this.nodes[nodeID];
  445. createPropertyAccessor.call( this, node.properties, propertyName );
  446. 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
  447. createPropertyAccessor.call( this, node, propertyName );
  448. // Invalidate the "future" cache.
  449. node.private.change++;
  450. return propertyValue !== undefined ?
  451. this.settingProperty( nodeID, propertyName, propertyValue ) : undefined;
  452. },
  453. // TODO: deletingProperty
  454. // -- settingProperty ----------------------------------------------------------------------
  455. settingProperty: function( nodeID, propertyName, propertyValue ) {
  456. var node = this.nodes[nodeID];
  457. if ( ! node ) return; // TODO: patch until full-graph sync is working; drivers should be able to assume that nodeIDs refer to valid objects
  458. var setter = node.private.setters && node.private.setters[propertyName];
  459. if ( setter && setter !== true ) { // is there is a setter (and not just a guard value)
  460. try {
  461. var valueJS = valueJSFromKernel.call( this, propertyValue );
  462. var resultJS = setter.call( node, valueJS );
  463. return valueKernelFromJS.call( this, resultJS );
  464. } catch ( e ) {
  465. this.logger.warnx( "settingProperty", nodeID, propertyName, propertyValue,
  466. "exception in setter:", utility.exceptionMessage( e ) );
  467. }
  468. }
  469. return undefined;
  470. },
  471. // -- gettingProperty ----------------------------------------------------------------------
  472. gettingProperty: function( nodeID, propertyName, propertyValue ) {
  473. var node = this.nodes[nodeID];
  474. var getter = node.private.getters && node.private.getters[propertyName];
  475. if ( getter && getter !== true ) { // is there is a getter (and not just a guard value)
  476. try {
  477. var resultJS = getter.call( node );
  478. return valueKernelFromJS.call( this, resultJS );
  479. } catch ( e ) {
  480. this.logger.warnx( "gettingProperty", nodeID, propertyName, propertyValue,
  481. "exception in getter:", utility.exceptionMessage( e ) );
  482. }
  483. }
  484. return undefined;
  485. },
  486. // -- creatingMethod -----------------------------------------------------------------------
  487. creatingMethod: function( nodeID, methodName, methodParameters, methodBody ) {
  488. var node = this.nodes[nodeID];
  489. createMethodAccessor.call( this, node.methods, methodName );
  490. 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
  491. createMethodAccessor.call( this, node, methodName );
  492. // Invalidate the "future" cache.
  493. node.private.change++;
  494. // Delegate to `settingMethod`.
  495. return this.settingMethod( nodeID, methodName, {
  496. parameters: methodParameters,
  497. body: methodBody,
  498. type: typeof methodBody === "string" || methodBody instanceof String ?
  499. scriptMediaType : undefined, // TODO: heuristic duplicated in vwf.js `normalizedHandler`.
  500. } );
  501. },
  502. // TODO: deletingMethod
  503. // -- settingMethod ------------------------------------------------------------------------
  504. settingMethod: function( nodeID, methodName, methodHandler ) {
  505. var node = this.nodes[nodeID];
  506. var self = this;
  507. var body = functionFromHandler( methodHandler, logException,
  508. configuration.active[ "preserve-script-closures" ] );
  509. if ( body ) {
  510. node.private.bodies[ methodName ] = body;
  511. return handlerFromFunction( body ); // TODO: shortcut to avoid retrieving this?
  512. } else {
  513. delete node.private.bodies[ methodName ];
  514. }
  515. function logException( exception ) {
  516. self.logger.warnx( "settingMethod", nodeID, methodName, methodHandler.parameters,
  517. "exception evaluating body:", utility.exceptionMessage( exception ) );
  518. }
  519. return undefined;
  520. },
  521. // -- gettingMethod ------------------------------------------------------------------------
  522. gettingMethod: function( nodeID, methodName ) {
  523. var node = this.nodes[nodeID];
  524. var body = node.private.bodies && node.private.bodies[methodName];
  525. if ( body ) {
  526. return handlerFromFunction( body );
  527. }
  528. return undefined;
  529. },
  530. // -- callingMethod ------------------------------------------------------------------------
  531. callingMethod: function( nodeID, methodName, methodParameters ) {
  532. var node = this.nodes[nodeID];
  533. var body = node.private.bodies && node.private.bodies[methodName];
  534. if ( body ) {
  535. try {
  536. var parametersJS = parametersJSFromKernel.call( this, methodParameters );
  537. var resultJS = body.apply( node, parametersJS );
  538. return valueKernelFromJS.call( this, resultJS );
  539. } catch ( e ) {
  540. this.logger.warnx( "callingMethod", nodeID, methodName, methodParameters, // TODO: limit methodParameters for log
  541. "exception:", utility.exceptionMessage( e ) );
  542. }
  543. }
  544. return undefined;
  545. },
  546. // -- creatingEvent ------------------------------------------------------------------------
  547. creatingEvent: function( nodeID, eventName, eventParameters ) {
  548. var node = this.nodes[nodeID];
  549. createEventAccessor.call( this, node.events, eventName );
  550. 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
  551. createEventAccessor.call( this, node, eventName );
  552. // Invalidate the "future" cache.
  553. node.private.change++;
  554. },
  555. // -- addingEventListener ------------------------------------------------------------------
  556. addingEventListener: function( nodeID, eventName, eventListenerID, eventHandler, eventContextID, eventPhases ) {
  557. var node = this.nodes[nodeID];
  558. // Create the listeners collection if this is the first listener added for an event on a
  559. // prototype.
  560. if ( ! node.private.listeners[eventName] ) {
  561. node.private.listeners[eventName] = [];
  562. }
  563. // Build a `Listener` from the `Handler` and the context and phases.
  564. var eventListener = utility.merge( eventHandler, {
  565. context: eventContextID,
  566. phases: eventPhases,
  567. } );
  568. // Delegate to `settingEventListener`.
  569. return this.settingEventListener( nodeID, eventName, eventListenerID, eventListener ) ?
  570. true : undefined;
  571. },
  572. // -- removingEventListener ----------------------------------------------------------------
  573. removingEventListener: function( nodeID, eventName, eventListenerID ) {
  574. var node = this.nodes[nodeID];
  575. var listeners = node.private.listeners[eventName];
  576. if ( listeners && listeners[ eventListenerID ] ) {
  577. delete listeners[ eventListenerID ];
  578. return true;
  579. }
  580. return undefined;
  581. },
  582. // -- settingEventListener -----------------------------------------------------------------
  583. settingEventListener: function( nodeID, eventName, eventListenerID, eventListener ) {
  584. var node = this.nodes[nodeID];
  585. var self = this;
  586. var listeners = node.private.listeners[eventName];
  587. var handler = functionFromHandler( eventListener, logException,
  588. configuration.active[ "preserve-script-closures" ] );
  589. if ( handler ) {
  590. listeners[ eventListenerID ] = {
  591. handler: handler,
  592. context: eventListener.context,
  593. phases: eventListener.phases,
  594. };
  595. // Kernel actions that set a value allow the driver to modify the value assigned.
  596. // The result of the action is the actually-assigned value reported by the driver.
  597. // Here, we should return a `Listener` derived from the function we just rendered so
  598. // that `kernel.setEvent` will return the same result that a following
  599. // `kernel.getEvent` would. However, since `Function.toString` is relatively heavy,
  600. // we'll just return the incoming value until there is a demonstrated need for the
  601. // precise result.
  602. // return utility.merge( handlerFromFunction( listener.handler ), {
  603. // context: listener.context,
  604. // phases: listener.phases,
  605. // } );
  606. return eventListener;
  607. } else {
  608. delete listeners[ eventListenerID ];
  609. }
  610. function logException( exception ) {
  611. self.logger.warnx( "settingEventListener", nodeID, eventName, eventListenerID,
  612. "exception evaluating listener:", utility.exceptionMessage( exception ) );
  613. }
  614. return undefined;
  615. },
  616. // -- gettingEventListener -----------------------------------------------------------------
  617. gettingEventListener: function( nodeID, eventName, eventListenerID ) {
  618. var node = this.nodes[nodeID];
  619. var listeners = node.private.listeners[eventName];
  620. if ( listeners ) {
  621. var listener = listeners[ eventListenerID ];
  622. return utility.merge( handlerFromFunction( listener.handler ), {
  623. context: listener.context,
  624. phases: listener.phases,
  625. } );
  626. }
  627. return undefined;
  628. },
  629. // -- flushingEventListeners ---------------------------------------------------------------
  630. flushingEventListeners: function( nodeID, eventName, eventContextID ) {
  631. // Prepare the return value
  632. var removedListenerIDs = [];
  633. // Extract the listeners of the specified node
  634. var node = this.nodes[ nodeID ];
  635. var listeners = node.private.listeners[ eventName ];
  636. // If listeners exist for the given eventName, loop through them, removing any for which
  637. // the context is the node specified by the parameter eventContextID
  638. if ( listeners ) {
  639. listeners.forEach( function( listener, listenerID ) {
  640. if ( listener.context === eventContextID ) {
  641. delete listeners[ listenerID ];
  642. removedListenerIDs.push( listenerID );
  643. }
  644. } );
  645. }
  646. return removedListenerIDs;
  647. },
  648. // -- firingEvent --------------------------------------------------------------------------
  649. firingEvent: function( nodeID, eventName, eventParameters ) {
  650. 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
  651. var node = this.nodes[nodeID];
  652. var listeners = findListeners( node, eventName );
  653. var parametersJS = parametersJSFromKernel.call( this, eventParameters );
  654. var self = this;
  655. // Call the handlers registered for the event, and calculate the logical OR of each
  656. // result. Normally, callers to fireEvent() ignore the handler result, but dispatched
  657. // events use the return value to determine when an event has been handled as it bubbles
  658. // up from its target.
  659. var handled = listeners && listeners.reduce( function( handled, listener ) {
  660. // Call the handler. If a phase is provided, only call handlers tagged for that
  661. // phase.
  662. try {
  663. if ( ! phase || listener.phases && listener.phases.indexOf( phase ) >= 0 ) {
  664. 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
  665. var resultJS = listener.handler.apply( contextNode, parametersJS );
  666. var result = valueKernelFromJS.call( self, resultJS );
  667. return handled || result || result === undefined; // interpret no return as "return true"
  668. }
  669. } catch ( e ) {
  670. self.logger.warnx( "firingEvent", nodeID, eventName, eventParameters, // TODO: limit eventParameters for log
  671. "exception:", utility.exceptionMessage( e ) );
  672. }
  673. return handled;
  674. }, false );
  675. return handled;
  676. },
  677. // -- executing ----------------------------------------------------------------------------
  678. executing: function( nodeID, scriptText, scriptType ) {
  679. var node = this.nodes[nodeID];
  680. if ( scriptType == scriptMediaType ) {
  681. try {
  682. var resultJS = ( function( scriptText ) { return eval( scriptText ) } ).call( node, scriptText || "" );
  683. return valueKernelFromJS.call( this, resultJS );
  684. } catch ( e ) {
  685. this.logger.warnx( "executing", nodeID,
  686. ( scriptText || "" ).replace( /\s+/g, " " ).substring( 0, 100 ), scriptType, "exception:", utility.exceptionMessage( e ) );
  687. }
  688. }
  689. return undefined;
  690. },
  691. } );
  692. // == Private functions ========================================================================
  693. // -- proxiedBehavior --------------------------------------------------------------------------
  694. function 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
  695. var self = this;
  696. var proxy = Object.create( prototype );
  697. Object.defineProperty( proxy, "private", {
  698. value: {}
  699. } );
  700. proxy.private.origin = behavior; // the node we're the proxy for
  701. Object.defineProperty( proxy, "id", {
  702. value: behavior.id,
  703. enumerable: true,
  704. } );
  705. proxy.name = behavior.name;
  706. proxy.parent = behavior.parent;
  707. proxy.source = behavior.source;
  708. proxy.type = behavior.type;
  709. proxy.initialize = behavior.initialize;
  710. proxy.properties = Object.create( prototype.properties || Object.prototype, {
  711. node: { value: proxy } // for proxy.properties accessors (non-enumerable) // TODO: hide this better
  712. } );
  713. proxy.private.getters = Object.create( prototype.private ?
  714. prototype.private.getters : Object.prototype
  715. );
  716. proxy.private.setters = Object.create( prototype.private ?
  717. prototype.private.setters : Object.prototype
  718. );
  719. for ( var propertyName in behavior.properties ) {
  720. if ( behavior.properties.hasOwnProperty( propertyName ) ) {
  721. ( function( propertyName ) {
  722. createPropertyAccessor.call( self, proxy.properties, propertyName );
  723. 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
  724. createPropertyAccessor.call( self, proxy, propertyName );
  725. } )( propertyName );
  726. if ( behavior.private.getters.hasOwnProperty( propertyName ) ) {
  727. proxy.private.getters[propertyName] = behavior.private.getters[propertyName];
  728. }
  729. if ( behavior.private.setters.hasOwnProperty( propertyName ) ) {
  730. proxy.private.setters[propertyName] = behavior.private.setters[propertyName];
  731. }
  732. }
  733. }
  734. proxy.methods = Object.create( prototype.methods || Object.prototype, {
  735. node: { value: proxy } // for proxy.methods accessors (non-enumerable) // TODO: hide this better
  736. } );
  737. proxy.private.bodies = Object.create( prototype.private ?
  738. prototype.private.bodies : Object.prototype
  739. );
  740. for ( var methodName in behavior.methods ) {
  741. if ( behavior.methods.hasOwnProperty( methodName ) ) {
  742. ( function( methodName ) {
  743. createMethodAccessor.call( self, proxy.methods, methodName );
  744. 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
  745. createMethodAccessor.call( self, proxy, methodName );
  746. } )( methodName );
  747. if ( behavior.private.bodies.hasOwnProperty( methodName ) ) {
  748. proxy.private.bodies[methodName] = behavior.private.bodies[methodName];
  749. }
  750. }
  751. }
  752. proxy.events = Object.create( prototype.events || Object.prototype, {
  753. node: { value: proxy } // for proxy.events accessors (non-enumerable) // TODO: hide this better
  754. } );
  755. proxy.private.listeners = {}; // not delegated to the prototype as with getters, setters, and bodies; findListeners() filters recursion
  756. for ( var eventName in behavior.events ) {
  757. if ( behavior.events.hasOwnProperty( eventName ) ) {
  758. ( function( eventName ) {
  759. createEventAccessor.call( self, proxy.events, eventName );
  760. 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
  761. createEventAccessor.call( self, proxy, eventName );
  762. } )( eventName );
  763. }
  764. }
  765. 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
  766. if ( behavior.private.listeners.hasOwnProperty( eventName ) ) {
  767. proxy.private.listeners[eventName] = behavior.private.listeners[eventName];
  768. }
  769. }
  770. proxy.private.future = Object.create( prototype.private ?
  771. prototype.private.future : Object.prototype
  772. );
  773. Object.defineProperty( proxy.private.future, "private", {
  774. value: {
  775. when: 0,
  776. callback: undefined,
  777. change: 0,
  778. }
  779. } );
  780. proxy.private.change = behavior.private.change;
  781. return proxy;
  782. }
  783. // -- refreshedFuture --------------------------------------------------------------------------
  784. function refreshedFuture( node, when, callback ) { // invoke with the model as "this"
  785. var self = this;
  786. if ( Object.getPrototypeOf( node ).private ) {
  787. refreshedFuture.call( this, Object.getPrototypeOf( node ) );
  788. }
  789. var future = node.private.future;
  790. future.private.when = when;
  791. future.private.callback = callback; // TODO: would like to be able to remove this reference after the future call has completed
  792. if ( future.private.change < node.private.change ) { // only if out of date
  793. Object.defineProperty( future, "id", {
  794. value: node.id,
  795. enumerable: true,
  796. } );
  797. future.properties = Object.create( Object.getPrototypeOf( future ).properties || Object.prototype, {
  798. node: { value: future } // for future.properties accessors (non-enumerable) // TODO: hide this better
  799. } );
  800. for ( var propertyName in node.properties ) {
  801. if ( node.properties.hasOwnProperty( propertyName ) ) {
  802. ( function( propertyName ) {
  803. createPropertyAccessor.call( self, future.properties, propertyName );
  804. future.hasOwnProperty( propertyName ) || // TODO: calculate so that properties take precedence over methods over events, for example
  805. createPropertyAccessor.call( self, future, propertyName );
  806. } )( propertyName );
  807. }
  808. }
  809. future.methods = Object.create( Object.getPrototypeOf( future ).methods || Object.prototype, {
  810. node: { value: future } // for future.methods accessors (non-enumerable) // TODO: hide this better
  811. } );
  812. for ( var methodName in node.methods ) {
  813. if ( node.methods.hasOwnProperty( methodName ) ) {
  814. ( function( methodName ) {
  815. createMethodAccessor.call( self, future.methods, methodName, true );
  816. future.hasOwnProperty( methodName ) || // TODO: calculate so that properties take precedence over methods over events, for example
  817. createMethodAccessor.call( self, future, methodName, true );
  818. } )( methodName );
  819. }
  820. }
  821. future.events = Object.create( Object.getPrototypeOf( future ).events || Object.prototype, {
  822. node: { value: future } // for future.events accessors (non-enumerable) // TODO: hide this better
  823. } );
  824. for ( var eventName in node.events ) {
  825. if ( node.events.hasOwnProperty( eventName ) ) {
  826. ( function( eventName ) {
  827. createEventAccessor.call( self, future.events, eventName, undefined, true );
  828. future.hasOwnProperty( eventName ) || // TODO: calculate so that properties take precedence over methods over events, for example
  829. createEventAccessor.call( self, future, eventName, undefined, true );
  830. } )( eventName );
  831. }
  832. }
  833. future.private.change = node.private.change;
  834. }
  835. return future;
  836. }
  837. /// Define a (JavaScript) accessor property on a node or a node's `properties` collection that
  838. /// will manipulate a (VWF) property on the node.
  839. ///
  840. /// Reading `node.properties.name` invokes the `get` accessor, which calls `kernel.getProperty`
  841. /// to return the value for the property on the node. Writing `node.properties.name` invokes
  842. /// the `set` accessor, which calls `kernel.setProperty` to assign a new value to the property
  843. /// on its node.
  844. ///
  845. /// This function must run as a method of the driver. Invoke it as:
  846. /// `createPropertyAccessor.call( driver, container, propertyName )`.
  847. ///
  848. /// @param {Object} container
  849. /// A `node` or `node.properties` object to receive the property.
  850. /// @param {String} propertyName
  851. /// The name of the property to create on `container`.
  852. function createPropertyAccessor( container, propertyName ) {
  853. var self = this;
  854. Object.defineProperty( container, propertyName, {
  855. // On read, call `kernel.getProperty` and return the result.
  856. get: function() { // `this` is the container
  857. var node = this.node || this; // the node via node.properties.node, or just node
  858. var resultKernel = self.kernel.getProperty( node.id, propertyName,
  859. node.private.when, node.private.callback );
  860. return valueJSFromKernel.call( self, resultKernel );
  861. },
  862. // On write, pass the assigned value to `kernel.setProperty`.
  863. set: function( value ) { // `this` is the container
  864. var node = this.node || this; // the node via node.properties.node, or just node
  865. var valueKernel = valueKernelFromJS.call( self, value );
  866. self.kernel.setProperty( node.id, propertyName, valueKernel,
  867. node.private.when, node.private.callback );
  868. },
  869. enumerable: true,
  870. } );
  871. }
  872. /// Define an accessor property on a node or a node's `methods` collection that manipulates a
  873. /// method on the node.
  874. ///
  875. /// Reading `node.methods.name` returns a function that when called calls `kernel.callMethod` to
  876. /// invoke the method on the node. Writing a function object to `node.methods.name` will set the
  877. /// method body to the assigned function.
  878. ///
  879. /// This function must run as a method of the driver. Invoke it as:
  880. /// `createMethodAccessor.call( driver, container, methodName )`.
  881. ///
  882. /// @param {Object} container
  883. /// A `node` or `node.methods` object to receive the property.
  884. /// @param {String} methodName
  885. /// The name of the property to create on `container`.
  886. /// @param {Boolean} [unsettable]
  887. /// When truthy, don't create the `set` accessor. An unsettable method property doesn't allow
  888. /// the method body to be changed.
  889. function createMethodAccessor( container, methodName, unsettable ) {
  890. var self = this;
  891. Object.defineProperty( container, methodName, {
  892. // On read, return a function that calls `kernel.callMethod` when invoked.
  893. get: function() { // `this` is the container
  894. var node = this.node || this; // the node via node.methods.node, or just node
  895. return function( /* parameter1, parameter2, ... */ ) { // `this` is the container
  896. var argumentsKernel = parametersKernelFromJS.call( self, arguments );
  897. var resultKernel = self.kernel.callMethod( node.id, methodName, argumentsKernel,
  898. node.private.when, node.private.callback );
  899. return valueJSFromKernel.call( self, resultKernel );
  900. };
  901. },
  902. // On write, update the method body. `unsettable` methods don't accept writes.
  903. set: unsettable ? undefined : function( value ) { // `this` is the container
  904. var node = this.node || this; // the node via node.methods.node, or just node
  905. self.kernel.setMethod( node.id, methodName,
  906. handlerFromFunction( value, configuration.active[ "preserve-script-closures" ] ) );
  907. },
  908. enumerable: true,
  909. } );
  910. }
  911. /// Define an accessor property on a node or a node's `events` collection that manipulates an
  912. /// event on the node. `createEventAccessor` is also used to define accessor properties at other
  913. /// locations on the node to expose the node's meta events.
  914. ///
  915. /// Reading `node.events.name` returns a function that when called calls `kernel.fireEvent` to
  916. /// fire the event from the node. Writing a function object to `node.events.name` will add the
  917. /// assigned function as a new listener using default parameters. To add a listener with
  918. /// specified paramters, call the `node.events.add` helper function and assign the result to
  919. /// `node.events.name`. Remove a listener by calling the `node.events.remove` helper and
  920. /// assigning the result. Flush a set of listeners with the `node.events.flush` helper.
  921. ///
  922. /// This function must run as a method of the driver. Invoke it as:
  923. /// `createEventAccessor.call( driver, container, eventName [, eventNamespace ] [, unsettable ] )`.
  924. ///
  925. /// @param {Object} container
  926. /// A `node` or `node.events` object to receive the property. Meta events will attach to
  927. /// `node.properties`, `node.methods`, `node.events`, and `node.children` as well.
  928. /// @param {String} eventName
  929. /// The name of the property to create on `container`.
  930. /// @param {String} [eventNamespace]
  931. /// For meta events, the namespace associated with the event.
  932. /// @param {Boolean} [unsettable]
  933. /// When truthy, don't create the `set` accessor. An unsettable event property can't add or
  934. /// remove listeners.
  935. function createEventAccessor( container, eventName, eventNamespace, unsettable ) {
  936. var self = this;
  937. Object.defineProperty( container, eventName, {
  938. // On read, return a function that calls `kernel.fireEvent` when invoked. Namespaced
  939. // events (which are meta events and controlled by the kernel) are ungettable and can't
  940. // be fired by the application.
  941. get: eventNamespace ? undefined : function() { // `this` is the container
  942. var node = this.node || this; // the node via node.*collection*.node, or just node
  943. return function( /* parameter1, parameter2, ... */ ) { // `this` is the container
  944. var argumentsKernel = parametersKernelFromJS.call( self, arguments );
  945. var resultKernel = self.kernel.fireEvent( node.id, eventName, argumentsKernel,
  946. node.private.when, node.private.callback );
  947. return valueJSFromKernel.call( self, resultKernel );
  948. };
  949. },
  950. // On write, update the listeners. `unsettable` events don't accept writes.
  951. set: unsettable ? undefined : function( value ) { // `this` is the container
  952. var node = this.node || this; // the node via node.*collection*.node, or just node
  953. var namespacedName = eventNamespace ? [ eventNamespace, eventName ] : eventName;
  954. if ( typeof value === "function" || value instanceof Function ) {
  955. // `container.*eventName* = handler` (context is the target node).
  956. addListener( value, node );
  957. } else if ( value.add ) {
  958. // `container.*eventName* = node.events.add( handler, phases, context, callback /* listenerID */ )`.
  959. if ( ! value.phases || value.phases instanceof Array ) {
  960. addListener( value.handler, value.context, value.phases, value.callback );
  961. } else {
  962. addListener( value.handler, value.context, [ value.phases ], value.callback );
  963. }
  964. } else if ( value.remove ) {
  965. // `container.*eventName* = node.events.remove( listenerID|handler )`.
  966. // For `node.events.remove( listenerID )`, remove using the direct parameter.
  967. // For `node.events.remove( handler )`, use the id that `addListener` attached
  968. // to the handler.
  969. self.kernel.removeEventListener( node.id, namespacedName,
  970. value.handler ? value.handler.listenerID : value.id );
  971. } else if ( value.flush ) {
  972. // `container.*eventName* = node.events.flush( context )`.
  973. self.kernel.flushEventListeners( node.id, namespacedName,
  974. value.context && value.context.id );
  975. }
  976. function addListener( handler, context, phases, callback ) {
  977. var listenerID = self.kernel.addEventListener( node.id, namespacedName,
  978. handlerFromFunction( handler, configuration.active[ "preserve-script-closures" ] ),
  979. context && context.id, phases );
  980. // For 0.6.23 and earlier, listeners were removed using a direct reference to
  981. // the handler. For backward compatability, tag the handler with the listener id
  982. // so that we can retrieve the id if the listener is removed by handler.
  983. handler.listenerID = listenerID;
  984. callback && callback.call( node, listenerID );
  985. }
  986. },
  987. // Meta events--including the `properties`, `methods`, and `events` `created` and
  988. // `deleted` events, and the `children` `added` and `removed` events--are not
  989. // enumerable.
  990. enumerable: ! eventNamespace,
  991. } );
  992. }
  993. /// Convert a `Handler` to a JavaScript function.
  994. ///
  995. /// @param {Handler} handler
  996. /// A `Handler` to convert to a function.
  997. /// @param {function} [errback]
  998. /// If `errback` is provided, any exception that occurs during the conversion will be passed
  999. /// to `errback` as `errback( exception )`.
  1000. /// @param {boolean} [bypass]
  1001. /// Expect that `handler.body` is a function object instead of the string representation of
  1002. /// the function body. Return the function without any conversion. This parameter should only
  1003. /// be used in support of the backwards-compatability `preserve-script-closures` configuration
  1004. /// option.
  1005. ///
  1006. /// @returns {function|undefined}
  1007. /// The function generated from `handler`, or `undefined` if `handler` does not describe a
  1008. /// JavaScript function or if the function could not be converted.
  1009. function functionFromHandler( handler, errback /* exception */, bypass ) {
  1010. if ( bypass && ( typeof handler.body === "function" || handler.body instanceof Function ) ) {
  1011. return handler.body;
  1012. } else if ( handler.type === scriptMediaType ) {
  1013. var name = handler.name, parameters = handler.parameters, body = handler.body;
  1014. var parameterString = parameters && parameters.length ?
  1015. " " + parameters.join( ", " ) + " " :
  1016. "";
  1017. var prefix = "function(" + parameterString + ") {";
  1018. var suffix = "}";
  1019. var functionString, indentedBody;
  1020. if ( body && body.length ) {
  1021. if ( body.charAt( body.length-1 ) === "\n" ) {
  1022. indentedBody = body.match( /^[^\S\n]/ ) ? body : body.replace( /^./gm, " $&" );
  1023. functionString = prefix + "\n" + indentedBody + suffix + "\n";
  1024. } else {
  1025. functionString = prefix + " " + body + " " + suffix;
  1026. }
  1027. } else {
  1028. functionString = prefix + suffix;
  1029. }
  1030. try {
  1031. return eval( "( " + functionString + ")" );
  1032. } catch( exception ) {
  1033. errback && errback( exception );
  1034. }
  1035. }
  1036. return undefined;
  1037. }
  1038. /// Convert a JavaScript `function` to a `Handler`.
  1039. ///
  1040. /// @param {function} funcshun
  1041. /// A function to convert to a `Handler`.
  1042. /// @param {boolean} [bypass]
  1043. /// Create an object having the form of a `Handler`, but with the `body` field set to the
  1044. /// function object instead of the string representation of the function body. The `name`,
  1045. /// `parameters`, and `type` fields will not be set. This parameter should only be used in
  1046. /// support of the backwards-compatability `preserve-script-closures` configuration option.
  1047. ///
  1048. /// @returns {Handler|undefined}
  1049. /// The `Handler` generated from `funcshun`, or `undefined` if the function's `toString` could
  1050. /// not be parsed.
  1051. function handlerFromFunction( funcshun, bypass ) {
  1052. var name, parameters, body, type = scriptMediaType;
  1053. var match, leadingMatch, trailingMatch, indention = "";
  1054. if ( bypass ) {
  1055. return {
  1056. body: funcshun,
  1057. };
  1058. } else if ( match = /* assignment! */ functionRegex.exec( funcshun.toString() ) ) {
  1059. name = match[1];
  1060. // Trim the parameter string. Also remove the `/**/` that Chrome adds to the parameter
  1061. // list for functions created using `Function( parameter, ..., body )`. See
  1062. // `NewFunctionString` in http://code.google.com/p/v8/source/browse/trunk/src/v8natives.js.
  1063. var parameterString = match[2].replace( /\/\*.*\*\//, "" ).trim();
  1064. parameters = parameterString.length ? parameterString.split( "," ).map( function( parameter ) {
  1065. return parameter.trim();
  1066. } ) : undefined;
  1067. // Trim the body string. Recognize block vs. inline formatting where possible and retain
  1068. // the existing spacing.
  1069. body = match[3];
  1070. leadingMatch = // leading spacing, if the leading brace is on its own line
  1071. body.match( /^([^\S\n]*\n)([^\S\n]*)/ );
  1072. trailingMatch = // trailing spacing, if the trailing brace is on own line
  1073. body.match( /\n([^\S\n]*)$/ );
  1074. // Trim the leading spaces. If the leading brace was on its own line, delete the empty
  1075. // first line and take the body indention to be the next line's spacing. Otherwise, just
  1076. // trim the beginning of the body.
  1077. if ( leadingMatch ) {
  1078. body = body.substr( leadingMatch[1].length );
  1079. indention = leadingMatch[2];
  1080. } else {
  1081. body = body.replace( /^\s*/, "" );
  1082. }
  1083. // Trim the trailing spaces. If the trailing brace was on its own line, delete its
  1084. // indention and take that as the body indention. The trailing brace indention takes
  1085. // priority over indention taken from the leading line. If the trailing brace was not on
  1086. // its own line, just trim the end of the body.
  1087. if ( trailingMatch ) {
  1088. body = body.substr( -trailingMatch[1] );
  1089. indention = trailingMatch[1];
  1090. } else {
  1091. body = body.replace( /\s*$/, "" );
  1092. }
  1093. // If we recognized the body as an block (not inline with the braces), unindent it and
  1094. // ensure that the last line ends with a newline.
  1095. if ( leadingMatch || trailingMatch ) {
  1096. body = body.replace( new RegExp( "^" + indention, "gm" ), "" );
  1097. body = body.replace( /\n?$/, "\n" );
  1098. }
  1099. return {
  1100. name: name,
  1101. parameters: parameters,
  1102. body: body,
  1103. type: type,
  1104. };
  1105. }
  1106. return undefined;
  1107. }
  1108. /// The `application/javascript` media type for scripts that this driver recognizes.
  1109. ///
  1110. /// @field
  1111. var scriptMediaType = "application/javascript";
  1112. /// Regex to crack a `Function.toString()` result.
  1113. ///
  1114. /// @field
  1115. var functionRegex = new RegExp(
  1116. "function" + // `function`
  1117. "\\s*" +
  1118. "([a-zA-Z_$][0-9a-zA-Z_$]*)?" + // optional name; capture #1
  1119. "\\s*" +
  1120. "\\(([^)]*)\\)" + // `(...)`; capture #2 inside `()`
  1121. "\\s*" +
  1122. "\\{([^]*)\\}" // `{...}`; capture #3 inside `{}`
  1123. );
  1124. // -- findListeners ----------------------------------------------------------------------------
  1125. // TODO: this walks the full prototype chain and is probably horribly inefficient.
  1126. function findListeners( node, eventName, targetOnly ) {
  1127. var prototypeListeners = Object.getPrototypeOf( node ).private ? // get any self-targeted listeners from the prototypes
  1128. findListeners( Object.getPrototypeOf( node ), eventName, true ) : [];
  1129. var nodeListeners = node.private.listeners && node.private.listeners[eventName] || [];
  1130. if ( targetOnly ) {
  1131. return prototypeListeners.concat( nodeListeners.filter( function( listener ) {
  1132. return listener.context === node.id || // in the prototypes, select self-targeted listeners only
  1133. ( node.private.origin && listener.context === node.private.origin.id );
  1134. } ) );
  1135. } else {
  1136. return prototypeListeners.map( function( listener ) { // remap the prototype listeners to target the node
  1137. return { handler: listener.handler, context: node.id, phases: listener.phases };
  1138. } ).concat( nodeListeners );
  1139. }
  1140. }
  1141. /// Transform node references in a component descriptor into kernel-style node references. The
  1142. /// resulting object will be suitable for passing to `kernel.createNode`.
  1143. ///
  1144. /// This function must run as a method of the driver. Invoke it as:
  1145. /// `componentKernelFromJS.call( driver, component )`.
  1146. ///
  1147. /// @param {String|Object} component
  1148. /// A component URI or descriptor. A URI will pass through unchanged (as will all descriptor
  1149. /// fields that aren't node references.)
  1150. ///
  1151. /// @returns {String|Object}
  1152. /// `component` with node references replaced with kernel-style node references.
  1153. function componentKernelFromJS( component ) {
  1154. return valueKernelFromJS.call( this, component );
  1155. }
  1156. /// Convert a parameter array of values using `valueKernelFromJS`.
  1157. ///
  1158. /// This function must run as a method of the driver. Invoke it as:
  1159. /// `parametersKernelFromJS.call( driver, parameters )`.
  1160. ///
  1161. /// @param {Object[]} parameters
  1162. ///
  1163. /// @returns {Object}
  1164. function parametersKernelFromJS( parameters ) {
  1165. return valueKernelFromJS.call( this, parameters );
  1166. }
  1167. /// Convert a parameter array of values using `valueJSFromKernel`.
  1168. ///
  1169. /// This function must run as a method of the driver. Invoke it as:
  1170. /// `parametersJSFromKernel.call( driver, parameters )`.
  1171. ///
  1172. /// @param {Object[]} parameters
  1173. ///
  1174. /// @returns {Object}
  1175. function parametersJSFromKernel( parameters ) {
  1176. return valueJSFromKernel.call( this, parameters );
  1177. }
  1178. /// Convert node references into special values that can pass through the kernel. These values
  1179. /// are wrapped in such a way that they won't be confused with any other application value, and
  1180. /// they will be replicated correctly by the kernel.
  1181. ///
  1182. /// Other values are returned unchanged. Use `valueJSFromKernel` to retrieve the original.
  1183. ///
  1184. /// This function must run as a method of the driver. Invoke it as:
  1185. /// `valueKernelFromJS.call( driver, value )`.
  1186. ///
  1187. /// @param {Object} value
  1188. ///
  1189. /// @returns {Object}
  1190. function valueKernelFromJS( value ) {
  1191. var self = this;
  1192. return utility.transform( value, function( object, names, depth, finished ) {
  1193. if ( valueIsNode.call( self, object ) ) {
  1194. finished();
  1195. return kutility.nodeReference( object.id );
  1196. } else if ( object && object.buffer && object.buffer.toString() === "[object ArrayBuffer]" ) {
  1197. finished();
  1198. return object;
  1199. } else if ( kutility.valueIsNodeReference( object ) ) {
  1200. finished();
  1201. self.logger.warnx( "valueKernelFromJS", "javascript-format value contains a kernel-format node reference" );
  1202. return object;
  1203. } else {
  1204. return object;
  1205. }
  1206. } );
  1207. }
  1208. /// Convert values wrapped by `valueKernelFromJS` into their original form for use in the
  1209. /// JavaScript driver's execution environment.
  1210. ///
  1211. /// This function must run as a method of the driver. Invoke it as:
  1212. /// `valueJSFromKernel.call( driver, value )`.
  1213. ///
  1214. /// @param {Object} value
  1215. ///
  1216. /// @returns {Object}
  1217. function valueJSFromKernel( value ) {
  1218. var self = this;
  1219. return utility.transform( value, function( object, names, depth, finished ) {
  1220. if ( kutility.valueIsNodeReference( object ) ) {
  1221. finished();
  1222. return self.nodes[ object.id ];
  1223. } else if ( object && object.buffer && object.buffer.toString() === "[object ArrayBuffer]" ) {
  1224. finished();
  1225. return object;
  1226. } else if ( valueIsNode.call( self, object ) ) {
  1227. finished();
  1228. self.logger.warnx( "valueJSFromKernel", "kernel-format value contains a javascript-format node reference" );
  1229. return object;
  1230. } else {
  1231. return object;
  1232. }
  1233. } );
  1234. }
  1235. /// Determine if a value is a `model/javascript` node.
  1236. ///
  1237. /// This function must run as a method of the driver. Invoke it as:
  1238. /// `valueIsNode.call( driver, value )`.
  1239. ///
  1240. /// @param {Object} value
  1241. ///
  1242. /// @returns {Boolean}
  1243. function valueIsNode( value ) {
  1244. return this.protoNode && // our proxy for the node.vwf prototype
  1245. ( this.protoNode.isPrototypeOf( value ) || value === this.protoNode );
  1246. }
  1247. return exports;
  1248. } );