filterjs-rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 );