5

I'm a part time newish developer working on a Chrome extension designed to work as an internal CR tool.

The concept is simple, on a keyboard shortcut, the extension gets the word next to the caret, checks it for a pattern match, and if the match is 'true' replaces the word with a canned response.

To do this, I mostly used a modified version of this answer.

I've hit a roadblock in that using this works for the active element, but it doesn't appear to work for things such as the 'compose' window in Chrome, or consistently across other services (Salesforce also seems to not like it, for example). Poking about a bit I thought this might be an issue with iFrames, so I tinkered about a bit and modified this peace of code:

function getActiveElement(document){
    document = document || window.document;
    if( document.body === document.activeElement || document.activeElement.tagName == 'IFRAME' ){// Check if the active element is in the main web or iframe
        var iframes = document.getElementsByTagName('iframe');// Get iframes
        for(var i = 0; i<iframes.length; i++ ){
            var focused = getActiveElement( iframes[i].contentWindow.document );// Recall
            if( focused !== false ){
                return focused; // The focused
             }
         }
     }
     else return document.activeElement;
};

(Which I originally got from another SO post I can no longer find). Seems as though I'm out of luck though, as no dice.

Is there a simple way to always get the active element with the active caret on every page, even for the Gmail compose window and similar services, or am I going to be stuck writting custom code for a growing list of servcies that my code can't fetch the caret on?

My full code is here. It's rough while I just try to get this to work, so I understand there's sloppy parts of it that need tidied up:

 function AlertPrevWord() {
        //var text = document.activeElement; //Fetch the active element on the page, cause that's where the cursor is. 
        var text = getActiveElement();
        console.log(text);
        var caretPos = text.selectionStart;//get the position of the cursor in the element.
        var word = ReturnWord(text.value, caretPos);//Get the word before the cursor. 
        if (word != null) {//If it's not blank
            return word //send it back.
        }
    }

    function ReturnWord(text, caretPos) {
        var index = text.indexOf(caretPos);//get the index of the cursor
        var preText = text.substring(0, caretPos);//get all the text between the start of the element and the cursor. 
        if (preText.indexOf(" ") > 0) {//if there's more then one space character
            var words = preText.split(" ");//split the words by space
            return words[words.length - 1]; //return last word
        }
        else {//Otherwise, if there's no space character
            return preText;//return the word
        }
    }




function getActiveElement(document){
    document = document || window.document;
    if( document.body === document.activeElement || document.activeElement.tagName == 'IFRAME' ){// Check if the active element is in the main web or iframe
        var iframes = document.getElementsByTagName('iframe');// Get iframes
        for(var i = 0; i<iframes.length; i++ ){
            var focused = getActiveElement( iframes[i].contentWindow.document );// Recall
            if( focused !== false ){
                return focused; // The focused
             }
         }
     }
     else return document.activeElement;
};
Community
  • 1
  • 1
HDCerberus
  • 2,113
  • 4
  • 20
  • 36
  • After identifying the frame (e.g. by recursively processing same-origin frames, as you've done), you can replace the part at the caret using the logic of "subproblem 2" in this answer: https://stackoverflow.com/questions/28055887/is-there-a-flexible-way-to-modify-the-contents-of-an-editable-element/28198957#28198957 – Rob W Jul 17 '15 at 12:55

1 Answers1

2

I've got it working for the Gmail window (and presumably other contenteditable elements, rather than just input elements).

Edit: the failure around linebreaks was because window.getSelection().anchorOffset returns the offset relative to that particular element, whereas ReturnWord was getting passed the text of the entire compose window (which contained multiple elements). window.getSelection().anchorNode returns the node that the offset is being calculated within.

function AlertPrevWord() {
    var text = getActiveElement();
    var caretPos = text.selectionStart || window.getSelection().anchorOffset;        
    var word = ReturnWord(text.value || window.getSelection().anchorNode.textContent, caretPos);
    if (word != null) {return word;}
}

I originally used a MutationObserver to account for the Gmail compose div being created after the page load, just to attach an event listener to it.

var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    var nodes = mutation.addedNodes; //list of new nodes in the DOM
    for (var i = 0; i < nodes.length; ++i) {
      //attach key listener to nodes[i] that calls AlertPrevWord
    }
  });    
});
observer.observe(document, {childList: true, subtree:true });
    //childList:true notifies observer when nodes are added or removed
    //subtree:true observes all the descendants of document as well

Edit: The delegated click handler I've been testing with. Key event handlers so far not working.

$(document).on( "click", ":text,[contenteditable='true']", function( e ) {
   e.stopPropagation();
   console.log(AlertPrevWord());
});
  • 1
    would a delegated event curtail the need for a MO? – dandavis Jul 18 '15 at 20:45
  • Yeah, that works way better. Thanks, I'd never used delegated events before. –  Jul 19 '15 at 15:12
  • This appears to work minimally, so long as I'm only trying to get a word in the first line of the message. It does not seem to like it if there's a second line separated by a line break. Additionally, would you be able to provide a sample of it working with a delegated event? That would really interest me. – HDCerberus Jul 21 '15 at 18:50
  • I've only got a delegated click event working; key events aren't working for reasons I don't yet understand. I fixed the issue with line break; edited answer forthcoming –  Jul 22 '15 at 04:35
  • Thanks, I've added and awarded yourself the bounty, as you've given me most of what I was looking for, and a robust way of finding it in most instances. I'm still dissecting the answer, but it works in most instances (Unfortunately, apart from one really picky case). I would appreciate if you would provide an update on any progress with the delegate click handler still, however. – HDCerberus Jul 23 '15 at 15:43