rails_slickgrid 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,294 @@
1
+ /***
2
+ * A simple observer pattern implementation.
3
+ */
4
+ function EventHelper() {
5
+ this.handlers = [];
6
+
7
+ this.subscribe = function(fn) {
8
+ this.handlers.push(fn);
9
+ };
10
+
11
+ this.notify = function(args) {
12
+ for (var i = 0; i < this.handlers.length; i++) {
13
+ this.handlers[i].call(this, args);
14
+ }
15
+ };
16
+
17
+ return this;
18
+ }
19
+
20
+
21
+ (function($) {
22
+ /***
23
+ * A sample Model implementation.
24
+ * Provides a filtered view of the underlying data.
25
+ *
26
+ * Relies on the data item having an "id" property uniquely identifying it.
27
+ */
28
+ function DataView() {
29
+ var self = this;
30
+
31
+ // private
32
+ var idProperty = "id"; // property holding a unique row id
33
+ var items = []; // data by index
34
+ var rows = []; // data by row
35
+ var idxById = {}; // indexes by id
36
+ var rowsById = null; // rows by id; lazy-calculated
37
+ var filter = null; // filter function
38
+ var updated = null; // updated item ids
39
+ var suspend = false; // suspends the recalculation
40
+ var sortAsc = true;
41
+ var sortComparer = null;
42
+ var fastSortField = null;
43
+
44
+ var pagesize = 0;
45
+ var pagenum = 0;
46
+ var totalRows = 0;
47
+
48
+ // events
49
+ var onRowCountChanged = new EventHelper();
50
+ var onRowsChanged = new EventHelper();
51
+ var onPagingInfoChanged = new EventHelper();
52
+
53
+
54
+ function beginUpdate() {
55
+ suspend = true;
56
+ }
57
+
58
+ function endUpdate() {
59
+ suspend = false;
60
+ refresh();
61
+ }
62
+
63
+ function refreshIdxById() {
64
+ idxById = {};
65
+ for (var i = 0,l = items.length; i < l; i++) {
66
+ var id = items[i][idProperty];
67
+ if (id == undefined || idxById[id] != undefined)
68
+ throw "Each data element must implement a unique 'id' property";
69
+ idxById[id] = i;
70
+ }
71
+ }
72
+
73
+ function getItems() {
74
+ return items;
75
+ }
76
+
77
+ function setItems(data, objectIdProperty) {
78
+ if (objectIdProperty !== undefined) idProperty = objectIdProperty;
79
+ items = data;
80
+ refreshIdxById();
81
+ refresh();
82
+ }
83
+
84
+ function setPagingOptions(args) {
85
+ if (args.pageSize != undefined)
86
+ pagesize = args.pageSize;
87
+
88
+ if (args.pageNum != undefined)
89
+ pagenum = Math.min(args.pageNum, Math.ceil(totalRows / pagesize));
90
+
91
+ onPagingInfoChanged.notify(getPagingInfo());
92
+
93
+ refresh();
94
+ }
95
+
96
+ function getPagingInfo() {
97
+ return {pageSize:pagesize, pageNum:pagenum, totalRows:totalRows};
98
+ }
99
+
100
+ function sort(comparer, ascending) {
101
+ sortAsc = ascending;
102
+ sortComparer = comparer;
103
+ fastSortField = null;
104
+ if (ascending === false) items.reverse();
105
+ items.sort(comparer);
106
+ if (ascending === false) items.reverse();
107
+ refreshIdxById();
108
+ refresh();
109
+ }
110
+
111
+ /***
112
+ * Provides a workaround for the extremely slow sorting in IE.
113
+ * Does a [lexicographic] sort on a give column by temporarily overriding Object.prototype.toString
114
+ * to return the value of that field and then doing a native Array.sort().
115
+ */
116
+ function fastSort(field, ascending) {
117
+ sortAsc = ascending;
118
+ fastSortField = field;
119
+ sortComparer = null;
120
+ var oldToString = Object.prototype.toString;
121
+ Object.prototype.toString = (typeof field == "function")?field:function() { return this[field] };
122
+ // an extra reversal for descending sort keeps the sort stable
123
+ // (assuming a stable native sort implementation, which isn't true in some cases)
124
+ if (ascending === false) items.reverse();
125
+ items.sort();
126
+ Object.prototype.toString = oldToString;
127
+ if (ascending === false) items.reverse();
128
+ refreshIdxById();
129
+ refresh();
130
+ }
131
+
132
+ function reSort() {
133
+ if (sortComparer)
134
+ sort(sortComparer,sortAsc);
135
+ else if (fastSortField)
136
+ fastSort(fastSortField,sortAsc);
137
+ }
138
+
139
+ function setFilter(filterFn) {
140
+ filter = filterFn;
141
+ refresh();
142
+ }
143
+
144
+ function getItemByIdx(i) {
145
+ return items[i];
146
+ }
147
+
148
+ function getIdxById(id) {
149
+ return idxById[id];
150
+ }
151
+
152
+ // calculate the lookup table on first call
153
+ function getRowById(id) {
154
+ if (!rowsById) {
155
+ rowsById = {};
156
+ for (var i = 0, l = rows.length; i < l; ++i) {
157
+ rowsById[rows[i][idProperty]] = i;
158
+ }
159
+ }
160
+
161
+ return rowsById[id];
162
+ }
163
+
164
+ function getItemById(id) {
165
+ return items[idxById[id]];
166
+ }
167
+
168
+ function updateItem(id, item) {
169
+ if (idxById[id] === undefined || id !== item[idProperty])
170
+ throw "Invalid or non-matching id";
171
+ items[idxById[id]] = item;
172
+ if (!updated) updated = {};
173
+ updated[id] = true;
174
+ refresh();
175
+ }
176
+
177
+ function insertItem(insertBefore, item) {
178
+ items.splice(insertBefore, 0, item);
179
+ refreshIdxById(); // TODO: optimize
180
+ refresh();
181
+ }
182
+
183
+ function addItem(item) {
184
+ items.push(item);
185
+ refreshIdxById(); // TODO: optimize
186
+ refresh();
187
+ }
188
+
189
+ function deleteItem(id) {
190
+ if (idxById[id] === undefined)
191
+ throw "Invalid id";
192
+ items.splice(idxById[id], 1);
193
+ refreshIdxById(); // TODO: optimize
194
+ refresh();
195
+ }
196
+
197
+ function recalc(_items, _rows, _filter, _updated) {
198
+ var diff = [];
199
+ var items = _items, rows = _rows, filter = _filter, updated = _updated; // cache as local vars
200
+
201
+ rowsById = null;
202
+
203
+ // go over all items remapping them to rows on the fly
204
+ // while keeping track of the differences and updating indexes
205
+ var rl = rows.length;
206
+ var currentRowIndex = 0;
207
+ var currentPageIndex = 0;
208
+ var item,id;
209
+
210
+ for (var i = 0, il = items.length; i < il; ++i) {
211
+ item = items[i];
212
+
213
+ if (!filter || filter(item)) {
214
+ id = item[idProperty];
215
+
216
+ if (!pagesize || (currentRowIndex >= pagesize * pagenum && currentRowIndex < pagesize * (pagenum + 1))) {
217
+ if (currentPageIndex >= rl || id != rows[currentPageIndex][idProperty] || (updated && updated[id])) {
218
+ diff[diff.length] = currentPageIndex;
219
+ rows[currentPageIndex] = item;
220
+ }
221
+
222
+ currentPageIndex++;
223
+ }
224
+
225
+ currentRowIndex++;
226
+ }
227
+ }
228
+
229
+ if (rl > currentPageIndex)
230
+ rows.splice(currentPageIndex, rl - currentPageIndex);
231
+
232
+ totalRows = currentRowIndex;
233
+
234
+ return diff;
235
+ }
236
+
237
+ function refresh() {
238
+ if (suspend) return;
239
+
240
+ var countBefore = rows.length;
241
+ var totalRowsBefore = totalRows;
242
+
243
+ var diff = recalc(items, rows, filter, updated); // pass as direct refs to avoid closure perf hit
244
+
245
+ // if the current page is no longer valid, go to last page and recalc
246
+ // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized
247
+ if (pagesize && totalRows < pagenum * pagesize) {
248
+ pagenum = Math.floor(totalRows / pagesize);
249
+ diff = recalc(items, rows, filter, updated);
250
+ }
251
+
252
+ updated = null;
253
+
254
+ if (totalRowsBefore != totalRows) onPagingInfoChanged.notify(getPagingInfo());
255
+ if (countBefore != rows.length) onRowCountChanged.notify({previous:countBefore, current:rows.length});
256
+ if (diff.length > 0) onRowsChanged.notify(diff);
257
+ }
258
+
259
+
260
+ return {
261
+ // properties
262
+ "rows": rows, // note: neither the array or the data in it should be modified directly
263
+
264
+ // methods
265
+ "beginUpdate": beginUpdate,
266
+ "endUpdate": endUpdate,
267
+ "setPagingOptions": setPagingOptions,
268
+ "getPagingInfo": getPagingInfo,
269
+ "getItems": getItems,
270
+ "setItems": setItems,
271
+ "setFilter": setFilter,
272
+ "sort": sort,
273
+ "fastSort": fastSort,
274
+ "reSort": reSort,
275
+ "getIdxById": getIdxById,
276
+ "getRowById": getRowById,
277
+ "getItemById": getItemById,
278
+ "getItemByIdx": getItemByIdx,
279
+ "refresh": refresh,
280
+ "updateItem": updateItem,
281
+ "insertItem": insertItem,
282
+ "addItem": addItem,
283
+ "deleteItem": deleteItem,
284
+
285
+ // events
286
+ "onRowCountChanged": onRowCountChanged,
287
+ "onRowsChanged": onRowsChanged,
288
+ "onPagingInfoChanged": onPagingInfoChanged
289
+ };
290
+ }
291
+
292
+ // Slick.Data.DataView
293
+ $.extend(true, window, { Slick: { Data: { DataView: DataView }}});
294
+ })(jQuery);
@@ -0,0 +1,146 @@
1
+ (function($) {
2
+ function SlickGridPager(dataView, grid, $container)
3
+ {
4
+ var $status, $contextMenu;
5
+
6
+ function init()
7
+ {
8
+ dataView.onPagingInfoChanged.subscribe(function(pagingInfo) {
9
+ updatePager(pagingInfo);
10
+ });
11
+
12
+ constructPagerUI();
13
+ updatePager(dataView.getPagingInfo());
14
+ }
15
+
16
+ function getNavState()
17
+ {
18
+ var cannotLeaveEditMode = !Slick.GlobalEditorLock.commitCurrentEdit();
19
+ var pagingInfo = dataView.getPagingInfo();
20
+ var lastPage = Math.floor(pagingInfo.totalRows/pagingInfo.pageSize);
21
+
22
+ return {
23
+ canGotoFirst: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum > 0,
24
+ canGotoLast: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum != lastPage,
25
+ canGotoPrev: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum > 0,
26
+ canGotoNext: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum < lastPage,
27
+ pagingInfo: pagingInfo,
28
+ lastPage: lastPage
29
+ }
30
+ }
31
+
32
+ function setPageSize(n)
33
+ {
34
+ dataView.setPagingOptions({pageSize:n});
35
+ }
36
+
37
+ function gotoFirst()
38
+ {
39
+ if (getNavState().canGotoFirst)
40
+ dataView.setPagingOptions({pageNum: 0});
41
+ }
42
+
43
+ function gotoLast()
44
+ {
45
+ var state = getNavState();
46
+ if (state.canGotoLast)
47
+ dataView.setPagingOptions({pageNum: state.lastPage});
48
+ }
49
+
50
+ function gotoPrev()
51
+ {
52
+ var state = getNavState();
53
+ if (state.canGotoPrev)
54
+ dataView.setPagingOptions({pageNum: state.pagingInfo.pageNum-1});
55
+ }
56
+
57
+ function gotoNext()
58
+ {
59
+ var state = getNavState();
60
+ if (state.canGotoNext)
61
+ dataView.setPagingOptions({pageNum: state.pagingInfo.pageNum+1});
62
+ }
63
+
64
+ function constructPagerUI()
65
+ {
66
+ $container.empty();
67
+
68
+ $status = $("<span class='slick-pager-status' />").appendTo($container);
69
+
70
+ var $nav = $("<span class='slick-pager-nav' />").appendTo($container);
71
+ var $settings = $("<span class='slick-pager-settings' />").appendTo($container);
72
+
73
+ $settings
74
+ .append("<span class='slick-pager-settings-expanded' style='display:none'>Show: <a data=0>All</a><a data='-1'>Auto</a><a data=25>25</a><a data=50>50</a><a data=100>100</a></span>");
75
+
76
+ $settings.find("a[data]").click(function(e) {
77
+ var pagesize = $(e.target).attr("data");
78
+ if (pagesize != undefined)
79
+ {
80
+ if (pagesize == -1)
81
+ {
82
+ var vp = grid.getViewport();
83
+ setPageSize(vp.bottom-vp.top);
84
+ }
85
+ else
86
+ setPageSize(parseInt(pagesize));
87
+ }
88
+ });
89
+
90
+ var icon_prefix = "<span class='ui-state-default ui-corner-all ui-icon-container'><span class='ui-icon ";
91
+ var icon_suffix = "' /></span>";
92
+
93
+ $(icon_prefix + "ui-icon-lightbulb" + icon_suffix)
94
+ .click(function() { $(".slick-pager-settings-expanded").toggle() })
95
+ .appendTo($settings);
96
+
97
+ $(icon_prefix + "ui-icon-seek-first" + icon_suffix)
98
+ .click(gotoFirst)
99
+ .appendTo($nav);
100
+
101
+ $(icon_prefix + "ui-icon-seek-prev" + icon_suffix)
102
+ .click(gotoPrev)
103
+ .appendTo($nav);
104
+
105
+ $(icon_prefix + "ui-icon-seek-next" + icon_suffix)
106
+ .click(gotoNext)
107
+ .appendTo($nav);
108
+
109
+ $(icon_prefix + "ui-icon-seek-end" + icon_suffix)
110
+ .click(gotoLast)
111
+ .appendTo($nav);
112
+
113
+ $container.find(".ui-icon-container")
114
+ .hover(function() {
115
+ $(this).toggleClass("ui-state-hover");
116
+ });
117
+
118
+ $container.children().wrapAll("<div class='slick-pager' />");
119
+ }
120
+
121
+
122
+ function updatePager(pagingInfo)
123
+ {
124
+ var state = getNavState();
125
+
126
+ $container.find(".slick-pager-nav span").removeClass("ui-state-disabled");
127
+ if (!state.canGotoFirst) $container.find(".ui-icon-seek-first").addClass("ui-state-disabled");
128
+ if (!state.canGotoLast) $container.find(".ui-icon-seek-end").addClass("ui-state-disabled");
129
+ if (!state.canGotoNext) $container.find(".ui-icon-seek-next").addClass("ui-state-disabled");
130
+ if (!state.canGotoPrev) $container.find(".ui-icon-seek-prev").addClass("ui-state-disabled");
131
+
132
+
133
+ if (pagingInfo.pageSize == 0)
134
+ $status.text("Showing all " + pagingInfo.totalRows + " rows");
135
+ else
136
+ $status.text("Showing page " + (pagingInfo.pageNum+1) + " of " + (Math.floor(pagingInfo.totalRows/pagingInfo.pageSize)+1));
137
+ }
138
+
139
+
140
+
141
+ init();
142
+ }
143
+
144
+ // Slick.Controls.Pager
145
+ $.extend(true, window, { Slick: { Controls: { Pager: SlickGridPager }}});
146
+ })(jQuery);
@@ -0,0 +1,30 @@
1
+ .slick-columnpicker {
2
+ border: 1px solid #718BB7;
3
+ background: #f0f0f0;
4
+ padding: 6px;
5
+ -moz-box-shadow: 2px 2px 2px silver;
6
+ -webkit-box-shadow: 2px 2px 2px silver;
7
+ min-width: 100px;
8
+ cursor: default;
9
+ }
10
+
11
+ .slick-columnpicker li {
12
+ list-style: none;
13
+ margin: 0;
14
+ padding: 0;
15
+ background: none;
16
+ }
17
+
18
+ .slick-columnpicker input {
19
+ margin: 4px;
20
+ }
21
+
22
+ .slick-columnpicker li a {
23
+ display: block;
24
+ padding: 4px;
25
+ font-weight: bold;
26
+ }
27
+
28
+ .slick-columnpicker li a:hover {
29
+ background: white;
30
+ }