From 3fe5a655e5f35952343402f0ecf3c3ce39fb4573 Mon Sep 17 00:00:00 2001
From: FezVrasta <fez@tekhy.net>
Date: Mon, 5 Jan 2015 13:32:06 +0100
Subject: [PATCH] 0.0.2dev

---
 README.md           |  29 +++++++----
 bower.json          |   2 +-
 index.html          |  37 ++------------
 jquery.dropdown.css |  35 +++++++++++--
 jquery.dropdown.js  | 142 ++++++++++++++++++++++++++++++++++++++--------------
 5 files changed, 160 insertions(+), 85 deletions(-)

diff --git a/README.md b/README.md
index 9eb8f9b..7ac6f59 100644
--- a/README.md
+++ b/README.md
@@ -2,33 +2,33 @@
 
 Finally a dropdown plugin that transforms select inputs in nice dropdowns and does not drive you crazy.
 
-I've always wondered why the most famous plugins (Chosen and Selectize) are so huge and full of useless/pointless features?  
+I've always wondered why the most famous plugins (Chosen and Selectize) are so huge and full of useless/pointless features?
 What do you need from a dropdown plugin?
 
-### Initialize it 
+### Initialize it
 
 Nice! I've found `<your_fav_plugin_here>` and I want to use it to style all my select inputs! But wha happens if I dinamically add a new element? I must init it manually.. Doh!
 
 Dropdown.js get rid of this problem, in fact you just need to run the plugin **one time** and it will take care of every new input which matches your rules.
 
-    // Init any existing .select element and then watch for new .select elements 
+    // Init any existing .select element and then watch for new .select elements
     // added dinamically by some black magic casted by a evil wizard
     $(".select").dropdown({ "autoinit" : ".select" });
-    
-Now what happens? We want our dropdowns to act like a boss.. emh like a standard select input. If it's a single select you want first element, or the selected one, already selected.  
+
+Now what happens? We want our dropdowns to act like a boss.. emh like a standard select input. If it's a single select you want first element, or the selected one, already selected.
 We want even keyboard support, so if we press tab, it get opened and with tab you can select the options etc etc...
 
 ### Style it
 
-What else? Maybe we want to be able to style our dropdown with our own classes so that we don't have to hack the plugin to make it looks good? 
+What else? Maybe we want to be able to style our dropdown with our own classes so that we don't have to hack the plugin to make it looks good?
 
     $(".select").dropdown({ "dropdownClass": "my-dropdown", "optionClass": "my-option awesome" });
-    
+
 With this command we init every `.select` element (yes they are initialized always in the same way) and the `my-dropdown` class will be applied to the dropdown wrapper (ex: `<select>`) and `my-option awesome` will be applied to the options (ex: `<option>`).
 
 ### Please don't fill my DOM with thousand of DIVs!!!
 
-Don't worry friend, this is the markup used by Dropdown.js:  
+Don't worry friend, this is the markup used by Dropdown.js:
 
     <div class="dropdownjs">
       <input type="text" readonly placeholder="This is a placeholder">
@@ -37,18 +37,25 @@ Don't worry friend, this is the markup used by Dropdown.js:
         <li value="great" selected class="selected" tabindex="0">...is great!</li>
       </ul>
     </div>
-    
+
 ### Callbacks
 
-Psstt, ya need some callback?  
+Psstt, ya need some callback?
 **Dropdown.js** supports callbacks, they are called on each element initialized, here an example:
 
     $(".select").dropdown({ "callback": function($dropdown) {
       // $dropdown is the shiny new generated dropdown element!
       $dropdown.fadeIn("slow");
     });
-    
+
 ### Dropdown position
 
 Dropdowns are placed in the best position, if a select input is placed on bottom of the page, the dropdown will open above it.
 
+### Dynamically added options
+
+You may add a new option to the select tag and it will be automatically added to the dropdown.js
+
+### Let users add options
+
+Add `data-dynamic-opts=true` to the select tag to let users add or remove options.
diff --git a/bower.json b/bower.json
index e7cf64f..e2d86d4 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
 {
   "name": "dropdown.js",
-  "version": "0.0.1",
+  "version": "0.0.2dev",
   "homepage": "https://github.com/FezVrasta/dropdown.js",
   "authors": [
     "Federico Zivolo <info@mywebexpression.com>"
diff --git a/index.html b/index.html
index db8b5cd..16ab6a8 100644
--- a/index.html
+++ b/index.html
@@ -10,39 +10,12 @@
   <body>
 
     <button>Click</button>
-
-    <select multiple class="select form-control floating-label" placeholder="This is a placeholder">
+<br>
+<br>
+<br>
+<br>
+    <select multiple class="select form-control floating-label" placeholder="This is a placeholder" data-dynamic-opts=true>
       <option></option>
-      <option selected>Foobar</option>
-      <option selected>Is great</option>
-      <option>Foobar</option>
-      <option>Is great</option>
-      <option>Foobar</option>
-      <option>Is great</option>
-      <option selected>Foobar</option>
-      <option selected>Is great</option>
-      <option>Foobar</option>
-      <option>Is great</option>
-      <option>Foobar</option>
-      <option>Is great</option>
-      <option selected>Foobar</option>
-      <option selected>Is great</option>
-      <option>Foobar</option>
-      <option>Is great</option>
-      <option>Foobar</option>
-      <option>Is great</option>
-      <option selected>Foobar</option>
-      <option selected>Is great</option>
-      <option>Foobar</option>
-      <option>Is great</option>
-      <option>Foobar</option>
-      <option>Is great</option>
-      <option selected>Foobar</option>
-      <option selected>Is great</option>
-      <option>Foobar</option>
-      <option>Is great</option>
-      <option>Foobar</option>
-      <option>Is great</option>
     </select>
 
     <br><br><br>
diff --git a/jquery.dropdown.css b/jquery.dropdown.css
index 5f7a9e1..33ccba8 100644
--- a/jquery.dropdown.css
+++ b/jquery.dropdown.css
@@ -5,7 +5,9 @@
   box-sizing: border-box;
 }
 .dropdownjs > input {
-    width: 100%;
+  width: 100%;
+  padding-right: 30px;
+  text-overflow: ellipsis;
 }
 .dropdownjs > input.focus ~ ul {
   transform: scale(1);
@@ -32,11 +34,18 @@
   list-style: none;
   padding: 10px 20px;
 }
-
+.dropdownjs > ul  > li.dropdownjs-add {
+  padding: 0;
+}
+.dropdownjs > ul  > li.dropdownjs-add > input {
+  border: 0;
+  padding: 10px 20px;
+  width: 100%;
+}
 
 /* Theme */
 .dropdownjs > input[readonly] {
-    cursor: pointer;
+  cursor: pointer;
 }
 select[data-dropdownjs][disabled] + .dropdownjs > input[readonly] {
   cursor: default;
@@ -47,9 +56,11 @@ select[data-dropdownjs][disabled] + .dropdownjs > input[readonly] {
   transition: transform 0.2s ease-out;
   padding: 10px;
   overflow: auto;
+  max-width: 500px;
 }
 .dropdownjs > ul > li {
   cursor: pointer;
+  word-wrap: break-word;
 }
 .dropdownjs > ul > li.selected,
 .dropdownjs > ul > li:active {
@@ -57,5 +68,21 @@ select[data-dropdownjs][disabled] + .dropdownjs > input[readonly] {
 }
 .dropdownjs > ul > li:focus {
   outline: 0;
-  background-color: #d4d4d4;
+  outline: 1px solid #d4d4d4;
+}
+.dropdownjs > ul > li > .close:before {
+  content: "\00d7";
+  display: block;
+  position: absolute;
+  right: 15px;
+  float: right;
+  font-size: 21px;
+  font-weight: 700;
+  line-height: 1;
+  color: #000;
+  text-shadow: 0 1px 0 #fff;
+  opacity: .6;
+}
+.dropdownjs > ul > li:h > .close:hover:before {
+  opacity: .9;
 }
diff --git a/jquery.dropdown.js b/jquery.dropdown.js
index a06a998..c37007f 100644
--- a/jquery.dropdown.js
+++ b/jquery.dropdown.js
@@ -7,7 +7,8 @@
       "optionClass": "",
       "dropdownClass": "",
       "autoinit": false,
-      "callback": false
+      "callback": false,
+      "dynamicOptLabel": "Add a new option..."
     },
     init: function(options) {
 
@@ -27,19 +28,24 @@
         // Is it a multi select?
         var multi = $select.attr("multiple");
 
+        // Does it allow to create new options dynamically?
+        var dynamicOptions = $select.attr("data-dynamic-opts"),
+            $dynamicInput = $();
+
         // Create the dropdown wrapper
         var $dropdown = $("<div></div>");
         $dropdown.addClass("dropdownjs").addClass(options.dropdownStyle);
         $dropdown.data("select", $select);
 
         // Create the fake input used as "select" element and cache it as $input
-        var $input = $("<input type=text readonly>");
+        var $input = $("<input type=text readonly class=fakeinput>");
         if ($.material) { $input.data("mdproc", true); }
         // Append it to the dropdown wrapper
         $dropdown.append($input);
 
         // Create the UL that will be used as dropdown and cache it AS $ul
         var $ul = $("<ul></ul>");
+        $ul.data("select", $select);
 
         // Append it to the dropdown
         $dropdown.append($ul);
@@ -51,32 +57,19 @@
         $select.find("option").each(function() {
           // Cache $(this)
           var $this = $(this);
+          methods._addOption($ul, $this);
 
-          // Create the option
-          var $option = $("<li></li>");
-
-          // Style the option
-          $option.addClass(options.optionStyle);
+        });
 
-          // If the option has some text then transfer it
-          if ($this.text()) {
-            $option.text($this.text());
-          }
-          // Otherwise set the empty label and set it as an empty option
-          else {
-            $option.html("&nbsp;");
-          }
-          // Set the value of the option
-          $option.attr("value", $this.val());
+        // If this select allows dynamic options add the widget
+        if (dynamicOptions) {
+          $dynamicInput = $("<li class=dropdownjs-add></li>");
+          $dynamicInput.append("<input>");
+          $dynamicInput.find("input").attr("placeholder", options.dynamicOptLabel);
+          $ul.append($dynamicInput);
+        }
 
-          // Ss it selected?
-          if ($this.attr("selected")) {
-            $option.attr("selected", true);
-          }
 
-          // Append option to our dropdown
-          $ul.append($option);
-        });
 
         // Cache the dropdown options
         var selectOptions = $dropdown.find("li");
@@ -114,39 +107,64 @@
         //---------------------------------------//
 
         // On click, set the clicked one as selected
-        selectOptions.on("click", function(e) {
+        $ul.on("click", "li:not(.dropdownjs-add)", function(e) {
           methods._select($dropdown, $(this));
           // trigger change event, if declared on the original selector
           $select.change();
         });
-        selectOptions.on("keydown", function(e) {
+        $ul.on("keydown", "li:not(.dropdownjs-add)", function(e) {
           if (e.which === 27) {
-            $(".dropdownjs > ul > li").each(function() { $(this).attr("tabindex", -1); });
+            $(".dropdownjs > ul > li").attr("tabindex", -1);
             return $input.removeClass("focus").blur();
           }
-          if (e.which === 32) {
+          if (e.which === 32 && !$(e.target).is("input")) {
             methods._select($dropdown, $(this));
             return false;
           }
         });
 
-        selectOptions.on("focus", function() {
+        $ul.on("focus", "li:not(.dropdownjs-add)", function() {
           if ($select.is(":disabled")) {
             return;
           }
           $input.addClass("focus");
         });
 
+        // Add new options when the widget is used
+        if (dynamicOptions && dynamicOptions.length) {
+          $dynamicInput.on("keydown", function(e) {
+            if(e.which !== 13) return;
+            var $option = $("<option>"),
+                val = $dynamicInput.find("input").val();
+            $dynamicInput.find("input").val("");
+
+            $option.attr("value", val);
+            $option.text(val);
+            $select.append($option);
+
+          });
+        }
+
+        // Listen for new added options and update dropdown if needed
+        $select.on("DOMNodeInserted", function(e) {
+          var $this = $(e.target);
+          if (!$this.val().length) return;
+
+          methods._addOption($ul, $this);
+          $ul.find("li").not(".dropdownjs-add").attr("tabindex", 0);
+
+        });
+
         // Used to make the dropdown menu more dropdown-ish
         $input.on("click focus", function(e) {
           e.stopPropagation();
           if ($select.is(":disabled")) {
             return;
           }
-          $(".dropdownjs > ul > li").each(function() { $(this).attr("tabindex", -1); });
+          $(".dropdownjs > ul > li").attr("tabindex", -1);
           $(".dropdownjs > input").not($(this)).removeClass("focus").blur();
 
-          selectOptions.each(function() { $(this).attr("tabindex", 0); });
+          $(".dropdownjs > ul > li").not(".dropdownjs-add").attr("tabindex", 0);
 
           // Set height of the dropdown
           var coords = {
@@ -169,8 +187,17 @@
           $(this).next("ul").css("max-height", height - 20);
           $(this).addClass("focus");
         });
-        $(document).on("click", function() {
-          $(".dropdownjs > ul > li").each(function() { $(this).attr("tabindex", -1); });
+        // Close every dropdown on click outside
+        $(document).on("click", function(e) {
+
+          // Don't close the multi dropdown if user is clicking inside it
+          if (multi && $(e.target).parents(".dropdownjs").length) return;
+
+          // Don't close the dropdown if user is clicking inside the dynamic-opts widget
+          if ($(e.target).parents(".dropdownjs-add").length || $(e.target).is(".dropdownjs-add")) return;
+
+          // Close opened dropdowns
+          $(".dropdownjs > ul > li").attr("tabindex", -1);
           $input.removeClass("focus");
         });
       }
@@ -194,9 +221,11 @@
       methods._select($(this), $target);
     },
     _select: function($dropdown, $target) {
+      if ($target.is(".dropdownjs-add")) return;
+
       // Get dropdown's elements
       var $select = $dropdown.data("select"),
-          $input  = $dropdown.find("input");
+          $input  = $dropdown.find("input.fakeinput");
 
       // Is it a multi select?
       var multi = $select.attr("multiple");
@@ -210,10 +239,10 @@
         $target.toggleClass("selected");
         // Toggle selection of the clicked option in native select
         var $selected = $select.find("[value=\"" + $target.attr("value") + "\"]");
-        if ($selected.attr("selected")) {
-          $selected.attr("selected", true);
+        if ($selected.prop("selected")) {
+          $selected.prop("selected", true);
         } else {
-          $selected.attr("selected", false);
+          $selected.prop("selected", false);
         }
         // Add or remove the value from the input
         var text = [];
@@ -246,6 +275,45 @@
         }
       }
 
+    },
+    _addOption: function($ul, $this) {
+      // Create the option
+      var $option = $("<li></li>");
+
+      // Style the option
+      $option.addClass(this.options.optionStyle);
+
+      // If the option has some text then transfer it
+      if ($this.text()) {
+        $option.text($this.text());
+      }
+      // Otherwise set the empty label and set it as an empty option
+      else {
+        $option.html("&nbsp;");
+      }
+      // Set the value of the option
+      $option.attr("value", $this.val());
+
+      // Will user be able to remove this option?
+      if ($ul.data("select").attr("data-dynamic-opts")) {
+        $option.append("<span class=close></span>");
+        $option.find(".close").on("click", function() {
+          $option.remove();
+          $this.remove();
+        });
+      }
+
+      // Ss it selected?
+      if ($this.prop("selected")) {
+        $option.prop("selected", true);
+      }
+
+      // Append option to our dropdown
+      if ($ul.find(".dropdownjs-add").length) {
+        $ul.find(".dropdownjs-add").before($option);
+      } else {
+        $ul.append($option);
+      }
     }
   };
 
