Improved textarea expanding.
This commit is contained in:
parent
6234ec27cd
commit
96e338f86a
7 changed files with 233 additions and 186 deletions
|
@ -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(@)
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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') )
|
||||||
|
|
||||||
|
|
|
@ -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') )
|
||||||
|
|
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