Thursday, February 5, 2015

Adding and keeping a paste event for Sencha's Extjs 4.x's HtmlEditor.

In Sencha's Extjs 4.x, they redid the entire HtmlEditor class vs. version 3.x. We needed to grab information off the clipboard before Ext and make custom changes. For Extjs 3.x, that was as simple as adding a listener to the iframe body. This is what several people on varying websites on the Internet suggested as the fix, and this worked for the older version. But on the newer version, the paste event was either ignored or would simply just quite working after a short amount of time. More searching the Internet found that no one had suggestions for fixing the issue in Extjs 4.x (and probably newer).

When the paste event did work, the pasted text would be edited by our custom code and inserted into the editor. Then, mysteriously, unedited text was also entered, which appeared as a double paste. Along with our custom paste methods not working after a short period time, this double edit issue confused many a developer.

Allowing anyone access to the user's clipboard is a security issue. Browsers have allowed sandboxing the clipboard content, but Mozilla's Firefox developers did not, at least not until recently. The only surefire cross-browser solution was to use the aging and outgoing Flash. We can not use Flash for our customer's code. Several developers for HTML editors have solved this by 'tricking' the browser. The solution was to have a hidden field and a visual field. The hidden field would grab the pasted html, manipulate it, then sync that edited text with the visual field. This works great. But we needed access to the unedited text straight from the clipboard to do our own custom editing.

The two parts of an HTML editor are a textArea and an iframe. We added our event to the iframe's body, as the several places on the web suggested. And this was the mistake when working with Extjs 4.x. The newer version loosly couples the textArea and the iframe to an parent object, whereas the older version more tightly coupled the two. Remember this.

Example of the textArea and the iframe:
<textarea id="ext-comp-1284-inputCmp-textareaEl" 
 class="x-hidden" autocomplete="off" tabindex="-1" 
 name="comments" style="height: 46px;">
</textarea>
<iframe id="ext-comp-1284-inputCmp-iframeEl" 
 class="x-htmleditor-iframe" frameborder="0" 
 src="about:blank" name="ext-gen1439" 
 style="width: 100%; 
 height: 46px;">
<!DOCTYPE html>
 <html>
  <head>
   <body style="font-size: 12px; 
   font-family: tahoma,arial,verdana,sans-serif; 
   background-image: none; background-repeat: 
   repeat; background-color: rgb(255, 255, 255); 
   color: rgb(0, 0, 0); background-attachment: fixed; 
   cursor: text;">
  </html>
</iframe>

The other issue we had was the double insert. After some searching, I came across a site that said we needed to stop the paste event when we did our custom editing. This was easy. I added Ext.EventManager.preventDefault(e), Ext's built in version of preventDefault(). 'e' is the event being passed into our method, which is our specific 'paste' event. PreventDefault stops the propagation of the event. Adding it solved the double insert. So we were having only one insert, instead of two. Yay!

Snippet of the onPaste method called by the 'paste' listener:
onPaste : function(e, elem) {
    var text = "";
    Ext.EventManager.preventDefault(e);

    if(Ext.isIE) {

But, we still had the issue of after some time, our paste event was being ignored. While searching the web for a solution, I came across another person that had a similar issue. They were hiding a form so that when the user needed it, it would appear, then when done it would hide. This makes showing forms or other objects easy as the html doesn't need to be rebuilt every time. In some code, that's a speed increase. As a user, that is nice to have a page load immediately instead of waiting those few precious seconds.

The answer to this issue was that losely coupled hidden objects are garbage collected by the browser. Bingo! Remember that losely coupled hidden field for HTML editors? It was being garbage collected, hence why our event was being ignored after some time. After some more searching and being fairly familiar with Ext's HtmlEditor.js file (after all, I searched over it time and time again looking for reasons why something was removing our 'paste' listener). I found the solution. Instead of adding the listener to the iframe, as was done in the older Extjs version, we add it to the parent via Ext.EventManager.on(doc, { paste : fn}) where 'doc' is a reference back to the parent document of the editor, and 'fn' is the function we pass in to do our custom editing. Previously we used el.addListener('paste', this.onPaste, this), where el is this.getEditorBody(), which is the iframe's .

As part of the 'constructor' function, add this to the config:
listeners: {
    initialize: function () {
        var doc = me.getDoc();
        var fn = Ext.Function.bind(me.onPaste, me);
        Ext.EventManager.on(doc, {
            paste : fn
        });
    }
}

Now we have the ability to manipulate text before adding it to the editor. We used preventDefault() to stop the paste event from bubbling, and added a listener to the document, rather than to it's iframe's body.

1 comment: