filterjs-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5342b3c97143e1cd7e9ab71978670e64cc9b2c6f
4
+ data.tar.gz: 871398441f3458b4d7d69a41d3cf2482d386bf5f
5
+ SHA512:
6
+ metadata.gz: 8d20045420a7bc51002f45da9feaaf69d4cfca9c359c8b1b506efa937611ce2481fe1205f1b9f629e27f28672149552786d0cd79c8feb3eccb50fca5b3729a18
7
+ data.tar.gz: b8511e2a8891e6df1e046af5f35360b7bff3c29c8e5808581d4c727ac25b12f7c016e616bae322867260b4bcea97417a1d8f208fa788c0924d8a6b44915ae619
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gem "rake"
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Kazuho Yamaguchi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,33 @@
1
+ # Filter.js for Rails
2
+
3
+ Integrate [Filter.js](https://github.com/jiren/filter.js) into Rails
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'filterjs-rails'
11
+ ```
12
+
13
+ And then execute:
14
+ ```console
15
+ $ bundle
16
+ ```
17
+
18
+ Or install it yourself as:
19
+
20
+ ```console
21
+ $ gem install filterjs-rails
22
+ ```
23
+
24
+ Add this to your application.js file:
25
+
26
+ ```javascript
27
+ //= require filter
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ Visit: https://github.com/jiren/filter.js
33
+ Demo: http://jiren.github.io/filter.js/
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ require File.expand_path('../lib/filterjs-rails/version', __FILE__)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "filterjs-rails"
6
+ spec.version = Filterjs::Rails::VERSION
7
+ spec.authors = ["Kazuho Yamaguchi"]
8
+ spec.email = ["kzh.yap@gmail.com"]
9
+
10
+ spec.summary = %q{Filter.js for Rails}
11
+ spec.description = %q{Filter.js for Ruby on Rails}
12
+ spec.homepage = "https://github.com/kyamaguchi/filterjs-rails"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "railties", "> 3.1"
22
+ end
@@ -0,0 +1,8 @@
1
+ require "filterjs-rails/version"
2
+
3
+ module Filterjs
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Filterjs
2
+ module Rails
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,1727 @@
1
+ /*
2
+ * filter.js
3
+ * 2.1.0 (2016-08-09)
4
+ *
5
+ * Released under the MIT license
6
+ * http://opensource.org/licenses/MIT
7
+ *
8
+ * Copyright 2011-2016 Jiren Patel[jirenpatel@gmail.com]
9
+ *
10
+ * Dependency:
11
+ * jQuery(v1.9 >=)
12
+ */
13
+
14
+ /*
15
+ * JsonQuery
16
+ * 0.0.2 (2015-08-06)
17
+ *
18
+ * Released under the MIT license
19
+ * http://opensource.org/licenses/MIT
20
+ *
21
+ * Copyright 2011-2015 Jiren Patel[jirenpatel@gmail.com]
22
+ *
23
+ */
24
+
25
+ (function(window) {
26
+
27
+ 'use strict';
28
+
29
+ var JsonQuery = function(records, opts){
30
+ return new _JsonQuery(records, opts || {});
31
+ };
32
+
33
+ window.JsonQuery = JsonQuery;
34
+
35
+ JsonQuery.VERSION = '0.0.2'
36
+
37
+ JsonQuery.Config = {
38
+ id: 'id',
39
+ latitude: 'latitude',
40
+ longitude: 'longitude',
41
+ date_regx: /^\d{4}-\d{2}-\d{2}$/
42
+ }
43
+
44
+ JsonQuery.blankClone = function(jq, records){
45
+ return new _JsonQuery(records, {
46
+ getterFns: jq.getterFns,
47
+ schema: jq.schema,
48
+ id: jq.id,
49
+ latitude: jq.latitude,
50
+ longitude: jq.longitude
51
+ })
52
+ }
53
+
54
+ var Config = JsonQuery.Config;
55
+
56
+ var each = function(objs, callback, context){
57
+ if (objs.length === +objs.length) {
58
+ for (var i = 0, l = objs.length; i < l; i++) {
59
+ callback.call(context, objs[i], i);
60
+ }
61
+ }else{
62
+ for (var key in objs) {
63
+ if (hasOwnProperty.call(objs, key)) {
64
+ callback.call(context, objs[key], key);
65
+ }
66
+ }
67
+ }
68
+ };
69
+
70
+ var eachWithBreak = function(objs, callback, context){
71
+ for (var i = 0, l = objs.length; i < l; i++) {
72
+ if(callback.call(context, objs[i], i) === false){
73
+ return;
74
+ }
75
+ }
76
+ };
77
+
78
+
79
+ var _JsonQuery = function(records, opts){
80
+ this.records = records || [];
81
+ this.getterFns = opts.getterFns || {};
82
+ this.lat = opts.latitude || Config.latitude;
83
+ this.lng = opts.longitude || Config.longitude;
84
+ this.id = opts.id;
85
+
86
+ if(opts.schema){
87
+ this.schema = opts.schema;
88
+ }
89
+
90
+ if(this.records.length && !this.schema){
91
+ initSchema(this, records[0], opts.schema);
92
+ }
93
+ };
94
+
95
+ var JQ = _JsonQuery.prototype;
96
+
97
+ var initSchema = function(context, record, hasSchema){
98
+ context.schema = {};
99
+
100
+ if(!context.id){
101
+ context.id = record._id ? '_id' : Config.id;
102
+ }
103
+
104
+ if(!hasSchema){
105
+ buildSchema.call(context, record);
106
+ buildPropGetters.call(context, record);
107
+ }
108
+ };
109
+
110
+ var getDataType = function(val){
111
+ if(val == null){
112
+ return 'String';
113
+ }
114
+
115
+ /*
116
+ * @info Fix for IE 10 & 11
117
+ * @bug Invalid calling object
118
+ */
119
+ var type = Object.prototype.toString.call(val).slice(8, -1);
120
+
121
+ if(type == 'String' && val.match(Config.date_regx)){
122
+ return 'Date';
123
+ }
124
+
125
+ return type;
126
+ };
127
+
128
+ var parseValue = function(type, value){
129
+ if(!value && value != 0){
130
+ return value;
131
+ }
132
+
133
+ if(type == 'String'){
134
+ return String(value);
135
+ }else if(type == 'Number'){
136
+ return Number(value)
137
+ }else if(type == 'Boolean'){
138
+ return (value == 'true' || value == true || value == '1') ? true : false;
139
+ }else if(type == 'Date'){
140
+ return new Date(value)
141
+ }else{
142
+ return value
143
+ }
144
+ }
145
+
146
+ var buildSchema = function(obj, parentField){
147
+ var field, dataType, fullPath, fieldValue;
148
+
149
+ for(field in obj){
150
+ fieldValue = obj[field];
151
+ dataType = getDataType(fieldValue);
152
+
153
+ fullPath = parentField ? (parentField + '.' + field) : field;
154
+ this.schema[fullPath] = dataType;
155
+
156
+ if(dataType == 'Object'){
157
+ buildSchema.call(this, fieldValue, fullPath);
158
+ }else if(dataType == 'Array'){
159
+
160
+ if(['Object', 'Array'].indexOf(getDataType(fieldValue[0])) > -1){
161
+ buildSchema.call(this, obj[field][0], fullPath);
162
+ }else{
163
+ this.schema[fullPath] = getDataType(fieldValue[0]);
164
+ }
165
+ }
166
+ }
167
+ };
168
+
169
+ var parseDate = function(dates){
170
+ if(dates.constructor.name == 'Array'){
171
+ return dates.map(function(d){ return (d ? new Date(d) : null ) });
172
+ }
173
+ return (dates ? new Date(dates) : null);
174
+ };
175
+
176
+ var buildPropGetters = function(record){
177
+ var selector, type, val;
178
+
179
+ for(selector in this.schema){
180
+ type = this.schema[selector];
181
+
182
+ try{
183
+ if(!this.getterFns[selector]){
184
+ this.getterFns[selector] = buildGetPropFn.call(this, selector, type);
185
+ }
186
+
187
+ //Remap if it is array
188
+ val = this.getterFns[selector](record);
189
+ if(getDataType(val) == 'Array'){
190
+ this.schema[selector] = 'Array';
191
+ }
192
+ }catch(err){
193
+ console.log("Error while generating getter function for selector : " + selector + " NOTE: Define manually");
194
+ }
195
+ }
196
+ };
197
+
198
+ var countArrHierarchy = function(schema, nestedPath){
199
+ var lastArr = 0,
200
+ arrCount = 0,
201
+ path,
202
+ pathLength = nestedPath.length - 1;
203
+
204
+ for(var i = nestedPath.length - 1; i >= 0; i--){
205
+ path = nestedPath.slice(0, i + 1).join('.');
206
+
207
+ if(schema[path] == 'Array' && i < pathLength){
208
+ lastArr = i;
209
+ arrCount = arrCount + 1;
210
+ }
211
+ }
212
+ return (arrCount > 1 ? (lastArr + 1) : -1);
213
+ };
214
+
215
+ var buildGetPropFn = function(field, type){
216
+ var accessPath = '',
217
+ nestedPath = field.split('.'),
218
+ path,
219
+ lastArr = countArrHierarchy(this.schema, nestedPath),
220
+ prefix,
221
+ accessFnBody;
222
+
223
+ for(var i = nestedPath.length - 1; i >= 0; i--){
224
+ path = nestedPath.slice(0, i + 1).join('.');
225
+ prefix = "['" + nestedPath[i] +"']";
226
+
227
+ if(this.schema[path] == 'Array'){
228
+ if(lastArr == i){
229
+ accessPath = prefix + (accessPath.length ? ".every(function(r" + i +"){ objs.push(r" + i + accessPath + ")})" : '');
230
+ }else{
231
+ accessPath = prefix + (accessPath.length ? ".map(function(r" + i +"){ return r" + i + accessPath + "})" : '');
232
+ }
233
+ }else{
234
+ accessPath = prefix + accessPath;
235
+ }
236
+ }
237
+
238
+ if(lastArr > -1){
239
+ accessFnBody = 'var objs = []; obj' + accessPath + ';' + (this.schema['path'] == 'Date' ? 'return parseDate(objs)' : 'return objs;');
240
+ }else{
241
+ accessFnBody = 'return ' + (this.schema['path'] == 'Date' ? 'parseDate(obj'+ accessPath +');' : 'obj'+ accessPath +';') ;
242
+ }
243
+
244
+ return new Function('obj', accessFnBody);
245
+ };
246
+
247
+ JQ.operators = {
248
+ eq: function(v1, v2){ return v1 == v2},
249
+ ne: function(v1, v2){ return v1 != v2},
250
+ lt: function(v1, v2){ return v1 < v2},
251
+ lte: function(v1, v2){ return v1 <= v2},
252
+ gt: function(v1, v2){ return v1 > v2},
253
+ gte: function(v1, v2){ return v1 >= v2},
254
+ in: function(v1, v2){ return v2.indexOf(v1) > -1},
255
+ ni: function(v1, v2){ return v2.indexOf(v1) == -1},
256
+ li: function(v, regx) { return regx.test(v)},
257
+ bt: function(v1, v2){ return (v1 >= v2[0] && v1 <= v2[1])}
258
+ };
259
+
260
+ JQ.addOperator = function(name, fn){
261
+ this.operators[name] = fn;
262
+ };
263
+
264
+ // rVal = Record Value
265
+ // cVal = Condition Value
266
+ var arrayMatcher = function(rVal, cVal, cFn){
267
+ var i = 0, l = rVal.length;
268
+
269
+ for(i; i < l; i++){
270
+ if(cFn(rVal[i], cVal)) return true;
271
+ }
272
+ };
273
+
274
+ JQ.addCondition = function(name, func){
275
+ this.operators[name] = func;
276
+ };
277
+
278
+ JQ.getCriteria = function(criteria){
279
+ var fieldCondition = criteria.split('.$');
280
+
281
+ return {
282
+ field: fieldCondition[0],
283
+ operator: fieldCondition[1] || 'eq'
284
+ };
285
+ };
286
+
287
+ JQ.setGetterFn = function(field, fn){
288
+ this.getterFns[field] = fn;
289
+ };
290
+
291
+ JQ.addRecords = function(records){
292
+ if(!records || !records.length){
293
+ return false;
294
+ }
295
+
296
+ if(getDataType(records) == 'Array'){
297
+ this.records = this.records.concat(records);
298
+ }else{
299
+ this.records.push(records);
300
+ }
301
+
302
+ if(!this.schema){
303
+ initSchema(this, records[0]);
304
+ }
305
+
306
+ return true;
307
+ };
308
+
309
+ JQ._findAll = function(records, qField, cVal, cOpt){
310
+ var result = [],
311
+ cFn,
312
+ rVal,
313
+ qFn = this.getterFns[qField], arrayCFn;
314
+
315
+ if(cOpt == 'li' && typeof cVal == 'string'){
316
+ cVal = new RegExp(cVal);
317
+ }
318
+
319
+ cFn = this.operators[cOpt];
320
+
321
+ if(this.schema[qField] == 'Array'){
322
+ arrayCFn = cFn;
323
+ cFn = arrayMatcher;
324
+ }
325
+
326
+ each(records, function(v){
327
+ rVal = qFn(v);
328
+
329
+ if(cFn(rVal, cVal, arrayCFn)) {
330
+ result.push(v);
331
+ }
332
+ });
333
+
334
+ return result;
335
+ };
336
+
337
+ JQ.find = function(field, value){
338
+ var result, qFn;
339
+
340
+ if(!value){
341
+ value = field;
342
+ field = this.id;
343
+ }
344
+
345
+ qFn = this.getterFns[field];
346
+
347
+ eachWithBreak(this.records, function(r){
348
+ if(qFn(r) == value){
349
+ result = r;
350
+ return false;
351
+ }
352
+ });
353
+
354
+ return result;
355
+ };
356
+
357
+ each(['where', 'or', 'groupBy', 'select', 'pluck', 'limit', 'offset', 'order', 'uniq', 'near'], function(c){
358
+ JQ[c] = function(query){
359
+ var q = new Query(this, this.records);
360
+ q[c].apply(q, arguments);
361
+ return q;
362
+ };
363
+ });
364
+
365
+ each(['update_all', 'destroy_all'], function(c){
366
+ JQ[c] = function(query){
367
+ var q = new Query(this, this.records);
368
+ return q[c].apply(q, arguments);
369
+ };
370
+ });
371
+
372
+ each(['count', 'first', 'last', 'all'], function(c){
373
+ Object.defineProperty(JQ, c, {
374
+ get: function(){
375
+ return (new Query(this, this.records))[c];
376
+ }
377
+ });
378
+ });
379
+
380
+ var compareObj = function(obj1, obj2, fields){
381
+ for(var i = 0, l = fields.length; i < l; i++){
382
+ if(this.getterFns[fields[i]](obj1) !== this.getterFns[fields[i]](obj2)){
383
+ return false;
384
+ }
385
+ }
386
+
387
+ return true;
388
+ };
389
+
390
+ var execWhere = function(query, records){
391
+ var q, criteria, result;
392
+
393
+ for(q in query){
394
+ criteria = this.jQ.getCriteria(q);
395
+ result = this.jQ._findAll(result || records, criteria.field, query[q], criteria.operator);
396
+ }
397
+
398
+ return result;
399
+ };
400
+
401
+ var execGroupBy = function(field, records){
402
+ var fn = this.jQ.getterFns[field], v, result = {}, i = 0, l = records.length;
403
+
404
+ each(records, function(r){
405
+ v = fn(r);
406
+ (result[v] || (result[v] = [])).push(r);
407
+ });
408
+
409
+ return result;
410
+ };
411
+
412
+ var execOrder = function(orders, records){
413
+ var fn,
414
+ direction,
415
+ _records = records.slice(0);
416
+
417
+ for(var i = 0, l = orders.length; i < l; i++){
418
+ fn = this.jQ.getterFns[orders[i].field],
419
+ direction = orders[i].direction == 'asc' ? 1 : -1;
420
+
421
+ _records.sort(function(r1,r2){
422
+ var a = fn(r1), b = fn(r2);
423
+
424
+ return (a < b ? -1 : a > b ? 1 : 0)*direction;
425
+ })
426
+ }
427
+
428
+ return _records;
429
+ };
430
+
431
+ var execSelect = function(fields, records){
432
+ var self = this, result = [], getFn;
433
+
434
+ each(fields, function(f){
435
+ getFn = self.jQ.getterFns[f];
436
+
437
+ each(records, function(r, i){
438
+ (result[i] || (result[i] = {}))[f] = getFn(r);
439
+ });
440
+ });
441
+
442
+ return result;
443
+ };
444
+
445
+ var execPluck = function(field, records){
446
+ var getFn = this.jQ.getterFns[field], result = [];
447
+
448
+ each(records, function(r){
449
+ result.push(getFn(r));
450
+ });
451
+
452
+ return result;
453
+ };
454
+
455
+ var execUniq = function(fields, records){
456
+ var result = [], self = this;
457
+
458
+ if(getDataType(records[0]) != 'Object'){
459
+ each(records, function(r){
460
+ if(result.indexOf(r) == -1){
461
+ result.push(r);
462
+ }
463
+ });
464
+
465
+ return result;
466
+ }
467
+
468
+ result.push(records[0]);
469
+
470
+ each(records, function(r){
471
+ var present = false;
472
+
473
+ for(var i = 0, l = result.length; i < l; i++){
474
+ if(compareObj.call(self.jQ, result[i], r, fields)){
475
+ present = true;
476
+ }
477
+ }
478
+
479
+ if(!present){
480
+ result.push(r);
481
+ }
482
+ });
483
+
484
+ return result;
485
+ };
486
+
487
+
488
+
489
+ var Query = function(jQ, records){
490
+ this.jQ = jQ;
491
+ this.records = records;
492
+ this.criteria = {};
493
+ return this;
494
+ };
495
+
496
+ var Q = Query.prototype;
497
+
498
+ Q.each = function(callback, context){
499
+ each(this.exec() || [], callback, context)
500
+ };
501
+
502
+ Q.exec = Q.toArray = function(callback){
503
+ var result, c;
504
+
505
+ if(this.criteria['all']){
506
+ result = this.records;
507
+ }
508
+
509
+ if(this.criteria['where']){
510
+ result = execWhere.call(this, this.criteria['where'], result || this.records);
511
+ }
512
+
513
+ if(this.criteria['or']){
514
+ result = result.concat(execWhere.call(this, this.criteria['or'], this.records));
515
+ result = execUniq.call(this, [this.jQ.id], result);
516
+ }
517
+
518
+ if(this.criteria['order']){
519
+ result = execOrder.call(this, this.criteria['order'], result || this.records);
520
+ }
521
+
522
+ if(this.criteria['near']){
523
+ result = execNear.call(this, this.criteria['near'], result || this.records);
524
+ }
525
+
526
+ if(this.criteria['uniq']){
527
+ result = execUniq.call(this, this.criteria['uniq'], result || this.records);
528
+ }
529
+
530
+ if(this.criteria['select']){
531
+ result = execSelect.call(this, this.criteria['select'], result || this.records);
532
+ }
533
+
534
+ if(this.criteria['pluck']){
535
+ result = execPluck.call(this, this.criteria['pluck'], result || this.records);
536
+ }
537
+
538
+ if(this.criteria['limit']){
539
+ result = (result || this.records).slice(this.criteria['offset'] || 0, (this.criteria['offset'] || 0) + this.criteria['limit']);
540
+ }
541
+
542
+ if(this.criteria['group_by']){
543
+ result = execGroupBy.call(this, this.criteria['group_by'], result || this.records);
544
+ }
545
+
546
+ if(!result){
547
+ result = this.records;
548
+ }
549
+
550
+ if(callback){
551
+ each(result, callback);
552
+ }
553
+
554
+ if(this.jQ.onResult){
555
+ this.jQ.onResult(result, this.criteria);
556
+ }
557
+
558
+ return result;
559
+ }
560
+
561
+ var addToCriteria = function(type, query){
562
+ var c;
563
+
564
+ if(!this.criteria[type]){
565
+ this.criteria[type] = {};
566
+ }
567
+
568
+ for(c in query){
569
+ this.criteria[type][c] = query[c];
570
+ }
571
+
572
+ return this;
573
+ };
574
+
575
+ Q.where = function(query){
576
+ return addToCriteria.call(this, 'where', query);
577
+ };
578
+
579
+ Q.or = function(query){
580
+ return addToCriteria.call(this, 'or', query);
581
+ };
582
+
583
+ Q.groupBy = function(field){
584
+ this.criteria['group_by'] = field;
585
+ return this;
586
+ };
587
+
588
+ Q.select = function(){
589
+ this.criteria['select'] = arguments;
590
+ return this;
591
+ };
592
+
593
+ Q.pluck = function(field){
594
+ this.criteria['pluck'] = field;
595
+ return this;
596
+ };
597
+
598
+ Q.limit = function(l){
599
+ this.criteria['limit'] = l;
600
+ return this;
601
+ };
602
+
603
+ Q.offset = function(o){
604
+ this.criteria['offset'] = o;
605
+ return this;
606
+ };
607
+
608
+ Q.order = function(criteria){
609
+ var field;
610
+ this.criteria['order'] = this.criteria['order'] || [];
611
+
612
+ for(field in criteria){
613
+ this.criteria['order'].push({field: field, direction: criteria[field].toLowerCase()});
614
+ }
615
+
616
+ return this;
617
+ };
618
+
619
+ Q.uniq = function(){
620
+ this.criteria['uniq'] = (arguments.length > 0 ? arguments : true);
621
+ return this;
622
+ };
623
+
624
+ Object.defineProperty(Q, 'count', {
625
+ get: function(){
626
+ this.criteria['count'] = true;
627
+ var r = this.exec();
628
+
629
+ if(getDataType(r) == 'Array'){
630
+ return this.exec().length;
631
+ }else{
632
+ return Object.keys(r).length;
633
+ }
634
+ }
635
+ });
636
+
637
+ Object.defineProperty(Q, 'all', {
638
+ get: function(){
639
+ this.criteria['all'] = true;
640
+ return this.exec();
641
+ }
642
+ });
643
+
644
+ Object.defineProperty(Q, 'first', {
645
+ get: function(){
646
+ this.criteria['first'] = true;
647
+ return this.exec()[0];
648
+ }
649
+ });
650
+
651
+ Object.defineProperty(Q, 'last', {
652
+ get: function(){
653
+ this.criteria['last'] = true;
654
+ var r = this.exec();
655
+ return r[r.length - 1];
656
+ }
657
+ });
658
+
659
+ //Geocoding
660
+ var GEO = {
661
+ redius: 6371,
662
+ toRad: function(v){
663
+ return v * Math.PI / 180;
664
+ }
665
+ };
666
+
667
+ var calculateDistance = function(lat1, lat2, lng1, lng2){
668
+ var dLat = GEO.toRad(lat2 - lat1),
669
+ dLon = GEO.toRad(lng2 - lng1),
670
+ lat1 = GEO.toRad(lat1),
671
+ lat2 = GEO.toRad(lat2);
672
+
673
+ var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
674
+ Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
675
+
676
+ return (2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))) * GEO.redius;
677
+ };
678
+
679
+ var execNear = function(opts, records){
680
+ var result = [],
681
+ self = this,
682
+ unit_c = opts.unit == 'mile' ? 0.6214 : 1,
683
+ latFn = self.jQ.getterFns[self.jQ.lat],
684
+ lngFn = self.jQ.getterFns[self.jQ.lng];
685
+
686
+ each(records, function(r){
687
+ r._distance = calculateDistance(latFn(r), opts.lat, lngFn(r), opts.lng) * unit_c;
688
+
689
+ if(r._distance <= opts.distance){
690
+ result.push(r);
691
+ }
692
+ });
693
+
694
+ result.sort(function(a, b){
695
+ return (a._distance < b._distance ? -1 : a._distance > b._distance ? 1 : 0);
696
+ })
697
+
698
+ return result;
699
+ };
700
+
701
+ Q.near = function(lat, lng, distance, unit){
702
+ this.criteria['near'] = {lat: lat, lng: lng, distance: distance, unit: (unit || 'km')};
703
+ return this;
704
+ };
705
+
706
+ //Helpers
707
+ Q.map = Q.collect = function(fn){
708
+ var result = [], out;
709
+
710
+ this.exec(function(r){
711
+ if(out = fn(r)){
712
+ result.push(out);
713
+ }
714
+ })
715
+ return result;
716
+ };
717
+
718
+ Q.sum = function(field){
719
+ var result = 0,
720
+ group,
721
+ getFn = this.jQ.getterFns[field];
722
+
723
+ if(this.criteria['group_by']){
724
+ group = true;
725
+ result = {};
726
+ }
727
+
728
+ this.exec(function(r, i){
729
+ if(group){
730
+ result[i] = 0;
731
+
732
+ each(r, function(e){
733
+ result[i] = result[i] + (getFn(e) || 0);
734
+ })
735
+ }else{
736
+ result = result + (getFn(r) || 0);
737
+ }
738
+ });
739
+
740
+ return result;
741
+ };
742
+
743
+ Q.toJQ = function(){
744
+ var q = JsonQuery(this.all, {schema: true});
745
+ q.schema = this.jQ.schema;
746
+ q.getterFns = this.jQ.getterFns;
747
+
748
+ return q;
749
+ };
750
+
751
+ Q.destroy_all = Q.destroy = function(){
752
+ var marked_records = this.all;
753
+
754
+ each(marked_records, function(r, i){
755
+ r._destroy_ = true;
756
+ });
757
+
758
+ this.records = this.jQ.records = this.records.filter(function(r){
759
+ return !r._destroy_;
760
+ });
761
+
762
+ return marked_records;
763
+ };
764
+
765
+ Q.update_all = Q.update = function(attrs){
766
+ if(!attrs){
767
+ return false;
768
+ }
769
+
770
+ var updated_count = 0;
771
+
772
+ each(this.all, function(r){
773
+ each(attrs, function(value, key){
774
+ r[key] = value;
775
+ });
776
+ updated_count = updated_count + 1;
777
+ });
778
+
779
+ return updated_count;
780
+ };
781
+
782
+
783
+ //In IE 8 indexOf method not define.
784
+ if (!Array.prototype.indexOf) {
785
+ Array.prototype.indexOf = function(obj, start) {
786
+ for (var i = (start || 0), j = this.length; i < j; i++) {
787
+ if (this[i] === obj) { return i; }
788
+ }
789
+ return -1;
790
+ }
791
+ }
792
+
793
+ if(!Object.defineProperty){
794
+ Object.defineProperty = function(obj, name, opts){
795
+ obj[name] = opts.get
796
+ }
797
+ }
798
+
799
+
800
+
801
+
802
+ })(this);
803
+
804
+ ;(function($, window, document) {
805
+
806
+ "use strict";
807
+
808
+
809
+ //View Template
810
+ // Ref: Underscopre.js
811
+ //JavaScript micro-templating, similar to John Resig's implementation.
812
+ var templateSettings = {
813
+ evaluate : /<%([\s\S]+?)%>/g,
814
+ interpolate : /<%=([\s\S]+?)%>/g,
815
+ escape : /<%-([\s\S]+?)%>/g
816
+ };
817
+
818
+ var escapeStr = function(string) {
819
+ return (''+string).replace(/&/g, '&amp;')
820
+ .replace(/</g, '&lt;')
821
+ .replace(/>/g, '&gt;')
822
+ .replace(/"/g, '&quot;')
823
+ .replace(/'/g, '&#x27;')
824
+ .replace(/\//g, '&#x2F;');
825
+ };
826
+
827
+ function templateBuilder(str, data) {
828
+ var c = templateSettings;
829
+ var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
830
+ 'with(obj||{}){__p.push(\'' +
831
+ str.replace(/\\/g, '\\\\')
832
+ .replace(/'/g, "\\'")
833
+ .replace(c.escape, function(match, code) {
834
+ return "',escapeStr(" + code.replace(/\\'/g, "'") + "),'";
835
+ })
836
+ .replace(c.interpolate, function(match, code) {
837
+ return "'," + code.replace(/\\'/g, "'") + ",'";
838
+ })
839
+ .replace(c.evaluate || null, function(match, code) {
840
+ return "');" + code.replace(/\\'/g, "'")
841
+ .replace(/[\r\n\t]/g, ' ') + ";__p.push('";
842
+ })
843
+ .replace(/\r/g, '\\r')
844
+ .replace(/\n/g, '\\n')
845
+ .replace(/\t/g, '\\t')
846
+ + "');}return __p.join('');";
847
+
848
+ var func = new Function('obj', tmpl);
849
+ return data ? func(data) : function(data) { return func(data) };
850
+ };
851
+
852
+
853
+ function each (objs, callback, context){
854
+ for (var i = 0, l = objs.length; i < l; i++) {
855
+ callback.call(context, objs[i], i);
856
+ }
857
+ }
858
+
859
+
860
+ var FJS = function(records, container, options) {
861
+ var self = this;
862
+
863
+ this.opts = options || {};
864
+ this.callbacks = this.opts.callbacks || {};
865
+ this.$container = $(container);
866
+ this.view = this.opts.view || renderRecord;
867
+ this.criterias = [];
868
+ this._index = 1;
869
+ this.appendToContainer = this.opts.appendToContainer || appendToContainer;
870
+ this.has_pagination = !!this.opts.pagination;
871
+ this.search_text = '';
872
+ this.anyFilterSelected = false;
873
+
874
+ this.setTemplate(this.opts.template);
875
+
876
+ $.each(this.opts.criterias || [], function(){
877
+ self.addCriteria(this);
878
+ });
879
+
880
+ this.Model = JsonQuery();
881
+ this.Model.getterFns['_fid'] = function(r){ return r['_fid'];};
882
+ this.addRecords(records, this.opts['filter_on_init'] || false);
883
+
884
+ if(this.has_pagination){
885
+ this.initPagination();
886
+ }
887
+ };
888
+
889
+ var F = FJS.prototype;
890
+
891
+ Object.defineProperty(F, 'records', {
892
+ get: function(){ return this.Model.records; }
893
+ });
894
+
895
+ Object.defineProperty(F, 'recordsCount', {
896
+ get: function(){ return this.Model.records.length; }
897
+ });
898
+
899
+ F.templateBuilder = templateBuilder;
900
+
901
+ //Callback
902
+ F.execCallback = function(){
903
+ var name = arguments[0];
904
+
905
+ if(this.callbacks[name]) {
906
+ this.callbacks[name].apply(this, Array.prototype.slice.call(arguments, 1));
907
+ }
908
+ };
909
+
910
+ F.addCallback = function(name, fns){
911
+ if(name && fns){
912
+ this.callbacks[name] = fns;
913
+ }
914
+ };
915
+
916
+ //Add Data
917
+ F.addRecords = function(records, applyFilter){
918
+ var has_scheme = !!this.Model.schema;
919
+
920
+ this.execCallback('beforeAddRecords', records);
921
+
922
+ if(this.Model.addRecords(records)){
923
+ if(!this.has_scheme){
924
+ this.initSearch(this.opts.search);
925
+ }
926
+
927
+ this.render(records);
928
+ if(applyFilter !== false){
929
+ this.filter();
930
+ }
931
+ }
932
+
933
+ this.execCallback('afterAddRecords', records);
934
+ };
935
+
936
+ F.removeRecords = function(criteria){
937
+ var ids;
938
+
939
+ if($.isPlainObject(criteria)){
940
+ ids = this.Model.where(criteria).pluck('_fid').all;
941
+ }else if($.isArray(criteria)){
942
+ ids = this.Model.where({'id.$in': criteria}).pluck('_fid').all;
943
+ }
944
+
945
+ if(!ids){
946
+ return false;
947
+ }
948
+
949
+ var records = this.Model.records,
950
+ removedCount = 0,
951
+ idsLength = ids.length,
952
+ fid;
953
+
954
+ for(var i = records.length - 1; i > -1; i--){
955
+ fid = records[i]._fid
956
+
957
+ if(ids.indexOf(fid) > -1){
958
+ records.splice(i, 1);
959
+ removedCount ++;
960
+
961
+ $('#fjs_' + fid).remove();
962
+ }
963
+
964
+ if(removedCount == idsLength){
965
+ break;
966
+ }
967
+ }
968
+
969
+ this.execCallback('afterRemove');
970
+
971
+ return true;
972
+ };
973
+
974
+ var renderRecord = function(record, index){
975
+ return this.templateFn(record);
976
+ };
977
+
978
+ F.render = function(records){
979
+ var self = this,
980
+ ele,
981
+ cbName;
982
+
983
+ if(!records.length){return; }
984
+
985
+ this.execCallback('beforeRender', records);
986
+ cbName = 'beforeRecordRender';
987
+
988
+ $.each(records, function(i){
989
+ self.execCallback(cbName, this);
990
+ this._fid = (self._index++);
991
+
992
+ if(!self.has_pagination){
993
+ self.renderItem(this, i);
994
+ }
995
+ });
996
+ };
997
+
998
+ F.renderItem = function(record, i){
999
+ if(!record){
1000
+ return;
1001
+ }
1002
+
1003
+ var ele = this.view(record, i);
1004
+
1005
+ if(typeof ele == 'string'){
1006
+ ele = $($.trim(ele));
1007
+ }
1008
+
1009
+ ele.attr('id', 'fjs_' + record._fid).addClass('fjs_item');
1010
+
1011
+ this.appendToContainer(ele, record);
1012
+ };
1013
+
1014
+ var appendToContainer = function(html_ele, record){
1015
+ this.$container.append(html_ele);
1016
+ };
1017
+
1018
+ var setDefaultCriteriaOpts = function(criteria){
1019
+ var ele = criteria.$ele,
1020
+ eleType = criteria.$ele.attr('type');
1021
+
1022
+ if(!criteria.selector){
1023
+ if (ele.get(0).tagName == 'INPUT'){
1024
+ criteria.selector = (eleType == 'checkbox' || eleType == 'radio') ? ':checked' : ':input';
1025
+ }else if (ele.get(0).tagName == 'SELECT'){
1026
+ criteria.selector = 'select';
1027
+ }
1028
+ }
1029
+
1030
+ if (!criteria.event){
1031
+ criteria.event = (eleType == 'checkbox' || eleType == 'radio') ? 'click' : 'change';
1032
+ }
1033
+
1034
+ return criteria;
1035
+ };
1036
+
1037
+ F.addCriteria = function(criterias){
1038
+ var self = this;
1039
+
1040
+ if(!criterias){ return false; }
1041
+
1042
+ if($.isArray(criterias)){
1043
+ $.each(criterias, function(){
1044
+ addFilterCriteria.call(self, this);
1045
+ });
1046
+ }else{
1047
+ addFilterCriteria.call(self, criterias);
1048
+ }
1049
+
1050
+ return true;
1051
+ };
1052
+
1053
+ // Add Filter criteria
1054
+ // criteria: { ele: '#name', event: 'check', field: 'name', type: 'range' }
1055
+ var addFilterCriteria = function(criteria){
1056
+ if(!criteria || !criteria.field || !criteria.ele){
1057
+ return false;
1058
+ }
1059
+
1060
+ criteria.$ele = $(criteria.ele);
1061
+
1062
+ if(!criteria.$ele.length){
1063
+ return false;
1064
+ }
1065
+
1066
+ criteria = setDefaultCriteriaOpts(criteria);
1067
+ this.bindEvent(criteria.ele, criteria.event);
1068
+
1069
+ criteria._q = criteria.field + (criteria.type == 'range' ? '.$bt' : '')
1070
+ criteria.active = true;
1071
+
1072
+ this.criterias.push(criteria);
1073
+
1074
+ return true;
1075
+ };
1076
+
1077
+ F.removeCriteria = function(field){
1078
+ var self = this, criteria, index;
1079
+
1080
+ $.each(self.criterias, function(i){
1081
+ if(this.field == field){
1082
+ index = i;
1083
+ }
1084
+ });
1085
+
1086
+ if(index != null){
1087
+ criteria = this.criterias.splice(index, 1)[0];
1088
+ $('body').off(criteria.event, criteria.ele)
1089
+ }
1090
+ };
1091
+
1092
+ var changeCriteriaStatus = function(names, active){
1093
+ var self = this;
1094
+
1095
+ if(!names){ return; }
1096
+
1097
+ if(!$.isArray(names)){
1098
+ names = [names]
1099
+ }
1100
+
1101
+ $.each(names, function(){
1102
+ var name = this;
1103
+
1104
+ $.each(self.criterias, function(){
1105
+ if(this.field == name){
1106
+ this.active = active;
1107
+ }
1108
+ })
1109
+ });
1110
+ };
1111
+
1112
+ F.deactivateCriteria = function(names){
1113
+ changeCriteriaStatus.call(self, names, false);
1114
+ };
1115
+
1116
+ F.activateCriteria = function(names){
1117
+ changeCriteriaStatus.call(this, names, true);
1118
+ };
1119
+
1120
+ F.getSelectedValues = function(criteria, context){
1121
+ var vals = [];
1122
+
1123
+ criteria.$ele.filter(criteria.selector).each(function() {
1124
+ vals.push($(this).val());
1125
+ });
1126
+
1127
+ if($.isArray(vals[0])){
1128
+ vals = [].concat.apply([], vals);
1129
+ }
1130
+
1131
+ if(criteria.all && vals.indexOf(criteria.all) > -1){
1132
+ return;
1133
+ }
1134
+
1135
+ if(criteria.type == 'range'){
1136
+ vals = vals[0].split(criteria.delimiter || '-');
1137
+ }
1138
+
1139
+ vals = this.parseValues(criteria.field, vals);
1140
+
1141
+ return context.execCallback('onFilterSelect', {criteria: criteria, values: vals}) || vals;
1142
+ };
1143
+
1144
+ F.lastResult = function(){
1145
+ return (this.last_result || this.records);
1146
+ };
1147
+
1148
+ F.filter = function(){
1149
+ var query = {},
1150
+ vals, _q,
1151
+ count = 0,
1152
+ self = this,
1153
+ criteria;
1154
+
1155
+ $.each(this.criterias, function(){
1156
+ if(this.active){
1157
+ vals = self.getSelectedValues(this, self);
1158
+
1159
+ if(vals && vals.length){
1160
+ _q = ($.isArray(vals) && !this.type) ? (this._q + '.$in') : this._q;
1161
+ query[_q] = vals ;
1162
+ count = count + 1;
1163
+ }
1164
+ }
1165
+ });
1166
+
1167
+ this.anyFilterSelected = count > 0;
1168
+ criteria = count ? this.Model.where(query) : this.Model;
1169
+ this.execCallback('shortResult', criteria);
1170
+ this.last_result = criteria.all;
1171
+
1172
+ if(this.searchFilter(this.last_result)){
1173
+ return query;
1174
+ }
1175
+
1176
+ this.show(this.last_result);
1177
+ this.renderPagination(this.last_result.length);
1178
+ this.execCallback('afterFilter', this.last_result, JsonQuery.blankClone(this.Model, this.last_result));
1179
+
1180
+ return query;
1181
+ };
1182
+
1183
+ F.show = function(result, type){
1184
+ var i = 0, l = result.length;
1185
+
1186
+ if(this.has_pagination){
1187
+
1188
+ i = this.page.perPage *(this.page.currentPage - 1);
1189
+ l = i + this.page.perPage;
1190
+
1191
+ this.$container.html("");
1192
+
1193
+ for(i; i < l; i++){
1194
+ this.renderItem(result[i], i);
1195
+ }
1196
+
1197
+ return;
1198
+ }
1199
+
1200
+ $('.fjs_item').hide();
1201
+
1202
+ for(i; i < l; i++){
1203
+ $('#fjs_' + result[i]._fid).show();
1204
+ }
1205
+
1206
+ };
1207
+
1208
+ F.filterTimer = function(timeout){
1209
+ var self = this;
1210
+
1211
+ if (this.filterTimeoutId) {
1212
+ clearTimeout(this.filterTimeoutId);
1213
+ }
1214
+
1215
+ this.filterTimeoutId = setTimeout(function() {
1216
+ self.filter();
1217
+ }, timeout);
1218
+ };
1219
+
1220
+ F.bindEvent = function(ele, eventName){
1221
+ var self = this;
1222
+
1223
+ $(document).on(eventName, ele, function(e){
1224
+ self.filterTimer(self.opts.timeout || 35);
1225
+ });
1226
+
1227
+ };
1228
+
1229
+ F.initSearch = function(opts){
1230
+ if(!opts || !opts.ele){
1231
+ return;
1232
+ }
1233
+
1234
+ if(!opts.start_length){
1235
+ this.opts.search.start_length = 2
1236
+ }
1237
+
1238
+ this.$search_ele = $(this.opts.search.ele);
1239
+
1240
+ if(this.$search_ele.length){
1241
+ this.has_search = true;
1242
+ this.searchFn = this.buildSearchFn(opts.fields);
1243
+ this.bindEvent(opts.ele, 'keyup');
1244
+ }
1245
+ };
1246
+
1247
+ F.buildSearchFn = function(fields){
1248
+ var self = this, getterFns = [];
1249
+
1250
+ if(fields){
1251
+ $.each(fields, function(){
1252
+ getterFns.push(self.Model.getterFns[this]);
1253
+ })
1254
+ }else{
1255
+ $.each(self.Model.getterFns, function(i, fn){
1256
+ getterFns.push(fn);
1257
+ });
1258
+ }
1259
+
1260
+ return function(text, record){
1261
+ text = text.toLocaleUpperCase();
1262
+
1263
+ for(var i = 0, l = getterFns.length; i < l; i++){
1264
+
1265
+ if((getterFns[i](record) + '').toLocaleUpperCase().indexOf(text) > -1){
1266
+ return true;
1267
+ }
1268
+
1269
+ }
1270
+ return false;
1271
+ }
1272
+ };
1273
+
1274
+ F.lastSearchResult = function(){
1275
+ if (this.search_text.length > this.opts.search.start_length){
1276
+ return this.search_result;
1277
+ }else{
1278
+ return this.lastResult();
1279
+ }
1280
+ }
1281
+
1282
+ F.searchFilter = function(records) {
1283
+ if(!this.has_search){
1284
+ return;
1285
+ }
1286
+
1287
+ var result;
1288
+ this.search_text = $.trim(this.$search_ele.val());
1289
+
1290
+ if (this.search_text.length < this.opts.search.start_length){
1291
+ return false;
1292
+ }
1293
+
1294
+ result = this.search(this.search_text, records || this.lastResult());
1295
+
1296
+ this.search_result = result;
1297
+ this.show(result);
1298
+ this.renderPagination(result.length)
1299
+ this.execCallback('afterFilter', result, JsonQuery.blankClone(this.Model, result));
1300
+
1301
+ return true;
1302
+ };
1303
+
1304
+ F.search = function(text, records){
1305
+ text = text.toLocaleUpperCase();
1306
+
1307
+ var result = [];
1308
+
1309
+ for(var i = 0, l = records.length; i < l; i++){
1310
+ if(this.searchFn(text, records[i])){
1311
+ result.push(records[i]);
1312
+ }
1313
+ }
1314
+
1315
+ return result;
1316
+ };
1317
+
1318
+ //Streaming
1319
+ F.setStreaming = function(opts){
1320
+ if(!opts) {return;}
1321
+
1322
+ this.opts.streaming = opts;
1323
+
1324
+ if(opts.data_url){
1325
+ opts.stream_after = (opts.stream_after || 2)*1000;
1326
+ opts.batch_size = opts.batch_size || false;
1327
+ this.streamData(opts.stream_after);
1328
+ }
1329
+
1330
+ };
1331
+
1332
+ var fetchData = function(){
1333
+ var self = this,
1334
+ params = this.opts.params || {},
1335
+ opts = this.opts.streaming;
1336
+
1337
+ params.offset = this.recordsCount;
1338
+
1339
+ if (opts.batch_size) {
1340
+ params.limit = opts.batch_size;
1341
+ }
1342
+
1343
+ if (this.has_search){
1344
+ params['q'] = $.trim(this.$search_ele.val());
1345
+ }
1346
+
1347
+ $.getJSON(opts.data_url, params).done(function(records){
1348
+ if (params.limit != null && (!records || !records.length)){
1349
+ self.stopStreaming();
1350
+ }else{
1351
+ self.setStreamInterval();
1352
+ self.addRecords(records);
1353
+ }
1354
+
1355
+ }).fail(function(e){
1356
+ self.stopStreaming();
1357
+ });
1358
+ };
1359
+
1360
+ F.setStreamInterval = function(){
1361
+ var self = this;
1362
+
1363
+ if(self.opts.streaming.stop == true){ return; }
1364
+
1365
+ self.streamingTimer = setTimeout(function(){
1366
+ fetchData.call(self);
1367
+ }, self.opts.streaming.stream_after);
1368
+ };
1369
+
1370
+ F.stopStreaming = function(){
1371
+ this.opts.streaming.stop = true;
1372
+
1373
+ if (this.streamingTimer){
1374
+ clearTimeout(this.streamingTimer);
1375
+ }
1376
+ };
1377
+
1378
+ F.resumeStreaming = function(){
1379
+ this.opts.streaming.stop = false;
1380
+ this.streamData(this.opts.streaming.stream_after);
1381
+ };
1382
+
1383
+ F.streamData = function(time){
1384
+ this.setStreamInterval();
1385
+
1386
+ if(!this.opts.streaming.batch_size){
1387
+ this.stopStreaming();
1388
+ }
1389
+ };
1390
+
1391
+ F.clear = function(){
1392
+ if(this.opts.streaming){
1393
+ this.stopStreaming();
1394
+ }
1395
+
1396
+ $.each(this.criterias, function(){
1397
+ $(document).off(this.event, this.ele);
1398
+ })
1399
+
1400
+ if(this.opts.search){
1401
+ $(document).off('keyup', this.opts.search.ele);
1402
+ }
1403
+
1404
+ if (this.filterTimeoutId) {
1405
+ clearTimeout(this.filterTimeoutId);
1406
+ }
1407
+ }
1408
+
1409
+ F.initPagination = function(){
1410
+ var self = this,
1411
+ opts = this.opts.pagination;
1412
+
1413
+ if(!opts.perPage){
1414
+ opts.perPage = {}
1415
+ }
1416
+
1417
+ if(!opts.perPage.values){
1418
+ opts.perPage.values = [10, 20, 30];
1419
+ }
1420
+
1421
+ this.page = { currentPage: 1, perPage: opts.perPage.values };
1422
+
1423
+ this.paginator = new Paginator(this.lastResult().length, this.opts.pagination, function(currentPage, perPage){
1424
+ self.page = { currentPage: currentPage, perPage: perPage }
1425
+
1426
+ if(self.has_search){
1427
+ self.show(self.lastSearchResult())
1428
+ }else{
1429
+ self.show(self.lastResult())
1430
+ }
1431
+ })
1432
+
1433
+ this.filter();
1434
+ };
1435
+
1436
+ F.renderPagination = function(totalCount){
1437
+ if(this.has_pagination){
1438
+ this.paginator.setRecordCount(totalCount);
1439
+ }
1440
+ };
1441
+
1442
+ F.parseValues = function(field, values){
1443
+ var type = this.Model.schema[field];
1444
+
1445
+ if(type == 'Number'){
1446
+ return $.map(values, function(v){ return Number(v) });
1447
+ }else if(type == 'Boolean'){
1448
+ return $.map(values, function(v){ return (v == 'true' || v == true) });
1449
+ }else{
1450
+ return values;
1451
+ }
1452
+ };
1453
+
1454
+ F.setTemplate = function(template, rebuild) {
1455
+ this.templateFn = templateBuilder($(template).html());
1456
+ if(rebuild === true) {
1457
+ this.$container.empty();
1458
+
1459
+ this.render(this.records);
1460
+ this.filter();
1461
+ }
1462
+ };
1463
+
1464
+
1465
+
1466
+ var Paginator = function(recordsCount, opts, onPagination) {
1467
+ var paginationView;
1468
+
1469
+ this.recordsCount = recordsCount;;
1470
+ this.opts = opts;
1471
+ this.$container = $(this.opts.container);
1472
+
1473
+ if(this.opts.paginationView){
1474
+ paginationView = $(this.opts.paginationView).html();
1475
+ } else {
1476
+ paginationView = views.pagination;
1477
+ }
1478
+
1479
+ this.paginationTmpl = templateBuilder(paginationView);
1480
+
1481
+ this.currentPage = 1;
1482
+ this.onPagination = onPagination;
1483
+ this.initPerPage();
1484
+ this.render();
1485
+ this.bindEvents();
1486
+ };
1487
+
1488
+ var P = Paginator.prototype;
1489
+
1490
+ P.bindEvents = function(){
1491
+ var self = this;
1492
+
1493
+ $(this.opts.container).on('click', '[data-page]', function(e){
1494
+ self.setCurrentPage($(this).data('page'));
1495
+ e.preventDefault();
1496
+ });
1497
+ };
1498
+
1499
+ P.totalPages = function(){
1500
+ return Math.ceil(this.recordsCount/this.perPageCount);
1501
+ };
1502
+
1503
+ P.setCurrentPage = function(page){
1504
+ page = this.toPage(page)
1505
+ this.prevCurrentPage = this.currentPage;
1506
+ this.currentPage = page;
1507
+ this.paginate(page);
1508
+ };
1509
+
1510
+ P.setRecordCount = function(total){
1511
+ this.recordsCount = total;
1512
+ this.setCurrentPage(this.currentPage);
1513
+ }
1514
+
1515
+ P.toPage = function(page){
1516
+ if(page == 'first'){
1517
+ return 1;
1518
+ }
1519
+
1520
+ if(page == 'last'){
1521
+ return this.totalPages();
1522
+ }
1523
+
1524
+ if(page == 'next'){
1525
+ var next_page = this.currentPage + 1;
1526
+ return (next_page > this.totalPages() ? this.currentPage : next_page);
1527
+ }
1528
+
1529
+ if(page == 'prev'){
1530
+ var prev_page = this.currentPage - 1;
1531
+ return (prev_page <= 0 ? this.currentPage : prev_page);
1532
+ }
1533
+
1534
+ return parseInt(page);
1535
+ };
1536
+
1537
+ P.paginate = function(page){
1538
+ this.render();
1539
+ this.onPagination(this.currentPage, this.perPageCount);
1540
+ };
1541
+
1542
+ P.render = function(){
1543
+ var pages = this.getPages();
1544
+
1545
+ if(this.currentPage > pages.totalPages){
1546
+ this.currentPage = pages.totalPages;
1547
+ }
1548
+
1549
+ if(this.currentPage == 0){
1550
+ this.currentPage = 1;
1551
+ }
1552
+
1553
+ pages.currentPage = this.currentPage;
1554
+ this.$container.html(this.paginationTmpl(pages))
1555
+ };
1556
+
1557
+ function makePageArray(start, end){
1558
+ var i = start, pages = [];
1559
+
1560
+ for(i; i <= end; i++){
1561
+ pages.push(i);
1562
+ }
1563
+
1564
+ return pages;
1565
+ }
1566
+
1567
+ P.getPages = function () {
1568
+ var total = this.totalPages();
1569
+
1570
+ if(!this.opts.visiblePages){
1571
+ return { totalPages: total, pages: makePageArray(0, total), self: this };
1572
+ }
1573
+
1574
+ var half = Math.floor(this.opts.visiblePages / 2);
1575
+ var start = this.currentPage - half + 1 - this.opts.visiblePages % 2;
1576
+ var end = this.currentPage + half;
1577
+
1578
+ // handle boundary case
1579
+ if (start <= 0) {
1580
+ start = 1;
1581
+ end = this.opts.visiblePages;
1582
+ }
1583
+
1584
+ if (end > total) {
1585
+ start = total - this.opts.visiblePages;
1586
+
1587
+ if(start <= 0){
1588
+ start = 1;
1589
+ }
1590
+
1591
+ end = total;
1592
+ }
1593
+
1594
+ return { currentPage: this.currentPage, totalPages: total, pages: makePageArray(start, end), self: this };
1595
+ },
1596
+
1597
+ P.initPerPage = function(){
1598
+ var opts = this.opts.perPage,
1599
+ template,
1600
+ html,
1601
+ ele,
1602
+ event_type,
1603
+ self = this;
1604
+
1605
+ this.perPageCount = opts.values[0];
1606
+
1607
+ template = opts.perPageView ? $(opts.perPageView).html() : views.per_page;
1608
+ html = templateBuilder(template)({ values: opts.values });
1609
+ $(opts.container).html(html);
1610
+
1611
+ ele = $(opts.container).find('[data-perpage]')
1612
+ event_type = ele.get(0).tagName == 'SELECT' ? 'change' : 'click';
1613
+
1614
+ $(opts.container).on(event_type, '[data-perpage]', function(e){
1615
+ var value = parseInt($(this).val() || $(this).data('value'));
1616
+ self.setPerPage(value)
1617
+ e.preventDefault();
1618
+ });
1619
+ };
1620
+
1621
+ P.setPerPage = function(value){
1622
+ this.perPageCount = value;
1623
+ this.setCurrentPage(this.currentPage);
1624
+ }
1625
+
1626
+
1627
+ $.fn.filterjs = function(records, options) {
1628
+ var $this = $(this);
1629
+
1630
+ if (!$this.data('fjs')){
1631
+ $this.data('fjs', FilterJS(records, $this, options));
1632
+ }
1633
+ };
1634
+
1635
+
1636
+
1637
+
1638
+ var list = [];
1639
+ var views = [];
1640
+ var FilterJS = function(records, container, options) {
1641
+ var fjs = new FJS(records, container, options);
1642
+ list.push(fjs);
1643
+
1644
+ return fjs;
1645
+ };
1646
+
1647
+ FilterJS.list = list;
1648
+ FilterJS.templateBuilder = templateBuilder;
1649
+
1650
+ window.FilterJS = FilterJS;
1651
+
1652
+ views['pagination'] = '<nav> <ul class="pagination"> <% if(currentPage > 1) { %> <li> <a href="#" data-page="first" aria-label="First"><span aria-hidden="true">First</span></a> </li> <li><a href="#" data-page="prev" aria-label="Previous"><span aria-hidden="true">&larr; Previous</span></a></li> <% } %> <% for(var i = 0, l = pages.length; i < l; i++ ){ %> <li class="<%= pages[i] == currentPage ? \'active\' : \'\' %>"> <a href="#" data-page="<%= pages[i] %>"><%= pages[i] %></a> </li> <% } %> <% if( currentPage < totalPages ) { %> <li><a href="#" data-page="next" aria-label="Next"><span aria-hidden="true">Next &rarr;</span></a></li> <li><a href="#" data-page="last" aria-label="Last"><span aria-hidden="true">Last</span></a></li> <% } %> </ul></nav>';
1653
+ views['per_page'] = '<select size="1" name="per_page" data-perpage="true" class="per-page"> <% for(var i = 0; i < values.length; i++ ){ %> <option value="<%= values[i] %>"><%= values[i] %></option> <% } %></select>';
1654
+
1655
+ /*
1656
+ * Find html tag and parse options for filter
1657
+ */
1658
+ function getElementWithOptions(name, hasMany){
1659
+ var attr = "fjs-"+ name;
1660
+ var $eles = $("[" + attr + "]");
1661
+ var options = [];
1662
+
1663
+ if(!$eles.length){
1664
+ return;
1665
+ }
1666
+
1667
+ $.each($eles, function(){
1668
+ var $ele = $(this);
1669
+ var option = { ele: $ele };
1670
+ var optionStr = $ele.attr(attr);
1671
+
1672
+ options.push(option);
1673
+
1674
+ if(!optionStr){
1675
+ return options;
1676
+ }
1677
+
1678
+ $.each(optionStr.split(','), function(i, opt){
1679
+ var kv = opt.split("=");
1680
+ option[kv[0]] = kv[1];
1681
+ })
1682
+ })
1683
+
1684
+ return hasMany ? options : options[0];
1685
+ };
1686
+
1687
+ FilterJS.auto = function(records, callbacks){
1688
+ var options = {};
1689
+ var container = getElementWithOptions("items");
1690
+ var fjs,
1691
+ search,
1692
+ template,
1693
+ criterias;
1694
+
1695
+ if(!container || !container.template){
1696
+ return;
1697
+ }
1698
+
1699
+ options.template = container.template
1700
+ search = getElementWithOptions("search");
1701
+
1702
+ if(search){
1703
+ if(search.fields){
1704
+ search.fields = search.fields.split(',');
1705
+ }
1706
+ options.search = search;
1707
+ }
1708
+
1709
+ if(callbacks){
1710
+ options.callbacks = callbacks;
1711
+ }
1712
+
1713
+ fjs = FilterJS(records, container.ele, options)
1714
+
1715
+ criterias = getElementWithOptions("criteria", true);
1716
+
1717
+ if(criterias){
1718
+ fjs.addCriteria(criterias);
1719
+ }
1720
+
1721
+ return fjs
1722
+ };
1723
+
1724
+
1725
+
1726
+
1727
+ })( jQuery, window , document );