1 // 2 // Copyright 2010, Tridium, Inc. All Rights Reserved. 3 // 4 5 /** 6 * Collections Architecture for BajaScript. 7 * 8 * @author Gareth Johnson 9 * @version 1.0.0.0 10 */ 11 12 //JsLint options (see http://www.jslint.com ) 13 /*jslint rhino: true, onevar: false, plusplus: true, white: true, undef: false, nomen: false, 14 eqeqeq: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false, indent: 2, 15 vars: true, continue: true */ 16 17 // Globals for JsLint to ignore 18 /*global baja, BaseBajaObj*/ 19 20 (function coll(baja) { 21 22 // Use ECMAScript 5 Strict Mode 23 "use strict"; 24 25 // Create local for improved minification 26 var strictArg = baja.strictArg, 27 strictAllArgs = baja.strictAllArgs, 28 bajaDef = baja.def; 29 30 function isObjEmpty(obj) { 31 var p; 32 for (p in obj) { 33 if (obj.hasOwnProperty(p)) { 34 return false; 35 } 36 } 37 return true; 38 } 39 40 /** 41 * @namespace BajaScript Collection Namespace. 42 */ 43 baja.coll = new BaseBajaObj(); 44 45 //////////////////////////////////////////////////////////////// 46 // Collection 47 //////////////////////////////////////////////////////////////// 48 49 /** 50 * @class Cursor for a Collection. 51 * 52 * @see baja.coll.Collection 53 * 54 * @name CollectionCursor 55 * @extends baja.Cursor 56 * @inner 57 * @public 58 */ 59 var CollectionCursor = function (collection, curData) { 60 CollectionCursor.$super.apply(this, arguments); 61 this.$collection = collection; 62 this.$curData = curData; 63 this.$index = -1; 64 this.$before = baja.noop; 65 this.$after = baja.noop; 66 }.$extend(baja.AsyncCursor); 67 68 /** 69 * Return the underlying Cursor's Collection. 70 * 71 * @returns {baja.coll.BoxCollection} 72 */ 73 CollectionCursor.prototype.getCollection = function () { 74 return this.$collection; 75 }; 76 77 var colGet = function () { 78 return this.$curData[this.$index] || null; 79 }; 80 81 /** 82 * Return the current row. 83 * 84 * @name CollectionCursor#get 85 * @function 86 * 87 * @returns the cursor value (null if none available) 88 */ 89 CollectionCursor.prototype.get = colGet; 90 91 /** 92 * When the cursor is iterated, the before function will be called 93 * just before iteration starts. 94 * <p> 95 * When the function is called, 'this' refers to the Cursor. The Cursor is also 96 * passed in as a argument to this function. 97 * 98 * @param {Function} func the before function. 99 */ 100 CollectionCursor.prototype.before = function (func) { 101 strictArg(func, Function); 102 this.$before = func; 103 }; 104 105 /** 106 * When the cursor is iterated, the before function will be called 107 * just after iteration has finished. 108 * <p> 109 * When the function is called, 'this' refers to the Cursor. The Cursor is also 110 * passed in as a argument to this function. 111 * 112 * @param {Function} func the before function. 113 */ 114 CollectionCursor.prototype.after = function (func) { 115 strictArg(func, Function); 116 this.$after = func; 117 }; 118 119 /** 120 * Iterate through the Cursor and call 'each' for every item. 121 * <p> 122 * When the function is called, 'this' refers to the Cursor. 123 * 124 * @param {Function} func function called on every iteration with 125 * the current row used as the argument. 126 */ 127 CollectionCursor.prototype.each = function (func) { 128 strictArg(func, Function); 129 130 this.$index = -1; 131 var size = this.$curData.length, 132 i, 133 result; 134 135 // Called just before iteration 136 this.$before.call(this, this); 137 138 for (i = 0; i < size; ++i) { 139 // Called for every item in the Cursor 140 ++this.$index; 141 result = func.call(this, this.get(), this.$index); 142 143 // Break if a truthy result is returned 144 if (result) { 145 break; 146 } 147 } 148 149 // Called just after iteration 150 this.$after.call(this, this); 151 }; 152 153 /** 154 * @class Represents a baja:ICollection in BajaScript. 155 * <p> 156 * Collections are usually returned as the result of resolving an ORD (i.e. a BQL query). 157 * 158 * @see CollectionCursor 159 * 160 * @name baja.coll.Collection 161 * @extends baja.Simple 162 */ 163 baja.coll.Collection = function (collData) { 164 baja.coll.Collection.$super.apply(this, arguments); 165 this.$collData = collData; 166 this.$Cursor = CollectionCursor; 167 }.$extend(baja.Simple); 168 169 baja.coll.Collection.DEFAULT = new baja.coll.Collection({}); 170 171 /** 172 * Make a Collection. 173 * 174 * @private 175 * 176 * @param {Object} collData 177 * @returns {baja.coll.Collection} the Collection 178 */ 179 baja.coll.Collection.make = function (collData) { 180 if (isObjEmpty(collData)) { 181 return baja.coll.Collection.DEFAULT; 182 } 183 return new baja.coll.Collection(collData); 184 }; 185 186 /** 187 * Make a Collection. 188 * 189 * @private 190 * 191 * @param {Object} collData 192 * @returns {baja.coll.Collection} the Collection 193 */ 194 baja.coll.Collection.prototype.make = function (collData) { 195 return baja.coll.Collection.make(collData); 196 }; 197 198 /** 199 * Decode a Collection from a String. 200 * 201 * @private 202 * 203 * @param {String} str 204 * @returns {baja.coll.Collection} 205 */ 206 baja.coll.Collection.prototype.decodeFromString = function (str) { 207 return baja.coll.Collection.make(JSON.parse(str)); 208 }; 209 210 /** 211 * Encode the Collection to a String. 212 * 213 * @private 214 * 215 * @returns {String} 216 */ 217 baja.coll.Collection.prototype.encodeToString = function () { 218 return JSON.stringify(this.$collData); 219 }; 220 221 // Register Type 222 baja.coll.Collection.registerType("box:BoxCollection"); 223 224 /** 225 * Iterate through a Collection. 226 * <p> 227 * Please note, this may retrieve data asynchronously. 228 * <p> 229 * A function is passed in to retrieve to the Cursor. 230 * For example... 231 * 232 * <pre> 233 * myCollection.cursor(function (cursor) { 234 * // Called once we have the Cursor 235 * }); 236 * 237 * // or via an Object Literal... 238 * 239 * myCollection.cursor({ 240 * each: function () { 241 * // Called for each item in the Cursor... 242 * var dataFromCursor = this.get(); 243 * }, 244 * ok: function (cursor) { 245 * // Called once we have the Cursor 246 * }, 247 * fail: function (err) { 248 * // Called if any errors in getting data 249 * } 250 * }); 251 * </pre> 252 * 253 * @see CollectionCursor 254 * 255 * @param {Object|Function} obj the Object Literal that specifies the method's arguments. 256 * For convenience, this can also be the ok function. 257 * @param {Function} [obj.ok] called when the cursor has been created with the cursor as an argument. 258 * @param {Function} [obj.fail] called if the cursor fails to be retrieved. 259 * An error is passed in as the first argument. 260 * @param {baja.comm.Batch} [obj.batch], if specified, the operation will be batched into this object. 261 * @param {Function} [obj.before] called just before the Cursor is about to be iterated through. 262 * @param {Function} [obj.after] called just after the Cursor has finished iterating. 263 * @param {Number} [obj.offset] Specifies the row number to start encoding the result-set from. 264 * @param {Number} [obj.limit] Specifies the maximum number of rows that can be encoded. 265 * By default, this is set to 10 rows. 266 */ 267 baja.coll.Collection.prototype.cursor = function (obj) { 268 obj = baja.objectify(obj, "each"); 269 270 var cb = new baja.comm.Callback(obj.ok, obj.fail, obj.batch), 271 that = this; 272 273 // Add an intermediate callback to create the Cursor Object and pass it back 274 cb.addOk(function (ok, fail, resp) { 275 var newOk = function () { 276 var i, 277 cursor; 278 279 // Decode each row of the result set 280 for (i = 0; i < resp.length; ++i) { 281 resp[i] = baja.bson.decodeValue(resp[i], baja.$serverDecodeContext); 282 } 283 284 // Create a Cursor Object and pass it into the Handler 285 cursor = new that.$Cursor(that, resp); 286 287 if (typeof obj.before === "function") { 288 cursor.before(obj.before); 289 } 290 291 if (typeof obj.after === "function") { 292 cursor.after(obj.after); 293 } 294 295 // Please note, if defined, this will trigger an iteration 296 if (typeof obj.each === "function") { 297 cursor.each(obj.each); 298 } 299 300 ok(cursor); 301 }; 302 303 if (!obj.$data) { 304 // Scan for any unknown types we don't have here and make another 305 // network request if necessary 306 var unknownTypes = baja.bson.scanForUnknownTypes(resp); 307 308 if (unknownTypes.length > 0) { 309 var importBatch = new baja.comm.Batch(); 310 311 baja.importTypes({ 312 "typeSpecs": unknownTypes, 313 "ok": newOk, 314 "fail": fail, 315 "batch": importBatch 316 }); 317 318 // Make a synchronous or asynchronous call depending how the orignal callback 319 // was invoked 320 if (cb.getBatch().isAsync()) { 321 cb.commit(); 322 } 323 else { 324 cb.commitSync(); 325 } 326 } 327 else { 328 newOk(); 329 } 330 } 331 else { 332 newOk(); 333 } 334 }); 335 336 obj.limit = obj.limit || 10; 337 obj.offset = obj.offset || 0; 338 339 // If '$data' is specified then use this for the first Cursor iteration. This is an optimization made 340 // by BajaScript's ORD architecture. This enables an ORD to be queried and then iterated through at the same time. 341 if (obj.$data) { 342 cb.ok(obj.$data); 343 } 344 else { 345 346 // Make a a network call for the Cursor data 347 baja.comm.cursor(this.$collData.req, cb, obj); 348 } 349 }; 350 351 //////////////////////////////////////////////////////////////// 352 // Collection 353 //////////////////////////////////////////////////////////////// 354 355 /** 356 * @class Cursor for a Table. 357 * 358 * @see baja.coll.Table 359 * 360 * @name TableCursor 361 * @extends CollectionCursor 362 * @inner 363 * @public 364 */ 365 var TableCursor = function (collection, curData) { 366 TableCursor.$super.apply(this, arguments); 367 this.$collection = collection; 368 }.$extend(CollectionCursor); 369 370 function getColDisp(cursor, column, display) { 371 var tof = typeof column; 372 373 if (tof === "undefined") { 374 // If no column is defined then just return the entire row 375 return colGet.call(cursor); 376 } 377 else if (column && tof === "object") { 378 column = column.getName(); 379 } 380 else if (tof !== "string") { 381 throw new Error("Invalid Column name: " + column); 382 } 383 384 var row = colGet.call(cursor); 385 if (row !== null) { 386 return display ? row.getDisplay(column) : row.get(column); 387 } 388 389 return null; 390 } 391 392 /** 393 * Return the current row or row item. 394 * <p> 395 * If column information is passed into this method then the value for a particular 396 * column and row will be returned. 397 * 398 * @name TableCursor#get 399 * @function 400 * 401 * @param {String|TableColumn} [column] the column name or column. If undefined, 402 * the entire row is returned. 403 * @returns the cursor value (null if none available). 404 */ 405 TableCursor.prototype.get = function (column) { 406 return getColDisp(this, column, /*display*/false); 407 }; 408 409 /** 410 * Return the current item display string. 411 * <p> 412 * If column information is passed into this method then the display String for a particular 413 * column and row will be returned. 414 * 415 * @param {String|TableColumn} [column] the column name or column. If undefined, 416 * the entire row is returned. 417 * @returns the cursor display string (null if none available). 418 */ 419 TableCursor.prototype.getDisplay = function (column) { 420 return getColDisp(this, column, /*display*/true); 421 }; 422 423 /** 424 * @class Represents a baja:ITable in BajaScript. 425 * <p> 426 * Tables are usually returned as the result of resolving an ORD (i.e. a BQL query). 427 * 428 * @name baja.coll.Table 429 * @extends baja.coll.Collection 430 * 431 * @see TableCursor 432 * @see TableColumn 433 */ 434 baja.coll.Table = function (tableData) { 435 baja.coll.Table.$super.apply(this, arguments); 436 this.$tableData = tableData; 437 this.$Cursor = TableCursor; 438 }.$extend(baja.coll.Collection); 439 440 baja.coll.Table.DEFAULT = new baja.coll.Table({}); 441 442 /** 443 * Make a Table. 444 * 445 * @private 446 * 447 * @param {Object} tableData 448 * @returns {baja.coll.Table} the Table. 449 */ 450 baja.coll.Table.make = function (tableData) { 451 if (isObjEmpty(tableData)) { 452 return baja.coll.Table.DEFAULT; 453 } 454 return new baja.coll.Table(tableData); 455 }; 456 457 /** 458 * Make a Table. 459 * 460 * @private 461 * 462 * @param {Object} tableData 463 * @returns {baja.coll.Table} the Table 464 */ 465 baja.coll.Table.prototype.make = function (tableData) { 466 return baja.coll.Table.make(tableData); 467 }; 468 469 /** 470 * Decode a Table from a String. 471 * 472 * @private 473 * 474 * @param {String} str 475 * @returns {Table} 476 */ 477 baja.coll.Table.prototype.decodeFromString = function (str) { 478 return baja.coll.Table.make(JSON.parse(str)); 479 }; 480 481 /** 482 * Encode the Table to a String. 483 * 484 * @private 485 * 486 * @returns {String} 487 */ 488 baja.coll.Table.prototype.encodeToString = function () { 489 return JSON.stringify(this.$tableData); 490 }; 491 492 // Register Type 493 baja.coll.Table.registerType("box:BoxTable"); 494 495 /** 496 * Returns an array of Table Columns. 497 * 498 * @see baja.coll.Table#getCol 499 * @see TableColumn 500 * 501 * @returns an array of columns (TableColumn) 502 */ 503 baja.coll.Table.prototype.getColumns = function () { 504 var columns = [], 505 cols = this.$tableData.cols, 506 i; 507 508 for (i = 0; i < cols.length; ++i) { 509 columns.push(this.getCol(cols[i].n)); 510 } 511 512 return columns; 513 }; 514 515 /** 516 * Returns a Column Object for the given column name. 517 * 518 * @param {String|Number} column the column name or index. 519 * @returns {TableColumn} the table column or null if the column can't be found. 520 */ 521 baja.coll.Table.prototype.getCol = function (column) { 522 strictArg(column); 523 524 var to = typeof column, 525 cols = this.$tableData.cols, 526 data, 527 i; 528 529 if (to === "number") { 530 data = cols[column]; 531 } 532 else if (to === "string") { 533 for (i = 0; i < cols.length; ++i) { 534 if (cols[i].n === column) { 535 data = cols[i]; 536 break; 537 } 538 } 539 } 540 541 // If there's no data then return null at this point 542 if (!data) { 543 return null; 544 } 545 546 /** 547 * @class Table Column 548 * @name TableColumn 549 * @inner 550 * @public 551 * 552 * @see baja.coll.Table 553 */ 554 return { 555 /** 556 * Return the column name. 557 * 558 * @name TableColumn#getName 559 * @returns {String} 560 */ 561 getName: function getName() { 562 return data.n; 563 }, 564 565 /** 566 * Return the column display name. 567 * 568 * @name TableColumn#getDisplayName 569 * @returns {String} 570 */ 571 getDisplayName: function getDisplayName() { 572 return data.dn; 573 }, 574 575 /** 576 * Return the column Type. 577 * 578 * @name TableColumn#getType 579 * @returns {Type} 580 */ 581 getType: function getType() { 582 return baja.lt(data.t); 583 }, 584 585 /** 586 * Return the column flags. 587 * 588 * @name TableColumn#getFlags 589 * @returns {Number} 590 */ 591 getFlags: function getFlags() { 592 return data.f; 593 }, 594 595 /** 596 * the column facets 597 * 598 * @name TableColumn#getFacets 599 * @returns {baja.Facets} 600 */ 601 getFacets: function getFacets() { 602 return baja.Facets.DEFAULT.decodeFromString(data.x); 603 } 604 }; 605 }; 606 607 }(baja));