PDA

View Full Version : Nativ Ext.Clipboard Cut / Copy / Paste Plugin (ie, safari, ff)



crp_spaeth
2 Apr 2009, 4:30 AM
Hi there after the idea stucks in my head for a while now I just started to develop it for extjs.

One most used features in applications are cut copy and paste. And after apple implemented it for the Iphone i ask my self why dont do that for Ext too.

"because you do not have acces to the Clipboard" many of you will shout.
Of cause not. But there is a way to use it anyhow!!!

You would like to see it in action first? Here is a little demo:

http://www.crp.de/Shared/extjs/examples/copyPasteExample/copyPasteExample.html
(http://www.crp.de/Shared/extjs/examples/copyPasteExample/copyPasteExample.html)

After you have seen the demo you may whant to know how its been done :)




I just pasted from Documentation.

Represents the base Plugin for adding Cut/Copy/Paste capabilities to a component.


The plugin Bases on a tricky little Bypass trick.
The plugin adds two hidden native HTMLElements to the body.
A Textarea for bypassing to clipboard. And a Iframe with editmode=On for bypassing from the Clipboard.

After a Component that instanciates this Plugin gets renderd the plugin attaches the
key events of ctrl+x ctrl+c ctrl+v two its own functions.

Since the browser fires the javascript keyevent before it does the native cutting, copieing and pasting stuff
we can use the time before the browser in example coping a text into the nativ System
Clipboard to selecting the text we want to have in the Clipboard.

This little hack makes the whole plugin possible.

Lets Clarify by explaining what the copyHandler, attached to the ctrl+c does.

First of all it saves a few states
- what was the caret State (the selection range)
- what was the marked Text (would normaly into the clipboard)

Than it asks the plugins client to build the copyIdentifier (this is what actualy does it way into the clipboard)
Than the the Magic Happens:
-The Value you returnd in the buildCopyIdentifierString function becomes the value of the Textarea attached to this component
-The Textarea gets its focus und all the text inside gets selected.
-Wait for about 10 Milliseconds and the Browser will have done the rest for you
-Woallla the value you just created via the buildCopyIdentifierString is now in the
native System Clipboard.
And the better thing about that: u exactly knwo about that string.
After all that, the copyToClipboard function with the IdentifierString gets called.

The cut Functionionalety is quiet similiar.

The Paste Function works a bit different.
First of all it saves a few states
- what was the caret State (the selection range)

Next Step is to focus the Iframe and set its innerHTML to '';
no we just wait for about 10 Milliseconds to let the browser paste the value from the
clipboard into the Iframe.
Now we call the plugins client pasteFromClipboard function under the scope of the client with the string from the iframes innerHTML
If this function retunrns undfined we simulate the default behavior.



If you got any questions just ask. Maybe you could try the demo and send some feedback + ?bugs?.

In the next Post I will place in the whole readable code for the plugin used in the Example

crp_spaeth
2 Apr 2009, 4:41 AM
First of all there is a litte Plugin i wrote that once it is instantiated, it registers the the component in a Singleton called Ext.ComponentFocusMgr automatically when it gets focused (indicated by contextmenu or click event).




Ext.namespace('Ext', 'Ext.plugins');

/**
* @class Ext.ComponentFocusMgr
*
* A global Manager that stores the information about the last focused Components.
* Since there is no bubling in the Ext events yet Focus just bases on the on Clickevent
* and on the oncontextmenu. To make your component focusable just use the Focus detector Plugin
*
* @author Martin Späth
* @date April 2, 2009
* @singleton
*/
Ext.ComponentFocusMgr = (function(){
var focusChain = new Ext.util.MixedCollection(false, function(item){return item.id});
var currentTarget = null;

return {

/**
* adds the passed cmpId to the focusChain if the currentTarget
* is the same as the eventTarget otherwise it will clear the list
* and add this cmpId to the list.
*
* @param {eventTarget} indicator for a new focus Chain. If the eventTarget change the focus Chain gets resetet
* @param {Ext.Component} the Component that been Focused
*/
addToFocusList: function(eventTarget, cmp){
if(currentTarget != eventTarget) {
focusChain.clear();
currentTarget = eventTarget;
}

if(!focusChain.contains(cmp)){
focusChain.add(cmp.id, cmp);
}
},

/**
* Gets the first element of the Focus Chain
* @returns {Ext.Component} The Top Element of the Focus Chain
*/
getTopFocus: function(){
return focusChain.itemAt(0);
},

/**
* Get the whole Focus Chain 0 ist the uppest Element
* @retunrs {Ext.util.MixedCollection} a mixed Collection with all the Elements of that have focus in it.
*/
getFocusChain: function(){
return focusChain;
}

}
})();


Ext.plugins.FocusDetector = function(config) {
Ext.apply(this, config);
};

/**
* A small Plugin that handles focusregistration for the component
* that implements this plugin
* @class Ext.plugins.FocusDetector
* @extends Ext.util.Observable
*/
Ext.extend(Ext.plugins.FocusDetector, Ext.util.Observable, {

init:function(client) {
this.client = client;
if(this.initFocus){
Ext.ComponentFocusMgr.addToFocusList('',this.client);
}

client.on({
'render': this.render,
scope: this
});

client.on({
'destroy': this.destroy,
scope: this
});
}, // end of function init // end of function init

/**
* gets called wenn this plugins child is rendered
*/
render: function() {
this.client.el.on('click', this.clickHandler, this);
this.client.on('contextmenu',this.clickHandler , this);
},



/**
* gets called wenn this plugins child been destroyed
*/
destroy: function(){
this.client.el.un('click', this.clickHandler);
this.client.un('contextmenu', this.clickHandler);
},


clickHandler: function(ev){
Ext.ComponentFocusMgr.addToFocusList( ev.target, this.client);
}
}); // end of extend

// end of file


overwrite the core Ext.Element to include a standardized "caret/Selection" Functionality:


// adds caret Functionalety to an Ext.Element
Ext.override(Ext.Element, {
_iCaretNative : undefined,
_isSupported : undefined,
/**
* @private
*/
isCaretNative: function(){
if(typeof this._isCaretNative == 'undefined') {
var d = document, o = d.createElement("input");
this._isCaretNative = "selectionStart" in o;
this._isCaretSupported = this._isCaretNative || (o = d.selection) && !!o.createRange;

}
return this._isCaretNative
},

/**
* @private
*/
isSupported: function(){
if(typeof this._isCaretSupported == 'undefined') {
this.isCaretNative();
}
return this._isCaretSupported
},

/**
* selects the text of the Element
* @param {Mixed} start Start position of the selection
* @param {Number} end End position of the selection
* @return {}
*/
setCaret: function(start, end){

if(!(this.dom.nodeName.toLowerCase() == "textarea" || this.dom.nodeName.toLowerCase() == "input")){
return;
}
if(typeof start.start != 'undefined') {
end = start.end;
start = start.start;
}
if(this.isCaretNative()) {
return this.dom.setSelectionRange(start, end)
} else {
var o = this.dom;
var t = o.createTextRange();
end -= start + o.value.slice(start + 1, end).split("\n").length - 1;
start -= o.value.slice(0, start).split("\n").length - 1;
t.move("character", start), t.moveEnd("character", end), t.select();
}
},

/**
* returns the current Selection of the Element
* @return {Object} {start: startposition, end: endposition} of the selection
*/
getCaret: function(){

var o = this.dom;
if(this.isCaretNative()) {
return { start: o.selectionStart, end: o.selectionEnd }
} else {
var s = (this.dom.focus(), document.selection.createRange()), r, start, end, value;
// the current selection range is not part of the controll asking for the caret...
if(s.parentElement() != o)
return {start: 0, end: 0};

var isTA = o.nodeName.toLowerCase() == "textarea";
if(isTA ? (r = s.duplicate()).moveToElementText(o) : r = o.createTextRange(), !isTA)
return r.setEndPoint("EndToStart", s), {start: r.text.length, end: r.text.length + s.text.length};
for(var $ = "[###]"; (value = o.value).indexOf($) + 1; $ += $);
r.setEndPoint("StartToEnd", s), r.text = $ + r.text, end = o.value.indexOf($);
s.text = $, start = o.value.indexOf($);
if(document.execCommand && document.queryCommandSupported("Undo"))
for(r in {0:0, 0:0})
document.execCommand("Undo");
return o.value = value, this.setCaret(start, end), {start: start, end: end};
}
},
/**
* Returns the caret Plus the Line, Column nummer and the position Number
* @return {}
*/
getCaretPosition : function(){

if(!(this.dom.nodeName.toLowerCase() == "textarea" || this.dom.nodeName.toLowerCase() == "input")){
return;
}
var c = this.getCaret(), o=this.dom;
if(c){
var lines;// = (typeof o.value != 'undefined') ? o.value.split("\n") : o.innerText.split("\n");
if(typeof o.value != 'undefined') {
lines = o.value.split("\n");
} else {
lines = o.innerText.split("\n");
}
c.lines = lines.length;
var p=c.start,ll=c.lines,len=0,line=0;
for(;line<ll;line++) {
len = lines[line].length + 1;
if(p<len)break;
p -= len;
}
line++;

c.line=line;
c.column = p;
c.position = c.start;

}
return c;
},
/**
* sets the CaretPosition
* @param {Number} start the beginnig of the selection
* @param {Number} end the end of the selection
*/
setCaretPosition : function(start, end) {
// FIX start.start ?
if(typeof start.start != 'undefined') {
end = start.end
start = start.start;
}
if(typeof start != 'undefined' ) {
this.setCaret(start,end || start);
}
},
/**
* gets the current selected text
* @return {}
*/
getCaretText : function(){
var o = this.getCaret();
return (typeof this.dom.value != 'undefined') ? this.dom.value.slice(o.start, o.end):'';
},
/**
* overrides the currently selected Text with an other text
* @param {String} text
*/
setCaretText : function(text){
var o = this.getCaret(), i = this.dom, s = i.value;
if(typeof s == 'undefined') {
return;
}
i.value = s.slice(0, o.start) + text + s.slice(o.end);
this.setCaret(o.start += text.length, o.start);
}

});


The nex Code represent a Clipboard Singletone that holds the data and representing string


/**
* @class ClipBoard
*
* A global Clipboard implementation that stores the Clipboard information.
* Since there is no bubling in the Ext events yet Focus just bases on the on Clickevent
* and on the oncontextmenu. To make your component focusable just use the Focus detector Plugin
*
* @author Martin Späth
* @date April 2, 2009
* @singleton
*/
Ext.ClipBoard = (function(){
var lastInExtCopiedString = false;
var dataObject = null;

return {
/**
* Sets the date of the Clipboard Object
* @param {Object} dataObj the Object to store into the Clipboard
* @param {String} identifierString (optional) the string which currently been stored into the System-Clipboard
*/
setData: function(dataObj, identifierString){
if(typeof identifieSring != 'undefined') {
lastInExtCopiedString = identifieString;
}
dataObject = dataObj;
},
/**
* Gets the data stored into the Clipboard
* @param {String} stringFromClipboard (optional) the String that represents the
* content of the current System-Clipboard.
* @returns {Object} if stringFromClipboard is undifined or it matches to the lastInExtCopiedString it returns the data Object.
* Otherwise it'll return the stringFromClipboard parameter.
*/
getData: function(stringFromClipboard){

if(typeof identifieSring == 'undefined' || lastInExtCopiedString == identifieSring) {
return dataObject;
}
return stringFromClipboard;
}
}

Last but not Least there is the Code where the "Magic" happens :)

The Base Plugin Ext.plugins.CopyPasteable


/**
* Ext.plugins.CopyPasteable plugin
*
* @author Martin Späth
* @date April 2, 2009
*
* @class Ext.plugins.CopyPasteable
* @extends Ext.util.Observable
*/
Ext.plugins.CopyPasteable = function(config) {
Ext.apply(this, config);
};


/**
* Represents the base Plugin for adding Cut/Copy/Paste capabilities to a component.
*
* The plugin Bases on a tricky little Bypass trick.
* The plugin adds two hidden native HTMLElements to the body.
* A Textarea for bypassing to clipboard. And a Iframe with editmode=On for bypassing from the Clipboard.
*
* After a Component that instanciates this Plugin gets renderd the plugin attaches the
* key events of ctrl+x ctrl+c ctrl+v two its own functions.
*
* Since the browser fires the javascript keyevent before it does the native cutting, copieing and pasting stuff
* we can use the time before the browser in example coping a text into the nativ System
* Clipboard to selecting the text we want to have in the Clipboard.
*
* This little hack makes the whole plugin possible.
*
* Lets Clarify by explaining what the copyHandler, attached to the ctrl+c does.
*
* First of all it saves a few states
* - what was the caret State (the selection range)
* - what was the marked Text (would normaly into the clipboard)
*
* Than it asks the plugins client to build the copyIdentifier (this is what actualy does it way into the clipboard)
* Than the the Magic Happens:
* -The Value you returnd in the buildCopyIdentifierString function becomes the value of the Textarea attached to this component
* -The Textarea gets its focus und all the text inside gets selected.
* -Wait for about 10 Milliseconds and the Browser will have done the rest for you
* -Woallla the value you just created via the buildCopyIdentifierString is now in the
* native System Clipboard.
* And the better thing about that: u exactly knwo about that string.
* After all that, the copyToClipboard function with the IdentifierString gets called.
*
* The cut Functionionalety is quiet similiar.
*
* The Paste Function works a bit different.
* First of all it saves a few states
* - what was the caret State (the selection range)
*
* Next Step is to focus the Iframe and set its innerHTML to '';
* no we just wait for about 10 Milliseconds to let the browser paste the value from the
* clipboard into the Iframe.
* Now we call the plugins client pasteFromClipboard function under the scope of the client with the string from the iframes innerHTML
* If this function retunrns undfined we simulate the default behavior.
*
*
* @author Martin Späth
* @date April 2, 2009
* @class Ext.plugins.CopyPasteable
* @extends Ext.util.Observable
*/
Ext.extend(Ext.plugins.CopyPasteable, Ext.util.Observable, {
init:function(client) {
this.client = client;

this.client.copyToClipboard = this.copyToClipboard;


this.client.pasteFromClipboard = this.pasteFromClipboard;

this.client.cutToClipboard = this.cutToClipboard;

// add the events to the client
this.client.addEvents(
/**
* @event beforeCopy
* Fires before the data gets filled into the clipboard
* @param {Ext.Component} this
*/
'beforeCopy',
/**
* @event copy
* Fires after something from this component has been added to the Ext.ClipBoard
* @param {String} the accessor used to connect the System Clibboard with the Ext Clipboard
*/
'copy',
/**
* @event beforePaste
* Fires before something gets pasted (return false when the standart browse Behavior)
*/
'beforePaste',
/**
* @event paste
* Fires something when should get pasted
*/
'paste'
);

client.on({
'render': this.render,
scope: this
});

client.on({
'destroy': this.destroy,
scope: this
});
}, // end of function init

/**
* gets called when the copy bypass was successfull Use this method for storing information in the
* Ext.Clipboard
* @param {string} identifierString (optional) Sting that links the clipboardData with the string in the system clipboard
*/
copyToClipboard: function(identifierString) {
Ext.ClipBoard.setData(identifierString, identifierString);
},

/**
* gets called when the cut bypass was successfull Use this method for storing information in the
* Ext.Clipboard
* @param {string} identifierString (optional) Sting that links the clipboardData with the string in the system clipboard
*/
cutToClipboard: function(identifierString){
Ext.ClipBoard.setData(identifierString, identifierString);
},

/**
* gets called when the paste bypass was successfull
* @param {string} orginalString the Parameter that was pasted from the System-Clipboard. You can use this information to get Data form the Ext.Clipboard
* @return {String} the string with which the normal browser behavior should get simulated
*/
pasteFromClipboard: function(orginalString){
return orginalString;
},

/**
* builds the IdentifierString that gets copied into the System clipboard
* you can override this with any string generating function you want.
* @param {string} orginalValue the orginal selected text/content (to develop)
* @return {string} the value that will get copied
*/
buildCopyIdentifierString: function(orginalValue){
// i didn't find a way to say what will make its way into the clipboad if i dont bypass it for all cases.
// so by default i just fill it with a uniq key.
if(typeof orginalValue == 'undefined' || orginalValue =='') {
return Ext.id();
}
return orginalValue;
},

/**
* builds the IdentifierString that gets cutted into the System clipboard
* you can override this with any string generating function you want.
* @param {string} orginalValue the orginal selected text/content (to develop)
* @return {string} the value that will get cutted
*/
buildCutIdentifierString: function(orginalValue){
// i didn't find a way to say what will make its way into the clipboad if i dont bypass it for all cases.
// so by default i just fill it with a uniq key.
if(typeof orginalValue == 'undefined' || orginalValue =='') {
return Ext.id();
}
return orginalValue;
},

elementSuportsPaste: function(el){
return el.nodeName && (el.nodeName.toLowerCase() == 'input' || el.nodeName.toLowerCase() == 'textarea');
},

/**
* gets called when the user presses ctrl+c
*/
cutHandler: function (key, event){

var el = event.target;
//var sh = false;

//if(this.elementSuportsPaste(el)){
// sh = new Ext.util.SelectionHelper(el);
//}

//Ext.fly(el).setCaretPosition(1,2);

// save the caretState for later restore

var caretState = Ext.fly(el).getCaret();
var orginalCuttedText = Ext.fly(el).getCaretText();

if(this.client.fireEvent("beforeCopy", this.client, event, key) !== false){
// fix
var value = (this.buildCutIdentifierString.createDelegate(this.client))(orginalCuttedText);
if(value !== false) {
this.bypassToClipboard(value);
(function(el){
el.focus();


Ext.fly(el).setCaretPosition(caretState);
}).defer(10, this, [el]);
} else {
// if the bypass wasn't used, save the copied text into the lastExtCopyString for later purpose
value = Ext.fly(el).getCaretText();
}

// now call the components copyToClipboard Function with the identifier value just created
(this.cutToClipboard.createDelegate(this.client))(value);
}
return true;
},


/**
* gets called when the user presses ctrl+c
*/
copyHandler: function (key, event){

var el = event.target;

// save the caretState for later restore
var caretState = Ext.fly(el).getCaret();
var orginalCopyText = Ext.fly(el).getCaretText();

if(this.client.fireEvent("beforeCopy", this.client, event, key) !== false){
// fix
var value = (this.buildCopyIdentifierString.createDelegate(this.client))(orginalCopyText);
if(value !== false) {
this.bypassToClipboard(value);
(function(el){
el.focus();


var elfly = Ext.fly(el);
elfly.setCaretPosition(caretState);
// and do what the browser would do if you wouldn't have bypass
elfly.setCaretText();
}).defer(10, this, [el]);
} else {
// if the bypass wasn't used, save the copied text into the lastExtCopyString for later purpose
value = Ext.fly(el).getCaretText();
}

// now call the components copyToClipboard Function with the identifier value just created
(this.copyToClipboard.createDelegate(this.client))(value);
}
return true;
},


/**
* Bypasses the string in dataToByPass to the Clipboard
* @param {String} dataToByPass the string that should get stored into the System Clipboard
*/
bypassToClipboard: function(dataToByPass){
// bypass over the textarea
this.bypassTextarea.value = dataToByPass;
this.bypassTextarea.focus();
this.bypassTextarea.select();
},


/**
* gets called from the keyhandler when ctrl+v was pressed in the scope of the clients events
*/
pasteHandler: function(key, event){
el = event.target;

// save the current caretState for later restore
var caretState = Ext.fly(el).getCaretPosition();
var bp = this.bypassIframe;

// clear the bypassIframe and focus it to paste in to it.
if (bp.contentWindow) {//ns6 syntax
this.getIframeBody().innerHTML = '';
bp.contentWindow.focus();
}
else if (bp.Document) {//ie5+ syntax
this.getIframeBody().innerHTML = '';

bp.contentDocument.focus();
}

// give the browser about 10ms to paste the stuff into the bypass area
(function(el){
// And Wholla we know what is in the Clipboard
// call the paste manipulation function
var pastVal = (this.pasteFromClipboard.createDelegate(this.client))(this.getIframeBody().innerHTML);
// by returning nothing we let the plugin prevent to simulate the normal browser behavior
if(typeof pastVailue != 'undefined'){

// re-set the caret of the field to the state before the bypass
el.focus();

var elfly = Ext.fly(el);
elfly.setCaretPosition(caretState);
// and do what the browser would do if you wouldn't have bypass
elfly.setCaretText(pastVal);

// reselect the text just pasted
elfly.setCaretPosition(caretState.start+pastVal.length, caretState.start+pastVal.length);
}

}).defer(10, this, [el]);


// TODO fire paste event with the value
//alert(pastVal);

},

render: function(){
this.wrapper = Ext.DomHelper.append(this.client.el, {
tag: 'div',
children: [{
tag: 'textarea'
}, {
tag: 'iframe',
name: Ext.id(),

frameBorder: '1',
src: Ext.isIE ? Ext.SSL_SECURE_URL : "javascript:;"

}
],
//'class': 'x-hidden',
style: 'height:0; width:0; overflow:hidden'
});

//this.wrapper.appendChild(iframe);

this.bypassIframe = this.wrapper.childNodes[1];

this.initFrame();
this.bypassTextarea = this.wrapper.firstChild;

// map the keystrokes ctrl+c and ctrl+v to its hanlders

// FIX doesn't get fired if a normal text in the Panel was selected
// before presing ctrl+(v|c)
new Ext.KeyMap(this.client.el, [{
key: 'c',
ctrl: true,
fn: this.copyHandler,
scope: this
},{
key: 'v',
ctrl: true,
fn: this.pasteHandler,
scope: this
},{
key: 'x',
ctrl: true,
fn: this.cutHandler,
scope: this
}]);

},

/**
* returns the body element of the bypass Iframe
* @return {Element}
* @private
*/
getIframeBody: function() {
this.doc = this.getDoc();
return this.doc.body || this.doc.documentElement;

},

// private
getDoc : function(){
return Ext.isIE ? this.getWin().document : (this.bypassIframe.contentDocument || this.getWin().document);
},

// private
getWin : function(){
return Ext.isIE ? this.bypassIframe.contentWindow : window.frames[this.bypassIframe.name];
},

/**
* initializes the iframe (sets designMode=on and so..)
*/
initFrame : function(){
this.doc = this.getDoc();
this.win = this.getWin();

this.doc.open();
this.doc.write('');
this.doc.close();

var task = { // must defer to wait for browser to be ready
run : function(){
if(this.doc.body || this.doc.readyState == 'complete'){
Ext.TaskMgr.stop(task);
this.doc.designMode="on";
//this.initEditor.defer(10, this);
}
},
interval : 10,
duration:10000,
scope: this
};
Ext.TaskMgr.start(task);
},

destroy: function(){

if(this.wrapper) {
this.wrapper.remove();
}

}
}); // end of extend

// end of file


For the demo I extend from Ext.plugins.CopyPasteable and I have just overwritten the methods for my needs



Ext.plugins.CopyPasteableGrid = function(config) {
Ext.apply(this, config);
};

/**
* Example Subclass of the CopyPasteable Plugin. Created for the Grid.
*
* it overwrites the buildCopyIdentifier to create a string that gets interpreted well by Excel
* @class Ext.plugins.CopyPasteableGrid
* @extends Ext.plugins.CopyPasteable
*/
Ext.extend(Ext.plugins.CopyPasteableGrid, Ext.plugins.CopyPasteable, {


/**
* Overwritten to create a string that gets well interpreted by Excel
* @param {string} orgVal the Value that would go its way into the Clipboard if you wouldnt bypass
* @return {string} the Value that should get bypassed into the Clipboard
*/
buildCopyIdentifierString: function (orgVal) {

// just format the output for excel
var records = this.getSelectionModel().getSelections();

returnstring = "";
for(var i = 0; i < records.length; i++){
var currRec = records[i];

for(var prop in currRec.data) {
returnstring = returnstring + currRec.data[prop] + '\t';
}


returnstring = returnstring + '\n';
}
//alert(returnstring);
return returnstring;
},

/**
* Overwritten to create a string that gets well interpreted by Excel
* @param {string} orgVal the Value that would go its way into the Clipboard if you wouldnt bypass
* @return {string} the Value that should get bypassed into the Clipboard
*/
buildCutIdentifierString: function (orgVal) {

// just format the output for excel
var records = this.getSelectionModel().getSelections();

returnstring = "";
for(var i = 0; i < records.length; i++){
var currRec = records[i];

for(var prop in currRec.data) {
returnstring = returnstring + currRec.data[prop] + '\t';
}


returnstring = returnstring + '\n';
}
//alert(returnstring);
return returnstring;
},


/**
* Overwritten to save a copy of the current selected Records into the Clipboard and remove those records from the grid
* @param {string} identifierString
*/
cutToClipboard: function(identifierString){
/** @type Ext.grid.GridPanel **/
var cl = this;
var records = cl.getSelectionModel().getSelections();
var clipBoardObject = [];

//returnstring = "";
for(var i = 0; i < records.length; i++){
var currRec = records[i];

clipBoardObject[clipBoardObject.length] = currRec.copy();
cl.getStore().remove(currRec);
}

// store the created object in the clipboard
Ext.ClipBoard.setData(clipBoardObject, identifierString);

},

/**
* Overwritten to save a copy of the current selected Records into the Clipboard
* @param {string} identifierString
*/
copyToClipboard: function(identifierString){
/** @type Ext.grid.GridPanel **/
var cl = this;
var records = cl.getSelectionModel().getSelections();
var clipBoardObject = [];

//returnstring = "";
for(var i = 0; i < records.length; i++){
var currRec = records[i];

clipBoardObject[clipBoardObject.length] = currRec.copy();

}

// store the created object in the clipboard
Ext.ClipBoard.setData(clipBoardObject, identifierString);

},

/**
* Overwritten to get the data from the Clipboard and insert all the records found
* into the Grid
* @param {string} identifierString
*/
pasteFromClipboard: function(identifierString){
var clipboardObject = Ext.ClipBoard.getData(identifierString);

/** @type Ext.grid.GridPanel **/
var cl = this;
cl.getSelectionModel().clearSelections();
for(var i = 0; i<clipboardObject.length; i++){


/** @type Ext.data.Store **/
var cs = this.getStore();

/** @type Ext.data.Record */
var rec = clipboardObject[i].copy(Ext.id());

cs.insert(0,rec);
}
cl.getSelectionModel().clearSelections();

cl.getSelectionModel().selectRecords(clipboardObject,false);
}


}); // end of extend

// end of file

crp_spaeth
4 Apr 2009, 1:38 AM
Come on ext'perts tell me your experience

ajaxvador
4 Apr 2009, 4:42 AM
very nice

TopKatz
4 Apr 2009, 8:07 AM
This is neat.

A suggestion would be to make the paste 'insert' the record where you select in the destination grid.

mxracer
4 Apr 2009, 8:26 AM
I tried it in FF 3 and IE 8 and worked nice.

I did notice that in FF:
1. I selected a row in grid A
2. Clicked ctrl+c
3. Now to paste into grid B ( by clicking ctrl+v ), I had to select a row first.

In IE, I just had to click to focus grid B ( did not select a row ) and it still pasted fine.

Bug Noticed:

I copied from grid A by clicking ctrl+c then pasted in Notepad on my desktop... which works nice.
I then refreshed the demo page and tried to copy from Notepad to grid B.

This caused a error in Firebug for the copyPaste.js file on line # 746



clipboardObject is null
pasteFromClipboard()("American Express Company&nbsp;&nbsp; &nbsp;52.55&nbsp;&nbsp; &nbsp;0.01&nbsp;&nbsp; &nbsp;0.02&nbsp;&nbsp; &nbsp;Tue Sep 01 2009 00:00:00 GMT-0400 (Eastern Daylight Time)")copyPaste.js (line 746)
getViewWidth()()ext-base.js (line 9)
(?)()(td.x-grid3-col)copyPaste.js (line 494)
getViewWidth()()ext-base.js (line 9)
[Break on this error] for(var i = 0; i<clipboardObject.length; i++){

mxracer
4 Apr 2009, 8:41 AM
Are we free to use this code?

crp_spaeth
6 Apr 2009, 12:23 AM
of cause feel free to use it :)

DigitalSkyline
6 Apr 2009, 9:20 AM
Cool... could this also work with the HTML editor?

crp_spaeth
6 Apr 2009, 12:33 PM
Sorry for the delay...

I did not start to write real extensions of the plugin jet. I just implemented the base functionality and a proprietary example for the grid to let you guys see what can be done with this little trick.


@mxracer: I know about this little issue in Firefox but this is a known Extjs - Bug see: Focus Bug (http://extjs.com/forum/showthread.php?t=63661)

I will give the Bug you came up with a try tomorrow.

@TopKatz: Nice Idea but notice, the extension is just proprietary and written as easy as possible to give you the ability to write your own extension of my plugin for other components like the tree or dataview and so one. But btw. I would really love to see your Idea implemented! So give it a try!! :)


@DigitalSkyline: Since the HTMLEditor bases on another iframe you will need to overwrite the bypass to work on an iframe but I dont think it is that hard.

Since i been using an iframe to let the browser paste stuff in you could use the existing iframe from the HTMLEditor and you could compare the innerHTML before the browser finishes the native ctrl+v event and after it...

@all: By the way did one of you try to paste into MS *duck* Excel after you copied a few rows via ctrl+c ?

best regards

bernd01
13 Apr 2009, 2:21 PM
Hi srt_spaeth,

Thanks for the great plugin! Works like a charm.

I guess, I might have found a typo in the class Ext.ClipBoard:

setData: function(dataObj, identifierString){

if(typeof identifieSring != 'undefined') {

lastInExtCopiedString = identifieString;

}

dataObject = dataObj;

},

I guess identifieSring should be identifierString

Thanks,
Bernd

crp_spaeth
13 Apr 2009, 10:58 PM
Hi bernd01,
Thank you for your attention and reporting this typo.

The bad news is that fixing this typo will break the whole mechannisem.
Since i am using a Textareas value property to bypass the content to the Clipboard but using an Iframes innerHTML property with designmod = on to bypass content from the Clipboard back to the Ext Plugin the copied and the pasted value differ even if the should be the same in a few cases.

This is happening cause the Browser will parse the sting you try to insert into the Iframe as a HTML. So if you try to past a text with a break in it "testvalue1 testvalue2 \n testvalue3 testvalue4\n", like i do in the Grid example you will end up with something like this:
"testvalue1 testvalue2<br> testvalue3 testvalue4<br>"

So if you compare those two strings in the getData function you will end up getting back the pasted string and not the dataObject. Cause of the typo you just found bernd2 and the other typo in the getData function you will end up getting the last copied Ext.Dataobject even if the user copies a text from another Application.

Maybe some of you got an idea how to solve this problem. Is there a property or method i dont know yet to get a javascript string from a dom element?

The other solution would be to use a Textarea to bypass from the clipboard but this means a few other thinks I was thinking about to implement will not be possible anymore since you cant Paste the same stuff into a Textarea like into an Iframe...

bernd01
14 Apr 2009, 10:44 AM
Hi!

Thanks for your reply. To be honest, I think in general I understand how your use the browser trick, however in detail I am not really sure...

I corrected the typo in my code and it is still working for my example (the grid). Not sure what is different...

crp_spaeth
14 Apr 2009, 1:02 PM
Did you change the getData methode too?

I think I will reimplement the Bypass for the Paste process with an Textarea to avoid the parse Problem I came up with.

bernd01
14 Apr 2009, 1:34 PM
Yes, I have overwritten that too - still working.

However, I faced another javascript error. I could eliminate that by overwriting your Ext.Element.setCaretText function like this:

/*
* override: setCaretText, because 'text' is not given in:
* Ext.plugins.CopyPasteable:copyHandler
* elfly.setCaretText();
*/
Ext.override(Ext.Element, {
/**
* overrides the currently selected Text with an other text
* @param {String} text
*/
setCaretText : function(text){
var o = this.getCaret(), i = this.dom, s = i.value;
if(typeof s == 'undefined') {
return;
}
if (text)
{
i.value = s.slice(0, o.start) + text + s.slice(o.end);
this.setCaret(o.start += text.length, o.start);
}
else
{
i.value = s.slice(0, o.start) + s.slice(o.end);
this.setCaret(o.start += o.start);
}
}

});I am not sure if anybody is interested in the next code part. It allows you to enable/disable the keymap with a toogle button. In that way the user can choose between using your cool plugin or the standard copy/paste behaviour (unfortunatly I cannot find the forum post anymore for that).
Code for enable/disable keymap:

Ext.override(Ext.plugins.CopyPasteableGrid, {
plugin_name: 'GridCopyPasteClipboard',
is_keymap_enabled: true,
copy_paste_keymap: false,

/*
* overwrite in order to by default disable keymap
*/
render: function(){
this.wrapper = Ext.DomHelper.append(this.client.el, {
tag: 'div',
children: [{
tag: 'textarea'
}, {
tag: 'iframe',
name: Ext.id(),

frameBorder: '1',
src: Ext.isIE ? Ext.SSL_SECURE_URL : "javascript:;"

}
],
//'class': 'x-hidden',
style: 'height:0; width:0; overflow:hidden'
});

//this.wrapper.appendChild(iframe);

this.bypassIframe = this.wrapper.childNodes[1];

this.initFrame();
this.bypassTextarea = this.wrapper.firstChild;

// map the keystrokes ctrl+c and ctrl+v to its hanlders

// FIX doesn't get fired if a normal text in the Panel was selected
// before presing ctrl+(v|c)
this.copy_paste_keymap = new Ext.KeyMap(this.client.el, [{
key: 'c',
ctrl: true,
fn: this.copyHandler,
scope: this
},{
key: 'v',
ctrl: true,
fn: this.pasteHandler,
scope: this
},{
key: 'x',
ctrl: true,
fn: this.cutHandler,
scope: this
}]);

//disable keymap by default
this.disable();
},

/*
* enables the keymap
*/
enable: function(){
this.copy_paste_keymap.enable();
},

/*
* disables the keymap
*/
disable: function(){
this.copy_paste_keymap.disable();
}
});Code for the standard copy/paste behaviour (which forum post I unfortunately don't find anymore):

.x-selectable, .x-selectable * {
-moz-user-select: text!important;
-khtml-user-select: text!important;
}

if (!Ext.grid.GridView.prototype.templates) {
Ext.grid.GridView.prototype.templates = {};
}
Ext.grid.GridView.prototype.templates.cell = new Ext.Template(
'<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} x-selectable {css}" style="{style}" tabIndex="0" {cellAttr}>',
'<div class="x-grid3-cell-inner x-grid3-col-{id}" {attr}>{value}</div>',
'</td>'
);

Cheers,
Bernd

blackhorn
8 Jul 2009, 4:23 AM
Hi,
Congralutions! Nice Plugin, very usefull; but there is a probleme with ExtJS 3.0.0
All works just only one thing don't want work!
When I copy there is data and a string representation
With CTRL+C no problem
but with a action and a copyToClipboard there is data but not a string representation?
Can you solve it? or give us a way to solve it?
Thanks
Have a nice day

crp_spaeth
8 Jul 2009, 4:42 AM
You ask for the possibilty to have a string representation in the nativ system clipboard by pressing a button in Extjs?


My Plugin uses a neat workaround to deal with this by bypassing data into the clipboard as descriped in the first post. The Bypass uses the Browsers Nativ Copy Command which gets Fired if the User press CTRL+C.

I know there is a way to write Data in the System-Clipboard using Javascript in the Internetexplorerer, but I think there is no way to access it in Firefox and Safari.

There is another missing thing which is not supported yet. The Ext.Clipboard will not work if the User uses the Internet-Explorer Menu Funktion Edit-->Copy.

I think I have an Idea to deal with that as well but therefore Ill need an Focusable Panel as descriped in the Following thread.

If you would like to see a better Implementation of this Plugin you may "+1" my Featurerequest in this thread...

http://extjs.com/forum/showthread.php?t=73555

blackhorn
8 Jul 2009, 5:15 AM
Thanks for your quick reply.
There is another (proper) way to make a copy system like yours works?
I saw many page with a swf file but i can make it works!
Thanks !

hellogavin
25 Sep 2009, 7:09 PM
I cannot see the demo right now!

notjoshing
10 Dec 2009, 1:21 PM
For some applications, I want to send specific text to the clipboard without the user needing to press keys. For instance, clicking on a "get link" control. I'm not sure how I'd do something like that using your model; it seems to need an element to drive off of to get the text. Am I missing something?

Thanks,

Josh

crp_spaeth
10 Dec 2009, 2:54 PM
Since my solution bypasses the data into the clipboard its necessary that the user presses ctrl+c to get the data into the clipboard....

snehap.1284
22 Apr 2010, 11:40 PM
Hi crp_spaeth,

I am new to extjs. Infact this is my first post in extjs forum :)

I want to develop two things in an existing tree grid of our product:
1. Single Cell Selection instead of rowselection. Copy it using Ctrl + C,
2. Multi select cells in grid, Paste the copied value using Ctrl + V

I wanted to know whether this is possible and how.

Any links or examples on this will be appreciated :)

Thanks in advance. Awaiting response

aagocs
4 Nov 2010, 8:18 AM
Does anyone know if this plugin would work with Extjs 3.x -- and if so - where to get it?
thanks in advance

crp_spaeth
4 Nov 2010, 8:55 AM
Hey aagocs,

for shure you could implement it for Extjs 3.0. I would love to do so but the time I was able to spent so much time in such things has long gone...

You can read the first entry of this thread and since its well documented you should be able implement a simmiliar plugin for Extjs 3 without much pain.

Just a small advice: wait for the new Extjs4.0 before you got to do things twice ;)

Best regards Martin

p.s. I you need help with the new implementation feel free to ask!

Surinder singh
17 Dec 2010, 11:04 PM
hi, here is another option
[/URL][url]http://www.developerextensions.com (http://joomlanewextentions.co.cc/index.php?option=com_demo&view=demo&layout=ext&extention=Ext-CopyPasteGrid_1.4&Itemid=53)

try it

Joyfulbob
18 Mar 2013, 12:36 PM
When I run it I get this error:



TypeError: p.init is not a function

Not sure how to plug it in to my test grid.

Her's my code:


var grid = new Ext.grid.GridPanel( {
store: dndStore,
...
plugins: [ Ext.plugins.CopyPasteableGrid ],
...

Thanks in advance!

dizelland
24 Jun 2013, 7:15 AM
it doesn't work in google chrome