Improved textarea expanding.
This commit is contained in:
parent
6234ec27cd
commit
96e338f86a
7 changed files with 233 additions and 186 deletions
|
@ -354,11 +354,18 @@ class App.ControllerForm extends App.Controller
|
|||
|
||||
# textarea
|
||||
else if attribute.tag is 'textarea'
|
||||
item = $( App.view('generic/textarea')( attribute: attribute ) )
|
||||
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>' )
|
||||
|
||||
a = =>
|
||||
$( item[0] ).expanding()
|
||||
$( item[0] ).on('focus', ->
|
||||
$( item[0] ).expanding()
|
||||
)
|
||||
App.Delay.set( a, 80 )
|
||||
|
||||
if attribute.upload
|
||||
|
||||
# add file uploader
|
||||
u = =>
|
||||
@el.find('#' + fileUploaderId ).fineUploader(
|
||||
|
@ -378,7 +385,7 @@ class App.ControllerForm extends App.Controller
|
|||
fail: ''
|
||||
debug: false
|
||||
)
|
||||
App.Delay.set( u, 80, undefined, 'form_upload' )
|
||||
App.Delay.set( u, 100, undefined, 'form_upload' )
|
||||
|
||||
# article
|
||||
else if attribute.tag is 'article'
|
||||
|
@ -954,9 +961,6 @@ class App.ControllerForm extends App.Controller
|
|||
# if attribute.onchange[]
|
||||
|
||||
ui = @
|
||||
# item.bind('focus', ->
|
||||
# ui.log 'focus', attribute
|
||||
# );
|
||||
item.bind('change', ->
|
||||
if ui.form_data
|
||||
params = App.ControllerForm.params(@)
|
||||
|
|
|
@ -71,7 +71,6 @@ class App.TicketCreate extends App.Controller
|
|||
|
||||
activate: =>
|
||||
@navupdate '#'
|
||||
@el.find('textarea').elastic()
|
||||
|
||||
changed: =>
|
||||
formCurrent = @formParam( @el.find('.ticket-create') )
|
||||
|
@ -184,12 +183,6 @@ class App.TicketCreate extends App.Controller
|
|||
params: params
|
||||
)
|
||||
|
||||
# add elastic to textarea
|
||||
@el.find('textarea').elastic()
|
||||
|
||||
# update textarea size
|
||||
@el.find('textarea').trigger('change')
|
||||
|
||||
# show template UI
|
||||
new App.WidgetTemplate(
|
||||
el: @el.find('.ticket_template')
|
||||
|
|
|
@ -108,12 +108,6 @@ class Index extends App.ControllerContent
|
|||
form_data: @edit_form
|
||||
)
|
||||
|
||||
# add elastic to textarea
|
||||
@el.find('textarea').elastic()
|
||||
|
||||
# update textarea size
|
||||
@el.find('textarea').trigger('change')
|
||||
|
||||
new App.ControllerDrox(
|
||||
el: @el.find('.sidebar')
|
||||
data:
|
||||
|
|
|
@ -48,7 +48,6 @@ class App.TicketZoom extends App.Controller
|
|||
|
||||
activate: =>
|
||||
@navupdate '#'
|
||||
@el.find('textarea').elastic()
|
||||
|
||||
changed: =>
|
||||
formCurrent = @formParam( @el.find('.ticket-update') )
|
||||
|
@ -414,8 +413,6 @@ class Edit extends App.Controller
|
|||
]
|
||||
)
|
||||
|
||||
@el.find('textarea').elastic()
|
||||
|
||||
# remember form defaults
|
||||
@ui.formDefault = @formParam( @el.find('.ticket-update') )
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ class App.UserZoom extends App.Controller
|
|||
|
||||
activate: =>
|
||||
@navupdate '#'
|
||||
@el.find('textarea').elastic()
|
||||
|
||||
changed: =>
|
||||
formCurrent = @formParam( @el.find('.ticket-update') )
|
||||
|
|
222
app/assets/javascripts/app/lib/base/expanding.js
Normal file
222
app/assets/javascripts/app/lib/base/expanding.js
Normal 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();
|
||||
}
|
||||
});
|
||||
|
||||
}));
|
|
@ -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,'&').replace(/ {2}/g, ' ').replace(/<|>/g, '>').replace(/\n/g, '<br />');
|
||||
|
||||
// Compare curated content with curated twin.
|
||||
var twinContent = $twin.html().replace(/<br>/ig,'<br />');
|
||||
|
||||
if(forced || textareaContent+' ' !== twinContent){
|
||||
|
||||
// Add an extra white space so new rows are added when you are at the end of a row.
|
||||
$twin.html(textareaContent+' ');
|
||||
|
||||
// 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);
|
Loading…
Reference in a new issue