taffy.js 64 KB

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