Stop x-tags from capturing focus/blur events
Asked Answered
C

1

0

I am trying to create a custom element which wraps tinyMCE functionality.

I have the following:-

(function(xtag) {
    xtag.register('x-tinymce', {
        lifecycle:{
            created: tinymceCreate,
            removed: tinymceDestroy
        },
        accessors: {
            disabled: {
                attribute: {
                    boolean: true
                },
                get: getDisabledAttribute,
                set: setDisabledAttribute
            }
        }
    });    
    function tinymceCreate(){
        var textarea = document.createElement('textarea');
        var currentElement = this;
        currentElement.textAreaId = xtag.uid();
        textarea.id = currentElement.textAreaId;
        currentElement.appendChild(textarea);
        currentElement.currentMode = 'design';
        var complexConfig = {
             selector: '#' + currentElement.textAreaId,
             setup: editorSetup
        }
        tinymce.init(complexConfig)
               .then(function thenRetrieveEditor(editors) {
                    currentElement.currentEditor = editors[0];
                    currentElement.currentEditor.setMode(currentElement.currentMode ? currentElement.currentMode :  'design');
        });    
        function editorSetup(editor) {
            editor.on('blur', function blur(event) {
                editor.save();
                document.getElementById(editor.id).blur();
                xtag.fireEvent(currentElement, 'blur', { detail: event, bubbles: false, cancellable: true });
            });
            editor.on('focus', function focus(event) {
                xtag.fireEvent(currentElement, 'focus', { detail: event, bubbles: false, cancellable: true });
            });
            editor.on('BeforeSetContent', function beforeSetContent(ed) {
                if (ed.content) 
                    ed.content = ed.content.replace(/\t/ig, '    ');
            });
        }
    }

    function tinymceDestroy() {
        if (this.currentEditor)
            tinymce.remove(this.currentEditor);
    }

    function getDisabledAttribute() {
        return this.currentMode === 'readonly';
    }

    function setDisabledAttribute(value) {
        if (value) {
            this.currentMode = 'readonly';
        }
        else {
            this.currentMode = 'design';
        }
        if (this.currentEditor) {
            this.currentEditor.setMode(this.currentMode);
        }
    }
})(xtag);

Now, when I register a blur event, it does get called, but so does the focus event. I think that this is because focus/blur events are captured by x-tag by default. I don't want it to do that. Instead, I want these events fired when the user focusses/blurs tinymce.

I am using xtags 1.5.11 and tinymce 4.4.3.

Update 1

OK, the problem is when I call:-

xtag.fireEvent(currentElement, 'focus', { detail: event, bubbles: false, cancellable: true });

This caused the focus to be lost on the editor and go to the containing eleemnt (x-tinymce). To counter this, I modified my editorSetup to look like this:-

   function editorSetup(editor) {
        // // Backspace is not detected in keypress, so need to include keyup event as well.
        // editor.on('keypress change keyup focus', function(ed) {
        //     $j("#" + editor.id).trigger(ed.type);
        // });
        var isFocusFromEditor = false;
        var isBlurFromEditor = false;
        editor.on('blur', function blurEvent(event) {
            console.log("blurred editor");
            if (!isFocusFromEditor) {
                editor.save();
                xtag.fireEvent(currentElement, 'blur', { detail: event, bubbles: false, cancellable: false });
            }
            else {
                console.log('refocussing');
                isFocusFromEditor = false;
                editor.focus();
                isBlurFromEditor = true;
            }

        });
        editor.on('focus', function focusEvent(event) {
            console.log("Focus triggered");
            isFocusFromEditor = true;
            xtag.fireEvent(currentElement, 'focus', { detail: event, bubbles: false, cancellable: false });
        });
        editor.on('BeforeSetContent', function beforeSetContent(ed) {
            if (ed.content) {
                ed.content = ed.content.replace(/\t/ig, '    ');
            }
        });
    }

This stops the blur event from triggering, unfortuantely, now, the blur event does not get called when you leave the editing area.

This feels like a tinyMCE problem, just not sure what.

Crwth answered 9/10, 2016 at 1:58 Comment(5)
@Supersharp Thank you for mentioning the formatting issue. I want tinyMCE to act like any other input element. So I want blur and focus to act like they do for textareas and inputs.Crwth
I don't see where is the issue. By default it acts like a normal control. No need to fire any blur or focus events.Smyrna
@Smyrna If I register a blur event with the x-tinymce element and I move away from the rich text area, I don't ever get the blur function being called. With my code above, it works fine.Crwth
OK i understand what you want!Smyrna
@Smyrna Yeah, sorry, I should have included the code to capture the focus and blur and explicitly stated what I seeing/not seeing.Crwth
S
0

Here is a solution that catches focus and blur events at the custom element level.

It uses the event.stopImmediatePropagation() method to stop the transmission of the blur event to other (external) event listeners when needed.

Also, it uses 2 hidden <input> (#FI and #FO) controls in order to catch the focus when the tab key is pressed.

document.registerElement('x-tinymce', {
  prototype: Object.create(HTMLElement.prototype, {
    createdCallback: {
      value: function() {
        var textarea = this.querySelector('textarea')
        var output = this.querySelector('output')
        output.textContent = "state"
        var self = this
        self.textAreaId = this.dataset.id // xtag.uid();
        textarea.id = self.textAreaId
        var complexConfig = {
          selector: '#' + self.textAreaId,
          setup: editorSetup,
        }

        var FI = this.querySelector('#FI')
        FI.addEventListener('focus', function(ev) {
          if (ev.relatedTarget) {
            var ev = new FocusEvent('focus')
            self.dispatchEvent(ev)
          } else {
            var ev = new FocusEvent('blur')
            self.dispatchEvent(ev)
            focusNextElement(-1)
          }
        })

        var FO = this.querySelector('#FO')
        FO.addEventListener('focus', function(ev) {
          if (!ev.relatedTarget) {
            var ev = new FocusEvent('blur')
            self.dispatchEvent(ev)
            focusNextElement(1)
          } else {
            var ev = new FocusEvent('focus')
            self.dispatchEvent(ev)
          }
        })


        var focused = false

        this.addEventListener('focus', function(ev) {
          console.log('{focus} in ', this.localName)
          if (!focused) {
            focused = true
            output.textContent = focused
            self.editor.focus()								
          } else {
            console.error('should not be here')
            ev.stopImmediatePropagation()
          }
        })

        this.addEventListener('blur', function(ev) {
          console.log('{blur} in %s', this.localName)
          if (focused) {
            focused = false
            output.textContent = focused
          } else {
            console.log('=> cancel blur')
            ev.stopImmediatePropagation()
          }
        })

        tinymce.init(complexConfig).then(function(editors) {
          //self.currentEditor = editors[0]
        })

        //private 

        function focusNextElement(diff) {
          //add all elements we want to include in our selection
          var focussableElements = 'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])'
          if (document.activeElement) {
            var focussable = Array.prototype.filter.call(document.querySelectorAll(focussableElements), function(element) {
              //check for visibility while always include the current activeElement 
              return element.offsetWidth > 0 || element.offsetHeight > 0 || element === document.activeElement
            })
            var index = focussable.indexOf(document.activeElement)
            focussable[index + diff].focus()
          }
        }

        function editorSetup(editor) {
          console.warn('editor setup')
          self.editor = editor

          editor.on('focus', function(event) {
            if (!focused) {
              var ev = new FocusEvent('focus')
              self.dispatchEvent(ev)
            }
          })

          editor.on('blur', function(event) {
              //if ( focused ) 
              {
                var ev = new FocusEvent('blur')
                self.dispatchEvent(ev)
              }
          })
        }
      }
    },
    detachedCallback: {
      value: function() {
        if (this.editor)
          tinymce.remove(this.editor)
      }
    }
  })
})

var xElem = document.querySelector('x-tinymce')
xElem.addEventListener('focus', function(ev) {
  console.info('focus!')
})
xElem.addEventListener('blur', function(ev) {
  console.info('blur!')
})
<script src="http://cdn.tinymce.com/4/tinymce.min.js"></script>
<input type="text">
<x-tinymce data-id="foo">
  <output id=output></output>
  <input type=text id=FI style='width:0;height:0;border:none'>
  <textarea>content</textarea>
  <input type=text id=FO style='width:0;height:0;border:none'>
</x-tinymce>
<input type="text">
Smyrna answered 13/10, 2016 at 14:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.