UNPKG

17.3 kBJavaScriptView Raw
1(function ($) {
2 // register namespace
3 $.extend(true, window, {
4 "Slick": {
5 "CellExternalCopyManager": CellExternalCopyManager
6 }
7 });
8
9
10 function CellExternalCopyManager(options) {
11 /*
12 This manager enables users to copy/paste data from/to an external Spreadsheet application
13 such as MS-Excel® or OpenOffice-Spreadsheet.
14
15 Since it is not possible to access directly the clipboard in javascript, the plugin uses
16 a trick to do it's job. After detecting the keystroke, we dynamically create a textarea
17 where the browser copies/pastes the serialized data.
18
19 options:
20 copiedCellStyle : sets the css className used for copied cells. default : "copied"
21 copiedCellStyleLayerKey : sets the layer key for setting css values of copied cells. default : "copy-manager"
22 dataItemColumnValueExtractor : option to specify a custom column value extractor function
23 dataItemColumnValueSetter : option to specify a custom column value setter function
24 clipboardCommandHandler : option to specify a custom handler for paste actions
25 includeHeaderWhenCopying : set to true and the plugin will take the name property from each column (which is usually what appears in your header) and put that as the first row of the text that's copied to the clipboard
26 bodyElement: option to specify a custom DOM element which to will be added the hidden textbox. It's useful if the grid is inside a modal dialog.
27 onCopyInit: optional handler to run when copy action initializes
28 onCopySuccess: optional handler to run when copy action is complete
29 newRowCreator: function to add rows to table if paste overflows bottom of table, if this function is not provided new rows will be ignored.
30 readOnlyMode: suppresses paste
31 headerColumnValueExtractor : option to specify a custom column header value extractor function
32 */
33 var _grid;
34 var _self = this;
35 var _copiedRanges;
36 var _options = options || {};
37 var _copiedCellStyleLayerKey = _options.copiedCellStyleLayerKey || "copy-manager";
38 var _copiedCellStyle = _options.copiedCellStyle || "copied";
39 var _clearCopyTI = 0;
40 var _bodyElement = _options.bodyElement || document.body;
41 var _onCopyInit = _options.onCopyInit || null;
42 var _onCopySuccess = _options.onCopySuccess || null;
43
44 var keyCodes = {
45 'C': 67,
46 'V': 86,
47 'ESC': 27,
48 'INSERT': 45
49 };
50
51 function init(grid) {
52 _grid = grid;
53 _grid.onKeyDown.subscribe(handleKeyDown);
54
55 // we need a cell selection model
56 var cellSelectionModel = grid.getSelectionModel();
57 if (!cellSelectionModel){
58 throw new Error("Selection model is mandatory for this plugin. Please set a selection model on the grid before adding this plugin: grid.setSelectionModel(new Slick.CellSelectionModel())");
59 }
60 // we give focus on the grid when a selection is done on it.
61 // without this, if the user selects a range of cell without giving focus on a particular cell, the grid doesn't get the focus and key stroke handles (ctrl+c) don't work
62 cellSelectionModel.onSelectedRangesChanged.subscribe(function(e, args){
63 _grid.focus();
64 });
65 }
66
67 function destroy() {
68 _grid.onKeyDown.unsubscribe(handleKeyDown);
69 }
70
71 function getHeaderValueForColumn(columnDef) {
72 if (_options.headerColumnValueExtractor) {
73 var val = _options.headerColumnValueExtractor(columnDef);
74
75 if (val) { return val; }
76 }
77
78 return columnDef.name;
79 }
80
81 function getDataItemValueForColumn(item, columnDef, e) {
82 if (_options.dataItemColumnValueExtractor) {
83 var val = _options.dataItemColumnValueExtractor(item, columnDef);
84
85 if (val) { return val; }
86 }
87
88 var retVal = '';
89
90 // if a custom getter is not defined, we call serializeValue of the editor to serialize
91 if (columnDef.editor){
92 var editorArgs = {
93 'container':$("<p>"), // a dummy container
94 'column':columnDef,
95 'position':{'top':0, 'left':0}, // a dummy position required by some editors
96 'grid':_grid,
97 'event':e
98 };
99 var editor = new columnDef.editor(editorArgs);
100 editor.loadValue(item);
101 retVal = editor.serializeValue();
102 editor.destroy();
103 } else {
104 retVal = item[columnDef.field];
105 }
106
107 return retVal;
108 }
109
110 function setDataItemValueForColumn(item, columnDef, value) {
111 if (columnDef.denyPaste) { return null; }
112
113 if (_options.dataItemColumnValueSetter) {
114 return _options.dataItemColumnValueSetter(item, columnDef, value);
115 }
116
117 // if a custom setter is not defined, we call applyValue of the editor to unserialize
118 if (columnDef.editor){
119 var editorArgs = {
120 'container':$("body"), // a dummy container
121 'column':columnDef,
122 'position':{'top':0, 'left':0}, // a dummy position required by some editors
123 'grid':_grid
124 };
125 var editor = new columnDef.editor(editorArgs);
126 editor.loadValue(item);
127 editor.applyValue(item, value);
128 editor.destroy();
129 } else {
130 item[columnDef.field] = value;
131 }
132 }
133
134
135 function _createTextBox(innerText){
136 var ta = document.createElement('textarea');
137 ta.style.position = 'absolute';
138 ta.style.left = '-1000px';
139 ta.style.top = document.body.scrollTop + 'px';
140 ta.value = innerText;
141 _bodyElement.appendChild(ta);
142 ta.select();
143
144 return ta;
145 }
146
147 function _decodeTabularData(_grid, ta){
148 var columns = _grid.getColumns();
149 var clipText = ta.value;
150 var clipRows = clipText.split(/[\n\f\r]/);
151 // trim trailing CR if present
152 if (clipRows[clipRows.length - 1]==="") { clipRows.pop(); }
153
154 var clippedRange = [];
155 var j = 0;
156
157 _bodyElement.removeChild(ta);
158 for (var i=0; i<clipRows.length; i++) {
159 if (clipRows[i]!=="")
160 clippedRange[j++] = clipRows[i].split("\t");
161 else
162 clippedRange[j++] = [""];
163 }
164 var selectedCell = _grid.getActiveCell();
165 var ranges = _grid.getSelectionModel().getSelectedRanges();
166 var selectedRange = ranges && ranges.length ? ranges[0] : null; // pick only one selection
167 var activeRow = null;
168 var activeCell = null;
169
170 if (selectedRange){
171 activeRow = selectedRange.fromRow;
172 activeCell = selectedRange.fromCell;
173 } else if (selectedCell){
174 activeRow = selectedCell.row;
175 activeCell = selectedCell.cell;
176 } else {
177 // we don't know where to paste
178 return;
179 }
180
181 var oneCellToMultiple = false;
182 var destH = clippedRange.length;
183 var destW = clippedRange.length ? clippedRange[0].length : 0;
184 if (clippedRange.length == 1 && clippedRange[0].length == 1 && selectedRange){
185 oneCellToMultiple = true;
186 destH = selectedRange.toRow - selectedRange.fromRow +1;
187 destW = selectedRange.toCell - selectedRange.fromCell +1;
188 }
189 var availableRows = _grid.getData().length - activeRow;
190 var addRows = 0;
191
192 // ignore new rows if we don't have a "newRowCreator"
193 if(availableRows < destH && _options.newRowCreator)
194 {
195 var d = _grid.getData();
196 for(addRows = 1; addRows <= destH - availableRows; addRows++)
197 d.push({});
198 _grid.setData(d);
199 _grid.render();
200 }
201
202 var overflowsBottomOfGrid = activeRow + destH > _grid.getDataLength();
203
204 if (_options.newRowCreator && overflowsBottomOfGrid) {
205
206 var newRowsNeeded = activeRow + destH - _grid.getDataLength();
207
208 _options.newRowCreator(newRowsNeeded);
209
210 }
211
212 var clipCommand = {
213
214 isClipboardCommand: true,
215 clippedRange: clippedRange,
216 oldValues: [],
217 cellExternalCopyManager: _self,
218 _options: _options,
219 setDataItemValueForColumn: setDataItemValueForColumn,
220 markCopySelection: markCopySelection,
221 oneCellToMultiple: oneCellToMultiple,
222 activeRow: activeRow,
223 activeCell: activeCell,
224 destH: destH,
225 destW: destW,
226 maxDestY: _grid.getDataLength(),
227 maxDestX: _grid.getColumns().length,
228 h: 0,
229 w: 0,
230
231 execute: function() {
232 this.h=0;
233 for (var y = 0; y < this.destH; y++){
234 this.oldValues[y] = [];
235 this.w=0;
236 this.h++;
237 for (var x = 0; x < this.destW; x++){
238 this.w++;
239 var desty = activeRow + y;
240 var destx = activeCell + x;
241
242 if (desty < this.maxDestY && destx < this.maxDestX ) {
243 var nd = _grid.getCellNode(desty, destx);
244 var dt = _grid.getDataItem(desty);
245 this.oldValues[y][x] = dt[columns[destx]['field']];
246 if (oneCellToMultiple)
247 this.setDataItemValueForColumn(dt, columns[destx], clippedRange[0][0]);
248 else
249 this.setDataItemValueForColumn(dt, columns[destx], clippedRange[y] ? clippedRange[y][x] : '');
250 _grid.updateCell(desty, destx);
251 _grid.onCellChange.notify({
252 row: desty,
253 cell: destx,
254 item: dt,
255 grid: _grid
256 });
257
258 }
259 }
260 }
261
262 var bRange = {
263 'fromCell': activeCell,
264 'fromRow': activeRow,
265 'toCell': activeCell+this.w-1,
266 'toRow': activeRow+this.h-1
267 };
268
269 this.markCopySelection([bRange]);
270 _grid.getSelectionModel().setSelectedRanges([bRange]);
271 this.cellExternalCopyManager.onPasteCells.notify({ranges: [bRange]});
272 },
273
274 undo: function() {
275 for (var y = 0; y < this.destH; y++){
276 for (var x = 0; x < this.destW; x++){
277 var desty = activeRow + y;
278 var destx = activeCell + x;
279
280 if (desty < this.maxDestY && destx < this.maxDestX ) {
281 var nd = _grid.getCellNode(desty, destx);
282 var dt = _grid.getDataItem(desty);
283 if (oneCellToMultiple)
284 this.setDataItemValueForColumn(dt, columns[destx], this.oldValues[0][0]);
285 else
286 this.setDataItemValueForColumn(dt, columns[destx], this.oldValues[y][x]);
287 _grid.updateCell(desty, destx);
288 _grid.onCellChange.notify({
289 row: desty,
290 cell: destx,
291 item: dt,
292 grid: _grid
293 });
294 }
295 }
296 }
297
298 var bRange = {
299 'fromCell': activeCell,
300 'fromRow': activeRow,
301 'toCell': activeCell+this.w-1,
302 'toRow': activeRow+this.h-1
303 };
304
305 this.markCopySelection([bRange]);
306 _grid.getSelectionModel().setSelectedRanges([bRange]);
307 this.cellExternalCopyManager.onPasteCells.notify({ranges: [bRange]});
308
309 if(addRows > 1){
310 var d = _grid.getData();
311 for(; addRows > 1; addRows--)
312 d.splice(d.length - 1, 1);
313 _grid.setData(d);
314 _grid.render();
315 }
316 }
317 };
318
319 if(_options.clipboardCommandHandler) {
320 _options.clipboardCommandHandler(clipCommand);
321 }
322 else {
323 clipCommand.execute();
324 }
325 }
326
327
328 function handleKeyDown(e, args) {
329 var ranges;
330 if (!_grid.getEditorLock().isActive() || _grid.getOptions().autoEdit) {
331 if (e.which == keyCodes.ESC) {
332 if (_copiedRanges) {
333 e.preventDefault();
334 clearCopySelection();
335 _self.onCopyCancelled.notify({ranges: _copiedRanges});
336 _copiedRanges = null;
337 }
338 }
339
340 if ((e.which === keyCodes.C || e.which === keyCodes.INSERT) && (e.ctrlKey || e.metaKey) && !e.shiftKey) { // CTRL+C or CTRL+INS
341 if (_onCopyInit) {
342 _onCopyInit.call();
343 }
344 ranges = _grid.getSelectionModel().getSelectedRanges();
345 if (ranges.length !== 0) {
346 _copiedRanges = ranges;
347 markCopySelection(ranges);
348 _self.onCopyCells.notify({ranges: ranges});
349
350 var columns = _grid.getColumns();
351 var clipText = "";
352
353 for (var rg = 0; rg < ranges.length; rg++){
354 var range = ranges[rg];
355 var clipTextRows = [];
356 for (var i=range.fromRow; i< range.toRow+1 ; i++){
357 var clipTextCells = [];
358 var dt = _grid.getDataItem(i);
359
360 if (clipTextRows.length === 0 && _options.includeHeaderWhenCopying) {
361 var clipTextHeaders = [];
362 for (var j = range.fromCell; j < range.toCell + 1 ; j++) {
363 if (columns[j].name.length > 0)
364 clipTextHeaders.push(getHeaderValueForColumn(columns[j]));
365 }
366 clipTextRows.push(clipTextHeaders.join("\t"));
367 }
368
369 for (var j=range.fromCell; j< range.toCell+1 ; j++){
370 clipTextCells.push(getDataItemValueForColumn(dt, columns[j], e));
371 }
372 clipTextRows.push(clipTextCells.join("\t"));
373 }
374 clipText += clipTextRows.join("\r\n") + "\r\n";
375 }
376
377 if(window.clipboardData) {
378 window.clipboardData.setData("Text", clipText);
379 return true;
380 }
381 else {
382 var focusEl = document.activeElement;
383
384 var ta = _createTextBox(clipText);
385
386 ta.focus();
387
388 setTimeout(function(){
389 _bodyElement.removeChild(ta);
390 // restore focus
391 if (focusEl)
392 focusEl.focus();
393 else
394 console.log("Not element to restore focus to after copy?");
395
396 }, 100);
397
398 if (_onCopySuccess) {
399 var rowCount = 0;
400 // If it's cell selection, use the toRow/fromRow fields
401 if (ranges.length === 1) {
402 rowCount = (ranges[0].toRow + 1) - ranges[0].fromRow;
403 }
404 else {
405 rowCount = ranges.length;
406 }
407 _onCopySuccess.call(this, rowCount);
408 }
409
410 return false;
411 }
412 }
413 }
414
415 if (!_options.readOnlyMode && (
416 (e.which === keyCodes.V && (e.ctrlKey || e.metaKey) && !e.shiftKey)
417 || (e.which === keyCodes.INSERT && e.shiftKey && !e.ctrlKey)
418 )) { // CTRL+V or Shift+INS
419 var ta = _createTextBox('');
420
421 setTimeout(function(){
422 _decodeTabularData(_grid, ta);
423 }, 100);
424
425 return false;
426 }
427 }
428 }
429
430 function markCopySelection(ranges) {
431 clearCopySelection();
432
433 var columns = _grid.getColumns();
434 var hash = {};
435 for (var i = 0; i < ranges.length; i++) {
436 for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
437 hash[j] = {};
438 for (var k = ranges[i].fromCell; k <= ranges[i].toCell && k<columns.length; k++) {
439 hash[j][columns[k].id] = _copiedCellStyle;
440 }
441 }
442 }
443 _grid.setCellCssStyles(_copiedCellStyleLayerKey, hash);
444 clearTimeout(_clearCopyTI);
445 _clearCopyTI = setTimeout(function(){
446 _self.clearCopySelection();
447 }, 2000);
448 }
449
450 function clearCopySelection() {
451 _grid.removeCellCssStyles(_copiedCellStyleLayerKey);
452 }
453
454 function setIncludeHeaderWhenCopying(includeHeaderWhenCopying) {
455 _options.includeHeaderWhenCopying = includeHeaderWhenCopying;
456 }
457
458 $.extend(this, {
459 "init": init,
460 "destroy": destroy,
461 "pluginName": "CellExternalCopyManager",
462
463 "clearCopySelection": clearCopySelection,
464 "handleKeyDown":handleKeyDown,
465
466 "onCopyCells": new Slick.Event(),
467 "onCopyCancelled": new Slick.Event(),
468 "onPasteCells": new Slick.Event(),
469 "setIncludeHeaderWhenCopying" : setIncludeHeaderWhenCopying
470 });
471 }
472})(jQuery);
\No newline at end of file