You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
374 lines
12 KiB
JavaScript
374 lines
12 KiB
JavaScript
/*
|
|
* jQuery plugin that makes elements editable
|
|
*
|
|
* @author Victor Jonsson (http://victorjonsson.se/)
|
|
* @website https://github.com/victorjonsson/jquery-editable/
|
|
* @license GPLv2 http://www.gnu.org/licenses/gpl-2.0.html
|
|
* @version 1.3.6.dev
|
|
* @donations http://victorjonsson.se/donations/
|
|
*/
|
|
(function($, window) {
|
|
|
|
'use strict';
|
|
|
|
var $win = $(window), // Reference to window
|
|
|
|
// Reference to textarea
|
|
$textArea = false,
|
|
|
|
// Reference to currently edit element
|
|
$currentlyEdited = false,
|
|
|
|
// Some constants
|
|
EVENT_ATTR = 'data-edit-event',
|
|
IS_EDITING_ATTR = 'data-is-editing',
|
|
EMPTY_ATTR = 'data-is-empty',
|
|
DBL_TAP_EVENT = 'dbltap',
|
|
SUPPORTS_TOUCH = 'ontouchend' in window,
|
|
TINYMCE_INSTALLED = 'tinyMCE' in window && typeof window.tinyMCE.init == 'function',
|
|
|
|
// reference to old is function
|
|
oldjQueryIs = $.fn.is,
|
|
|
|
/*
|
|
* Function responsible of triggering double tap event
|
|
*/
|
|
lastTap = 0,
|
|
tapper = function() {
|
|
var now = new Date().getTime();
|
|
if( (now-lastTap) < 250 ) {
|
|
$(this).trigger(DBL_TAP_EVENT);
|
|
}
|
|
lastTap = now;
|
|
},
|
|
|
|
/**
|
|
* Event listener that largens font size
|
|
*/
|
|
keyHandler = function(e) {
|
|
if( e.keyCode == 13 && e.data.closeOnEnter ) {
|
|
$currentlyEdited.editable('close');
|
|
}
|
|
else if( e.data.toggleFontSize && (e.metaKey && (e.keyCode == 38 || e.keyCode == 40)) ) {
|
|
var fontSize = parseInt($textArea.css('font-size'), 10);
|
|
fontSize += e.keyCode == 40 ? -1 : 1;
|
|
$textArea.css('font-size', fontSize+'px');
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Adjusts the height of the textarea to remove scroll
|
|
* @todo This way of doing it does not make the textarea smaller when the number of text lines gets smaller
|
|
*/
|
|
adjustTextAreaHeight = function() {
|
|
if( $textArea[0].scrollHeight !== parseInt($textArea.attr('data-scroll'), 10) ) {
|
|
$textArea.css('height', $textArea[0].scrollHeight +'px');
|
|
$textArea.attr('data-scroll', $textArea[0].scrollHeight);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param {jQuery} $el
|
|
* @param {String} newText
|
|
*/
|
|
resetElement = function($el, newText, emptyMessage) {
|
|
$el.removeAttr(IS_EDITING_ATTR);
|
|
|
|
if (newText.length == 0 && emptyMessage) {
|
|
$el.html(emptyMessage);
|
|
$el.attr(EMPTY_ATTR, 'empty');
|
|
} else {
|
|
$el.html( newText );
|
|
$el.removeAttr(EMPTY_ATTR);
|
|
}
|
|
$textArea.remove();
|
|
},
|
|
|
|
|
|
/**
|
|
* Function creating editor
|
|
*/
|
|
elementEditor = function($el, opts) {
|
|
|
|
if( $el.is(':editing') )
|
|
return;
|
|
|
|
$currentlyEdited = $el;
|
|
$el.attr(IS_EDITING_ATTR, '1');
|
|
|
|
if ($el.is(':empty')) {
|
|
$el.removeAttr(EMPTY_ATTR);
|
|
$el.html('');
|
|
}
|
|
|
|
var defaultText = $.trim( $el.html() ),
|
|
defaultFontSize = $el.css('font-size'),
|
|
elementHeight = $el.height(),
|
|
textareaStyle = 'width: 96%; padding:0; margin:0; border:0; background:none;'+
|
|
'font-family: '+$el.css('font-family')+'; font-size: '+$el.css('font-size')+';'+
|
|
'font-weight: '+$el.css('font-weight')+';';
|
|
|
|
if( opts.lineBreaks ) {
|
|
defaultText = defaultText.replace(/<br( |)(|\/)>/g, '\n');
|
|
}
|
|
|
|
$textArea = $('<textarea></textarea>');
|
|
$el.text('');
|
|
|
|
if( navigator.userAgent.match(/webkit/i) !== null ) {
|
|
textareaStyle = document.defaultView.getComputedStyle($el.get(0), "").cssText;
|
|
}
|
|
|
|
// The editor should always be static
|
|
textareaStyle += 'position: static';
|
|
|
|
/*
|
|
TINYMCE EDITOR
|
|
*/
|
|
if( opts.tinyMCE !== false ) {
|
|
var id = 'editable-area-'+(new Date().getTime());
|
|
$textArea
|
|
.val(defaultText)
|
|
.appendTo($el)
|
|
.attr('id', id);
|
|
|
|
if( typeof opts.tinyMCE != 'object' )
|
|
opts.tinyMCE = {};
|
|
|
|
opts.tinyMCE.mode = 'exact';
|
|
opts.tinyMCE.elements = id;
|
|
opts.tinyMCE.width = $el.innerWidth();
|
|
opts.tinyMCE.height = $el.height() + 200;
|
|
opts.tinyMCE.theme_advanced_resize_vertical = true;
|
|
|
|
opts.tinyMCE.setup = function (ed) {
|
|
ed.onInit.add(function(editor, evt) {
|
|
var editorWindow = editor.getWin();
|
|
var hasPressedKey = false;
|
|
var editorBlur = function() {
|
|
|
|
var newText = $(editor.getDoc()).find('body').html();
|
|
if( $(newText).get(0).nodeName == $el.get(0).nodeName ) {
|
|
newText = $(newText).html();
|
|
}
|
|
|
|
// Update element and remove editor
|
|
resetElement($el, newText, opts.emptyMessage);
|
|
editor.remove();
|
|
$textArea = false;
|
|
$win.unbind('click', editorBlur);
|
|
$currentlyEdited = false;
|
|
|
|
// Run callback
|
|
if( typeof opts.callback == 'function' ) {
|
|
opts.callback({
|
|
content : newText == defaultText || !hasPressedKey ? false : newText,
|
|
fontSize : false,
|
|
$el : $el
|
|
});
|
|
}
|
|
};
|
|
|
|
// Blur editor when user clicks outside the editor
|
|
setTimeout(function() {
|
|
$win.bind('click', editorBlur);
|
|
}, 500);
|
|
|
|
// Create a dummy textarea that will called upon when
|
|
// programmatically interacting with the editor
|
|
$textArea = $('<textarea></textarea>');
|
|
$textArea.bind('blur', editorBlur);
|
|
|
|
editorWindow.onkeydown = function() {
|
|
hasPressedKey = true;
|
|
};
|
|
|
|
editorWindow.focus();
|
|
});
|
|
};
|
|
|
|
tinyMCE.init(opts.tinyMCE);
|
|
}
|
|
|
|
/*
|
|
TEXTAREA EDITOR
|
|
*/
|
|
else {
|
|
|
|
if( opts.toggleFontSize || opts.closeOnEnter ) {
|
|
$win.bind('keydown', opts, keyHandler);
|
|
}
|
|
$win.bind('keyup', adjustTextAreaHeight);
|
|
|
|
$textArea
|
|
.val(defaultText)
|
|
.blur(function() {
|
|
|
|
$currentlyEdited = false;
|
|
|
|
// Get new text and font size
|
|
var newText = $.trim( $textArea.val() ),
|
|
newFontSize = $textArea.css('font-size');
|
|
if( opts.lineBreaks ) {
|
|
newText = newText.replace(new RegExp('\n','g'), '<br />');
|
|
}
|
|
|
|
// Update element
|
|
resetElement($el, newText, opts.emptyMessage);
|
|
if( newFontSize != defaultFontSize ) {
|
|
$el.css('font-size', newFontSize);
|
|
}
|
|
|
|
// remove textarea and size toggles
|
|
$win.unbind('keydown', keyHandler);
|
|
$win.unbind('keyup', adjustTextAreaHeight);
|
|
|
|
// Run callback
|
|
if( typeof opts.callback == 'function' ) {
|
|
opts.callback({
|
|
content : newText == defaultText ? false : newText,
|
|
fontSize : newFontSize == defaultFontSize ? false : newFontSize,
|
|
$el : $el
|
|
});
|
|
}
|
|
})
|
|
.attr('style', textareaStyle)
|
|
.appendTo($el)
|
|
.css({
|
|
margin: 0,
|
|
padding: 0,
|
|
height : elementHeight +'px',
|
|
overflow : 'hidden'
|
|
})
|
|
.css(opts.editorStyle)
|
|
.focus()
|
|
.get(0).select();
|
|
|
|
adjustTextAreaHeight();
|
|
|
|
}
|
|
|
|
$el.trigger('edit', [$textArea]);
|
|
},
|
|
|
|
/**
|
|
* Event listener
|
|
*/
|
|
editEvent = function(event) {
|
|
if( $currentlyEdited !== false ) {
|
|
// Not closing the currently open editor before opening a new
|
|
// editor makes things go crazy
|
|
$currentlyEdited.editable('close');
|
|
elementEditor($(this), event.data);
|
|
}
|
|
else {
|
|
elementEditor($(this), event.data);
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Jquery plugin that makes elments editable
|
|
* @param {Object|String} [opts] Either callback function or the string 'destroy' if wanting to remove the editor event
|
|
* @return {jQuery|Boolean}
|
|
*/
|
|
$.fn.editable = function(opts) {
|
|
|
|
if(typeof opts == 'string') {
|
|
|
|
if( this.is(':editable') ) {
|
|
|
|
switch (opts) {
|
|
case 'open':
|
|
if( !this.is(':editing') ) {
|
|
this.trigger(this.attr(EVENT_ATTR));
|
|
}
|
|
break;
|
|
case 'close':
|
|
if( this.is(':editing') ) {
|
|
$textArea.trigger('blur');
|
|
}
|
|
break;
|
|
case 'destroy':
|
|
if( this.is(':editing') ) {
|
|
$textArea.trigger('blur');
|
|
}
|
|
this.unbind(this.attr(EVENT_ATTR));
|
|
this.removeAttr(EVENT_ATTR);
|
|
break;
|
|
default:
|
|
console.warn('Unknown command "'+opts+'" for jquery.editable');
|
|
}
|
|
|
|
} else {
|
|
console.error('Calling .editable() on an element that is not editable, call .editable() first');
|
|
}
|
|
}
|
|
else {
|
|
|
|
if( this.is(':editable') ) {
|
|
console.warn('Making an already editable element editable, call .editable("destroy") first');
|
|
this.editable('destroy');
|
|
}
|
|
|
|
opts = $.extend({
|
|
event : 'dblclick',
|
|
touch : true,
|
|
lineBreaks : true,
|
|
toggleFontSize : true,
|
|
closeOnEnter : false,
|
|
emptyMessage : false,
|
|
tinyMCE : false,
|
|
editorStyle : {}
|
|
}, opts);
|
|
|
|
if( opts.tinyMCE !== false && !TINYMCE_INSTALLED ) {
|
|
console.warn('Trying to use tinyMCE as editor but id does not seem to be installed');
|
|
opts.tinyMCE = false;
|
|
}
|
|
|
|
if( SUPPORTS_TOUCH && opts.touch ) {
|
|
opts.event = DBL_TAP_EVENT;
|
|
this.unbind('touchend', tapper);
|
|
this.bind('touchend', tapper);
|
|
}
|
|
else {
|
|
opts.event += '.textEditor';
|
|
}
|
|
|
|
this.bind(opts.event, opts, editEvent);
|
|
this.attr(EVENT_ATTR, opts.event);
|
|
|
|
// If it is empty to start with, apply the empty message
|
|
if (this.html().length == 0 && opts.emptyMessage) {
|
|
this.html(opts.emptyMessage);
|
|
this.attr(EMPTY_ATTR, 'empty');
|
|
} else {
|
|
this.removeAttr(EMPTY_ATTR);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Add :editable :editing to $.is()
|
|
* @param {Object} statement
|
|
* @return {*}
|
|
*/
|
|
$.fn.is = function(statement) {
|
|
if( typeof statement == 'string' && statement.indexOf(':') === 0) {
|
|
if( statement == ':editable' ) {
|
|
return this.attr(EVENT_ATTR) !== undefined;
|
|
} else if( statement == ':editing' ) {
|
|
return this.attr(IS_EDITING_ATTR) !== undefined;
|
|
} else if( statement == ':empty' ) {
|
|
return this.attr(EMPTY_ATTR) !== undefined;
|
|
}
|
|
}
|
|
return oldjQueryIs.apply(this, arguments);
|
|
}
|
|
|
|
})(jQuery, window);
|