Merge branch 'release-1.2.0'

This commit is contained in:
vitalets 2012-12-07 00:06:05 +04:00
commit e969ccf436
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
----------------------------
[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 #7] 'shown', 'hidden' events added (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)
@ -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] 'validate' option cannot be defined as object anymore.
[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] value can be stored internaly as object (previously was always string). Usefull for date inupt.
[change] input's option 'template' renamed to 'tpl' (to exclude conflict with container's template).
[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.
[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.
[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] 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**
## 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!
## Contribution
@ -19,7 +19,7 @@ A few steps how to start contributing:
**x-editable**
| -- **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)
| -- **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_ ).
Please replace <code>&lt;your-github-name&gt;</code> with your name:
@ -43,7 +43,7 @@ npm install
cd ..
#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
cd playground
npm install

@ -100,10 +100,19 @@ module.exports = function(grunt) {
//module for testing
var module = '';
//module = '&module=textarea';
// module = '&module=textarea';
//module = '&module=select';
//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
var files = getFiles();
@ -133,8 +142,9 @@ module.exports = function(grunt) {
plain: [
'http://localhost:8000/test/index.html?f=plain&c=popup'+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: {
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: {}
});
//test task
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.
// grunt.registerTask('default', 'lint qunit');

@ -2,7 +2,7 @@
"name": "X-editable",
"title": "X-editable",
"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",
"author": {
"name": "Vitaliy Potapov",

@ -1,5 +1,5 @@
.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 {
@ -10,4 +10,8 @@
.editable-container.editable-inline {
display: inline;
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) {
this.$element = $(element);
//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.initContainer();
@ -75,12 +75,15 @@ Applied as jQuery method.
},
initForm: function() {
this.formOptions.scope = this.$element[0]; //set scope of form callbacks to element
this.$form = $('<div>')
.editableform(this.formOptions)
.on({
save: $.proxy(this.save, this),
cancel: $.proxy(this.cancel, this),
show: $.proxy(this.setPosition, this), //re-position container every time form is shown (after loading state)
cancel: $.proxy(function(){
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
rendered: $.proxy(function(){
/**
@ -119,7 +122,7 @@ Applied as jQuery method.
/**
Shows container with form
@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) {
this.$element.addClass('editable-open');
@ -143,8 +146,9 @@ Applied as jQuery method.
/**
Hides container with form
@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')) {
return;
}
@ -154,9 +158,17 @@ Applied as jQuery method.
Fired when container was hidden. It occurs on both save or cancel.
@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 */
@ -167,7 +179,7 @@ Applied as jQuery method.
/**
Toggles container visibility (show / hide)
@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) {
if(this.tip && this.tip().is(':visible')) {
@ -185,23 +197,8 @@ Applied as jQuery method.
//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) {
if(this.options.autohide) {
this.hide();
}
this.hide('save');
/**
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) {
$('.editable-open').each(function(i, el){
//do nothing with passed element
if(el === element) {
//do nothing with passed element and it's children
if(el === element || $(el).find(element).length) {
return;
}
@ -276,7 +273,7 @@ Applied as jQuery method.
}
if(ec.options.onblur === 'cancel') {
$el.data('editableContainer').hide();
$el.data('editableContainer').hide('onblur');
} else if(ec.options.onblur === 'submit') {
$el.data('editableContainer').tip().find('form').submit();
}
@ -350,7 +347,7 @@ Applied as jQuery method.
**/
placement: 'top',
/**
Wether to hide container on save/cancel.
Whether to hide container on save/cancel.
@property autohide
@type boolean
@ -365,6 +362,7 @@ Applied as jQuery method.
@property onblur
@type string
@default 'cancel'
@since 1.1.1
**/
onblur: 'cancel'
};

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

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

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

@ -16,6 +16,8 @@
*display: inline;
}
.editable-input {
vertical-align: top;
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*/
.editable-buttons button.ui-button {
.editable-buttons button.ui-button-icon-only {
height: 24px;
width: 30px;
}
.editableform-loading {
@ -79,6 +82,10 @@
margin: 0;
}
.editable-checklist label {
white-space: nowrap;
}
.editable-clear {
clear: both;
font-size: 0.9em;

@ -1,7 +1,7 @@
/**
Form with single input element, two buttons and two states: normal/loading.
Applied as jQuery method to DIV tag (not to form tag!)
Editableform is linked with one of input types, e.g. 'text' or 'select'.
Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown.
Editableform is linked with one of input types, e.g. 'text', 'select' etc.
@class editableform
@uses text
@ -9,9 +9,12 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
**/
(function ($) {
var EditableForm = function (element, options) {
var EditableForm = function (div, 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();
};
@ -21,9 +24,9 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
var TypeConstructor, typeOptions;
//create input of specified type
if(typeof $.fn.editableform.types[this.options.type] === 'function') {
TypeConstructor = $.fn.editableform.types[this.options.type];
typeOptions = $.fn.editableform.utils.sliceObj(this.options, $.fn.editableform.utils.objectKeys(TypeConstructor.defaults));
if(typeof $.fn.editabletypes[this.options.type] === 'function') {
TypeConstructor = $.fn.editabletypes[this.options.type];
typeOptions = $.fn.editableutils.sliceObj(this.options, $.fn.editableutils.objectKeys(TypeConstructor.defaults));
this.input = new TypeConstructor(typeOptions);
} else {
$.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() {
this.$loading = $($.fn.editableform.loading);
this.$element.empty().append(this.$loading);
this.$div.empty().append(this.$loading);
this.showLoading();
//init form template and buttons
@ -61,7 +64,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
@event rendering
@param {Object} event event object
**/
this.$element.triggerHandler('rendering');
this.$div.triggerHandler('rendering');
//render input
$.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
this.$element.append(this.$form);
this.$div.append(this.$form);
//attach 'cancel' handler
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) {
this.error(this.input.error);
this.$form.find('.editable-submit').attr('disabled', true);
this.input.$input.attr('disabled', true);
//prevent form from submitting
this.$form.submit(function(e){ e.preventDefault(); });
} else {
this.error(false);
this.input.$input.removeAttr('disabled');
this.$form.find('.editable-submit').removeAttr('disabled');
this.input.value2input(this.value);
//attach submit handler
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
@param {Object} event event object
**/
this.$element.triggerHandler('rendered');
this.$div.triggerHandler('rendered');
this.showForm();
}, this));
@ -114,7 +119,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
@event cancel
@param {Object} event event object
**/
this.$element.triggerHandler('cancel');
this.$div.triggerHandler('cancel');
},
showLoading: function() {
var w;
@ -133,16 +138,18 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
this.$loading.show();
},
showForm: function() {
showForm: function(activate) {
this.$loading.hide();
this.$form.show();
this.input.activate();
if(activate !== false) {
this.input.activate();
}
/**
Fired when form is shown
@event show
@param {Object} event event object
**/
this.$element.triggerHandler('show');
this.$div.triggerHandler('show');
},
error: function(msg) {
@ -163,8 +170,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
e.preventDefault();
var error,
newValue = this.input.input2value(), //get new value from input
newValueStr;
newValue = this.input.input2value(); //get new value from input
//validation
if (error = this.validate(newValue)) {
@ -173,25 +179,29 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
return;
}
//value as string
newValueStr = this.input.value2str(newValue);
//if value not changed --> cancel
/*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*/
this.cancel();
return;
}
//sending data to server
$.when(this.save(newValueStr))
$.when(this.save(newValue))
.done($.proxy(function(response) {
//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(res && typeof res === 'string') {
//if success callback returns false --> keep form open and do not activate input
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.showForm();
return;
@ -218,7 +228,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
if(params.newValue === 'username') {...}
});
**/
this.$element.triggerHandler('save', {newValue: newValue, response: response});
this.$div.triggerHandler('save', {newValue: newValue, response: response});
}, this))
.fail($.proxy(function(xhr) {
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));
},
save: function(value) {
var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this) : this.options.pk,
save: function(newValue) {
//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)))),
params, ajaxOptions;
params;
if (send) { //send to server
this.showLoading();
@ -237,30 +253,29 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
//standard params
params = {
name: this.options.name || '',
value: value,
value: submitValue,
pk: pk
};
//additional params
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 {
//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);
}
if(typeof this.options.url === 'function') { //user's function
return this.options.url.call(this, params);
} else { //send ajax to server and return deferred object
ajaxOptions = $.extend({
return this.options.url.call(this.options.scope, params);
} else {
//send ajax to server and return deferred object
return $.ajax($.extend({
url : this.options.url,
data : params,
type : 'post',
dataType: 'json'
}, this.options.ajaxOptions);
return $.ajax(ajaxOptions);
}, this.options.ajaxOptions));
}
}
},
@ -270,7 +285,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
value = this.value;
}
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,
/**
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
@type string|object|function
@ -418,7 +433,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
validate: null,
/**
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.
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.
@ -440,28 +455,44 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
@property ajaxOptions
@type object
@default null
@since 1.1.1
**/
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'.
@example
ajaxOptions: {
method: 'PUT',
dataType: 'xml'
}
@property showbuttons
@type boolean
@default true
@since 1.1.1
**/
showbuttons: true
/*todo:
Submit strategy. Can be <code>normal|never</code>
<code>submitmode='never'</code> usefull for turning into classic form several inputs and submitting them together manually.
Works pretty with <code>showbuttons=false</code>
showbuttons: true,
/**
Scope for callback methods (success, validate).
If <code>null</code> means editableform instance itself.
@property submitmode
@type string
@default normal
*/
// submitmode: 'normal'
@property scope
@type DOMElement|object
@default null
@since 1.2.0
@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>'+
'<button type="button" class="editable-cancel">cancel</button>';
//error class attahced to control-group
//error class attached to control-group
$.fn.editableform.errorGroupClass = null;
//error class attahced to editable-error-block
//error class attached to editable-error-block
$.fn.editableform.errorBlockClass = 'editable-error';
//input types
$.fn.editableform.types = {};
//utils
$.fn.editableform.utils = {};
}(window.jQuery));

@ -8,7 +8,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
var Editable = function (element, options) {
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();
};
@ -20,9 +20,6 @@ Makes editable any HTML element on the page. Applied as jQuery method.
doAutotext,
finalize;
//initialization flag
this.isInit = true;
//editableContainer must be defined
if(!$.fn.editableContainer) {
$.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');
//create input of specified type. Input will be used for converting value, not in form
if(typeof $.fn.editableform.types[this.options.type] === 'function') {
TypeConstructor = $.fn.editableform.types[this.options.type];
this.typeOptions = $.fn.editableform.utils.sliceObj(this.options, $.fn.editableform.utils.objectKeys(TypeConstructor.defaults));
if(typeof $.fn.editabletypes[this.options.type] === 'function') {
TypeConstructor = $.fn.editabletypes[this.options.type];
this.typeOptions = $.fn.editableutils.sliceObj(this.options, $.fn.editableutils.objectKeys(TypeConstructor.defaults));
this.input = new TypeConstructor(this.typeOptions);
} else {
$.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()));
isValueByText = true;
} 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') {
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');
//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
doAutotext = !isValueByText && this.value !== null && this.value !== undefined;
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) {
this.disable();
} else {
this.enable();
}
/**
Fired each time when element's text is rendered. Occurs on initialization and on each update of value.
Can be used for display customization.
Fired when element was initialized by editable method.
@event render
@event init
@param {Object} event event object
@param {Object} editable editable instance
@example
$('#action').on('render', function(e, editable) {
var colors = {0: "gray", 1: "green", 2: "blue", 3: "red"};
$(this).css("color", colors[editable.value]);
});
@since 1.2.0
**/
this.$element.triggerHandler('render', this);
this.isInit = false;
this.$element.triggerHandler('init', 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
@ -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)
*/
handleEmpty: function () {
//do not handle empty if we do not display anything
if(this.options.display === false) {
return;
}
var emptyClass = 'editable-empty';
//emptytext shown only for enabled
if(!this.options.disabled) {
@ -211,7 +237,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
/**
Shows container with form
@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) {
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..
if(!this.container) {
var containerOptions = $.extend({}, this.options, {
value: this.value,
autohide: false //element will take care to show/hide container
value: this.value
});
this.$element.editableContainer(containerOptions);
this.$element.on({
save: $.proxy(this.save, this),
cancel: $.proxy(this.hide, this)
save: $.proxy(this.save, this)
});
this.container = this.$element.data('editableContainer');
} 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) {
this.container.hide();
}
//return focus on element
if (this.options.enablefocus && this.options.toggle === 'click') {
this.$element.focus();
}
},
/**
Toggles container visibility (show / hide)
@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) {
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) {
//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');
} else {
this.$element.removeClass('editable-unsaved');
}
this.hide();
// this.hide();
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
@method setValue(value, convertStr)
@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) {
if(convertStr) {
@ -323,10 +342,9 @@ Makes editable any HTML element on the page. Applied as jQuery method.
if(this.container) {
this.container.option('value', this.value);
}
$.when(this.input.value2html(this.value, this.$element))
$.when(this.render())
.then($.proxy(function() {
this.handleEmpty();
this.$element.triggerHandler('render', this);
}, this));
},
@ -338,7 +356,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
if(this.container) {
this.container.activate();
}
}
}
};
/* EDITABLE PLUGIN DEFINITION
@ -369,7 +387,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
$('#username, #fullname').editable('validate');
// possible result:
{
username: "username is requied",
username: "username is required",
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 () {
var $this = $(this), data = $this.data(datakey);
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;
@ -411,6 +429,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
@param {object} options
@param {object} options.url url to submit data
@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.success(obj) success handler
@returns {Object} jQuery object
@ -421,21 +440,20 @@ Makes editable any HTML element on the page. Applied as jQuery method.
errors = this.editable('validate'),
values;
if(typeof config.error !== 'function') {
config.error = function() {};
}
if($.isEmptyObject(errors)) {
values = this.editable('getValue');
if(config.data) {
$.extend(values, config.data);
}
$.ajax({
type: 'POST',
}
$.ajax($.extend({
url: config.url,
data: values,
type: 'POST',
dataType: 'json'
}).success(function(response) {
}, config.ajaxOptions))
.success(function(response) {
//successful response
if(typeof response === 'object' && response.id) {
$elems.editable('option', 'pk', response.id);
$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);
}
} 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);
}
}).error(function(){ //ajax error
config.error.apply($elems, arguments);
});
} else { //client-side validation error
config.error.call($elems, {errors: errors});
if(typeof config.error === 'function') {
config.error.call($elems, {errors: errors});
}
}
return this;
}
@ -505,7 +530,6 @@ Makes editable any HTML element on the page. Applied as jQuery method.
@default 'click'
**/
toggle: 'click',
/**
Text shown when element is empty.
@ -515,7 +539,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
**/
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>.
<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.
@ -526,22 +550,31 @@ Makes editable any HTML element on the page. Applied as jQuery method.
**/
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.
@property value
@type mixed
@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));

@ -26,19 +26,21 @@ $(function(){
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, {
render: function() {
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) {
var html = value.city + ', ' + value.street + ' st., bld. ' + value.building;
$(element).text(html);
if(!value) {
$(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) {
@ -59,11 +61,17 @@ $(function(){
},
/*
method for converting data before sent on server.
As jQuery correctly sends objects via ajax, you can just return value
converts value to string.
It is used in internal comparing (not for sending to server).
*/
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, {
tpl: '<div><label><span>City: </span><input type="text" name="city" class="span2"></label></div>'+
'<div><label><span>Street: </span><input type="text" name="street" class="span2"></label></div>'+
'<div><label><span>Building: </span><input type="text" name="building" class="span1"></label></div>',
Address.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
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="input-small"></label></div>'+
'<div><label><span>Building: </span><input type="text" name="building" class="input-mini"></label></div>',
inputclass: 'editable-address'
});
$.fn.editableform.types.address = Address;
$.fn.editabletypes.address = Address;
}(window.jQuery));

@ -6,11 +6,14 @@ To create your own input you should inherit from this class.
**/
(function ($) {
//types
$.fn.editabletypes = {};
var Abstract = function () { };
Abstract.prototype = {
/**
Iinitializes input
Initializes input
@method init()
**/
@ -46,8 +49,7 @@ To create your own input you should inherit from this class.
@param {DOMElement} element
**/
value2html: function(value, element) {
var html = this.escape(value);
$(element).html(html);
$(element).text(value);
},
/**
@ -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)
@param {mixed} value
@ -83,6 +85,17 @@ To create your own input you should inherit from this class.
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.
@ -114,11 +127,11 @@ To create your own input you should inherit from this class.
},
/**
Creares input.
Creates input.
@method clear()
**/
clear: function() {
clear: function() {
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() {
@ -148,12 +161,12 @@ To create your own input you should inherit from this class.
tpl: '',
/**
CSS class automatically applied to input
@property inputclass
@type string
@default span2
@default input-medium
**/
inputclass: 'span2',
inputclass: 'input-medium',
/**
Name attribute of input
@ -164,6 +177,6 @@ To create your own input you should inherit from this class.
name: null
};
$.extend($.fn.editableform.types, {abstract: Abstract});
$.extend($.fn.editabletypes, {abstract: Abstract});
}(window.jQuery));

@ -27,7 +27,7 @@ $(function(){
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, {
renderList: function() {
@ -49,10 +49,8 @@ $(function(){
},
value2str: function(value) {
return $.isArray(value) ? value.join($.trim(this.options.separator)) : '';
//it is also possible to sent as array
//return value;
},
return $.isArray(value) ? value.sort().join($.trim(this.options.separator)) : '';
},
//parse separated string
str2value: function(str) {
@ -95,19 +93,18 @@ $(function(){
//collect text of checked boxes
value2htmlFinal: function(value, element) {
var selected = [], item, i, html = '';
if($.isArray(value) && value.length <= this.options.limit) {
for(i=0; i<value.length; i++){
item = this.itemByVal(value[i]);
if(item) {
selected.push($('<div>').text(item.text).html());
}
}
html = selected.join(this.options.viewseparator);
} else {
html = this.options.limitText.replace('{checked}', $.isArray(value) ? value.length : 0).replace('{count}', this.sourceData.length);
var html = [],
/*jslint eqeq: true*/
checked = $.grep(this.sourceData, function(o){
return $.grep(value, function(v){ return v == o.value; }).length;
});
/*jslint eqeq: false*/
if(checked.length) {
$.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
$(element).html(html.join('<br>'));
} else {
$(element).empty();
}
$(element).html(html);
},
activate: function() {
@ -123,7 +120,7 @@ $(function(){
}
});
Checklist.defaults = $.extend({}, $.fn.editableform.types.list.defaults, {
Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
/**
@property tpl
@default <div></div>
@ -133,46 +130,20 @@ $(function(){
/**
@property inputclass
@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
@type string
@default ', '
**/
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}'
separator: ','
});
$.fn.editableform.types.checklist = Checklist;
$.fn.editabletypes.checklist = Checklist;
}(window.jQuery));

@ -292,7 +292,7 @@
.text(dates[this.language].months[month]+' '+year);
this.picker.find('tfoot th.today')
.text(dates[this.language].today)
.toggle(this.todayBtn);
.toggle(this.todayBtn !== false);
this.updateNavArrows();
this.fillMonths();
var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
@ -429,10 +429,7 @@
break;
case 'today':
var date = new Date();
date.setUTCHours(0);
date.setUTCMinutes(0);
date.setUTCSeconds(0);
date.setUTCMilliseconds(0);
date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
this.showMode(-2);
var which = this.todayBtn == 'linked' ? null : 'view';
@ -655,7 +652,17 @@
if (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();
}
};
@ -773,7 +780,7 @@
val, filtered, part;
setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
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) {
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
val = parseInt(parts[i], 10);

@ -27,7 +27,7 @@ $(function(){
this.init('date', options, Date.defaults);
//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)
this.options.datepicker = $.extend({}, Date.defaults.datepicker, directOptions, options.datepicker);
@ -48,7 +48,7 @@ $(function(){
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, {
render: function () {
@ -79,7 +79,11 @@ $(function(){
str2value: function(str) {
return str ? this.dpg.parseDate(str, this.parsedFormat, this.options.datepicker.language) : null;
},
},
value2submit: function(value) {
return this.value2str(value);
},
value2input: function(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
@default <div></div>
@ -165,6 +169,6 @@ $(function(){
clear: '&times; clear'
});
$.fn.editableform.types.date = Date;
$.fn.editabletypes.date = Date;
}(window.jQuery));

@ -27,7 +27,7 @@ $(function(){
this.init('dateui', options, DateUI.defaults);
//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)
this.options.datepicker = $.extend({}, DateUI.defaults.datepicker, directOptions, options.datepicker);
@ -46,7 +46,7 @@ $(function(){
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, {
render: function () {
@ -97,7 +97,11 @@ $(function(){
} catch(e) {}
return d;
},
},
value2submit: function(value) {
return this.value2str(value);
},
value2input: function(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
@default <div></div>
@ -183,7 +187,7 @@ $(function(){
clear: '&times; clear'
});
$.fn.editableform.types.dateui = DateUI;
$.fn.editableform.types.date = DateUI;
$.fn.editabletypes.dateui = DateUI;
$.fn.editabletypes.date = DateUI;
}(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, {
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
},
value2html: function (value, element) {
value2html: function (value, element, display) {
var deferred = $.Deferred();
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();
}, function () {
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 {
this.options.source = $.fn.editableform.utils.tryParseJson(this.options.source, false);
this.options.source = $.fn.editableutils.tryParseJson(this.options.source, false);
} catch (e) {
error.call(this);
return;
@ -66,32 +71,35 @@ List - abstract class for inputs that have source option loaded from js array or
//loading from url
if (typeof this.options.source === 'string') {
var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''),
cache;
//try to get from cache
if(this.options.sourceCache) {
var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''),
cache;
if (!$(document).data(cacheID)) {
$(document).data(cacheID, {});
}
cache = $(document).data(cacheID);
if (!$(document).data(cacheID)) {
$(document).data(cacheID, {});
}
cache = $(document).data(cacheID);
//check for cached data
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 () {
//check for cached data
if (cache.loading === false && cache.sourceData) { //take source from cache
this.sourceData = cache.sourceData;
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
cache.err_callbacks.push($.proxy(error, this));
return;
} else { //no cache yet, activate it
cache.loading = true;
cache.callbacks = [];
cache.err_callbacks = [];
//also collecting error callbacks
cache.err_callbacks.push($.proxy(error, this));
return;
} else { //no cache yet, activate it
cache.loading = true;
cache.callbacks = [];
cache.err_callbacks = [];
}
}
//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} : {},
dataType: 'json',
success: $.proxy(function (data) {
cache.loading = false;
if(cache) {
cache.loading = false;
}
this.sourceData = this.makeArray(data);
if($.isArray(this.sourceData)) {
this.doPrepend();
//store result in cache
cache.sourceData = this.sourceData;
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 {
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),
error: $.proxy(function () {
cache.loading = false;
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)
});
} 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)) {
//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') {
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.
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.
If source is **string**, results will be cached for fields with the same source and name. See also <code>sourceCache</code> option.
@property source
@type string|array|object
@default null
**/
source:null,
/**
Data automatically prepended to the begining of dropdown list.
Data automatically prepended to the beginning of dropdown list.
@property prepend
@type string|array|object
@ -246,9 +264,19 @@ List - abstract class for inputs that have source option loaded from js array or
@type string
@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));

@ -26,7 +26,7 @@ $(function(){
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, {
renderList: function() {
@ -37,6 +37,13 @@ $(function(){
for(var i=0; i<this.sourceData.length; i++) {
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) {
@ -48,13 +55,13 @@ $(function(){
},
autosubmit: function() {
this.$input.on('change', function(){
this.$input.off('keydown.editable').on('change.editable', function(){
$(this).closest('form').submit();
});
}
});
Select.defaults = $.extend({}, $.fn.editableform.types.list.defaults, {
Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
/**
@property tpl
@default <select></select>
@ -62,6 +69,6 @@ $(function(){
tpl:'<select></select>'
});
$.fn.editableform.types.select = Select;
$.fn.editabletypes.select = Select;
}(window.jQuery));

@ -20,18 +20,18 @@ $(function(){
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, {
activate: function() {
if(this.$input.is(':visible')) {
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
@default <input type="text">
@ -47,6 +47,6 @@ $(function(){
placeholder: null
});
$.fn.editableform.types.text = Text;
$.fn.editabletypes.text = Text;
}(window.jQuery));

@ -21,7 +21,7 @@ $(function(){
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, {
render: function () {
@ -60,13 +60,13 @@ $(function(){
activate: function() {
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();
}
}
});
Textarea.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
Textarea.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
/**
@property tpl
@default <textarea></textarea>
@ -74,9 +74,9 @@ $(function(){
tpl:'<textarea></textarea>',
/**
@property inputclass
@default span3
@default input-large
**/
inputclass:'span3',
inputclass: 'input-large',
/**
Placeholder attribute of input. Shown when input is empty.
@ -87,6 +87,6 @@ $(function(){
placeholder: null
});
$.fn.editableform.types.textarea = Textarea;
$.fn.editabletypes.textarea = Textarea;
}(window.jQuery));
}(window.jQuery));

@ -3,55 +3,25 @@
<head>
<meta charset="utf-8">
<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>
var fc = getFC(),
assets = getAssets(fc.f, fc.c, '../src/', 'libs/');
loadAssets(assets.css, assets.js);
var fx, sfx;
$(function () {
$.fx.off = true;
$.support.transition = false;
});
</script>
window.onload = function() {
QUnit.config.autostart = false;
};
</script>
<!-- <script data-main="main.js" src="require-jquery.js"></script>-->
<script data-main="main.js" src="require.js"></script>
<!-- qunit (should be included here, otherwise phantomjs hangs-up on several tests )-->
<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>
<body>
<div>
<div>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<div id="async-fixture"></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>
</html>

@ -1,128 +1,170 @@
/**
* load requred js and css according to f (form) and c (container) parameters
*/
define(function () {
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() {
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 { //bootstrap
f = 'bootstrap';
//bootstrap
'bootstrap/js/bootstrap': {
deps: ['require'],
init: function(require) {
loadCss(require.toUrl("../css/bootstrap.css"));
}
},
'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) {
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') ;
});
/*
//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 () {
test("'init' event", function () {
expect(1);
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();
});
*/
asyncTest("'render' event for text", function () {
expect(4);
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',
});
asyncTest("events: shown / hidden (reason: cancel, onblur, manual)", function () {
expect(11);
var val = '1', test_reason,
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) {
@ -188,14 +89,8 @@ $(function () {
equal(editable.value, val, 'shown triggered, value correct');
});
e.on('cancel', function(event) {
var editable = $(this).data('editable');
ok(true, 'cancel triggered');
});
e.on('hidden', function(event) {
var editable = $(this).data('editable');
ok(true, 'hidden triggered');
e.on('hidden', function(event, reason) {
ok((reason === test_reason) || (test_reason === 'manual' && reason === undefined), 'hidden triggered, reason ok');
});
e.editable({
@ -206,41 +101,54 @@ $(function () {
setTimeout(function() {
var p = tip(e);
p.find('button[type=button]').click();
setTimeout(function() {
e.remove();
start();
}, timeout);
test_reason = 'cancel'
p.find('button[type=button]').click(); //cancel
ok(!p.is(':visible'), 'popover closed');
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);
});
asyncTest("event: save / hidden", function () {
asyncTest("event: save / hidden (reason: save)", function () {
expect(2);
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.on('save', function(event, params) {
var editable = $(this).data('editable');
equal(params.newValue, 2, 'save triggered, value correct');
});
e.on('hidden', function(event) {
var editable = $(this).data('editable');
ok(true, 'hidden triggered');
e.on('hidden', function(event, reason) {
equal(reason, 'save', 'hidden triggered, reason ok');
});
e.editable({
source: 'groups.php',
source: groups,
});
e.click();
var p = tip(e);
p.find('select').val(2);
p.find('form').submit();
setTimeout(function() {
p.find('button[type=button]').click();
e.remove();
start();
}, timeout);
@ -305,7 +213,6 @@ $(function () {
});
asyncTest("'submit' method: client and server validation", function () {
expect(6);
var ev1 = 'ev1',
ev2 = 'ev2',
e1v = 'e1v',
@ -322,6 +229,7 @@ $(function () {
equal(settings.data.text, ev2, 'first value ok');
equal(settings.data.text1, e1v, 'second value ok');
equal(settings.data.a, 123, 'custom data ok');
equal(settings.type, 'PUT', 'ajaxOptions ok');
this.responseText = {errors: {
text1: 'server-invalid'
}
@ -348,11 +256,13 @@ $(function () {
data: {a: 123},
error: function(data) {
equal(data.errors.text1, 'server-invalid', 'server validation error ok');
e.remove();
e1.remove();
start();
}
},
ajaxOptions: {
type: 'PUT'
}
});
});
@ -431,4 +341,4 @@ $(function () {
equal(e.text(), groups[2], 'new text shown correctly');
});
});
});

@ -4,21 +4,28 @@ $(function () {
setup: function(){
sfx = $('#qunit-fixture'),
fx = $('#async-fixture');
$.support.transition = false;
}
});
asyncTest("should load options, set correct value and save new value", function () {
var sep = '-',
var sep = '<br>',
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,
source: groupsArr,
value: [2, 3],
viewseparator: sep
value: [2, 3]
});
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();
var p = tip(e);
@ -28,8 +35,8 @@ $(function () {
equal(p.find('input[type="checkbox"]:checked').eq(1).val(), 3, '2nd checked');
//set new value
p.find('input[type="checkbox"]:checked').eq(0).click();
p.find('input[type="checkbox"]').first().click();
p.find('input[type="checkbox"]:checked').eq(0).click(); //uncheck 2
p.find('input[type="checkbox"]').first().click(); //check first
newValue = p.find('input[type="checkbox"]').first().val();
//submit
@ -39,7 +46,7 @@ $(function () {
ok(!p.is(':visible'), 'popup closed');
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
e.click()
@ -54,40 +61,5 @@ $(function () {
start();
}, 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)');
});
test("should take container's title from json options", function () {
test("container's title and placement from json options", function () {
//do not test inline
if(fc.c === 'inline') {
if($.fn.editableContainer.Constructor.prototype.containerName === 'editableform') {
expect(0);
return;
}
@ -81,7 +81,7 @@
ok(p.is(':visible'), 'popover shown');
//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');
}
@ -362,36 +362,88 @@
ok(!p.find('.editable-buttons').length, '.editable-buttons block not rendered');
});
//unfortunatly, testing this feature does not always work in browsers. Tested manually.
/*
test("enablefocus option", function () {
// focusing not passed in phantomjs
if($.browser.webkit) {
ok(true, 'skipped in PhantomJS');
return;
}
var e = $('<a href="#">abc</a>').appendTo('#qunit-fixture').editable({
enablefocus: true
}),
e1 = $('<a href="#">abcd</a>').appendTo('#qunit-fixture').editable({
enablefocus: false
});
e.click()
var p = tip(e);
ok(p.is(':visible'), 'popover 1 visible');
p.find('button[type=button]').click();
ok(!p.is(':visible'), 'popover closed');
ok(e.is(':focus'), 'element 1 is focused');
e1.click()
p = tip(e1);
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("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({
name: 'username'
}),
newText = 'cd<e>;"'
$.mockjax({
url: 'post-pk.php',
response: function(settings) {
equal(settings.data.pk.a, 1, 'first part ok');
equal(settings.data.pk.b, 2, 'second part ok');
}
});
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() {
e.remove();
start();
}, timeout);
});
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));

@ -14,9 +14,7 @@ $(function () {
}
asyncTest("container should contain datepicker with value and save new entered date", function () {
expect(9);
$.fn.editableform.types.date.defaults.datepicker.weekStart = 1;
$.fn.editabletypes.date.defaults.datepicker.weekStart = 1;
var d = '15.05.1984',
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();
var p = tip(e);
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');
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('th.dow').eq(0).text(), 'Mo', 'weekStart correct');
@ -60,7 +60,7 @@ $(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',
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(),
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(),
errText = $.fn.editableform.types.select.defaults.sourceError;
errText = $.fn.editabletypes.select.defaults.sourceError;
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 () {
expect(3);
@ -474,6 +523,60 @@ $(function () {
e.remove();
start();
}, 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 () {
var v = 'ab<b>"',
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({
success: function(data) {
return false;
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(response, newValue) {
equal(newValue, newText, 'new value in success correct');
}
}),
data,
newText = 'cd<e>;"';
newText = 'cd&gt;e>;"';
$.mockjax({
url: 'post-text.php',
url: 'post-text-main.php',
response: function(settings) {
data = settings.data;
}
@ -61,13 +61,13 @@ $(function () {
e.click()
var p = tip(e);
ok(p.is(':visible'), 'popover visible')
ok(p.find('.editableform-loading').length, 'loading class exists')
ok(!p.find('.editableform-loading').is(':visible'), 'loading class is hidden')
ok(p.find('input[type=text]').length, 'input exists')
equal(p.find('input[type=text]').val(), v, 'input contain correct value')
ok(p.is(':visible'), 'popover visible');
ok(p.find('.editableform-loading').length, 'loading class exists');
ok(!p.find('.editableform-loading').is(':visible'), 'loading class is hidden');
ok(p.find('input[type=text]').length, 'input exists');
equal(p.find('input[type=text]').val(), v, 'input contain correct value');
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');
setTimeout(function() {
@ -88,7 +88,10 @@ $(function () {
asyncTest("should show error on server validation", function () {
var msg = 'required',
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 = '';
@ -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 () {
var newText = 'cd<e>;"',
e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo(fx).editable({
success: function(response, newValue) {
ok(this === e[0], 'scope is ok');
equal(newValue, newText, 'value in success passed correctly');
return 'error';
}
@ -266,6 +246,7 @@ $(function () {
var e = $('<a href="#" data-pk="1" data-url="post-resp.php">abc</a>').appendTo(fx).editable({
name: 'username',
params: function(params) {
ok(this === e[0], 'scope is ok');
equal(params.pk, 1, 'params in func already have values (pk)');
return { q: 2, pk: 3 };
},
@ -328,11 +309,12 @@ $(function () {
asyncTest("submit to url defined as function", function () {
expect(3);
expect(4);
var newText = 'qwe',
//should be called even without pk!
e = $('<a href="#" data-pk1="1" id="a"></a>').appendTo(fx).editable({
url: function(params) {
ok(this === e[0], 'scope is ok');
ok(params.value, newText, 'new text passed in users function');
var d = new $.Deferred;
return d.reject('my error');
@ -472,6 +454,55 @@ $(function () {
delete $.fn.editable.defaults.name;
var e = $('<a href="#" id="cde">abc</a>').appendTo('#qunit-fixture').editable();
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({
type: 'textarea',
send: 'ifpk',
success: function(data) {
return false;
success: function(response, newvalue) {
equal(newvalue, v2, 'value in success ok');
}
});
@ -93,7 +93,7 @@ $(function () {
var p = tip(e);
p.find('textarea').val(vnew);
event = jQuery.Event("keydown");
var event = jQuery.Event("keydown");
event.ctrlKey = true;
event.which = 13;