/* * jQuery inlineEdit * * Copyright (c) 2009 Ca-Phun Ung * Licensed under the MIT (MIT-LICENSE.txt) license. * * http://github.com/caphun/jquery.inlineedit/ * * Inline (in-place) editing. */ (function( factory ) { if ( typeof define === 'function' && define.amd ) { define( ['jquery'], factory ); } else { factory( jQuery ); } }(function( $ ) { // cached values var namespace = '.inlineedit', placeholderClass = 'inlineEdit-placeholder', events = ['click', 'mouseenter','mouseleave'].join(namespace+' '); // define inlineEdit method $.fn.inlineEdit = function( options ) { this.each( function() { $.inlineEdit.getInstance( this, options ).initValue(); }); var cbBindings = function( event ) { bindings.apply( this, [event] ); }; if ($.fn.on) { $(this.context).on( events, this.selector, cbBindings ); } else { // legacy support $(this).live( events, cbBindings ); } function bindings( event ) { var widget = $.inlineEdit.getInstance( this, options ), editableElement = widget.element.find( widget.options.control ), mutated = !!editableElement.length; widget.element.removeClass( widget.options.hover ); if ( editableElement[0] != event.target && editableElement.has(event.target).length == 0 ) { switch ( event.type ) { case 'click': widget[ mutated ? 'mutate' : 'init' ](); break; case 'mouseover': // jquery 1.4.x case 'mouseout': // jquery 1.4.x case 'mouseenter': case 'mouseleave': if ( !mutated ) { widget.hoverClassChange( event ); } break; } } } } // plugin constructor $.inlineEdit = function( elem, options ) { // deep extend this.options = $.extend( true, {}, $.inlineEdit.defaults, options ); // the original element this.element = $( elem ); } // plugin instance $.inlineEdit.getInstance = function( elem, options ) { return ( $.inlineEdit.initialised( elem ) ) ? $( elem ).data( 'widget' + namespace ) : new $.inlineEdit( elem, options ); } // check if plugin initialised $.inlineEdit.initialised = function( elem ) { var init = $( elem ).data( 'init' + namespace ); return init !== undefined && init !== null ? true : false; } // plugin defaults $.inlineEdit.defaults = { hover: 'ui-state-hover', editInProgress: 'edit-in-progress', value: '', save: '', cancel: '', buttons: ' ', placeholder: 'Click to edit', control: 'input', cancelOnBlur: false, saveOnBlur: false, nl2br: true, debug: false, mutate: '' }; // plugin prototypes $.inlineEdit.prototype = { // initialisation init: function() { // set initialise flag this.element.data( 'init' + namespace, true ); // initialise value this.initValue(); // mutate this.mutate(); // save widget data this.element.data( 'widget' + namespace, this ); }, initValue: function() { this.value( $.trim( this.element.data('original-content') || this.element.html() ) || this.options.value ); if ( !this.value() ) { this.element.html( $( this.placeholderHtml() ) ); } else if ( this.options.value ) { this.element.html( this.options.value ); } }, mutate: function() { var self = this; // save a copy of self before mutation (useful for cancel) self.prevValue( self.element.html() ); self .element .html( self.mutatedHtml( self.value() ) ) .addClass( self.options.editInProgress ) .find( '.save' ) .bind( 'click', function( event ) { self.save( self.element, event ); self.change( self.element, event ); return false; }) .end() .find( '.cancel' ) .bind( 'click', function( event ) { self.cancel( self.element, event ); self.change( self.element, event ); return false; }) .end() .find( self.options.control ) .bind( 'blur', function( event ) { if (self.options.cancelOnBlur === true) { self.cancel( self.element, event ); self.change( self.element, event ); } else if (self.options.saveOnBlur == true){ self.save( self.element, event ); self.change( self.element, event ); } }) .bind( 'keyup', function( event ) { switch ( event.keyCode ) { case 13: // save on ENTER if (self.options.control !== 'textarea') { self.save( self.element, event ); self.change( self.element, event ); } break; case 27: // cancel on ESC self.cancel( self.element, event ); self.change( self.element, event ); break; } }) .focus() .end(); // trigger mutate event on mutation (i.e. edit-in-progress) if (this._callback('mutate', [event, this])) { this._debug( 'mutate:', 'Current stored value', this.value() ); } return this; }, value: function( newValue ) { if ( arguments.length ) { var value = newValue == this.placeholderHtml() ? '' : newValue; this._debug('value:','to save', value); this.element.data( 'value' + namespace, value && this.encodeHtml( this.nl2br(value) ) ); this._debug('value:','saved', this.element.data( 'value' + namespace )); } return this._decodeHtml( this.element.data( 'value' + namespace) ); }, prevValue: function( newValue ) { if ( arguments.length ) { var value = newValue === this.options.placeholder ? '' : newValue; this.element.data('prev_value' + namespace, value); } return this.element.data( 'prev_value' + namespace ); }, mutatedHtml: function( value ) { //console.log('mutatedHtml', value); return this.controls[ this.options.control ].call( this, value ); }, placeholderHtml: function() { return ''+ this.options.placeholder +''; }, buttonHtml: function( options ) { var o = $.extend({}, { before: ' ', buttons: this.options.buttons, after: '' }, options); return o.before + o.buttons + o.after; }, save: function( elem, event ) { var $control = this.element.find( this.options.control ), hash = { value: this.encodeHtml( $control.val() ) }; this._debug('save:',"Saving..."); // save value back to control to avoid XSS $control.val(hash.value); //if ( ( $.isFunction( this.options.save ) && this.options.save.call( this.element[0], event, hash, this ) ) !== false || !this.options.save ) { if (this._callback('save', [event, hash, this])) { this.value( hash.value ); this._debug( 'save:', 'Current stored value', this.value() ); } }, cancel: function( elem, event ) { var $control = this.element.find( this.options.control ), hash = { value: this.encodeHtml( $control.val() ) }; this._debug('cancel:', "Cancelling..."); //if ( ( $.isFunction( this.options.cancel ) && this.options.cancel.call( this.element[0], event, hash, this ) ) !== false || !this.options.cancel ) { if (this._callback('cancel', [event, hash, this])) { this.value( this.prevValue() ); // put back previous value this._debug( 'cancel:', 'Current stored value', this.value() ); } }, change: function( elem, event ) { var self = this; if ( this.timer ) { window.clearTimeout( this.timer ); } this.timer = window.setTimeout( function() { self._debug( 'change:', 'Change', self.value() ); self.element.html( self.value() || self.placeholderHtml() ); self.element.removeClass( self.options.hover ); self.element.removeClass( self.options.editInProgress ); self._callback( 'change', [event, self] ); self._debug( 'change:', 'Change complete' ); }, 200 ); }, controls: { textarea: function( value ) { return '' + this.buttonHtml( { before: '
' } ); }, input: function( value ) { return '' + this.buttonHtml(); } }, hoverClassChange: function( event ) { $( this.element )[ /mouseover|mouseenter/.test( event.type ) ? 'addClass':'removeClass']( this.options.hover ); }, encodeHtml: function( s ) { var encoding = [ {key: //g, value: '>'}, {key: /"/g, value: '"'} ], value = s; $.each(encoding, function(i,n) { value = value.replace(n.key, n.value); }); return value; }, br2nl: function( val ) { return this.options.nl2br ? val.replace( //g, "\n" ) : val; }, nl2br: function( val ) { return this.options.nl2br ? val.replace( /\n/g, "
" ) : val; }, _debug: function() { if (this.options && this.options.debug) { return window.console && console.log.call(console, arguments); } }, _callback: function( fn, args ) { return ($.isFunction( this.options[fn] ) && this.options[fn].apply( this.element[0], args ) ) !== false || !this.options[fn]; }, _decodeHtml: function( encoded ) { var decoded = encoded.replace(/"/g,'"'); this._debug('_decodeHtml:', decoded); return decoded; } }; }));