From c77ebf6cc773e829c1fbccfc8ebd85186ee3c21e Mon Sep 17 00:00:00 2001
From: vitalets <noginsk@rambler.ru>
Date: Sat, 2 Nov 2013 22:28:45 +0400
Subject: [PATCH] select2: show placeholder when value is empty

---
 CHANGELOG.txt                 |  1 +
 src/inputs/select2/select2.js | 98 +++++++++++++++++++----------------
 test/unit/select2.js          | 19 ++++++-
 3 files changed, 72 insertions(+), 46 deletions(-)

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 197f5fc..bc7b25e 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -4,6 +4,7 @@ X-editable changelog
 
 Version 1.5.1 wip
 ----------------------------
+[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)
 [bug #357] select2: tags mode with space separator (vitalets)
diff --git a/src/inputs/select2/select2.js b/src/inputs/select2/select2.js
index 40f8e87..be4c6d5 100644
--- a/src/inputs/select2/select2.js
+++ b/src/inputs/select2/select2.js
@@ -38,7 +38,11 @@ $(function(){
     });
     //remote source (simple)
     $('#country').editable({
-        source: '/getCountries'  
+        source: '/getCountries',
+        select2: {
+            placeholder: 'Select Country',
+            minimumInputLength: 1
+        }
     });
     //remote source (advanced)
     $('#country').editable({
@@ -114,14 +118,14 @@ $(function(){
                 options.select2.data = this.sourceData;
             }
         } 
-           
+
         //overriding objects in config (as by default jQuery extend() is not recursive)
         this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2);
-        
+
         //detect whether it is multi-valued
         this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
         this.isRemote = ('ajax' in this.options.select2);
-        
+
         //store function returning ID of item
         //should be here as used inautotext for local source
         this.idFunc = this.options.select2.id;
@@ -129,12 +133,12 @@ $(function(){
             var idKey = this.idFunc || 'id';
             this.idFunc = function (e) { return e[idKey]; };
         }
-        
+
         //store function that renders text in select2
-        this.formatSelection = this.options.select2.formatSelection; 
+        this.formatSelection = this.options.select2.formatSelection;
         if (typeof(this.formatSelection) !== "function") {
             this.formatSelection = function (e) { return e.text; };
-        }       
+        }
     };
 
     $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
@@ -156,49 +160,49 @@ $(function(){
                 }, this));
             }
 
-            //trigger resize of editableform to re-position container in multi-valued mode           
+            //trigger resize of editableform to re-position container in multi-valued mode
             if(this.isMultiple) {
                this.$input.on('change', function() {
                    $(this).closest('form').parent().triggerHandler('resize');
-               }); 
+               });
             }
        },
-       
+
        value2html: function(value, element) {
            var text = '', data,
                that = this;
-           
+
            if(this.options.select2.tags) { //in tags mode just assign value
               data = value; 
-              //data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc); 
+              //data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc);
            } else if(this.sourceData) {
               data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc); 
            } else {
               //can not get list of possible values 
-              //(e.g. autotext for select2 with ajax source) 
+              //(e.g. autotext for select2 with ajax source)
            }
-           
-           //data may be array (when multiple values allowed)          
+
+           //data may be array (when multiple values allowed)
            if($.isArray(data)) {
                //collect selected data and show with separator
                text = [];
                $.each(data, function(k, v){
-                   text.push(v && typeof v === 'object' ? that.formatSelection(v) : v); 
-               });                   
+                   text.push(v && typeof v === 'object' ? that.formatSelection(v) : v);
+               });
            } else if(data) {
-               text = that.formatSelection(data);  
+               text = that.formatSelection(data);
            }
 
            text = $.isArray(text) ? text.join(this.options.viewseparator) : text;
 
            //$(element).text(text);
            Constructor.superclass.value2html.call(this, text, element); 
-       },       
-        
+       },
+
        html2value: function(html) {
            return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;
-       }, 
-       
+       },
+
        value2input: function(value) {
            // if value array => join it anyway
            if($.isArray(value)) {
@@ -216,19 +220,23 @@ $(function(){
                //Uncaught Error: cannot call val() if initSelection() is not defined
                //this.$input.select2('val', value);
            }
-           
+
            // if defined remote source AND no multiple mode AND no user's initSelection provided --> 
            // we should somehow get text for provided id.
-           // The solution is to use element's text as text for that id
+           // The solution is to use element's text as text for that id (exclude empty)
            if(this.isRemote && !this.isMultiple && !this.options.select2.initSelection) {
                // customId and customText are methods to extract `id` and `text` from data object
                // we can use this workaround only if user did not define these methods
                // otherwise we cant construct data object
                var customId = this.options.select2.id,
                    customText = this.options.select2.formatSelection;
-               if(!customId && !customText) {      
-                   var data = {id: value, text: $(this.options.scope).text()};
-                   this.$input.select2('data', data); 
+
+               if(!customId && !customText) {
+                   var $el = $(this.options.scope);
+                   if (!$el.data('editable').isEmpty) {
+                       var data = {id: value, text: $el.text()};
+                       this.$input.select2('data', data); 
+                   }
                }
            }
        },
@@ -241,11 +249,11 @@ $(function(){
             if(typeof str !== 'string' || !this.isMultiple) {
                 return str;
             }
-            
+
             separator = separator || this.getSeparator();
-            
+
             var val, i, l;
-                
+
             if (str === null || str.length < 1) {
                 return null;
             }
@@ -253,10 +261,10 @@ $(function(){
             for (i = 0, l = val.length; i < l; i = i + 1) {
                 val[i] = $.trim(val[i]);
             }
-            
+
             return val;
        },
-       
+
         autosubmit: function() {
             this.$input.on('change', function(e, isInitial){
                 if(!isInitial) {
@@ -268,7 +276,7 @@ $(function(){
         getSeparator: function() {
             return this.options.select2.separator || $.fn.select2.defaults.separator;
         },
-        
+
         /*
         Converts source from x-editable format: {value: 1, text: "1"} to
         select2 format: {id: 1, text: "1"}
@@ -282,26 +290,26 @@ $(function(){
                     }
                 }
             }
-            return source;            
+            return source;
         },
         
         destroy: function() {
             if(this.$input.data('select2')) {
                 this.$input.select2('destroy');
             }
-        }               
+        }
         
-    });      
+    });
 
     Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
         /**
         @property tpl 
         @default <input type="hidden">
-        **/         
+        **/
         tpl:'<input type="hidden">',
         /**
         Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
-        
+
         @property select2 
         @type object
         @default null
@@ -313,21 +321,21 @@ $(function(){
         @property placeholder 
         @type string
         @default null
-        **/             
+        **/
         placeholder: null,
         /**
         Source data for select. It will be assigned to select2 `data` property and kept here just for convenience.
         Please note, that format is different from simple `select` input: use 'id' instead of 'value'.
-        E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`.  
-        
+        E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`.
+
         @property source 
         @type array|string|function
         @default null        
         **/
         source: null,
         /**
-        Separator used to display tags. 
-        
+        Separator used to display tags.
+
         @property viewseparator 
         @type string
         @default ', '        
@@ -335,6 +343,6 @@ $(function(){
         viewseparator: ', '
     });
 
-    $.fn.editabletypes.select2 = Constructor;      
-    
+    $.fn.editabletypes.select2 = Constructor;
+
 }(window.jQuery));
diff --git a/test/unit/select2.js b/test/unit/select2.js
index c05b3ba..01f521c 100644
--- a/test/unit/select2.js
+++ b/test/unit/select2.js
@@ -317,7 +317,24 @@ $(function () {
                start();
            }, timeout);
         }, timeout);             
-     });    
+     });  
+     
+    test("remote: initially empty", function () {
+        var s = 2, text = groups[s],
+            newVal = 0, newText = groups[newVal],
+            e = $('<a href="#" data-type="select2" data-name="select2"></a>').appendTo(sfx).editable({
+                source: 'groupsArr2',
+                select2: {
+                    placeholder: 'placeholder'
+                }
+            }); 
+            
+        e.click();
+        var p = tip(e);
+        
+        ok(p.is(':visible'), 'popover visible');
+        equal(p.find('.select2-choice span').text(), 'placeholder', 'placeholder shown in select2');            
+    });      
     
     asyncTest("remote: custom id, custom text, init selection (not multiple)", function () {
         var s = 2,