smartkiosk-server 0.12.1 → 0.13

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.
Files changed (78) hide show
  1. data/.ruby-version +1 -1
  2. data/Gemfile +9 -6
  3. data/Gemfile.lock +36 -9
  4. data/app/assets/images/sort-asc.png +0 -0
  5. data/app/assets/images/sort-desc.png +0 -0
  6. data/app/assets/javascripts/application.js +1 -1
  7. data/app/assets/javascripts/monitoring.js.coffee +25 -0
  8. data/app/assets/javascripts/monitoring/helpers/application.js.coffee +12 -0
  9. data/app/assets/javascripts/monitoring/layouts/application.js.coffee +2 -0
  10. data/app/assets/javascripts/monitoring/pages/application.js.coffee +1 -0
  11. data/app/assets/javascripts/monitoring/pages/welcome/index.js.coffee +229 -0
  12. data/app/assets/javascripts/monitoring/resources/.gitkeep +0 -0
  13. data/app/assets/javascripts/monitoring/routes.js.coffee +8 -0
  14. data/app/assets/javascripts/monitoring/templates/layouts/.gitkeep +0 -0
  15. data/app/assets/javascripts/monitoring/templates/layouts/application.jst.hamlc +1 -0
  16. data/app/assets/javascripts/monitoring/templates/pages/welcome/_row.jst.hamlc +28 -0
  17. data/app/assets/javascripts/monitoring/templates/pages/welcome/_tbody.jst.hamlc +3 -0
  18. data/app/assets/javascripts/monitoring/templates/pages/welcome/index.jst.hamlc +37 -0
  19. data/app/assets/javascripts/monitoring/templates/widgets/.gitkeep +0 -0
  20. data/app/assets/javascripts/monitoring/widgets/.gitkeep +0 -0
  21. data/app/assets/javascripts/monitoring_preloader.js.coffee.erb +17 -0
  22. data/app/assets/stylesheets/active_admin.css.scss +4 -4
  23. data/app/assets/stylesheets/{fonts.css.scss → active_admin/fonts.css.scss} +0 -0
  24. data/app/assets/stylesheets/active_admin/provider_groups.css.scss +12 -0
  25. data/app/assets/stylesheets/{provider_receipt_templates.css.scss → active_admin/provider_receipt_templates.css.scss} +0 -0
  26. data/app/assets/stylesheets/{terminals.css.scss → active_admin/terminals.css.scss} +0 -0
  27. data/app/assets/stylesheets/monitoring.css.scss +4 -0
  28. data/app/assets/stylesheets/monitoring/bootstrap.css +9 -0
  29. data/app/assets/stylesheets/monitoring/theme.css.scss +35 -0
  30. data/app/controllers/monitoring_controller.rb +31 -0
  31. data/app/controllers/payments_controller.rb +1 -1
  32. data/app/controllers/terminal_builds_controller.rb +2 -0
  33. data/app/controllers/welcome_controller.rb +5 -2
  34. data/app/models/ability.rb +6 -4
  35. data/app/models/agent.rb +6 -0
  36. data/app/models/collection.rb +2 -2
  37. data/app/models/payment.rb +3 -3
  38. data/app/models/provider_gateway.rb +1 -1
  39. data/app/models/report_result.rb +1 -1
  40. data/app/models/report_template.rb +3 -3
  41. data/app/models/role.rb +51 -10
  42. data/app/models/terminal.rb +25 -1
  43. data/app/models/terminal_build.rb +1 -1
  44. data/app/models/terminal_order.rb +1 -1
  45. data/app/models/terminal_ping.rb +3 -0
  46. data/app/models/terminal_profile.rb +6 -0
  47. data/app/models/user_role.rb +1 -1
  48. data/app/views/monitoring/index.html.erb +28 -0
  49. data/config.ru +7 -0
  50. data/config/deploy.rb +1 -1
  51. data/config/environments/development.rb +2 -0
  52. data/config/environments/production.rb +1 -1
  53. data/config/initializers/monitoring.rb +3 -0
  54. data/config/initializers/redis.rb +1 -1
  55. data/config/locales/activerecord.ru.yml +14 -0
  56. data/config/locales/smartkiosk.ru.yml +38 -0
  57. data/config/routes.rb +1 -1
  58. data/db/migrate/20130419125334_add_ping_to_terminals.rb +22 -0
  59. data/db/schema.rb +25 -7
  60. data/lib/blueprints.rb +2 -0
  61. data/lib/monitorer.rb +41 -0
  62. data/lib/seeder.rb +1 -1
  63. data/lib/smartkiosk/server/version.rb +1 -1
  64. data/vendor/assets/javascripts/chosen.jquery.js +1 -1
  65. data/vendor/assets/javascripts/cookie.jquery.js +95 -0
  66. data/vendor/assets/javascripts/copypaste.js +148 -0
  67. data/vendor/assets/javascripts/event-drag.jquery.js +402 -0
  68. data/vendor/assets/javascripts/sheetclip.js +87 -0
  69. data/vendor/assets/javascripts/slick.cellrangedecorator.js +64 -0
  70. data/vendor/assets/javascripts/slick.cellrangeselector.js +111 -0
  71. data/vendor/assets/javascripts/slick.cellselectionmodel.js +152 -0
  72. data/vendor/assets/javascripts/slick.core.js +458 -0
  73. data/vendor/assets/javascripts/slick.grid.js +3287 -0
  74. data/vendor/assets/stylesheets/slick.grid.css +167 -0
  75. metadata +42 -9
  76. data/app/assets/stylesheets/provider_groups.css.scss +0 -12
  77. data/app/views/layouts/application.html.erb +0 -1
  78. data/app/views/welcome/index.html.erb +0 -25
@@ -0,0 +1,87 @@
1
+ /**
2
+ * SheetClip - Spreadsheet Clipboard Parser
3
+ * version 0.2
4
+ *
5
+ * This tiny library transforms JavaScript arrays to strings that are pasteable by LibreOffice, OpenOffice,
6
+ * Google Docs and Microsoft Excel.
7
+ *
8
+ * Copyright 2012, Marcin Warpechowski
9
+ * Licensed under the MIT license.
10
+ * http://github.com/warpech/sheetclip/
11
+ */
12
+ /*jslint white: true*/
13
+ (function (global) {
14
+ "use strict";
15
+
16
+ function countQuotes(str) {
17
+ return str.split('"').length - 1;
18
+ }
19
+
20
+ global.SheetClip = {
21
+ parse: function (str) {
22
+ var r, rlen, rows, arr = [], a = 0, c, clen, multiline, last;
23
+ rows = str.split('\n');
24
+ if (rows.length > 1 && rows[rows.length - 1] === '') {
25
+ rows.pop();
26
+ }
27
+ for (r = 0, rlen = rows.length; r < rlen; r += 1) {
28
+ rows[r] = rows[r].split('\t');
29
+ for (c = 0, clen = rows[r].length; c < clen; c += 1) {
30
+ if (!arr[a]) {
31
+ arr[a] = [];
32
+ }
33
+ if (multiline && c === 0) {
34
+ last = arr[a].length - 1;
35
+ arr[a][last] = arr[a][last] + '\n' + rows[r][0];
36
+ if (multiline && countQuotes(rows[r][0]) % 2 === 1) {
37
+ multiline = false;
38
+ arr[a][last] = arr[a][last].substring(0, arr[a][last].length - 1).replace(/""/g, '"');
39
+ }
40
+ }
41
+ else {
42
+ if (c === clen - 1 && rows[r][c].indexOf('"') === 0) {
43
+ arr[a].push(rows[r][c].substring(1).replace(/""/g, '"'));
44
+ multiline = true;
45
+ }
46
+ else {
47
+ arr[a].push(rows[r][c].replace(/""/g, '"'));
48
+ multiline = false;
49
+ }
50
+ }
51
+ }
52
+ if (!multiline) {
53
+ a += 1;
54
+ }
55
+ }
56
+ return arr;
57
+ },
58
+
59
+ stringify: function (arr) {
60
+ var r, rlen, c, clen, str = '', val;
61
+ for (r = 0, rlen = arr.length; r < rlen; r += 1) {
62
+ for (c = 0, clen = arr[r].length; c < clen; c += 1) {
63
+ if (c > 0) {
64
+ str += '\t';
65
+ }
66
+ val = arr[r][c];
67
+ if (typeof val === 'string') {
68
+ if (val.indexOf('\n') > -1) {
69
+ str += '"' + val.replace(/"/g, '""') + '"';
70
+ }
71
+ else {
72
+ str += val;
73
+ }
74
+ }
75
+ else if (val === null || val === void 0) { //void 0 resolves to undefined
76
+ str += '';
77
+ }
78
+ else {
79
+ str += val;
80
+ }
81
+ }
82
+ str += '\n';
83
+ }
84
+ return str;
85
+ }
86
+ };
87
+ }(window));
@@ -0,0 +1,64 @@
1
+ (function ($) {
2
+ // register namespace
3
+ $.extend(true, window, {
4
+ "Slick": {
5
+ "CellRangeDecorator": CellRangeDecorator
6
+ }
7
+ });
8
+
9
+ /***
10
+ * Displays an overlay on top of a given cell range.
11
+ *
12
+ * TODO:
13
+ * Currently, it blocks mouse events to DOM nodes behind it.
14
+ * Use FF and WebKit-specific "pointer-events" CSS style, or some kind of event forwarding.
15
+ * Could also construct the borders separately using 4 individual DIVs.
16
+ *
17
+ * @param {Grid} grid
18
+ * @param {Object} options
19
+ */
20
+ function CellRangeDecorator(grid, options) {
21
+ var _elem;
22
+ var _defaults = {
23
+ selectionCss: {
24
+ "zIndex": "9999",
25
+ "border": "2px dashed red"
26
+ }
27
+ };
28
+
29
+ options = $.extend(true, {}, _defaults, options);
30
+
31
+
32
+ function show(range) {
33
+ if (!_elem) {
34
+ _elem = $("<div></div>", {css: options.selectionCss})
35
+ .css("position", "absolute")
36
+ .appendTo(grid.getCanvasNode());
37
+ }
38
+
39
+ var from = grid.getCellNodeBox(range.fromRow, range.fromCell);
40
+ var to = grid.getCellNodeBox(range.toRow, range.toCell);
41
+
42
+ _elem.css({
43
+ top: from.top - 1,
44
+ left: from.left - 1,
45
+ height: to.bottom - from.top - 2,
46
+ width: to.right - from.left - 2
47
+ });
48
+
49
+ return _elem;
50
+ }
51
+
52
+ function hide() {
53
+ if (_elem) {
54
+ _elem.remove();
55
+ _elem = null;
56
+ }
57
+ }
58
+
59
+ $.extend(this, {
60
+ "show": show,
61
+ "hide": hide
62
+ });
63
+ }
64
+ })(jQuery);
@@ -0,0 +1,111 @@
1
+ (function ($) {
2
+ // register namespace
3
+ $.extend(true, window, {
4
+ "Slick": {
5
+ "CellRangeSelector": CellRangeSelector
6
+ }
7
+ });
8
+
9
+
10
+ function CellRangeSelector(options) {
11
+ var _grid;
12
+ var _canvas;
13
+ var _dragging;
14
+ var _decorator;
15
+ var _self = this;
16
+ var _handler = new Slick.EventHandler();
17
+ var _defaults = {
18
+ selectionCss: {
19
+ "border": "2px dashed blue"
20
+ }
21
+ };
22
+
23
+
24
+ function init(grid) {
25
+ options = $.extend(true, {}, _defaults, options);
26
+ _decorator = new Slick.CellRangeDecorator(grid, options);
27
+ _grid = grid;
28
+ _canvas = _grid.getCanvasNode();
29
+ _handler
30
+ .subscribe(_grid.onDragInit, handleDragInit)
31
+ .subscribe(_grid.onDragStart, handleDragStart)
32
+ .subscribe(_grid.onDrag, handleDrag)
33
+ .subscribe(_grid.onDragEnd, handleDragEnd);
34
+ }
35
+
36
+ function destroy() {
37
+ _handler.unsubscribeAll();
38
+ }
39
+
40
+ function handleDragInit(e, dd) {
41
+ // prevent the grid from cancelling drag'n'drop by default
42
+ e.stopImmediatePropagation();
43
+ }
44
+
45
+ function handleDragStart(e, dd) {
46
+ var cell = _grid.getCellFromEvent(e);
47
+ if (_self.onBeforeCellRangeSelected.notify(cell) !== false) {
48
+ if (_grid.canCellBeSelected(cell.row, cell.cell)) {
49
+ _dragging = true;
50
+ e.stopImmediatePropagation();
51
+ }
52
+ }
53
+ if (!_dragging) {
54
+ return;
55
+ }
56
+
57
+ var start = _grid.getCellFromPoint(
58
+ dd.startX - $(_canvas).offset().left,
59
+ dd.startY - $(_canvas).offset().top);
60
+
61
+ dd.range = {start: start, end: {}};
62
+
63
+ return _decorator.show(new Slick.Range(start.row, start.cell));
64
+ }
65
+
66
+ function handleDrag(e, dd) {
67
+ if (!_dragging) {
68
+ return;
69
+ }
70
+ e.stopImmediatePropagation();
71
+
72
+ var end = _grid.getCellFromPoint(
73
+ e.pageX - $(_canvas).offset().left,
74
+ e.pageY - $(_canvas).offset().top);
75
+
76
+ if (!_grid.canCellBeSelected(end.row, end.cell)) {
77
+ return;
78
+ }
79
+
80
+ dd.range.end = end;
81
+ _decorator.show(new Slick.Range(dd.range.start.row, dd.range.start.cell, end.row, end.cell));
82
+ }
83
+
84
+ function handleDragEnd(e, dd) {
85
+ if (!_dragging) {
86
+ return;
87
+ }
88
+
89
+ _dragging = false;
90
+ e.stopImmediatePropagation();
91
+
92
+ _decorator.hide();
93
+ _self.onCellRangeSelected.notify({
94
+ range: new Slick.Range(
95
+ dd.range.start.row,
96
+ dd.range.start.cell,
97
+ dd.range.end.row,
98
+ dd.range.end.cell
99
+ )
100
+ });
101
+ }
102
+
103
+ $.extend(this, {
104
+ "init": init,
105
+ "destroy": destroy,
106
+
107
+ "onBeforeCellRangeSelected": new Slick.Event(),
108
+ "onCellRangeSelected": new Slick.Event()
109
+ });
110
+ }
111
+ })(jQuery);
@@ -0,0 +1,152 @@
1
+ (function ($) {
2
+ // register namespace
3
+ $.extend(true, window, {
4
+ "Slick": {
5
+ "CellSelectionModel": CellSelectionModel
6
+ }
7
+ });
8
+
9
+
10
+ function CellSelectionModel(options) {
11
+ var _grid;
12
+ var _canvas;
13
+ var _ranges = [];
14
+ var _self = this;
15
+ var _selector = new Slick.CellRangeSelector({
16
+ "selectionCss": {
17
+ "border": "2px solid black"
18
+ }
19
+ });
20
+ var _options;
21
+ var _defaults = {
22
+ selectActiveCell: true
23
+ };
24
+
25
+
26
+ function init(grid) {
27
+ _options = $.extend(true, {}, _defaults, options);
28
+ _grid = grid;
29
+ _canvas = _grid.getCanvasNode();
30
+ _grid.onActiveCellChanged.subscribe(handleActiveCellChange);
31
+ _grid.onKeyDown.subscribe(handleKeyDown);
32
+ grid.registerPlugin(_selector);
33
+ _selector.onCellRangeSelected.subscribe(handleCellRangeSelected);
34
+ _selector.onBeforeCellRangeSelected.subscribe(handleBeforeCellRangeSelected);
35
+ }
36
+
37
+ function destroy() {
38
+ _grid.onActiveCellChanged.unsubscribe(handleActiveCellChange);
39
+ _grid.onKeyDown.unsubscribe(handleKeyDown);
40
+ _selector.onCellRangeSelected.unsubscribe(handleCellRangeSelected);
41
+ _selector.onBeforeCellRangeSelected.unsubscribe(handleBeforeCellRangeSelected);
42
+ _grid.unregisterPlugin(_selector);
43
+ }
44
+
45
+ function removeInvalidRanges(ranges) {
46
+ var result = [];
47
+
48
+ for (var i = 0; i < ranges.length; i++) {
49
+ var r = ranges[i];
50
+ if (_grid.canCellBeSelected(r.fromRow, r.fromCell) && _grid.canCellBeSelected(r.toRow, r.toCell)) {
51
+ result.push(r);
52
+ }
53
+ }
54
+
55
+ return result;
56
+ }
57
+
58
+ function setSelectedRanges(ranges) {
59
+ _ranges = removeInvalidRanges(ranges);
60
+ _self.onSelectedRangesChanged.notify(_ranges);
61
+ }
62
+
63
+ function getSelectedRanges() {
64
+ return _ranges;
65
+ }
66
+
67
+ function handleBeforeCellRangeSelected(e, args) {
68
+ if (_grid.getEditorLock().isActive()) {
69
+ e.stopPropagation();
70
+ return false;
71
+ }
72
+ }
73
+
74
+ function handleCellRangeSelected(e, args) {
75
+ setSelectedRanges([args.range]);
76
+ }
77
+
78
+ function handleActiveCellChange(e, args) {
79
+ if (_options.selectActiveCell && args.row != null && args.cell != null) {
80
+ setSelectedRanges([new Slick.Range(args.row, args.cell)]);
81
+ }
82
+ }
83
+
84
+ function handleKeyDown(e) {
85
+ /***
86
+ * Кey codes
87
+ * 37 left
88
+ * 38 up
89
+ * 39 right
90
+ * 40 down
91
+ */
92
+ var ranges, last;
93
+ var active = _grid.getActiveCell();
94
+
95
+ if ( active && e.shiftKey && !e.ctrlKey && !e.altKey &&
96
+ (e.which == 37 || e.which == 39 || e.which == 38 || e.which == 40) ) {
97
+
98
+ ranges = getSelectedRanges();
99
+ if (!ranges.length)
100
+ ranges.push(new Slick.Range(active.row, active.cell));
101
+
102
+ // keyboard can work with last range only
103
+ last = ranges.pop();
104
+
105
+ // can't handle selection out of active cell
106
+ if (!last.contains(active.row, active.cell))
107
+ last = new Slick.Range(active.row, active.cell);
108
+
109
+ var dRow = last.toRow - last.fromRow,
110
+ dCell = last.toCell - last.fromCell,
111
+ // walking direction
112
+ dirRow = active.row == last.fromRow ? 1 : -1,
113
+ dirCell = active.cell == last.fromCell ? 1 : -1;
114
+
115
+ if (e.which == 37) {
116
+ dCell -= dirCell;
117
+ } else if (e.which == 39) {
118
+ dCell += dirCell ;
119
+ } else if (e.which == 38) {
120
+ dRow -= dirRow;
121
+ } else if (e.which == 40) {
122
+ dRow += dirRow;
123
+ }
124
+
125
+ // define new selection range
126
+ var new_last = new Slick.Range(active.row, active.cell, active.row + dirRow*dRow, active.cell + dirCell*dCell);
127
+ if (removeInvalidRanges([new_last]).length) {
128
+ ranges.push(new_last);
129
+ _grid.scrollRowIntoView(dirRow > 0 ? new_last.toRow : new_last.fromRow);
130
+ _grid.scrollCellIntoView(new_last.fromRow, dirCell > 0 ? new_last.toCell : new_last.fromCell);
131
+ }
132
+ else
133
+ ranges.push(last);
134
+
135
+ setSelectedRanges(ranges);
136
+
137
+ e.preventDefault();
138
+ e.stopPropagation();
139
+ }
140
+ }
141
+
142
+ $.extend(this, {
143
+ "getSelectedRanges": getSelectedRanges,
144
+ "setSelectedRanges": setSelectedRanges,
145
+
146
+ "init": init,
147
+ "destroy": destroy,
148
+
149
+ "onSelectedRangesChanged": new Slick.Event()
150
+ });
151
+ }
152
+ })(jQuery);
@@ -0,0 +1,458 @@
1
+ /***
2
+ * Contains core SlickGrid classes.
3
+ * @module Core
4
+ * @namespace Slick
5
+ */
6
+
7
+ (function ($) {
8
+ // register namespace
9
+ $.extend(true, window, {
10
+ "Slick": {
11
+ "Event": Event,
12
+ "EventData": EventData,
13
+ "EventHandler": EventHandler,
14
+ "Range": Range,
15
+ "NonDataRow": NonDataItem,
16
+ "Group": Group,
17
+ "GroupTotals": GroupTotals,
18
+ "EditorLock": EditorLock,
19
+
20
+ /***
21
+ * A global singleton editor lock.
22
+ * @class GlobalEditorLock
23
+ * @static
24
+ * @constructor
25
+ */
26
+ "GlobalEditorLock": new EditorLock()
27
+ }
28
+ });
29
+
30
+ /***
31
+ * An event object for passing data to event handlers and letting them control propagation.
32
+ * <p>This is pretty much identical to how W3C and jQuery implement events.</p>
33
+ * @class EventData
34
+ * @constructor
35
+ */
36
+ function EventData() {
37
+ var isPropagationStopped = false;
38
+ var isImmediatePropagationStopped = false;
39
+
40
+ /***
41
+ * Stops event from propagating up the DOM tree.
42
+ * @method stopPropagation
43
+ */
44
+ this.stopPropagation = function () {
45
+ isPropagationStopped = true;
46
+ };
47
+
48
+ /***
49
+ * Returns whether stopPropagation was called on this event object.
50
+ * @method isPropagationStopped
51
+ * @return {Boolean}
52
+ */
53
+ this.isPropagationStopped = function () {
54
+ return isPropagationStopped;
55
+ };
56
+
57
+ /***
58
+ * Prevents the rest of the handlers from being executed.
59
+ * @method stopImmediatePropagation
60
+ */
61
+ this.stopImmediatePropagation = function () {
62
+ isImmediatePropagationStopped = true;
63
+ };
64
+
65
+ /***
66
+ * Returns whether stopImmediatePropagation was called on this event object.\
67
+ * @method isImmediatePropagationStopped
68
+ * @return {Boolean}
69
+ */
70
+ this.isImmediatePropagationStopped = function () {
71
+ return isImmediatePropagationStopped;
72
+ }
73
+ }
74
+
75
+ /***
76
+ * A simple publisher-subscriber implementation.
77
+ * @class Event
78
+ * @constructor
79
+ */
80
+ function Event() {
81
+ var handlers = [];
82
+
83
+ /***
84
+ * Adds an event handler to be called when the event is fired.
85
+ * <p>Event handler will receive two arguments - an <code>EventData</code> and the <code>data</code>
86
+ * object the event was fired with.<p>
87
+ * @method subscribe
88
+ * @param fn {Function} Event handler.
89
+ */
90
+ this.subscribe = function (fn) {
91
+ handlers.push(fn);
92
+ };
93
+
94
+ /***
95
+ * Removes an event handler added with <code>subscribe(fn)</code>.
96
+ * @method unsubscribe
97
+ * @param fn {Function} Event handler to be removed.
98
+ */
99
+ this.unsubscribe = function (fn) {
100
+ for (var i = handlers.length - 1; i >= 0; i--) {
101
+ if (handlers[i] === fn) {
102
+ handlers.splice(i, 1);
103
+ }
104
+ }
105
+ };
106
+
107
+ /***
108
+ * Fires an event notifying all subscribers.
109
+ * @method notify
110
+ * @param args {Object} Additional data object to be passed to all handlers.
111
+ * @param e {EventData}
112
+ * Optional.
113
+ * An <code>EventData</code> object to be passed to all handlers.
114
+ * For DOM events, an existing W3C/jQuery event object can be passed in.
115
+ * @param scope {Object}
116
+ * Optional.
117
+ * The scope ("this") within which the handler will be executed.
118
+ * If not specified, the scope will be set to the <code>Event</code> instance.
119
+ */
120
+ this.notify = function (args, e, scope) {
121
+ e = e || new EventData();
122
+ scope = scope || this;
123
+
124
+ var returnValue;
125
+ for (var i = 0; i < handlers.length && !(e.isPropagationStopped() || e.isImmediatePropagationStopped()); i++) {
126
+ returnValue = handlers[i].call(scope, e, args);
127
+ }
128
+
129
+ return returnValue;
130
+ };
131
+ }
132
+
133
+ function EventHandler() {
134
+ var handlers = [];
135
+
136
+ this.subscribe = function (event, handler) {
137
+ handlers.push({
138
+ event: event,
139
+ handler: handler
140
+ });
141
+ event.subscribe(handler);
142
+
143
+ return this; // allow chaining
144
+ };
145
+
146
+ this.unsubscribe = function (event, handler) {
147
+ var i = handlers.length;
148
+ while (i--) {
149
+ if (handlers[i].event === event &&
150
+ handlers[i].handler === handler) {
151
+ handlers.splice(i, 1);
152
+ event.unsubscribe(handler);
153
+ return;
154
+ }
155
+ }
156
+
157
+ return this; // allow chaining
158
+ };
159
+
160
+ this.unsubscribeAll = function () {
161
+ var i = handlers.length;
162
+ while (i--) {
163
+ handlers[i].event.unsubscribe(handlers[i].handler);
164
+ }
165
+ handlers = [];
166
+
167
+ return this; // allow chaining
168
+ }
169
+ }
170
+
171
+ /***
172
+ * A structure containing a range of cells.
173
+ * @class Range
174
+ * @constructor
175
+ * @param fromRow {Integer} Starting row.
176
+ * @param fromCell {Integer} Starting cell.
177
+ * @param toRow {Integer} Optional. Ending row. Defaults to <code>fromRow</code>.
178
+ * @param toCell {Integer} Optional. Ending cell. Defaults to <code>fromCell</code>.
179
+ */
180
+ function Range(fromRow, fromCell, toRow, toCell) {
181
+ if (toRow === undefined && toCell === undefined) {
182
+ toRow = fromRow;
183
+ toCell = fromCell;
184
+ }
185
+
186
+ /***
187
+ * @property fromRow
188
+ * @type {Integer}
189
+ */
190
+ this.fromRow = Math.min(fromRow, toRow);
191
+
192
+ /***
193
+ * @property fromCell
194
+ * @type {Integer}
195
+ */
196
+ this.fromCell = Math.min(fromCell, toCell);
197
+
198
+ /***
199
+ * @property toRow
200
+ * @type {Integer}
201
+ */
202
+ this.toRow = Math.max(fromRow, toRow);
203
+
204
+ /***
205
+ * @property toCell
206
+ * @type {Integer}
207
+ */
208
+ this.toCell = Math.max(fromCell, toCell);
209
+
210
+ /***
211
+ * Returns whether a range represents a single row.
212
+ * @method isSingleRow
213
+ * @return {Boolean}
214
+ */
215
+ this.isSingleRow = function () {
216
+ return this.fromRow == this.toRow;
217
+ };
218
+
219
+ /***
220
+ * Returns whether a range represents a single cell.
221
+ * @method isSingleCell
222
+ * @return {Boolean}
223
+ */
224
+ this.isSingleCell = function () {
225
+ return this.fromRow == this.toRow && this.fromCell == this.toCell;
226
+ };
227
+
228
+ /***
229
+ * Returns whether a range contains a given cell.
230
+ * @method contains
231
+ * @param row {Integer}
232
+ * @param cell {Integer}
233
+ * @return {Boolean}
234
+ */
235
+ this.contains = function (row, cell) {
236
+ return row >= this.fromRow && row <= this.toRow &&
237
+ cell >= this.fromCell && cell <= this.toCell;
238
+ };
239
+
240
+ /***
241
+ * Returns a readable representation of a range.
242
+ * @method toString
243
+ * @return {String}
244
+ */
245
+ this.toString = function () {
246
+ if (this.isSingleCell()) {
247
+ return "(" + this.fromRow + ":" + this.fromCell + ")";
248
+ }
249
+ else {
250
+ return "(" + this.fromRow + ":" + this.fromCell + " - " + this.toRow + ":" + this.toCell + ")";
251
+ }
252
+ }
253
+ }
254
+
255
+
256
+ /***
257
+ * A base class that all special / non-data rows (like Group and GroupTotals) derive from.
258
+ * @class NonDataItem
259
+ * @constructor
260
+ */
261
+ function NonDataItem() {
262
+ this.__nonDataRow = true;
263
+ }
264
+
265
+
266
+ /***
267
+ * Information about a group of rows.
268
+ * @class Group
269
+ * @extends Slick.NonDataItem
270
+ * @constructor
271
+ */
272
+ function Group() {
273
+ this.__group = true;
274
+
275
+ /**
276
+ * Grouping level, starting with 0.
277
+ * @property level
278
+ * @type {Number}
279
+ */
280
+ this.level = 0;
281
+
282
+ /***
283
+ * Number of rows in the group.
284
+ * @property count
285
+ * @type {Integer}
286
+ */
287
+ this.count = 0;
288
+
289
+ /***
290
+ * Grouping value.
291
+ * @property value
292
+ * @type {Object}
293
+ */
294
+ this.value = null;
295
+
296
+ /***
297
+ * Formatted display value of the group.
298
+ * @property title
299
+ * @type {String}
300
+ */
301
+ this.title = null;
302
+
303
+ /***
304
+ * Whether a group is collapsed.
305
+ * @property collapsed
306
+ * @type {Boolean}
307
+ */
308
+ this.collapsed = false;
309
+
310
+ /***
311
+ * GroupTotals, if any.
312
+ * @property totals
313
+ * @type {GroupTotals}
314
+ */
315
+ this.totals = null;
316
+
317
+ /**
318
+ * Rows that are part of the group.
319
+ * @property rows
320
+ * @type {Array}
321
+ */
322
+ this.rows = [];
323
+
324
+ /**
325
+ * Sub-groups that are part of the group.
326
+ * @property groups
327
+ * @type {Array}
328
+ */
329
+ this.groups = null;
330
+
331
+ /**
332
+ * A unique key used to identify the group. This key can be used in calls to DataView
333
+ * collapseGroup() or expandGroup().
334
+ * @property groupingKey
335
+ * @type {Object}
336
+ */
337
+ this.groupingKey = null;
338
+ }
339
+
340
+ Group.prototype = new NonDataItem();
341
+
342
+ /***
343
+ * Compares two Group instances.
344
+ * @method equals
345
+ * @return {Boolean}
346
+ * @param group {Group} Group instance to compare to.
347
+ */
348
+ Group.prototype.equals = function (group) {
349
+ return this.value === group.value &&
350
+ this.count === group.count &&
351
+ this.collapsed === group.collapsed;
352
+ };
353
+
354
+ /***
355
+ * Information about group totals.
356
+ * An instance of GroupTotals will be created for each totals row and passed to the aggregators
357
+ * so that they can store arbitrary data in it. That data can later be accessed by group totals
358
+ * formatters during the display.
359
+ * @class GroupTotals
360
+ * @extends Slick.NonDataItem
361
+ * @constructor
362
+ */
363
+ function GroupTotals() {
364
+ this.__groupTotals = true;
365
+
366
+ /***
367
+ * Parent Group.
368
+ * @param group
369
+ * @type {Group}
370
+ */
371
+ this.group = null;
372
+ }
373
+
374
+ GroupTotals.prototype = new NonDataItem();
375
+
376
+ /***
377
+ * A locking helper to track the active edit controller and ensure that only a single controller
378
+ * can be active at a time. This prevents a whole class of state and validation synchronization
379
+ * issues. An edit controller (such as SlickGrid) can query if an active edit is in progress
380
+ * and attempt a commit or cancel before proceeding.
381
+ * @class EditorLock
382
+ * @constructor
383
+ */
384
+ function EditorLock() {
385
+ var activeEditController = null;
386
+
387
+ /***
388
+ * Returns true if a specified edit controller is active (has the edit lock).
389
+ * If the parameter is not specified, returns true if any edit controller is active.
390
+ * @method isActive
391
+ * @param editController {EditController}
392
+ * @return {Boolean}
393
+ */
394
+ this.isActive = function (editController) {
395
+ return (editController ? activeEditController === editController : activeEditController !== null);
396
+ };
397
+
398
+ /***
399
+ * Sets the specified edit controller as the active edit controller (acquire edit lock).
400
+ * If another edit controller is already active, and exception will be thrown.
401
+ * @method activate
402
+ * @param editController {EditController} edit controller acquiring the lock
403
+ */
404
+ this.activate = function (editController) {
405
+ if (editController === activeEditController) { // already activated?
406
+ return;
407
+ }
408
+ if (activeEditController !== null) {
409
+ throw "SlickGrid.EditorLock.activate: an editController is still active, can't activate another editController";
410
+ }
411
+ if (!editController.commitCurrentEdit) {
412
+ throw "SlickGrid.EditorLock.activate: editController must implement .commitCurrentEdit()";
413
+ }
414
+ if (!editController.cancelCurrentEdit) {
415
+ throw "SlickGrid.EditorLock.activate: editController must implement .cancelCurrentEdit()";
416
+ }
417
+ activeEditController = editController;
418
+ };
419
+
420
+ /***
421
+ * Unsets the specified edit controller as the active edit controller (release edit lock).
422
+ * If the specified edit controller is not the active one, an exception will be thrown.
423
+ * @method deactivate
424
+ * @param editController {EditController} edit controller releasing the lock
425
+ */
426
+ this.deactivate = function (editController) {
427
+ if (activeEditController !== editController) {
428
+ throw "SlickGrid.EditorLock.deactivate: specified editController is not the currently active one";
429
+ }
430
+ activeEditController = null;
431
+ };
432
+
433
+ /***
434
+ * Attempts to commit the current edit by calling "commitCurrentEdit" method on the active edit
435
+ * controller and returns whether the commit attempt was successful (commit may fail due to validation
436
+ * errors, etc.). Edit controller's "commitCurrentEdit" must return true if the commit has succeeded
437
+ * and false otherwise. If no edit controller is active, returns true.
438
+ * @method commitCurrentEdit
439
+ * @return {Boolean}
440
+ */
441
+ this.commitCurrentEdit = function () {
442
+ return (activeEditController ? activeEditController.commitCurrentEdit() : true);
443
+ };
444
+
445
+ /***
446
+ * Attempts to cancel the current edit by calling "cancelCurrentEdit" method on the active edit
447
+ * controller and returns whether the edit was successfully cancelled. If no edit controller is
448
+ * active, returns true.
449
+ * @method cancelCurrentEdit
450
+ * @return {Boolean}
451
+ */
452
+ this.cancelCurrentEdit = function cancelCurrentEdit() {
453
+ return (activeEditController ? activeEditController.cancelCurrentEdit() : true);
454
+ };
455
+ }
456
+ })(jQuery);
457
+
458
+