smartkiosk-server 0.12.1 → 0.13

Sign up to get free protection for your applications and to get access to all the features.
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
+