/**
* This {@link Ext.grid.Panel grid} plugin adds clipboard support to a grid.
*
* *Note that the grid must use the
* {@link Ext.grid.selection.SpreadsheetModel spreadsheet selection model}
* to utilize this plugin.*
*
* This class supports the following `{@link Ext.plugin.AbstractClipboard#formats formats}`
* for grid data:
*
* * `cell` - Complete field data that can be matched to other grids using the same
* {@link Ext.data.Model model} regardless of column order.
* * `text` - Cell content stripped of HTML tags.
* * `html` - Complete cell content, including any rendered HTML tags.
* * `raw` - Underlying field values based on `dataIndex`.
*
* The `cell` format is not valid for the `{@link Ext.plugin.AbstractClipboard#system system}`
* clipboard format.
*/
Ext.define('Ext.grid.plugin.Clipboard', {
extend: 'Ext.plugin.AbstractClipboard',
alias: 'plugin.clipboard',
requires: [
'Ext.util.Format',
'Ext.util.TSV'
],
formats: {
cell: {
get: 'getCells'
},
html: {
get: 'getCellData'
},
raw: {
get: 'getCellData',
put: 'putCellData'
}
},
gridListeners: {
render: 'onCmpReady'
},
getCellData: function(format, erase) {
var cmp = this.getCmp(),
selection = cmp.getSelectionModel().getSelected(),
ret = [],
isRaw = format === 'raw',
isText = format === 'text',
viewNode,
cell, data, dataIndex, lastRecord, column, record, row, view, isEditable, isEditor;
if (selection) {
selection.eachCell(function(cellContext) {
column = cellContext.column;
view = cellContext.column.getView();
record = cellContext.record;
isEditor = column.getEditor ? column.getEditor() : false;
isEditable = !!isEditor || !column.getReadOnly();
// Do not copy the check column or row numberer column
if (column.ignoreExport) {
return;
}
if (lastRecord !== record) {
lastRecord = record;
ret.push(row = []);
}
dataIndex = column.dataIndex;
if (isRaw) {
data = record.data[dataIndex];
}
else {
// Try to access the view node.
viewNode = view.all.item(cellContext.rowIdx);
// If we could not, it's because it's outside of the rendered block -
// recreate it.
if (!viewNode) {
viewNode = Ext.fly(view.createRowElement(record, cellContext.rowIdx));
}
cell = viewNode.dom.querySelector(column.getCellInnerSelector());
data = cell.innerHTML;
if (isText) {
data = Ext.util.Format.stripTags(data);
}
}
row.push(data);
if (erase && dataIndex && isEditable) {
record.set(dataIndex, null);
}
});
}
// See decode() comment below
return Ext.util.TSV.encode(ret, undefined, null);
},
getCells: function(format, erase) {
var cmp = this.getCmp(),
selection = cmp.getSelectionModel().getSelected(),
ret = [],
dataIndex, lastRecord, record, row;
if (selection) {
selection.eachCell(function(cellContext) {
record = cellContext.record;
if (lastRecord !== record) {
lastRecord = record;
ret.push(row = {
model: record.self,
fields: []
});
}
dataIndex = cellContext.column.dataIndex;
row.fields.push({
name: dataIndex,
value: record.data[dataIndex]
});
if (erase && dataIndex) {
record.set(dataIndex, null);
}
});
}
return ret;
},
getTextData: function(format, erase) {
return this.getCellData(format, erase);
},
putCellData: function(data, format) {
// We pass null as field quote here to override default TSV decoding behavior
// that will try to unquote fields and break if double quote character is
// encountered in the data. TSV format does not support any kind of field quoting
// but Ext.util.TSV mistakenly assumed otherwise pre-6.5.3
var values = Ext.util.TSV.decode(data, undefined, null),
row,
recCount = values.length,
colCount = recCount ? values[0].length : 0,
sourceRowIdx, sourceColIdx,
view = this.getCmp().getView(),
maxRowIdx = view.dataSource.getCount() - 1,
maxColIdx = view.getVisibleColumnManager().getColumns().length - 1,
selModel = view.getSelectionModel(),
selected = selModel.getSelected(),
navModel = view.getNavigationModel(),
destination = selected.startCell || navModel.getPosition(),
dataIndex, destinationStartColumn,
dataObject = {},
isEditable, isEditor, destinationColumn;
// If the view is not focused, use the first cell of the selection as the destination.
if (!destination && selected) {
selected.eachCell(function(c) {
destination = c;
return false;
});
}
if (destination) {
// Create a new Context based upon the outermost View.
// NavigationModel works on local views.
// TODO: remove this step when NavModel is fixed to use outermost view in locked grid.
// At that point, we can use navModel.getPosition()
destination = new Ext.grid.CellContext(view).setPosition(
destination.record, destination.column
);
}
else {
destination = new Ext.grid.CellContext(view).setPosition(0, 0);
}
destinationStartColumn = destination.colIdx;
for (sourceRowIdx = 0; sourceRowIdx < recCount; sourceRowIdx++) {
row = values[sourceRowIdx];
// Collect new values in dataObject
for (sourceColIdx = 0; sourceColIdx < colCount; sourceColIdx++) {
destinationColumn = destination.column;
dataIndex = destinationColumn.dataIndex;
isEditor = destinationColumn.getEditor ? destinationColumn.getEditor() : false;
isEditable = !!isEditor || !destinationColumn.getReadOnly();
if (dataIndex && isEditable) {
switch (format) {
// Raw field values
case 'raw':
dataObject[dataIndex] = row[sourceColIdx];
break;
// Textual data with HTML tags stripped
case 'text':
dataObject[dataIndex] = row[sourceColIdx];
break;
// innerHTML from the cell inner
case 'html':
break;
}
}
// If we are at the end of the destination row, break the column loop.
if (destination.colIdx === maxColIdx) {
break;
}
destination.setColumn(destination.colIdx + 1);
}
// Update the record in one go.
destination.record.set(dataObject);
// If we are at the end of the destination store, break the row loop.
if (destination.rowIdx === maxRowIdx) {
break;
}
// Jump to next row in destination
destination.setPosition(destination.rowIdx + 1, destinationStartColumn);
}
},
putTextData: function(data, format) {
this.putCellData(data, format);
},
getTarget: function(comp) {
return comp.body;
},
privates: {
validateAction: function(event) {
var view = this.getCmp().getView();
if (view.actionableMode) {
return false;
}
}
}
});