rails_slickgrid 0.0.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.
- data/MIT-LICENSE +20 -0
- data/README.md +24 -0
- data/Rakefile +58 -0
- data/lib/generators/rails_slickgrid/install/install_generator.rb +17 -0
- data/lib/generators/rails_slickgrid/install/javascripts/slick.columnpicker.js +105 -0
- data/lib/generators/rails_slickgrid/install/javascripts/slick.editors.js +608 -0
- data/lib/generators/rails_slickgrid/install/javascripts/slick.grid.js +2504 -0
- data/lib/generators/rails_slickgrid/install/javascripts/slick.model.js +294 -0
- data/lib/generators/rails_slickgrid/install/javascripts/slick.pager.js +146 -0
- data/lib/generators/rails_slickgrid/install/stylesheets/slick.columnpicker.css +30 -0
- data/lib/generators/rails_slickgrid/install/stylesheets/slick.grid.css +153 -0
- data/lib/generators/rails_slickgrid/install/stylesheets/slick.pager.css +48 -0
- data/lib/rails_slickgrid.rb +7 -0
- data/lib/rails_slickgrid/action_view.rb +22 -0
- data/lib/rails_slickgrid/railtie.rb +11 -0
- data/lib/rails_slickgrid/utils.rb +37 -0
- data/lib/rails_slickgrid/version.rb +3 -0
- metadata +83 -0
@@ -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
|
+
}
|