Autoresize the content area?
Is there any way to auto resize the editing area based on the size of the content?
Something like this could work (untested):
editor.observe("load", function() {
editor.composer.element.addEventListener("keyup", function() {
editor.composer.iframe.style.height = editor.composer.element.scrollHeight + "px";
});
});
I'm considering it to implement it for wysihtml5 0.4. Please don't use the issues for questions or support requests :) thanks!
I would appreciate this feature. That code causes the height to grow, but not shrink.
With textareas, the accepted means of doing this seems to be to clone the textarea, set the clone's height to 0, then track the height of the original textarea's height to the scrollTop of the clone. One example is: https://github.com/jackmoore/autosize/blob/master/jquery.autosize.js
However, assuming this approach works, it would require cloning the contenteditable in the iframe. I'm not sure that's too feasible from outside wysihtml5.
Sorry for opening an issue for a question -- if there's a better place for such questions (aside from reading the source and figuring it out myself), maybe mention it in the README? There's lots of good stuff hidden, undocumented in the source, I thought I might've missed it.
Thanks.
I hope I can implement something for v0.4.
@wvl Well apparently you are right: opening an issue is probably the best way right now to get an answer and share knowledge :) Thanks
Would it be possible to get a wiki?
Of course. Coming soon!
I :+1: on issues for feature requests. This way you can group everybody asking about the same thing. Thanks to this ticket I was able to come up to speed, if I get it working I will share a pull request
Here's a working snippet that allows a textarea to resize: https://gist.github.com/2243439
It grows and shrinks. It calculates the target size by creating a test container to measure dimensions.
this.editor = new wysihtml5.Editor("textarea");
this.editor.observe("load", function () {
$(this.composer.iframe).autoResize();
});
Awesome. Will do a code review and consider implementing it. Thanks @micho!
My only worries are using jQuery for measuring and some Underscore.
This seems to work quite well. Already had the dependencies, so no issues there. Thanks!
A dynamically-sized editor (that behaves like a contenteditable element directly on the page) is the number one thing I need to figure out before I can use this. Glad to hear it's got your attention.
Iceton, check out the snippet I posted. It's fully functional, but it's hard to merge it since it depends on jquery and underscore.
Sent from my phone
On 11/04/2012, at 06:35, iceton [email protected] wrote:
A dynamically-sized editor (that behaves like a contenteditable element directly on the page) is the #1 thing I need to figure out before I can use this. Glad to hear it's got your attention.
Reply to this email directly or view it on GitHub: https://github.com/xing/wysihtml5/issues/18#issuecomment-5062042
Saw that, thanks micho! I'm not using Underscore, so I'm hoping for/messing around with a library-independent solution.
The underscore part is easily replaceable! You can just pull out the function from the source.
Sent from my phone
On 11/04/2012, at 20:50, iceton [email protected] wrote:
Saw that, thanks micho! I'm not using Underscore, so I'm hoping for/messing around with a library-independent solution.
Reply to this email directly or view it on GitHub: https://github.com/xing/wysihtml5/issues/18#issuecomment-5076004
I've found that the code @micho posted has an issue when the editor's contents contain an image without explicit height attributes.
This is because the image is not loaded when the height of the test container is measured.
While this adds another dependency, I'm using this plugin: https://github.com/desandro/imagesloaded (which sets up an imagesLoaded event, even for dynamically added images), and have modified the adjustHeight function to:
a.prototype.adjustHeight = function () {
var a, b, c, d, e, _this;
_this = this;
this.$testContainer.width(this.$source.width());
e = this.$testContainer.html("X").height();
this.$testContainer.html(this.sourceContents());
this.$testContainer.imagesLoaded(function(){
d = parseInt(_this.$el.data("rows") || _this.$el.attr("rows")) || false;
c = d === 1 ? 1 : _this.resizeBy * e;
a = d ? e * d + 1 : _this.originalHeight;
b = _this.$testContainer.height() + c;
if (_this.heightLimit && b > _this.heightLimit) {
b = _this.heightLimit;
}
if (b < a) {
b = a;
}
b = Math.round(b);
return _this.$el.css("min-height", b);
});
};
Edit: As I experiment with this change, it seems to have some issues... Ok, there can be an issue, I think, where we're getting overlapping "imagesLoaded" events. By increasing the _.throttle window from 5ms to 100ms seems to have suppressed the problem, at least on this computer (needs more testing), but is still quick enough that it feels fine.
a.prototype.watchForChanges = function () {
var a = this;
this.$source.bind("keyup keydown paste change focus", _.throttle(function (evt) {
return a.adjustHeight(evt);
}, $.support.touch ? 300 : 100));
this.$el.closest("form").bind("reset", function () {
return a.resetHeight();
});
};
I've run across other difficulties with this resize process that I'm going to have to, for expediency, go back to @tiff's first solution and forgo shrinking.
In our case, we have a lot of images and oembed-previewed objects.
Every time the contents gets copied to the test node for sizing, all these assets reload again. Some of the images are cached, but some others are not, and the oembeds are not cached either, and this is causing a LOT of network traffic.
Just wanted to bring this up for anybody else that might have a content load similar to ours.
Shrink-to-fit: The snippet by @tiff above works for me in Chrome more or less by adding the following:
remove "height", "padding-top", "padding-bottom" from BOX_FORMATTING -because this is causing the fixed height of the iframe add "min-height" to BOX_FORMATTING (so we can see a composer line)
add textarea{min-height: 2em;} in the CSS (height of this line)
Would be great if someone could try this. I think that the composer is already shrink-to-fit (because of inline-block), and its height can be copied to its parent iframe if it is allowed to.
I got this working properly by observing keyup, focus and blur. Shrinking works only on blur, but thats acceptable for me.
var resizeIframe = function() {
editor.composer.iframe.style.height = editor.composer.element.scrollHeight + "px";
}
editor.on("load", function() {
editor.composer.element.addEventListener("keyup", resizeIframe, false)
editor.composer.element.addEventListener("blur", resizeIframe, false)
editor.composer.element.addEventListener("focus", resizeIframe, false)
})
Didnt mess with BOX_FORMATTING.
@TomoGlavas: Great fix. Having a bit of a focus issue, though: clicking at a lower point in the document causes the page to jump to the top. Related?
Yea, I had some strange effects happening too. Temporarily, I use a different check for height, as iframe and composer height values seem to not allways reflect reality. No strange effects with this code:
var resizeIframe = function() { if($(chunk).find(".wysihtml5-sandbox").height() != editor.composer.element.offsetHeight) { $(chunk).find(".wysihtml5-sandbox").height(editor.composer.element.offsetHeight); } }
@TomoGlavas what does chunk refer to in that last snippet?
Sory bout that, its a proprietary element of my cms. Here its just a html element containing the editor.
@TomoGlavas if I use the code from your last comment I get a weird effect where the box increases in size minutely for every character typed...
It is tricky, yes. Try playing the css of the editor (css that is applied to the body inside the iframe), specifically - padding. It affects the height calculation and can cause such effects.
@TomoGlavas FTW!
Hi, love to have this feature too
I see that you plan to add auto-resize for V0.4 … great feature !! :) any release date ?
@tiff … i'm not good at javscript but i have this code to emulate this feature : (btw, i use multiple wysihtml5 whom can be added dynamicaly … if you need some beta tester, don't hesitate ! it would be a pleasure to help you :) )
When a new word is added:
//resize iframe
function onNewWord() {
var editorHeight = editor.composer.commands.doc.body.clientHeight;
editor.composer.iframe.style.height = editorHeight+20+"px";
};
and on load :
function onLoad() {
resizeTextFrame();
}
function resizeTextFrame(){
var editorHeight = editor.composer.commands.doc.body.clientHeight;
var iframeClassName = ".wysihtml5-sandbox."+ textareaid;
$(iframeClassName).height(editorHeight+20);
}
I have been working on code to resize the wsyihtml5 iframe for a while now. The aforementioned autoresize.js does not working correctly with certain content. Also it's use of a temporary off-screen copy of the content is not ideal, performance wise. And the code is hard to read!
The recommend way to calculate actual content height is to use scrollHeight (autoresize.js doesn't use this).
However with certain content this doesn't give the correct height when the content is inside the <body> element. And different browsers behave differently. For example in Chrome, if the first node is a textnode, scrollHeight is incorrect.
To resolve this issue we need to wrap the content in a <div>. I am doing this outside of wysihtml5, however it is all a bit messy and I have seen a case where the div wrapper was able to be deleted by the user.
So the best way to handle this is for wysihtml5 to remove the contenteditable attribute from the <body>, add a <div> child node to it and make this the contenteditable element. This paves the way for correct auto-resizing and prevents the user from ever deleting the div.
I've had a look through the code and the <body> element is used all over the place. The editor (composer) itself uses this.element as the editable element, but whether changing this to the new div will work properly I have no idea.
So I'd really like to see this change incorporated, as we can then finally solve the resizing issue that many of us want and need.
-Neville
FWIW I would definitely also be interested in whatever comes up in v0.4 for resizing the WYSIHTML5 editor.
I'd like to suggest a small snippet of logic to add to the resizeIframe() proposed by @tiff and @TomoGlavas. This snippet checks to make sure the editor has a certain scrollHeight, e.g., 200px, before resizing the iframe. Without it, the iframe will shrink down to less than 1 line tall until more than 1 line of text exists which is terrible from a UI/UX perspective.
var resizeIframe = function() {
//check to make sure the scrollHeight is some reasonable height, e.g, 200px, before resizing the <iframe>
if(editor.composer.element.scrollHeight>200) editor.composer.iframe.style.height = editor.composer.element.scrollHeight + "px";
};
//editor.composer.iframe.style.height='1000';
editor.on("load", function() {
editor.composer.element.addEventListener("keyup", resizeIframe, false)
editor.composer.element.addEventListener("blur", resizeIframe, false)
editor.composer.element.addEventListener("focus", resizeIframe, false)
});
The only other alternative/suggestion I can add to this discussion is to consider using JqueryUI's resizeable(). However, this is a seriously suboptimal solution since 1) its manual resizing; 2) you really can't get JqueryUI component by component (don't want to tack on 100kb just for this).
how i done that, using jquery
html :
<div id="editor"><textarea>:)</textarea></div>
css :
#editor > * { width:100%; height:100%; padding:0; margin:0; }
javascript :
var $editor = $("#editor");
var editor = ... init wysihtml5 ...
var ifrm = $(editor.composer.iframe).css({border:0});
var ifrmContent = $(ifrm[0].contentWindow.document);
ifrmContent = ifrmContent.find("html").css({width:"100%",height:"100%",margin:0,padding:0,overflow:"hidden"}).find("body").css({height:"auto",width:"100%",margin:0,padding:0});
function resize(){
var h = ifrmContent.height();
$editor.stop().animate({height:h});
}
editor.composer.element.addEventListener("keyup", resize);
editor.on("aftercommand:composer", resize);
window.setTimeout(resize,10);
Love this thread so far! Autoresizing is the major missing feature in wysihtml5, as far as I can see.
The examples above pretty much nailed it for me. I found one or two minor things:
- I use
box-sizing: border-boxon textareas, which gets imported by wysihtml5, so when setting the<iframe>height I needed to factor in border width and padding to get the height right. - When you set the height of the
<iframe>to the exact scrollHeight of the<body>, you get a weird effect on newlines where all the text in the<body>shunts upwards (on the keydown) before the height increases (on the keyup). The simplest way around this (I've found) is to add a buffer (of 50px) to the<iframe>height, so a newline doesn't cause the<body>to shunt down. - When you backspace and remove lines, the scrollheight isn't calculated correctly (as it includes the CSS height property we set on the previous keypress). As someone mentioned above, the best way to do it on textareas is to clone the textarea, set the height to 1, and calculate the height off the clone. I found the best way to do this in wysihtml5 was to clone the contents of the
<iframe>body into a new<div>, get the height of that<div>, remove it again, and set the height of the<iframe>based on the<div>.
The code I ended up using, that covers all these, is below. Note that it doesn't do detection for the box-sizing mode - it just assumes you'd always use border-box, because why wouldn't you! Also note that it's a weird mix of jQuery and native JS, that could probably be cleaned up a bit
var editor = new wysihtml5.Editor('textarea', {
useLineBreaks: false
});
editor.on('load', function()
{
var minheight = 150;
var buffer = 50;
var padding = parseFloat(editor.composer.iframe.style.paddingTop) + parseFloat(editor.composer.iframe.style.paddingBottom) + parseFloat(editor.composer.iframe.style.borderTopWidth) + parseFloat(editor.composer.iframe.style.borderBottomWidth);
editor.composer.iframe.style.height = (minheight + padding) + 'px';
var resize = function() {
var $div = $('<div>').append($(editor.composer.element).clone().contents()).appendTo(editor.composer.element);
var scrollheight = $div.get(0).scrollHeight;
$div.remove();
if (scrollheight > (minheight - buffer)) editor.composer.iframe.style.height = (scrollheight + buffer + padding) + 'px';
else editor.composer.iframe.style.height = (minheight + padding) + 'px';
}
editor.composer.element.addEventListener('keyup', resize, false)
editor.composer.element.addEventListener('blur', resize, false)
editor.composer.element.addEventListener('focus', resize, false)
});