Merge branch 'release-1.3.0'

This commit is contained in:
vitalets 2012-12-10 22:50:47 +04:00
commit 69a4ad5393
36 changed files with 9898 additions and 218 deletions

@ -2,6 +2,21 @@ X-editable changelog
=============================
Version 1.3.0 Dec 10, 2012
----------------------------
[enh] added html5 inputs support: password, email, url, tel, number, range (vitalets)
[bug #43] fix for bootstrap 2.2.2 (vitalets)
[enh #41] 'abstract' class renamed to 'abstractinput' as abstract is reserved word (vitalets)
[enh #40] 'params' option defined as function overwrites original ajax data instead of appending (vitalets)
[bug] datepicker: error when click on arrows after clear date (vitalets)
[enh] 'hidden' event: added possible value of reason param - 'nochange'. Occurs when form is submitted but value was not changed (vitalets)
[enh] 'submit' method changed: error-callback's parameter simplified (vitalets)
[enh] 'submit' method changed: now when response 200 OK it does not set pk automatically (vitalets)
[enh] 'submit' method changed: removed dataType='json'. Use 'ajaxOptions' to specify dataType if needed (vitalets)
[enh] removed default ajax dataType='json'. Use 'ajaxOptions' to specify dataType if needed (vitalets)
[enh] select: do not show 'sourceError' in element during autotext execution (vitalets)
Version 1.2.0 Dec 6, 2012
----------------------------
[enh #36] 'submit' method: added 'ajaxOptions' property to modify ajax request (vitalets)

@ -42,7 +42,8 @@ function getFiles() {
inputs+'text.js',
inputs+'textarea.js',
inputs+'select.js',
inputs+'checklist.js'
inputs+'checklist.js',
inputs+'html5types.js'
];
//common css files

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

@ -8,7 +8,8 @@
}
.editable-container.editable-inline {
display: inline;
/* display: inline; */ /* display: inline does not correctly work with show()/hide() in jquery <= 1.7.2 */
display: inline-block;
vertical-align: middle;
}

@ -80,9 +80,8 @@ Applied as jQuery method.
.editableform(this.formOptions)
.on({
save: $.proxy(this.save, this),
cancel: $.proxy(function(){
this.hide('cancel');
}, this),
cancel: $.proxy(function(){ this.hide('cancel'); }, this),
nochange: $.proxy(function(){ this.hide('nochange'); }, 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(){
@ -146,7 +145,7 @@ 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>
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
**/
hide: function(reason) {
if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
@ -159,7 +158,7 @@ Applied as jQuery method.
@event hidden
@param {object} event event object
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|undefined (=manual)</code>
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
@example
$('#username').on('hidden', function(e, reason) {
if(reason === 'save' || reason === 'cancel') {

@ -8,7 +8,8 @@
//extend methods
$.extend($.fn.editableContainer.Constructor.prototype, {
containerName: 'popover',
innerCss: '.popover-content p',
//for compatibility with bootstrap <= 2.2.1 (content inserted into <p> instead of directly .popover-content)
innerCss: $($.fn.popover.defaults.template).find('p').length ? '.popover-content p' : '.popover-content',
initContainer: function(){
$.extend(this.containerOptions, {

@ -53,6 +53,7 @@
max-width: 300px;
margin: 5px 0 0 0;
width: auto;
white-space: normal;
}
/*add padding for jquery ui*/

@ -179,11 +179,16 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
return;
}
//if value not changed --> cancel
//if value not changed --> trigger 'nochange' event and return
/*jslint eqeq: true*/
if (!this.options.savenochange && this.input.value2str(newValue) == this.input.value2str(this.value)) {
/*jslint eqeq: false*/
this.cancel();
/**
Fired when value not changed but form is submitted. Requires savenochange = false.
@event nochange
@param {Object} event event object
**/
this.$div.triggerHandler('nochange');
return;
}
@ -259,7 +264,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
//additional params
if(typeof this.options.params === 'function') {
$.extend(params, this.options.params.call(this.options.scope, params));
params = this.options.params.call(this.options.scope, params);
} else {
//try parse json in single quotes (from data-params attribute)
this.options.params = $.fn.editableutils.tryParseJson(this.options.params, true);
@ -273,8 +278,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
return $.ajax($.extend({
url : this.options.url,
data : params,
type : 'post',
dataType: 'json'
type : 'POST'
}, this.options.ajaxOptions));
}
}
@ -371,10 +375,13 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
**/
url:null,
/**
Additional params for submit. Function can be used to calculate params dynamically
Additional params for submit. If defined as <code>object</code> - it is **appended** to original ajax data (pk, name and value).
If defined as <code>function</code> - returned object **overwrites** original ajax data.
@example
params: function(params) {
return { a: 1 };
//originally params contain pk, name and value
params.a = 1;
return params;
}
@property params
@ -447,7 +454,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
if(!response.success) return response.msg;
}
**/
success: function(response, newValue) {},
success: null,
/**
Additional options for ajax request.
List of values: http://api.jquery.com/jQuery.ajax

@ -250,9 +250,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
value: this.value
});
this.$element.editableContainer(containerOptions);
this.$element.on({
save: $.proxy(this.save, this)
});
this.$element.on("save.internal", $.proxy(this.save, this));
this.container = this.$element.data('editableContainer');
} else if(this.container.tip().is(':visible')) {
return;
@ -422,16 +420,17 @@ Makes editable any HTML element on the page. Applied as jQuery method.
return result;
/**
This method collects values from several editable elements and submit them all to server.
It is designed mainly for <a href="#newrecord">creating new records</a>.
This method collects values from several editable elements and submit them all to server.
Internally it runs client-side validation for all fields and submits only in case of success.
See <a href="#newrecord">creating new records</a> for details.
@method submit(options)
@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
@param {function} options.error(obj) error handler
@param {function} options.success(obj,config) success handler
@returns {Object} jQuery object
**/
case 'submit': //collects value, validate and submit to server for creating new record
@ -449,22 +448,13 @@ Makes editable any HTML element on the page. Applied as jQuery method.
$.ajax($.extend({
url: config.url,
data: values,
type: 'POST',
dataType: 'json'
type: 'POST'
}, config.ajaxOptions))
.success(function(response) {
//successful response
if(typeof response === 'object' && response.id) {
$elems.editable('option', 'pk', response.id);
$elems.removeClass('editable-unsaved');
if(typeof config.success === 'function') {
config.success.apply($elems, arguments);
}
} else { //server-side validation error
if(typeof config.error === 'function') {
config.error.apply($elems, arguments);
}
}
//successful response 200 OK
if(typeof config.success === 'function') {
config.success.call($elems, response, config);
}
})
.error(function(){ //ajax error
if(typeof config.error === 'function') {
@ -473,7 +463,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
});
} else { //client-side validation error
if(typeof config.error === 'function') {
config.error.call($elems, {errors: errors});
config.error.call($elems, errors);
}
}
return this;
@ -577,4 +567,4 @@ Makes editable any HTML element on the page. Applied as jQuery method.
display: null
};
}(window.jQuery));
}(window.jQuery));

@ -3,7 +3,7 @@ Address editable input.
Internally value stored as {city: "Moscow", street: "Lenina", building: "15"}
@class address
@extends abstract
@extends abstractinput
@final
@example
<a href="#" id="address" data-type="address" data-pk="1">awesome</a>
@ -26,14 +26,24 @@ $(function(){
this.init('address', options, Address.defaults);
};
$.fn.editableutils.inherit(Address, $.fn.editabletypes.abstract);
//inherit from Abstract input
$.fn.editableutils.inherit(Address, $.fn.editabletypes.abstractinput);
$.extend(Address.prototype, {
render: function() {
Address.superclass.render.call(this);
},
/**
Renders input from tpl
@method render()
**/
render: function() {
Address.superclass.render.call(this);
},
//standard way to show value in element. Used only if display option not defined.
/**
Default method to show value in element. Can be overwritten by display option.
@method value2html(value, element)
**/
value2html: function(value, element) {
if(!value) {
$(element).empty();
@ -43,12 +53,17 @@ $(function(){
$(element).html(html);
},
/**
Gets value from element's html
@method html2value(html)
**/
html2value: function(html) {
/*
you may write parsing method to get value by element's html
e.g. "Moscow, st. Lenina, bld. 15" => {city: "Moscow", street: "Lenina", building: "15"}
but for complex structures I do not recommend do that.
Better always set value directly via javascript, e.g.
but for complex structures it's not recommended.
Better set value directly via javascript, e.g.
editable({
value: {
city: "Moscow",
@ -60,10 +75,12 @@ $(function(){
return null;
},
/*
converts value to string.
/**
Converts value to string.
It is used in internal comparing (not for sending to server).
*/
@method value2str(value)
**/
value2str: function(value) {
var str = '';
if(value) {
@ -75,19 +92,35 @@ $(function(){
},
/*
this is mainly for parsing value defined in data-value attribute.
If you will always set value by javascript, no need to overwrite it
Converts string to value. Used for reading value from 'data-value' attribute.
@method str2value(str)
*/
str2value: function(str) {
/*
this is mainly for parsing value defined in data-value attribute.
If you will always set value by javascript, no need to overwrite it
*/
return str;
},
/**
Sets value of input.
@method value2input(value)
@param {mixed} value
**/
value2input: function(value) {
this.$input.find('input[name="city"]').val(value.city);
this.$input.find('input[name="street"]').val(value.street);
this.$input.find('input[name="building"]').val(value.building);
},
/**
Returns value of input.
@method input2value()
**/
input2value: function() {
return {
city: this.$input.find('input[name="city"]').val(),
@ -95,14 +128,31 @@ $(function(){
building: this.$input.find('input[name="building"]').val()
};
},
/**
Activates input: sets focus on the first field.
@method activate()
**/
activate: function() {
//set focus on city
this.$input.find('input[name="city"]').focus();
}
},
/**
Attaches handler to submit form in case of 'showbuttons=false' mode
@method autosubmit()
**/
autosubmit: function() {
this.$input.find('input[type="text"]').keydown(function (e) {
if (e.which === 13) {
$(this).closest('form').submit();
}
});
}
});
Address.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
Address.defaults = $.extend({}, $.fn.editabletypes.abstractinput.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>',

@ -1,17 +1,18 @@
/**
Abstract editable input class.
To create your own input you should inherit from this class.
AbstractInput - base class for all editable inputs.
It defines interface to be implemented by any input type.
To create your own input you can inherit from this class.
@class abstract
@class abstractinput
**/
(function ($) {
//types
$.fn.editabletypes = {};
var Abstract = function () { };
var AbstractInput = function () { };
Abstract.prototype = {
AbstractInput.prototype = {
/**
Initializes input
@ -26,7 +27,7 @@ To create your own input you should inherit from this class.
},
/**
Renders input. Can return jQuery deferred object.
Renders input from tpl. Can return jQuery deferred object.
@method render()
**/
@ -64,7 +65,7 @@ To create your own input you should inherit from this class.
},
/**
Converts value to string (for comparering)
Converts value to string (for internal compare). For submitting to server used value2submit().
@method value2str(value)
@param {mixed} value
@ -150,7 +151,7 @@ To create your own input you should inherit from this class.
}
};
Abstract.defaults = {
AbstractInput.defaults = {
/**
HTML template of input. Normally you should not change it.
@ -177,6 +178,6 @@ To create your own input you should inherit from this class.
name: null
};
$.extend($.fn.editabletypes, {abstract: Abstract});
$.extend($.fn.editabletypes, {abstractinput: AbstractInput});
}(window.jQuery));
}(window.jQuery));

@ -286,7 +286,7 @@
startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
currentDate = this.date.valueOf(),
currentDate = this.date && this.date.valueOf(),
today = new Date();
this.picker.find('.datepicker-days thead th:eq(1)')
.text(dates[this.language].months[month]+' '+year);
@ -321,7 +321,7 @@
prevMonth.getUTCDate() == today.getDate()) {
clsName += ' today';
}
if (prevMonth.valueOf() == currentDate) {
if (currentDate && prevMonth.valueOf() == currentDate) {
clsName += ' active';
}
if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate) {
@ -334,14 +334,14 @@
prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
}
this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
var currentYear = this.date.getUTCFullYear();
var currentYear = this.date && this.date.getUTCFullYear();
var months = this.picker.find('.datepicker-months')
.find('th:eq(1)')
.text(year)
.end()
.find('span').removeClass('active');
if (currentYear == year) {
if (currentYear && currentYear == year) {
months.eq(this.date.getUTCMonth()).addClass('active');
}
if (year < startYear || year > endYear) {

@ -4,7 +4,7 @@ Description and examples: http://vitalets.github.com/bootstrap-datepicker.
For localization you can include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
@class date
@extends abstract
@extends abstractinput
@final
@example
<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-original-title="Select date">15/05/1984</a>
@ -48,7 +48,7 @@ $(function(){
this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
};
$.fn.editableutils.inherit(Date, $.fn.editabletypes.abstract);
$.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
$.extend(Date.prototype, {
render: function () {
@ -112,7 +112,7 @@ $(function(){
});
Date.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
/**
@property tpl
@default <div></div>

@ -4,7 +4,7 @@ Description and examples: http://jqueryui.com/datepicker.
This input is also accessible as **date** type. Do not use it together with __bootstrap-datepicker__ as both apply <code>$().datepicker()</code> method.
@class dateui
@extends abstract
@extends abstractinput
@final
@example
<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-original-title="Select date">15/05/1984</a>
@ -46,7 +46,7 @@ $(function(){
this.options.datepicker.dateFormat = this.options.datepicker.format;
};
$.fn.editableutils.inherit(DateUI, $.fn.editabletypes.abstract);
$.fn.editableutils.inherit(DateUI, $.fn.editabletypes.abstractinput);
$.extend(DateUI.prototype, {
render: function () {
@ -129,7 +129,7 @@ $(function(){
});
DateUI.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
DateUI.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
/**
@property tpl
@default <div></div>

188
src/inputs/html5types.js Normal file

@ -0,0 +1,188 @@
/**
HTML5 input types.
Following types are supported:
* password
* email
* url
* tel
* number
* range
Learn more about html5 inputs:
http://www.w3.org/wiki/HTML5_form_additions
To check browser compatibility please see:
https://developer.mozilla.org/en-US/docs/HTML/Element/Input
@class html5types
@extends text
@final
@since 1.3.0
@example
<a href="#" id="email" data-type="email" data-pk="1">admin@example.com</a>
<script>
$(function(){
$('#email').editable({
url: '/post',
title: 'Enter email'
});
});
</script>
**/
/**
@property tpl
@default depends on type
**/
/*
Password
*/
(function ($) {
var Password = function (options) {
this.init('password', options, Password.defaults);
};
$.fn.editableutils.inherit(Password, $.fn.editabletypes.text);
$.extend(Password.prototype, {
//do not display password, show '[hidden]' instead
value2html: function(value, element) {
if(value) {
$(element).text('[hidden]');
} else {
$(element).empty();
}
},
//as password not displayed, should not set value by html
html2value: function(html) {
return null;
}
});
Password.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
tpl: '<input type="password">'
});
$.fn.editabletypes.password = Password;
}(window.jQuery));
/*
Email
*/
(function ($) {
var Email = function (options) {
this.init('email', options, Email.defaults);
};
$.fn.editableutils.inherit(Email, $.fn.editabletypes.text);
Email.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
tpl: '<input type="email">'
});
$.fn.editabletypes.email = Email;
}(window.jQuery));
/*
Url
*/
(function ($) {
var Url = function (options) {
this.init('url', options, Url.defaults);
};
$.fn.editableutils.inherit(Url, $.fn.editabletypes.text);
Url.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
tpl: '<input type="url">'
});
$.fn.editabletypes.url = Url;
}(window.jQuery));
/*
Tel
*/
(function ($) {
var Tel = function (options) {
this.init('tel', options, Tel.defaults);
};
$.fn.editableutils.inherit(Tel, $.fn.editabletypes.text);
Tel.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
tpl: '<input type="tel">'
});
$.fn.editabletypes.tel = Tel;
}(window.jQuery));
/*
Number
*/
(function ($) {
var NumberInput = function (options) {
this.init('number', options, NumberInput.defaults);
};
$.fn.editableutils.inherit(NumberInput, $.fn.editabletypes.text);
$.extend(NumberInput.prototype, {
render: function () {
NumberInput.superclass.render.call(this);
if (this.options.min !== null) {
this.$input.attr('min', this.options.min);
}
if (this.options.max !== null) {
this.$input.attr('max', this.options.max);
}
if (this.options.step !== null) {
this.$input.attr('step', this.options.step);
}
}
});
NumberInput.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
tpl: '<input type="number">',
inputclass: 'input-mini',
min: null,
max: null,
step: null
});
$.fn.editabletypes.number = NumberInput;
}(window.jQuery));
/*
Range (inherit from number)
*/
(function ($) {
var Range = function (options) {
this.init('range', options, Range.defaults);
};
$.fn.editableutils.inherit(Range, $.fn.editabletypes.number);
$.extend(Range.prototype, {
render: function () {
this.$input = $(this.options.tpl);
var $slider = this.$input.filter('input');
if(this.options.inputclass) {
$slider.addClass(this.options.inputclass);
}
if (this.options.min !== null) {
$slider.attr('min', this.options.min);
}
if (this.options.max !== null) {
$slider.attr('max', this.options.max);
}
if (this.options.step !== null) {
$slider.attr('step', this.options.step);
}
$slider.on('input', function(){
$(this).siblings('output').text($(this).val());
});
},
activate: function() {
this.$input.filter('input').focus();
}
});
Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, {
tpl: '<input type="range"><output style="width: 30px; display: inline-block"></output>',
inputclass: 'input-medium'
});
$.fn.editabletypes.range = Range;
}(window.jQuery));

@ -2,7 +2,7 @@
List - abstract class for inputs that have source option loaded from js array or via ajax
@class list
@extends abstract
@extends abstractinput
**/
(function ($) {
@ -10,7 +10,7 @@ List - abstract class for inputs that have source option loaded from js array or
};
$.fn.editableutils.inherit(List, $.fn.editabletypes.abstract);
$.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput);
$.extend(List.prototype, {
render: function () {
@ -45,7 +45,7 @@ List - abstract class for inputs that have source option loaded from js array or
}
deferred.resolve();
}, function () {
List.superclass.value2html(this.options.sourceError, element);
//do nothing with element
deferred.resolve();
});
@ -237,7 +237,7 @@ List - abstract class for inputs that have source option loaded from js array or
});
List.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.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>

@ -2,7 +2,7 @@
Text input
@class text
@extends abstract
@extends abstractinput
@final
@example
<a href="#" id="username" data-type="text" data-pk="1">awesome</a>
@ -20,7 +20,7 @@ $(function(){
this.init('text', options, Text.defaults);
};
$.fn.editableutils.inherit(Text, $.fn.editabletypes.abstract);
$.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
$.extend(Text.prototype, {
activate: function() {
@ -31,7 +31,7 @@ $(function(){
}
});
Text.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
/**
@property tpl
@default <input type="text">

@ -2,7 +2,7 @@
Textarea input
@class textarea
@extends abstract
@extends abstractinput
@final
@example
<a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a>
@ -21,7 +21,7 @@ $(function(){
this.init('textarea', options, Textarea.defaults);
};
$.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstract);
$.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput);
$.extend(Textarea.prototype, {
render: function () {
@ -66,7 +66,7 @@ $(function(){
}
});
Textarea.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
/**
@property tpl
@default <textarea></textarea>
@ -84,7 +84,7 @@ $(function(){
@type string
@default null
**/
placeholder: null
placeholder: null
});
$.fn.editabletypes.textarea = Textarea;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

6039
test/libs/bootstrap222/css/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

(image error) Size: 8.6 KiB

Binary file not shown.

After

(image error) Size: 12 KiB

2159
test/libs/bootstrap222/js/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -33,6 +33,7 @@ define(function () {
'inputs/textarea',
'inputs/select',
'inputs/checklist',
'inputs/html5types',
'inputs-ext/address/address'],
init: function(require) {
loadCss(require.toUrl("./editable-form.css"));
@ -44,6 +45,7 @@ define(function () {
'inputs/text': ['inputs/abstract'],
'inputs/textarea': ['inputs/abstract'],
'inputs/abstract': ['editable-form/editable-form-utils'],
'inputs/html5types': ['inputs/text'],
//bootstrap
'bootstrap/js/bootstrap': {
@ -145,7 +147,8 @@ define(function () {
return {
baseUrl: baseUrl,
paths: {
"bootstrap": "../test/libs/bootstrap221",
// "bootstrap": "../test/libs/bootstrap221",
"bootstrap": "../test/libs/bootstrap222",
"jqueryui": "../test/libs/jquery-ui-1.9.1.custom",
"poshytip": "../test/libs/poshytip",
"test": "../test"

@ -4,30 +4,41 @@ var jqver = decodeURIComponent((new RegExp('[?|&]' + 'jquery' + '=' + '([^&;]+?)
require(["loader", jqurl], function(loader) {
requirejs.config(loader.getConfig("../src"));
var config = loader.getConfig("../src"),
params = loader.getParams();
//add test specific dependencies
config.shim['test/mocks'] = ['element/editable-element', 'test/libs/mockjax/jquery.mockjax'];
//as we need to keep order of tests, create shim dependencies automatically
addTests(config);
requirejs.config(config);
require(['element/editable-element',
'test/libs/mockjax/jquery.mockjax'
],
require(['test/unit/api'],
function() {
//disable effects
$.fx.off = true;
$.support.transition = false;
var params = loader.getParams();
require([
QUnit.load();
QUnit.start();
});
function addTests(config) {
var tests = [
'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();
});
});
(params.f === 'bootstrap') ? 'test/unit/date' : 'test/unit/dateui',
'test/unit/api'
];
for(var i=0; i<tests.length-1; i++) {
config.shim[tests[i+1]] = [tests[i]];
}
}
});

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

@ -79,8 +79,8 @@ $(function () {
e.editable();
});
asyncTest("events: shown / hidden (reason: cancel, onblur, manual)", function () {
expect(11);
asyncTest("events: shown / hidden (reason: cancel, onblur, nochange, manual)", function () {
expect(15);
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);
@ -113,6 +113,13 @@ $(function () {
e.parent().click();
ok(!p.is(':visible'), 'popover closed');
test_reason = 'nochange'
e.click();
p = tip(e);
ok(p.is(':visible'), 'popover shown');
p.find('form').submit(); //submit value without changes
ok(!p.is(':visible'), 'popover closed');
test_reason = 'manual'
e.click();
p = tip(e);
@ -152,9 +159,8 @@ $(function () {
e.remove();
start();
}, timeout);
});
});
test("show/hide/toggle methods", function () {
var e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo('#qunit-fixture').editable();
e.editable('show');
@ -212,16 +218,16 @@ $(function () {
equal(e.text(), 'abcd', 'text set correctly (by object)');
});
asyncTest("'submit' method: client and server validation", function () {
asyncTest("'submit' method: client and server validation errors", function () {
var ev1 = 'ev1',
ev2 = 'ev2',
e1v = 'e1v',
e = $('<a href="#" class="new" data-type="text" data-url="post.php" data-name="text">'+ev1+'</a>').appendTo(fx).editable({
e = $('<a href="#" class="new-val" data-type="text" data-url="post.php" data-name="text">'+ev1+'</a>').appendTo(fx).editable({
validate: function(value) {
if(value == ev1) return 'invalid';
}
}),
e1 = $('<a href="#" class="new" data-type="text" data-name="text1">'+e1v+'</a>').appendTo(fx).editable();
e1 = $('<a href="#" class="new-val" data-type="text" data-name="text1">'+e1v+'</a>').appendTo(fx).editable();
$.mockjax({
url: 'new-error.php',
@ -236,49 +242,71 @@ $(function () {
};
}
});
$(fx).find('.new').editable('submit', {
$.mockjax({
url: 'new.php',
error: function(data) {
ok(data.errors, 'errors defined');
equal(data.errors.text, 'invalid', 'client validation error ok');
response: function(settings) {
ok(false, 'should not submit to new.php');
}
});
$(fx).find('.new-val').editable('submit', {
url: 'new.php',
error: function(errors) {
equal(errors.text, 'invalid', 'client validation error ok');
}
});
//change value to pass client side validation
e.click();
var p = tip(e);
p.find('input[type=text]').val(ev2);
p.find('button[type=submit]').click();
$(fx).find('.new').editable('submit', {
$(fx).find('.new-val').editable('submit', {
url: 'new-error.php',
data: {a: 123},
error: function(data) {
equal(data.errors.text1, 'server-invalid', 'server validation error ok');
e.remove();
e1.remove();
start();
success: function(data, config) {
ok(data.errors, 'errors received from server');
ok(typeof config.error === 'function', 'config passed correctly');
if(data && data.id) {
//success
} else if(data && data.errors){
config.error.call(this, data.errors); //call error from success
}
},
error: function(errors) {
equal(errors.text1, 'server-invalid', 'server validation error ok');
},
ajaxOptions: {
type: 'PUT'
type: 'PUT',
dataType: 'json'
}
});
});
setTimeout(function() {
e.remove();
e1.remove();
start();
}, timeout);
});
asyncTest("'submit' method: server error", function () {
expect(2);
var ev1 = 'ev1',
e1v = 'e1v',
e = $('<a href="#" class="new" data-type="text" data-url="post.php" data-name="text">'+ev1+'</a>').appendTo(fx).editable(),
e1 = $('<a href="#" class="new" data-type="text" data-name="text1">'+e1v+'</a>').appendTo(fx).editable();
e = $('<a href="#" class="new-err" data-type="text" data-url="post.php" data-name="text">'+ev1+'</a>').appendTo(fx).editable(),
e1 = $('<a href="#" class="new-err" data-type="text" data-name="text1">'+e1v+'</a>').appendTo(fx).editable();
$(fx).find('.new').editable('submit', {
$(fx).find('.new-err').editable('submit', {
url: 'error.php',
error: function(data) {
ok(!data.errors, 'no client errors');
equal(this[0], $(fx).find('.new-err')[0], 'success context ok');
equal(this[1], $(fx).find('.new-err')[1], 'success context2 ok');
equal(data.status, 500, 'status 500 ok');
equal(data.responseText, 'customtext', 'server error ok');
e.remove();
@ -290,7 +318,6 @@ $(function () {
});
asyncTest("'submit' method: success", function () {
expect(7);
var ev1 = 'ev1',
e1v = 'e1v',
pk = 123,
@ -302,24 +329,23 @@ $(function () {
response: function(settings) {
equal(settings.data.text, ev1, 'first value ok');
equal(settings.data.text1, e1v, 'second value ok');
this.responseText = {id: pk};
this.responseText = 'response-body';
}
});
$(fx).find('.new').editable('submit', {
url: 'new-success.php',
success: function(data) {
equal(e.data('editable').options.pk, pk, 'pk1 ok');
ok(!e.hasClass('editable-changed'), 'no "editable-changed" class');
equal(e1.data('editable').options.pk, pk, 'pk2 ok');
ok(!e1.hasClass('editable-changed'), 'no "editable-changed" class');
equal(data.id, pk, 'server result id ok');
equal(this[0], $(fx).find('.new')[0], 'success context ok');
equal(this[1], $(fx).find('.new')[1], 'success context2 ok');
equal(data, 'response-body', 'response body ok');
e.remove();
e1.remove();
start();
},
error: function(errors) {
ok(false, 'error should not be called');
}
});

@ -3,7 +3,8 @@ $(function () {
module("checklist", {
setup: function(){
sfx = $('#qunit-fixture'),
fx = $('#async-fixture');
fx = $('#async-fixture');
$.support.transition = false;
}
});

@ -444,6 +444,81 @@
e.remove();
start();
}, timeout);
});
});
asyncTest("should submit all required params", function () {
var e = $('<a href="#" data-pk="1" data-url="post-resp.php">abc</a>').appendTo(fx).editable({
name: 'username',
params: {
q: 2
},
ajaxOptions: {
dataType: 'json'
},
success: function(resp) {
equal(resp.dataType, 'json', 'dataType ok');
equal(resp.data.pk, 1, 'pk ok');
equal(resp.data.name, 'username', 'name ok');
equal(resp.data.value, newText, 'value ok');
equal(resp.data.q, 2, 'additional params ok');
}
}),
newText = 'cd<e>;"'
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("params as function", function () {
var e = $('<a href="#" data-pk="1" data-url="post-params-func.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 $.extend(params, {q: 2, pk: 3});
},
ajaxOptions: {
headers: {"myHeader": "123"}
}
}),
newText = 'cd<e>;"'
$.mockjax({
url: 'post-params-func.php',
response: function(settings) {
equal(settings.dataType, undefined, 'dataType undefined (correct)');
equal(settings.data.pk, 3, 'pk ok');
equal(settings.data.name, 'username', 'name ok');
equal(settings.data.value, newText, 'value ok');
equal(settings.data.q, 2, 'additional params 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);
});
}(jQuery));

@ -6,6 +6,7 @@ $(function () {
setup: function(){
fx = $('#async-fixture');
dpg = $.fn.datepicker.DPGlobal;
$.support.transition = false;
}
});

@ -5,6 +5,7 @@ $(function () {
module("dateui", {
setup: function(){
fx = $('#async-fixture');
$.support.transition = false;
}
});

@ -295,12 +295,12 @@ $(function () {
expect(4);
//clear cache
$(document).removeData('groups.php-name1');
$(document).removeData('groups-cache-sim.php-name1');
var req = 0;
$.mockjax({
url: 'groups-cache-sim.php',
responseTime: 200,
responseTime: 50,
response: function() {
req++;
this.responseText = groups;
@ -312,7 +312,6 @@ $(function () {
e2 = $('<a href="#" data-type="select" data-pk="1" data-name="name1" data-value="3" data-url="post.php" data-source="groups-cache-sim.php"></a>').appendTo(fx).editable();
setTimeout(function() {
equal(req, 1, 'one request');
equal(e.text(), groups[1], 'text1 correct');
equal(e1.text(), groups[2], 'text2 correct');
@ -330,7 +329,7 @@ $(function () {
expect(4);
//clear cache
$(document).removeData('groups.php-name1');
$(document).removeData('groups-cache-sim-err.php-name1');
var req = 0;
$.mockjax({
@ -342,17 +341,16 @@ $(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.editabletypes.select.defaults.sourceError;
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">11</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">22</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"></a>').appendTo(fx).editable();
setTimeout(function() {
equal(req, 1, 'one request');
equal(e.text(), errText, 'text1 correct');
equal(e1.text(), errText, 'text2 correct');
equal(e2.text(), errText, 'text3 correct');
equal(e.text(), '11', 'text1 correct');
equal(e1.text(), '22', 'text2 correct');
equal(e2.text(), $.fn.editable.defaults.emptytext, 'text3 correct');
e.remove();
e1.remove();

@ -211,71 +211,6 @@ $(function () {
});
asyncTest("should submit all required params", function () {
var e = $('<a href="#" data-pk="1" data-url="post-resp.php">abc</a>').appendTo(fx).editable({
name: 'username',
params: {
q: 2
},
success: function(resp) {
equal(resp.dataType, 'json', 'dataType ok');
equal(resp.data.pk, 1, 'pk ok');
equal(resp.data.name, 'username', 'name ok');
equal(resp.data.value, newText, 'value ok');
equal(resp.data.q, 2, 'additional params ok');
}
}),
newText = 'cd<e>;"'
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("params as function", 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 };
},
success: function(resp) {
equal(resp.dataType, 'json', 'dataType ok');
equal(resp.data.pk, 3, 'pk ok');
equal(resp.data.name, 'username', 'name ok');
equal(resp.data.value, newText, 'value ok');
equal(resp.data.q, 2, 'additional params ok');
},
ajaxOptions: {
headers: {"myHeader": "123"}
}
}),
newText = 'cd<e>;"'
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("ajaxOptions", function () {
var e = $('<a href="#" data-pk="1" data-url="post-options.php">abc</a>').appendTo(fx).editable({
@ -503,6 +438,67 @@ $(function () {
start();
}, timeout);
});
});
test("password", function () {
var v = '123', v1 = '456';
var e = $('<a href="#" data-pk="1" data-name="name" data-value="'+v+'"></a>').appendTo('#qunit-fixture').editable({
type: 'password',
url: function(params) {
equal(params.value, v1, 'submitted value correct');
}
});
equal(e.text(), '[hidden]', 'text is hidden');
e.click()
var p = tip(e);
ok(p.is(':visible'), 'popover visible');
var $input = p.find('input[type="password"]');
ok($input.length, 'input exists');
equal($input.val(), v, 'input contains correct value');
$input.val(v1);
p.find('form').submit();
ok(!p.is(':visible'), 'popover closed');
equal(e.data('editable').value, v1, 'new value saved to value');
equal(e.text(), '[hidden]', 'new text shown');
});
test("html5 types", function () {
var types = ['email', 'url', 'tel', 'number', 'range'],
v = '12',
v1 = '45';
expect(8*types.length);
for(var i = 0; i< types.length; i++) {
var e = $('<a href="#" data-pk="1" data-name="name">'+v+'</a>').appendTo('#qunit-fixture').editable({
type: types[i],
url: function(params) {
equal(params.value, v1, 'submitted value correct');
}
});
equal(e.data('editable').value, v, 'value correct');
e.click()
var p = tip(e);
ok(p.is(':visible'), 'popover visible');
var $input = p.find('input[type='+types[i]+']');
ok($input.length, 'input exists');
equal($input.val(), v, 'input contain correct value');
$input.val(v1);
p.find('form').submit();
ok(!p.is(':visible'), 'popover closed');
equal(e.data('editable').value, v1, 'new value saved to value');
equal(e.text(), v1, 'new text shown');
}
});
});