angular-ng-table-rails 0.5.4.1

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: e39c7adb1a521d431e7778beba70284725ca92fa
4
+ data.tar.gz: 43823248579636a4b7856385273be86177403ff4
5
+ SHA512:
6
+ metadata.gz: 57415fb4b68bc22fe4bdbd0aeb20933a4b6c6562ef60ab1d5a02666bcb7677a4ac979e4a7c36c642fa33da9801a661181286a9e3c8e4b89d1ce1f0c086201fd5
7
+ data.tar.gz: b9cb61a68e3c16da539074ae75652f0d4479713c7a9dad6171ba003b7c6bf1499194645a822b717118d02d371a4f8dd662bec2091c68e773a386b01f70166524
@@ -0,0 +1,22 @@
1
+ Angular-UI-Bootstrap-Rails RubyGem Copyright (c) 2013 Chris Constantin
2
+
3
+ Angular.JS and related components Copyright (c) 2010-2013 Google Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,25 @@
1
+ # angular-ng-table-rails
2
+
3
+ ng-table angular wraps the [Angular.js](https://github.com/esvit/ng-table) library for use in Rails 3.1 and above. Assets will minify automatically during production.
4
+
5
+ ## Usage
6
+
7
+ Add the following to your gemfile:
8
+
9
+ gem 'angular-ng-table-rails'
10
+
11
+ Add the following directive to your Javascript manifest file (application.js):
12
+
13
+ //= require ng-table
14
+
15
+ Add the following directive to your Stylesheets manifest file (application.css):
16
+
17
+ *= require ng-table
18
+
19
+
20
+ You may need to add 'ui.bootstrap' into your app declaration for example
21
+
22
+ app = angular.module('MyApp', ["ngTable"])
23
+
24
+ Gem based on ng-table(https://github.com/esvit/ng-table) by Bazalt CMS
25
+
@@ -0,0 +1,8 @@
1
+ require "angular-ng-table-rails/version"
2
+
3
+ module NgTable
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module NgTable
2
+ module Rails
3
+ VERSION = "0.5.4.1"
4
+ end
5
+ end
@@ -0,0 +1,973 @@
1
+
2
+ (function(angular, factory) {
3
+ 'use strict';
4
+
5
+ if (typeof define === 'function' && define.amd) {
6
+ define(['angular'], function(angular) {
7
+ return factory(angular);
8
+ });
9
+ } else {
10
+ return factory(angular);
11
+ }
12
+ }(angular || null, function(angular) {
13
+ 'use strict';
14
+ /**
15
+ * ngTable: Table + Angular JS
16
+ *
17
+ * @author Vitalii Savchuk <esvit666@gmail.com>
18
+ * @url https://github.com/esvit/ng-table/
19
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
20
+ */
21
+
22
+ /**
23
+ * @ngdoc module
24
+ * @name ngTable
25
+ * @description ngTable: Table + Angular JS
26
+ * @example
27
+ <doc:example>
28
+ <doc:source>
29
+ <script>
30
+ var app = angular.module('myApp', ['ngTable']);
31
+ app.controller('MyCtrl', function($scope) {
32
+ $scope.users = [
33
+ {name: "Moroni", age: 50},
34
+ {name: "Tiancum", age: 43},
35
+ {name: "Jacob", age: 27},
36
+ {name: "Nephi", age: 29},
37
+ {name: "Enos", age: 34}
38
+ ];
39
+ });
40
+ </script>
41
+ <table ng-table class="table">
42
+ <tr ng-repeat="user in users">
43
+ <td data-title="'Name'">{{user.name}}</td>
44
+ <td data-title="'Age'">{{user.age}}</td>
45
+ </tr>
46
+ </table>
47
+ </doc:source>
48
+ </doc:example>
49
+ */
50
+ var app = angular.module('ngTable', []);
51
+ /**
52
+ * ngTable: Table + Angular JS
53
+ *
54
+ * @author Vitalii Savchuk <esvit666@gmail.com>
55
+ * @url https://github.com/esvit/ng-table/
56
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
57
+ */
58
+
59
+ /**
60
+ * @ngdoc value
61
+ * @name ngTable.value:ngTableDefaultParams
62
+ * @description Default Parameters for ngTable
63
+ */
64
+ app.value('ngTableDefaults', {
65
+ params: {},
66
+ settings: {}
67
+ });
68
+
69
+ /**
70
+ * @ngdoc service
71
+ * @name ngTable.factory:NgTableParams
72
+ * @description Parameters manager for ngTable
73
+ */
74
+
75
+ app.factory('NgTableParams', ['$q', '$log', 'ngTableDefaults', function($q, $log, ngTableDefaults) {
76
+ var isNumber = function(n) {
77
+ return !isNaN(parseFloat(n)) && isFinite(n);
78
+ };
79
+ var NgTableParams = function(baseParameters, baseSettings) {
80
+ var self = this,
81
+ log = function() {
82
+ if (settings.debugMode && $log.debug) {
83
+ $log.debug.apply(this, arguments);
84
+ }
85
+ };
86
+
87
+ this.data = [];
88
+
89
+ /**
90
+ * @ngdoc method
91
+ * @name ngTable.factory:NgTableParams#parameters
92
+ * @methodOf ngTable.factory:NgTableParams
93
+ * @description Set new parameters or get current parameters
94
+ *
95
+ * @param {string} newParameters New parameters
96
+ * @param {string} parseParamsFromUrl Flag if parse parameters like in url
97
+ * @returns {Object} Current parameters or `this`
98
+ */
99
+ this.parameters = function(newParameters, parseParamsFromUrl) {
100
+ parseParamsFromUrl = parseParamsFromUrl || false;
101
+ if (angular.isDefined(newParameters)) {
102
+ for (var key in newParameters) {
103
+ var value = newParameters[key];
104
+ if (parseParamsFromUrl && key.indexOf('[') >= 0) {
105
+ var keys = key.split(/\[(.*)\]/).reverse()
106
+ var lastKey = '';
107
+ for (var i = 0, len = keys.length; i < len; i++) {
108
+ var name = keys[i];
109
+ if (name !== '') {
110
+ var v = value;
111
+ value = {};
112
+ value[lastKey = name] = (isNumber(v) ? parseFloat(v) : v);
113
+ }
114
+ }
115
+ if (lastKey === 'sorting') {
116
+ params[lastKey] = {};
117
+ }
118
+ params[lastKey] = angular.extend(params[lastKey] || {}, value[lastKey]);
119
+ } else {
120
+ params[key] = (isNumber(newParameters[key]) ? parseFloat(newParameters[key]) : newParameters[key]);
121
+ }
122
+ }
123
+ log('ngTable: set parameters', params);
124
+ return this;
125
+ }
126
+ return params;
127
+ };
128
+
129
+ /**
130
+ * @ngdoc method
131
+ * @name ngTable.factory:NgTableParams#settings
132
+ * @methodOf ngTable.factory:NgTableParams
133
+ * @description Set new settings for table
134
+ *
135
+ * @param {string} newSettings New settings or undefined
136
+ * @returns {Object} Current settings or `this`
137
+ */
138
+ this.settings = function(newSettings) {
139
+ if (angular.isDefined(newSettings)) {
140
+ if (angular.isArray(newSettings.data)) {
141
+ //auto-set the total from passed in data
142
+ newSettings.total = newSettings.data.length;
143
+ }
144
+ settings = angular.extend(settings, newSettings);
145
+ log('ngTable: set settings', settings);
146
+ return this;
147
+ }
148
+ return settings;
149
+ };
150
+
151
+ /**
152
+ * @ngdoc method
153
+ * @name ngTable.factory:NgTableParams#page
154
+ * @methodOf ngTable.factory:NgTableParams
155
+ * @description If parameter page not set return current page else set current page
156
+ *
157
+ * @param {string} page Page number
158
+ * @returns {Object|Number} Current page or `this`
159
+ */
160
+ this.page = function(page) {
161
+ return angular.isDefined(page) ? this.parameters({
162
+ 'page': page
163
+ }) : params.page;
164
+ };
165
+
166
+ /**
167
+ * @ngdoc method
168
+ * @name ngTable.factory:NgTableParams#total
169
+ * @methodOf ngTable.factory:NgTableParams
170
+ * @description If parameter total not set return current quantity else set quantity
171
+ *
172
+ * @param {string} total Total quantity of items
173
+ * @returns {Object|Number} Current page or `this`
174
+ */
175
+ this.total = function(total) {
176
+ return angular.isDefined(total) ? this.settings({
177
+ 'total': total
178
+ }) : settings.total;
179
+ };
180
+
181
+ /**
182
+ * @ngdoc method
183
+ * @name ngTable.factory:NgTableParams#count
184
+ * @methodOf ngTable.factory:NgTableParams
185
+ * @description If parameter count not set return current count per page else set count per page
186
+ *
187
+ * @param {string} count Count per number
188
+ * @returns {Object|Number} Count per page or `this`
189
+ */
190
+ this.count = function(count) {
191
+ // reset to first page because can be blank page
192
+ return angular.isDefined(count) ? this.parameters({
193
+ 'count': count,
194
+ 'page': 1
195
+ }) : params.count;
196
+ };
197
+
198
+ /**
199
+ * @ngdoc method
200
+ * @name ngTable.factory:NgTableParams#filter
201
+ * @methodOf ngTable.factory:NgTableParams
202
+ * @description If parameter page not set return current filter else set current filter
203
+ *
204
+ * @param {string} filter New filter
205
+ * @returns {Object} Current filter or `this`
206
+ */
207
+ this.filter = function(filter) {
208
+ return angular.isDefined(filter) ? this.parameters({
209
+ 'filter': filter,
210
+ 'page': 1
211
+ }) : params.filter;
212
+ };
213
+
214
+ /**
215
+ * @ngdoc method
216
+ * @name ngTable.factory:NgTableParams#sorting
217
+ * @methodOf ngTable.factory:NgTableParams
218
+ * @description If 'sorting' parameter is not set, return current sorting. Otherwise set current sorting.
219
+ *
220
+ * @param {string} sorting New sorting
221
+ * @returns {Object} Current sorting or `this`
222
+ */
223
+ this.sorting = function(sorting) {
224
+ if (arguments.length == 2) {
225
+ var sortArray = {};
226
+ sortArray[sorting] = arguments[1];
227
+ this.parameters({
228
+ 'sorting': sortArray
229
+ });
230
+ return this;
231
+ }
232
+ return angular.isDefined(sorting) ? this.parameters({
233
+ 'sorting': sorting
234
+ }) : params.sorting;
235
+ };
236
+
237
+ /**
238
+ * @ngdoc method
239
+ * @name ngTable.factory:NgTableParams#isSortBy
240
+ * @methodOf ngTable.factory:NgTableParams
241
+ * @description Checks sort field
242
+ *
243
+ * @param {string} field Field name
244
+ * @param {string} direction Direction of sorting 'asc' or 'desc'
245
+ * @returns {Array} Return true if field sorted by direction
246
+ */
247
+ this.isSortBy = function(field, direction) {
248
+ return angular.isDefined(params.sorting[field]) && angular.equals(params.sorting[field], direction);
249
+ };
250
+
251
+ /**
252
+ * @ngdoc method
253
+ * @name ngTable.factory:NgTableParams#orderBy
254
+ * @methodOf ngTable.factory:NgTableParams
255
+ * @description Return object of sorting parameters for angular filter
256
+ *
257
+ * @returns {Array} Array like: [ '-name', '+age' ]
258
+ */
259
+ this.orderBy = function() {
260
+ var sorting = [];
261
+ for (var column in params.sorting) {
262
+ sorting.push((params.sorting[column] === "asc" ? "+" : "-") + column);
263
+ }
264
+ return sorting;
265
+ };
266
+
267
+ /**
268
+ * @ngdoc method
269
+ * @name ngTable.factory:NgTableParams#getData
270
+ * @methodOf ngTable.factory:NgTableParams
271
+ * @description Called when updated some of parameters for get new data
272
+ *
273
+ * @param {Object} $defer promise object
274
+ * @param {Object} params New parameters
275
+ */
276
+ this.getData = function($defer, params) {
277
+ if (angular.isArray(this.data) && angular.isObject(params)) {
278
+ $defer.resolve(this.data.slice((params.page() - 1) * params.count(), params.page() * params.count()));
279
+ } else {
280
+ $defer.resolve([]);
281
+ }
282
+ return $defer.promise;
283
+ };
284
+
285
+ /**
286
+ * @ngdoc method
287
+ * @name ngTable.factory:NgTableParams#getGroups
288
+ * @methodOf ngTable.factory:NgTableParams
289
+ * @description Return groups for table grouping
290
+ */
291
+ this.getGroups = function($defer, column) {
292
+ var defer = $q.defer();
293
+
294
+ defer.promise.then(function(data) {
295
+ var groups = {};
296
+ angular.forEach(data, function(item) {
297
+ var groupName = angular.isFunction(column) ? column(item) : item[column];
298
+
299
+ groups[groupName] = groups[groupName] || {
300
+ data: []
301
+ };
302
+ groups[groupName]['value'] = groupName;
303
+ groups[groupName].data.push(item);
304
+ });
305
+ var result = [];
306
+ for (var i in groups) {
307
+ result.push(groups[i]);
308
+ }
309
+ log('ngTable: refresh groups', result);
310
+ $defer.resolve(result);
311
+ });
312
+ return this.getData(defer, self);
313
+ };
314
+
315
+ /**
316
+ * @ngdoc method
317
+ * @name ngTable.factory:NgTableParams#generatePagesArray
318
+ * @methodOf ngTable.factory:NgTableParams
319
+ * @description Generate array of pages
320
+ *
321
+ * @param {boolean} currentPage which page must be active
322
+ * @param {boolean} totalItems Total quantity of items
323
+ * @param {boolean} pageSize Quantity of items on page
324
+ * @returns {Array} Array of pages
325
+ */
326
+ this.generatePagesArray = function(currentPage, totalItems, pageSize) {
327
+ var maxBlocks, maxPage, maxPivotPages, minPage, numPages, pages;
328
+ maxBlocks = 11;
329
+ pages = [];
330
+ numPages = Math.ceil(totalItems / pageSize);
331
+ if (numPages > 1) {
332
+ pages.push({
333
+ type: 'prev',
334
+ number: Math.max(1, currentPage - 1),
335
+ active: currentPage > 1
336
+ });
337
+ pages.push({
338
+ type: 'first',
339
+ number: 1,
340
+ active: currentPage > 1,
341
+ current: currentPage === 1
342
+ });
343
+ maxPivotPages = Math.round((maxBlocks - 5) / 2);
344
+ minPage = Math.max(2, currentPage - maxPivotPages);
345
+ maxPage = Math.min(numPages - 1, currentPage + maxPivotPages * 2 - (currentPage - minPage));
346
+ minPage = Math.max(2, minPage - (maxPivotPages * 2 - (maxPage - minPage)));
347
+ var i = minPage;
348
+ while (i <= maxPage) {
349
+ if ((i === minPage && i !== 2) || (i === maxPage && i !== numPages - 1)) {
350
+ pages.push({
351
+ type: 'more',
352
+ active: false
353
+ });
354
+ } else {
355
+ pages.push({
356
+ type: 'page',
357
+ number: i,
358
+ active: currentPage !== i,
359
+ current: currentPage === i
360
+ });
361
+ }
362
+ i++;
363
+ }
364
+ pages.push({
365
+ type: 'last',
366
+ number: numPages,
367
+ active: currentPage !== numPages,
368
+ current: currentPage === numPages
369
+ });
370
+ pages.push({
371
+ type: 'next',
372
+ number: Math.min(numPages, currentPage + 1),
373
+ active: currentPage < numPages
374
+ });
375
+ }
376
+ return pages;
377
+ };
378
+
379
+ /**
380
+ * @ngdoc method
381
+ * @name ngTable.factory:NgTableParams#url
382
+ * @methodOf ngTable.factory:NgTableParams
383
+ * @description Return groups for table grouping
384
+ *
385
+ * @param {boolean} asString flag indicates return array of string or object
386
+ * @returns {Array} If asString = true will be return array of url string parameters else key-value object
387
+ */
388
+ this.url = function(asString) {
389
+ asString = asString || false;
390
+ var pairs = (asString ? [] : {});
391
+ for (var key in params) {
392
+ if (params.hasOwnProperty(key)) {
393
+ var item = params[key],
394
+ name = encodeURIComponent(key);
395
+ if (typeof item === "object") {
396
+ for (var subkey in item) {
397
+ if (!angular.isUndefined(item[subkey]) && item[subkey] !== "") {
398
+ var pname = name + "[" + encodeURIComponent(subkey) + "]";
399
+ if (asString) {
400
+ pairs.push(pname + "=" + item[subkey]);
401
+ } else {
402
+ pairs[pname] = item[subkey];
403
+ }
404
+ }
405
+ }
406
+ } else if (!angular.isFunction(item) && !angular.isUndefined(item) && item !== "") {
407
+ if (asString) {
408
+ pairs.push(name + "=" + encodeURIComponent(item));
409
+ } else {
410
+ pairs[name] = encodeURIComponent(item);
411
+ }
412
+ }
413
+ }
414
+ }
415
+ return pairs;
416
+ };
417
+
418
+ /**
419
+ * @ngdoc method
420
+ * @name ngTable.factory:NgTableParams#reload
421
+ * @methodOf ngTable.factory:NgTableParams
422
+ * @description Reload table data
423
+ */
424
+ this.reload = function() {
425
+ var $defer = $q.defer(),
426
+ self = this,
427
+ pData = null;
428
+
429
+ if (!settings.$scope) {
430
+ return;
431
+ }
432
+
433
+ settings.$loading = true;
434
+ if (settings.groupBy) {
435
+ pData = settings.getGroups($defer, settings.groupBy, this);
436
+ } else {
437
+ pData = settings.getData($defer, this);
438
+ }
439
+ log('ngTable: reload data');
440
+
441
+ if (!pData) {
442
+ // If getData resolved the $defer, and didn't promise us data,
443
+ // create a promise from the $defer. We need to return a promise.
444
+ pData = $defer.promise;
445
+ }
446
+ return pData.then(function(data) {
447
+ settings.$loading = false;
448
+ log('ngTable: current scope', settings.$scope);
449
+ if (settings.groupBy) {
450
+ self.data = data;
451
+ if (settings.$scope) settings.$scope.$groups = data;
452
+ } else {
453
+ self.data = data;
454
+ if (settings.$scope) settings.$scope.$data = data;
455
+ }
456
+ if (settings.$scope) settings.$scope.pages = self.generatePagesArray(self.page(), self.total(), self.count());
457
+ settings.$scope.$emit('ngTableAfterReloadData');
458
+ return data;
459
+ });
460
+ };
461
+
462
+ this.reloadPages = function() {
463
+ var self = this;
464
+ settings.$scope.pages = self.generatePagesArray(self.page(), self.total(), self.count());
465
+ };
466
+
467
+ var params = this.$params = {
468
+ page: 1,
469
+ count: 1,
470
+ filter: {},
471
+ sorting: {},
472
+ group: {},
473
+ groupBy: null
474
+ };
475
+ angular.extend(params, ngTableDefaults.params);
476
+
477
+ var settings = {
478
+ $scope: null, // set by ngTable controller
479
+ $loading: false,
480
+ data: null, //allows data to be set when table is initialized
481
+ total: 0,
482
+ defaultSort: 'desc',
483
+ filterDelay: 750,
484
+ counts: [10, 25, 50, 100],
485
+ sortingIndicator: 'span',
486
+ getGroups: this.getGroups,
487
+ getData: this.getData
488
+ };
489
+ angular.extend(settings, ngTableDefaults.settings);
490
+
491
+ this.settings(baseSettings);
492
+ this.parameters(baseParameters, true);
493
+ return this;
494
+ };
495
+ return NgTableParams;
496
+ }]);
497
+
498
+ /**
499
+ * @ngdoc service
500
+ * @name ngTable.factory:ngTableParams
501
+ * @description Backwards compatible shim for lowercase 'n' in NgTableParams
502
+ */
503
+ app.factory('ngTableParams', ['NgTableParams', function(NgTableParams) {
504
+ return NgTableParams;
505
+ }]);
506
+
507
+ /**
508
+ * ngTable: Table + Angular JS
509
+ *
510
+ * @author Vitalii Savchuk <esvit666@gmail.com>
511
+ * @url https://github.com/esvit/ng-table/
512
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
513
+ */
514
+
515
+ /**
516
+ * @ngdoc object
517
+ * @name ngTable.directive:ngTable.ngTableController
518
+ *
519
+ * @description
520
+ * Each {@link ngTable.directive:ngTable ngTable} directive creates an instance of `ngTableController`
521
+ */
522
+ app.controller('ngTableController', ['$scope', 'NgTableParams', '$timeout', '$parse', '$compile', '$attrs', '$element',
523
+ 'ngTableColumn',
524
+ function($scope, NgTableParams, $timeout, $parse, $compile, $attrs, $element, ngTableColumn) {
525
+ var isFirstTimeLoad = true;
526
+ $scope.$filterRow = {};
527
+ $scope.$loading = false;
528
+
529
+ // until such times as the directive uses an isolated scope, we need to ensure that the check for
530
+ // the params field only consults the "own properties" of the $scope. This is to avoid seeing the params
531
+ // field on a $scope higher up in the prototype chain
532
+ if (!$scope.hasOwnProperty("params")) {
533
+ $scope.params = new NgTableParams();
534
+ $scope.params.isNullInstance = true;
535
+ }
536
+ $scope.params.settings().$scope = $scope;
537
+
538
+ var delayFilter = (function() {
539
+ var timer = 0;
540
+ return function(callback, ms) {
541
+ $timeout.cancel(timer);
542
+ timer = $timeout(callback, ms);
543
+ };
544
+ })();
545
+
546
+ function resetPage() {
547
+ $scope.params.$params.page = 1;
548
+ }
549
+
550
+ $scope.$watch('params.$params', function(newParams, oldParams) {
551
+
552
+ if (newParams === oldParams) {
553
+ return;
554
+ }
555
+
556
+ $scope.params.settings().$scope = $scope;
557
+
558
+ if (!angular.equals(newParams.filter, oldParams.filter)) {
559
+ var maybeResetPage = isFirstTimeLoad ? angular.noop : resetPage;
560
+ delayFilter(function() {
561
+ maybeResetPage();
562
+ $scope.params.reload();
563
+ }, $scope.params.settings().filterDelay);
564
+ } else {
565
+ $scope.params.reload();
566
+ }
567
+
568
+ if (!$scope.params.isNullInstance) {
569
+ isFirstTimeLoad = false;
570
+ }
571
+
572
+ }, true);
573
+
574
+ this.compileDirectiveTemplates = function () {
575
+ if (!$element.hasClass('ng-table')) {
576
+ $scope.templates = {
577
+ header: ($attrs.templateHeader ? $attrs.templateHeader : 'ng-table/header.html'),
578
+ pagination: ($attrs.templatePagination ? $attrs.templatePagination : 'ng-table/pager.html')
579
+ };
580
+ $element.addClass('ng-table');
581
+ var headerTemplate = null;
582
+ if ($element.find('> thead').length === 0) {
583
+ headerTemplate = angular.element(document.createElement('thead')).attr('ng-include', 'templates.header');
584
+ $element.prepend(headerTemplate);
585
+ }
586
+ var paginationTemplate = angular.element(document.createElement('div')).attr({
587
+ 'ng-table-pagination': 'params',
588
+ 'template-url': 'templates.pagination'
589
+ });
590
+ $element.after(paginationTemplate);
591
+ if (headerTemplate) {
592
+ $compile(headerTemplate)($scope);
593
+ }
594
+ $compile(paginationTemplate)($scope);
595
+ }
596
+ };
597
+
598
+ this.loadFilterData = function ($columns) {
599
+ angular.forEach($columns, function ($column) {
600
+ var def;
601
+ def = $column.filterData($scope, {
602
+ $column: $column
603
+ });
604
+ if (!def) {
605
+ delete $column.filterData;
606
+ return;
607
+ }
608
+
609
+ // if we're working with a deferred object, let's wait for the promise
610
+ if ((angular.isObject(def) && angular.isObject(def.promise))) {
611
+ delete $column.filterData;
612
+ return def.promise.then(function(data) {
613
+ // our deferred can eventually return arrays, functions and objects
614
+ if (!angular.isArray(data) && !angular.isFunction(data) && !angular.isObject(data)) {
615
+ // if none of the above was found - we just want an empty array
616
+ data = [];
617
+ } else if (angular.isArray(data)) {
618
+ data.unshift({
619
+ title: '-',
620
+ id: ''
621
+ });
622
+ }
623
+ $column.data = data;
624
+ });
625
+ }
626
+ // otherwise, we just return what the user gave us. It could be a function, array, object, whatever
627
+ else {
628
+ return $column.data = def;
629
+ }
630
+ });
631
+ };
632
+
633
+ this.buildColumns = function (columns) {
634
+ return columns.map(function(col){
635
+ return ngTableColumn.buildColumn(col, $scope)
636
+ })
637
+ };
638
+
639
+ this.setupBindingsToInternalScope = function(tableParamsExpr){
640
+
641
+ // note: this we're setting up watches to simulate angular's isolated scope bindings
642
+
643
+ // note: is REALLY important to watch for a change to the ngTableParams *reference* rather than
644
+ // $watch for value equivalence. This is because ngTableParams references the current page of data as
645
+ // a field and it's important not to watch this
646
+ var tableParamsGetter = $parse(tableParamsExpr);
647
+ $scope.$watch(tableParamsGetter, (function (params) {
648
+ if (angular.isUndefined(params)) {
649
+ return;
650
+ }
651
+ $scope.paramsModel = tableParamsGetter;
652
+ $scope.params = params;
653
+ }), false);
654
+
655
+ if ($attrs.showFilter) {
656
+ $scope.$parent.$watch($attrs.showFilter, function(value) {
657
+ $scope.show_filter = value;
658
+ });
659
+ }
660
+ if ($attrs.disableFilter) {
661
+ $scope.$parent.$watch($attrs.disableFilter, function(value) {
662
+ $scope.$filterRow.disabled = value;
663
+ });
664
+ }
665
+ };
666
+
667
+ $scope.sortBy = function($column, event) {
668
+ var parsedSortable = $column.sortable && $column.sortable();
669
+ if (!parsedSortable) {
670
+ return;
671
+ }
672
+ var defaultSort = $scope.params.settings().defaultSort;
673
+ var inverseSort = (defaultSort === 'asc' ? 'desc' : 'asc');
674
+ var sorting = $scope.params.sorting() && $scope.params.sorting()[parsedSortable] && ($scope.params.sorting()[parsedSortable] === defaultSort);
675
+ var sortingParams = (event.ctrlKey || event.metaKey) ? $scope.params.sorting() : {};
676
+ sortingParams[parsedSortable] = (sorting ? inverseSort : defaultSort);
677
+ $scope.params.parameters({
678
+ sorting: sortingParams
679
+ });
680
+ };
681
+ }]);
682
+
683
+
684
+ /**
685
+ * @ngdoc service
686
+ * @name ngTable.factory:ngTableColumn
687
+ *
688
+ * @description
689
+ * Service to construct a $column definition used by {@link ngTable.directive:ngTable ngTable} directive
690
+ */
691
+ app.factory('ngTableColumn', [function () {
692
+
693
+ var defaults = {
694
+ 'class': function(){ return ''; },
695
+ filter: function(){ return false; },
696
+ filterData: angular.noop,
697
+ headerTemplateURL: function(){ return false; },
698
+ headerTitle: function(){ return ' '; },
699
+ sortable: function(){ return false; },
700
+ show: function(){ return true; },
701
+ title: function(){ return ' '; },
702
+ titleAlt: function(){ return ''; }
703
+ };
704
+
705
+ /**
706
+ * @ngdoc method
707
+ * @name ngTable.factory:ngTableColumn#buildColumn
708
+ * @methodOf ngTable.factory:ngTableColumn
709
+ * @description Creates a $column for use within a header template
710
+ *
711
+ * @param {Object} column an existing $column or simple column data object
712
+ * @param {Scope} defaultScope the $scope to supply to the $column getter methods when not supplied by caller
713
+ * @returns {Object} a $column object
714
+ */
715
+ function buildColumn(column, defaultScope){
716
+ // note: we're not modifying the original column object. This helps to avoid unintended side affects
717
+ var extendedCol = Object.create(column);
718
+ for (var prop in defaults) {
719
+ if (extendedCol[prop] === undefined) {
720
+ extendedCol[prop] = defaults[prop];
721
+ }
722
+ if(!angular.isFunction(extendedCol[prop])){
723
+ // wrap raw field values with "getter" functions
724
+ // - this is to ensure consistency with how ngTable.compile builds columns
725
+ // - note that the original column object is being "proxied"; this is important
726
+ // as it ensure that any changes to the original object will be returned by the "getter"
727
+ (function(prop1){
728
+ extendedCol[prop1] = function(){
729
+ return column[prop1];
730
+ };
731
+ })(prop);
732
+ }
733
+ (function(prop1){
734
+ // satisfy the arguments expected by the function returned by parsedAttribute in the ngTable directive
735
+ var getterFn = extendedCol[prop1];
736
+ extendedCol[prop1] = function(){
737
+ if (arguments.length === 0){
738
+ return getterFn.call(column, defaultScope);
739
+ } else {
740
+ return getterFn.apply(column, arguments);
741
+ }
742
+ };
743
+ })(prop);
744
+ }
745
+ return extendedCol;
746
+ }
747
+
748
+ return {
749
+ buildColumn: buildColumn
750
+ };
751
+ }]);
752
+
753
+ /**
754
+ * ngTable: Table + Angular JS
755
+ *
756
+ * @author Vitalii Savchuk <esvit666@gmail.com>
757
+ * @url https://github.com/esvit/ng-table/
758
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
759
+ */
760
+
761
+ /**
762
+ * @ngdoc directive
763
+ * @name ngTable.directive:ngTable
764
+ * @restrict A
765
+ *
766
+ * @description
767
+ * Directive that instantiates {@link ngTable.directive:ngTable.ngTableController ngTableController}.
768
+ */
769
+ app.directive('ngTable', ['$q', '$parse',
770
+ function($q, $parse) {
771
+ 'use strict';
772
+
773
+ return {
774
+ restrict: 'A',
775
+ priority: 1001,
776
+ scope: true,
777
+ controller: 'ngTableController',
778
+ compile: function(element) {
779
+ var columns = [],
780
+ i = 0,
781
+ row = null;
782
+
783
+ // IE 8 fix :not(.ng-table-group) selector
784
+ angular.forEach(angular.element(element.find('tr')), function(tr) {
785
+ tr = angular.element(tr);
786
+ if (!tr.hasClass('ng-table-group') && !row) {
787
+ row = tr;
788
+ }
789
+ });
790
+ if (!row) {
791
+ return;
792
+ }
793
+ angular.forEach(row.find('td'), function(item) {
794
+ var el = angular.element(item);
795
+ if (el.attr('ignore-cell') && 'true' === el.attr('ignore-cell')) {
796
+ return;
797
+ }
798
+
799
+ var getAttrValue = function(attr){
800
+ return el.attr('x-data-' + attr) || el.attr('data-' + attr) || el.attr(attr);
801
+ };
802
+
803
+ var parsedAttribute = function(attr) {
804
+ var expr = getAttrValue(attr);
805
+ if (!expr){
806
+ return undefined;
807
+ }
808
+ return function(scope, locals) {
809
+ return $parse(expr)(scope, angular.extend(locals || {}, {
810
+ $columns: columns
811
+ }));
812
+ };
813
+ };
814
+
815
+ var titleExpr = getAttrValue('title-alt') || getAttrValue('title');
816
+ if (titleExpr){
817
+ el.attr('data-title-text', '{{' + titleExpr + '}}'); // this used in responsive table
818
+ }
819
+ // NOTE TO MAINTAINERS: if you add extra fields to a $column be sure to extend ngTableColumn with
820
+ // a corresponding "safe" default
821
+ columns.push({
822
+ id: i++,
823
+ title: parsedAttribute('title'),
824
+ titleAlt: parsedAttribute('title-alt'),
825
+ headerTitle: parsedAttribute('header-title'),
826
+ sortable: parsedAttribute('sortable'),
827
+ 'class': parsedAttribute('header-class'),
828
+ filter: parsedAttribute('filter'),
829
+ headerTemplateURL: parsedAttribute('header'),
830
+ filterData: parsedAttribute('filter-data'),
831
+ show: (el.attr("ng-show") ? function (scope) {
832
+ return $parse(el.attr("ng-show"))(scope);
833
+ } : undefined)
834
+ });
835
+ });
836
+ return function(scope, element, attrs, controller) {
837
+ scope.$columns = columns = controller.buildColumns(columns);
838
+
839
+ controller.setupBindingsToInternalScope(attrs.ngTable);
840
+ controller.loadFilterData(columns);
841
+ controller.compileDirectiveTemplates();
842
+ };
843
+ }
844
+ }
845
+ }
846
+ ]);
847
+
848
+ /**
849
+ * @ngdoc directive
850
+ * @name ngTable.directive:ngTableDynamic
851
+ * @restrict A
852
+ *
853
+ * @description
854
+ * A dynamic version of the {@link ngTable.directive:ngTable ngTable} directive that accepts a dynamic list of columns
855
+ * definitions to render
856
+ */
857
+ app.directive('ngTableDynamic', ['$parse', function ($parse){
858
+
859
+ function parseDirectiveExpression(attr) {
860
+ if (!attr || attr.indexOf(" with ") > -1) {
861
+ var parts = attr.split(/\s+with\s+/);
862
+ return {
863
+ tableParams: parts[0],
864
+ columns: parts[1]
865
+ };
866
+ } else {
867
+ throw new Error('Parse error (expected example: ng-table-dynamic=\'tableParams with cols\')');
868
+ }
869
+ }
870
+
871
+ return {
872
+ restrict: 'A',
873
+ priority: 1001,
874
+ scope: true,
875
+ controller: 'ngTableController',
876
+ compile: function(tElement) {
877
+ var row;
878
+
879
+ // IE 8 fix :not(.ng-table-group) selector
880
+ angular.forEach(angular.element(tElement.find('tr')), function(tr) {
881
+ tr = angular.element(tr);
882
+ if (!tr.hasClass('ng-table-group') && !row) {
883
+ row = tr;
884
+ }
885
+ });
886
+ if (!row) {
887
+ return;
888
+ }
889
+
890
+ angular.forEach(row.find('td'), function(item) {
891
+ var el = angular.element(item);
892
+ var getAttrValue = function(attr){
893
+ return el.attr('x-data-' + attr) || el.attr('data-' + attr) || el.attr(attr);
894
+ };
895
+
896
+ // this used in responsive table
897
+ var titleExpr = getAttrValue('title');
898
+ if (!titleExpr){
899
+ el.attr('data-title-text', '{{$columns[$index].titleAlt(this) || $columns[$index].title(this)}}');
900
+ }
901
+ var showExpr = el.attr('ng-show');
902
+ if (!showExpr){
903
+ el.attr('ng-show', '$columns[$index].show(this)');
904
+ }
905
+ });
906
+
907
+ return function(scope, element, attrs, controller) {
908
+ var expr = parseDirectiveExpression(attrs.ngTableDynamic);
909
+ var columns = $parse(expr.columns)(scope) || [];
910
+ scope.$columns = controller.buildColumns(columns);
911
+
912
+ controller.setupBindingsToInternalScope(expr.tableParams);
913
+ controller.loadFilterData(scope.$columns);
914
+ controller.compileDirectiveTemplates();
915
+ };
916
+ }
917
+ };
918
+ }]);
919
+
920
+ /**
921
+ * ngTable: Table + Angular JS
922
+ *
923
+ * @author Vitalii Savchuk <esvit666@gmail.com>
924
+ * @url https://github.com/esvit/ng-table/
925
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
926
+ */
927
+
928
+ /**
929
+ * @ngdoc directive
930
+ * @name ngTable.directive:ngTablePagination
931
+ * @restrict A
932
+ */
933
+ app.directive('ngTablePagination', ['$compile',
934
+ function($compile) {
935
+ 'use strict';
936
+
937
+ return {
938
+ restrict: 'A',
939
+ scope: {
940
+ 'params': '=ngTablePagination',
941
+ 'templateUrl': '='
942
+ },
943
+ replace: false,
944
+ link: function(scope, element, attrs) {
945
+
946
+ scope.params.settings().$scope.$on('ngTableAfterReloadData', function() {
947
+ scope.pages = scope.params.generatePagesArray(scope.params.page(), scope.params.total(), scope.params.count());
948
+ }, true);
949
+
950
+ scope.$watch('templateUrl', function(templateUrl) {
951
+ if (angular.isUndefined(templateUrl)) {
952
+ return;
953
+ }
954
+ var template = angular.element(document.createElement('div'))
955
+ template.attr({
956
+ 'ng-include': 'templateUrl'
957
+ });
958
+ element.append(template);
959
+ $compile(template)(scope);
960
+ });
961
+ }
962
+ };
963
+ }
964
+ ]);
965
+ angular.module('ngTable').run(['$templateCache', function ($templateCache) {
966
+ $templateCache.put('ng-table/filters/select-multiple.html', '<select ng-options="data.id as data.title for data in $column.data" ng-disabled="$filterRow.disabled" multiple ng-multiple="true" ng-model="params.filter()[name]" ng-show="filter==\'select-multiple\'" class="filter filter-select-multiple form-control" name="{{name}}"> </select>');
967
+ $templateCache.put('ng-table/filters/select.html', '<select ng-options="data.id as data.title for data in $column.data" ng-disabled="$filterRow.disabled" ng-model="params.filter()[name]" ng-show="filter==\'select\'" class="filter filter-select form-control" name="{{name}}"> </select>');
968
+ $templateCache.put('ng-table/filters/text.html', '<input type="text" name="{{name}}" ng-disabled="$filterRow.disabled" ng-model="params.filter()[name]" ng-if="filter==\'text\'" class="input-filter form-control"/>');
969
+ $templateCache.put('ng-table/header.html', '<tr> <th title="{{$column.headerTitle(this)}}" ng-repeat="$column in $columns" ng-class="{ \'sortable\': $column.sortable(this), \'sort-asc\': params.sorting()[$column.sortable(this)]==\'asc\', \'sort-desc\': params.sorting()[$column.sortable(this)]==\'desc\' }" ng-click="sortBy($column, $event)" ng-show="$column.show(this)" ng-init="template=$column.headerTemplateURL(this)" class="header {{$column.class(this)}}"> <div ng-if="!template" ng-show="!template" class="ng-table-header" ng-class="{\'sort-indicator\': params.settings().sortingIndicator==\'div\'}"> <span ng-bind="$column.title(this)" ng-class="{\'sort-indicator\': params.settings().sortingIndicator==\'span\'}"></span> </div> <div ng-if="template" ng-show="template" ng-include="template"></div> </th> </tr> <tr ng-show="show_filter" class="ng-table-filters"> <th data-title-text="{{$column.titleAlt(this) || $column.title(this)}}" ng-repeat="$column in $columns" ng-show="$column.show(this)" class="filter"> <div ng-repeat="(name, filter) in $column.filter(this)"> <div ng-if="filter.indexOf(\'/\') !==-1" ng-include="filter"></div> <div ng-if="filter.indexOf(\'/\')===-1" ng-include="\'ng-table/filters/\' + filter + \'.html\'"></div> </div> </th> </tr> ');
970
+ $templateCache.put('ng-table/pager.html', '<div class="ng-cloak ng-table-pager" ng-if="params.data.length"> <div ng-if="params.settings().counts.length" class="ng-table-counts btn-group pull-right"> <button ng-repeat="count in params.settings().counts" type="button" ng-class="{\'active\':params.count()==count}" ng-click="params.count(count)" class="btn btn-default"> <span ng-bind="count"></span> </button> </div> <ul class="pagination ng-table-pagination"> <li ng-class="{\'disabled\': !page.active && !page.current, \'active\': page.current}" ng-repeat="page in pages" ng-switch="page.type"> <a ng-switch-when="prev" ng-click="params.page(page.number)" href="">&laquo;</a> <a ng-switch-when="first" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="page" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="more" ng-click="params.page(page.number)" href="">&#8230;</a> <a ng-switch-when="last" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="next" ng-click="params.page(page.number)" href="">&raquo;</a> </li> </ul> </div> ');
971
+ }]);
972
+ return app;
973
+ }));
@@ -0,0 +1,137 @@
1
+
2
+ .ng-table th {
3
+ text-align: center;
4
+ -webkit-touch-callout: none;
5
+ -webkit-user-select: none;
6
+ -khtml-user-select: none;
7
+ -moz-user-select: none;
8
+ -ms-user-select: none;
9
+ user-select: none;
10
+ }
11
+ .ng-table th.sortable {
12
+ cursor: pointer;
13
+ }
14
+ .ng-table th.sortable .sort-indicator {
15
+ padding-right: 18px;
16
+ position: relative;
17
+ }
18
+ .ng-table th.sortable .sort-indicator:after,
19
+ .ng-table th.sortable .sort-indicator:before {
20
+ content: "";
21
+ border-width: 0 4px 4px;
22
+ border-style: solid;
23
+ border-color: #000 transparent;
24
+ visibility: visible;
25
+ right: 5px;
26
+ top: 50%;
27
+ position: absolute;
28
+ opacity: .3;
29
+ margin-top: -4px;
30
+ }
31
+ .ng-table th.sortable .sort-indicator:before {
32
+ margin-top: 2px;
33
+ border-bottom: none;
34
+ border-left: 4px solid transparent;
35
+ border-right: 4px solid transparent;
36
+ border-top: 4px solid #000;
37
+ }
38
+ .ng-table th.sortable .sort-indicator:hover:after,
39
+ .ng-table th.sortable .sort-indicator:hover:before {
40
+ opacity: 1;
41
+ visibility: visible;
42
+ }
43
+ .ng-table th.sortable.sort-desc,
44
+ .ng-table th.sortable.sort-asc {
45
+ background-color: rgba(141, 192, 219, 0.25);
46
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
47
+ }
48
+ .ng-table th.sortable.sort-desc .sort-indicator:after,
49
+ .ng-table th.sortable.sort-asc .sort-indicator:after {
50
+ margin-top: -2px;
51
+ }
52
+ .ng-table th.sortable.sort-desc .sort-indicator:before,
53
+ .ng-table th.sortable.sort-asc .sort-indicator:before {
54
+ visibility: hidden;
55
+ }
56
+ .ng-table th.sortable.sort-asc .sort-indicator:after,
57
+ .ng-table th.sortable.sort-asc .sort-indicator:hover:after {
58
+ visibility: visible;
59
+ filter: alpha(opacity=60);
60
+ -khtml-opacity: 0.6;
61
+ -moz-opacity: 0.6;
62
+ opacity: 0.6;
63
+ }
64
+ .ng-table th.sortable.sort-desc .sort-indicator:after {
65
+ border-bottom: none;
66
+ border-left: 4px solid transparent;
67
+ border-right: 4px solid transparent;
68
+ border-top: 4px solid #000;
69
+ visibility: visible;
70
+ -webkit-box-shadow: none;
71
+ -moz-box-shadow: none;
72
+ box-shadow: none;
73
+ filter: alpha(opacity=60);
74
+ -khtml-opacity: 0.6;
75
+ -moz-opacity: 0.6;
76
+ opacity: 0.6;
77
+ }
78
+ .ng-table th.filter .input-filter {
79
+ margin: 0;
80
+ display: block;
81
+ width: 100%;
82
+ min-height: 30px;
83
+ -webkit-box-sizing: border-box;
84
+ -moz-box-sizing: border-box;
85
+ box-sizing: border-box;
86
+ }
87
+ .ng-table + .pagination {
88
+ margin-top: 0;
89
+ }
90
+ @media only screen and (max-width: 800px) {
91
+ .ng-table-responsive {
92
+ border-bottom: 1px solid #999999;
93
+ }
94
+ .ng-table-responsive tr {
95
+ border-top: 1px solid #999999;
96
+ border-left: 1px solid #999999;
97
+ border-right: 1px solid #999999;
98
+ }
99
+ .ng-table-responsive td:before {
100
+ position: absolute;
101
+ padding: 8px;
102
+ left: 0;
103
+ top: 0;
104
+ width: 50%;
105
+ white-space: nowrap;
106
+ text-align: left;
107
+ font-weight: bold;
108
+ }
109
+ .ng-table-responsive thead tr th {
110
+ text-align: left;
111
+ }
112
+ .ng-table-responsive thead tr.ng-table-filters th {
113
+ padding: 0;
114
+ }
115
+ .ng-table-responsive thead tr.ng-table-filters th form > div {
116
+ padding: 8px;
117
+ }
118
+ .ng-table-responsive td {
119
+ border: none;
120
+ border-bottom: 1px solid #eeeeee;
121
+ position: relative;
122
+ padding-left: 50%;
123
+ white-space: normal;
124
+ text-align: left;
125
+ }
126
+ .ng-table-responsive td:before {
127
+ content: attr(data-title-text);
128
+ }
129
+ .ng-table-responsive,
130
+ .ng-table-responsive thead,
131
+ .ng-table-responsive tbody,
132
+ .ng-table-responsive th,
133
+ .ng-table-responsive td,
134
+ .ng-table-responsive tr {
135
+ display: block;
136
+ }
137
+ }
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: angular-ng-table-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.4.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniel García-Gil
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-22 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Injects ng-table Rails directives into your asset pipeline.
14
+ email: 'danielgarciagil@gmail.com '
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - MIT-LICENSE
20
+ - README.md
21
+ - lib/angular-ng-table-rails.rb
22
+ - lib/angular-ng-table-rails/version.rb
23
+ - vendor/assets/javascripts/ng-table.js
24
+ - vendor/assets/stylesheets/ng-table.css
25
+ homepage: https://github.com/danielgarciagil/angular-ng-table-rails.git
26
+ licenses:
27
+ - MIT
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.4.6
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: ng-table directive for angular on rails
49
+ test_files: []