From 3b7a7ec4ebe9ad3f6e0b7afa4a2d6afc53b9f78b Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Wed, 28 Nov 2012 12:27:33 +0400 Subject: [PATCH 01/25] success returns object --- CHANGELOG.txt | 5 +++++ src/editable-form/editable-form.js | 27 ++++++++++++++++-------- test/loader.js | 3 +-- test/unit/text.js | 33 +++++++++++++++++++++++++++++- 4 files changed, 57 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 8257188..ee0d897 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,5 +1,10 @@ X-editable changelog ============================= + + +Version 1.1.1 wip +---------------------------- +[enh] success callback can return object to overwrite submitted value (vitalets) Version 1.1.0 Nov 27, 2012 diff --git a/src/editable-form/editable-form.js b/src/editable-form/editable-form.js index b6f8018..90e96f9 100644 --- a/src/editable-form/editable-form.js +++ b/src/editable-form/editable-form.js @@ -177,13 +177,20 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. //sending data to server $.when(this.save(newValueStr)) .done($.proxy(function(response) { - var error; - //call success callback. if it returns string --> show error - if(error = this.options.success.call(this, response, newValue)) { - this.error(error); + //run success callback + var res = typeof this.options.success === 'function' ? this.options.success.call(this, response, newValue) : null; + + //if it returns string --> show error + if(res && typeof res === 'string') { + this.error(res); this.showForm(); return; - } + } + + //if it returns object like {newValue: <something>} --> use that value + if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) { + newValue = res.newValue; + } //clear error message this.error(false); @@ -398,9 +405,13 @@ 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. - Can be used to process json response. If this function returns string - means error occured and string is shown as error message. - + 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> + 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: <something>}</code> - it overwrites value, submitted by user. + Otherwise newValue simply rendered into element. + @property success @type function @default null diff --git a/test/loader.js b/test/loader.js index ba91063..b1b4a6b 100644 --- a/test/loader.js +++ b/test/loader.js @@ -80,8 +80,7 @@ function getAssets(f, c, src, libs) { //core js.unshift(bootstrap+'js/bootstrap.js') css.unshift(bootstrap+'css/bootstrap.css'); -// css.push(bootstrap+'css/bootstrap.css'); - //css.unshift(bootstrap+'css/bootstrap-responsive.css'); + css.unshift(bootstrap+'css/bootstrap-responsive.css'); //editable js.push(forms+'editable-form-bootstrap.js'); diff --git a/test/unit/text.js b/test/unit/text.js index 79f1b90..538e786 100644 --- a/test/unit/text.js +++ b/test/unit/text.js @@ -211,7 +211,38 @@ $(function () { start(); }, timeout); - }); + }); + + asyncTest("should show new value if success callback returns object", 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) { + equal(newValue, newText, 'value in success passed correctly'); + return {newValue: 'xyz'}; + } + }); + + 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 closed'); + equal(p.find('.editable-error-block').text(), '', 'no error msg'); + equal(e.data('editable').value, 'xyz', 'value ok'); + equal(e.text(), 'xyz', 'text ok'); + + p.find('button[type=button]').click(); + ok(!p.is(':visible'), 'popover was removed'); + 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({ From 36e7f2827fce30a660063c9da2c6071df995075f Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Wed, 28 Nov 2012 15:06:04 +0400 Subject: [PATCH 02/25] ajaxOptions parameter --- src/editable-form/editable-form.js | 19 +++++++++++---- test/unit/text.js | 37 ++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/editable-form/editable-form.js b/src/editable-form/editable-form.js index 90e96f9..225f1de 100644 --- a/src/editable-form/editable-form.js +++ b/src/editable-form/editable-form.js @@ -219,7 +219,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. save: function(value) { var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this) : this.options.pk, send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk)))), - params; + params, ajaxOptions; if (send) { //send to server this.showLoading(); @@ -243,12 +243,14 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. 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 - return $.ajax({ + ajaxOptions = $.extend({ url : this.options.url, data : params, type : 'post', dataType: 'json' - }); + }, this.options.ajaxOptions); + + return $.ajax(ajaxOptions); } } }, @@ -420,7 +422,16 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. if(!response.success) return response.msg; } **/ - success: function(response, newValue) {} + success: function(response, newValue) {}, + /** + Additional options for ajax request. + List of values: http://api.jquery.com/jQuery.ajax + + @property ajaxOptions + @type object + @default null + **/ + ajaxOptions: null }; /* diff --git a/test/unit/text.js b/test/unit/text.js index 538e786..8e56652 100644 --- a/test/unit/text.js +++ b/test/unit/text.js @@ -287,6 +287,9 @@ $(function () { 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>;"' @@ -303,8 +306,38 @@ $(function () { start(); }, timeout); - }); - + }); + + asyncTest("ajaxOptions", function () { + var e = $('<a href="#" data-pk="1" data-url="post-options.php">abc</a>').appendTo(fx).editable({ + name: 'username', + ajaxOptions: { + dataType: 'html' + } + }), + newText = 'cd<e>;"' + + $.mockjax({ + url: 'post-options.php', + response: function(settings) { + equal(settings.dataType, 'html', 'dataType key 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("submit to url defined as function", function () { expect(3); From 37d10a0db0fafb0a9ce27abd473a9dbf712ba43d Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Wed, 28 Nov 2012 15:18:45 +0400 Subject: [PATCH 03/25] update base fiddle: add mockjax --- CHANGELOG.txt | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ee0d897..b149c02 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,6 +4,7 @@ X-editable changelog Version 1.1.1 wip ---------------------------- +[enh] 'ajaxOptions' parameter for advanced ajax configuration (vitalets) [enh] success callback can return object to overwrite submitted value (vitalets) diff --git a/README.md b/README.md index ca3354d..b8417ad 100644 --- a/README.md +++ b/README.md @@ -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/1/) as starting point. +When creating issues please provide jsFiddle example. You can just fork [this fiddle](http://jsfiddle.net/xBB5x/5/) as starting point. Your feedback is very appreciated! ## Contribution From da44cfd715fc3507f1f0d3ce6b1a5021f82c3448 Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Wed, 28 Nov 2012 16:20:37 +0400 Subject: [PATCH 04/25] display: inline for inline container --- src/containers/editable-container.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/containers/editable-container.css b/src/containers/editable-container.css index 9098275..c0a5c48 100644 --- a/src/containers/editable-container.css +++ b/src/containers/editable-container.css @@ -5,4 +5,9 @@ .editable-container.popover { /* width: 300px;*/ /* debug */ width: auto; /* without this rule popover does not stretch */ -} \ No newline at end of file +} + +.editable-container.editable-inline { + display: inline; + vertical-align: middle; +} \ No newline at end of file From ba4a2055f78334234e90f42cee68d2ae754fd6bc Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Wed, 28 Nov 2012 16:48:58 +0400 Subject: [PATCH 05/25] inile fixes for loading div --- src/containers/editable-container.css | 2 +- src/editable-form/editable-form.css | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/containers/editable-container.css b/src/containers/editable-container.css index c0a5c48..1a91e98 100644 --- a/src/containers/editable-container.css +++ b/src/containers/editable-container.css @@ -10,4 +10,4 @@ .editable-container.editable-inline { display: inline; vertical-align: middle; -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/editable-form/editable-form.css b/src/editable-form/editable-form.css index 1ff18fe..874705a 100644 --- a/src/editable-form/editable-form.css +++ b/src/editable-form/editable-form.css @@ -38,7 +38,8 @@ .editableform-loading { background: url('img/loading.gif') center center no-repeat; height: 25px; - width: auto; + width: auto; + min-width: 25px; } .editable-inline .editableform-loading { From ee13a2b1fbfe627b86a23a2bd82dfde1f0f1175a Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Wed, 28 Nov 2012 17:41:50 +0400 Subject: [PATCH 06/25] activate method in checklist --- src/inputs/checklist.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/inputs/checklist.js b/src/inputs/checklist.js index 1a5b149..376e80b 100644 --- a/src/inputs/checklist.js +++ b/src/inputs/checklist.js @@ -99,7 +99,11 @@ $(function(){ html = this.options.limitText.replace('{checked}', $.isArray(value) ? value.length : 0).replace('{count}', this.sourceData.length); } $(element).html(html); - } + }, + + activate: function() { + this.$input.find('input[type="checkbox"]').first().focus(); + } }); Checklist.defaults = $.extend({}, $.fn.editableform.types.list.defaults, { From 79847d1d9295caf4abf2061b96a27f867ae4192f Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Wed, 28 Nov 2012 21:24:51 +0400 Subject: [PATCH 07/25] onblur option ready, need test --- src/containers/editable-container.js | 133 +++++++++++++++++++++++++-- src/containers/editable-inline.js | 10 +- src/containers/editable-poshytip.js | 5 +- src/containers/editable-tooltip.js | 8 +- src/element/editable-element.js | 32 ++----- 5 files changed, 140 insertions(+), 48 deletions(-) diff --git a/src/containers/editable-container.js b/src/containers/editable-container.js index 8cc64c2..0228d55 100644 --- a/src/containers/editable-container.js +++ b/src/containers/editable-container.js @@ -27,7 +27,73 @@ Applied as jQuery method. //bind 'destroyed' listener to destroy container when element is removed from dom this.$element.on('destroyed', $.proxy(function(){ this.destroy(); - }, this)); + }, this)); + + //attach document handlers (once) + if(!$(document).data('editable-handlers-attached')) { + //close all on escape + $(document).on('keyup.editable', function (e) { + if (e.which === 27) { + $('.editable-open').editableContainer('hide'); + //todo: return focus on element + } + }); + + //close containers when click outside + $(document).on('click.editable', function(e) { + var element = e.target, + $target = $(e.target), + $clickedContainer = $target.is('.editable-container') ? $target : $target.closest('.editable-container'); + + //if click inside some editableContainer --> find corresponding element + if($clickedContainer.length) { + $('.editable-open').each(function(i, el){ + if($(el).data('editableContainer').tip()[0] === $clickedContainer[0]) { + element = el; + return false; + } + }); + } + + //close all open containers (except one) + EditableContainer.prototype.closeOthers(element); + + /* $('.editable-open').each(function(){ + //if click target is editable element --> do nothing with el + if(this === e.target) { + return; + } + + var $el = $(this), + ec = $el.data('editableContainer'); + + //if click in some editableContainer and current el is it's owner --> do nothing with el + if($clickedContainer.length && ec.tip()[0] === $clickedContainer[0]) { + return; + } + + //otherwise cancel or submit el's container + if(ec.options.onblur === 'cancel') { + $el.data('editableContainer').hide(); + } else if(ec.options.onblur === 'submit') { + $el.data('editableContainer').tip().find('form').submit(); + } + }); */ + + //if click inside container --> do nothing + // if($target.is('.editable-container') || $target.parents('.editable-container').length || $target.parents('.ui-datepicker-header').length) { + /* + if($target.is('.editable-container') || $target.parents('.editable-container').length || $target.parents('.ui-datepicker-header').length) { + return; + } + + //close all other containers + $('.editable-container').find('.editable-cancel').click(); + */ + }); + + $(document).data('editable-handlers-attached', true); + } }, //split options on containerOptions and formOptions @@ -93,11 +159,22 @@ Applied as jQuery method. /** Shows container with form @method show() + @param {boolean} multi if true - other editable containers will not be closed. Default false. **/ - show: function () { + show: function (multi) { + this.$element.addClass('editable-open'); + if(!multi) { + //close all open containers (except one) + this.closeOthers(this.$element[0]); + } + + this.innerShow(); + }, + + /* internal show method. To be overwritten in child classes */ + innerShow: function () { this.call('show'); this.tip().addClass('editable-container'); - this.initForm(); this.tip().find(this.innerCss).empty().append(this.$form); this.$form.editableform('render'); @@ -108,10 +185,11 @@ Applied as jQuery method. @method hide() **/ hide: function() { - if(!this.tip() || !this.tip().is(':visible')) { + if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) { return; } - this.call('hide'); + this.$element.removeClass('editable-open'); + this.innerHide(); /** Fired when container was hidden. It occurs on both save or cancel. @@ -121,6 +199,11 @@ Applied as jQuery method. this.$element.triggerHandler('hidden'); }, + /* internal hide method. To be overwritten in child classes */ + innerHide: function () { + this.call('hide'); + }, + /** Toggles container visibility (show / hide) @method toggle() @@ -210,6 +293,34 @@ Applied as jQuery method. **/ destroy: function() { this.call('destroy'); + }, + + /* + Closes other containers except one related to passed element. + Other containers can be cancelled or submitted (depends on onblur option) + */ + closeOthers: function(element) { + $('.editable-open').each(function(i, el){ + //do nothing with passed element + if(el === element) { + return; + } + + //otherwise cancel or submit all open containers + var $el = $(el), + ec = $el.data('editableContainer'); + + if(!ec) { + return; + } + + if(ec.options.onblur === 'cancel') { + $el.data('editableContainer').hide(); + } else if(ec.options.onblur === 'submit') { + $el.data('editableContainer').tip().find('form').submit(); + } + }); + } }; @@ -248,7 +359,7 @@ Applied as jQuery method. //store constructor $.fn.editableContainer.Constructor = EditableContainer; - //defaults - must be redefined! + //defaults $.fn.editableContainer.defaults = { /** Initial value of form input @@ -275,7 +386,15 @@ Applied as jQuery method. @default true @private **/ - autohide: true + autohide: true, + /** + Action when click outside container. Can be <code>cancel|submit|ignore</code> + + @property onblur + @type string + @default cancel + **/ + onblur: 'cancel' }; /* diff --git a/src/containers/editable-inline.js b/src/containers/editable-inline.js index 92c5455..df585a6 100644 --- a/src/containers/editable-inline.js +++ b/src/containers/editable-inline.js @@ -26,7 +26,7 @@ return this.$form; }, - show: function () { + innerShow: function () { this.$element.hide(); if(this.$form) { @@ -40,19 +40,13 @@ this.$form.editableform('render'); }, - hide: function () { - if(!this.tip() || !this.tip().is(':visible')) { - return; - } + innerHide: function () { this.$form.hide(this.options.anim, $.proxy(function() { this.$element.show(); //return focus on element if (this.options.enablefocus) { this.$element.focus(); } - - //trigger event - this.$element.triggerHandler('hidden'); }, this)); }, diff --git a/src/containers/editable-poshytip.js b/src/containers/editable-poshytip.js index 5cbd427..3488ec9 100644 --- a/src/containers/editable-poshytip.js +++ b/src/containers/editable-poshytip.js @@ -28,11 +28,10 @@ this.call('update', $content); }, - show: function () { + innerShow: function () { this.$form.editableform('render'); - this.tip().addClass('editable-container'); - this.call('show'); + this.tip().addClass('editable-container'); this.$form.data('editableform').input.activate(); }, diff --git a/src/containers/editable-tooltip.js b/src/containers/editable-tooltip.js index dcfa261..4fbc842 100644 --- a/src/containers/editable-tooltip.js +++ b/src/containers/editable-tooltip.js @@ -50,7 +50,7 @@ return this.container()._find(this.container().element); }, - show: function() { + innerShow: function() { this.call('open'); this.tip().addClass('editable-container'); @@ -62,12 +62,8 @@ this.$form.editableform('render'); }, - hide: function() { - if(!this.tip() || !this.tip().is(':visible')) { - return; - } + innerHide: function() { this.call('close'); - this.$element.triggerHandler('hidden'); }, setPosition: function() { diff --git a/src/element/editable-element.js b/src/element/editable-element.js index f51afec..1a3dd78 100644 --- a/src/element/editable-element.js +++ b/src/element/editable-element.js @@ -50,24 +50,6 @@ Makes editable any HTML element on the page. Applied as jQuery method. this.value = this.input.str2value($.trim(this.options.value)); } - //attach handler to close any container on escape - $(document).off('keyup.editable').on('keyup.editable', function (e) { - if (e.which === 27) { - $('.editable-container').find('.editable-cancel').click(); - } - }); - - //attach handler to close container when click outside - $(document).off('click.editable').on('click.editable', function(e) { - var $target = $(e.target); - //if click inside container --> do nothing - if($target.is('.editable-container') || $target.parents('.editable-container').length || $target.parents('.ui-datepicker-header').length) { - return; - } - //close all other containers - $('.editable-container').find('.editable-cancel').click(); - }); - //add 'editable' class this.$element.addClass('editable'); @@ -199,15 +181,16 @@ Makes editable any HTML element on the page. Applied as jQuery method. return; } //stop propagation bacause document listen any click to hide all editableContainers - e.stopPropagation(); + //e.stopPropagation(); this.toggle(); }, /** Shows container with form @method show() + @param {boolean} multi if true - other editable containers will not be closed. Default false. **/ - show: function () { + show: function (multi) { if(this.options.disabled) { return; } @@ -216,7 +199,7 @@ Makes editable any HTML element on the page. Applied as jQuery method. if(!this.container) { var containerOptions = $.extend({}, this.options, { value: this.value, - autohide: false //element itsef will show/hide container + autohide: false //element will take care to show/hide container }); this.$element.editableContainer(containerOptions); this.$element.on({ @@ -229,10 +212,11 @@ Makes editable any HTML element on the page. Applied as jQuery method. } //hide all other editable containers. Required to work correctly with toggle = manual - $('.editable-container').find('.editable-cancel').click(); + //temp + //$('.editable-container').find('.editable-cancel').click(); //show container - this.container.show(); + this.container.show(multi); }, /** @@ -247,7 +231,7 @@ Makes editable any HTML element on the page. Applied as jQuery method. //return focus on element if (this.options.enablefocus && this.options.toggle === 'click') { this.$element.focus(); - } + } }, /** From 7f306aacbed84c6f39a66f7c56bb9331a26b2bf2 Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Thu, 29 Nov 2012 12:46:10 +0400 Subject: [PATCH 08/25] onblur option test ready --- CHANGELOG.txt | 1 + test/unit/common.js | 189 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 188 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index b149c02..a9260f3 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,6 +4,7 @@ X-editable changelog Version 1.1.1 wip ---------------------------- +[enh #13] 'onblur' option: to cancel, submit or ignore when user clicks outside the form (vitalets) [enh] 'ajaxOptions' parameter for advanced ajax configuration (vitalets) [enh] success callback can return object to overwrite submitted value (vitalets) diff --git a/test/unit/common.js b/test/unit/common.js index 468c48e..22e8da2 100644 --- a/test/unit/common.js +++ b/test/unit/common.js @@ -78,7 +78,7 @@ e.click(); var p = tip(e); - ok(p.is(':visible'), 'popover shown'); + ok(p.is(':visible'), 'popover shown'); //todo: for jqueryui phantomjs calcs wrong position. Need investigation if(!$.browser.webkit && fc.f !== 'jqueryui') { @@ -89,7 +89,7 @@ ok(p.find(':contains("'+title+'")').length, 'title ok'); e.remove(); }); - + /* test("should close all other containers on click on editable", function () { var e1 = $('<a href="#" data-pk="1" data-url="post.php" id="a">abc</a>').appendTo('#qunit-fixture').editable(), e2 = $('<a href="#" data-pk="1" data-url="post.php" id="b">abcd</a>').appendTo('#qunit-fixture').editable(); @@ -121,6 +121,191 @@ e1.click(); ok(!p.is(':visible'), 'popover closed'); }); + */ + + test("onblur: cancel", function () { + var oldValue = 'abc', + newValue = 'cde', + e = $('<a href="#" data-type="text" data-pk="1" data-url="post.php" id="a">'+oldValue+'</a>').appendTo('#qunit-fixture').editable({ + onblur: 'cancel', + url: function() {} + }), + e2 = $('<a href="#" data-type="text" data-pk="1" data-url="post.php" id="b">abcd</a>').appendTo('#qunit-fixture').editable(); + + //click inside + e.click(); + var p = tip(e); + ok(p.is(':visible'), 'popover1 visible'); + p.find('input').val(newValue); + p.click(); + p.find('input').click(); + ok(p.is(':visible'), 'popover1 still visible'); + + //click outside + p.find('input').val(newValue); + $('#qunit-fixture').click(); + ok(!p.is(':visible'), 'popover1 closed'); + equal(e.data('editable').value, oldValue, 'old value exists'); + + //click on another editable + e.click(); + p = tip(e); + ok(p.is(':visible'), 'popover1 visible'); + p.find('input').val(newValue); + e2.click(); + var p2 = tip(e2); + ok(!p.is(':visible'), 'popover1 closed'); + ok(p2.is(':visible'), 'popover2 visible'); + equal(e.data('editable').value, oldValue, 'old value exists'); + e2.editable('hide'); + ok(!p2.is(':visible'), 'popover2 closed'); + + //call show method of another editable (multi = false) + e.click(); + p = tip(e); + ok(p.is(':visible'), 'popover1 visible'); + p.find('input').val(newValue); + e2.editable('show'); + p2 = tip(e2); + ok(!p.is(':visible'), 'popover1 closed'); + ok(p2.is(':visible'), 'popover2 visible'); + equal(e.data('editable').value, oldValue, 'old value exists'); + e2.editable('hide'); + ok(!p2.is(':visible'), 'popover2 closed'); + + //call show method of another editable (multi = true) + e.click(); + p = tip(e); + ok(p.is(':visible'), 'popover1 visible'); + p.find('input').val(newValue); + e2.editable('show', true); + p2 = tip(e2); + ok(p.is(':visible'), 'popover1 visible'); + ok(p2.is(':visible'), 'popover2 visible'); + + e.editable('hide'); + e2.editable('hide'); + ok(!p.is(':visible'), 'popover1 closed'); + ok(!p2.is(':visible'), 'popover2 closed'); + }); + + test("onblur: submit", function () { + var oldValue = 'abc', + newValue = 'cde', + e = $('<a href="#" data-type="text" data-pk="1" data-url="post.php" id="a">'+oldValue+'</a>').appendTo('#qunit-fixture').editable({ + onblur: 'submit', + url: function() {} + }), + e2 = $('<a href="#" data-type="text" data-pk="1" data-url="post.php" id="b">abcd</a>').appendTo('#qunit-fixture').editable(); + + //click inside + e.click(); + var p = tip(e); + ok(p.is(':visible'), 'popover1 visible'); + p.find('input').val(newValue); + p.click(); + p.find('input').click(); + ok(p.is(':visible'), 'popover1 still visible'); + + //click outside + p.find('input').val(newValue); + $('#qunit-fixture').click(); + ok(!p.is(':visible'), 'popover1 closed'); + equal(e.data('editable').value, newValue, 'new value saved'); + + //click on another editable + e.click(); + p = tip(e); + ok(p.is(':visible'), 'popover1 visible'); + p.find('input').val(oldValue); + e2.click(); + var p2 = tip(e2); + ok(!p.is(':visible'), 'popover1 closed'); + ok(p2.is(':visible'), 'popover2 visible'); + equal(e.data('editable').value, oldValue, 'old value re-saved'); + e2.editable('hide'); + ok(!p2.is(':visible'), 'popover2 closed'); + + //call show method of another editable (multi = false) + e.click(); + p = tip(e); + ok(p.is(':visible'), 'popover1 visible'); + p.find('input').val(newValue); + e2.editable('show'); + p2 = tip(e2); + ok(!p.is(':visible'), 'popover1 closed'); + ok(p2.is(':visible'), 'popover2 visible'); + equal(e.data('editable').value, newValue, 'new value saved'); + e2.editable('hide'); + ok(!p2.is(':visible'), 'popover2 closed'); + + //call show method of another editable (multi = true) + e.click(); + p = tip(e); + ok(p.is(':visible'), 'popover1 visible'); + p.find('input').val(oldValue); + e2.editable('show', true); + p2 = tip(e2); + ok(p.is(':visible'), 'popover1 visible'); + ok(p2.is(':visible'), 'popover2 visible'); + + e.editable('hide'); + e2.editable('hide'); + ok(!p.is(':visible'), 'popover1 closed'); + ok(!p2.is(':visible'), 'popover2 closed'); + }); + + test("onblur: ignore", function () { + var oldValue = 'abc', + newValue = 'cde', + e = $('<a href="#" data-type="text" data-pk="1" data-url="post.php" id="a">'+oldValue+'</a>').appendTo('#qunit-fixture').editable({ + onblur: 'ignore', + url: function() {} + }), + e2 = $('<a href="#" data-type="text" data-pk="1" data-url="post.php" id="b">abcd</a>').appendTo('#qunit-fixture').editable(); + + //click inside + e.click(); + var p = tip(e); + ok(p.is(':visible'), 'popover1 visible'); + p.find('input').val(newValue); + p.click(); + p.find('input').click(); + ok(p.is(':visible'), 'popover1 still visible'); + + //click outside + p.find('input').val(newValue); + $('#qunit-fixture').click(); + ok(p.is(':visible'), 'popover1 still visible'); + + //click on another editable + e2.click(); + var p2 = tip(e2); + ok(p.is(':visible'), 'popover1 still visible'); + ok(p2.is(':visible'), 'popover2 visible'); + e2.editable('hide'); + ok(!p2.is(':visible'), 'popover2 closed'); + + //call show method of another editable (multi = false) + e2.editable('show'); + p2 = tip(e2); + ok(p.is(':visible'), 'popover1 still visible'); + ok(p2.is(':visible'), 'popover2 visible'); + e2.editable('hide'); + ok(!p2.is(':visible'), 'popover2 closed'); + + //call show method of another editable (multi = true) + e2.editable('show', true); + p2 = tip(e2); + ok(p.is(':visible'), 'popover1 still visible'); + ok(p2.is(':visible'), 'popover2 visible'); + e2.editable('hide'); + ok(!p2.is(':visible'), 'popover2 closed'); + + e.editable('hide'); + ok(!p.is(':visible'), 'popover1 closed'); + }); + test("should not wrap buttons when parent has position:absolute", function () { var d = $('<div style="position: absolute; top: 200px">').appendTo(fx), From e24322bc52288a342b3a1dd070411bfad8b43201 Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Thu, 29 Nov 2012 13:39:52 +0400 Subject: [PATCH 09/25] escape method in abstract input --- src/inputs/abstract.js | 9 ++++++++- src/inputs/checklist.js | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/inputs/abstract.js b/src/inputs/abstract.js index a40d742..6fe6dfe 100644 --- a/src/inputs/abstract.js +++ b/src/inputs/abstract.js @@ -46,7 +46,7 @@ To create your own input you should inherit from this class. @param {DOMElement} element **/ value2html: function(value, element) { - var html = $('<div>').text(value).html(); + var html = this.escape(value); $(element).html(html); }, @@ -120,6 +120,13 @@ To create your own input you should inherit from this class. **/ clear: function() { this.$input.val(null); + }, + + /** + method to escape html + **/ + escape: function(str) { + return $('<div>').text(str).html(); } }; diff --git a/src/inputs/checklist.js b/src/inputs/checklist.js index 376e80b..73c70b7 100644 --- a/src/inputs/checklist.js +++ b/src/inputs/checklist.js @@ -1,5 +1,6 @@ /** -List of checkboxes. Internally value stored as javascript array of values. +List of checkboxes. +Internally value stored as javascript array of values. @class checklist @extends list @@ -49,6 +50,8 @@ $(function(){ value2str: function(value) { return $.isArray(value) ? value.join($.trim(this.options.separator)) : ''; + //it is also possible to sent as array + //return value; }, //parse separated string From a7c71001f6510002ba4e714095c9c6e5dcf94464 Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Thu, 29 Nov 2012 15:02:00 +0400 Subject: [PATCH 10/25] trim value only if it is string --- src/element/editable-element.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/element/editable-element.js b/src/element/editable-element.js index 1a3dd78..6ce4e2d 100644 --- a/src/element/editable-element.js +++ b/src/element/editable-element.js @@ -47,7 +47,10 @@ Makes editable any HTML element on the page. Applied as jQuery method. this.value = this.input.html2value($.trim(this.$element.html())); isValueByText = true; } else { - this.value = this.input.str2value($.trim(this.options.value)); + if(typeof this.options.value === 'string') { + this.options.value = $.trim(this.options.value); + } + this.value = this.input.str2value(this.options.value); } //add 'editable' class From c9c8f5894a10eee9a740fcab6576a00cb273c72b Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Thu, 29 Nov 2012 15:12:52 +0400 Subject: [PATCH 11/25] comments for docs --- src/containers/editable-container.js | 5 +++-- src/inputs/abstract.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/containers/editable-container.js b/src/containers/editable-container.js index 0228d55..56d7290 100644 --- a/src/containers/editable-container.js +++ b/src/containers/editable-container.js @@ -388,7 +388,8 @@ Applied as jQuery method. **/ autohide: true, /** - Action when click outside container. Can be <code>cancel|submit|ignore</code> + Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code> + Setting <code>ignore</code> ignore allows to have several containers open. @property onblur @type string @@ -409,4 +410,4 @@ Applied as jQuery method. } }; -}(window.jQuery)); \ No newline at end of file +}(window.jQuery)); diff --git a/src/inputs/abstract.js b/src/inputs/abstract.js index 6fe6dfe..fe7ed84 100644 --- a/src/inputs/abstract.js +++ b/src/inputs/abstract.js @@ -123,7 +123,7 @@ To create your own input you should inherit from this class. }, /** - method to escape html + method to escape html. **/ escape: function(str) { return $('<div>').text(str).html(); From c9444d0a5f5f667d75b567663bcb7864a5525f74 Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Thu, 29 Nov 2012 18:24:23 +0400 Subject: [PATCH 12/25] comments fix --- src/editable-form/editable-form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editable-form/editable-form.js b/src/editable-form/editable-form.js index 225f1de..d5a14ae 100644 --- a/src/editable-form/editable-form.js +++ b/src/editable-form/editable-form.js @@ -348,7 +348,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. /** Additional params for submit. Function can be used to calculate params dynamically @example - params: function() { + params: function(params) { return { a: 1 }; } From cc7b4ad0b69a35238b3811f00ad3e4412fa2f1b7 Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Thu, 29 Nov 2012 18:59:04 +0400 Subject: [PATCH 13/25] inputs-ext directory with sample address input --- grunt.js | 30 +++----- src/inputs-ext/address/address.css | 9 +++ src/inputs-ext/address/address.js | 107 +++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 19 deletions(-) create mode 100644 src/inputs-ext/address/address.css create mode 100644 src/inputs-ext/address/address.js diff --git a/grunt.js b/grunt.js index 3b66d5b..4282da2 100644 --- a/grunt.js +++ b/grunt.js @@ -151,7 +151,8 @@ module.exports = function(grunt) { 'src/element/*.js', 'src/inputs/*.js', 'src/inputs/date/date.js', - 'src/inputs/dateui/dateui.js' + 'src/inputs/dateui/dateui.js', + 'src/inputs-ext/**/*.js' ] }, /* @@ -192,29 +193,20 @@ module.exports = function(grunt) { flatten: true } }, + inputs_ext: { + files: { + '<%= dist %>/inputs-ext/': 'src/inputs-ext/**' + }, + options: { + basePath: 'inputs-ext' + } + }, ui_datepicker: { files: { //copy jquery ui datepicker '<%= dist %>/jquery-editable/jquery-ui-datepicker/' : 'src/inputs/dateui/jquery-ui-datepicker/**' } - } - }, - yuidoc: { - compile: { - name: '<%= pkg.title || pkg.name %>', - description: '<%= pkg.description %>', - version: '<%= pkg.version %>', - url: "<%= pkg.homepage %>", - // logo: 'src/editable-form/img/loading.gif', - options: { - paths: "src/", - ignorePaths: ['src/inputs/date/locales'], - outdir: "../docs/", -// theme: "simple", - themedir: "../yuidoc-theme" - //themedir: "../yuidoc-bootstrap-theme-master" - } - } + } }, //compress does not work properly for MAC OS (see https://github.com/vitalets/bootstrap-editable/issues/19) diff --git a/src/inputs-ext/address/address.css b/src/inputs-ext/address/address.css new file mode 100644 index 0000000..f37a43b --- /dev/null +++ b/src/inputs-ext/address/address.css @@ -0,0 +1,9 @@ +.editable-address { + display: block; + margin-bottom: 5px; +} + +.editable-address span { + width: 70px; + display: inline-block; +} \ No newline at end of file diff --git a/src/inputs-ext/address/address.js b/src/inputs-ext/address/address.js new file mode 100644 index 0000000..7cc562e --- /dev/null +++ b/src/inputs-ext/address/address.js @@ -0,0 +1,107 @@ +/** +Address editable input. +Internally value stored as {city: "Moscow", street: "Lenina", building: "15"} + +@class address +@extends abstract +@final +@example +<a href="#" id="address" data-type="address" data-pk="1">awesome</a> +<script> +$(function(){ + $('#address').editable({ + url: '/post', + title: 'Enter city, street and building #', + value: { + city: "Moscow", + street: "Lenina", + building: "15" + } + }); +}); +</script> +**/ +(function ($) { + var Address = function (options) { + this.init('address', options, Address.defaults); + }; + + $.fn.editableform.utils.inherit(Address, $.fn.editableform.types.abstract); + + $.extend(Address.prototype, { + render: function() { + Address.superclass.render.call(this); + + // this.$input. + }, + + + value2html: function(value, element) { + var html = value.city + ', st. ' + value.street + ', bld. ' + value.building; + $(element).text(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. + editable({ + value: { + city: "Moscow", + street: "Lenina", + building: "15" + } + }); + */ + return null; + }, + + /* + method for converting data before sent on server. + As jQuery correctly sends objects via ajax, you can just return value + */ + value2str: function(value) { + return value; + }, + + /* + this is mainly for parsing value defined in data-value attribute. + If you will always set value by javascript, no need to overwrite it + */ + str2value: function(str) { + return str; + }, + + 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); + }, + + input2value: function() { + return { + city: this.$input.find('input[name="city"]').val(), + street: this.$input.find('input[name="street"]').val(), + building: this.$input.find('input[name="building"]').val() + }; + }, + + activate: function() { + //set focus on city + this.$input.find('input[name="city"]').focus(); + } + }); + + 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>', + + inputclass: 'editable-address' + }); + + $.fn.editableform.types.address = Address; + +}(window.jQuery)); \ No newline at end of file From ad420118681ba7d3e7c7b584becb63b2a990a9f7 Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Thu, 29 Nov 2012 19:09:03 +0400 Subject: [PATCH 14/25] bump ver 1.1.1 --- README.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b8417ad..f7f61c2 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Or use grunt's _qunit_ task <code>grunt test</code>. For that you also need to [ 5.To build lib + docs: * run <code>grunt build</code> in **lib** directory -* run <code>build data-docs-dist</code> in **gh-pages** directory +* run <code>build data-docs-dist-zip</code> in **gh-pages** directory You will get distributive in **lib/dist** and updated docs in **gh-pages/*.html**. Do not edit **index.html** and **docs.html** directly! Instead look at [Handlebars](https://github.com/wycats/handlebars.js) templates in **generator/templates**. diff --git a/package.json b/package.json index f7cf0e1..e0700a2 100644 --- a/package.json +++ b/package.json @@ -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.0", + "version": "1.1.1", "homepage": "http://github.com/vitalets/x-editable", "author": { "name": "Vitaliy Potapov", From cb6cf702b666068fd38e3c376bd617e0ccf06471 Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Thu, 29 Nov 2012 19:54:57 +0400 Subject: [PATCH 15/25] html tpl fix in address --- src/inputs-ext/address/address.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/inputs-ext/address/address.js b/src/inputs-ext/address/address.js index 7cc562e..ec144b8 100644 --- a/src/inputs-ext/address/address.js +++ b/src/inputs-ext/address/address.js @@ -37,7 +37,7 @@ $(function(){ value2html: function(value, element) { - var html = value.city + ', st. ' + value.street + ', bld. ' + value.building; + var html = value.city + ', ' + value.street + ' st., bld. ' + value.building; $(element).text(html); }, @@ -99,7 +99,7 @@ $(function(){ '<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>', - inputclass: 'editable-address' + inputclass: 'editable-address' }); $.fn.editableform.types.address = Address; From c3a3553b295bcc08481f100b7fab38126a57bf70 Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Thu, 29 Nov 2012 19:55:10 +0400 Subject: [PATCH 16/25] comments --- src/editable-form/editable-form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editable-form/editable-form.js b/src/editable-form/editable-form.js index d5a14ae..a4b7514 100644 --- a/src/editable-form/editable-form.js +++ b/src/editable-form/editable-form.js @@ -11,7 +11,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. var EditableForm = function (element, options) { this.options = $.extend({}, $.fn.editableform.defaults, options); - this.$element = $(element); //div (usually), containing form. not form tag! + this.$element = $(element); //div, containing form. Not form tag! this.initInput(); }; From 518b2d88b135d91ef3134864596511d28bb5d5db Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Thu, 29 Nov 2012 22:05:40 +0400 Subject: [PATCH 17/25] checklist: replace inArray on loop with non-strict comparison --- src/inputs/checklist.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/inputs/checklist.js b/src/inputs/checklist.js index 73c70b7..7f76b5b 100644 --- a/src/inputs/checklist.js +++ b/src/inputs/checklist.js @@ -71,11 +71,17 @@ $(function(){ var $checks = this.$input.find('input[type="checkbox"]'); $checks.removeAttr('checked'); if($.isArray(value) && value.length) { - $checks.each(function(i, el) { - if($.inArray($(el).val(), value) !== -1) { - $(el).attr('checked', 'checked'); - } - }); + $checks.each(function(i, el) { + var $el = $(el); + // cannot use $.inArray as it performs strict comparison + $.each(value, function(j, val){ + /*jslint eqeq: true*/ + if($el.val() == val) { + /*jslint eqeq: false*/ + $el.attr('checked', 'checked'); + } + }); + }); } }, From 5b8f00d01b62734f5a128d5990dd177b4fd10f19 Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Thu, 29 Nov 2012 22:06:15 +0400 Subject: [PATCH 18/25] added toggle values dblclick and hover --- CHANGELOG.txt | 3 +- src/containers/editable-container.js | 13 +-- src/element/editable-element.js | 45 +++++++---- test/unit/common.js | 114 +++++++++++++++++---------- test/unit/text.js | 12 --- 5 files changed, 108 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index a9260f3..62f3d74 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,9 +4,10 @@ X-editable changelog Version 1.1.1 wip ---------------------------- +[enh] added 'inputs-ext' directory with sample input 'address'. They will not be concatenated to main files (vitalets) [enh #13] 'onblur' option: to cancel, submit or ignore when user clicks outside the form (vitalets) [enh] 'ajaxOptions' parameter for advanced ajax configuration (vitalets) -[enh] success callback can return object to overwrite submitted value (vitalets) +[enh] 'success' callback can return object to overwrite submitted value (vitalets) Version 1.1.0 Nov 27, 2012 diff --git a/src/containers/editable-container.js b/src/containers/editable-container.js index 56d7290..9072aa0 100644 --- a/src/containers/editable-container.js +++ b/src/containers/editable-container.js @@ -159,12 +159,12 @@ Applied as jQuery method. /** Shows container with form @method show() - @param {boolean} multi if true - other editable containers will not be closed. Default false. + @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true. **/ - show: function (multi) { + show: function (closeAll) { this.$element.addClass('editable-open'); - if(!multi) { - //close all open containers (except one) + if(closeAll !== false) { + //close all open containers (except this) this.closeOthers(this.$element[0]); } @@ -207,12 +207,13 @@ 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. **/ - toggle: function() { + toggle: function(closeAll) { if(this.tip && this.tip().is(':visible')) { this.hide(); } else { - this.show(); + this.show(closeAll); } }, diff --git a/src/element/editable-element.js b/src/element/editable-element.js index 6ce4e2d..8b07eb8 100644 --- a/src/element/editable-element.js +++ b/src/element/editable-element.js @@ -57,9 +57,9 @@ Makes editable any HTML element on the page. Applied as jQuery method. this.$element.addClass('editable'); //attach click handler. In disabled mode it just prevent default action (useful for links) - if(this.options.toggle === 'click') { + if(this.options.toggle !== 'manual') { this.$element.addClass('editable-click'); - this.$element.on('click.editable', $.proxy(this.click, this)); + this.$element.on(this.options.toggle + '.editable', $.proxy(this.activate, this)); } else { this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually } @@ -100,7 +100,7 @@ Makes editable any HTML element on the page. Applied as jQuery method. this.options.disabled = false; this.$element.removeClass('editable-disabled'); this.handleEmpty(); - if(this.options.toggle === 'click') { + if(this.options.toggle !== 'manual') { if(this.$element.attr('tabindex') === '-1') { this.$element.removeAttr('tabindex'); } @@ -178,22 +178,34 @@ Makes editable any HTML element on the page. Applied as jQuery method. } }, - click: function (e) { + activate: function (e) { e.preventDefault(); if(this.options.disabled) { return; } - //stop propagation bacause document listen any click to hide all editableContainers + /* + stop propagation not required anymore because in document click handler it checks event target + */ //e.stopPropagation(); - this.toggle(); + + if(this.options.toggle === 'mouseenter') { + //for hover only show container + this.show(); + } else { + /* + if toggle = click we should not close all other containers as they will be closed automatically in document click listener + */ + var closeAll = (this.options.toggle !== 'click'); + this.toggle(closeAll); + } }, /** Shows container with form @method show() - @param {boolean} multi if true - other editable containers will not be closed. Default false. + @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true. **/ - show: function (multi) { + show: function (closeAll) { if(this.options.disabled) { return; } @@ -213,13 +225,9 @@ Makes editable any HTML element on the page. Applied as jQuery method. } else if(this.container.tip().is(':visible')) { return; } - - //hide all other editable containers. Required to work correctly with toggle = manual - //temp - //$('.editable-container').find('.editable-cancel').click(); //show container - this.container.show(multi); + this.container.show(closeAll); }, /** @@ -240,12 +248,13 @@ Makes editable any HTML element on the page. 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. **/ - toggle: function() { + toggle: function(closeAll) { if(this.container && this.container.tip().is(':visible')) { this.hide(); } else { - this.show(); + this.show(closeAll); } }, @@ -462,9 +471,10 @@ Makes editable any HTML element on the page. Applied as jQuery method. **/ disabled: false, /** - How to toggle editable. Can be <code>click|manual</code>. + How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>. When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable. - Note: if you are calling <code>show</code> on **click** event you need to apply <code>e.stopPropagation()</code> because container has behavior to hide on any click outside. + **Note**: if you call <code>show</code> or <code>toggle</code> inside **click** handler of some DOM element, + you need to apply <code>e.stopPropagation()</code> because containers are being closed on any click on document. @example $('#edit-button').click(function(e) { @@ -477,6 +487,7 @@ Makes editable any HTML element on the page. Applied as jQuery method. @default 'click' **/ toggle: 'click', + /** Text shown when element is empty. diff --git a/test/unit/common.js b/test/unit/common.js index 22e8da2..0edd2d8 100644 --- a/test/unit/common.js +++ b/test/unit/common.js @@ -89,39 +89,6 @@ ok(p.find(':contains("'+title+'")').length, 'title ok'); e.remove(); }); - /* - test("should close all other containers on click on editable", function () { - var e1 = $('<a href="#" data-pk="1" data-url="post.php" id="a">abc</a>').appendTo('#qunit-fixture').editable(), - e2 = $('<a href="#" data-pk="1" data-url="post.php" id="b">abcd</a>').appendTo('#qunit-fixture').editable(); - - e1.click() - var p1 = tip(e1); - ok(p1.is(':visible'), 'popover1 visible'); - - e2.click() - var p2 = tip(e2); - ok(p2.is(':visible'), 'popover2 visible'); - ok(!p1.is(':visible'), 'popover1 closed'); - - p2.find('button[type=button]').click(); - ok(!p2.is(':visible'), 'popover2 closed'); - }); - - test("click outside container should hide it", function () { - var e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo('#qunit-fixture').editable(), - e1 = $('<div>').appendTo('body'); - - e.click(); - var p = tip(e); - ok(p.is(':visible'), 'popover shown'); - - p.click(); - ok(p.is(':visible'), 'popover still shown'); - - e1.click(); - ok(!p.is(':visible'), 'popover closed'); - }); - */ test("onblur: cancel", function () { var oldValue = 'abc', @@ -160,7 +127,7 @@ e2.editable('hide'); ok(!p2.is(':visible'), 'popover2 closed'); - //call show method of another editable (multi = false) + //call show method of another editable, closeAll = true (default) e.click(); p = tip(e); ok(p.is(':visible'), 'popover1 visible'); @@ -173,12 +140,12 @@ e2.editable('hide'); ok(!p2.is(':visible'), 'popover2 closed'); - //call show method of another editable (multi = true) + //call show method of another editable, closeAll = false e.click(); p = tip(e); ok(p.is(':visible'), 'popover1 visible'); p.find('input').val(newValue); - e2.editable('show', true); + e2.editable('show', false); p2 = tip(e2); ok(p.is(':visible'), 'popover1 visible'); ok(p2.is(':visible'), 'popover2 visible'); @@ -226,7 +193,7 @@ e2.editable('hide'); ok(!p2.is(':visible'), 'popover2 closed'); - //call show method of another editable (multi = false) + //call show method of another editable, closeAll = true (default) e.click(); p = tip(e); ok(p.is(':visible'), 'popover1 visible'); @@ -239,12 +206,12 @@ e2.editable('hide'); ok(!p2.is(':visible'), 'popover2 closed'); - //call show method of another editable (multi = true) + //call show method of another editable, closeAll = false e.click(); p = tip(e); ok(p.is(':visible'), 'popover1 visible'); p.find('input').val(oldValue); - e2.editable('show', true); + e2.editable('show', false); p2 = tip(e2); ok(p.is(':visible'), 'popover1 visible'); ok(p2.is(':visible'), 'popover2 visible'); @@ -286,7 +253,7 @@ e2.editable('hide'); ok(!p2.is(':visible'), 'popover2 closed'); - //call show method of another editable (multi = false) + //call show method of another editable, closeAll = true (default) e2.editable('show'); p2 = tip(e2); ok(p.is(':visible'), 'popover1 still visible'); @@ -294,8 +261,8 @@ e2.editable('hide'); ok(!p2.is(':visible'), 'popover2 closed'); - //call show method of another editable (multi = true) - e2.editable('show', true); + //call show method of another editable, closeAll = false + e2.editable('show', false); p2 = tip(e2); ok(p.is(':visible'), 'popover1 still visible'); ok(p2.is(':visible'), 'popover2 visible'); @@ -317,7 +284,68 @@ ok(p.find('button').offset().left > p.find('.editable-input').offset().left + p.find('.editable-input').width(), 'buttons left ok'); d.remove(); - }); + }); + + test("toggle: manual", function () { + var e = $('<a href="#" id="a"></a>').appendTo('#qunit-fixture').editable({ + toggle: 'manual' + }); + + e.click(); + ok(!e.data('editableContainer'), 'popover not visible after click'); + e.editable('show'); + var p = tip(e); + ok(p.is(':visible'), 'shown manually'); + }); + + test("toggle: dblclick", function () { + var e = $('<a href="#" id="a"></a>').appendTo('#qunit-fixture').editable({ + toggle: 'dblclick' + }), + p, p2, + e2 = $('<a href="#" data-type="text" data-pk="1" data-url="post.php" id="b">abcd</a>').appendTo('#qunit-fixture').editable(); + + e.click(); + ok(!e.data('editableContainer'), 'popover not visible after click'); + + e2.click(); + p2 = tip(e2); + ok(p2.is(':visible'), 'popover2 visible'); + + e.dblclick(); + p = tip(e); + ok(p.is(':visible'), 'popover1 visible'); + ok(!p2.is(':visible'), 'popover2 closed'); + }); + + test("toggle: mouseenter", function () { + var e = $('<a href="#" id="a"></a>').appendTo('#qunit-fixture').editable({ + toggle: 'mouseenter' + }), + p, p2, + e2 = $('<a href="#" data-type="text" data-pk="1" data-url="post.php" id="b">abcd</a>').appendTo('#qunit-fixture').editable(); + + e.click(); + ok(!e.data('editableContainer'), 'popover not visible after click'); + + e.dblclick(); + ok(!e.data('editableContainer'), 'popover not visible after dblclick'); + + e2.click(); + p2 = tip(e2); + ok(p2.is(':visible'), 'popover2 visible'); + + e.mouseenter(); + ok(e.data('editableContainer'), 'container defined'); + p = tip(e); + ok(p.is(':visible'), 'popover1 visible'); + ok(!p2.is(':visible'), 'popover2 closed'); + + //hover once again --> container should stay open + e.hover(); + p = tip(e); + ok(p.is(':visible'), 'popover1 visible after second hover'); + }); //unfortunatly, testing this feature does not always work in browsers. Tested manually. /* diff --git a/test/unit/text.js b/test/unit/text.js index 8e56652..f7fc3e9 100644 --- a/test/unit/text.js +++ b/test/unit/text.js @@ -39,18 +39,6 @@ $(function () { p.find('button[type=button]').click(); ok(!p.is(':visible'), 'popover was removed'); }); - - test("option 'toggle' = manual", function () { - var e = $('<a href="#" id="a"></a>').appendTo('#qunit-fixture').editable({ - toggle: 'manual' - }); - - e.click(); - ok(!e.data().editableContainer, 'popover not visible after click'); - e.editable('show'); - var p = tip(e); - ok(p.is(':visible'), 'shown manually'); - }); asyncTest("should load correct value and save new entered text (and value)", function () { var v = 'ab<b>"', From 3dc331cf5bb26a4a13510bdda94ba8fdf10c39fc Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Fri, 30 Nov 2012 14:20:18 +0400 Subject: [PATCH 19/25] pass object in option method --- CHANGELOG.txt | 2 ++ src/element/editable-element.js | 31 +++++++++++++++++++++++++------ test/unit/api.js | 8 +++++++- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 62f3d74..7c17193 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,6 +4,8 @@ X-editable changelog Version 1.1.1 wip ---------------------------- +[enh] object can be passed in 'option' method to set several options simultaneously (vitalets) +[enh #20] toggle editable by 'dblclick' and 'mouseenter' (vitalets) [enh] added 'inputs-ext' directory with sample input 'address'. They will not be concatenated to main files (vitalets) [enh #13] 'onblur' option: to cancel, submit or ignore when user clicks outside the form (vitalets) [enh] 'ajaxOptions' parameter for advanced ajax configuration (vitalets) diff --git a/src/element/editable-element.js b/src/element/editable-element.js index 8b07eb8..4d91d58 100644 --- a/src/element/editable-element.js +++ b/src/element/editable-element.js @@ -136,10 +136,24 @@ Makes editable any HTML element on the page. Applied as jQuery method. Sets new option @method option(key, value) - @param {string} key - @param {mixed} value + @param {string|object} key option name or object with several options + @param {mixed} value option new value + @example + $('.editable').editable('option', 'pk', 2); **/ option: function(key, value) { + //set option(s) by object + if(key && typeof key === 'object') { + $.each(key, $.proxy(function(k, v){ + this.option($.trim(k), v); + }, this)); + return; + } + + //set option by string + this.options[key] = value; + + //disabled if(key === 'disabled') { if(value) { this.disable(); @@ -148,12 +162,15 @@ Makes editable any HTML element on the page. Applied as jQuery method. } return; } - - this.options[key] = value; + + //value + if(key === 'value') { + this.setValue(value); + } //transfer new option to container! if(this.container) { - this.container.option(key, value); + this.container.option(key, value); } }, @@ -177,7 +194,9 @@ Makes editable any HTML element on the page. Applied as jQuery method. } } }, - + /* + show / hide editable container when element triggers event defined by toggle option + */ activate: function (e) { e.preventDefault(); if(this.options.disabled) { diff --git a/test/unit/api.js b/test/unit/api.js index 313e682..47f6ca2 100644 --- a/test/unit/api.js +++ b/test/unit/api.js @@ -287,7 +287,7 @@ $(function () { }); - test("option method", function () { + test("option method (string and object)", function () { var e = $('<a href="#" data-url="post.php" data-name="text">abc</a>').appendTo('#qunit-fixture').editable(), e1 = $('<a href="#" data-pk="1" data-name="text1">abc</a>').appendTo('#qunit-fixture').editable(), url = 'abc'; @@ -296,6 +296,12 @@ $(function () { equal(e.data('editable').options.pk, 2, 'pk set correctly'); equal(e1.data('editable').options.pk, 2, 'pk2 set correctly'); + + $('#qunit-fixture a').editable('option', {pk: 3, value: 'abcd'}); + + equal(e.data('editable').options.pk, 3, 'pk set correctly (by object)'); + equal(e.data('editable').value, 'abcd', 'value set correctly (by object)'); + equal(e.text(), 'abcd', 'text set correctly (by object)'); }); asyncTest("'submit' method: client and server validation", function () { From de7907575ae26d250cb6bad5b4452e8d02b7975e Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Fri, 30 Nov 2012 16:33:54 +0400 Subject: [PATCH 20/25] showbuttons option ready --- CHANGELOG.txt | 3 +- README.md | 4 +-- src/editable-form/editable-form-bootstrap.js | 7 ++-- src/editable-form/editable-form-jqueryui.js | 6 +--- src/editable-form/editable-form.js | 35 +++++++++++++++++--- test/unit/common.js | 15 ++++++++- 6 files changed, 51 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 7c17193..035888d 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,7 +4,8 @@ X-editable changelog Version 1.1.1 wip ---------------------------- -[enh] object can be passed in 'option' method to set several options simultaneously (vitalets) +[enh] 'showbuttons' option to hide buttons in form (vitalets) +[enh] object can be passed in 'option' method to set several options at once (vitalets) [enh #20] toggle editable by 'dblclick' and 'mouseenter' (vitalets) [enh] added 'inputs-ext' directory with sample input 'address'. They will not be concatenated to main files (vitalets) [enh #13] 'onblur' option: to cancel, submit or ignore when user clicks outside the form (vitalets) diff --git a/README.md b/README.md index f7f61c2..aa774c1 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,11 @@ Or use grunt's _qunit_ task <code>grunt test</code>. For that you also need to [ 5.To build lib + docs: * run <code>grunt build</code> in **lib** directory -* run <code>build data-docs-dist-zip</code> in **gh-pages** directory +* run <code>build data-docs-dist</code> in **gh-pages** directory You will get distributive in **lib/dist** and updated docs in **gh-pages/*.html**. Do not edit **index.html** and **docs.html** directly! Instead look at [Handlebars](https://github.com/wycats/handlebars.js) templates in **generator/templates**. -6.Commit changes on <code>dev</code> branch and make pull request as usual. +6.Commit changes on <code>dev</code> / <code>gh-pages-dev</code> branch and make pull request as usual. Thanks for your support! diff --git a/src/editable-form/editable-form-bootstrap.js b/src/editable-form/editable-form-bootstrap.js index 50f72bb..3388544 100644 --- a/src/editable-form/editable-form-bootstrap.js +++ b/src/editable-form/editable-form-bootstrap.js @@ -5,11 +5,8 @@ Editableform based on Twitter Bootstrap $.extend($.fn.editableform.Constructor.prototype, { initTemplate: function() { - this.$form = $($.fn.editableform.template); - this.$form.find('.editable-error-block').addClass('help-block'); - - //buttons - this.$form.find('div.editable-buttons').append($.fn.editableform.buttons); + this.$form = $($.fn.editableform.template); + this.$form.find('.editable-error-block').addClass('help-block'); } }); diff --git a/src/editable-form/editable-form-jqueryui.js b/src/editable-form/editable-form-jqueryui.js index 804b6d2..9f0ea9e 100644 --- a/src/editable-form/editable-form-jqueryui.js +++ b/src/editable-form/editable-form-jqueryui.js @@ -4,10 +4,7 @@ Editableform based on jQuery UI (function ($) { $.extend($.fn.editableform.Constructor.prototype, { - initTemplate: function() { - this.$form = $($.fn.editableform.template); - - //buttons + initButtons: function() { this.$form.find('.editable-buttons').append($.fn.editableform.buttons); this.$form.find('.editable-submit').button({ icons: { primary: "ui-icon-check" }, @@ -17,7 +14,6 @@ Editableform based on jQuery UI icons: { primary: "ui-icon-closethick" }, text: false }).removeAttr('title'); - } }); diff --git a/src/editable-form/editable-form.js b/src/editable-form/editable-form.js index a4b7514..7373ec7 100644 --- a/src/editable-form/editable-form.js +++ b/src/editable-form/editable-form.js @@ -34,9 +34,9 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. }, initTemplate: function() { this.$form = $($.fn.editableform.template); - - //buttons - this.$form.find('div.editable-buttons').append($.fn.editableform.buttons); + }, + initButtons: function() { + this.$form.find('.editable-buttons').append($.fn.editableform.buttons); }, /** Renders editableform @@ -47,8 +47,14 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. this.$loading = $($.fn.editableform.loading); this.$element.empty().append(this.$loading); this.showLoading(); - + + //init form template and buttons this.initTemplate(); + if(this.options.showbuttons) { + this.initButtons(); + } else { + this.$form.find('.editable-buttons').remove(); + } /** Fired when rendering starts @@ -431,7 +437,26 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. @type object @default null **/ - ajaxOptions: null + ajaxOptions: null, + /** + Wether to show buttons or not. + Form without buttons can be submitted by Enter (depends on input), by onblur = 'submit' or manually (via submit api method) + + @property showbuttons + @type boolean + @default true + **/ + showbuttons: true, + /** + Submit strategy. Can be <code>normal|never</code> + <code>submit='never'</code> usefull for turning into classic form several inputs and submitting them together manually. + Works pretty with <code>showbuttons=false</code> + + @property submit + @type string + @default normal + **/ + submit: 'normal' }; /* diff --git a/test/unit/common.js b/test/unit/common.js index 0edd2d8..61e6658 100644 --- a/test/unit/common.js +++ b/test/unit/common.js @@ -345,7 +345,20 @@ e.hover(); p = tip(e); ok(p.is(':visible'), 'popover1 visible after second hover'); - }); + }); + + test("showbuttons: false", function () { + var e = $('<a href="#" id="a"></a>').appendTo('#qunit-fixture').editable({ + showbuttons: false + }); + + e.click(); + var p = tip(e); + ok(p.is(':visible'), 'popover visible'); + ok(!p.find('.editable-submit').length, 'submit not rendered'); + ok(!p.find('.editable-cancel').length, 'cancel not rendered'); + ok(!p.find('.editable-buttons').length, '.editable-buttons block not rendered'); + }); //unfortunatly, testing this feature does not always work in browsers. Tested manually. /* From 49d795bf0d1761f5d0a56904f6555279a691c424 Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Fri, 30 Nov 2012 18:48:20 +0400 Subject: [PATCH 21/25] activate method --- src/containers/editable-container.js | 10 ++++++ src/element/editable-element.js | 53 ++++++++++++++-------------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/containers/editable-container.js b/src/containers/editable-container.js index 9072aa0..864f800 100644 --- a/src/containers/editable-container.js +++ b/src/containers/editable-container.js @@ -322,6 +322,16 @@ Applied as jQuery method. } }); + }, + + /** + Activates input of visible container (e.g. set focus) + @method activate() + **/ + activate: function() { + if(this.tip && this.tip().is(':visible') && this.$form) { + this.$form.data('editableform').input.activate(); + } } }; diff --git a/src/element/editable-element.js b/src/element/editable-element.js index 4d91d58..0e2cfe5 100644 --- a/src/element/editable-element.js +++ b/src/element/editable-element.js @@ -56,10 +56,23 @@ Makes editable any HTML element on the page. Applied as jQuery method. //add 'editable' class this.$element.addClass('editable'); - //attach click handler. In disabled mode it just prevent default action (useful for links) + //attach handler activating editable. In disabled mode it just prevent default action (useful for links) if(this.options.toggle !== 'manual') { this.$element.addClass('editable-click'); - this.$element.on(this.options.toggle + '.editable', $.proxy(this.activate, this)); + this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){ + e.preventDefault(); + //stop propagation not required anymore because in document click handler it checks event target + //e.stopPropagation(); + + if(this.options.toggle === 'mouseenter') { + //for hover only show container + this.show(); + } else { + //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener + var closeAll = (this.options.toggle !== 'click'); + this.toggle(closeAll); + } + }, this)); } else { this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually } @@ -194,30 +207,6 @@ Makes editable any HTML element on the page. Applied as jQuery method. } } }, - /* - show / hide editable container when element triggers event defined by toggle option - */ - activate: function (e) { - e.preventDefault(); - if(this.options.disabled) { - return; - } - /* - stop propagation not required anymore because in document click handler it checks event target - */ - //e.stopPropagation(); - - if(this.options.toggle === 'mouseenter') { - //for hover only show container - this.show(); - } else { - /* - if toggle = click we should not close all other containers as they will be closed automatically in document click listener - */ - var closeAll = (this.options.toggle !== 'click'); - this.toggle(closeAll); - } - }, /** Shows container with form @@ -339,7 +328,17 @@ Makes editable any HTML element on the page. Applied as jQuery method. this.handleEmpty(); this.$element.triggerHandler('render', this); }, this)); - } + }, + + /** + Activates input of visible container (e.g. set focus) + @method activate() + **/ + activate: function() { + if(this.container) { + this.container.activate(); + } + } }; /* EDITABLE PLUGIN DEFINITION From ed836c22711209a57c2bbd660d06bd40aeb5e48b Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Fri, 30 Nov 2012 18:48:42 +0400 Subject: [PATCH 22/25] autosubmit method --- src/editable-form/editable-form.js | 35 +++++++++++++++++------------- src/inputs/abstract.js | 9 +++++++- src/inputs/checklist.js | 10 ++++++++- src/inputs/date/date.js | 11 +++++++++- src/inputs/dateui/dateui.js | 11 +++++++++- src/inputs/select.js | 8 ++++++- 6 files changed, 64 insertions(+), 20 deletions(-) diff --git a/src/editable-form/editable-form.js b/src/editable-form/editable-form.js index 7373ec7..12ae191 100644 --- a/src/editable-form/editable-form.js +++ b/src/editable-form/editable-form.js @@ -11,7 +11,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. var EditableForm = function (element, options) { this.options = $.extend({}, $.fn.editableform.defaults, options); - this.$element = $(element); //div, containing form. Not form tag! + this.$element = $(element); //div, containing form. Not form tag! Not editable-element. this.initInput(); }; @@ -69,6 +69,11 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. //input this.$form.find('div.editable-input').append(this.input.$input); + //automatically submit inputs when no buttons shown + if(!this.options.showbuttons) { + this.input.autosubmit(); + } + //"clear" link if(this.input.$clear) { this.$form.find('div.editable-input').append($('<div class="editable-clear">').append(this.input.$clear)); @@ -156,11 +161,10 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. submit: function(e) { e.stopPropagation(); e.preventDefault(); - + var error, - //get value from input - newValue = this.input.input2value(), - newValueStr; + newValue = this.input.input2value(), //get new value from input + newValueStr; //validation if (error = this.validate(newValue)) { @@ -168,14 +172,14 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. this.showForm(); return; } - + //value as string newValueStr = this.input.value2str(newValue); //if value not changed --> cancel /*jslint eqeq: true*/ if (newValueStr == this.input.value2str(this.value)) { - /*jslint eqeq: false*/ + /*jslint eqeq: false*/ this.cancel(); return; } @@ -186,14 +190,14 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. //run success callback var res = typeof this.options.success === 'function' ? this.options.success.call(this, response, newValue) : null; - //if it returns string --> show error + //if success callback returns string --> show error if(res && typeof res === 'string') { this.error(res); this.showForm(); return; } - //if it returns object like {newValue: <something>} --> use that value + //if success callback returns object like {newValue: <something>} --> use that value instead of submitted if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) { newValue = res.newValue; } @@ -446,17 +450,18 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. @type boolean @default true **/ - showbuttons: true, - /** + showbuttons: false + + /*todo: Submit strategy. Can be <code>normal|never</code> - <code>submit='never'</code> usefull for turning into classic form several inputs and submitting them together manually. + <code>submitmode='never'</code> usefull for turning into classic form several inputs and submitting them together manually. Works pretty with <code>showbuttons=false</code> - @property submit + @property submitmode @type string @default normal - **/ - submit: 'normal' + */ +// submitmode: 'normal' }; /* diff --git a/src/inputs/abstract.js b/src/inputs/abstract.js index fe7ed84..0da5ddb 100644 --- a/src/inputs/abstract.js +++ b/src/inputs/abstract.js @@ -127,7 +127,14 @@ To create your own input you should inherit from this class. **/ escape: function(str) { return $('<div>').text(str).html(); - } + }, + + /** + attach handler to automatically submit form when value changed (usefull when buttons not shown) + **/ + autosubmit: function() { + + } }; Abstract.defaults = { diff --git a/src/inputs/checklist.js b/src/inputs/checklist.js index 7f76b5b..60af4ce 100644 --- a/src/inputs/checklist.js +++ b/src/inputs/checklist.js @@ -112,7 +112,15 @@ $(function(){ activate: function() { this.$input.find('input[type="checkbox"]').first().focus(); - } + }, + + autosubmit: function() { + this.$input.find('input[type="checkbox"]').on('keydown', function(e){ + if (e.which === 13) { + $(this).closest('form').submit(); + } + }); + } }); Checklist.defaults = $.extend({}, $.fn.editableform.types.list.defaults, { diff --git a/src/inputs/date/date.js b/src/inputs/date/date.js index 6490021..2fb8eab 100644 --- a/src/inputs/date/date.js +++ b/src/inputs/date/date.js @@ -95,7 +95,16 @@ $(function(){ clear: function() { this.$input.data('datepicker').date = null; this.$input.find('.active').removeClass('active'); - } + }, + + autosubmit: function() { + this.$input.on('changeDate', function(e){ + var $form = $(this).closest('form'); + setTimeout(function() { + $form.submit(); + }, 200); + }); + } }); diff --git a/src/inputs/dateui/dateui.js b/src/inputs/dateui/dateui.js index 5b39f27..a8978dc 100644 --- a/src/inputs/dateui/dateui.js +++ b/src/inputs/dateui/dateui.js @@ -112,7 +112,16 @@ $(function(){ clear: function() { this.$input.datepicker('setDate', null); - } + }, + + autosubmit: function() { + this.$input.on('mouseup', 'table.ui-datepicker-calendar a.ui-state-default', function(e){ + var $form = $(this).closest('form'); + setTimeout(function() { + $form.submit(); + }, 200); + }); + } }); diff --git a/src/inputs/select.js b/src/inputs/select.js index 9497ae1..42ffc77 100644 --- a/src/inputs/select.js +++ b/src/inputs/select.js @@ -45,7 +45,13 @@ $(function(){ text = item.text; } Select.superclass.constructor.superclass.value2html(text, element); - } + }, + + autosubmit: function() { + this.$input.on('change', function(){ + $(this).closest('form').submit(); + }); + } }); Select.defaults = $.extend({}, $.fn.editableform.types.list.defaults, { From a62afc9180d596014a34f6005a420f38ba6c7a7e Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Fri, 30 Nov 2012 19:09:44 +0400 Subject: [PATCH 23/25] autosubmit select test + document click tiny refactor --- src/containers/editable-container.js | 52 ++++------------------------ src/editable-form/editable-form.js | 2 +- test/unit/common.js | 8 +++-- test/unit/select.js | 25 +++++++++++++ 4 files changed, 37 insertions(+), 50 deletions(-) diff --git a/src/containers/editable-container.js b/src/containers/editable-container.js index 864f800..651eb22 100644 --- a/src/containers/editable-container.js +++ b/src/containers/editable-container.js @@ -41,55 +41,15 @@ Applied as jQuery method. //close containers when click outside $(document).on('click.editable', function(e) { - var element = e.target, - $target = $(e.target), - $clickedContainer = $target.is('.editable-container') ? $target : $target.closest('.editable-container'); + var $target = $(e.target); - //if click inside some editableContainer --> find corresponding element - if($clickedContainer.length) { - $('.editable-open').each(function(i, el){ - if($(el).data('editableContainer').tip()[0] === $clickedContainer[0]) { - element = el; - return false; - } - }); - } - - //close all open containers (except one) - EditableContainer.prototype.closeOthers(element); - - /* $('.editable-open').each(function(){ - //if click target is editable element --> do nothing with el - if(this === e.target) { - return; - } - - var $el = $(this), - ec = $el.data('editableContainer'); - - //if click in some editableContainer and current el is it's owner --> do nothing with el - if($clickedContainer.length && ec.tip()[0] === $clickedContainer[0]) { - return; - } - - //otherwise cancel or submit el's container - if(ec.options.onblur === 'cancel') { - $el.data('editableContainer').hide(); - } else if(ec.options.onblur === 'submit') { - $el.data('editableContainer').tip().find('form').submit(); - } - }); */ - - //if click inside container --> do nothing - // if($target.is('.editable-container') || $target.parents('.editable-container').length || $target.parents('.ui-datepicker-header').length) { - /* - if($target.is('.editable-container') || $target.parents('.editable-container').length || $target.parents('.ui-datepicker-header').length) { + //if click inside some editableContainer --> no nothing + if($target.is('.editable-container') || $target.parents('.editable-container').length || $target.parents('.ui-datepicker-header').length) { return; + } else { + //close all open containers (except one) + EditableContainer.prototype.closeOthers(e.target); } - - //close all other containers - $('.editable-container').find('.editable-cancel').click(); - */ }); $(document).data('editable-handlers-attached', true); diff --git a/src/editable-form/editable-form.js b/src/editable-form/editable-form.js index 12ae191..dcf661f 100644 --- a/src/editable-form/editable-form.js +++ b/src/editable-form/editable-form.js @@ -450,7 +450,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. @type boolean @default true **/ - showbuttons: false + showbuttons: true /*todo: Submit strategy. Can be <code>normal|never</code> diff --git a/test/unit/common.js b/test/unit/common.js index 61e6658..a5e6eb6 100644 --- a/test/unit/common.js +++ b/test/unit/common.js @@ -276,7 +276,9 @@ test("should not wrap buttons when parent has position:absolute", function () { var d = $('<div style="position: absolute; top: 200px">').appendTo(fx), - e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo(d).editable(); + e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo(d).editable({ + showbuttons: true + }); e.click(); var p = tip(e); @@ -348,7 +350,7 @@ }); test("showbuttons: false", function () { - var e = $('<a href="#" id="a"></a>').appendTo('#qunit-fixture').editable({ + var e = $('<a href="#" id="a" data-type="text"></a>').appendTo('#qunit-fixture').editable({ showbuttons: false }); @@ -357,7 +359,7 @@ ok(p.is(':visible'), 'popover visible'); ok(!p.find('.editable-submit').length, 'submit not rendered'); ok(!p.find('.editable-cancel').length, 'cancel not rendered'); - ok(!p.find('.editable-buttons').length, '.editable-buttons block not rendered'); + ok(!p.find('.editable-buttons').length, '.editable-buttons block not rendered'); }); //unfortunatly, testing this feature does not always work in browsers. Tested manually. diff --git a/test/unit/select.js b/test/unit/select.js index b4b969f..05d37b3 100644 --- a/test/unit/select.js +++ b/test/unit/select.js @@ -450,5 +450,30 @@ $(function () { start(); }, timeout); }); + + asyncTest("autosubmit when showbuttons=false", function () { + expect(4); + var e = $('<a href="#" data-type="select" data-value="2" data-url="post.php">customer</a>').appendTo(fx).editable({ + pk: 1, + source: groups, + showbuttons: false + }), + selected = 3; + + e.click(); + var p = tip(e); + equal(p.find('select').val(), e.data('editable').value, 'selected value correct'); + + p.find('select').val(selected); + p.find('select').trigger('change'); + + 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); + }); }); \ No newline at end of file From 8df731036a3be2589bc78e1d43ff36a00e47e952 Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Fri, 30 Nov 2012 19:27:23 +0400 Subject: [PATCH 24/25] comments for docs --- src/containers/editable-container.js | 6 +++--- src/editable-form/editable-form.js | 4 ++-- src/element/editable-element.js | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/containers/editable-container.js b/src/containers/editable-container.js index 651eb22..0029eba 100644 --- a/src/containers/editable-container.js +++ b/src/containers/editable-container.js @@ -359,12 +359,12 @@ Applied as jQuery method. **/ autohide: true, /** - Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code> - Setting <code>ignore</code> ignore allows to have several containers open. + Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>. + Setting <code>ignore</code> allows to have several containers open. @property onblur @type string - @default cancel + @default 'cancel' **/ onblur: 'cancel' }; diff --git a/src/editable-form/editable-form.js b/src/editable-form/editable-form.js index dcf661f..a2db423 100644 --- a/src/editable-form/editable-form.js +++ b/src/editable-form/editable-form.js @@ -443,8 +443,8 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'. **/ ajaxOptions: null, /** - Wether to show buttons or not. - Form without buttons can be submitted by Enter (depends on input), by onblur = 'submit' or manually (via submit api method) + Wether to show buttons or not. + Form without buttons can be auto-submitted by input or by onblur = 'submit'. @property showbuttons @type boolean diff --git a/src/element/editable-element.js b/src/element/editable-element.js index 0e2cfe5..bd23548 100644 --- a/src/element/editable-element.js +++ b/src/element/editable-element.js @@ -473,7 +473,7 @@ Makes editable any HTML element on the page. Applied as jQuery method. $.fn.editable.defaults = { /** - Type of input. Can be <code>text|textarea|select|date</code> + Type of input. Can be <code>text|textarea|select|date|checklist</code> and more @property type @type string @@ -489,8 +489,8 @@ Makes editable any HTML element on the page. Applied as jQuery method. **/ disabled: false, /** - How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>. - When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable. + How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>. + When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable. **Note**: if you call <code>show</code> or <code>toggle</code> inside **click** handler of some DOM element, you need to apply <code>e.stopPropagation()</code> because containers are being closed on any click on document. @@ -535,7 +535,7 @@ Makes editable any HTML element on the page. Applied as jQuery method. **/ enablefocus: false, /** - Initial value of input + Initial value of input. Taken from <code>data-value</code> or element's text. @property value @type mixed From 0c5b09fcddadf6acfdb1691914129bb9156926ba Mon Sep 17 00:00:00 2001 From: vitalets <noginsk@rambler.ru> Date: Fri, 30 Nov 2012 19:28:50 +0400 Subject: [PATCH 25/25] changelog ready --- CHANGELOG.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 035888d..6498081 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -2,7 +2,7 @@ X-editable changelog ============================= -Version 1.1.1 wip +Version 1.1.1 Nov 30, 2012 ---------------------------- [enh] 'showbuttons' option to hide buttons in form (vitalets) [enh] object can be passed in 'option' method to set several options at once (vitalets)