Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

better support for contenteditables in iframes (ckeditor, tinymce, etc) #252

Merged
merged 2 commits into from
May 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/completer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
27 changes: 20 additions & 7 deletions src/content_editable.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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();
Expand All @@ -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;
},
Expand All @@ -100,7 +113,7 @@
// this.getTextFromHeadToCaret()
// // => ' wor' // not '<b>hello</b> 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);
Expand Down
5 changes: 4 additions & 1 deletion src/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -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});
}
}
},

Expand Down