3

UPDATE 1: developed the first sample code to set the basis for correct implementation.
UPDATE 2: developed a working model. See answers.

I found this library:

https://libraries.io/bower/editable-dropdown-angularjs

which allows adding editable dropdown list using HTML5 datalist feature.

It works fine, however, the only needed feature is to make the field editable only if the selected value is "Other".

See working sample in plunkr.co vreated based on the demo from the repository

http://plnkr.co/edit/wDm2mbTqTsT1YC5H7UPy?p=preview

See sample code below for details.

Appreciate your suggestions to make the dropdown field editable only if the selected value is "Other".

HTML5:

<div ng-app="myApp">
    <div ng-controller="demo" style="width:300px;position:fixed;top:20px;left:20px">    
        <p>You selected {{selected}}</p>
        <editable-dropdown options='list' ng-model='selected'></editable-dropdown>
    </div>
</div>

JavaScript:

angular.module('myApp', ['editableDropdown'])
.controller('demo', function($scope){
    $scope.list = ['one', 'two', 'other']
    $scope.selected;
});

I was able to develop this sample code using jsfiddle (based in this answer):

http://jsfiddle.net/tarekahf/t392djx1/

Which will allow making the dropdown list editable if "Other" is selected. Now, I am converting this mode to the Angular way. If you have any suggestion, please let me know.

Community
  • 1
  • 1
tarekahf
  • 738
  • 1
  • 16
  • 42

2 Answers2

5

Seems like you are not attracting too much attention to your question and as I commented, you'd (still) be better off writing your own implementation rather than trying to force editable-dropdown-angular to your needs.

Anyway, I took liberty of writing my own editable-select directive for you.

Directive takes array of options to choose from and optional other string, which is default value for user-editable selection. Editing is disabled while choosing from options, while other can be freely modified.

Hope you find it useful.

App HTML template

<body ng-controller="Ctrl as vm">
  <!-- directive -->
  <editable-select 
    ng-model="vm.selected" 
    options="vm.options" 
    other="Other"> <!-- write "other" here or assign var in controller -->
  </editable-select>

  <hr>
  <span>User selected: {{ vm.selected }}</span>
</body>

App JavaScript

angular
.module('app', [])    
.controller('Ctrl', function() {
  var vm = this;
  vm.options = ['One', 'Two']; // selection options
})    
.directive('editableSelect', function() {
  return {
    restrict: 'E',
    require: '^ngModel',
    scope: {
      ngModel: '=',
      options: '=',
      other: '@'
    },
    replace: true,
    templateUrl: 'editable-select-tpl.html', 
    link: function(scope, element) {
      scope.isDisabled = true;

      // option clicked handler    
      scope.click = function(option) {
        scope.ngModel = option;
        scope.isDisabled = !scope.other || scope.other !== option;
        if (!scope.isDisabled) {
          element[0].querySelector('.editable-select').focus();
        }
      };

      // option typed handler          
      var unwatch = scope.$watch('ngModel', function(val) {
        if (!scope.isDisabled) {
          scope.other = scope.ngModel;
        }
      });

      // release watcher          
      scope.$on('$destroy', unwatch);
    }
  };
}); 

Directive HTML template (editable-select-tpl.html)

<div>
  <div class="input-group dropdown">
    <input name="editable-select" 
           type="text" 
           class="form-control dropdown-toggle editable-select" 
           ng-disabled="isDisabled" 
           ng-model="ngModel">
    <ul class="dropdown-menu">
      <li ng-repeat="option in options" 
          ng-bind="::option" 
          ng-click="click(option)">
      </li>
      <li ng-if="other" role="presentation" class="divider"></li>
      <li ng-if="other" ng-bind="other" ng-click="click(other)"></li>
    </ul>
    <span class="input-group-addon dropdown-toggle" data-toggle="dropdown">
      <span class="caret"></span>
    </span>
  </div>
  <span class="small text-muted" ng-show="!isDisabled">Type in your selection</span>
</div>

CSS

input[name="editable-select"]:disabled {
  background-color: #ffffff;
}

.dropdown-menu:hover {
  cursor: pointer;
}

.dropdown-menu li {
  padding-left: 10px;
}

.dropdown-menu li:hover {
  background-color: #eeeeee;
}

enter image description here

Related plunker here https://plnkr.co/edit/7bVgDW

Mikko Viitala
  • 8,344
  • 4
  • 37
  • 62
  • Many thanks @Mikko for the answer. I didn't have the chance to verify if it will work for my case, since I had to push the developed solution due to schedule constraints. I will post my solution as an answer (as another option), and will try my best to check your solution in the coming sprints. – tarekahf Jan 16 '17 at 22:22
  • 1
    Just to let you know that I now understood your solution after I had enough time to study it. Your solution is very efficient and I will arrange to implement it in my project in the coming sprints. Probably the only modification required is to allow for option `value` and option `text` same like the standard drop-down list. I think I know how to add this feature to your solution. – tarekahf Jun 08 '17 at 15:48
0

Here is my solution. This is based on another post, but I cannot remember the source. Thanks to all who helped.

Just add the attribute editable-dropdown against the select element. The value of the attribute must be the ID of the input element.

Style:

.stop-wrap {
    display: inline-block;
}

.select-editable {
    position:relative;
    background-color:white;
    border:solid grey 1px;
    width:120px;
    height:25px;
    vertical-align: middle;
    margin-bottom: 5px;
}
.select-editable select {
    position:absolute;
    top:0px;
    left:0px;
    border:none;
    width:118px;
    margin:0;
}
.select-editable input {
    position:absolute;
    top:0px;
    left:0px;
    width:100px;
    padding:1px;
    border:none;
}
.select-editable select:focus, .select-editable input:focus {
    outline:none;
}

HTML:

<div class="select-editable stop-wrap">
    <select editable-dropdown="input_elem" id="input_elem_sel" name="input_elem_sel">
          <option value=""></option>
          <option value="Option 1">Option 1</option>
          <option value="Option 2">Option 2</option>
          <option value="Option 3">Option 3</option>
          <option value="Option 4">Option 4</option>
          <option value="Other">Other ...</option>
    </select>
    <input id="input_elem" name="input_elem" ng-model="input_elem" force-model-update>
</div>

JavaScript:

//This uses two fields, SELECT and INPUT. The input element is identfied by the attribute 'editableDropdown'
app.directive('editableDropdown', function (){
    return {
        link: function (scope, elemSel, attrs) {
            //debugger;
            var inpElemID = attrs.editableDropdown;
            var inpElem;
            //The parameter 'elemSel' is the SELECT field
            function initInpElem() {
                if ($(elemSel).is("select")) {
                    inpElem = $('#' + inpElemID);
                } else {
                    //This is in case the Dropdown is based on DATALIST which is not yet implemented
                    //In this case, the input element is actually the same as the dropdown field using DATALIST
                    inpElem = elemSel;
                }
            }
            function updateEditable(elm) {
                initInpElem();
                //Copy value from SELECT element to the INPUT Element
                //Use NgModelController to copy value in order to trigger validation for 'inpElem'
                var selectedValue = $(elm).children("option").filter(":selected").text();
                //or var selectedValue = elm.val();
                //TODO: will have to add more control here since the SELECT value and text are not the same
                //      Might cause some issue while rendering value in PDF.
                angular.element(inpElem).controller('ngModel').$setViewValue(elm.val());
                angular.element(inpElem).controller('ngModel').$render();
                makeEditable(elm);
            }
            function makeEditable(selElm) {
                //debugger;
                initInpElem();
                if ($(selElm).is("select")) {
                    if (selElm.val() == "Other") {
                          $(inpElem).prop("readonly", false);
                    } else {
                          $(inpElem).prop("readonly", true);
                    }
                } else {
                    //This part is not yet implemented. It is to be completed and verified in case the dropdown is `datalist`. You can skip this part.
                    if (elm.value != "Other" && !$(elm).attr("keypressOff")) {
                        $(elm).keypress(function(event) {
                            console.log("keypress preventd")
                            event.preventDefault();
                        })
                    } else {
                        $(elm).off("keypress");
                        $(elm).attr("keypressOff", true);
                        console.log("keypress event removed")
                    }
                }
            }           
            angular.element(document).ready(function(){
                makeEditable(elemSel);
            });
            $(elemSel).change(function () {
                updateEditable(elemSel);
            });
        }
    }
});
tarekahf
  • 738
  • 1
  • 16
  • 42
  • If you doing sprints, then you are familiar with constant refactoring :) In this case, while this solution works, you'd want to get rid of all jquery related stuff when using angular as well as hardcoded 'Other' and mandatory id. – Mikko Viitala Jan 17 '17 at 06:47
  • @MikkoViitala, it would be great to help me by pointing out to the exact parts to be improved with samples on how to do it. For example, I understand that it's nearly impossible to get rid of jQuery 100% within Angular. I'll schedule such improvements in the coming sprints. – tarekahf Jan 17 '17 at 15:20
  • 1
    I posted an answer :) which should point you in right direction. There's also link to Plunkr if you missed it. Anyway, pass whatever you need in your directive via directive (isolated) `scope` if you can. If you use `$` in any part of your angular application, you do need to refactor. – Mikko Viitala Jan 17 '17 at 16:12