diff --git a/src/completer.js b/src/completer.js index 259d0193..67376531 100644 --- a/src/completer.js +++ b/src/completer.js @@ -78,7 +78,8 @@ throw new Error('textcomplete must be called on a Textarea or a ContentEditable.'); } - if (element === document.activeElement) { + // use ownerDocument to fix iframe / IE issues + if (element === element.ownerDocument.activeElement) { // element has already been focused. Initialize view objects immediately. this.initialize() } else { @@ -109,12 +110,26 @@ adapter: null, dropdown: null, $el: null, + $iframe: null, // Public methods // -------------- initialize: function () { var element = this.$el.get(0); + + // check if we are in an iframe + // we need to alter positioning logic if using an iframe + if (this.$el.prop('ownerDocument') !== document && window.frames.length) { + for (var iframeIndex = 0; iframeIndex < window.frames.length; iframeIndex++) { + if (this.$el.prop('ownerDocument') === window.frames[iframeIndex].document) { + this.$iframe = $(window.frames[iframeIndex].frameElement); + break; + } + } + } + + // Initialize view objects. this.dropdown = new $.fn.textcomplete.Dropdown(element, this, this.option); var Adapter, viewName; diff --git a/src/content_editable.js b/src/content_editable.js index 33f652dd..e4c8f298 100644 --- a/src/content_editable.js +++ b/src/content_editable.js @@ -21,7 +21,9 @@ // When an dropdown item is selected, it is executed. select: function (value, strategy, e) { var pre = this.getTextFromHeadToCaret(); - var sel = window.getSelection() + // use ownerDocument instead of window to support iframes + var sel = this.el.ownerDocument.getSelection(); + var range = sel.getRangeAt(0); var selection = range.cloneRange(); selection.selectNodeContents(range.startContainer); @@ -38,13 +40,13 @@ range.deleteContents(); // create temporary elements - var preWrapper = document.createElement("div"); + var preWrapper = this.el.ownerDocument.createElement("div"); preWrapper.innerHTML = pre; - var postWrapper = document.createElement("div"); + var postWrapper = this.el.ownerDocument.createElement("div"); postWrapper.innerHTML = post; // create the fragment thats inserted - var fragment = document.createDocumentFragment(); + var fragment = this.el.ownerDocument.createDocumentFragment(); var childNode; var lastOfPre; while (childNode = preWrapper.firstChild) { @@ -77,8 +79,8 @@ // // Dropdown's position will be decided using the result. _getCaretRelativePosition: function () { - var range = window.getSelection().getRangeAt(0).cloneRange(); - var node = document.createElement('span'); + var range = this.el.ownerDocument.getSelection().getRangeAt(0).cloneRange(); + var node = this.el.ownerDocument.createElement('span'); range.insertNode(node); range.selectNodeContents(node); range.deleteContents(); @@ -87,6 +89,17 @@ position.left -= this.$el.offset().left; position.top += $node.height() - this.$el.offset().top; position.lineHeight = $node.height(); + + // special positioning logic for iframes + // this is typically used for contenteditables such as tinymce or ckeditor + if (this.completer.$iframe) { + var iframePosition = this.completer.$iframe.offset(); + position.top += iframePosition.top; + position.left += iframePosition.left; + //subtract scrollTop from element in iframe + position.top -= this.$el.scrollTop(); + } + $node.remove(); return position; }, @@ -100,7 +113,7 @@ // this.getTextFromHeadToCaret() // // => ' wor' // not 'hello wor' getTextFromHeadToCaret: function () { - var range = window.getSelection().getRangeAt(0); + var range = this.el.ownerDocument.getSelection().getRangeAt(0); var selection = range.cloneRange(); selection.selectNodeContents(range.startContainer); return selection.toString().substring(0, range.startOffset); diff --git a/src/dropdown.js b/src/dropdown.js index 959dbcdd..3be2e1c7 100644 --- a/src/dropdown.js +++ b/src/dropdown.js @@ -455,7 +455,10 @@ var windowScrollBottom = $window.scrollTop() + $window.height(); var height = this.$el.height(); if ((this.$el.position().top + height) > windowScrollBottom) { - this.$el.offset({top: windowScrollBottom - height}); + // only do this if we are not in an iframe + if (!this.completer.$iframe) { + this.$el.offset({top: windowScrollBottom - height}); + } } },