Merge branch 'release-1.2.0'

This commit is contained in:
vitalets
2012-12-07 00:06:05 +04:00
34 changed files with 1050 additions and 776 deletions

@ -2,6 +2,34 @@ X-editable changelog
============================= =============================
Version 1.2.0 Dec 6, 2012
----------------------------
[enh #36] 'submit' method: added 'ajaxOptions' property to modify ajax request (vitalets)
[enh] inputs now internally use 'value2submit' method instead of previous 'value2str' (vitalets)
[enh] editableContainer removed from docs (vitalets)
[enh] editableContainer: removed 'autohide' option and 'cancel' event. Use 'hidden' event instead (vitalets)
[enh] 'hidden' event: added param 'reason' that points to reason caused hiding (vitalets)
[enh] 'select' submit by enter (vitalets)
[bug #37] fix incorrectly shown datepicker in jquery 1.7.1 + webkit (vitalets)
[enh] added url param 'jquery' to run tests in different versions of jquery, e.g. '&jquery=1.7.2' (vitalets)
[enh] 'enablefocus' option removed. More efficient to use 'save/hide' events to set focus to any element (vitalets)
[enh] 'init' event was added due to removal of render event (vitalets)
[enh] 'render' event was removed, use 'display' callback instead (vitalets)
[enh] 'checklist' submit value as array, not comma separated string (vitalets)
[enh] 'checklist' was refactored: options 'viewseparator', 'limit', 'limitText' are supressed by 'display' callback (vitalets)
[enh] new option: 'display' callback. Makes far more flexible rendering value into element's text. (vitalets)
[bug] fix typos (atrophic)
[enh] all callbacks scope changed to element (vitalets)
[enh] new option: 'savenochange' to save or cancel value when it was not changed in form (vitalets)
[enh] composite pk can be defined as JSON in data-pk attribute (vitalets)
[enh #30] new option 'sourceCache' true|false to disable cache for select (vitalets)
[bug #34] inputclass span* broken with fluid bootstrap layout. Classes changed to 'input-*'. (vitalets)
[enh] utils now added to $.fn.editableutils instead of $.fn.editableform.utils (vitalets)
[enh] input types now added to $.fn.editabletypes instead of $.fn.editableform.types (vitalets)
[enh] playground and tests now use requirejs (vitalets)
[bug #27] 'today' button toggle bug in bootstrap-datepicker (vitalets)
Version 1.1.1 Nov 30, 2012 Version 1.1.1 Nov 30, 2012
---------------------------- ----------------------------
[enh] 'showbuttons' option to hide buttons in form (vitalets) [enh] 'showbuttons' option to hide buttons in form (vitalets)
@ -29,7 +57,7 @@ Version 1.0.1 Nov 22, 2012
[enh] contribution guide in README.md (vitalets) [enh] contribution guide in README.md (vitalets)
[enh #7] 'shown', 'hidden' events added (vitalets) [enh #7] 'shown', 'hidden' events added (vitalets)
[enh #1] params can be a function to calculate it dynamically (vitalets) [enh #1] params can be a function to calculate it dynamically (vitalets)
[enh #6] do not preventDetault() in click when toggle='manual'. This allows to have clickable links (vitalets) [enh #6] do not preventDefault() in click when toggle='manual'. This allows to have clickable links (vitalets)
[bug #3] should not mark element with unsave css if url is user's function (vitalets) [bug #3] should not mark element with unsave css if url is user's function (vitalets)
@ -48,13 +76,13 @@ Here list of differences to help you to upgrade your application:
[change] 'toggle' option value can be only click|manual (not toggling element id). In case of 'manual' you should write handler calling 'show' method. [change] 'toggle' option value can be only click|manual (not toggling element id). In case of 'manual' you should write handler calling 'show' method.
[change] 'validate' option cannot be defined as object anymore. [change] 'validate' option cannot be defined as object anymore.
[change] events 'init', 'update', 'shown', 'hidden' removed. Events 'save', 'cancel' added. Event 'render' remains. [change] events 'init', 'update', 'shown', 'hidden' removed. Events 'save', 'cancel' added. Event 'render' remains.
[change] input's optiom 'template' renamed to 'tpl' (to exclude conflict with container's template). [change] input's option 'template' renamed to 'tpl' (to exclude conflict with container's template).
[change] value can be stored internaly as object (previously was always string). Usefull for date inupt. [change] value can be stored internally as object (previously was always string). Useful for date input.
[change] 'error' callback option is removed. 'success' callback remained. [change] 'error' callback option is removed. 'success' callback remained.
[enh] 'source' option in select can be array of structure [{value: 1, text: 'abc'}, {...}]. This allows to keep ordering of items in dropdown list. Previous format is supported for compability. [enh] 'source' option in select can be array of structure [{value: 1, text: 'abc'}, {...}]. This allows to keep ordering of items in dropdown list. Previous format is supported for compatibility.
[enh] api method 'setValue' to set manually value of editable. [enh] api method 'setValue' to set manually value of editable.
[change] locales directory is excluded from bootstrap-datepicker input. If you need localization you should jus download corresponding file from github. [change] locales directory is excluded from bootstrap-datepicker input. If you need localization you should jus download corresponding file from github.
[change] date and dateui specific options can be set only via 'datepicker' option in first level of config (previously it was possible to set some options directly in config, e.g. weekStart). [change] date and dateui specific options can be set only via 'datepicker' option in first level of config (previously it was possible to set some options directly in config, e.g. weekStart).
[change] if 'url' option defined as function - it is used as submit method instead of ajax (previously it was dynamically return url string and ajax occured anyway) [change] if 'url' option defined as function - it is used as submit method instead of ajax (previously it was dynamically return url string and ajax occurred anyway)
Also all known bugs of bootstrap-editable were closed. Also all known bugs of bootstrap-editable were closed.

@ -7,7 +7,7 @@ It is a new life of [bootstrap-editable plugin](http://github.com/vitalets/boots
See **http://vitalets.github.com/x-editable** See **http://vitalets.github.com/x-editable**
## Reporting issues ## Reporting issues
When creating issues please provide jsFiddle example. You can just fork [this fiddle](http://jsfiddle.net/xBB5x/5/) as starting point. When creating issues please provide jsFiddle example. You can easily fork one of templates: [bootstrap](http://jsfiddle.net/xBB5x/25/), [jqueryui](http://jsfiddle.net/xBB5x/24/), [plain](http://jsfiddle.net/xBB5x/23/).
Your feedback is very appreciated! Your feedback is very appreciated!
## Contribution ## Contribution
@ -19,7 +19,7 @@ A few steps how to start contributing:
**x-editable** **x-editable**
| -- **lib** (repo related to <code>dev</code> and <code>master</code> branches) | -- **lib** (repo related to <code>dev</code> and <code>master</code> branches)
| -- **gh-pages** (repo related to <code>gh-pages</code> branch for docs & demo) | -- **gh-pages** (repo related to <code>gh-pages</code> branch for docs & demo)
| -- **playground** (simple node-server and html page for testing, [playground.zip](https://github.com/downloads/vitalets/x-editable/playground.zip)) | -- **playground** (simple node-server and html page for testing, [playground_1.2.zip](https://github.com/downloads/vitalets/x-editable/playground_1.2.zip), **updated in 1.2.0!**)
To make it easy follow this script ( _assuming you have [nodejs](http://nodejs.org) installed_ ). To make it easy follow this script ( _assuming you have [nodejs](http://nodejs.org) installed_ ).
Please replace <code>&lt;your-github-name&gt;</code> with your name: Please replace <code>&lt;your-github-name&gt;</code> with your name:
@ -43,7 +43,7 @@ npm install
cd .. cd ..
#playground #playground
#download playground.zip from https://github.com/downloads/vitalets/x-editable/playground.zip #download playground.zip from https://github.com/downloads/vitalets/x-editable/playground_1.2.zip
unzip playground.zip unzip playground.zip
cd playground cd playground
npm install npm install

@ -100,10 +100,19 @@ module.exports = function(grunt) {
//module for testing //module for testing
var module = ''; var module = '';
//module = '&module=textarea'; // module = '&module=textarea';
//module = '&module=select'; //module = '&module=select';
//module = '&module=text'; //module = '&module=text';
var qunit_testover = [];
['bootstrap', 'jqueryui', 'plain'].forEach(function(f){
['popup', 'inline'].forEach(function(c){
['1.6.4', '1.7.1', '1.7.2', '1.8.2', '1.8.3'].forEach(function(jqver) {
qunit_testover.push('http://localhost:8000/test/index.html?f='+f+'&c='+c+'&jquery='+jqver+module);
});
});
});
//get js and css for different builds //get js and css for different builds
var files = getFiles(); var files = getFiles();
@ -133,8 +142,9 @@ module.exports = function(grunt) {
plain: [ plain: [
'http://localhost:8000/test/index.html?f=plain&c=popup'+module, 'http://localhost:8000/test/index.html?f=plain&c=popup'+module,
'http://localhost:8000/test/index.html?f=plain&c=inline'+module 'http://localhost:8000/test/index.html?f=plain&c=inline'+module
] ],
// files: ['test/index.html'] //test all builds under several versions of jquery
testover: qunit_testover
}, },
server: { server: {
port: 8000, port: 8000,
@ -208,38 +218,14 @@ module.exports = function(grunt) {
} }
} }
}, },
//compress does not work properly for MAC OS (see https://github.com/vitalets/bootstrap-editable/issues/19)
//zip will be created manually
/*
compress: {
zip: {
options: {
mode: "zip",
//TODO: unfortunatly here <%= dist_source %> and <config:dist_source> does not work
basePath: "dist"
},
files: {
"<%= dist %>/bootstrap-editable-v<%= pkg.version %>.zip": ["<%= dist_source %>/ **", "<%= dist %>/libs/ **"]
}
},
tgz: {
options: {
mode: "tgz",
basePath: "dist"
},
files: {
"<%= dist %>/bootstrap-editable-v<%= pkg.version %>.tar.gz": ["<%= dist_source %>/ **", "<%= dist %>/libs/ **"]
}
}
},
*/
uglify: {} uglify: {}
}); });
//test task //test task
grunt.registerTask('test', 'lint server qunit:bootstrap'); grunt.registerTask('test', 'lint server qunit:bootstrap');
grunt.registerTask('testall', 'lint server qunit'); grunt.registerTask('testall', 'lint server qunit:bootstrap qunit:jqueryui qunit:plain');
grunt.registerTask('testover', 'lint server qunit:testover');
// Default task. // Default task.
// grunt.registerTask('default', 'lint qunit'); // grunt.registerTask('default', 'lint qunit');

@ -2,7 +2,7 @@
"name": "X-editable", "name": "X-editable",
"title": "X-editable", "title": "X-editable",
"description": "In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery", "description": "In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery",
"version": "1.1.1", "version": "1.2.0",
"homepage": "http://github.com/vitalets/x-editable", "homepage": "http://github.com/vitalets/x-editable",
"author": { "author": {
"name": "Vitaliy Potapov", "name": "Vitaliy Potapov",

@ -1,5 +1,5 @@
.editable-container { .editable-container {
max-width: none; /* without this rule poshytip does not stretch */ max-width: none !important; /* without this rule poshytip/tooltip does not stretch */
} }
.editable-container.popover { .editable-container.popover {
@ -10,4 +10,8 @@
.editable-container.editable-inline { .editable-container.editable-inline {
display: inline; display: inline;
vertical-align: middle; vertical-align: middle;
}
.editable-container.ui-widget {
font-size: inherit; /* jqueryui widget font 1.1em too big, overwrite it */
} }

@ -20,7 +20,7 @@ Applied as jQuery method.
init: function(element, options) { init: function(element, options) {
this.$element = $(element); this.$element = $(element);
//todo: what is in priority: data or js? //todo: what is in priority: data or js?
this.options = $.extend({}, $.fn.editableContainer.defaults, $.fn.editableform.utils.getConfigData(this.$element), options); this.options = $.extend({}, $.fn.editableContainer.defaults, $.fn.editableutils.getConfigData(this.$element), options);
this.splitOptions(); this.splitOptions();
this.initContainer(); this.initContainer();
@ -75,12 +75,15 @@ Applied as jQuery method.
}, },
initForm: function() { initForm: function() {
this.formOptions.scope = this.$element[0]; //set scope of form callbacks to element
this.$form = $('<div>') this.$form = $('<div>')
.editableform(this.formOptions) .editableform(this.formOptions)
.on({ .on({
save: $.proxy(this.save, this), save: $.proxy(this.save, this),
cancel: $.proxy(this.cancel, this), cancel: $.proxy(function(){
show: $.proxy(this.setPosition, this), //re-position container every time form is shown (after loading state) this.hide('cancel');
}, this),
show: $.proxy(this.setPosition, this), //re-position container every time form is shown (occurs each time after loading state)
rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
rendered: $.proxy(function(){ rendered: $.proxy(function(){
/** /**
@ -119,7 +122,7 @@ Applied as jQuery method.
/** /**
Shows container with form Shows container with form
@method show() @method show()
@param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true. @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
**/ **/
show: function (closeAll) { show: function (closeAll) {
this.$element.addClass('editable-open'); this.$element.addClass('editable-open');
@ -143,8 +146,9 @@ Applied as jQuery method.
/** /**
Hides container with form Hides container with form
@method hide() @method hide()
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|undefined (=manual)</code>
**/ **/
hide: function() { hide: function(reason) {
if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) { if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
return; return;
} }
@ -154,9 +158,17 @@ Applied as jQuery method.
Fired when container was hidden. It occurs on both save or cancel. Fired when container was hidden. It occurs on both save or cancel.
@event hidden @event hidden
@param {Object} event event object @param {object} event event object
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|undefined (=manual)</code>
@example
$('#username').on('hidden', function(e, reason) {
if(reason === 'save' || reason === 'cancel') {
//auto-open next editable
$(this).closest('tr').next().find('.editable').editable('show');
}
});
**/ **/
this.$element.triggerHandler('hidden'); this.$element.triggerHandler('hidden', reason);
}, },
/* internal hide method. To be overwritten in child classes */ /* internal hide method. To be overwritten in child classes */
@ -167,7 +179,7 @@ Applied as jQuery method.
/** /**
Toggles container visibility (show / hide) Toggles container visibility (show / hide)
@method toggle() @method toggle()
@param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true. @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
**/ **/
toggle: function(closeAll) { toggle: function(closeAll) {
if(this.tip && this.tip().is(':visible')) { if(this.tip && this.tip().is(':visible')) {
@ -185,23 +197,8 @@ Applied as jQuery method.
//tbd in child class //tbd in child class
}, },
cancel: function() {
if(this.options.autohide) {
this.hide();
}
/**
Fired when form was cancelled by user
@event cancel
@param {Object} event event object
**/
this.$element.triggerHandler('cancel');
},
save: function(e, params) { save: function(e, params) {
if(this.options.autohide) { this.hide('save');
this.hide();
}
/** /**
Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance
@ -262,8 +259,8 @@ Applied as jQuery method.
*/ */
closeOthers: function(element) { closeOthers: function(element) {
$('.editable-open').each(function(i, el){ $('.editable-open').each(function(i, el){
//do nothing with passed element //do nothing with passed element and it's children
if(el === element) { if(el === element || $(el).find(element).length) {
return; return;
} }
@ -276,7 +273,7 @@ Applied as jQuery method.
} }
if(ec.options.onblur === 'cancel') { if(ec.options.onblur === 'cancel') {
$el.data('editableContainer').hide(); $el.data('editableContainer').hide('onblur');
} else if(ec.options.onblur === 'submit') { } else if(ec.options.onblur === 'submit') {
$el.data('editableContainer').tip().find('form').submit(); $el.data('editableContainer').tip().find('form').submit();
} }
@ -350,7 +347,7 @@ Applied as jQuery method.
**/ **/
placement: 'top', placement: 'top',
/** /**
Wether to hide container on save/cancel. Whether to hide container on save/cancel.
@property autohide @property autohide
@type boolean @type boolean
@ -365,6 +362,7 @@ Applied as jQuery method.
@property onblur @property onblur
@type string @type string
@default 'cancel' @default 'cancel'
@since 1.1.1
**/ **/
onblur: 'cancel' onblur: 'cancel'
}; };

@ -43,10 +43,6 @@
innerHide: function () { innerHide: function () {
this.$form.hide(this.options.anim, $.proxy(function() { this.$form.hide(this.options.anim, $.proxy(function() {
this.$element.show(); this.$element.show();
//return focus on element
if (this.options.enablefocus) {
this.$element.focus();
}
}, this)); }, this));
}, },
@ -57,8 +53,7 @@
//defaults //defaults
$.fn.editableContainer.defaults = $.extend({}, $.fn.editableContainer.defaults, { $.fn.editableContainer.defaults = $.extend({}, $.fn.editableContainer.defaults, {
anim: 'fast', anim: 'fast'
enablefocus: false
}); });

@ -14,7 +14,7 @@
$.extend(this.containerOptions, { $.extend(this.containerOptions, {
trigger: 'manual', trigger: 'manual',
selector: false, selector: false,
content: 'dfgh' content: ' '
}); });
this.call(this.containerOptions); this.call(this.containerOptions);
}, },

@ -2,10 +2,11 @@
* EditableForm utilites * EditableForm utilites
*/ */
(function ($) { (function ($) {
$.fn.editableform.utils = { //utils
$.fn.editableutils = {
/** /**
* classic JS inheritance function * classic JS inheritance function
*/ */
inherit: function (Child, Parent) { inherit: function (Child, Parent) {
var F = function() { }; var F = function() { };
F.prototype = Parent.prototype; F.prototype = Parent.prototype;
@ -116,6 +117,13 @@
return k; return k;
} }
} },
/**
method to escape html.
**/
escape: function(str) {
return $('<div>').text(str).html();
}
}; };
}(window.jQuery)); }(window.jQuery));

@ -16,6 +16,8 @@
*display: inline; *display: inline;
} }
.editable-input { .editable-input {
vertical-align: top; vertical-align: top;
display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */ display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */
@ -31,8 +33,9 @@
} }
/*for jquery-ui buttons need set height to look more pretty*/ /*for jquery-ui buttons need set height to look more pretty*/
.editable-buttons button.ui-button { .editable-buttons button.ui-button-icon-only {
height: 24px; height: 24px;
width: 30px;
} }
.editableform-loading { .editableform-loading {
@ -79,6 +82,10 @@
margin: 0; margin: 0;
} }
.editable-checklist label {
white-space: nowrap;
}
.editable-clear { .editable-clear {
clear: both; clear: both;
font-size: 0.9em; font-size: 0.9em;

@ -1,7 +1,7 @@
/** /**
Form with single input element, two buttons and two states: normal/loading. Form with single input element, two buttons and two states: normal/loading.
Applied as jQuery method to DIV tag (not to form tag!) 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' or 'select'. Editableform is linked with one of input types, e.g. 'text', 'select' etc.
@class editableform @class editableform
@uses text @uses text
@ -9,9 +9,12 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
**/ **/
(function ($) { (function ($) {
var EditableForm = function (element, options) { var EditableForm = function (div, options) {
this.options = $.extend({}, $.fn.editableform.defaults, options); this.options = $.extend({}, $.fn.editableform.defaults, options);
this.$element = $(element); //div, containing form. Not form tag! Not editable-element. this.$div = $(div); //div, containing form. Not form tag! Not editable-element.
if(!this.options.scope) {
this.options.scope = this;
}
this.initInput(); this.initInput();
}; };
@ -21,9 +24,9 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
var TypeConstructor, typeOptions; var TypeConstructor, typeOptions;
//create input of specified type //create input of specified type
if(typeof $.fn.editableform.types[this.options.type] === 'function') { if(typeof $.fn.editabletypes[this.options.type] === 'function') {
TypeConstructor = $.fn.editableform.types[this.options.type]; TypeConstructor = $.fn.editabletypes[this.options.type];
typeOptions = $.fn.editableform.utils.sliceObj(this.options, $.fn.editableform.utils.objectKeys(TypeConstructor.defaults)); typeOptions = $.fn.editableutils.sliceObj(this.options, $.fn.editableutils.objectKeys(TypeConstructor.defaults));
this.input = new TypeConstructor(typeOptions); this.input = new TypeConstructor(typeOptions);
} else { } else {
$.error('Unknown type: '+ this.options.type); $.error('Unknown type: '+ this.options.type);
@ -45,7 +48,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
**/ **/
render: function() { render: function() {
this.$loading = $($.fn.editableform.loading); this.$loading = $($.fn.editableform.loading);
this.$element.empty().append(this.$loading); this.$div.empty().append(this.$loading);
this.showLoading(); this.showLoading();
//init form template and buttons //init form template and buttons
@ -61,7 +64,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
@event rendering @event rendering
@param {Object} event event object @param {Object} event event object
**/ **/
this.$element.triggerHandler('rendering'); this.$div.triggerHandler('rendering');
//render input //render input
$.when(this.input.render()) $.when(this.input.render())
@ -80,21 +83,23 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
} }
//append form to container //append form to container
this.$element.append(this.$form); this.$div.append(this.$form);
//attach 'cancel' handler //attach 'cancel' handler
this.$form.find('.editable-cancel').click($.proxy(this.cancel, this)); this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
// this.$form.find('.editable-buttons button').eq(1).click($.proxy(this.cancel, this));
if(this.input.error) { if(this.input.error) {
this.error(this.input.error); this.error(this.input.error);
this.$form.find('.editable-submit').attr('disabled', true); this.$form.find('.editable-submit').attr('disabled', true);
this.input.$input.attr('disabled', true); this.input.$input.attr('disabled', true);
//prevent form from submitting
this.$form.submit(function(e){ e.preventDefault(); });
} else { } else {
this.error(false); this.error(false);
this.input.$input.removeAttr('disabled'); this.input.$input.removeAttr('disabled');
this.$form.find('.editable-submit').removeAttr('disabled'); this.$form.find('.editable-submit').removeAttr('disabled');
this.input.value2input(this.value); this.input.value2input(this.value);
//attach submit handler
this.$form.submit($.proxy(this.submit, this)); this.$form.submit($.proxy(this.submit, this));
} }
@ -103,7 +108,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
@event rendered @event rendered
@param {Object} event event object @param {Object} event event object
**/ **/
this.$element.triggerHandler('rendered'); this.$div.triggerHandler('rendered');
this.showForm(); this.showForm();
}, this)); }, this));
@ -114,7 +119,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
@event cancel @event cancel
@param {Object} event event object @param {Object} event event object
**/ **/
this.$element.triggerHandler('cancel'); this.$div.triggerHandler('cancel');
}, },
showLoading: function() { showLoading: function() {
var w; var w;
@ -133,16 +138,18 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
this.$loading.show(); this.$loading.show();
}, },
showForm: function() { showForm: function(activate) {
this.$loading.hide(); this.$loading.hide();
this.$form.show(); this.$form.show();
this.input.activate(); if(activate !== false) {
this.input.activate();
}
/** /**
Fired when form is shown Fired when form is shown
@event show @event show
@param {Object} event event object @param {Object} event event object
**/ **/
this.$element.triggerHandler('show'); this.$div.triggerHandler('show');
}, },
error: function(msg) { error: function(msg) {
@ -163,8 +170,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
e.preventDefault(); e.preventDefault();
var error, var error,
newValue = this.input.input2value(), //get new value from input newValue = this.input.input2value(); //get new value from input
newValueStr;
//validation //validation
if (error = this.validate(newValue)) { if (error = this.validate(newValue)) {
@ -173,25 +179,29 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
return; return;
} }
//value as string
newValueStr = this.input.value2str(newValue);
//if value not changed --> cancel //if value not changed --> cancel
/*jslint eqeq: true*/ /*jslint eqeq: true*/
if (newValueStr == this.input.value2str(this.value)) { if (!this.options.savenochange && this.input.value2str(newValue) == this.input.value2str(this.value)) {
/*jslint eqeq: false*/ /*jslint eqeq: false*/
this.cancel(); this.cancel();
return; return;
} }
//sending data to server //sending data to server
$.when(this.save(newValueStr)) $.when(this.save(newValue))
.done($.proxy(function(response) { .done($.proxy(function(response) {
//run success callback //run success callback
var res = typeof this.options.success === 'function' ? this.options.success.call(this, response, newValue) : null; var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;
//if success callback returns string --> show error //if success callback returns false --> keep form open and do not activate input
if(res && typeof res === 'string') { if(res === false) {
this.error(false);
this.showForm(false);
return;
}
//if success callback returns string --> keep form open, show error and activate input
if(typeof res === 'string') {
this.error(res); this.error(res);
this.showForm(); this.showForm();
return; return;
@ -218,7 +228,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
if(params.newValue === 'username') {...} if(params.newValue === 'username') {...}
}); });
**/ **/
this.$element.triggerHandler('save', {newValue: newValue, response: response}); this.$div.triggerHandler('save', {newValue: newValue, response: response});
}, this)) }, this))
.fail($.proxy(function(xhr) { .fail($.proxy(function(xhr) {
this.error(typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!'); this.error(typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!');
@ -226,10 +236,16 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
}, this)); }, this));
}, },
save: function(value) { save: function(newValue) {
var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this) : this.options.pk, //convert value for submitting to server
var submitValue = this.input.value2submit(newValue);
//try parse composite pk defined as json string in data-pk
this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true);
var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,
send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk)))), send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk)))),
params, ajaxOptions; params;
if (send) { //send to server if (send) { //send to server
this.showLoading(); this.showLoading();
@ -237,30 +253,29 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
//standard params //standard params
params = { params = {
name: this.options.name || '', name: this.options.name || '',
value: value, value: submitValue,
pk: pk pk: pk
}; };
//additional params //additional params
if(typeof this.options.params === 'function') { if(typeof this.options.params === 'function') {
$.extend(params, this.options.params.call(this, params)); $.extend(params, this.options.params.call(this.options.scope, params));
} else { } else {
//try parse json in single quotes (from data-params attribute) //try parse json in single quotes (from data-params attribute)
this.options.params = $.fn.editableform.utils.tryParseJson(this.options.params, true); this.options.params = $.fn.editableutils.tryParseJson(this.options.params, true);
$.extend(params, this.options.params); $.extend(params, this.options.params);
} }
if(typeof this.options.url === 'function') { //user's function if(typeof this.options.url === 'function') { //user's function
return this.options.url.call(this, params); return this.options.url.call(this.options.scope, params);
} else { //send ajax to server and return deferred object } else {
ajaxOptions = $.extend({ //send ajax to server and return deferred object
return $.ajax($.extend({
url : this.options.url, url : this.options.url,
data : params, data : params,
type : 'post', type : 'post',
dataType: 'json' dataType: 'json'
}, this.options.ajaxOptions); }, this.options.ajaxOptions));
return $.ajax(ajaxOptions);
} }
} }
}, },
@ -270,7 +285,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
value = this.value; value = this.value;
} }
if (typeof this.options.validate === 'function') { if (typeof this.options.validate === 'function') {
return this.options.validate.call(this, value); return this.options.validate.call(this.options.scope, value);
} }
}, },
@ -377,7 +392,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
name: null, name: null,
/** /**
Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>. Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>.
Can be calculated dinamically via function. Can be calculated dynamically via function.
@property pk @property pk
@type string|object|function @type string|object|function
@ -418,7 +433,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
validate: null, validate: null,
/** /**
Success callback. Called when value successfully sent on server and **response status = 200**. Success callback. Called when value successfully sent on server and **response status = 200**.
Usefull to work with json response. For example, if your backend response can be <code>{success: true}</code> Useful to work with json response. For example, if your backend response can be <code>{success: true}</code>
or <code>{success: false, msg: "server error"}</code> you can check it inside this callback. or <code>{success: false, msg: "server error"}</code> you can check it inside this callback.
If it returns **string** - means error occured and string is shown as error message. If it returns **string** - means error occured and string is shown as error message.
If it returns **object like** <code>{newValue: &lt;something&gt;}</code> - it overwrites value, submitted by user. If it returns **object like** <code>{newValue: &lt;something&gt;}</code> - it overwrites value, submitted by user.
@ -440,28 +455,44 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
@property ajaxOptions @property ajaxOptions
@type object @type object
@default null @default null
@since 1.1.1
**/ **/
ajaxOptions: null, ajaxOptions: null,
/** /**
Wether to show buttons or not. Whether to show buttons or not.
Form without buttons can be auto-submitted by input or by onblur = 'submit'. Form without buttons can be auto-submitted by input or by onblur = 'submit'.
@example
ajaxOptions: {
method: 'PUT',
dataType: 'xml'
}
@property showbuttons @property showbuttons
@type boolean @type boolean
@default true @default true
@since 1.1.1
**/ **/
showbuttons: true showbuttons: true,
/**
/*todo: Scope for callback methods (success, validate).
Submit strategy. Can be <code>normal|never</code> If <code>null</code> means editableform instance itself.
<code>submitmode='never'</code> usefull for turning into classic form several inputs and submitting them together manually.
Works pretty with <code>showbuttons=false</code>
@property submitmode @property scope
@type string @type DOMElement|object
@default normal @default null
*/ @since 1.2.0
// submitmode: 'normal' @private
**/
scope: null,
/**
Whether to save or cancel value when it was not changed but form was submitted
@property savenochange
@type boolean
@default false
@since 1.2.0
**/
savenochange: false
}; };
/* /*
@ -482,15 +513,9 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
$.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+ $.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+
'<button type="button" class="editable-cancel">cancel</button>'; '<button type="button" class="editable-cancel">cancel</button>';
//error class attahced to control-group //error class attached to control-group
$.fn.editableform.errorGroupClass = null; $.fn.editableform.errorGroupClass = null;
//error class attahced to editable-error-block //error class attached to editable-error-block
$.fn.editableform.errorBlockClass = 'editable-error'; $.fn.editableform.errorBlockClass = 'editable-error';
//input types
$.fn.editableform.types = {};
//utils
$.fn.editableform.utils = {};
}(window.jQuery)); }(window.jQuery));

@ -8,7 +8,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
var Editable = function (element, options) { var Editable = function (element, options) {
this.$element = $(element); this.$element = $(element);
this.options = $.extend({}, $.fn.editable.defaults, $.fn.editableform.utils.getConfigData(this.$element), options); this.options = $.extend({}, $.fn.editable.defaults, $.fn.editableutils.getConfigData(this.$element), options);
this.init(); this.init();
}; };
@ -20,9 +20,6 @@ Makes editable any HTML element on the page. Applied as jQuery method.
doAutotext, doAutotext,
finalize; finalize;
//initialization flag
this.isInit = true;
//editableContainer must be defined //editableContainer must be defined
if(!$.fn.editableContainer) { if(!$.fn.editableContainer) {
$.error('You must define $.fn.editableContainer via including corresponding file (e.g. editable-popover.js)'); $.error('You must define $.fn.editableContainer via including corresponding file (e.g. editable-popover.js)');
@ -33,9 +30,9 @@ Makes editable any HTML element on the page. Applied as jQuery method.
this.options.name = this.options.name || this.$element.attr('id'); this.options.name = this.options.name || this.$element.attr('id');
//create input of specified type. Input will be used for converting value, not in form //create input of specified type. Input will be used for converting value, not in form
if(typeof $.fn.editableform.types[this.options.type] === 'function') { if(typeof $.fn.editabletypes[this.options.type] === 'function') {
TypeConstructor = $.fn.editableform.types[this.options.type]; TypeConstructor = $.fn.editabletypes[this.options.type];
this.typeOptions = $.fn.editableform.utils.sliceObj(this.options, $.fn.editableform.utils.objectKeys(TypeConstructor.defaults)); this.typeOptions = $.fn.editableutils.sliceObj(this.options, $.fn.editableutils.objectKeys(TypeConstructor.defaults));
this.input = new TypeConstructor(this.typeOptions); this.input = new TypeConstructor(this.typeOptions);
} else { } else {
$.error('Unknown type: '+ this.options.type); $.error('Unknown type: '+ this.options.type);
@ -47,13 +44,20 @@ Makes editable any HTML element on the page. Applied as jQuery method.
this.value = this.input.html2value($.trim(this.$element.html())); this.value = this.input.html2value($.trim(this.$element.html()));
isValueByText = true; isValueByText = true;
} else { } else {
/*
value can be string when received from 'data-value' attribute
for complext objects value can be set as json string in data-value attribute,
e.g. data-value="{city: 'Moscow', street: 'Lenina'}"
*/
this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true);
if(typeof this.options.value === 'string') { if(typeof this.options.value === 'string') {
this.options.value = $.trim(this.options.value); this.value = this.input.str2value(this.options.value);
} else {
this.value = this.options.value;
} }
this.value = this.input.str2value(this.options.value);
} }
//add 'editable' class //add 'editable' class to every editable element
this.$element.addClass('editable'); this.$element.addClass('editable');
//attach handler activating editable. In disabled mode it just prevent default action (useful for links) //attach handler activating editable. In disabled mode it just prevent default action (useful for links)
@ -81,29 +85,46 @@ Makes editable any HTML element on the page. Applied as jQuery method.
//if value was generated by text or value is empty, no sense to run autotext //if value was generated by text or value is empty, no sense to run autotext
doAutotext = !isValueByText && this.value !== null && this.value !== undefined; doAutotext = !isValueByText && this.value !== null && this.value !== undefined;
doAutotext &= (this.options.autotext === 'always') || (this.options.autotext === 'auto' && !this.$element.text().length); doAutotext &= (this.options.autotext === 'always') || (this.options.autotext === 'auto' && !this.$element.text().length);
$.when(doAutotext ? this.input.value2html(this.value, this.$element) : true).then($.proxy(function() { $.when(doAutotext ? this.render() : true).then($.proxy(function() {
if(this.options.disabled) { if(this.options.disabled) {
this.disable(); this.disable();
} else { } else {
this.enable(); this.enable();
} }
/** /**
Fired each time when element's text is rendered. Occurs on initialization and on each update of value. Fired when element was initialized by editable method.
Can be used for display customization.
@event render @event init
@param {Object} event event object @param {Object} event event object
@param {Object} editable editable instance @param {Object} editable editable instance
@example @since 1.2.0
$('#action').on('render', function(e, editable) {
var colors = {0: "gray", 1: "green", 2: "blue", 3: "red"};
$(this).css("color", colors[editable.value]);
});
**/ **/
this.$element.triggerHandler('render', this); this.$element.triggerHandler('init', this);
this.isInit = false;
}, this)); }, this));
}, },
/*
Renders value into element's text.
Can call custom display method from options.
Can return deferred object.
@method render()
*/
render: function() {
//do not display anything
if(this.options.display === false) {
return;
}
//if it is input with source, we pass callback in third param to be called when source is loaded
if(this.input.options.hasOwnProperty('source')) {
return this.input.value2html(this.value, this.$element[0], this.options.display);
//if display method defined --> use it
} else if(typeof this.options.display === 'function') {
return this.options.display.call(this.$element[0], this.value);
//else use input's original value2html() method
} else {
return this.input.value2html(this.value, this.$element[0]);
}
},
/** /**
Enables editable Enables editable
@ -191,6 +212,11 @@ Makes editable any HTML element on the page. Applied as jQuery method.
* set emptytext if element is empty (reverse: remove emptytext if needed) * set emptytext if element is empty (reverse: remove emptytext if needed)
*/ */
handleEmpty: function () { handleEmpty: function () {
//do not handle empty if we do not display anything
if(this.options.display === false) {
return;
}
var emptyClass = 'editable-empty'; var emptyClass = 'editable-empty';
//emptytext shown only for enabled //emptytext shown only for enabled
if(!this.options.disabled) { if(!this.options.disabled) {
@ -211,7 +237,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
/** /**
Shows container with form Shows container with form
@method show() @method show()
@param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true. @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
**/ **/
show: function (closeAll) { show: function (closeAll) {
if(this.options.disabled) { if(this.options.disabled) {
@ -221,13 +247,11 @@ Makes editable any HTML element on the page. Applied as jQuery method.
//init editableContainer: popover, tooltip, inline, etc.. //init editableContainer: popover, tooltip, inline, etc..
if(!this.container) { if(!this.container) {
var containerOptions = $.extend({}, this.options, { var containerOptions = $.extend({}, this.options, {
value: this.value, value: this.value
autohide: false //element will take care to show/hide container
}); });
this.$element.editableContainer(containerOptions); this.$element.editableContainer(containerOptions);
this.$element.on({ this.$element.on({
save: $.proxy(this.save, this), save: $.proxy(this.save, this)
cancel: $.proxy(this.hide, this)
}); });
this.container = this.$element.data('editableContainer'); this.container = this.$element.data('editableContainer');
} else if(this.container.tip().is(':visible')) { } else if(this.container.tip().is(':visible')) {
@ -246,17 +270,12 @@ Makes editable any HTML element on the page. Applied as jQuery method.
if(this.container) { if(this.container) {
this.container.hide(); this.container.hide();
} }
//return focus on element
if (this.options.enablefocus && this.options.toggle === 'click') {
this.$element.focus();
}
}, },
/** /**
Toggles container visibility (show / hide) Toggles container visibility (show / hide)
@method toggle() @method toggle()
@param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true. @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
**/ **/
toggle: function(closeAll) { toggle: function(closeAll) {
if(this.container && this.container.tip().is(':visible')) { if(this.container && this.container.tip().is(':visible')) {
@ -271,13 +290,13 @@ Makes editable any HTML element on the page. Applied as jQuery method.
*/ */
save: function(e, params) { save: function(e, params) {
//if url is not user's function and value was not sent to server and value changed --> mark element with unsaved css. //if url is not user's function and value was not sent to server and value changed --> mark element with unsaved css.
if(typeof this.options.url !== 'function' && params.response === undefined && this.input.value2str(this.value) !== this.input.value2str(params.newValue)) { if(typeof this.options.url !== 'function' && this.options.display !== false && params.response === undefined && this.input.value2str(this.value) !== this.input.value2str(params.newValue)) {
this.$element.addClass('editable-unsaved'); this.$element.addClass('editable-unsaved');
} else { } else {
this.$element.removeClass('editable-unsaved'); this.$element.removeClass('editable-unsaved');
} }
this.hide(); // this.hide();
this.setValue(params.newValue); this.setValue(params.newValue);
/** /**
@ -312,7 +331,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
Sets new value of editable Sets new value of editable
@method setValue(value, convertStr) @method setValue(value, convertStr)
@param {mixed} value new value @param {mixed} value new value
@param {boolean} convertStr wether to convert value from string to internal format @param {boolean} convertStr whether to convert value from string to internal format
**/ **/
setValue: function(value, convertStr) { setValue: function(value, convertStr) {
if(convertStr) { if(convertStr) {
@ -323,10 +342,9 @@ Makes editable any HTML element on the page. Applied as jQuery method.
if(this.container) { if(this.container) {
this.container.option('value', this.value); this.container.option('value', this.value);
} }
$.when(this.input.value2html(this.value, this.$element)) $.when(this.render())
.then($.proxy(function() { .then($.proxy(function() {
this.handleEmpty(); this.handleEmpty();
this.$element.triggerHandler('render', this);
}, this)); }, this));
}, },
@ -338,7 +356,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
if(this.container) { if(this.container) {
this.container.activate(); this.container.activate();
} }
} }
}; };
/* EDITABLE PLUGIN DEFINITION /* EDITABLE PLUGIN DEFINITION
@ -369,7 +387,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
$('#username, #fullname').editable('validate'); $('#username, #fullname').editable('validate');
// possible result: // possible result:
{ {
username: "username is requied", username: "username is required",
fullname: "fullname should be minimum 3 letters length" fullname: "fullname should be minimum 3 letters length"
} }
**/ **/
@ -398,7 +416,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
this.each(function () { this.each(function () {
var $this = $(this), data = $this.data(datakey); var $this = $(this), data = $this.data(datakey);
if (data && data.value !== undefined && data.value !== null) { if (data && data.value !== undefined && data.value !== null) {
result[data.options.name] = data.input.value2str(data.value); result[data.options.name] = data.input.value2submit(data.value);
} }
}); });
return result; return result;
@ -411,6 +429,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
@param {object} options @param {object} options
@param {object} options.url url to submit data @param {object} options.url url to submit data
@param {object} options.data additional data to submit @param {object} options.data additional data to submit
@param {object} options.ajaxOptions additional ajax options
@param {function} options.error(obj) error handler (called on both client-side and server-side validation errors) @param {function} options.error(obj) error handler (called on both client-side and server-side validation errors)
@param {function} options.success(obj) success handler @param {function} options.success(obj) success handler
@returns {Object} jQuery object @returns {Object} jQuery object
@ -421,21 +440,20 @@ Makes editable any HTML element on the page. Applied as jQuery method.
errors = this.editable('validate'), errors = this.editable('validate'),
values; values;
if(typeof config.error !== 'function') {
config.error = function() {};
}
if($.isEmptyObject(errors)) { if($.isEmptyObject(errors)) {
values = this.editable('getValue'); values = this.editable('getValue');
if(config.data) { if(config.data) {
$.extend(values, config.data); $.extend(values, config.data);
} }
$.ajax({
type: 'POST', $.ajax($.extend({
url: config.url, url: config.url,
data: values, data: values,
type: 'POST',
dataType: 'json' dataType: 'json'
}).success(function(response) { }, config.ajaxOptions))
.success(function(response) {
//successful response
if(typeof response === 'object' && response.id) { if(typeof response === 'object' && response.id) {
$elems.editable('option', 'pk', response.id); $elems.editable('option', 'pk', response.id);
$elems.removeClass('editable-unsaved'); $elems.removeClass('editable-unsaved');
@ -443,13 +461,20 @@ Makes editable any HTML element on the page. Applied as jQuery method.
config.success.apply($elems, arguments); config.success.apply($elems, arguments);
} }
} else { //server-side validation error } else { //server-side validation error
if(typeof config.error === 'function') {
config.error.apply($elems, arguments);
}
}
})
.error(function(){ //ajax error
if(typeof config.error === 'function') {
config.error.apply($elems, arguments); config.error.apply($elems, arguments);
} }
}).error(function(){ //ajax error
config.error.apply($elems, arguments);
}); });
} else { //client-side validation error } else { //client-side validation error
config.error.call($elems, {errors: errors}); if(typeof config.error === 'function') {
config.error.call($elems, {errors: errors});
}
} }
return this; return this;
} }
@ -505,7 +530,6 @@ Makes editable any HTML element on the page. Applied as jQuery method.
@default 'click' @default 'click'
**/ **/
toggle: 'click', toggle: 'click',
/** /**
Text shown when element is empty. Text shown when element is empty.
@ -515,7 +539,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
**/ **/
emptytext: 'Empty', emptytext: 'Empty',
/** /**
Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Usefull for select and date. Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Useful for select and date.
For example, if dropdown list is <code>{1: 'a', 2: 'b'}</code> and element's value set to <code>1</code>, it's html will be automatically set to <code>'a'</code>. For example, if dropdown list is <code>{1: 'a', 2: 'b'}</code> and element's value set to <code>1</code>, it's html will be automatically set to <code>'a'</code>.
<code>auto</code> - text will be automatically set only if element is empty. <code>auto</code> - text will be automatically set only if element is empty.
<code>always|never</code> - always(never) try to set element's text. <code>always|never</code> - always(never) try to set element's text.
@ -526,22 +550,31 @@ Makes editable any HTML element on the page. Applied as jQuery method.
**/ **/
autotext: 'auto', autotext: 'auto',
/** /**
Wether to return focus on element after form is closed.
This allows fully keyboard input.
@property enablefocus
@type boolean
@default false
**/
enablefocus: false,
/**
Initial value of input. Taken from <code>data-value</code> or element's text. Initial value of input. Taken from <code>data-value</code> or element's text.
@property value @property value
@type mixed @type mixed
@default element's text @default element's text
**/ **/
value: null value: null,
/**
Callback to perform custom displaying of value in element's text.
If <code>null</code>, default input's value2html() will be called.
If <code>false</code>, no displaying methods will be called, element's text will no change.
Runs under element's scope.
Second parameter __sourceData__ is passed for inputs with source (select, checklist).
@property display
@type function|boolean
@default null
@since 1.2.0
@example
display: function(value, sourceData) {
var escapedValue = $('<div>').text(value).html();
$(this).html('<b>'+escapedValue+'</b>');
}
**/
display: null
}; };
}(window.jQuery)); }(window.jQuery));

@ -26,19 +26,21 @@ $(function(){
this.init('address', options, Address.defaults); this.init('address', options, Address.defaults);
}; };
$.fn.editableform.utils.inherit(Address, $.fn.editableform.types.abstract); $.fn.editableutils.inherit(Address, $.fn.editabletypes.abstract);
$.extend(Address.prototype, { $.extend(Address.prototype, {
render: function() { render: function() {
Address.superclass.render.call(this); Address.superclass.render.call(this);
// this.$input.
}, },
//standard way to show value in element. Used only if display option not defined.
value2html: function(value, element) { value2html: function(value, element) {
var html = value.city + ', ' + value.street + ' st., bld. ' + value.building; if(!value) {
$(element).text(html); $(element).empty();
return;
}
var html = $('<div>').text(value.city).html() + ', ' + $('<div>').text(value.street).html() + ' st., bld. ' + $('<div>').text(value.building).html();
$(element).html(html);
}, },
html2value: function(html) { html2value: function(html) {
@ -59,11 +61,17 @@ $(function(){
}, },
/* /*
method for converting data before sent on server. converts value to string.
As jQuery correctly sends objects via ajax, you can just return value It is used in internal comparing (not for sending to server).
*/ */
value2str: function(value) { value2str: function(value) {
return value; var str = '';
if(value) {
for(var k in value) {
str = str + k + ':' + value[k] + ';';
}
}
return str;
}, },
/* /*
@ -94,14 +102,14 @@ $(function(){
} }
}); });
Address.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, { Address.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
tpl: '<div><label><span>City: </span><input type="text" name="city" class="span2"></label></div>'+ tpl: '<div><label><span>City: </span><input type="text" name="city" class="input-small"></label></div>'+
'<div><label><span>Street: </span><input type="text" name="street" class="span2"></label></div>'+ '<div><label><span>Street: </span><input type="text" name="street" class="input-small"></label></div>'+
'<div><label><span>Building: </span><input type="text" name="building" class="span1"></label></div>', '<div><label><span>Building: </span><input type="text" name="building" class="input-mini"></label></div>',
inputclass: 'editable-address' inputclass: 'editable-address'
}); });
$.fn.editableform.types.address = Address; $.fn.editabletypes.address = Address;
}(window.jQuery)); }(window.jQuery));

@ -6,11 +6,14 @@ To create your own input you should inherit from this class.
**/ **/
(function ($) { (function ($) {
//types
$.fn.editabletypes = {};
var Abstract = function () { }; var Abstract = function () { };
Abstract.prototype = { Abstract.prototype = {
/** /**
Iinitializes input Initializes input
@method init() @method init()
**/ **/
@ -46,8 +49,7 @@ To create your own input you should inherit from this class.
@param {DOMElement} element @param {DOMElement} element
**/ **/
value2html: function(value, element) { value2html: function(value, element) {
var html = this.escape(value); $(element).text(value);
$(element).html(html);
}, },
/** /**
@ -62,7 +64,7 @@ To create your own input you should inherit from this class.
}, },
/** /**
Converts value to string (for submiting to server) Converts value to string (for comparering)
@method value2str(value) @method value2str(value)
@param {mixed} value @param {mixed} value
@ -83,6 +85,17 @@ To create your own input you should inherit from this class.
return str; return str;
}, },
/**
Converts value for submitting to server
@method value2submit(value)
@param {mixed} value
@returns {mixed}
**/
value2submit: function(value) {
return value;
},
/** /**
Sets value of input. Sets value of input.
@ -114,11 +127,11 @@ To create your own input you should inherit from this class.
}, },
/** /**
Creares input. Creates input.
@method clear() @method clear()
**/ **/
clear: function() { clear: function() {
this.$input.val(null); this.$input.val(null);
}, },
@ -130,7 +143,7 @@ To create your own input you should inherit from this class.
}, },
/** /**
attach handler to automatically submit form when value changed (usefull when buttons not shown) attach handler to automatically submit form when value changed (useful when buttons not shown)
**/ **/
autosubmit: function() { autosubmit: function() {
@ -148,12 +161,12 @@ To create your own input you should inherit from this class.
tpl: '', tpl: '',
/** /**
CSS class automatically applied to input CSS class automatically applied to input
@property inputclass @property inputclass
@type string @type string
@default span2 @default input-medium
**/ **/
inputclass: 'span2', inputclass: 'input-medium',
/** /**
Name attribute of input Name attribute of input
@ -164,6 +177,6 @@ To create your own input you should inherit from this class.
name: null name: null
}; };
$.extend($.fn.editableform.types, {abstract: Abstract}); $.extend($.fn.editabletypes, {abstract: Abstract});
}(window.jQuery)); }(window.jQuery));

@ -27,7 +27,7 @@ $(function(){
this.init('checklist', options, Checklist.defaults); this.init('checklist', options, Checklist.defaults);
}; };
$.fn.editableform.utils.inherit(Checklist, $.fn.editableform.types.list); $.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list);
$.extend(Checklist.prototype, { $.extend(Checklist.prototype, {
renderList: function() { renderList: function() {
@ -49,10 +49,8 @@ $(function(){
}, },
value2str: function(value) { value2str: function(value) {
return $.isArray(value) ? value.join($.trim(this.options.separator)) : ''; return $.isArray(value) ? value.sort().join($.trim(this.options.separator)) : '';
//it is also possible to sent as array },
//return value;
},
//parse separated string //parse separated string
str2value: function(str) { str2value: function(str) {
@ -95,19 +93,18 @@ $(function(){
//collect text of checked boxes //collect text of checked boxes
value2htmlFinal: function(value, element) { value2htmlFinal: function(value, element) {
var selected = [], item, i, html = ''; var html = [],
if($.isArray(value) && value.length <= this.options.limit) { /*jslint eqeq: true*/
for(i=0; i<value.length; i++){ checked = $.grep(this.sourceData, function(o){
item = this.itemByVal(value[i]); return $.grep(value, function(v){ return v == o.value; }).length;
if(item) { });
selected.push($('<div>').text(item.text).html()); /*jslint eqeq: false*/
} if(checked.length) {
} $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
html = selected.join(this.options.viewseparator); $(element).html(html.join('<br>'));
} else { } else {
html = this.options.limitText.replace('{checked}', $.isArray(value) ? value.length : 0).replace('{count}', this.sourceData.length); $(element).empty();
} }
$(element).html(html);
}, },
activate: function() { activate: function() {
@ -123,7 +120,7 @@ $(function(){
} }
}); });
Checklist.defaults = $.extend({}, $.fn.editableform.types.list.defaults, { Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
/** /**
@property tpl @property tpl
@default <div></div> @default <div></div>
@ -133,46 +130,20 @@ $(function(){
/** /**
@property inputclass @property inputclass
@type string @type string
@default span2 editable-checklist @default editable-checklist
**/ **/
inputclass: 'span2 editable-checklist', inputclass: 'editable-checklist',
/** /**
Separator of values in string when sending to server Separator of values when reading from 'data-value' string
@property separator @property separator
@type string @type string
@default ', ' @default ', '
**/ **/
separator: ',', separator: ','
/**
Separator of text when display as element content.
@property viewseparator
@type string
@default '<br>'
**/
viewseparator: '<br>',
/**
Maximum number of items shown as element content.
If checked more items - <code>limitText</code> will be shown.
@property limit
@type integer
@default 4
**/
limit: 4,
/**
Text shown when count of checked items is greater than <code>limit</code> parameter.
You can use <code>{checked}</code> and <code>{count}</code> placeholders.
@property limitText
@type string
@default 'Selected {checked} of {count}'
**/
limitText: 'Selected {checked} of {count}'
}); });
$.fn.editableform.types.checklist = Checklist; $.fn.editabletypes.checklist = Checklist;
}(window.jQuery)); }(window.jQuery));

@ -292,7 +292,7 @@
.text(dates[this.language].months[month]+' '+year); .text(dates[this.language].months[month]+' '+year);
this.picker.find('tfoot th.today') this.picker.find('tfoot th.today')
.text(dates[this.language].today) .text(dates[this.language].today)
.toggle(this.todayBtn); .toggle(this.todayBtn !== false);
this.updateNavArrows(); this.updateNavArrows();
this.fillMonths(); this.fillMonths();
var prevMonth = UTCDate(year, month-1, 28,0,0,0,0), var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
@ -429,10 +429,7 @@
break; break;
case 'today': case 'today':
var date = new Date(); var date = new Date();
date.setUTCHours(0); date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
date.setUTCMinutes(0);
date.setUTCSeconds(0);
date.setUTCMilliseconds(0);
this.showMode(-2); this.showMode(-2);
var which = this.todayBtn == 'linked' ? null : 'view'; var which = this.todayBtn == 'linked' ? null : 'view';
@ -655,7 +652,17 @@
if (dir) { if (dir) {
this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir)); this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir));
} }
this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show(); /*
vitalets: fixing bug of very special conditions:
jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
Method show() does not set display css correctly and datepicker is not shown.
Changed to .css('display', 'block') solve the problem.
See https://github.com/vitalets/x-editable/issues/37
In jquery 1.7.2+ everything works fine.
*/
//this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
this.updateNavArrows(); this.updateNavArrows();
} }
}; };
@ -773,7 +780,7 @@
val, filtered, part; val, filtered, part;
setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m']; setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
setters_map['dd'] = setters_map['d']; setters_map['dd'] = setters_map['d'];
date = UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0); date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
if (parts.length == format.parts.length) { if (parts.length == format.parts.length) {
for (var i=0, cnt = format.parts.length; i < cnt; i++) { for (var i=0, cnt = format.parts.length; i < cnt; i++) {
val = parseInt(parts[i], 10); val = parseInt(parts[i], 10);

@ -27,7 +27,7 @@ $(function(){
this.init('date', options, Date.defaults); this.init('date', options, Date.defaults);
//set popular options directly from settings or data-* attributes //set popular options directly from settings or data-* attributes
var directOptions = $.fn.editableform.utils.sliceObj(this.options, ['format']); var directOptions = $.fn.editableutils.sliceObj(this.options, ['format']);
//overriding datepicker config (as by default jQuery extend() is not recursive) //overriding datepicker config (as by default jQuery extend() is not recursive)
this.options.datepicker = $.extend({}, Date.defaults.datepicker, directOptions, options.datepicker); this.options.datepicker = $.extend({}, Date.defaults.datepicker, directOptions, options.datepicker);
@ -48,7 +48,7 @@ $(function(){
this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat); this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
}; };
$.fn.editableform.utils.inherit(Date, $.fn.editableform.types.abstract); $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstract);
$.extend(Date.prototype, { $.extend(Date.prototype, {
render: function () { render: function () {
@ -79,7 +79,11 @@ $(function(){
str2value: function(str) { str2value: function(str) {
return str ? this.dpg.parseDate(str, this.parsedFormat, this.options.datepicker.language) : null; return str ? this.dpg.parseDate(str, this.parsedFormat, this.options.datepicker.language) : null;
}, },
value2submit: function(value) {
return this.value2str(value);
},
value2input: function(value) { value2input: function(value) {
this.$input.datepicker('update', value); this.$input.datepicker('update', value);
@ -108,7 +112,7 @@ $(function(){
}); });
Date.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, { Date.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
/** /**
@property tpl @property tpl
@default <div></div> @default <div></div>
@ -165,6 +169,6 @@ $(function(){
clear: '&times; clear' clear: '&times; clear'
}); });
$.fn.editableform.types.date = Date; $.fn.editabletypes.date = Date;
}(window.jQuery)); }(window.jQuery));

@ -27,7 +27,7 @@ $(function(){
this.init('dateui', options, DateUI.defaults); this.init('dateui', options, DateUI.defaults);
//set popular options directly from settings or data-* attributes //set popular options directly from settings or data-* attributes
var directOptions = $.fn.editableform.utils.sliceObj(this.options, ['format']); var directOptions = $.fn.editableutils.sliceObj(this.options, ['format']);
//overriding datepicker config (as by default jQuery extend() is not recursive) //overriding datepicker config (as by default jQuery extend() is not recursive)
this.options.datepicker = $.extend({}, DateUI.defaults.datepicker, directOptions, options.datepicker); this.options.datepicker = $.extend({}, DateUI.defaults.datepicker, directOptions, options.datepicker);
@ -46,7 +46,7 @@ $(function(){
this.options.datepicker.dateFormat = this.options.datepicker.format; this.options.datepicker.dateFormat = this.options.datepicker.format;
}; };
$.fn.editableform.utils.inherit(DateUI, $.fn.editableform.types.abstract); $.fn.editableutils.inherit(DateUI, $.fn.editabletypes.abstract);
$.extend(DateUI.prototype, { $.extend(DateUI.prototype, {
render: function () { render: function () {
@ -97,7 +97,11 @@ $(function(){
} catch(e) {} } catch(e) {}
return d; return d;
}, },
value2submit: function(value) {
return this.value2str(value);
},
value2input: function(value) { value2input: function(value) {
this.$input.datepicker('setDate', value); this.$input.datepicker('setDate', value);
@ -125,7 +129,7 @@ $(function(){
}); });
DateUI.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, { DateUI.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
/** /**
@property tpl @property tpl
@default <div></div> @default <div></div>
@ -183,7 +187,7 @@ $(function(){
clear: '&times; clear' clear: '&times; clear'
}); });
$.fn.editableform.types.dateui = DateUI; $.fn.editabletypes.dateui = DateUI;
$.fn.editableform.types.date = DateUI; $.fn.editabletypes.date = DateUI;
}(window.jQuery)); }(window.jQuery));

@ -10,7 +10,7 @@ List - abstract class for inputs that have source option loaded from js array or
}; };
$.fn.editableform.utils.inherit(List, $.fn.editableform.types.abstract); $.fn.editableutils.inherit(List, $.fn.editabletypes.abstract);
$.extend(List.prototype, { $.extend(List.prototype, {
render: function () { render: function () {
@ -34,10 +34,15 @@ List - abstract class for inputs that have source option loaded from js array or
return null; //can't set value by text return null; //can't set value by text
}, },
value2html: function (value, element) { value2html: function (value, element, display) {
var deferred = $.Deferred(); var deferred = $.Deferred();
this.onSourceReady(function () { this.onSourceReady(function () {
this.value2htmlFinal(value, element); if(typeof display === 'function') {
//custom display method
display.call(element, value, this.sourceData);
} else {
this.value2htmlFinal(value, element);
}
deferred.resolve(); deferred.resolve();
}, function () { }, function () {
List.superclass.value2html(this.options.sourceError, element); List.superclass.value2html(this.options.sourceError, element);
@ -58,7 +63,7 @@ List - abstract class for inputs that have source option loaded from js array or
// try parse json in single quotes (for double quotes jquery does automatically) // try parse json in single quotes (for double quotes jquery does automatically)
try { try {
this.options.source = $.fn.editableform.utils.tryParseJson(this.options.source, false); this.options.source = $.fn.editableutils.tryParseJson(this.options.source, false);
} catch (e) { } catch (e) {
error.call(this); error.call(this);
return; return;
@ -66,32 +71,35 @@ List - abstract class for inputs that have source option loaded from js array or
//loading from url //loading from url
if (typeof this.options.source === 'string') { if (typeof this.options.source === 'string') {
var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''), //try to get from cache
cache; if(this.options.sourceCache) {
var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''),
cache;
if (!$(document).data(cacheID)) { if (!$(document).data(cacheID)) {
$(document).data(cacheID, {}); $(document).data(cacheID, {});
} }
cache = $(document).data(cacheID); cache = $(document).data(cacheID);
//check for cached data //check for cached data
if (cache.loading === false && cache.sourceData) { //take source from cache if (cache.loading === false && cache.sourceData) { //take source from cache
this.sourceData = cache.sourceData;
success.call(this);
return;
} else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
cache.callbacks.push($.proxy(function () {
this.sourceData = cache.sourceData; this.sourceData = cache.sourceData;
success.call(this); success.call(this);
}, this)); return;
} else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
cache.callbacks.push($.proxy(function () {
this.sourceData = cache.sourceData;
success.call(this);
}, this));
//also collecting error callbacks //also collecting error callbacks
cache.err_callbacks.push($.proxy(error, this)); cache.err_callbacks.push($.proxy(error, this));
return; return;
} else { //no cache yet, activate it } else { //no cache yet, activate it
cache.loading = true; cache.loading = true;
cache.callbacks = []; cache.callbacks = [];
cache.err_callbacks = []; cache.err_callbacks = [];
}
} }
//loading sourceData from server //loading sourceData from server
@ -102,23 +110,32 @@ List - abstract class for inputs that have source option loaded from js array or
data: this.options.name ? {name: this.options.name} : {}, data: this.options.name ? {name: this.options.name} : {},
dataType: 'json', dataType: 'json',
success: $.proxy(function (data) { success: $.proxy(function (data) {
cache.loading = false; if(cache) {
cache.loading = false;
}
this.sourceData = this.makeArray(data); this.sourceData = this.makeArray(data);
if($.isArray(this.sourceData)) { if($.isArray(this.sourceData)) {
this.doPrepend(); this.doPrepend();
//store result in cache
cache.sourceData = this.sourceData;
success.call(this); success.call(this);
$.each(cache.callbacks, function () { this.call(); }); //run success callbacks for other fields if(cache) {
//store result in cache
cache.sourceData = this.sourceData;
$.each(cache.callbacks, function () { this.call(); }); //run success callbacks for other fields
}
} else { } else {
error.call(this); error.call(this);
$.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields if(cache) {
$.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields
}
} }
}, this), }, this),
error: $.proxy(function () { error: $.proxy(function () {
cache.loading = false;
error.call(this); error.call(this);
$.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields if(cache) {
cache.loading = false;
//run error callbacks for other fields
$.each(cache.err_callbacks, function () { this.call(); });
}
}, this) }, this)
}); });
} else { //options as json/array } else { //options as json/array
@ -139,7 +156,7 @@ List - abstract class for inputs that have source option loaded from js array or
if(!$.isArray(this.prependData)) { if(!$.isArray(this.prependData)) {
//try parse json in single quotes //try parse json in single quotes
this.options.prepend = $.fn.editableform.utils.tryParseJson(this.options.prepend, true); this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true);
if (typeof this.options.prepend === 'string') { if (typeof this.options.prepend === 'string') {
this.options.prepend = {'': this.options.prepend}; this.options.prepend = {'': this.options.prepend};
} }
@ -220,19 +237,20 @@ List - abstract class for inputs that have source option loaded from js array or
}); });
List.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, { List.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
/** /**
Source data for list. If string - considered ajax url to load items. Otherwise should be an array. Source data for list. If string - considered ajax url to load items. Otherwise should be an array.
Array format is: <code>[{value: 1, text: "text"}, {...}]</code><br> Array format is: <code>[{value: 1, text: "text"}, {...}]</code><br>
For compability it also supports format <code>{value1: "text1", value2: "text2" ...}</code> but it does not guarantee elements order. For compability it also supports format <code>{value1: "text1", value2: "text2" ...}</code> but it does not guarantee elements order.
If source is **string**, results will be cached for fields with the same source and name. See also <code>sourceCache</code> option.
@property source @property source
@type string|array|object @type string|array|object
@default null @default null
**/ **/
source:null, source:null,
/** /**
Data automatically prepended to the begining of dropdown list. Data automatically prepended to the beginning of dropdown list.
@property prepend @property prepend
@type string|array|object @type string|array|object
@ -246,9 +264,19 @@ List - abstract class for inputs that have source option loaded from js array or
@type string @type string
@default Error when loading list @default Error when loading list
**/ **/
sourceError: 'Error when loading list' sourceError: 'Error when loading list',
/**
if <code>true</code> and source is **string url** - results will be cached for fields with the same source and name.
Usefull for editable grids.
@property sourceCache
@type boolean
@default true
@since 1.2.0
**/
sourceCache: true
}); });
$.fn.editableform.types.list = List; $.fn.editabletypes.list = List;
}(window.jQuery)); }(window.jQuery));

@ -26,7 +26,7 @@ $(function(){
this.init('select', options, Select.defaults); this.init('select', options, Select.defaults);
}; };
$.fn.editableform.utils.inherit(Select, $.fn.editableform.types.list); $.fn.editableutils.inherit(Select, $.fn.editabletypes.list);
$.extend(Select.prototype, { $.extend(Select.prototype, {
renderList: function() { renderList: function() {
@ -37,6 +37,13 @@ $(function(){
for(var i=0; i<this.sourceData.length; i++) { for(var i=0; i<this.sourceData.length; i++) {
this.$input.append($('<option>', {value: this.sourceData[i].value}).text(this.sourceData[i].text)); this.$input.append($('<option>', {value: this.sourceData[i].value}).text(this.sourceData[i].text));
} }
//enter submit
this.$input.on('keydown.editable', function (e) {
if (e.which === 13) {
$(this).closest('form').submit();
}
});
}, },
value2htmlFinal: function(value, element) { value2htmlFinal: function(value, element) {
@ -48,13 +55,13 @@ $(function(){
}, },
autosubmit: function() { autosubmit: function() {
this.$input.on('change', function(){ this.$input.off('keydown.editable').on('change.editable', function(){
$(this).closest('form').submit(); $(this).closest('form').submit();
}); });
} }
}); });
Select.defaults = $.extend({}, $.fn.editableform.types.list.defaults, { Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
/** /**
@property tpl @property tpl
@default <select></select> @default <select></select>
@ -62,6 +69,6 @@ $(function(){
tpl:'<select></select>' tpl:'<select></select>'
}); });
$.fn.editableform.types.select = Select; $.fn.editabletypes.select = Select;
}(window.jQuery)); }(window.jQuery));

@ -20,18 +20,18 @@ $(function(){
this.init('text', options, Text.defaults); this.init('text', options, Text.defaults);
}; };
$.fn.editableform.utils.inherit(Text, $.fn.editableform.types.abstract); $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstract);
$.extend(Text.prototype, { $.extend(Text.prototype, {
activate: function() { activate: function() {
if(this.$input.is(':visible')) { if(this.$input.is(':visible')) {
this.$input.focus(); this.$input.focus();
$.fn.editableform.utils.setCursorPosition(this.$input.get(0), this.$input.val().length); $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
} }
} }
}); });
Text.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, { Text.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
/** /**
@property tpl @property tpl
@default <input type="text"> @default <input type="text">
@ -47,6 +47,6 @@ $(function(){
placeholder: null placeholder: null
}); });
$.fn.editableform.types.text = Text; $.fn.editabletypes.text = Text;
}(window.jQuery)); }(window.jQuery));

@ -21,7 +21,7 @@ $(function(){
this.init('textarea', options, Textarea.defaults); this.init('textarea', options, Textarea.defaults);
}; };
$.fn.editableform.utils.inherit(Textarea, $.fn.editableform.types.abstract); $.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstract);
$.extend(Textarea.prototype, { $.extend(Textarea.prototype, {
render: function () { render: function () {
@ -60,13 +60,13 @@ $(function(){
activate: function() { activate: function() {
if(this.$input.is(':visible')) { if(this.$input.is(':visible')) {
$.fn.editableform.utils.setCursorPosition(this.$input.get(0), this.$input.val().length); $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
this.$input.focus(); this.$input.focus();
} }
} }
}); });
Textarea.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, { Textarea.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
/** /**
@property tpl @property tpl
@default <textarea></textarea> @default <textarea></textarea>
@ -74,9 +74,9 @@ $(function(){
tpl:'<textarea></textarea>', tpl:'<textarea></textarea>',
/** /**
@property inputclass @property inputclass
@default span3 @default input-large
**/ **/
inputclass:'span3', inputclass: 'input-large',
/** /**
Placeholder attribute of input. Shown when input is empty. Placeholder attribute of input. Shown when input is empty.
@ -87,6 +87,6 @@ $(function(){
placeholder: null placeholder: null
}); });
$.fn.editableform.types.textarea = Textarea; $.fn.editabletypes.textarea = Textarea;
}(window.jQuery)); }(window.jQuery));

@ -3,55 +3,25 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Test X-editable</title> <title>Test X-editable</title>
<!-- jquery -->
<script src="libs/jquery/jquery-1.8.2.js"></script>
<!-- qunit -->
<link rel="stylesheet" href="libs/qunit/qunit-1.10.0.css" type="text/css" media="screen" />
<script src="libs/qunit/qunit-1.10.0.js"></script>
<!-- mockjax -->
<script src="libs/mockjax/jquery.mockjax.js"></script>
<script src="mocks.js"></script>
<script src="loader.js"></script>
<script> <script>
var fc = getFC(), window.onload = function() {
assets = getAssets(fc.f, fc.c, '../src/', 'libs/'); QUnit.config.autostart = false;
loadAssets(assets.css, assets.js); };
</script>
var fx, sfx;
$(function () { <!-- <script data-main="main.js" src="require-jquery.js"></script>-->
$.fx.off = true; <script data-main="main.js" src="require.js"></script>
$.support.transition = false;
}); <!-- qunit (should be included here, otherwise phantomjs hangs-up on several tests )-->
</script> <link rel="stylesheet" href="libs/qunit/qunit-1.10.0.css" type="text/css" media="screen" />
<script src="libs/qunit/qunit-1.10.0.js"></script>
</head> </head>
<body> <body>
<div> <div>
<div id="qunit"></div> <div id="qunit"></div>
<div id="qunit-fixture"></div> <div id="qunit-fixture"></div>
<div id="async-fixture"></div> <div id="async-fixture"></div>
</div> </div>
<!-- unit tests -->
<!-- You can also add "?module=[module]" URL to run only tests within that module-->
<script src="unit/common.js"></script>
<script src="unit/text.js"></script>
<script src="unit/select.js"></script>
<script src="unit/textarea.js"></script>
<script src="unit/api.js"></script>
<script src="unit/checklist.js"></script>
<script>
if(fc.f === 'bootstrap') {
loadJs('unit/date.js');
} else {
loadJs('unit/dateui.js');
}
</script>
</body> </body>
</html> </html>

@ -1,128 +1,170 @@
/** define(function () {
* load requred js and css according to f (form) and c (container) parameters
*/ function loadCss(url) {
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = url;
document.getElementsByTagName("head")[0].appendChild(link);
};
return {
loadCss: loadCss,
getConfig: function (baseUrl) {
var params = this.getParams(),
f = params.f,
c = params.c,
shim = {
'containers/editable-container': {
deps: ['require', 'editable-form/editable-form-utils', 'editable-form/editable-form'],
init: function(require) {
loadCss(require.toUrl("./editable-container.css"));
}
},
'element/editable-element': {
deps: ['require'], //here should be dynamically added container
init: function(require) {
loadCss(require.toUrl("./editable-element.css"));
}
},
'editable-form/editable-form': {
deps: ['require',
'inputs/text',
'inputs/textarea',
'inputs/select',
'inputs/checklist',
'inputs-ext/address/address'],
init: function(require) {
loadCss(require.toUrl("./editable-form.css"));
}
},
'inputs/select': ['inputs/list'],
'inputs/checklist': ['inputs/list'],
'inputs/list': ['inputs/abstract'],
'inputs/text': ['inputs/abstract'],
'inputs/textarea': ['inputs/abstract'],
'inputs/abstract': ['editable-form/editable-form-utils'],
function getFC() { //bootstrap
var url = window.location.href, f, c; 'bootstrap/js/bootstrap': {
if(url.match(/f=jqueryui/i)) { deps: ['require'],
f = 'jqueryui'; init: function(require) {
} else if(url.match(/f=plain/i)) { loadCss(require.toUrl("../css/bootstrap.css"));
f = 'plain'; }
} else { //bootstrap },
f = 'bootstrap'; 'editable-form/editable-form-bootstrap': [
'editable-form/editable-form',
'bootstrap/js/bootstrap'
],
'containers/editable-popover': ['containers/editable-container',
'bootstrap/js/bootstrap'
],
'inputs/date/date': {
deps: ['require',
'bootstrap/js/bootstrap',
'inputs/abstract',
'inputs/date/bootstrap-datepicker/js/bootstrap-datepicker'],
init: function(require) {
loadCss(require.toUrl("./bootstrap-datepicker/css/datepicker.css"));
}
},
//jqueryui
'jqueryui/js/jquery-ui-1.9.1.custom': {
deps: ['require'],
init: function(require) {
loadCss(require.toUrl("../css/redmond/jquery-ui-1.9.1.custom.css"));
}
},
'editable-form/editable-form-jqueryui': [
'editable-form/editable-form',
'jqueryui/js/jquery-ui-1.9.1.custom'
],
'containers/editable-tooltip': ['containers/editable-container',
'jqueryui/js/jquery-ui-1.9.1.custom'
],
'inputs/dateui/dateui': ['inputs/abstract'],
//plain
//'inputs/dateui/dateui': ['inputs/abstract', 'inputs/date/bootstrap-datepicker/js/bootstrap-datepicker'],
'containers/editable-poshytip': [
'containers/editable-container',
'poshytip/jquery.poshytip'
],
'poshytip/jquery.poshytip': {
deps: ['require'],
init: function(require) {
loadCss(require.toUrl("./tip-yellowsimple/tip-yellowsimple.css"));
}
},
'inputs/dateui/jquery-ui-datepicker/js/jquery-ui-1.9.1.custom': {
deps: ['require'],
init: function(require) {
loadCss(require.toUrl("../css/redmond/jquery-ui-1.9.1.custom.css"));
}
},
//inline container
'containers/editable-inline': ['containers/editable-container'],
//inputs-ext
'inputs-ext/address/address': {
deps: ['require', 'inputs/abstract'],
init: function(require) {
loadCss(require.toUrl("./address.css"));
}
}
};
/*
modify shim for bootstrap, jqueryui or plain
*/
if(f === 'bootstrap') {
//bootstrap
shim['editable-form/editable-form'].deps.push('inputs/date/date');
shim['element/editable-element'].deps.push('editable-form/editable-form-bootstrap');
shim['element/editable-element'].deps.push(c === 'popup' ? 'containers/editable-popover' : 'containers/editable-inline');
} else if(f === 'jqueryui') {
//jqueryui
shim['editable-form/editable-form'].deps.push('inputs/dateui/dateui');
shim['element/editable-element'].deps.push('editable-form/editable-form-jqueryui');
shim['element/editable-element'].deps.push(c === 'popup' ? 'containers/editable-tooltip' : 'containers/editable-inline');
} else {
//plain
shim['editable-form/editable-form'].deps.push('inputs/dateui/dateui');
shim['inputs/dateui/dateui'].push('inputs/dateui/jquery-ui-datepicker/js/jquery-ui-1.9.1.custom');
shim['element/editable-element'].deps.push(c === 'popup' ? 'containers/editable-poshytip' : 'containers/editable-inline');
}
/*
return requirejs config
*/
return {
baseUrl: baseUrl,
paths: {
"bootstrap": "../test/libs/bootstrap221",
"jqueryui": "../test/libs/jquery-ui-1.9.1.custom",
"poshytip": "../test/libs/poshytip",
"test": "../test"
},
shim: shim
};
},
getParams: function() {
var url = window.location.href, f, c;
if(url.match(/f=jqueryui/i)) {
f = 'jqueryui';
} else if(url.match(/f=plain/i)) {
f = 'plain';
} else {
f = 'bootstrap';
}
c = url.match(/c=inline/i) ? 'inline' : 'popup';
return {f: f, c: c};
}
} }
c = url.match(/c=inline/i) ? 'inline' : 'popup'; });
return {f: f, c: c};
}
function getAssets(f, c, src, libs) {
var
forms = src+'editable-form/',
inputs = src+'inputs/',
containers = src+'containers/',
element = src+'element/',
bootstrap = libs+'bootstrap221/',
jqueryui = libs+'jquery-ui-1.9.1.custom/',
js = [
forms+'editable-form.js',
forms+'editable-form-utils.js',
containers+'editable-container.js',
element+'editable-element.js',
inputs+'abstract.js',
inputs+'list.js',
inputs+'text.js',
inputs+'textarea.js',
inputs+'select.js',
inputs+'checklist.js'
],
css = [
forms+'editable-form.css',
containers+'editable-container.css',
element+'editable-element.css'
];
//tune js and css
if(f==='jqueryui') {
//core
js.unshift(jqueryui+'js/jquery-ui-1.9.1.custom.js')
css.unshift(jqueryui+'css/redmond/jquery-ui-1.9.1.custom.css');
//editable
js.push(forms+'editable-form-jqueryui.js');
js.push(getContainer('editable-tooltip.js'));
//date
js.push(inputs+'dateui/dateui.js');
//style
css.push('style.css');
} else if(f==='plain') {
//core
js.unshift(libs+'poshytip/jquery.poshytip.js');
css.unshift(libs+'poshytip/tip-yellowsimple/tip-yellowsimple.css');
//editable
js.push(getContainer('editable-poshytip.js'));
//date
js.push(inputs+'dateui/dateui.js');
js.push(inputs+'dateui/jquery-ui-datepicker/js/jquery-ui-1.9.1.custom.js');
css.unshift(inputs+'dateui/jquery-ui-datepicker/css/redmond/jquery-ui-1.9.1.custom.css');
//style
css.push('style.css');
/* bootstrap */
} else {
//core
js.unshift(bootstrap+'js/bootstrap.js')
css.unshift(bootstrap+'css/bootstrap.css');
css.unshift(bootstrap+'css/bootstrap-responsive.css');
//editable
js.push(forms+'editable-form-bootstrap.js');
js.push(getContainer('editable-popover.js'));
//date
js.push(inputs+'date/bootstrap-datepicker/js/bootstrap-datepicker.js');
js.push(inputs+'date/date.js');
css.push(inputs+'date/bootstrap-datepicker/css/datepicker.css');
}
function getContainer(container) {
return (c === 'inline') ? containers+'/editable-inline.js' : containers + container;
}
//js.push('main.js');
return {css: css, js: js};
}
function loadAssets(css, js) {
for(var i = 0; i < css.length; i++) {
loadCss(css[i]);
}
for(i = 0; i < js.length; i++) {
loadJs(js[i]);
}
}
function loadCss(url) {
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = url;
document.getElementsByTagName("head")[0].appendChild(link);
}
function loadJs(url) {
if(!url) return;
var script = document.createElement("script");
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}

33
test/main.js Normal file

@ -0,0 +1,33 @@
//detect version of jquery from url param, e.g. 'jquery=1.7.2'
var jqver = decodeURIComponent((new RegExp('[?|&]' + 'jquery' + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||null;
jqurl = jqver ? "http://ajax.googleapis.com/ajax/libs/jquery/"+jqver+"/jquery.min.js" : "libs/jquery/jquery-1.8.2.min.js";
require(["loader", jqurl], function(loader) {
requirejs.config(loader.getConfig("../src"));
require(['element/editable-element',
'test/libs/mockjax/jquery.mockjax'
],
function() {
//disable effects
$.fx.off = true;
$.support.transition = false;
var params = loader.getParams();
require([
'test/mocks',
'test/unit/common',
'test/unit/text',
'test/unit/textarea',
'test/unit/select',
'test/unit/checklist',
'test/unit/api',
(params.f === 'bootstrap') ? 'test/unit/date' : 'test/unit/dateui'
], function() {
QUnit.load();
QUnit.start();
});
});
});

@ -57,7 +57,7 @@ $(function () {
}); });
// usefull functions // useful functions
function tip(e) { function tip(e) {
return e.data('editableContainer').tip(); return e.data('editableContainer').tip();

35
test/require.js Normal file

@ -0,0 +1,35 @@
/*
RequireJS 2.1.2 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
Available via the MIT or new BSD license.
see: http://github.com/jrburke/requirejs for details
*/
var requirejs,require,define;
(function(Y){function H(b){return"[object Function]"===L.call(b)}function I(b){return"[object Array]"===L.call(b)}function x(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function M(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));d-=1);}}function r(b,c){return da.call(b,c)}function i(b,c){return r(b,c)&&b[c]}function E(b,c){for(var d in b)if(r(b,d)&&c(b[d],d))break}function Q(b,c,d,i){c&&E(c,function(c,h){if(d||!r(b,h))i&&"string"!==typeof c?(b[h]||(b[h]={}),Q(b[h],
c,d,i)):b[h]=c});return b}function t(b,c){return function(){return c.apply(b,arguments)}}function Z(b){if(!b)return b;var c=Y;x(b.split("."),function(b){c=c[b]});return c}function J(b,c,d,i){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=i;d&&(c.originalError=d);return c}function ea(b){function c(a,g,v){var e,n,b,c,d,j,f,h=g&&g.split("/");e=h;var l=m.map,k=l&&l["*"];if(a&&"."===a.charAt(0))if(g){e=i(m.pkgs,g)?h=[g]:h.slice(0,h.length-1);g=a=e.concat(a.split("/"));
for(e=0;g[e];e+=1)if(n=g[e],"."===n)g.splice(e,1),e-=1;else if(".."===n)if(1===e&&(".."===g[2]||".."===g[0]))break;else 0<e&&(g.splice(e-1,2),e-=2);e=i(m.pkgs,g=a[0]);a=a.join("/");e&&a===g+"/"+e.main&&(a=g)}else 0===a.indexOf("./")&&(a=a.substring(2));if(v&&(h||k)&&l){g=a.split("/");for(e=g.length;0<e;e-=1){b=g.slice(0,e).join("/");if(h)for(n=h.length;0<n;n-=1)if(v=i(l,h.slice(0,n).join("/")))if(v=i(v,b)){c=v;d=e;break}if(c)break;!j&&(k&&i(k,b))&&(j=i(k,b),f=e)}!c&&j&&(c=j,d=f);c&&(g.splice(0,d,
c),a=g.join("/"))}return a}function d(a){z&&x(document.getElementsByTagName("script"),function(g){if(g.getAttribute("data-requiremodule")===a&&g.getAttribute("data-requirecontext")===j.contextName)return g.parentNode.removeChild(g),!0})}function y(a){var g=i(m.paths,a);if(g&&I(g)&&1<g.length)return d(a),g.shift(),j.require.undef(a),j.require([a]),!0}function f(a){var g,b=a?a.indexOf("!"):-1;-1<b&&(g=a.substring(0,b),a=a.substring(b+1,a.length));return[g,a]}function h(a,g,b,e){var n,u,d=null,h=g?g.name:
null,l=a,m=!0,k="";a||(m=!1,a="_@r"+(L+=1));a=f(a);d=a[0];a=a[1];d&&(d=c(d,h,e),u=i(p,d));a&&(d?k=u&&u.normalize?u.normalize(a,function(a){return c(a,h,e)}):c(a,h,e):(k=c(a,h,e),a=f(k),d=a[0],k=a[1],b=!0,n=j.nameToUrl(k)));b=d&&!u&&!b?"_unnormalized"+(M+=1):"";return{prefix:d,name:k,parentMap:g,unnormalized:!!b,url:n,originalName:l,isDefine:m,id:(d?d+"!"+k:k)+b}}function q(a){var g=a.id,b=i(k,g);b||(b=k[g]=new j.Module(a));return b}function s(a,g,b){var e=a.id,n=i(k,e);if(r(p,e)&&(!n||n.defineEmitComplete))"defined"===
g&&b(p[e]);else q(a).on(g,b)}function C(a,g){var b=a.requireModules,e=!1;if(g)g(a);else if(x(b,function(g){if(g=i(k,g))g.error=a,g.events.error&&(e=!0,g.emit("error",a))}),!e)l.onError(a)}function w(){R.length&&(fa.apply(F,[F.length-1,0].concat(R)),R=[])}function A(a,g,b){var e=a.map.id;a.error?a.emit("error",a.error):(g[e]=!0,x(a.depMaps,function(e,c){var d=e.id,h=i(k,d);h&&(!a.depMatched[c]&&!b[d])&&(i(g,d)?(a.defineDep(c,p[d]),a.check()):A(h,g,b))}),b[e]=!0)}function B(){var a,g,b,e,n=(b=1E3*m.waitSeconds)&&
j.startTime+b<(new Date).getTime(),c=[],h=[],f=!1,l=!0;if(!T){T=!0;E(k,function(b){a=b.map;g=a.id;if(b.enabled&&(a.isDefine||h.push(b),!b.error))if(!b.inited&&n)y(g)?f=e=!0:(c.push(g),d(g));else if(!b.inited&&(b.fetched&&a.isDefine)&&(f=!0,!a.prefix))return l=!1});if(n&&c.length)return b=J("timeout","Load timeout for modules: "+c,null,c),b.contextName=j.contextName,C(b);l&&x(h,function(a){A(a,{},{})});if((!n||e)&&f)if((z||$)&&!U)U=setTimeout(function(){U=0;B()},50);T=!1}}function D(a){r(p,a[0])||
q(h(a[0],null,!0)).init(a[1],a[2])}function G(a){var a=a.currentTarget||a.srcElement,b=j.onScriptLoad;a.detachEvent&&!V?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=j.onScriptError;(!a.detachEvent||V)&&a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function K(){var a;for(w();F.length;){a=F.shift();if(null===a[0])return C(J("mismatch","Mismatched anonymous define() module: "+a[a.length-1]));D(a)}}var T,W,j,N,U,m={waitSeconds:7,
baseUrl:"./",paths:{},pkgs:{},shim:{},map:{},config:{}},k={},X={},F=[],p={},S={},L=1,M=1;N={require:function(a){return a.require?a.require:a.require=j.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?a.exports:a.exports=p[a.map.id]={}},module:function(a){return a.module?a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){return m.config&&i(m.config,a.map.id)||{}},exports:p[a.map.id]}}};W=function(a){this.events=i(X,a.id)||{};this.map=a;this.shim=
i(m.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};W.prototype={init:function(a,b,c,e){e=e||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=t(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=c;this.inited=!0;this.ignore=e.ignore;e.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=
b)},fetch:function(){if(!this.fetched){this.fetched=!0;j.startTime=(new Date).getTime();var a=this.map;if(this.shim)j.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],t(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=this.map.url;S[a]||(S[a]=!0,j.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,b,c=this.map.id;b=this.depExports;var e=this.exports,n=this.factory;
if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(H(n)){if(this.events.error)try{e=j.execCb(c,n,b,e)}catch(d){a=d}else e=j.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=[this.map.id],a.requireType="define",C(this.error=a)}else e=n;this.exports=e;if(this.map.isDefine&&
!this.ignore&&(p[c]=e,l.onResourceLoad))l.onResourceLoad(j,this.map,this.depMaps);delete k[c];this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=h(a.prefix);this.depMaps.push(d);s(d,"defined",t(this,function(e){var n,d;d=this.map.name;var v=this.map.parentMap?this.map.parentMap.name:null,f=j.makeRequire(a.parentMap,{enableBuildCallback:!0,
skipMap:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,v,!0)})||""),e=h(a.prefix+"!"+d,this.map.parentMap),s(e,"defined",t(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=i(k,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",t(this,function(a){this.emit("error",a)}));d.enable()}}else n=t(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=t(this,function(a){this.inited=!0;this.error=
a;a.requireModules=[b];E(k,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&delete k[a.map.id]});C(a)}),n.fromText=t(this,function(e,c){var d=a.name,u=h(d),v=O;c&&(e=c);v&&(O=!1);q(u);r(m.config,b)&&(m.config[d]=m.config[b]);try{l.exec(e)}catch(k){throw Error("fromText eval for "+d+" failed: "+k);}v&&(O=!0);this.depMaps.push(u);j.completeLoad(d);f([d],n)}),e.load(a.name,f,n,m)}));j.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){this.enabling=this.enabled=!0;x(this.depMaps,t(this,function(a,
b){var c,e;if("string"===typeof a){a=h(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=i(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",t(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&s(a,"error",this.errback)}c=a.id;e=k[c];!r(N,c)&&(e&&!e.enabled)&&j.enable(a,this)}));E(this.pluginMaps,t(this,function(a){var b=i(k,a.id);b&&!b.enabled&&j.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=
this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){x(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};j={config:m,contextName:b,registry:k,defined:p,urlFetched:S,defQueue:F,Module:W,makeModuleMap:h,nextTick:l.nextTick,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=m.pkgs,c=m.shim,e={paths:!0,config:!0,map:!0};E(a,function(a,b){e[b]?"map"===b?Q(m[b],a,!0,!0):Q(m[b],a,!0):m[b]=a});a.shim&&(E(a.shim,function(a,
b){I(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=j.makeShimExports(a);c[b]=a}),m.shim=c);a.packages&&(x(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ga,"").replace(aa,"")}}),m.pkgs=b);E(k,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=h(b))});if(a.deps||a.callback)j.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Y,arguments));
return b||a.exports&&Z(a.exports)}},makeRequire:function(a,d){function f(e,c,u){var i,m;d.enableBuildCallback&&(c&&H(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(H(c))return C(J("requireargs","Invalid require call"),u);if(a&&r(N,e))return N[e](k[a.id]);if(l.get)return l.get(j,e,a);i=h(e,a,!1,!0);i=i.id;return!r(p,i)?C(J("notloaded",'Module name "'+i+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):p[i]}K();j.nextTick(function(){K();m=q(h(null,a));m.skipMap=d.skipMap;
m.init(e,c,u,{enabled:!0});B()});return f}d=d||{};Q(f,{isBrowser:z,toUrl:function(b){var d=b.lastIndexOf("."),g=null;-1!==d&&(g=b.substring(d,b.length),b=b.substring(0,d));return j.nameToUrl(c(b,a&&a.id,!0),g)},defined:function(b){return r(p,h(b,a,!1,!0).id)},specified:function(b){b=h(b,a,!1,!0).id;return r(p,b)||r(k,b)}});a||(f.undef=function(b){w();var c=h(b,a,!0),d=i(k,b);delete p[b];delete S[c.url];delete X[b];d&&(d.events.defined&&(X[b]=d.events),delete k[b])});return f},enable:function(a){i(k,
a.id)&&q(a).enable()},completeLoad:function(a){var b,c,d=i(m.shim,a)||{},h=d.exports;for(w();F.length;){c=F.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);D(c)}c=i(k,a);if(!b&&!r(p,a)&&c&&!c.inited){if(m.enforceDefine&&(!h||!Z(h)))return y(a)?void 0:C(J("nodefine","No define call for "+a,null,[a]));D([a,d.deps||[],d.exportsFn])}B()},nameToUrl:function(a,b){var c,d,h,f,j,k;if(l.jsExtRegExp.test(a))f=a+(b||"");else{c=m.paths;d=m.pkgs;f=a.split("/");for(j=f.length;0<j;j-=1)if(k=
f.slice(0,j).join("/"),h=i(d,k),k=i(c,k)){I(k)&&(k=k[0]);f.splice(0,j,k);break}else if(h){c=a===h.name?h.location+"/"+h.main:h.location;f.splice(0,j,c);break}f=f.join("/");f+=b||(/\?/.test(f)?"":".js");f=("/"===f.charAt(0)||f.match(/^[\w\+\.\-]+:/)?"":m.baseUrl)+f}return m.urlArgs?f+((-1===f.indexOf("?")?"?":"&")+m.urlArgs):f},load:function(a,b){l.load(j,a,b)},execCb:function(a,b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if("load"===a.type||ha.test((a.currentTarget||a.srcElement).readyState))P=
null,a=G(a),j.completeLoad(a.id)},onScriptError:function(a){var b=G(a);if(!y(b.id))return C(J("scripterror","Script error",a,[b.id]))}};j.require=j.makeRequire();return j}var l,w,A,D,s,G,P,K,ba,ca,ia=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ja=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,aa=/\.js$/,ga=/^\.\//;w=Object.prototype;var L=w.toString,da=w.hasOwnProperty,fa=Array.prototype.splice,z=!!("undefined"!==typeof window&&navigator&&document),$=!z&&"undefined"!==typeof importScripts,ha=z&&
"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,V="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),B={},q={},R=[],O=!1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(H(requirejs))return;q=requirejs;requirejs=void 0}"undefined"!==typeof require&&!H(require)&&(q=require,require=void 0);l=requirejs=function(b,c,d,y){var f,h="_";!I(b)&&"string"!==typeof b&&(f=b,I(c)?(b=c,c=d,d=y):b=[]);f&&f.context&&(h=f.context);(y=i(B,h))||(y=B[h]=l.s.newContext(h));
f&&y.configure(f);return y.require(b,c,d)};l.config=function(b){return l(b)};l.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,4)}:function(b){b()};require||(require=l);l.version="2.1.2";l.jsExtRegExp=/^\/|:|\?|\.js$/;l.isBrowser=z;w=l.s={contexts:B,newContext:ea};l({});x(["toUrl","undef","defined","specified"],function(b){l[b]=function(){var c=B._;return c.require[b].apply(c,arguments)}});if(z&&(A=w.head=document.getElementsByTagName("head")[0],D=document.getElementsByTagName("base")[0]))A=
w.head=D.parentNode;l.onError=function(b){throw b;};l.load=function(b,c,d){var i=b&&b.config||{},f;if(z)return f=i.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"),f.type=i.scriptType||"text/javascript",f.charset="utf-8",f.async=!0,f.setAttribute("data-requirecontext",b.contextName),f.setAttribute("data-requiremodule",c),f.attachEvent&&!(f.attachEvent.toString&&0>f.attachEvent.toString().indexOf("[native code"))&&!V?(O=!0,f.attachEvent("onreadystatechange",
b.onScriptLoad)):(f.addEventListener("load",b.onScriptLoad,!1),f.addEventListener("error",b.onScriptError,!1)),f.src=d,K=f,D?A.insertBefore(f,D):A.appendChild(f),K=null,f;$&&(importScripts(d),b.completeLoad(c))};z&&M(document.getElementsByTagName("script"),function(b){A||(A=b.parentNode);if(s=b.getAttribute("data-main"))return q.baseUrl||(G=s.split("/"),ba=G.pop(),ca=G.length?G.join("/")+"/":"./",q.baseUrl=ca,s=ba),s=s.replace(aa,""),q.deps=q.deps?q.deps.concat(s):[s],!0});define=function(b,c,d){var i,
f;"string"!==typeof b&&(d=c,c=b,b=null);I(c)||(d=c,c=[]);!c.length&&H(d)&&d.length&&(d.toString().replace(ia,"").replace(ja,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c));if(O){if(!(i=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),i=P;i&&(b||(b=i.getAttribute("data-requiremodule")),f=B[i.getAttribute("data-requirecontext")])}(f?f.defQueue:R).push([b,c,d])};define.amd=
{jQuery:!0};l.exec=function(b){return eval(b)};l(q)}})(this);

@ -68,43 +68,7 @@ $(function () {
ok(!('dob' in values), 'date value not present') ; ok(!('dob' in values), 'date value not present') ;
}); });
/* test("'init' event", function () {
//deprecated in 2.0
asyncTest("'update' event", function () {
expect(2);
var e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo(fx).editable(),
e_nopk = $('<a href="#" data-url="post.php" data-name="text1">abc</a>').appendTo(fx).editable(),
newVal = 'xyt';
e.on('update', function() {
equal($(this).data('editable').value, newVal, 'triggered update after submit');
});
e_nopk.on('update', function() {
equal($(this).data('editable').value, newVal, 'triggered update after no-submit');
});
e_nopk.click();
var p = e_nopk.data('popover').$tip;
p.find('input').val(newVal);
p.find('form').submit();
e.click();
p = tip(e);
p.find('input').val(newVal);
p.find('form').submit();
setTimeout(function() {
e.remove();
e_nopk.remove();
start();
}, timeout);
});
*/
/*
//deprecated in 2.0
test("'init' event", function () {
expect(1); expect(1);
var e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo('#qunit-fixture'); var e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo('#qunit-fixture');
@ -113,74 +77,11 @@ $(function () {
}); });
e.editable(); e.editable();
}); });
*/
asyncTest("events: shown / hidden (reason: cancel, onblur, manual)", function () {
asyncTest("'render' event for text", function () { expect(11);
expect(4); var val = '1', test_reason,
var val = 'afas',
e = $('<a href="#" data-pk="1" data-type="text" data-url="post.php" data-name="text1">'+val+'</a>').appendTo(fx),
isInit = true;
e.on('render', function(e, editable) {
equal(editable.isInit, isInit, 'isInit flag correct');
equal(editable.value, val, 'value correct');
});
e.editable();
isInit = false;
val = '123';
e.click();
var p = tip(e);
p.find('input[type=text]').val(val);
p.find('form').submit();
setTimeout(function() {
e.remove();
start();
}, timeout);
});
asyncTest("'render' event for select", function () {
expect(4);
var val = '1',
e = $('<a href="#" data-pk="1" data-type="select" data-url="post.php" data-name="text1" data-value="'+val+'"></a>').appendTo(fx),
isInit = true;
e.on('render', function(e, editable) {
equal(editable.isInit, isInit, 'isInit flag correct');
equal(editable.value, val, 'init triggered, value correct');
});
e.editable({
source: 'groups.php',
autotext: 'always'
});
setTimeout(function() {
isInit = false;
val = '3';
e.click();
var p = tip(e);
p.find('select').val(val);
p.find('form').submit();
setTimeout(function() {
e.remove();
start();
}, timeout);
}, timeout);
});
asyncTest("events: shown / cancel / hidden", function () {
expect(3);
var val = '1',
e = $('<a href="#" data-pk="1" data-type="select" data-url="post.php" data-name="text" data-value="'+val+'"></a>').appendTo(fx); e = $('<a href="#" data-pk="1" data-type="select" data-url="post.php" data-name="text" data-value="'+val+'"></a>').appendTo(fx);
e.on('shown', function(event) { e.on('shown', function(event) {
@ -188,14 +89,8 @@ $(function () {
equal(editable.value, val, 'shown triggered, value correct'); equal(editable.value, val, 'shown triggered, value correct');
}); });
e.on('cancel', function(event) { e.on('hidden', function(event, reason) {
var editable = $(this).data('editable'); ok((reason === test_reason) || (test_reason === 'manual' && reason === undefined), 'hidden triggered, reason ok');
ok(true, 'cancel triggered');
});
e.on('hidden', function(event) {
var editable = $(this).data('editable');
ok(true, 'hidden triggered');
}); });
e.editable({ e.editable({
@ -206,41 +101,54 @@ $(function () {
setTimeout(function() { setTimeout(function() {
var p = tip(e); var p = tip(e);
p.find('button[type=button]').click();
setTimeout(function() { test_reason = 'cancel'
e.remove(); p.find('button[type=button]').click(); //cancel
start(); ok(!p.is(':visible'), 'popover closed');
}, timeout);
test_reason = 'onblur'
e.click();
p = tip(e);
ok(p.is(':visible'), 'popover shown');
e.parent().click();
ok(!p.is(':visible'), 'popover closed');
test_reason = 'manual'
e.click();
p = tip(e);
ok(p.is(':visible'), 'popover shown');
e.editable('hide');
ok(!p.is(':visible'), 'popover closed');
e.remove();
start();
}, timeout); }, timeout);
}); });
asyncTest("event: save / hidden", function () { asyncTest("event: save / hidden (reason: save)", function () {
expect(2); expect(2);
var val = '1', var val = '1',
e = $('<a href="#" data-pk="1" data-type="select" data-url="post.php" data-name="text" data-value="'+val+'"></a>').appendTo(fx); e = $('<a href="#" data-pk="1" data-type="select" data-url="post.php" data-name="text" data-value="'+val+'"></a>').appendTo(fx);
e.on('save', function(event, params) { e.on('save', function(event, params) {
var editable = $(this).data('editable');
equal(params.newValue, 2, 'save triggered, value correct'); equal(params.newValue, 2, 'save triggered, value correct');
}); });
e.on('hidden', function(event) { e.on('hidden', function(event, reason) {
var editable = $(this).data('editable'); equal(reason, 'save', 'hidden triggered, reason ok');
ok(true, 'hidden triggered');
}); });
e.editable({ e.editable({
source: 'groups.php', source: groups,
}); });
e.click(); e.click();
var p = tip(e); var p = tip(e);
p.find('select').val(2); p.find('select').val(2);
p.find('form').submit(); p.find('form').submit();
setTimeout(function() { setTimeout(function() {
p.find('button[type=button]').click();
e.remove(); e.remove();
start(); start();
}, timeout); }, timeout);
@ -305,7 +213,6 @@ $(function () {
}); });
asyncTest("'submit' method: client and server validation", function () { asyncTest("'submit' method: client and server validation", function () {
expect(6);
var ev1 = 'ev1', var ev1 = 'ev1',
ev2 = 'ev2', ev2 = 'ev2',
e1v = 'e1v', e1v = 'e1v',
@ -322,6 +229,7 @@ $(function () {
equal(settings.data.text, ev2, 'first value ok'); equal(settings.data.text, ev2, 'first value ok');
equal(settings.data.text1, e1v, 'second value ok'); equal(settings.data.text1, e1v, 'second value ok');
equal(settings.data.a, 123, 'custom data ok'); equal(settings.data.a, 123, 'custom data ok');
equal(settings.type, 'PUT', 'ajaxOptions ok');
this.responseText = {errors: { this.responseText = {errors: {
text1: 'server-invalid' text1: 'server-invalid'
} }
@ -348,11 +256,13 @@ $(function () {
data: {a: 123}, data: {a: 123},
error: function(data) { error: function(data) {
equal(data.errors.text1, 'server-invalid', 'server validation error ok'); equal(data.errors.text1, 'server-invalid', 'server validation error ok');
e.remove(); e.remove();
e1.remove(); e1.remove();
start(); start();
} },
ajaxOptions: {
type: 'PUT'
}
}); });
}); });
@ -431,4 +341,4 @@ $(function () {
equal(e.text(), groups[2], 'new text shown correctly'); equal(e.text(), groups[2], 'new text shown correctly');
}); });
}); });

@ -4,21 +4,28 @@ $(function () {
setup: function(){ setup: function(){
sfx = $('#qunit-fixture'), sfx = $('#qunit-fixture'),
fx = $('#async-fixture'); fx = $('#async-fixture');
$.support.transition = false;
} }
}); });
asyncTest("should load options, set correct value and save new value", function () { asyncTest("should load options, set correct value and save new value", function () {
var sep = '-', var sep = '<br>',
newValue, newValue,
e = $('<a href="#" data-type="checklist" data-url="post.php"></a>').appendTo(fx).editable({ e = $('<a href="#" data-type="checklist" data-url="post-checklist.php"></a>').appendTo(fx).editable({
pk: 1, pk: 1,
source: groupsArr, source: groupsArr,
value: [2, 3], value: [2, 3]
viewseparator: sep
}); });
equal(e.text(), groups[2]+sep+groups[3], 'autotext ok'); equal(e.html(), groups[2]+sep+groups[3], 'autotext ok');
$.mockjax({
url: 'post-checklist.php',
response: function(settings) {
ok($.isArray(settings.data.value), 'value submitted as array');
equal(settings.data.value.sort().join(''), [newValue, 3].join(''), 'submitted array correct');
}
});
e.click(); e.click();
var p = tip(e); var p = tip(e);
@ -28,8 +35,8 @@ $(function () {
equal(p.find('input[type="checkbox"]:checked').eq(1).val(), 3, '2nd checked'); equal(p.find('input[type="checkbox"]:checked').eq(1).val(), 3, '2nd checked');
//set new value //set new value
p.find('input[type="checkbox"]:checked').eq(0).click(); p.find('input[type="checkbox"]:checked').eq(0).click(); //uncheck 2
p.find('input[type="checkbox"]').first().click(); p.find('input[type="checkbox"]').first().click(); //check first
newValue = p.find('input[type="checkbox"]').first().val(); newValue = p.find('input[type="checkbox"]').first().val();
//submit //submit
@ -39,7 +46,7 @@ $(function () {
ok(!p.is(':visible'), 'popup closed'); ok(!p.is(':visible'), 'popup closed');
equal(e.data('editable').value.join(''), [newValue, 3].join(''), 'new value ok') equal(e.data('editable').value.join(''), [newValue, 3].join(''), 'new value ok')
equal(e.text(), groups[newValue]+sep+groups[3], 'new text ok'); equal(e.html(), groups[newValue]+'<br>'+groups[3], 'new text ok');
// open container again to see what checked // open container again to see what checked
e.click() e.click()
@ -54,40 +61,5 @@ $(function () {
start(); start();
}, timeout); }, timeout);
}); });
asyncTest("limit option", function () {
var e = $('<a href="#" data-type="checklist" data-value="2,3" data-url="post.php"></a>').appendTo(fx).editable({
pk: 1,
source: groupsArr,
limit: 1,
limitText: '{checked} of {count}'
});
equal(e.text(), '2 of '+groupsArr.length, 'autotext ok');
e.click();
var p = tip(e);
equal(p.find('input[type="checkbox"]:checked').length, 2, 'checked count ok');
equal(p.find('input[type="checkbox"]:checked').eq(0).val(), 2, '1st checked');
equal(p.find('input[type="checkbox"]:checked').eq(1).val(), 3, '2nd checked');
//set new value
p.find('input[type="checkbox"]').first().click();
newValue = p.find('input[type="checkbox"]').first().val();
//submit
p.find('form').submit();
setTimeout(function() {
ok(!p.is(':visible'), 'popup closed');
equal(e.text(), '3 of '+groupsArr.length, 'autotext ok');
e.remove();
start();
}, timeout);
});
}); });

@ -62,9 +62,9 @@
// equal(e3.data('editable').lastSavedValue, v, 'lastSavedValue taken from text correctly (escaped)'); // equal(e3.data('editable').lastSavedValue, v, 'lastSavedValue taken from text correctly (escaped)');
}); });
test("should take container's title from json options", function () { test("container's title and placement from json options", function () {
//do not test inline //do not test inline
if(fc.c === 'inline') { if($.fn.editableContainer.Constructor.prototype.containerName === 'editableform') {
expect(0); expect(0);
return; return;
} }
@ -81,7 +81,7 @@
ok(p.is(':visible'), 'popover shown'); ok(p.is(':visible'), 'popover shown');
//todo: for jqueryui phantomjs calcs wrong position. Need investigation //todo: for jqueryui phantomjs calcs wrong position. Need investigation
if(!$.browser.webkit && fc.f !== 'jqueryui') { if(!$.browser.webkit && $.fn.editableContainer.Constructor.prototype.containerName !== 'tooltip') {
ok(p.offset().top > e.offset().top, 'placement ok'); ok(p.offset().top > e.offset().top, 'placement ok');
} }
@ -362,36 +362,88 @@
ok(!p.find('.editable-buttons').length, '.editable-buttons block not rendered'); ok(!p.find('.editable-buttons').length, '.editable-buttons block not rendered');
}); });
//unfortunatly, testing this feature does not always work in browsers. Tested manually. asyncTest("composite pk defined as json in data-pk attribute", function () {
/* var e = $('<a href="#" data-pk="{a: 1, b: 2}" data-url="post-pk.php">abc</a>').appendTo(fx).editable({
test("enablefocus option", function () { name: 'username'
// focusing not passed in phantomjs }),
if($.browser.webkit) { newText = 'cd<e>;"'
ok(true, 'skipped in PhantomJS');
return; $.mockjax({
} url: 'post-pk.php',
response: function(settings) {
var e = $('<a href="#">abc</a>').appendTo('#qunit-fixture').editable({ equal(settings.data.pk.a, 1, 'first part ok');
enablefocus: true equal(settings.data.pk.b, 2, 'second part ok');
}), }
e1 = $('<a href="#">abcd</a>').appendTo('#qunit-fixture').editable({ });
enablefocus: false
}); e.click()
var p = tip(e);
e.click()
var p = tip(e); ok(p.find('input[type=text]').length, 'input exists')
ok(p.is(':visible'), 'popover 1 visible'); p.find('input').val(newText);
p.find('button[type=button]').click(); p.find('form').submit();
ok(!p.is(':visible'), 'popover closed');
ok(e.is(':focus'), 'element 1 is focused'); setTimeout(function() {
e.remove();
e1.click() start();
p = tip(e1); }, timeout);
ok(p.is(':visible'), 'popover 2 visible');
p.find('button[type=button]').click(); });
ok(!p.is(':visible'), 'popover closed');
ok(!e1.is(':focus'), 'element 2 is not focused'); asyncTest("savenochange: false", function () {
}); var v = 'abc',
*/ e = $('<a href="#" data-type="text" data-pk="1" data-url="post-no.php" data-name="text1">'+v+'</a>').appendTo(fx).editable({
savenochange: false
}),
req = 0;
$.mockjax({
url: 'post-no.php',
response: function() {
req++;
}
});
e.click();
var p = tip(e);
ok(p.is(':visible'), 'popover visible');
p.find('input[type="text"]').val(v);
p.find('form').submit();
setTimeout(function() {
ok(!p.is(':visible'), 'popover closed');
equal(req, 0, 'request was not performed');
e.remove();
start();
}, timeout);
});
asyncTest("savenochange: true", function () {
var v = 'abc',
e = $('<a href="#" data-type="text" data-pk="1" data-url="post-yes.php" data-name="text1">'+v+'</a>').appendTo(fx).editable({
savenochange: true
}),
req = 0;
$.mockjax({
url: 'post-yes.php',
response: function() {
req++;
}
});
e.click();
var p = tip(e);
ok(p.is(':visible'), 'popover visible');
p.find('input[type="text"]').val(v);
p.find('form').submit();
setTimeout(function() {
ok(!p.is(':visible'), 'popover closed');
equal(req, 1, 'request was performed');
e.remove();
start();
}, timeout);
});
}(jQuery)); }(jQuery));

@ -14,9 +14,7 @@ $(function () {
} }
asyncTest("container should contain datepicker with value and save new entered date", function () { asyncTest("container should contain datepicker with value and save new entered date", function () {
expect(9); $.fn.editabletypes.date.defaults.datepicker.weekStart = 1;
$.fn.editableform.types.date.defaults.datepicker.weekStart = 1;
var d = '15.05.1984', var d = '15.05.1984',
e = $('<a href="#" data-type="date" data-pk="1" data-url="post-date.php">'+d+'</a>').appendTo(fx).editable({ e = $('<a href="#" data-type="date" data-pk="1" data-url="post-date.php">'+d+'</a>').appendTo(fx).editable({
@ -39,8 +37,10 @@ $(function () {
e.click(); e.click();
var p = tip(e); var p = tip(e);
ok(p.find('.datepicker').is(':visible'), 'datepicker exists'); ok(p.find('.datepicker').is(':visible'), 'datepicker exists');
ok(p.find('.datepicker').find('.datepicker-days').is(':visible'), 'datepicker days visible');
equal(frmt(e.data('editable').value, f), d, 'day set correct'); equal(frmt(e.data('editable').value, f), d, 'day set correct');
ok(p.find('td.day.active').is(':visible'), 'active day is visible');
equal(p.find('td.day.active').text(), 15, 'day shown correct'); equal(p.find('td.day.active').text(), 15, 'day shown correct');
equal(p.find('th.dow').eq(0).text(), 'Mo', 'weekStart correct'); equal(p.find('th.dow').eq(0).text(), 'Mo', 'weekStart correct');
@ -60,7 +60,7 @@ $(function () {
asyncTest("viewformat, init by text", function () { asyncTest("viewformat, init by text", function () {
$.fn.editableform.types.date.defaults.datepicker.weekStart = 1; $.fn.editabletypes.date.defaults.datepicker.weekStart = 1;
var dview = '15/05/1984', var dview = '15/05/1984',
d = '1984-05-15', d = '1984-05-15',

@ -345,7 +345,7 @@ $(function () {
var e = $('<a href="#" data-type="select" data-pk="1" data-name="name1" data-value="1" data-autotext="always" data-url="post.php" data-source="groups-cache-sim-err.php">35</a>').appendTo(fx).editable(), var e = $('<a href="#" data-type="select" data-pk="1" data-name="name1" data-value="1" data-autotext="always" data-url="post.php" data-source="groups-cache-sim-err.php">35</a>').appendTo(fx).editable(),
e1 = $('<a href="#" data-type="select" data-pk="1" data-name="name1" data-value="2" data-autotext="always" data-url="post.php" data-source="groups-cache-sim-err.php">35</a>').appendTo(fx).editable(), e1 = $('<a href="#" data-type="select" data-pk="1" data-name="name1" data-value="2" data-autotext="always" data-url="post.php" data-source="groups-cache-sim-err.php">35</a>').appendTo(fx).editable(),
e2 = $('<a href="#" data-type="select" data-pk="1" data-name="name1" data-value="3" data-autotext="always" data-url="post.php" data-source="groups-cache-sim-err.php">6456</a>').appendTo(fx).editable(), e2 = $('<a href="#" data-type="select" data-pk="1" data-name="name1" data-value="3" data-autotext="always" data-url="post.php" data-source="groups-cache-sim-err.php">6456</a>').appendTo(fx).editable(),
errText = $.fn.editableform.types.select.defaults.sourceError; errText = $.fn.editabletypes.select.defaults.sourceError;
setTimeout(function() { setTimeout(function() {
@ -363,6 +363,55 @@ $(function () {
}); });
asyncTest("sourceCache: false", function () {
var e = $('<a href="#" data-type="select" data-pk="1" data-name="name1" data-value="2" data-url="post.php" data-source="groups-cache-false.php">customer</a>').appendTo(fx).editable({
sourceCache: false
}),
e1 = $('<a href="#" data-type="select" data-pk="1" id="name1" data-value="2" data-url="post.php" data-source="groups-cache-false.php">customer</a>').appendTo(fx).editable({
sourceCache: false
}),
req = 0;
$.mockjax({
url: 'groups-cache-false.php',
response: function() {
req++;
this.responseText = groups;
}
});
//click first
e.click();
var p = tip(e);
setTimeout(function() {
ok(p.is(':visible'), 'popover visible');
equal(p.find('select').find('option').length, size, 'options loaded');
equal(req, 1, 'one request performed');
p.find('button[type=button]').click();
ok(!p.is(':visible'), 'popover was removed');
//click second
e1.click();
p = tip(e1);
setTimeout(function() {
ok(p.is(':visible'), 'popover2 visible');
equal(p.find('select').find('option').length, size, 'options loaded');
equal(req, 2, 'second request performed');
p.find('button[type=button]').click();
ok(!p.is(':visible'), 'popover was removed');
e.remove();
e1.remove();
start();
}, timeout);
}, timeout);
});
asyncTest("autotext: auto", function () { asyncTest("autotext: auto", function () {
expect(3); expect(3);
@ -474,6 +523,60 @@ $(function () {
e.remove(); e.remove();
start(); start();
}, timeout); }, timeout);
}); });
}); asyncTest("'display' callback", function () {
var e = $('<a href="#" data-type="select" data-value="2" data-url="post.php"></a>').appendTo(fx).editable({
pk: 1,
source: groups,
display: function(value, sourceData) {
var els = $.grep(sourceData, function(o) {return o.value == value;});
$(this).text('qq' + els[0].text);
}
}),
selected = 3;
equal(e.text(), 'qq'+groups[2], 'autotext display ok');
e.click();
var p = tip(e);
p.find('select').val(selected);
p.find('form').submit();
setTimeout(function() {
ok(!p.is(':visible'), 'popover closed');
equal(e.data('editable').value, selected, 'new value saved')
equal(e.text(), 'qq'+groups[selected], 'text shown correctly')
e.remove();
start();
}, timeout);
});
asyncTest("submit by enter", function () {
var e = $('<a href="#" data-type="select" data-value="2" data-url="post.php"></a>').appendTo(fx).editable({
pk: 1,
source: groups
}),
selected = 3;
e.click();
var p = tip(e);
p.find('select').val(selected);
var event = jQuery.Event("keydown");
event.which = 13;
p.find('select').trigger(event);
setTimeout(function() {
ok(!p.is(':visible'), 'popover closed');
equal(e.data('editable').value, selected, 'new value saved')
equal(e.text(), groups[selected], 'text shown correctly')
e.remove();
start();
}, timeout);
})
});

@ -43,16 +43,16 @@ $(function () {
asyncTest("should load correct value and save new entered text (and value)", function () { asyncTest("should load correct value and save new entered text (and value)", function () {
var v = 'ab<b>"', var v = 'ab<b>"',
esc_v = $('<div>').text(v).html(), esc_v = $('<div>').text(v).html(),
e = $('<a href="#" data-pk="1" data-name="text1" data-url="post-text.php" data-params="{\'q\': \'w\'}">'+esc_v+'</a>').appendTo(fx).editable({ e = $('<a href="#" data-pk="1" data-name="text1" data-url="post-text-main.php" data-params="{\'q\': \'w\'}">'+esc_v+'</a>').appendTo(fx).editable({
success: function(data) { success: function(response, newValue) {
return false; equal(newValue, newText, 'new value in success correct');
} }
}), }),
data, data,
newText = 'cd<e>;"'; newText = 'cd&gt;e>;"';
$.mockjax({ $.mockjax({
url: 'post-text.php', url: 'post-text-main.php',
response: function(settings) { response: function(settings) {
data = settings.data; data = settings.data;
} }
@ -61,13 +61,13 @@ $(function () {
e.click() e.click()
var p = tip(e); var p = tip(e);
ok(p.is(':visible'), 'popover visible') ok(p.is(':visible'), 'popover visible');
ok(p.find('.editableform-loading').length, 'loading class exists') ok(p.find('.editableform-loading').length, 'loading class exists');
ok(!p.find('.editableform-loading').is(':visible'), 'loading class is hidden') ok(!p.find('.editableform-loading').is(':visible'), 'loading class is hidden');
ok(p.find('input[type=text]').length, 'input exists') ok(p.find('input[type=text]').length, 'input exists');
equal(p.find('input[type=text]').val(), v, 'input contain correct value') equal(p.find('input[type=text]').val(), v, 'input contain correct value');
p.find('input').val(newText); p.find('input').val(newText);
p.find('button[type=submit]').click(); p.find('form').submit();
ok(p.find('.editableform-loading').is(':visible'), 'loading class is visible'); ok(p.find('.editableform-loading').is(':visible'), 'loading class is visible');
setTimeout(function() { setTimeout(function() {
@ -88,7 +88,10 @@ $(function () {
asyncTest("should show error on server validation", function () { asyncTest("should show error on server validation", function () {
var msg = 'required', var msg = 'required',
e = $('<a href="#" data-name="text1">abc</a>').appendTo(fx).editable({ e = $('<a href="#" data-name="text1">abc</a>').appendTo(fx).editable({
validate: function(value) { if(value == '') return msg; } validate: function(value) {
ok(this === e[0], 'scope is ok');
if(value == '') return msg;
}
}), }),
newText = ''; newText = '';
@ -149,34 +152,11 @@ $(function () {
}); });
*/ */
asyncTest("should not perform request if value not changed", function () {
var e = $('<a href="#" data-pk="1" data-url="post-no.php" data-name="text1">abc</a>').appendTo(fx).editable(),
req = 0;
$.mockjax({
url: 'post-no.php',
response: function() {
req++;
}
});
e.click();
var p = tip(e);
ok(p.is(':visible'), 'popover visible');
p.find('button[type=submit]').click();
setTimeout(function() {
ok(!p.is(':visible'), 'popover closed');
equal(req, 0, 'request was not performed');
e.remove();
start();
}, timeout);
});
asyncTest("should show error if success callback returns string", function () { asyncTest("should show error if success callback returns string", function () {
var newText = 'cd<e>;"', var newText = 'cd<e>;"',
e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo(fx).editable({ e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo(fx).editable({
success: function(response, newValue) { success: function(response, newValue) {
ok(this === e[0], 'scope is ok');
equal(newValue, newText, 'value in success passed correctly'); equal(newValue, newText, 'value in success passed correctly');
return 'error'; return 'error';
} }
@ -266,6 +246,7 @@ $(function () {
var e = $('<a href="#" data-pk="1" data-url="post-resp.php">abc</a>').appendTo(fx).editable({ var e = $('<a href="#" data-pk="1" data-url="post-resp.php">abc</a>').appendTo(fx).editable({
name: 'username', name: 'username',
params: function(params) { params: function(params) {
ok(this === e[0], 'scope is ok');
equal(params.pk, 1, 'params in func already have values (pk)'); equal(params.pk, 1, 'params in func already have values (pk)');
return { q: 2, pk: 3 }; return { q: 2, pk: 3 };
}, },
@ -328,11 +309,12 @@ $(function () {
asyncTest("submit to url defined as function", function () { asyncTest("submit to url defined as function", function () {
expect(3); expect(4);
var newText = 'qwe', var newText = 'qwe',
//should be called even without pk! //should be called even without pk!
e = $('<a href="#" data-pk1="1" id="a"></a>').appendTo(fx).editable({ e = $('<a href="#" data-pk1="1" id="a"></a>').appendTo(fx).editable({
url: function(params) { url: function(params) {
ok(this === e[0], 'scope is ok');
ok(params.value, newText, 'new text passed in users function'); ok(params.value, newText, 'new text passed in users function');
var d = new $.Deferred; var d = new $.Deferred;
return d.reject('my error'); return d.reject('my error');
@ -472,6 +454,55 @@ $(function () {
delete $.fn.editable.defaults.name; delete $.fn.editable.defaults.name;
var e = $('<a href="#" id="cde">abc</a>').appendTo('#qunit-fixture').editable(); var e = $('<a href="#" id="cde">abc</a>').appendTo('#qunit-fixture').editable();
equal(e.data('editable').options.name, 'cde', 'name is taken from id'); equal(e.data('editable').options.name, 'cde', 'name is taken from id');
}); });
asyncTest("'display' callback", function () {
var newText = 'cd<e>;"',
e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo(fx).editable({
display: function(value) {
ok(this === e[0], 'scope is ok');
$(this).text('qq'+value);
}
});
e.click()
var p = tip(e);
ok(p.find('input[type=text]').length, 'input exists')
p.find('input').val(newText);
p.find('form').submit();
setTimeout(function() {
ok(!p.is(':visible'), 'popover was removed');
equal(e.text(), 'qq'+newText, 'custom display ok');
e.remove();
start();
}, timeout);
});
asyncTest("display: false", function () {
var newText = 'cd<e>;"',
e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1" data-value="abc"></a>').appendTo(fx).editable({
display: false
});
ok(!e.text().length, 'element still empty, autotext did not display value');
e.click()
var p = tip(e);
p.find('input').val(newText);
p.find('form').submit();
setTimeout(function() {
ok(!p.is(':visible'), 'popover was removed');
ok(!e.text().length, 'element still empty, new value was not displayed');
equal(e.data('editable').value, newText, 'new text saved to value');
e.remove();
start();
}, timeout);
});
}); });

@ -34,8 +34,8 @@ $(function () {
var e = $('<a href="#" data-pk="1" data-url="post.php">'+v1+'</a>').appendTo(fx).editable({ var e = $('<a href="#" data-pk="1" data-url="post.php">'+v1+'</a>').appendTo(fx).editable({
type: 'textarea', type: 'textarea',
send: 'ifpk', send: 'ifpk',
success: function(data) { success: function(response, newvalue) {
return false; equal(newvalue, v2, 'value in success ok');
} }
}); });
@ -93,7 +93,7 @@ $(function () {
var p = tip(e); var p = tip(e);
p.find('textarea').val(vnew); p.find('textarea').val(vnew);
event = jQuery.Event("keydown"); var event = jQuery.Event("keydown");
event.ctrlKey = true; event.ctrlKey = true;
event.which = 13; event.which = 13;