UNPKG

65.2 kBJavaScriptView Raw
1/*
2
3 Software License Agreement (BSD License)
4 http://taffydb.com
5 Copyright (c)
6 All rights reserved.
7
8
9 Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following condition is met:
10
11 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
12
13 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
14 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
16 */
17
18/*jslint browser : true, continue : true,
19 devel : true, indent : 2, maxerr : 500,
20 newcap : true, nomen : true, plusplus : true,
21 regexp : true, sloppy : true, vars : false,
22 white : true
23*/
24
25// BUILD 193d48d, modified by mmikowski to pass jslint
26
27// Setup TAFFY name space to return an object with methods
28var TAFFY, exports, T;
29(function () {
30 'use strict';
31 var
32 typeList, makeTest, idx, typeKey,
33 version, TC, idpad, cmax,
34 API, protectJSON, each, eachin,
35 isIndexable, returnFilter, runFilters,
36 numcharsplit, orderByCol, run, intersection,
37 filter, makeCid, safeForJson,
38 isRegexp
39 ;
40
41
42 if ( ! TAFFY ){
43 // TC = Counter for Taffy DBs on page, used for unique IDs
44 // cmax = size of charnumarray conversion cache
45 // idpad = zeros to pad record IDs with
46 version = '2.7';
47 TC = 1;
48 idpad = '000000';
49 cmax = 1000;
50 API = {};
51
52 protectJSON = function ( t ) {
53 // ****************************************
54 // *
55 // * Takes: a variable
56 // * Returns: the variable if object/array or the parsed variable if JSON
57 // *
58 // ****************************************
59 if ( TAFFY.isArray( t ) || TAFFY.isObject( t ) ){
60 return t;
61 }
62 else {
63 return JSON.parse( t );
64 }
65 };
66
67 // gracefully stolen from underscore.js
68 intersection = function(array1, array2) {
69 return filter(array1, function(item) {
70 return array2.indexOf(item) >= 0;
71 });
72 };
73
74 // gracefully stolen from underscore.js
75 filter = function(obj, iterator, context) {
76 var results = [];
77 if (obj == null) return results;
78 if (Array.prototype.filter && obj.filter === Array.prototype.filter) return obj.filter(iterator, context);
79 each(obj, function(value, index, list) {
80 if (iterator.call(context, value, index, list)) results[results.length] = value;
81 });
82 return results;
83 };
84
85 isRegexp = function(aObj) {
86 return Object.prototype.toString.call(aObj)==='[object RegExp]';
87 }
88
89 safeForJson = function(aObj) {
90 var myResult = T.isArray(aObj) ? [] : T.isObject(aObj) ? {} : null;
91 if(aObj===null) return aObj;
92 for(var i in aObj) {
93 myResult[i] = isRegexp(aObj[i]) ? aObj[i].toString() : T.isArray(aObj[i]) || T.isObject(aObj[i]) ? safeForJson(aObj[i]) : aObj[i];
94 }
95 return myResult;
96 }
97
98 makeCid = function(aContext) {
99 var myCid = JSON.stringify(aContext);
100 if(myCid.match(/regex/)===null) return myCid;
101 return JSON.stringify(safeForJson(aContext));
102 }
103
104 each = function ( a, fun, u ) {
105 var r, i, x, y;
106 // ****************************************
107 // *
108 // * Takes:
109 // * a = an object/value or an array of objects/values
110 // * f = a function
111 // * u = optional flag to describe how to handle undefined values
112 // in array of values. True: pass them to the functions,
113 // False: skip. Default False;
114 // * Purpose: Used to loop over arrays
115 // *
116 // ****************************************
117 if ( a && ((T.isArray( a ) && a.length === 1) || (!T.isArray( a ))) ){
118 fun( (T.isArray( a )) ? a[0] : a, 0 );
119 }
120 else {
121 for ( r, i, x = 0, a = (T.isArray( a )) ? a : [a], y = a.length;
122 x < y; x++ )
123 {
124 i = a[x];
125 if ( !T.isUndefined( i ) || (u || false) ){
126 r = fun( i, x );
127 if ( r === T.EXIT ){
128 break;
129 }
130
131 }
132 }
133 }
134 };
135
136 eachin = function ( o, fun ) {
137 // ****************************************
138 // *
139 // * Takes:
140 // * o = an object
141 // * f = a function
142 // * Purpose: Used to loop over objects
143 // *
144 // ****************************************
145 var x = 0, r, i;
146
147 for ( i in o ){
148 if ( o.hasOwnProperty( i ) ){
149 r = fun( o[i], i, x++ );
150 if ( r === T.EXIT ){
151 break;
152 }
153 }
154 }
155
156 };
157
158 API.extend = function ( m, f ) {
159 // ****************************************
160 // *
161 // * Takes: method name, function
162 // * Purpose: Add a custom method to the API
163 // *
164 // ****************************************
165 API[m] = function () {
166 return f.apply( this, arguments );
167 };
168 };
169
170 isIndexable = function ( f ) {
171 var i;
172 // Check to see if record ID
173 if ( T.isString( f ) && /[t][0-9]*[r][0-9]*/i.test( f ) ){
174 return true;
175 }
176 // Check to see if record
177 if ( T.isObject( f ) && f.___id && f.___s ){
178 return true;
179 }
180
181 // Check to see if array of indexes
182 if ( T.isArray( f ) ){
183 i = true;
184 each( f, function ( r ) {
185 if ( !isIndexable( r ) ){
186 i = false;
187
188 return TAFFY.EXIT;
189 }
190 });
191 return i;
192 }
193
194 return false;
195 };
196
197 runFilters = function ( r, filter ) {
198 // ****************************************
199 // *
200 // * Takes: takes a record and a collection of filters
201 // * Returns: true if the record matches, false otherwise
202 // ****************************************
203 var match = true;
204
205
206 each( filter, function ( mf ) {
207 switch ( T.typeOf( mf ) ){
208 case 'function':
209 // run function
210 if ( !mf.apply( r ) ){
211 match = false;
212 return TAFFY.EXIT;
213 }
214 break;
215 case 'array':
216 // loop array and treat like a SQL or
217 match = (mf.length === 1) ? (runFilters( r, mf[0] )) :
218 (mf.length === 2) ? (runFilters( r, mf[0] ) ||
219 runFilters( r, mf[1] )) :
220 (mf.length === 3) ? (runFilters( r, mf[0] ) ||
221 runFilters( r, mf[1] ) || runFilters( r, mf[2] )) :
222 (mf.length === 4) ? (runFilters( r, mf[0] ) ||
223 runFilters( r, mf[1] ) || runFilters( r, mf[2] ) ||
224 runFilters( r, mf[3] )) : false;
225 if ( mf.length > 4 ){
226 each( mf, function ( f ) {
227 if ( runFilters( r, f ) ){
228 match = true;
229 }
230 });
231 }
232 break;
233 }
234 });
235
236 return match;
237 };
238
239 returnFilter = function ( f ) {
240 // ****************************************
241 // *
242 // * Takes: filter object
243 // * Returns: a filter function
244 // * Purpose: Take a filter object and return a function that can be used to compare
245 // * a TaffyDB record to see if the record matches a query
246 // ****************************************
247 var nf = [];
248 if ( T.isString( f ) && /[t][0-9]*[r][0-9]*/i.test( f ) ){
249 f = { ___id : f };
250 }
251 if ( T.isArray( f ) ){
252 // if we are working with an array
253
254 each( f, function ( r ) {
255 // loop the array and return a filter func for each value
256 nf.push( returnFilter( r ) );
257 });
258 // now build a func to loop over the filters and return true if ANY of the filters match
259 // This handles logical OR expressions
260 f = function () {
261 var that = this, match = false;
262 each( nf, function ( f ) {
263 if ( runFilters( that, f ) ){
264 match = true;
265 }
266 });
267 return match;
268 };
269 return f;
270
271 }
272 // if we are dealing with an Object
273 if ( T.isObject( f ) ){
274 if ( T.isObject( f ) && f.___id && f.___s ){
275 f = { ___id : f.___id };
276 }
277
278 // Loop over each value on the object to prep match type and match value
279 eachin( f, function ( v, i ) {
280
281 // default match type to IS/Equals
282 if ( !T.isObject( v ) ){
283 v = {
284 'is' : v
285 };
286 }
287 // loop over each value on the value object - if any
288 eachin( v, function ( mtest, s ) {
289 // s = match type, e.g. is, hasAll, like, etc
290 var c = [], looper;
291
292 // function to loop and apply filter
293 looper = (s === 'hasAll') ?
294 function ( mtest, func ) {
295 func( mtest );
296 } : each;
297
298 // loop over each test
299 looper( mtest, function ( mtest ) {
300
301 // su = match success
302 // f = match false
303 var su = true, f = false, matchFunc;
304
305
306 // push a function onto the filter collection to do the matching
307 matchFunc = function () {
308
309 // get the value from the record
310 var
311 mvalue = this[i],
312 eqeq = '==',
313 bangeq = '!=',
314 eqeqeq = '===',
315 lt = '<',
316 gt = '>',
317 lteq = '<=',
318 gteq = '>=',
319 bangeqeq = '!==',
320 r
321 ;
322
323 if (typeof mvalue === 'undefined') {
324 return false;
325 }
326
327 if ( (s.indexOf( '!' ) === 0) && s !== bangeq &&
328 s !== bangeqeq )
329 {
330 // if the filter name starts with ! as in '!is' then reverse the match logic and remove the !
331 su = false;
332 s = s.substring( 1, s.length );
333 }
334 // get the match results based on the s/match type
335 /*jslint eqeq : true */
336 r = (
337 (s === 'regex') ? (mtest.test( mvalue )) : (s === 'lt' || s === lt)
338 ? (mvalue < mtest) : (s === 'gt' || s === gt)
339 ? (mvalue > mtest) : (s === 'lte' || s === lteq)
340 ? (mvalue <= mtest) : (s === 'gte' || s === gteq)
341 ? (mvalue >= mtest) : (s === 'left')
342 ? (mvalue.indexOf( mtest ) === 0) : (s === 'leftnocase')
343 ? (mvalue.toLowerCase().indexOf( mtest.toLowerCase() )
344 === 0) : (s === 'right')
345 ? (mvalue.substring( (mvalue.length - mtest.length) )
346 === mtest) : (s === 'rightnocase')
347 ? (mvalue.toLowerCase().substring(
348 (mvalue.length - mtest.length) ) === mtest.toLowerCase())
349 : (s === 'like')
350 ? (mvalue.indexOf( mtest ) >= 0) : (s === 'likenocase')
351 ? (mvalue.toLowerCase().indexOf(mtest.toLowerCase()) >= 0)
352 : (s === eqeqeq || s === 'is')
353 ? (mvalue === mtest) : (s === eqeq)
354 ? (mvalue == mtest) : (s === bangeqeq)
355 ? (mvalue !== mtest) : (s === bangeq)
356 ? (mvalue != mtest) : (s === 'isnocase')
357 ? (mvalue.toLowerCase
358 ? mvalue.toLowerCase() === mtest.toLowerCase()
359 : mvalue === mtest) : (s === 'has')
360 ? (T.has( mvalue, mtest )) : (s === 'hasall')
361 ? (T.hasAll( mvalue, mtest )) : (s === 'contains')
362 ? (TAFFY.isArray(mvalue) && mvalue.indexOf(mtest) > -1) : (
363 s.indexOf( 'is' ) === -1
364 && !TAFFY.isNull( mvalue )
365 && !TAFFY.isUndefined( mvalue )
366 && !TAFFY.isObject( mtest )
367 && !TAFFY.isArray( mtest )
368 )
369 ? (mtest === mvalue[s])
370 : (T[s] && T.isFunction( T[s] )
371 && s.indexOf( 'is' ) === 0)
372 ? T[s]( mvalue ) === mtest
373 : (T[s] && T.isFunction( T[s] ))
374 ? T[s]( mvalue, mtest ) : (false)
375 );
376 /*jslint eqeq : false */
377 r = (r && !su) ? false : (!r && !su) ? true : r;
378
379 return r;
380 };
381 c.push( matchFunc );
382
383 });
384 // if only one filter in the collection push it onto the filter list without the array
385 if ( c.length === 1 ){
386
387 nf.push( c[0] );
388 }
389 else {
390 // else build a function to loop over all the filters and return true only if ALL match
391 // this is a logical AND
392 nf.push( function () {
393 var that = this, match = false;
394 each( c, function ( f ) {
395 if ( f.apply( that ) ){
396 match = true;
397 }
398 });
399 return match;
400 });
401 }
402 });
403 });
404 // finally return a single function that wraps all the other functions and will run a query
405 // where all functions have to return true for a record to appear in a query result
406 f = function () {
407 var that = this, match = true;
408 // faster if less than 4 functions
409 match = (nf.length === 1 && !nf[0].apply( that )) ? false :
410 (nf.length === 2 &&
411 (!nf[0].apply( that ) || !nf[1].apply( that ))) ? false :
412 (nf.length === 3 &&
413 (!nf[0].apply( that ) || !nf[1].apply( that ) ||
414 !nf[2].apply( that ))) ? false :
415 (nf.length === 4 &&
416 (!nf[0].apply( that ) || !nf[1].apply( that ) ||
417 !nf[2].apply( that ) || !nf[3].apply( that ))) ? false
418 : true;
419 if ( nf.length > 4 ){
420 each( nf, function ( f ) {
421 if ( !runFilters( that, f ) ){
422 match = false;
423 }
424 });
425 }
426 return match;
427 };
428 return f;
429 }
430
431 // if function
432 if ( T.isFunction( f ) ){
433 return f;
434 }
435 };
436
437 orderByCol = function ( ar, o ) {
438 // ****************************************
439 // *
440 // * Takes: takes an array and a sort object
441 // * Returns: the array sorted
442 // * Purpose: Accept filters such as "[col], [col2]" or "[col] desc" and sort on those columns
443 // *
444 // ****************************************
445
446 var sortFunc = function ( a, b ) {
447 // function to pass to the native array.sort to sort an array
448 var r = 0;
449
450 T.each( o, function ( sd ) {
451 // loop over the sort instructions
452 // get the column name
453 var o, col, dir, c, d;
454 o = sd.split( ' ' );
455 col = o[0];
456
457 // get the direction
458 dir = (o.length === 1) ? "logical" : o[1];
459
460
461 if ( dir === 'logical' ){
462 // if dir is logical than grab the charnum arrays for the two values we are looking at
463 c = numcharsplit( a[col] );
464 d = numcharsplit( b[col] );
465 // loop over the charnumarrays until one value is higher than the other
466 T.each( (c.length <= d.length) ? c : d, function ( x, i ) {
467 if ( c[i] < d[i] ){
468 r = -1;
469 return TAFFY.EXIT;
470 }
471 else if ( c[i] > d[i] ){
472 r = 1;
473 return TAFFY.EXIT;
474 }
475 } );
476 }
477 else if ( dir === 'logicaldesc' ){
478 // if logicaldesc than grab the charnum arrays for the two values we are looking at
479 c = numcharsplit( a[col] );
480 d = numcharsplit( b[col] );
481 // loop over the charnumarrays until one value is lower than the other
482 T.each( (c.length <= d.length) ? c : d, function ( x, i ) {
483 if ( c[i] > d[i] ){
484 r = -1;
485 return TAFFY.EXIT;
486 }
487 else if ( c[i] < d[i] ){
488 r = 1;
489 return TAFFY.EXIT;
490 }
491 } );
492 }
493 else if ( dir === 'asec' && a[col] < b[col] ){
494 // if asec - default - check to see which is higher
495 r = -1;
496 return T.EXIT;
497 }
498 else if ( dir === 'asec' && a[col] > b[col] ){
499 // if asec - default - check to see which is higher
500 r = 1;
501 return T.EXIT;
502 }
503 else if ( dir === 'desc' && a[col] > b[col] ){
504 // if desc check to see which is lower
505 r = -1;
506 return T.EXIT;
507
508 }
509 else if ( dir === 'desc' && a[col] < b[col] ){
510 // if desc check to see which is lower
511 r = 1;
512 return T.EXIT;
513
514 }
515 // if r is still 0 and we are doing a logical sort than look to see if one array is longer than the other
516 if ( r === 0 && dir === 'logical' && c.length < d.length ){
517 r = -1;
518 }
519 else if ( r === 0 && dir === 'logical' && c.length > d.length ){
520 r = 1;
521 }
522 else if ( r === 0 && dir === 'logicaldesc' && c.length > d.length ){
523 r = -1;
524 }
525 else if ( r === 0 && dir === 'logicaldesc' && c.length < d.length ){
526 r = 1;
527 }
528
529 if ( r !== 0 ){
530 return T.EXIT;
531 }
532
533
534 } );
535 return r;
536 };
537 // call the sort function and return the newly sorted array
538 return (ar && ar.push) ? ar.sort( sortFunc ) : ar;
539
540
541 };
542
543 // ****************************************
544 // *
545 // * Takes: a string containing numbers and letters and turn it into an array
546 // * Returns: return an array of numbers and letters
547 // * Purpose: Used for logical sorting. String Example: 12ABC results: [12,'ABC']
548 // ****************************************
549 (function () {
550 // creates a cache for numchar conversions
551 var cache = {}, cachcounter = 0;
552 // creates the numcharsplit function
553 numcharsplit = function ( thing ) {
554 // if over 1000 items exist in the cache, clear it and start over
555 if ( cachcounter > cmax ){
556 cache = {};
557 cachcounter = 0;
558 }
559
560 // if a cache can be found for a numchar then return its array value
561 return cache['_' + thing] || (function () {
562 // otherwise do the conversion
563 // make sure it is a string and setup so other variables
564 var nthing = String( thing ),
565 na = [],
566 rv = '_',
567 rt = '',
568 x, xx, c;
569
570 // loop over the string char by char
571 for ( x = 0, xx = nthing.length; x < xx; x++ ){
572 // take the char at each location
573 c = nthing.charCodeAt( x );
574 // check to see if it is a valid number char and append it to the array.
575 // if last char was a string push the string to the charnum array
576 if ( ( c >= 48 && c <= 57 ) || c === 46 ){
577 if ( rt !== 'n' ){
578 rt = 'n';
579 na.push( rv.toLowerCase() );
580 rv = '';
581 }
582 rv = rv + nthing.charAt( x );
583 }
584 else {
585 // check to see if it is a valid string char and append to string
586 // if last char was a number push the whole number to the charnum array
587 if ( rt !== 's' ){
588 rt = 's';
589 na.push( parseFloat( rv ) );
590 rv = '';
591 }
592 rv = rv + nthing.charAt( x );
593 }
594 }
595 // once done, push the last value to the charnum array and remove the first uneeded item
596 na.push( (rt === 'n') ? parseFloat( rv ) : rv.toLowerCase() );
597 na.shift();
598 // add to cache
599 cache['_' + thing] = na;
600 cachcounter++;
601 // return charnum array
602 return na;
603 }());
604 };
605 }());
606
607 // ****************************************
608 // *
609 // * Runs a query
610 // ****************************************
611
612
613 run = function () {
614 this.context( {
615 results : this.getDBI().query( this.context() )
616 });
617
618 };
619
620 API.extend( 'filter', function () {
621 // ****************************************
622 // *
623 // * Takes: takes unlimited filter objects as arguments
624 // * Returns: method collection
625 // * Purpose: Take filters as objects and cache functions for later lookup when a query is run
626 // ****************************************
627 var
628 nc = TAFFY.mergeObj( this.context(), { run : null } ),
629 nq = []
630 ;
631 each( nc.q, function ( v ) {
632 nq.push( v );
633 });
634 nc.q = nq;
635 // Hadnle passing of ___ID or a record on lookup.
636 each( arguments, function ( f ) {
637 nc.q.push( returnFilter( f ) );
638 nc.filterRaw.push( f );
639 });
640
641 return this.getroot( nc );
642 });
643
644 API.extend( 'order', function ( o ) {
645 // ****************************************
646 // *
647 // * Purpose: takes a string and creates an array of order instructions to be used with a query
648 // ****************************************
649
650 o = o.split( ',' );
651 var x = [], nc;
652
653 each( o, function ( r ) {
654 x.push( r.replace( /^\s*/, '' ).replace( /\s*$/, '' ) );
655 });
656
657 nc = TAFFY.mergeObj( this.context(), {sort : null} );
658 nc.order = x;
659
660 return this.getroot( nc );
661 });
662
663 API.extend( 'limit', function ( n ) {
664 // ****************************************
665 // *
666 // * Purpose: takes a limit number to limit the number of rows returned by a query. Will update the results
667 // * of a query
668 // ****************************************
669 var nc = TAFFY.mergeObj( this.context(), {}),
670 limitedresults
671 ;
672
673 nc.limit = n;
674
675 if ( nc.run && nc.sort ){
676 limitedresults = [];
677 each( nc.results, function ( i, x ) {
678 if ( (x + 1) > n ){
679 return TAFFY.EXIT;
680 }
681 limitedresults.push( i );
682 });
683 nc.results = limitedresults;
684 }
685
686 return this.getroot( nc );
687 });
688
689 API.extend( 'start', function ( n ) {
690 // ****************************************
691 // *
692 // * Purpose: takes a limit number to limit the number of rows returned by a query. Will update the results
693 // * of a query
694 // ****************************************
695 var nc = TAFFY.mergeObj( this.context(), {} ),
696 limitedresults
697 ;
698
699 nc.start = n;
700
701 if ( nc.run && nc.sort && !nc.limit ){
702 limitedresults = [];
703 each( nc.results, function ( i, x ) {
704 if ( (x + 1) > n ){
705 limitedresults.push( i );
706 }
707 });
708 nc.results = limitedresults;
709 }
710 else {
711 nc = TAFFY.mergeObj( this.context(), {run : null, start : n} );
712 }
713
714 return this.getroot( nc );
715 });
716
717 API.extend( 'update', function ( arg0, arg1, arg2 ) {
718 // ****************************************
719 // *
720 // * Takes: a object and passes it off DBI update method for all matched records
721 // ****************************************
722 var runEvent = true, o = {}, args = arguments, that;
723 if ( TAFFY.isString( arg0 ) &&
724 (arguments.length === 2 || arguments.length === 3) )
725 {
726 o[arg0] = arg1;
727 if ( arguments.length === 3 ){
728 runEvent = arg2;
729 }
730 }
731 else {
732 o = arg0;
733 if ( args.length === 2 ){
734 runEvent = arg1;
735 }
736 }
737
738 that = this;
739 run.call( this );
740 each( this.context().results, function ( r ) {
741 var c = o;
742 if ( TAFFY.isFunction( c ) ){
743 c = c.apply( TAFFY.mergeObj( r, {} ) );
744 }
745 else {
746 if ( T.isFunction( c ) ){
747 c = c( TAFFY.mergeObj( r, {} ) );
748 }
749 }
750 if ( TAFFY.isObject( c ) ){
751 that.getDBI().update( r.___id, c, runEvent );
752 }
753 });
754 if ( this.context().results.length ){
755 this.context( { run : null });
756 }
757 return this;
758 });
759 API.extend( 'remove', function ( runEvent ) {
760 // ****************************************
761 // *
762 // * Purpose: removes records from the DB via the remove and removeCommit DBI methods
763 // ****************************************
764 var that = this, c = 0;
765 run.call( this );
766 each( this.context().results, function ( r ) {
767 that.getDBI().remove( r.___id );
768 c++;
769 });
770 if ( this.context().results.length ){
771 this.context( {
772 run : null
773 });
774 that.getDBI().removeCommit( runEvent );
775 }
776
777 return c;
778 });
779
780
781 API.extend( 'count', function () {
782 // ****************************************
783 // *
784 // * Returns: The length of a query result
785 // ****************************************
786 run.call( this );
787 return this.context().results.length;
788 });
789
790 API.extend( 'callback', function ( f, delay ) {
791 // ****************************************
792 // *
793 // * Returns null;
794 // * Runs a function on return of run.call
795 // ****************************************
796 if ( f ){
797 var that = this;
798 setTimeout( function () {
799 run.call( that );
800 f.call( that.getroot( that.context() ) );
801 }, delay || 0 );
802 }
803
804
805 return null;
806 });
807
808 API.extend( 'get', function () {
809 // ****************************************
810 // *
811 // * Returns: An array of all matching records
812 // ****************************************
813 run.call( this );
814 return this.context().results;
815 });
816
817 API.extend( 'stringify', function () {
818 // ****************************************
819 // *
820 // * Returns: An JSON string of all matching records
821 // ****************************************
822 return JSON.stringify( this.get() );
823 });
824 API.extend( 'first', function () {
825 // ****************************************
826 // *
827 // * Returns: The first matching record
828 // ****************************************
829 run.call( this );
830 return this.context().results[0] || false;
831 });
832 API.extend( 'last', function () {
833 // ****************************************
834 // *
835 // * Returns: The last matching record
836 // ****************************************
837 run.call( this );
838 return this.context().results[this.context().results.length - 1] ||
839 false;
840 });
841
842
843 API.extend( 'sum', function () {
844 // ****************************************
845 // *
846 // * Takes: column to sum up
847 // * Returns: Sums the values of a column
848 // ****************************************
849 var total = 0, that = this;
850 run.call( that );
851 each( arguments, function ( c ) {
852 each( that.context().results, function ( r ) {
853 total = total + (r[c] || 0);
854 });
855 });
856 return total;
857 });
858
859 API.extend( 'min', function ( c ) {
860 // ****************************************
861 // *
862 // * Takes: column to find min
863 // * Returns: the lowest value
864 // ****************************************
865 var lowest = null;
866 run.call( this );
867 each( this.context().results, function ( r ) {
868 if ( lowest === null || r[c] < lowest ){
869 lowest = r[c];
870 }
871 });
872 return lowest;
873 });
874
875 // Taffy innerJoin Extension (OCD edition)
876 // =======================================
877 //
878 // How to Use
879 // **********
880 //
881 // left_table.innerJoin( right_table, condition1 <,... conditionN> )
882 //
883 // A condition can take one of 2 forms:
884 //
885 // 1. An ARRAY with 2 or 3 values:
886 // A column name from the left table, an optional comparison string,
887 // and column name from the right table. The condition passes if the test
888 // indicated is true. If the condition string is omitted, '===' is assumed.
889 // EXAMPLES: [ 'last_used_time', '>=', 'current_use_time' ], [ 'user_id','id' ]
890 //
891 // 2. A FUNCTION:
892 // The function receives a left table row and right table row during the
893 // cartesian join. If the function returns true for the rows considered,
894 // the merged row is included in the result set.
895 // EXAMPLE: function (l,r){ return l.name === r.label; }
896 //
897 // Conditions are considered in the order they are presented. Therefore the best
898 // performance is realized when the least expensive and highest prune-rate
899 // conditions are placed first, since if they return false Taffy skips any
900 // further condition tests.
901 //
902 // Other notes
903 // ***********
904 //
905 // This code passes jslint with the exception of 2 warnings about
906 // the '==' and '!=' lines. We can't do anything about that short of
907 // deleting the lines.
908 //
909 // Credits
910 // *******
911 //
912 // Heavily based upon the work of Ian Toltz.
913 // Revisions to API by Michael Mikowski.
914 // Code convention per standards in http://manning.com/mikowski
915 (function () {
916 var innerJoinFunction = (function () {
917 var fnCompareList, fnCombineRow, fnMain;
918
919 fnCompareList = function ( left_row, right_row, arg_list ) {
920 var data_lt, data_rt, op_code, error;
921
922 if ( arg_list.length === 2 ){
923 data_lt = left_row[arg_list[0]];
924 op_code = '===';
925 data_rt = right_row[arg_list[1]];
926 }
927 else {
928 data_lt = left_row[arg_list[0]];
929 op_code = arg_list[1];
930 data_rt = right_row[arg_list[2]];
931 }
932
933 /*jslint eqeq : true */
934 switch ( op_code ){
935 case '===' :
936 return data_lt === data_rt;
937 case '!==' :
938 return data_lt !== data_rt;
939 case '<' :
940 return data_lt < data_rt;
941 case '>' :
942 return data_lt > data_rt;
943 case '<=' :
944 return data_lt <= data_rt;
945 case '>=' :
946 return data_lt >= data_rt;
947 case '==' :
948 return data_lt == data_rt;
949 case '!=' :
950 return data_lt != data_rt;
951 default :
952 throw String( op_code ) + ' is not supported';
953 }
954 // 'jslint eqeq : false' here results in
955 // "Unreachable '/*jslint' after 'return'".
956 // We don't need it though, as the rule exception
957 // is discarded at the end of this functional scope
958 };
959
960 fnCombineRow = function ( left_row, right_row ) {
961 var out_map = {}, i, prefix;
962
963 for ( i in left_row ){
964 if ( left_row.hasOwnProperty( i ) ){
965 out_map[i] = left_row[i];
966 }
967 }
968 for ( i in right_row ){
969 if ( right_row.hasOwnProperty( i ) && i !== '___id' &&
970 i !== '___s' )
971 {
972 prefix = !TAFFY.isUndefined( out_map[i] ) ? 'right_' : '';
973 out_map[prefix + String( i ) ] = right_row[i];
974 }
975 }
976 return out_map;
977 };
978
979 fnMain = function ( table ) {
980 var
981 right_table, i,
982 arg_list = arguments,
983 arg_length = arg_list.length,
984 result_list = []
985 ;
986
987 if ( typeof table.filter !== 'function' ){
988 if ( table.TAFFY ){ right_table = table(); }
989 else {
990 throw 'TAFFY DB or result not supplied';
991 }
992 }
993 else { right_table = table; }
994
995 this.context( {
996 results : this.getDBI().query( this.context() )
997 } );
998
999 TAFFY.each( this.context().results, function ( left_row ) {
1000 right_table.each( function ( right_row ) {
1001 var arg_data, is_ok = true;
1002 CONDITION:
1003 for ( i = 1; i < arg_length; i++ ){
1004 arg_data = arg_list[i];
1005 if ( typeof arg_data === 'function' ){
1006 is_ok = arg_data( left_row, right_row );
1007 }
1008 else if ( typeof arg_data === 'object' && arg_data.length ){
1009 is_ok = fnCompareList( left_row, right_row, arg_data );
1010 }
1011 else {
1012 is_ok = false;
1013 }
1014
1015 if ( !is_ok ){ break CONDITION; } // short circuit
1016 }
1017
1018 if ( is_ok ){
1019 result_list.push( fnCombineRow( left_row, right_row ) );
1020 }
1021 } );
1022 } );
1023 return TAFFY( result_list )();
1024 };
1025
1026 return fnMain;
1027 }());
1028
1029 API.extend( 'join', innerJoinFunction );
1030 }());
1031
1032 API.extend( 'max', function ( c ) {
1033 // ****************************************
1034 // *
1035 // * Takes: column to find max
1036 // * Returns: the highest value
1037 // ****************************************
1038 var highest = null;
1039 run.call( this );
1040 each( this.context().results, function ( r ) {
1041 if ( highest === null || r[c] > highest ){
1042 highest = r[c];
1043 }
1044 });
1045 return highest;
1046 });
1047
1048 API.extend( 'select', function () {
1049 // ****************************************
1050 // *
1051 // * Takes: columns to select values into an array
1052 // * Returns: array of values
1053 // * Note if more than one column is given an array of arrays is returned
1054 // ****************************************
1055
1056 var ra = [], args = arguments;
1057 run.call( this );
1058 if ( arguments.length === 1 ){
1059
1060 each( this.context().results, function ( r ) {
1061
1062 ra.push( r[args[0]] );
1063 });
1064 }
1065 else {
1066 each( this.context().results, function ( r ) {
1067 var row = [];
1068 each( args, function ( c ) {
1069 row.push( r[c] );
1070 });
1071 ra.push( row );
1072 });
1073 }
1074 return ra;
1075 });
1076 API.extend( 'distinct', function () {
1077 // ****************************************
1078 // *
1079 // * Takes: columns to select unique alues into an array
1080 // * Returns: array of values
1081 // * Note if more than one column is given an array of arrays is returned
1082 // ****************************************
1083 var ra = [], args = arguments;
1084 run.call( this );
1085 if ( arguments.length === 1 ){
1086
1087 each( this.context().results, function ( r ) {
1088 var v = r[args[0]], dup = false;
1089 each( ra, function ( d ) {
1090 if ( v === d ){
1091 dup = true;
1092 return TAFFY.EXIT;
1093 }
1094 });
1095 if ( !dup ){
1096 ra.push( v );
1097 }
1098 });
1099 }
1100 else {
1101 each( this.context().results, function ( r ) {
1102 var row = [], dup = false;
1103 each( args, function ( c ) {
1104 row.push( r[c] );
1105 });
1106 each( ra, function ( d ) {
1107 var ldup = true;
1108 each( args, function ( c, i ) {
1109 if ( row[i] !== d[i] ){
1110 ldup = false;
1111 return TAFFY.EXIT;
1112 }
1113 });
1114 if ( ldup ){
1115 dup = true;
1116 return TAFFY.EXIT;
1117 }
1118 });
1119 if ( !dup ){
1120 ra.push( row );
1121 }
1122 });
1123 }
1124 return ra;
1125 });
1126 API.extend( 'supplant', function ( template, returnarray ) {
1127 // ****************************************
1128 // *
1129 // * Takes: a string template formated with key to be replaced with values from the rows, flag to determine if we want array of strings
1130 // * Returns: array of values or a string
1131 // ****************************************
1132 var ra = [];
1133 run.call( this );
1134 each( this.context().results, function ( r ) {
1135 // TODO: The curly braces used to be unescaped
1136 ra.push( template.replace( /\{([^\{\}]*)\}/g, function ( a, b ) {
1137 var v = r[b];
1138 return typeof v === 'string' || typeof v === 'number' ? v : a;
1139 } ) );
1140 });
1141 return (!returnarray) ? ra.join( "" ) : ra;
1142 });
1143
1144
1145 API.extend( 'each', function ( m ) {
1146 // ****************************************
1147 // *
1148 // * Takes: a function
1149 // * Purpose: loops over every matching record and applies the function
1150 // ****************************************
1151 run.call( this );
1152 each( this.context().results, m );
1153 return this;
1154 });
1155 API.extend( 'map', function ( m ) {
1156 // ****************************************
1157 // *
1158 // * Takes: a function
1159 // * Purpose: loops over every matching record and applies the function, returing the results in an array
1160 // ****************************************
1161 var ra = [];
1162 run.call( this );
1163 each( this.context().results, function ( r ) {
1164 ra.push( m( r ) );
1165 });
1166 return ra;
1167 });
1168
1169
1170
1171 T = function ( d ) {
1172 // ****************************************
1173 // *
1174 // * T is the main TAFFY object
1175 // * Takes: an array of objects or JSON
1176 // * Returns a new TAFFYDB
1177 // ****************************************
1178 var TOb = [],
1179 ID = {},
1180 RC = 1,
1181 settings = {
1182 template : false,
1183 onInsert : false,
1184 onUpdate : false,
1185 onRemove : false,
1186 onDBChange : false,
1187 storageName : false,
1188 forcePropertyCase : null,
1189 cacheSize : 100,
1190 name : ''
1191 },
1192 dm = new Date(),
1193 CacheCount = 0,
1194 CacheClear = 0,
1195 Cache = {},
1196 DBI, runIndexes, root
1197 ;
1198 // ****************************************
1199 // *
1200 // * TOb = this database
1201 // * ID = collection of the record IDs and locations within the DB, used for fast lookups
1202 // * RC = record counter, used for creating IDs
1203 // * settings.template = the template to merge all new records with
1204 // * settings.onInsert = event given a copy of the newly inserted record
1205 // * settings.onUpdate = event given the original record, the changes, and the new record
1206 // * settings.onRemove = event given the removed record
1207 // * settings.forcePropertyCase = on insert force the proprty case to be lower or upper. default lower, null/undefined will leave case as is
1208 // * dm = the modify date of the database, used for query caching
1209 // ****************************************
1210
1211
1212 runIndexes = function ( indexes ) {
1213 // ****************************************
1214 // *
1215 // * Takes: a collection of indexes
1216 // * Returns: collection with records matching indexed filters
1217 // ****************************************
1218
1219 var records = [], UniqueEnforce = false;
1220
1221 if ( indexes.length === 0 ){
1222 return TOb;
1223 }
1224
1225 each( indexes, function ( f ) {
1226 // Check to see if record ID
1227 if ( T.isString( f ) && /[t][0-9]*[r][0-9]*/i.test( f ) &&
1228 TOb[ID[f]] )
1229 {
1230 records.push( TOb[ID[f]] );
1231 UniqueEnforce = true;
1232 }
1233 // Check to see if record
1234 if ( T.isObject( f ) && f.___id && f.___s &&
1235 TOb[ID[f.___id]] )
1236 {
1237 records.push( TOb[ID[f.___id]] );
1238 UniqueEnforce = true;
1239 }
1240 // Check to see if array of indexes
1241 if ( T.isArray( f ) ){
1242 each( f, function ( r ) {
1243 each( runIndexes( r ), function ( rr ) {
1244 records.push( rr );
1245 });
1246
1247 });
1248 }
1249 });
1250 if ( UniqueEnforce && records.length > 1 ){
1251 records = [];
1252 }
1253
1254 return records;
1255 };
1256
1257 DBI = {
1258 // ****************************************
1259 // *
1260 // * The DBI is the internal DataBase Interface that interacts with the data
1261 // ****************************************
1262 dm : function ( nd ) {
1263 // ****************************************
1264 // *
1265 // * Takes: an optional new modify date
1266 // * Purpose: used to get and set the DB modify date
1267 // ****************************************
1268 if ( nd ){
1269 dm = nd;
1270 Cache = {};
1271 CacheCount = 0;
1272 CacheClear = 0;
1273 }
1274 if ( settings.onDBChange ){
1275 setTimeout( function () {
1276 settings.onDBChange.call( TOb );
1277 }, 0 );
1278 }
1279 if ( settings.storageName ){
1280 setTimeout( function () {
1281 localStorage.setItem( 'taffy_' + settings.storageName,
1282 JSON.stringify( TOb ) );
1283 });
1284 }
1285 return dm;
1286 },
1287 insert : function ( i, runEvent ) {
1288 // ****************************************
1289 // *
1290 // * Takes: a new record to insert
1291 // * Purpose: merge the object with the template, add an ID, insert into DB, call insert event
1292 // ****************************************
1293 var columns = [],
1294 records = [],
1295 input = protectJSON( i )
1296 ;
1297 each( input, function ( v, i ) {
1298 var nv, o;
1299 if ( T.isArray( v ) && i === 0 ){
1300 each( v, function ( av ) {
1301
1302 columns.push( (settings.forcePropertyCase === 'lower')
1303 ? av.toLowerCase()
1304 : (settings.forcePropertyCase === 'upper')
1305 ? av.toUpperCase() : av );
1306 });
1307 return true;
1308 }
1309 else if ( T.isArray( v ) ){
1310 nv = {};
1311 each( v, function ( av, ai ) {
1312 nv[columns[ai]] = av;
1313 });
1314 v = nv;
1315
1316 }
1317 else if ( T.isObject( v ) && settings.forcePropertyCase ){
1318 o = {};
1319
1320 eachin( v, function ( av, ai ) {
1321 o[(settings.forcePropertyCase === 'lower') ? ai.toLowerCase()
1322 : (settings.forcePropertyCase === 'upper')
1323 ? ai.toUpperCase() : ai] = v[ai];
1324 });
1325 v = o;
1326 }
1327
1328 RC++;
1329 v.___id = 'T' + String( idpad + TC ).slice( -6 ) + 'R' +
1330 String( idpad + RC ).slice( -6 );
1331 v.___s = true;
1332 records.push( v.___id );
1333 if ( settings.template ){
1334 v = T.mergeObj( settings.template, v );
1335 }
1336 TOb.push( v );
1337
1338 ID[v.___id] = TOb.length - 1;
1339 if ( settings.onInsert &&
1340 (runEvent || TAFFY.isUndefined( runEvent )) )
1341 {
1342 settings.onInsert.call( v );
1343 }
1344 DBI.dm( new Date() );
1345 });
1346 return root( records );
1347 },
1348 sort : function ( o ) {
1349 // ****************************************
1350 // *
1351 // * Purpose: Change the sort order of the DB itself and reset the ID bucket
1352 // ****************************************
1353 TOb = orderByCol( TOb, o.split( ',' ) );
1354 ID = {};
1355 each( TOb, function ( r, i ) {
1356 ID[r.___id] = i;
1357 });
1358 DBI.dm( new Date() );
1359 return true;
1360 },
1361 update : function ( id, changes, runEvent ) {
1362 // ****************************************
1363 // *
1364 // * Takes: the ID of record being changed and the changes
1365 // * Purpose: Update a record and change some or all values, call the on update method
1366 // ****************************************
1367
1368 var nc = {}, or, nr, tc, hasChange;
1369 if ( settings.forcePropertyCase ){
1370 eachin( changes, function ( v, p ) {
1371 nc[(settings.forcePropertyCase === 'lower') ? p.toLowerCase()
1372 : (settings.forcePropertyCase === 'upper') ? p.toUpperCase()
1373 : p] = v;
1374 });
1375 changes = nc;
1376 }
1377
1378 or = TOb[ID[id]];
1379 nr = T.mergeObj( or, changes );
1380
1381 tc = {};
1382 hasChange = false;
1383 eachin( nr, function ( v, i ) {
1384 if ( TAFFY.isUndefined( or[i] ) || or[i] !== v ){
1385 tc[i] = v;
1386 hasChange = true;
1387 }
1388 });
1389 if ( hasChange ){
1390 if ( settings.onUpdate &&
1391 (runEvent || TAFFY.isUndefined( runEvent )) )
1392 {
1393 settings.onUpdate.call( nr, TOb[ID[id]], tc );
1394 }
1395 TOb[ID[id]] = nr;
1396 DBI.dm( new Date() );
1397 }
1398 },
1399 remove : function ( id ) {
1400 // ****************************************
1401 // *
1402 // * Takes: the ID of record to be removed
1403 // * Purpose: remove a record, changes its ___s value to false
1404 // ****************************************
1405 TOb[ID[id]].___s = false;
1406 },
1407 removeCommit : function ( runEvent ) {
1408 var x;
1409 // ****************************************
1410 // *
1411 // *
1412 // * Purpose: loop over all records and remove records with ___s = false, call onRemove event, clear ID
1413 // ****************************************
1414 for ( x = TOb.length - 1; x > -1; x-- ){
1415
1416 if ( !TOb[x].___s ){
1417 if ( settings.onRemove &&
1418 (runEvent || TAFFY.isUndefined( runEvent )) )
1419 {
1420 settings.onRemove.call( TOb[x] );
1421 }
1422 ID[TOb[x].___id] = undefined;
1423 TOb.splice( x, 1 );
1424 }
1425 }
1426 ID = {};
1427 each( TOb, function ( r, i ) {
1428 ID[r.___id] = i;
1429 });
1430 DBI.dm( new Date() );
1431 },
1432 query : function ( context ) {
1433 // ****************************************
1434 // *
1435 // * Takes: the context object for a query and either returns a cache result or a new query result
1436 // ****************************************
1437 var returnq, cid, results, indexed, limitq, ni;
1438
1439 if ( settings.cacheSize ) {
1440 cid = '';
1441 each( context.filterRaw, function ( r ) {
1442 if ( T.isFunction( r ) ){
1443 cid = 'nocache';
1444 return TAFFY.EXIT;
1445 }
1446 });
1447 if ( cid === '' ){
1448 cid = makeCid( T.mergeObj( context,
1449 {q : false, run : false, sort : false} ) );
1450 }
1451 }
1452 // Run a new query if there are no results or the run date has been cleared
1453 if ( !context.results || !context.run ||
1454 (context.run && DBI.dm() > context.run) )
1455 {
1456 results = [];
1457
1458 // check Cache
1459
1460 if ( settings.cacheSize && Cache[cid] ){
1461
1462 Cache[cid].i = CacheCount++;
1463 return Cache[cid].results;
1464 }
1465 else {
1466 // if no filter, return DB
1467 if ( context.q.length === 0 && context.index.length === 0 ){
1468 each( TOb, function ( r ) {
1469 results.push( r );
1470 });
1471 returnq = results;
1472 }
1473 else {
1474 // use indexes
1475
1476 indexed = runIndexes( context.index );
1477
1478 // run filters
1479 each( indexed, function ( r ) {
1480 // Run filter to see if record matches query
1481 if ( context.q.length === 0 || runFilters( r, context.q ) ){
1482 results.push( r );
1483 }
1484 });
1485
1486 returnq = results;
1487 }
1488 }
1489
1490
1491 }
1492 else {
1493 // If query exists and run has not been cleared return the cache results
1494 returnq = context.results;
1495 }
1496 // If a custom order array exists and the run has been clear or the sort has been cleared
1497 if ( context.order.length > 0 && (!context.run || !context.sort) ){
1498 // order the results
1499 returnq = orderByCol( returnq, context.order );
1500 }
1501
1502 // If a limit on the number of results exists and it is less than the returned results, limit results
1503 if ( returnq.length &&
1504 ((context.limit && context.limit < returnq.length) ||
1505 context.start)
1506 ) {
1507 limitq = [];
1508 each( returnq, function ( r, i ) {
1509 if ( !context.start ||
1510 (context.start && (i + 1) >= context.start) )
1511 {
1512 if ( context.limit ){
1513 ni = (context.start) ? (i + 1) - context.start : i;
1514 if ( ni < context.limit ){
1515 limitq.push( r );
1516 }
1517 else if ( ni > context.limit ){
1518 return TAFFY.EXIT;
1519 }
1520 }
1521 else {
1522 limitq.push( r );
1523 }
1524 }
1525 });
1526 returnq = limitq;
1527 }
1528
1529 // update cache
1530 if ( settings.cacheSize && cid !== 'nocache' ){
1531 CacheClear++;
1532
1533 setTimeout( function () {
1534 var bCounter, nc;
1535 if ( CacheClear >= settings.cacheSize * 2 ){
1536 CacheClear = 0;
1537 bCounter = CacheCount - settings.cacheSize;
1538 nc = {};
1539 eachin( function ( r, k ) {
1540 if ( r.i >= bCounter ){
1541 nc[k] = r;
1542 }
1543 });
1544 Cache = nc;
1545 }
1546 }, 0 );
1547
1548 Cache[cid] = { i : CacheCount++, results : returnq };
1549 }
1550 return returnq;
1551 }
1552 };
1553
1554
1555 root = function () {
1556 var iAPI, context;
1557 // ****************************************
1558 // *
1559 // * The root function that gets returned when a new DB is created
1560 // * Takes: unlimited filter arguments and creates filters to be run when a query is called
1561 // ****************************************
1562 // ****************************************
1563 // *
1564 // * iAPI is the the method collection valiable when a query has been started by calling dbname
1565 // * Certain methods are or are not avaliable once you have started a query such as insert -- you can only insert into root
1566 // ****************************************
1567 iAPI = TAFFY.mergeObj( TAFFY.mergeObj( API, { insert : undefined } ),
1568 { getDBI : function () { return DBI; },
1569 getroot : function ( c ) { return root.call( c ); },
1570 context : function ( n ) {
1571 // ****************************************
1572 // *
1573 // * The context contains all the information to manage a query including filters, limits, and sorts
1574 // ****************************************
1575 if ( n ){
1576 context = TAFFY.mergeObj( context,
1577 n.hasOwnProperty('results')
1578 ? TAFFY.mergeObj( n, { run : new Date(), sort: new Date() })
1579 : n
1580 );
1581 }
1582 return context;
1583 },
1584 extend : undefined
1585 });
1586
1587 context = (this && this.q) ? this : {
1588 limit : false,
1589 start : false,
1590 q : [],
1591 filterRaw : [],
1592 index : [],
1593 order : [],
1594 results : false,
1595 run : null,
1596 sort : null,
1597 settings : settings
1598 };
1599 // ****************************************
1600 // *
1601 // * Call the query method to setup a new query
1602 // ****************************************
1603 each( arguments, function ( f ) {
1604
1605 if ( isIndexable( f ) ){
1606 context.index.push( f );
1607 }
1608 else {
1609 context.q.push( returnFilter( f ) );
1610 }
1611 context.filterRaw.push( f );
1612 });
1613
1614
1615 return iAPI;
1616 };
1617
1618 // ****************************************
1619 // *
1620 // * If new records have been passed on creation of the DB either as JSON or as an array/object, insert them
1621 // ****************************************
1622 TC++;
1623 if ( d ){
1624 DBI.insert( d );
1625 }
1626
1627
1628 root.insert = DBI.insert;
1629
1630 root.merge = function ( i, key, runEvent ) {
1631 var
1632 search = {},
1633 finalSearch = [],
1634 obj = {}
1635 ;
1636
1637 runEvent = runEvent || false;
1638 key = key || 'id';
1639
1640 each( i, function ( o ) {
1641 var existingObject;
1642 search[key] = o[key];
1643 finalSearch.push( o[key] );
1644 existingObject = root( search ).first();
1645 if ( existingObject ){
1646 DBI.update( existingObject.___id, o, runEvent );
1647 }
1648 else {
1649 DBI.insert( o, runEvent );
1650 }
1651 });
1652
1653 obj[key] = finalSearch;
1654 return root( obj );
1655 };
1656
1657 root.TAFFY = true;
1658 root.sort = DBI.sort;
1659 // ****************************************
1660 // *
1661 // * These are the methods that can be accessed on off the root DB function. Example dbname.insert;
1662 // ****************************************
1663 root.settings = function ( n ) {
1664 // ****************************************
1665 // *
1666 // * Getting and setting for this DB's settings/events
1667 // ****************************************
1668 if ( n ){
1669 settings = TAFFY.mergeObj( settings, n );
1670 if ( n.template ){
1671
1672 root().update( n.template );
1673 }
1674 }
1675 return settings;
1676 };
1677
1678 // ****************************************
1679 // *
1680 // * These are the methods that can be accessed on off the root DB function. Example dbname.insert;
1681 // ****************************************
1682 root.store = function ( n ) {
1683 // ****************************************
1684 // *
1685 // * Setup localstorage for this DB on a given name
1686 // * Pull data into the DB as needed
1687 // ****************************************
1688 var r = false, i;
1689 if ( localStorage ){
1690 if ( n ){
1691 i = localStorage.getItem( 'taffy_' + n );
1692 if ( i && i.length > 0 ){
1693 root.insert( i );
1694 r = true;
1695 }
1696 if ( TOb.length > 0 ){
1697 setTimeout( function () {
1698 localStorage.setItem( 'taffy_' + settings.storageName,
1699 JSON.stringify( TOb ) );
1700 });
1701 }
1702 }
1703 root.settings( {storageName : n} );
1704 }
1705 return root;
1706 };
1707
1708 // ****************************************
1709 // *
1710 // * Return root on DB creation and start having fun
1711 // ****************************************
1712 return root;
1713 };
1714 // ****************************************
1715 // *
1716 // * Sets the global TAFFY object
1717 // ****************************************
1718 TAFFY = T;
1719
1720
1721 // ****************************************
1722 // *
1723 // * Create public each method
1724 // *
1725 // ****************************************
1726 T.each = each;
1727
1728 // ****************************************
1729 // *
1730 // * Create public eachin method
1731 // *
1732 // ****************************************
1733 T.eachin = eachin;
1734 // ****************************************
1735 // *
1736 // * Create public extend method
1737 // * Add a custom method to the API
1738 // *
1739 // ****************************************
1740 T.extend = API.extend;
1741
1742
1743 // ****************************************
1744 // *
1745 // * Creates TAFFY.EXIT value that can be returned to stop an each loop
1746 // *
1747 // ****************************************
1748 TAFFY.EXIT = 'TAFFYEXIT';
1749
1750 // ****************************************
1751 // *
1752 // * Create public utility mergeObj method
1753 // * Return a new object where items from obj2
1754 // * have replaced or been added to the items in
1755 // * obj1
1756 // * Purpose: Used to combine objs
1757 // *
1758 // ****************************************
1759 TAFFY.mergeObj = function ( ob1, ob2 ) {
1760 var c = {};
1761 eachin( ob1, function ( v, n ) { c[n] = ob1[n]; });
1762 eachin( ob2, function ( v, n ) { c[n] = ob2[n]; });
1763 return c;
1764 };
1765
1766
1767 // ****************************************
1768 // *
1769 // * Create public utility has method
1770 // * Returns true if a complex object, array
1771 // * or taffy collection contains the material
1772 // * provided in the second argument
1773 // * Purpose: Used to comare objects
1774 // *
1775 // ****************************************
1776 TAFFY.has = function ( var1, var2 ) {
1777
1778 var re = false, n;
1779
1780 if ( (var1.TAFFY) ){
1781 re = var1( var2 );
1782 if ( re.length > 0 ){
1783 return true;
1784 }
1785 else {
1786 return false;
1787 }
1788 }
1789 else {
1790
1791 switch ( T.typeOf( var1 ) ){
1792 case 'object':
1793 if ( T.isObject( var2 ) ){
1794 eachin( var2, function ( v, n ) {
1795 if ( re === true && !T.isUndefined( var1[n] ) &&
1796 var1.hasOwnProperty( n ) )
1797 {
1798 re = T.has( var1[n], var2[n] );
1799 }
1800 else {
1801 re = false;
1802 return TAFFY.EXIT;
1803 }
1804 });
1805 }
1806 else if ( T.isArray( var2 ) ){
1807 each( var2, function ( v, n ) {
1808 re = T.has( var1, var2[n] );
1809 if ( re ){
1810 return TAFFY.EXIT;
1811 }
1812 });
1813 }
1814 else if ( T.isString( var2 ) ){
1815 if ( !TAFFY.isUndefined( var1[var2] ) ){
1816 return true;
1817 }
1818 else {
1819 return false;
1820 }
1821 }
1822 return re;
1823 case 'array':
1824 if ( T.isObject( var2 ) ){
1825 each( var1, function ( v, i ) {
1826 re = T.has( var1[i], var2 );
1827 if ( re === true ){
1828 return TAFFY.EXIT;
1829 }
1830 });
1831 }
1832 else if ( T.isArray( var2 ) ){
1833 each( var2, function ( v2, i2 ) {
1834 each( var1, function ( v1, i1 ) {
1835 re = T.has( var1[i1], var2[i2] );
1836 if ( re === true ){
1837 return TAFFY.EXIT;
1838 }
1839 });
1840 if ( re === true ){
1841 return TAFFY.EXIT;
1842 }
1843 });
1844 }
1845 else if ( T.isString( var2 ) || T.isNumber( var2 ) ){
1846 re = false;
1847 for ( n = 0; n < var1.length; n++ ){
1848 re = T.has( var1[n], var2 );
1849 if ( re ){
1850 return true;
1851 }
1852 }
1853 }
1854 return re;
1855 case 'string':
1856 if ( T.isString( var2 ) && var2 === var1 ){
1857 return true;
1858 }
1859 break;
1860 default:
1861 if ( T.typeOf( var1 ) === T.typeOf( var2 ) && var1 === var2 ){
1862 return true;
1863 }
1864 break;
1865 }
1866 }
1867 return false;
1868 };
1869
1870 // ****************************************
1871 // *
1872 // * Create public utility hasAll method
1873 // * Returns true if a complex object, array
1874 // * or taffy collection contains the material
1875 // * provided in the call - for arrays it must
1876 // * contain all the material in each array item
1877 // * Purpose: Used to comare objects
1878 // *
1879 // ****************************************
1880 TAFFY.hasAll = function ( var1, var2 ) {
1881
1882 var T = TAFFY, ar;
1883 if ( T.isArray( var2 ) ){
1884 ar = true;
1885 each( var2, function ( v ) {
1886 ar = T.has( var1, v );
1887 if ( ar === false ){
1888 return TAFFY.EXIT;
1889 }
1890 });
1891 return ar;
1892 }
1893 else {
1894 return T.has( var1, var2 );
1895 }
1896 };
1897
1898
1899 // ****************************************
1900 // *
1901 // * typeOf Fixed in JavaScript as public utility
1902 // *
1903 // ****************************************
1904 TAFFY.typeOf = function ( v ) {
1905 var s = typeof v;
1906 if ( s === 'object' ){
1907 if ( v ){
1908 if ( typeof v.length === 'number' &&
1909 !(v.propertyIsEnumerable( 'length' )) )
1910 {
1911 s = 'array';
1912 }
1913 }
1914 else {
1915 s = 'null';
1916 }
1917 }
1918 return s;
1919 };
1920
1921 // ****************************************
1922 // *
1923 // * Create public utility getObjectKeys method
1924 // * Returns an array of an objects keys
1925 // * Purpose: Used to get the keys for an object
1926 // *
1927 // ****************************************
1928 TAFFY.getObjectKeys = function ( ob ) {
1929 var kA = [];
1930 eachin( ob, function ( n, h ) {
1931 kA.push( h );
1932 });
1933 kA.sort();
1934 return kA;
1935 };
1936
1937 // ****************************************
1938 // *
1939 // * Create public utility isSameArray
1940 // * Returns an array of an objects keys
1941 // * Purpose: Used to get the keys for an object
1942 // *
1943 // ****************************************
1944 TAFFY.isSameArray = function ( ar1, ar2 ) {
1945 return (TAFFY.isArray( ar1 ) && TAFFY.isArray( ar2 ) &&
1946 ar1.join( ',' ) === ar2.join( ',' )) ? true : false;
1947 };
1948
1949 // ****************************************
1950 // *
1951 // * Create public utility isSameObject method
1952 // * Returns true if objects contain the same
1953 // * material or false if they do not
1954 // * Purpose: Used to comare objects
1955 // *
1956 // ****************************************
1957 TAFFY.isSameObject = function ( ob1, ob2 ) {
1958 var T = TAFFY, rv = true;
1959
1960 if ( T.isObject( ob1 ) && T.isObject( ob2 ) ){
1961 if ( T.isSameArray( T.getObjectKeys( ob1 ),
1962 T.getObjectKeys( ob2 ) ) )
1963 {
1964 eachin( ob1, function ( v, n ) {
1965 if ( ! ( (T.isObject( ob1[n] ) && T.isObject( ob2[n] ) &&
1966 T.isSameObject( ob1[n], ob2[n] )) ||
1967 (T.isArray( ob1[n] ) && T.isArray( ob2[n] ) &&
1968 T.isSameArray( ob1[n], ob2[n] )) || (ob1[n] === ob2[n]) )
1969 ) {
1970 rv = false;
1971 return TAFFY.EXIT;
1972 }
1973 });
1974 }
1975 else {
1976 rv = false;
1977 }
1978 }
1979 else {
1980 rv = false;
1981 }
1982 return rv;
1983 };
1984
1985 // ****************************************
1986 // *
1987 // * Create public utility is[DataType] methods
1988 // * Return true if obj is datatype, false otherwise
1989 // * Purpose: Used to determine if arguments are of certain data type
1990 // *
1991 // * mmikowski 2012-08-06 refactored to make much less "magical":
1992 // * fewer closures and passes jslint
1993 // *
1994 // ****************************************
1995
1996 typeList = [
1997 'String', 'Number', 'Object', 'Array',
1998 'Boolean', 'Null', 'Function', 'Undefined'
1999 ];
2000
2001 makeTest = function ( thisKey ) {
2002 return function ( data ) {
2003 return TAFFY.typeOf( data ) === thisKey.toLowerCase() ? true : false;
2004 };
2005 };
2006
2007 for ( idx = 0; idx < typeList.length; idx++ ){
2008 typeKey = typeList[idx];
2009 TAFFY['is' + typeKey] = makeTest( typeKey );
2010 }
2011 }
2012}());
2013
2014if ( typeof(exports) === 'object' ){
2015 exports.taffy = TAFFY;
2016}
2017