Improved textarea expanding.

This commit is contained in:
Martin Edenhofer 2014-04-04 19:57:34 +02:00
parent 6234ec27cd
commit 96e338f86a
7 changed files with 233 additions and 186 deletions

View file

@ -354,10 +354,17 @@ class App.ControllerForm extends App.Controller
# textarea # textarea
else if attribute.tag is 'textarea' else if attribute.tag is 'textarea'
item = $( App.view('generic/textarea')( attribute: attribute ) ) fileUploaderId = 'file-uploader-' + new Date().getTime() + '-' + Math.floor( Math.random() * 99999 )
item = $( App.view('generic/textarea')( attribute: attribute ) + '<div class="file-uploader ' + attribute.class + '" id="' + fileUploaderId + '"></div>' )
a = =>
$( item[0] ).expanding()
$( item[0] ).on('focus', ->
$( item[0] ).expanding()
)
App.Delay.set( a, 80 )
if attribute.upload if attribute.upload
fileUploaderId = 'file-uploader-' + new Date().getTime() + '-' + Math.floor( Math.random() * 99999 )
item = $( App.view('generic/textarea')( attribute: attribute ) + '<div class="file-uploader ' + attribute.class + '" id="' + fileUploaderId + '"></div>' )
# add file uploader # add file uploader
u = => u = =>
@ -378,7 +385,7 @@ class App.ControllerForm extends App.Controller
fail: '' fail: ''
debug: false debug: false
) )
App.Delay.set( u, 80, undefined, 'form_upload' ) App.Delay.set( u, 100, undefined, 'form_upload' )
# article # article
else if attribute.tag is 'article' else if attribute.tag is 'article'
@ -954,9 +961,6 @@ class App.ControllerForm extends App.Controller
# if attribute.onchange[] # if attribute.onchange[]
ui = @ ui = @
# item.bind('focus', ->
# ui.log 'focus', attribute
# );
item.bind('change', -> item.bind('change', ->
if ui.form_data if ui.form_data
params = App.ControllerForm.params(@) params = App.ControllerForm.params(@)

View file

@ -71,7 +71,6 @@ class App.TicketCreate extends App.Controller
activate: => activate: =>
@navupdate '#' @navupdate '#'
@el.find('textarea').elastic()
changed: => changed: =>
formCurrent = @formParam( @el.find('.ticket-create') ) formCurrent = @formParam( @el.find('.ticket-create') )
@ -184,12 +183,6 @@ class App.TicketCreate extends App.Controller
params: params params: params
) )
# add elastic to textarea
@el.find('textarea').elastic()
# update textarea size
@el.find('textarea').trigger('change')
# show template UI # show template UI
new App.WidgetTemplate( new App.WidgetTemplate(
el: @el.find('.ticket_template') el: @el.find('.ticket_template')

View file

@ -108,12 +108,6 @@ class Index extends App.ControllerContent
form_data: @edit_form form_data: @edit_form
) )
# add elastic to textarea
@el.find('textarea').elastic()
# update textarea size
@el.find('textarea').trigger('change')
new App.ControllerDrox( new App.ControllerDrox(
el: @el.find('.sidebar') el: @el.find('.sidebar')
data: data:

View file

@ -48,7 +48,6 @@ class App.TicketZoom extends App.Controller
activate: => activate: =>
@navupdate '#' @navupdate '#'
@el.find('textarea').elastic()
changed: => changed: =>
formCurrent = @formParam( @el.find('.ticket-update') ) formCurrent = @formParam( @el.find('.ticket-update') )
@ -414,8 +413,6 @@ class Edit extends App.Controller
] ]
) )
@el.find('textarea').elastic()
# remember form defaults # remember form defaults
@ui.formDefault = @formParam( @el.find('.ticket-update') ) @ui.formDefault = @formParam( @el.find('.ticket-update') )

View file

@ -27,7 +27,6 @@ class App.UserZoom extends App.Controller
activate: => activate: =>
@navupdate '#' @navupdate '#'
@el.find('textarea').elastic()
changed: => changed: =>
formCurrent = @formParam( @el.find('.ticket-update') ) formCurrent = @formParam( @el.find('.ticket-update') )

View file

@ -0,0 +1,222 @@
// Expanding Textareas v0.1.1
// MIT License
// https://github.com/bgrins/ExpandingTextareas
(function(factory) {
// Add jQuery via AMD registration or browser globals
if (typeof define === 'function' && define.amd) {
define([ 'jquery' ], factory);
}
else {
factory(jQuery);
}
}(function ($) {
var Expanding = function($textarea, opts) {
Expanding._registry.push(this);
this.$textarea = $textarea;
this.$textCopy = $("<span />");
this.$clone = $("<pre class='expanding-clone'><br /></pre>").prepend(this.$textCopy);
this._resetStyles();
this._setCloneStyles();
this._setTextareaStyles();
$textarea
.wrap($("<div class='expanding-wrapper' style='position:relative' />"))
.after(this.$clone);
this.attach();
this.update();
if (opts.update) $textarea.bind("update.expanding", opts.update);
};
// Stores (active) `Expanding` instances
// Destroyed instances are removed
Expanding._registry = [];
// Returns the `Expanding` instance given a DOM node
Expanding.getExpandingInstance = function(textarea) {
var $textareas = $.map(Expanding._registry, function(instance) {
return instance.$textarea[0];
}),
index = $.inArray(textarea, $textareas);
return index > -1 ? Expanding._registry[index] : null;
};
// Returns the version of Internet Explorer or -1
// (indicating the use of another browser).
// From: http://msdn.microsoft.com/en-us/library/ms537509(v=vs.85).aspx#ParsingUA
var ieVersion = (function() {
var v = -1;
if (navigator.appName === "Microsoft Internet Explorer") {
var ua = navigator.userAgent;
var re = new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})");
if (re.exec(ua) !== null) v = parseFloat(RegExp.$1);
}
return v;
})();
// Check for oninput support
// IE9 supports oninput, but not when deleting text, so keyup is used.
// onpropertychange _is_ supported by IE8/9, but may not be fired unless
// attached with `attachEvent`
// (see: http://stackoverflow.com/questions/18436424/ie-onpropertychange-event-doesnt-fire),
// and so is avoided altogether.
var inputSupported = "oninput" in document.createElement("input") && ieVersion !== 9;
Expanding.prototype = {
// Attaches input events
// Only attaches `keyup` events if `input` is not fully suported
attach: function() {
var events = 'input.expanding change.expanding',
_this = this;
if(!inputSupported) events += ' keyup.expanding';
this.$textarea.bind(events, function() { _this.update(); });
},
// Updates the clone with the textarea value
update: function() {
this.$textCopy.text(this.$textarea.val().replace(/\r\n/g, "\n"));
// Use `triggerHandler` to prevent conflicts with `update` in Prototype.js
this.$textarea.triggerHandler("update.expanding");
},
// Tears down the plugin: removes generated elements, applies styles
// that were prevously present, removes instance from registry,
// unbinds events
destroy: function() {
this.$clone.remove();
this.$textarea
.unwrap()
.attr('style', this._oldTextareaStyles || '');
delete this._oldTextareaStyles;
var index = $.inArray(this, Expanding._registry);
if (index > -1) Expanding._registry.splice(index, 1);
this.$textarea.unbind(
'input.expanding change.expanding keyup.expanding update.expanding');
},
// Applies reset styles to the textarea and clone
// Stores the original textarea styles in case of destroying
_resetStyles: function() {
this._oldTextareaStyles = this.$textarea.attr('style');
this.$textarea.add(this.$clone).css({
margin: 0,
webkitBoxSizing: "border-box",
mozBoxSizing: "border-box",
boxSizing: "border-box",
width: "100%"
});
},
// Sets the basic clone styles and copies styles over from the textarea
_setCloneStyles: function() {
var css = {
display: 'block',
border: '0 solid',
visibility: 'hidden',
minHeight: this.$textarea.outerHeight()
};
if(this.$textarea.attr("wrap") === "off") css.overflowX = "scroll";
else css.whiteSpace = "pre-wrap";
this.$clone.css(css);
this._copyTextareaStylesToClone();
},
_copyTextareaStylesToClone: function() {
var _this = this,
properties = [
'lineHeight', 'textDecoration', 'letterSpacing',
'fontSize', 'fontFamily', 'fontStyle',
'fontWeight', 'textTransform', 'textAlign',
'direction', 'wordSpacing', 'fontSizeAdjust',
'wordWrap', 'word-break',
'borderLeftWidth', 'borderRightWidth',
'borderTopWidth','borderBottomWidth',
'paddingLeft', 'paddingRight',
'paddingTop','paddingBottom', 'maxHeight'];
$.each(properties, function(i, property) {
var val = _this.$textarea.css(property);
// Prevent overriding percentage css values.
if(_this.$clone.css(property) !== val) {
_this.$clone.css(property, val);
if(property === 'maxHeight' && val !== 'none') {
_this.$clone.css('overflow', 'hidden');
}
}
});
},
_setTextareaStyles: function() {
this.$textarea.css({
position: "absolute",
top: 0,
left: 0,
height: "100%",
resize: "none",
overflow: "auto"
});
}
};
$.expanding = $.extend({
autoInitialize: true,
initialSelector: "textarea.expanding",
opts: {
update: function() { }
}
}, $.expanding || {});
$.fn.expanding = function(o) {
if (o === "destroy") {
this.each(function() {
var instance = Expanding.getExpandingInstance(this);
if (instance) instance.destroy();
});
return this;
}
// Checks to see if any of the given DOM nodes have the
// expanding behaviour.
if (o === "active") {
return !!this.filter(function() {
return !!Expanding.getExpandingInstance(this);
}).length;
}
var opts = $.extend({ }, $.expanding.opts, o);
this.filter("textarea").each(function() {
var visible = this.offsetWidth > 0 || this.offsetHeight > 0,
initialized = Expanding.getExpandingInstance(this);
if(visible && !initialized) new Expanding($(this), opts);
else {
if(!visible) _warn("ExpandingTextareas: attempt to initialize an invisible textarea. Call expanding() again once it has been inserted into the page and/or is visible.");
if(initialized) _warn("ExpandingTextareas: attempt to initialize a textarea that has already been initialized. Subsequent calls are ignored.");
}
});
return this;
};
function _warn(text) {
if(window.console && console.warn) console.warn(text);
}
$(function () {
if ($.expanding.autoInitialize) {
$($.expanding.initialSelector).expanding();
}
});
}));

View file

@ -1,162 +0,0 @@
/**
* @name Elastic
* @descripton Elastic is jQuery plugin that grow and shrink your textareas automatically
* @version 1.6.11
* @requires jQuery 1.2.6+
*
* @author Jan Jarfalk
* @author-email jan.jarfalk@unwrongest.com
* @author-website http://www.unwrongest.com
*
* @licence MIT License - http://www.opensource.org/licenses/mit-license.php
*/
(function($){
jQuery.fn.extend({
elastic: function() {
// We will create a div clone of the textarea
// by copying these attributes from the textarea to the div.
var mimics = [
'paddingTop',
'paddingRight',
'paddingBottom',
'paddingLeft',
'fontSize',
'lineHeight',
'fontFamily',
'width',
'fontWeight',
'border-top-width',
'border-right-width',
'border-bottom-width',
'border-left-width',
'borderTopStyle',
'borderTopColor',
'borderRightStyle',
'borderRightColor',
'borderBottomStyle',
'borderBottomColor',
'borderLeftStyle',
'borderLeftColor'
];
return this.each( function() {
// Elastic only works on textareas
if ( this.type !== 'textarea' ) {
return false;
}
var $textarea = jQuery(this),
$twin = jQuery('<div />').css({
'position' : 'absolute',
'display' : 'none',
'word-wrap' : 'break-word',
'white-space' :'pre-wrap'
}),
lineHeight = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
goalheight = 0;
// Opera returns max-height of -1 if not set
if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
// Append the twin to the DOM
// We are going to meassure the height of this, not the textarea.
$twin.appendTo($textarea.parent());
// Copy the essential styles (mimics) from the textarea to the twin
var i = mimics.length;
while(i--){
$twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
}
// Updates the width of the twin. (solution for textareas with widths in percent)
function setTwinWidth(){
var curatedWidth = Math.floor(parseInt($textarea.width(),10));
if($twin.width() !== curatedWidth){
$twin.css({'width': curatedWidth + 'px'});
// Update height of textarea
update(true);
}
}
// Sets a given height and overflow state on the textarea
function setHeightAndOverflow(height, overflow){
var curratedHeight = Math.floor(parseInt(height,10));
if($textarea.height() !== curratedHeight){
$textarea.css({'height': curratedHeight + 'px','overflow':overflow});
}
}
// This function will update the height of the textarea if necessary
function update(forced) {
// Get curated content from the textarea.
var textareaContent = $textarea.val().replace(/&/g,'&amp;').replace(/ {2}/g, '&nbsp;').replace(/<|>/g, '&gt;').replace(/\n/g, '<br />');
// Compare curated content with curated twin.
var twinContent = $twin.html().replace(/<br>/ig,'<br />');
if(forced || textareaContent+'&nbsp;' !== twinContent){
// Add an extra white space so new rows are added when you are at the end of a row.
$twin.html(textareaContent+'&nbsp;');
// Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){
var goalheight = $twin.height()+lineHeight;
if(goalheight >= maxheight) {
setHeightAndOverflow(maxheight,'auto');
} else if(goalheight <= minheight) {
setHeightAndOverflow(minheight,'hidden');
} else {
setHeightAndOverflow(goalheight,'hidden');
}
}
}
}
// Hide scrollbars
$textarea.css({'overflow':'hidden'});
// Update textarea size on keyup, change, cut and paste
$textarea.bind('keyup change cut paste', function(){
update();
});
// Update width of twin if browser or textarea is resized (solution for textareas with widths in percent)
$(window).bind('resize', setTwinWidth);
$textarea.bind('resize', setTwinWidth);
$textarea.bind('update', update);
// Compact textarea on blur
$textarea.bind('blur',function(){
if($twin.height() < maxheight){
if($twin.height() > minheight) {
$textarea.height($twin.height());
} else {
$textarea.height(minheight);
}
}
});
// And this line is to catch the browser paste event
$textarea.bind('input paste',function(e){ setTimeout( update, 250); });
// Run update once when elastic is initialized
update();
});
}
});
})(jQuery);