From 00709fcc4651156a7fa5d5eed4600236928cbdac Mon Sep 17 00:00:00 2001
From: vitalets <noginsk@rambler.ru>
Date: Mon, 4 Nov 2013 17:53:13 +0400
Subject: [PATCH] submit single field, fix #371

---
 CHANGELOG.txt                   |  1 +
 src/element/editable-element.js | 93 +++++++++++++++++++++++++--------
 test/unit/api.js                | 53 ++++++++++++++++++-
 3 files changed, 123 insertions(+), 24 deletions(-)

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index bc7b25e..04a64f0 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -4,6 +4,7 @@ X-editable changelog
 
 Version 1.5.1 wip
 ----------------------------
+[enh #371] submit single field (vitalets)
 [bug] select2: placeholder not shown if value initially empty (vitalets)
 [enh #400] allow `validate` to change submitted value, also fix #354 (vitalets)
 [enh #396] bs3 popover: placement `auto` (vitalets)
diff --git a/src/element/editable-element.js b/src/element/editable-element.js
index c80b0de..698fff3 100644
--- a/src/element/editable-element.js
+++ b/src/element/editable-element.js
@@ -567,7 +567,9 @@ Makes editable any HTML element on the page. Applied as jQuery method.
             /**
             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.
+            See <a href="#newrecord">creating new records</a> for details.  
+            Since 1.5.1 `submit` can be applied to single element to send data programmatically. In that case
+            `url`, `success` and `error` is taken from initial options and you can just call `$('#username').editable('submit')`. 
             
             @method submit(options)
             @param {object} options 
@@ -581,31 +583,76 @@ Makes editable any HTML element on the page. Applied as jQuery method.
             case 'submit':  //collects value, validate and submit to server for creating new record
                 var config = arguments[1] || {},
                 $elems = this,
-                errors = this.editable('validate'),
-                values;
+                errors = this.editable('validate');
 
+                // validation ok
                 if($.isEmptyObject(errors)) {
-                    values = this.editable('getValue'); 
-                    if(config.data) {
-                        $.extend(values, config.data);
-                    }                    
-                    
-                    $.ajax($.extend({
-                        url: config.url, 
-                        data: values, 
-                        type: 'POST'                        
-                    }, config.ajaxOptions))
-                    .success(function(response) {
-                        //successful response 200 OK
-                        if(typeof config.success === 'function') {
-                            config.success.call($elems, response, config);
-                        } 
-                    })
-                    .error(function(){  //ajax error
-                        if(typeof config.error === 'function') {
-                            config.error.apply($elems, arguments);
+                    var ajaxOptions = {};
+                                                      
+                    // for single element use url, success etc from options
+                    if($elems.length === 1) {
+                        var editable = $elems.data('editable');
+                        //standard params
+                        var params = {
+                            name: editable.options.name || '',
+                            value: editable.input.value2submit(editable.value),
+                            pk: (typeof editable.options.pk === 'function') ? 
+                                editable.options.pk.call(editable.options.scope) : 
+                                editable.options.pk 
+                        };
+
+                        //additional params
+                        if(typeof editable.options.params === 'function') {
+                            params = editable.options.params.call(editable.options.scope, params);  
+                        } else {
+                            //try parse json in single quotes (from data-params attribute)
+                            editable.options.params = $.fn.editableutils.tryParseJson(editable.options.params, true);   
+                            $.extend(params, editable.options.params);
                         }
-                    });
+
+                        ajaxOptions = {
+                            url: editable.options.url,
+                            data: params,
+                            type: 'POST'  
+                        };
+                        
+                        // use success / error from options 
+                        config.success = config.success || editable.options.success;
+                        config.error = config.error || editable.options.error;
+                        
+                    // multiple elements
+                    } else {
+                        var values = this.editable('getValue'); 
+                        
+                        ajaxOptions = {
+                            url: config.url,
+                            data: values, 
+                            type: 'POST'
+                        };                        
+                    }                    
+
+                    // ajax success callabck (response 200 OK)
+                    ajaxOptions.success = typeof config.success === 'function' ? function(response) {
+                            config.success.call($elems, response, config);
+                        } : $.noop;
+                                  
+                    // ajax error callabck
+                    ajaxOptions.error = typeof config.error === 'function' ? function() {
+                             config.error.apply($elems, arguments);
+                        } : $.noop;
+                       
+                    // extend ajaxOptions    
+                    if(config.ajaxOptions) { 
+                        $.extend(ajaxOptions, config.ajaxOptions);
+                    }
+                    
+                    // extra data 
+                    if(config.data) {
+                        $.extend(ajaxOptions.data, config.data);
+                    }                     
+                    
+                    // perform ajax request
+                    $.ajax(ajaxOptions);
                 } else { //client-side validation error
                     if(typeof config.error === 'function') {
                         config.error.call($elems, errors);
diff --git a/test/unit/api.js b/test/unit/api.js
index 21298e3..b1bf585 100644
--- a/test/unit/api.js
+++ b/test/unit/api.js
@@ -369,7 +369,7 @@ $(function () {
         
      });       
      
-     asyncTest("'submit' method: success", function () {
+     asyncTest("'submit' method: success (multiple elems)", function () {
         var ev1 = 'ev1',
             e1v = 'e1v',
             pk = 123,
@@ -403,6 +403,57 @@ $(function () {
         
      }); 
      
+     asyncTest("'submit' method: success (single elem)", function () {
+        expect(5);
+        
+        var ev1 = 'ev1',
+            pk = 123,
+            e = $('<a href="#" class="new" data-type="text" data-pk="'+pk+'" data-url="submit-single" data-name="text">'+ev1+'</a>').appendTo(fx).editable({
+               success: function(data) {
+                   equal(data, 'response-body', 'response body ok');
+               }             
+            });
+
+        $.mockjax({
+            url: 'submit-single',
+            response: function(settings) {
+                equal(settings.data.name, 'text', 'name ok');
+                equal(settings.data.pk, pk, 'pk ok');
+                equal(settings.data.value, ev1, 'value ok');
+                equal(settings.data.a, 1, 'extra data ok');
+                this.responseText = 'response-body';  
+            }
+        });            
+            
+       $(fx).find('.new').editable('submit', {
+           data: {a: 1}           
+       });
+       
+       setTimeout(function() {
+           e.remove();
+           start();
+       }, timeout);
+        
+     });  
+     
+     asyncTest("'submit' method: error (single elem)", function () {
+        expect(1);
+        
+        var e = $('<a href="#" class="new" data-type="text" data-pk="123" data-url="error.php" data-name="text">text</a>').appendTo(fx).editable();
+
+       $(fx).find('.new').editable('submit', {
+           error: function() {
+               equal(this[0], e[0], 'error called in correct scope');
+           }
+       });
+       
+       setTimeout(function() {
+           e.remove();
+           start();
+       }, timeout);
+        
+     });           
+     
      
      test("setValue method", function () {
         var e = $('<a href="#" data-name="name" data-type="select" data-url="post.php"></a>').appendTo('#qunit-fixture').editable({