780 lines
26 KiB
JavaScript
780 lines
26 KiB
JavaScript
/*
|
|
BestInPlace (for jQuery)
|
|
version: 0.1.0 (01/01/2011)
|
|
@requires jQuery >= v1.4
|
|
@requires jQuery.purr to display pop-up windows
|
|
|
|
By Bernat Farrero based on the work of Jan Varwig.
|
|
Examples at http://bernatfarrero.com
|
|
|
|
Licensed under the MIT:
|
|
http://www.opensource.org/licenses/mit-license.php
|
|
|
|
Usage:
|
|
|
|
Attention.
|
|
The format of the JSON object given to the select inputs is the following:
|
|
[["key", "value"],["key", "value"]]
|
|
The format of the JSON object given to the checkbox inputs is the following:
|
|
["falseValue", "trueValue"]
|
|
*/
|
|
|
|
|
|
function BestInPlaceEditor(e) {
|
|
this.element = e;
|
|
this.initOptions();
|
|
this.bindForm();
|
|
this.initNil();
|
|
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
|
}
|
|
|
|
BestInPlaceEditor.prototype = {
|
|
// Public Interface Functions //////////////////////////////////////////////
|
|
|
|
activate : function() {
|
|
var to_display = "";
|
|
if (this.isNil()) {
|
|
to_display = "";
|
|
}
|
|
else if (this.original_content) {
|
|
to_display = this.original_content;
|
|
}
|
|
else {
|
|
if (this.sanitize) {
|
|
to_display = this.element.text();
|
|
} else {
|
|
to_display = this.element.html();
|
|
}
|
|
}
|
|
|
|
this.oldValue = this.isNil() ? "" : this.element.html();
|
|
this.display_value = to_display;
|
|
jQuery(this.activator).unbind("click", this.clickHandler);
|
|
this.activateForm();
|
|
this.element.trigger(jQuery.Event("best_in_place:activate"));
|
|
},
|
|
|
|
abort : function() {
|
|
this.activateText(this.oldValue);
|
|
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
|
this.element.trigger(jQuery.Event("best_in_place:abort"));
|
|
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
|
},
|
|
|
|
abortIfConfirm : function () {
|
|
if (!this.useConfirm) {
|
|
this.abort();
|
|
return;
|
|
}
|
|
|
|
if (confirm("Are you sure you want to discard your changes?")) {
|
|
this.abort();
|
|
}
|
|
},
|
|
|
|
update : function() {
|
|
var editor = this;
|
|
if (this.formType in {"input":1, "textarea":1} && this.getValue() == this.oldValue)
|
|
{ // Avoid request if no change is made
|
|
this.abort();
|
|
return true;
|
|
}
|
|
editor.ajax({
|
|
"type" : "post",
|
|
"dataType" : "text",
|
|
"data" : editor.requestData(),
|
|
"success" : function(data){ editor.loadSuccessCallback(data); },
|
|
"error" : function(request, error){ editor.loadErrorCallback(request, error); }
|
|
});
|
|
if (this.formType == "select") {
|
|
var value = this.getValue();
|
|
this.previousCollectionValue = value;
|
|
|
|
jQuery.each(this.values, function(i, v) {
|
|
if (value == v[0]) {
|
|
editor.element.html(v[1]);
|
|
}
|
|
}
|
|
);
|
|
} else if (this.formType == "checkbox") {
|
|
editor.element.html(this.getValue() ? this.values[1] : this.values[0]);
|
|
} else {
|
|
if (this.getValue() !== "") {
|
|
editor.element.text(this.getValue());
|
|
} else {
|
|
editor.element.html(this.nil);
|
|
}
|
|
}
|
|
editor.element.trigger(jQuery.Event("best_in_place:update"));
|
|
},
|
|
|
|
activateForm : function() {
|
|
alert("The form was not properly initialized. activateForm is unbound");
|
|
},
|
|
|
|
activateText : function(value){
|
|
this.element.html(value);
|
|
if(this.isNil()) this.element.html(this.nil);
|
|
},
|
|
|
|
// Helper Functions ////////////////////////////////////////////////////////
|
|
|
|
initOptions : function() {
|
|
// Try parent supplied info
|
|
var self = this;
|
|
self.element.parents().each(function(){
|
|
$parent = jQuery(this);
|
|
self.url = self.url || $parent.attr("data-url");
|
|
self.collection = self.collection || $parent.attr("data-collection");
|
|
self.formType = self.formType || $parent.attr("data-type");
|
|
self.objectName = self.objectName || $parent.attr("data-object");
|
|
self.attributeName = self.attributeName || $parent.attr("data-attribute");
|
|
self.activator = self.activator || $parent.attr("data-activator");
|
|
self.okButton = self.okButton || $parent.attr("data-ok-button");
|
|
self.okButtonClass = self.okButtonClass || $parent.attr("data-ok-button-class");
|
|
self.cancelButton = self.cancelButton || $parent.attr("data-cancel-button");
|
|
self.cancelButtonClass = self.cancelButtonClass || $parent.attr("data-cancel-button-class");
|
|
self.nil = self.nil || $parent.attr("data-nil");
|
|
self.inner_class = self.inner_class || $parent.attr("data-inner-class");
|
|
self.html_attrs = self.html_attrs || $parent.attr("data-html-attrs");
|
|
self.original_content = self.original_content || $parent.attr("data-original-content");
|
|
self.collectionValue = self.collectionValue || $parent.attr("data-value");
|
|
});
|
|
|
|
// Try Rails-id based if parents did not explicitly supply something
|
|
self.element.parents().each(function(){
|
|
var res = this.id.match(/^(\w+)_(\d+)$/i);
|
|
if (res) {
|
|
self.objectName = self.objectName || res[1];
|
|
}
|
|
});
|
|
|
|
// Load own attributes (overrides all others)
|
|
self.url = self.element.attr("data-url") || self.url || document.location.pathname;
|
|
self.collection = self.element.attr("data-collection") || self.collection;
|
|
self.formType = self.element.attr("data-type") || self.formtype || "input";
|
|
self.objectName = self.element.attr("data-object") || self.objectName;
|
|
self.attributeName = self.element.attr("data-attribute") || self.attributeName;
|
|
self.activator = self.element.attr("data-activator") || self.element;
|
|
self.okButton = self.element.attr("data-ok-button") || self.okButton;
|
|
self.okButtonClass = self.element.attr("data-ok-button-class") || self.okButtonClass || "";
|
|
self.cancelButton = self.element.attr("data-cancel-button") || self.cancelButton;
|
|
self.cancelButtonClass = self.element.attr("data-cancel-button-class") || self.cancelButtonClass || "";
|
|
self.nil = self.element.attr("data-nil") || self.nil || "—";
|
|
self.inner_class = self.element.attr("data-inner-class") || self.inner_class || null;
|
|
self.html_attrs = self.element.attr("data-html-attrs") || self.html_attrs;
|
|
self.original_content = self.element.attr("data-original-content") || self.original_content;
|
|
self.collectionValue = self.element.attr("data-value") || self.collectionValue;
|
|
|
|
if (!self.element.attr("data-sanitize")) {
|
|
self.sanitize = true;
|
|
}
|
|
else {
|
|
self.sanitize = (self.element.attr("data-sanitize") == "true");
|
|
}
|
|
|
|
if (!self.element.attr("data-use-confirm")) {
|
|
self.useConfirm = true;
|
|
} else {
|
|
self.useConfirm = (self.element.attr("data-use-confirm") != "false");
|
|
}
|
|
|
|
if ((self.formType == "select" || self.formType == "checkbox") && self.collection !== null)
|
|
{
|
|
self.values = jQuery.parseJSON(self.collection);
|
|
}
|
|
|
|
},
|
|
|
|
bindForm : function() {
|
|
this.activateForm = BestInPlaceEditor.forms[this.formType].activateForm;
|
|
this.getValue = BestInPlaceEditor.forms[this.formType].getValue;
|
|
},
|
|
|
|
initNil: function() {
|
|
if (this.element.html() === "")
|
|
{
|
|
this.element.html(this.nil);
|
|
}
|
|
},
|
|
|
|
isNil: function() {
|
|
// TODO: It only work when form is deactivated.
|
|
// Condition will fail when form is activated
|
|
return this.element.html() === "" || this.element.html() === this.nil;
|
|
},
|
|
|
|
getValue : function() {
|
|
alert("The form was not properly initialized. getValue is unbound");
|
|
},
|
|
|
|
// Trim and Strips HTML from text
|
|
sanitizeValue : function(s) {
|
|
return jQuery.trim(s);
|
|
},
|
|
|
|
/* Generate the data sent in the POST request */
|
|
requestData : function() {
|
|
// To prevent xss attacks, a csrf token must be defined as a meta attribute
|
|
csrf_token = jQuery('meta[name=csrf-token]').attr('content');
|
|
csrf_param = jQuery('meta[name=csrf-param]').attr('content');
|
|
|
|
var data = "_method=put";
|
|
data += "&" + this.objectName + '[' + this.attributeName + ']=' + encodeURIComponent(this.getValue());
|
|
|
|
if (csrf_param !== undefined && csrf_token !== undefined) {
|
|
data += "&" + csrf_param + "=" + encodeURIComponent(csrf_token);
|
|
}
|
|
return data;
|
|
},
|
|
|
|
ajax : function(options) {
|
|
options.url = this.url;
|
|
options.beforeSend = function(xhr){ xhr.setRequestHeader("Accept", "application/json"); };
|
|
return jQuery.ajax(options);
|
|
},
|
|
|
|
// Handlers ////////////////////////////////////////////////////////////////
|
|
|
|
loadSuccessCallback : function(data) {
|
|
data = jQuery.trim(data);
|
|
|
|
if(data && data!=""){
|
|
var response = jQuery.parseJSON(jQuery.trim(data));
|
|
if (response !== null && response.hasOwnProperty("display_as")) {
|
|
this.element.attr("data-original-content", this.element.text());
|
|
this.original_content = this.element.text();
|
|
this.element.html(response["display_as"]);
|
|
}
|
|
|
|
this.element.trigger(jQuery.Event("best_in_place:success"), data);
|
|
this.element.trigger(jQuery.Event("ajax:success"), data);
|
|
} else {
|
|
this.element.trigger(jQuery.Event("best_in_place:success"));
|
|
this.element.trigger(jQuery.Event("ajax:success"));
|
|
}
|
|
|
|
// Binding back after being clicked
|
|
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
|
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
|
|
|
if (this.collectionValue !== null && this.formType == "select") {
|
|
this.collectionValue = this.previousCollectionValue;
|
|
this.previousCollectionValue = null;
|
|
}
|
|
},
|
|
|
|
loadErrorCallback : function(request, error) {
|
|
this.activateText(this.oldValue);
|
|
|
|
this.element.trigger(jQuery.Event("best_in_place:error"), [request, error]);
|
|
this.element.trigger(jQuery.Event("ajax:error"), request, error);
|
|
|
|
// Binding back after being clicked
|
|
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
|
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
|
},
|
|
|
|
clickHandler : function(event) {
|
|
event.preventDefault();
|
|
event.data.editor.activate();
|
|
},
|
|
|
|
setHtmlAttributes : function() {
|
|
var formField = this.element.find(this.formType);
|
|
|
|
if(this.html_attrs){
|
|
var attrs = jQuery.parseJSON(this.html_attrs);
|
|
for(var key in attrs){
|
|
formField.attr(key, attrs[key]);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
// Button cases:
|
|
// If no buttons, then blur saves, ESC cancels
|
|
// If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!)
|
|
// If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels
|
|
// If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels
|
|
BestInPlaceEditor.forms = {
|
|
"input" : {
|
|
activateForm : function() {
|
|
var output = jQuery(document.createElement('form'))
|
|
.addClass('form_in_place')
|
|
.attr('action', 'javascript:void(0);')
|
|
.attr('style', 'display:inline');
|
|
var input_elt = jQuery(document.createElement('input'))
|
|
.attr('type', 'text')
|
|
.attr('name', this.attributeName)
|
|
.val(this.display_value);
|
|
if(this.inner_class !== null) {
|
|
input_elt.addClass(this.inner_class);
|
|
}
|
|
output.append(input_elt);
|
|
if(this.okButton) {
|
|
output.append(
|
|
jQuery(document.createElement('input'))
|
|
.attr('type', 'submit')
|
|
.attr('class', this.okButtonClass)
|
|
.attr('value', this.okButton)
|
|
)
|
|
}
|
|
if(this.cancelButton) {
|
|
output.append(
|
|
jQuery(document.createElement('input'))
|
|
.attr('type', 'button')
|
|
.attr('class', this.cancelButtonClass)
|
|
.attr('value', this.cancelButton)
|
|
)
|
|
}
|
|
|
|
this.element.html(output);
|
|
this.setHtmlAttributes();
|
|
// START METAMAPS CODE
|
|
//this.element.find("input[type='text']")[0].select();
|
|
this.element.find("input[type='text']")[0].focus();
|
|
// END METAMAPS CODE
|
|
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
|
|
if (this.cancelButton) {
|
|
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler);
|
|
}
|
|
this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
|
|
// START METAMAPS CODE
|
|
this.element.find("input[type='text']").bind('keydown', {editor: this}, BestInPlaceEditor.forms.input.keydownHandler);
|
|
// END METAMAPS CODE
|
|
this.element.find("input[type='text']").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
|
|
this.blurTimer = null;
|
|
this.userClicked = false;
|
|
},
|
|
|
|
getValue : function() {
|
|
return this.sanitizeValue(this.element.find("input").val());
|
|
},
|
|
|
|
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
|
inputBlurHandler : function(event) {
|
|
if (event.data.editor.okButton) {
|
|
event.data.editor.blurTimer = setTimeout(function () {
|
|
if (!event.data.editor.userClicked) {
|
|
event.data.editor.abort();
|
|
}
|
|
}, 500);
|
|
} else {
|
|
if (event.data.editor.cancelButton) {
|
|
event.data.editor.blurTimer = setTimeout(function () {
|
|
if (!event.data.editor.userClicked) {
|
|
event.data.editor.update();
|
|
}
|
|
}, 500);
|
|
} else {
|
|
event.data.editor.update();
|
|
}
|
|
}
|
|
},
|
|
|
|
submitHandler : function(event) {
|
|
event.data.editor.userClicked = true;
|
|
clearTimeout(event.data.editor.blurTimer);
|
|
event.data.editor.update();
|
|
},
|
|
|
|
cancelButtonHandler : function(event) {
|
|
event.data.editor.userClicked = true;
|
|
clearTimeout(event.data.editor.blurTimer);
|
|
event.data.editor.abort();
|
|
event.stopPropagation(); // Without this, click isn't handled
|
|
},
|
|
|
|
keyupHandler : function(event) {
|
|
if (event.keyCode == 27) {
|
|
event.data.editor.abort();
|
|
}
|
|
// START METAMAPS CODE
|
|
else if (event.keyCode == 13 && !event.shiftKey) {
|
|
event.data.editor.update();
|
|
}
|
|
// END METAMAPS CODE
|
|
}
|
|
},
|
|
|
|
"date" : {
|
|
activateForm : function() {
|
|
var that = this,
|
|
output = jQuery(document.createElement('form'))
|
|
.addClass('form_in_place')
|
|
.attr('action', 'javascript:void(0);')
|
|
.attr('style', 'display:inline'),
|
|
input_elt = jQuery(document.createElement('input'))
|
|
.attr('type', 'text')
|
|
.attr('name', this.attributeName)
|
|
.attr('value', this.sanitizeValue(this.display_value));
|
|
if(this.inner_class !== null) {
|
|
input_elt.addClass(this.inner_class);
|
|
}
|
|
output.append(input_elt)
|
|
|
|
this.element.html(output);
|
|
this.setHtmlAttributes();
|
|
this.element.find('input')[0].select();
|
|
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
|
|
this.element.find("input").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
|
|
|
|
this.element.find('input')
|
|
.datepicker({
|
|
onClose: function() {
|
|
that.update();
|
|
}
|
|
})
|
|
.datepicker('show');
|
|
},
|
|
|
|
getValue : function() {
|
|
return this.sanitizeValue(this.element.find("input").val());
|
|
},
|
|
|
|
submitHandler : function(event) {
|
|
event.data.editor.update();
|
|
},
|
|
|
|
// START METAMAPS CODE
|
|
keydownHandler : function(event) {
|
|
if (event.keyCode == 13 && !event.shiftKey) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
return false;
|
|
}
|
|
},
|
|
// END METAMAPS CODE
|
|
|
|
keyupHandler : function(event) {
|
|
if (event.keyCode == 27) {
|
|
event.data.editor.abort();
|
|
}
|
|
}
|
|
},
|
|
|
|
"select" : {
|
|
activateForm : function() {
|
|
var output = jQuery(document.createElement('form'))
|
|
.attr('action', 'javascript:void(0)')
|
|
.attr('style', 'display:inline');
|
|
selected = '',
|
|
oldValue = this.oldValue,
|
|
select_elt = jQuery(document.createElement('select'))
|
|
.attr('class', this.inned_class !== null ? this.inner_class : '' ),
|
|
currentCollectionValue = this.collectionValue;
|
|
|
|
jQuery.each(this.values, function (index, value) {
|
|
var option_elt = jQuery(document.createElement('option'))
|
|
// .attr('value', value[0])
|
|
.val(value[0])
|
|
.html(value[1]);
|
|
if(value[0] == currentCollectionValue) {
|
|
option_elt.attr('selected', 'selected');
|
|
}
|
|
select_elt.append(option_elt);
|
|
});
|
|
output.append(select_elt);
|
|
|
|
this.element.html(output);
|
|
this.setHtmlAttributes();
|
|
this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
|
this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
|
this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
|
|
this.element.find("select")[0].focus();
|
|
},
|
|
|
|
getValue : function() {
|
|
return this.sanitizeValue(this.element.find("select").val());
|
|
// return this.element.find("select").val();
|
|
},
|
|
|
|
blurHandler : function(event) {
|
|
event.data.editor.update();
|
|
},
|
|
|
|
keyupHandler : function(event) {
|
|
if (event.keyCode == 27) event.data.editor.abort();
|
|
}
|
|
},
|
|
|
|
"checkbox" : {
|
|
activateForm : function() {
|
|
this.collectionValue = !this.getValue();
|
|
this.setHtmlAttributes();
|
|
this.update();
|
|
},
|
|
|
|
getValue : function() {
|
|
return this.collectionValue;
|
|
}
|
|
},
|
|
|
|
"textarea" : {
|
|
activateForm : function() {
|
|
// grab width and height of text
|
|
width = this.element.css('width');
|
|
height = this.element.css('height');
|
|
|
|
// construct form
|
|
var output = jQuery(document.createElement('form'))
|
|
.attr('action', 'javascript:void(0)')
|
|
.attr('style', 'display:inline')
|
|
.append(jQuery(document.createElement('textarea'))
|
|
.val(this.sanitizeValue(this.display_value)));
|
|
if(this.okButton) {
|
|
output.append(
|
|
jQuery(document.createElement('input'))
|
|
.attr('type', 'submit')
|
|
.attr('value', this.okButton)
|
|
);
|
|
}
|
|
if(this.cancelButton) {
|
|
output.append(
|
|
jQuery(document.createElement('input'))
|
|
.attr('type', 'button')
|
|
.attr('value', this.cancelButton)
|
|
)
|
|
}
|
|
|
|
this.element.html(output);
|
|
this.setHtmlAttributes();
|
|
|
|
// set width and height of textarea
|
|
jQuery(this.element.find("textarea")[0]).css({ 'min-width': width, 'min-height': height });
|
|
jQuery(this.element.find("textarea")[0]).elastic();
|
|
|
|
this.element.find("textarea")[0].focus();
|
|
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler);
|
|
if (this.cancelButton) {
|
|
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler);
|
|
}
|
|
this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
|
|
// START METAMAPS CODE
|
|
this.element.find("textarea").bind('keydown', {editor: this}, BestInPlaceEditor.forms.textarea.keydownHandler);
|
|
// END METAMAPS CODE
|
|
this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
|
|
this.blurTimer = null;
|
|
this.userClicked = false;
|
|
},
|
|
|
|
getValue : function() {
|
|
return this.sanitizeValue(this.element.find("textarea").val());
|
|
},
|
|
|
|
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
|
blurHandler : function(event) {
|
|
if (event.data.editor.okButton) {
|
|
event.data.editor.blurTimer = setTimeout(function () {
|
|
if (!event.data.editor.userClicked) {
|
|
event.data.editor.abortIfConfirm();
|
|
}
|
|
}, 500);
|
|
} else {
|
|
if (event.data.editor.cancelButton) {
|
|
event.data.editor.blurTimer = setTimeout(function () {
|
|
if (!event.data.editor.userClicked) {
|
|
event.data.editor.update();
|
|
}
|
|
}, 500);
|
|
} else {
|
|
event.data.editor.update();
|
|
}
|
|
}
|
|
},
|
|
|
|
submitHandler : function(event) {
|
|
event.data.editor.userClicked = true;
|
|
clearTimeout(event.data.editor.blurTimer);
|
|
event.data.editor.update();
|
|
},
|
|
|
|
cancelButtonHandler : function(event) {
|
|
event.data.editor.userClicked = true;
|
|
clearTimeout(event.data.editor.blurTimer);
|
|
event.data.editor.abortIfConfirm();
|
|
event.stopPropagation(); // Without this, click isn't handled
|
|
},
|
|
|
|
// START METAMAPS CODE
|
|
keydownHandler : function(event) {
|
|
if (event.keyCode == 13 && !event.shiftKey) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
return false;
|
|
}
|
|
},
|
|
// END METAMAPS CODE
|
|
|
|
keyupHandler : function(event) {
|
|
if (event.keyCode == 27) {
|
|
event.data.editor.abortIfConfirm();
|
|
}
|
|
// START METAMAPS CODE
|
|
else if (event.keyCode == 13 && !event.shiftKey) {
|
|
event.data.editor.update();
|
|
}
|
|
// END METAMAPS CODE
|
|
}
|
|
}
|
|
};
|
|
|
|
jQuery.fn.best_in_place = function() {
|
|
|
|
function setBestInPlace(element) {
|
|
if (!element.data('bestInPlaceEditor')) {
|
|
element.data('bestInPlaceEditor', new BestInPlaceEditor(element));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
jQuery(this.context).delegate(this.selector, 'click', function () {
|
|
var el = jQuery(this);
|
|
if (setBestInPlace(el))
|
|
el.click();
|
|
});
|
|
|
|
this.each(function () {
|
|
setBestInPlace(jQuery(this));
|
|
});
|
|
|
|
return this;
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* @name Elastic
|
|
* @descripton Elastic is Jquery plugin that grow and shrink your textareas automaticliy
|
|
* @version 1.6.5
|
|
* @requires Jquery 1.2.6+
|
|
*
|
|
* @author Jan Jarfalk
|
|
* @author-email jan.jarfalk@unwrongest.com
|
|
* @author-website http://www.unwrongest.com
|
|
*
|
|
* @licens MIT License - http://www.opensource.org/licenses/mit-license.php
|
|
*/
|
|
|
|
(function(jQuery){
|
|
if (typeof jQuery.fn.elastic !== 'undefined') return;
|
|
|
|
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'];
|
|
|
|
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'}),
|
|
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,
|
|
i = 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
|
|
i = mimics.length;
|
|
while(i--){
|
|
$twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
|
|
}
|
|
|
|
|
|
// Sets a given height and overflow state on the textarea
|
|
function setHeightAndOverflow(height, overflow){
|
|
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() {
|
|
|
|
// Get curated content from the textarea.
|
|
var textareaContent = $textarea.val().replace(/&/g,'&').replace(/ /g, ' ').replace(/<|>/g, '>').replace(/\n/g, '<br />');
|
|
|
|
// Compare curated content with curated twin.
|
|
var twinContent = $twin.html().replace(/<br>/ig,'<br />');
|
|
|
|
if(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();
|
|
});
|
|
|
|
// Compact textarea on blur
|
|
// Lets animate this....
|
|
$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.on("input paste", function(e){ setTimeout( update, 250); });
|
|
|
|
// Run update once when elastic is initialized
|
|
update();
|
|
|
|
});
|
|
|
|
}
|
|
});
|
|
})(jQuery);
|