/*! X-editable - v1.5.1
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
* http://github.com/vitalets/x-editable
* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
/**
Form with single input element, two buttons and two states: normal/loading.
Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown.
Editableform is linked with one of input types, e.g. 'text', 'select' etc.
@class editableform
@uses text
@uses textarea
**/
(function ($) {
"use strict";
var EditableForm = function (div, options) {
this.options = $.extend({}, $.fn.editableform.defaults, options);
this.$div = $(div); //div, containing form. Not form tag. Not editable-element.
if(!this.options.scope) {
this.options.scope = this;
}
//nothing shown after init
};
EditableForm.prototype = {
constructor: EditableForm,
initInput: function() { //called once
//take input from options (as it is created in editable-element)
this.input = this.options.input;
//set initial value
//todo: may be add check: typeof str === 'string' ?
this.value = this.input.str2value(this.options.value);
//prerender: get input.$input
this.input.prerender();
},
initTemplate: function() {
this.$form = $($.fn.editableform.template);
},
initButtons: function() {
var $btn = this.$form.find('.editable-buttons');
$btn.append($.fn.editableform.buttons);
if(this.options.showbuttons === 'bottom') {
$btn.addClass('editable-buttons-bottom');
}
},
/**
Renders editableform
@method render
**/
render: function() {
//init loader
this.$loading = $($.fn.editableform.loading);
this.$div.empty().append(this.$loading);
//init form template and buttons
this.initTemplate();
if(this.options.showbuttons) {
this.initButtons();
} else {
this.$form.find('.editable-buttons').remove();
}
//show loading state
this.showLoading();
//flag showing is form now saving value to server.
//It is needed to wait when closing form.
this.isSaving = false;
/**
Fired when rendering starts
@event rendering
@param {Object} event event object
**/
this.$div.triggerHandler('rendering');
//init input
this.initInput();
//append input to form
this.$form.find('div.editable-input').append(this.input.$tpl);
//append form to container
this.$div.append(this.$form);
//render input
$.when(this.input.render())
.then($.proxy(function () {
//setup input to submit automatically when no buttons shown
if(!this.options.showbuttons) {
this.input.autosubmit();
}
//attach 'cancel' handler
this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
if(this.input.error) {
this.error(this.input.error);
this.$form.find('.editable-submit').attr('disabled', true);
this.input.$input.attr('disabled', true);
//prevent form from submitting
this.$form.submit(function(e){ e.preventDefault(); });
} else {
this.error(false);
this.input.$input.removeAttr('disabled');
this.$form.find('.editable-submit').removeAttr('disabled');
var value = (this.value === null || this.value === undefined || this.value === '') ? this.options.defaultValue : this.value;
this.input.value2input(value);
//attach submit handler
this.$form.submit($.proxy(this.submit, this));
}
/**
Fired when form is rendered
@event rendered
@param {Object} event event object
**/
this.$div.triggerHandler('rendered');
this.showForm();
//call postrender method to perform actions required visibility of form
if(this.input.postrender) {
this.input.postrender();
}
}, this));
},
cancel: function() {
/**
Fired when form was cancelled by user
@event cancel
@param {Object} event event object
**/
this.$div.triggerHandler('cancel');
},
showLoading: function() {
var w, h;
if(this.$form) {
//set loading size equal to form
w = this.$form.outerWidth();
h = this.$form.outerHeight();
if(w) {
this.$loading.width(w);
}
if(h) {
this.$loading.height(h);
}
this.$form.hide();
} else {
//stretch loading to fill container width
w = this.$loading.parent().width();
if(w) {
this.$loading.width(w);
}
}
this.$loading.show();
},
showForm: function(activate) {
this.$loading.hide();
this.$form.show();
if(activate !== false) {
this.input.activate();
}
/**
Fired when form is shown
@event show
@param {Object} event event object
**/
this.$div.triggerHandler('show');
},
error: function(msg) {
var $group = this.$form.find('.control-group'),
$block = this.$form.find('.editable-error-block'),
lines;
if(msg === false) {
$group.removeClass($.fn.editableform.errorGroupClass);
$block.removeClass($.fn.editableform.errorBlockClass).empty().hide();
} else {
//convert newline to <br> for more pretty error display
if(msg) {
lines = (''+msg).split('\n');
for (var i = 0; i < lines.length; i++) {
lines[i] = $('<div>').text(lines[i]).html();
}
msg = lines.join('<br>');
}
$group.addClass($.fn.editableform.errorGroupClass);
$block.addClass($.fn.editableform.errorBlockClass).html(msg).show();
}
},
submit: function(e) {
e.stopPropagation();
e.preventDefault();
//get new value from input
var newValue = this.input.input2value();
//validation: if validate returns string or truthy value - means error
//if returns object like {newValue: '...'} => submitted value is reassigned to it
var error = this.validate(newValue);
if ($.type(error) === 'object' && error.newValue !== undefined) {
newValue = error.newValue;
this.input.value2input(newValue);
if(typeof error.msg === 'string') {