taffy.js 62 KB

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