Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
568 views
in Technique[技术] by (71.8m points)

javascript - Customizing ngTagsInput & autoComplete directives (AngularJS)

I am new to AngularJS and am currently working on an input field, which can accept multiple tags at a time along with the auto-complete feature, which display the available tags as dropdown options. For this I am using the ngTagsInput directive I found on the web(http://mbenford.github.io/ngTagsInput/), which gives me a custom HTML element <tags-input>. This works beautifully:

index.html:

<script>
var app = angular.module('plunker', ['ngTagsInput']);

app.controller('MainCtrl', function($scope, $http) {
  $scope.tags = [
    { text: 'Tag1' },
    { text: 'Tag2' },
    { text: 'Tag3' }
  ];

  $scope.loadTags = function(query) {
    return $http.get('tags.json');
  };
});
</script>
<div ng-app="plunker" ng-controller="MainCtrl">    
    <tags-input ng-model="tags" add-on-paste="true" display-property="text" placeholder="Add a Tag" add-from-autocomplete-only="true">
           <auto-complete max-results-to-show="4" min-length="2" source="loadTags($query)"></auto-complete>
    </tags-input>
</div>

tags.json:

[
  { "text": "Tag1" },
  { "text": "Tag2" },
  { "text": "Tag3" },
  { "text": "Tag4" },
  { "text": "Tag5" },
  { "text": "Tag6" },
  { "text": "Tag7" },
  { "text": "Tag8" },
  { "text": "Tag9" },
  { "text": "Tag10" }
]

However I wanted to use the standard HTML <input> element instead of the custom <tags-input> element which comes along with the directive, so with a lot of help and using <script src="https://code.jquery.com/jquery-3.1.0.js" integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk=" crossorigin="anonymous"></script> I was able to do it here:

Here is the new index.html:

<script>
var app = angular.module('plunker', ['ngTagsInput']);

app.controller('MainCtrl', function($scope, $http) {
  $scope.tags = [
    { "id":1, "tagname": 'Tag1' },
    { "id":2, "tagname": 'Tag2' },
    { "id":3, "tagname": 'Tag3' },
    { "id":4, "tagname": 'Tag4' }
  ];

    $scope.loadTags = function(query) {
    return $http.get('tags.json');
  };

});

app.directive('tagsInputAttr', 
  function($compile){
    return {
      restrict: 'A',
      require: '?ngModel',
      scope:{
        ngModel: '='
      },
      link: function($scope, element, attrs, controller) {
        var attrsText = '';
        $.each($(element)[0].attributes, function(idx, attr) {
          if (attr.nodeName === "tags-input-attr" || attr.nodeName === "ng-model") 
            return;

          attrsText += " " + attr.nodeName + "='" + attr.nodeValue + "'";
        });

        var html ='<tags-input ng-model="ngModel" ' + attrsText + '></tags-input>';
        e =$compile(html)($scope);
        $(element).replaceWith(e);
      }
    };
  }
);
</script>
<div ng-app="plunker" ng-controller="MainCtrl">

    <input tags-input-attr ng-model="tags" add-on-paste="true" display-property="tagname" placeholder="Add tags here..." add-from-autocomplete-only="true">
      <auto-complete max-results-to-show="3" min-length="2" source="loadTags($query)"></auto-complete>
    </input>

  </div>

And the new tags.json:

[
  { "id":1, "tagname": "Tag1" },
  { "id":2, "tagname": "Tag2" },
  { "id":3, "tagname": "Tag3" },
  { "id":4, "tagname": "Tag4" },
  { "id":5, "tagname": "Tag5" },
  { "id":6, "tagname": "Tag6" },
  { "id":7, "tagname": "Tag7" },
  { "id":8, "tagname": "Tag8" },
  { "id":9, "tagname": "Tag9" },
  { "id":10, "tagname": "Tag10" }
]

As you can notice,the new directive tagsInputAttr, which wraps the <tags-input> provides the same functionality and can be used inside <input> tag as an attribute along with the rest of attributes such as ng-model, display-property etc. So I don't have to use the <tags-input> element directly. The problem is that the <auto-complete> placed inside the <input> tag doesn't work.

For this I need to alter my directive, considering the following:

Note: I do not want to use jquery for this

My question is how do I wrap the <auto-complete> inside the same <input tags-input-attr> element:

  1. Either as an attribute inside the same <input tags-input-attr> element

  2. or as an attribute inside a standard HTML element like <div> or <span>, wrapped inside the same <input tags-input-attr> element.

  3. If not the above two, then as last resort, as the <auto-complete> tag wrapped inside the same <input tags-input-attr> element

All help is appreciated. Thanks in advance.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I made some changes on the previus directive and now it accepts all kind of transformation from attribute to element directive.

You still have the elem-as-attr attribute, but now you have to specify the value of it, that it will be the element that will replace.

Example:

<div elem-as-attr="tags-input"></div>

Your application JavaScript:

var app = angular.module('plunker', ['ngTagsInput']);

app.controller('MainCtrl', function($scope, $http) {
  $scope.allTags = [
    { "id":1, "tagname": "Tag1" },
    { "id":2, "tagname": "Tag2" },
    { "id":3, "tagname": "Tag3" },
    { "id":4, "tagname": "Tag4" },
    { "id":5, "tagname": "Tag5" },
    { "id":6, "tagname": "Tag6" },
    { "id":7, "tagname": "Tag7" },
    { "id":8, "tagname": "Tag8" },
    { "id":9, "tagname": "Tag9" },
    { "id":10, "tagname": "Tag10" }
  ];

  $scope.myTags =[
    $scope.allTags[2],
    $scope.allTags[4],
    $scope.allTags[8]
  ];

  $scope.loadTags = function(query) {
    return $scope.allTags;
  };
});

The directive code:

app.directive('elemAsAttr', function($compile) {
  return {
    restrict: 'A',
    require: '?ngModel',
    replace: true,
    scope: true,
    compile: function(tElement, tAttrs) {
      return function($scope) {
        var attrs = tElement[0].attributes;

        var attrsText = '';
        for (var i=0; i < attrs.length; i++) {
          var attr = attrs.item(i);
          if (attr.nodeName === "elem-as-attr") {
            continue;
          }
          attrsText += " " + attr.nodeName + "='" + attr.nodeValue + "'";        
        }

        var hasModel = $(tElement)[0].hasAttribute("ng-model");
        var innerHtml = $(tElement)[0].innerHTML;
        var html = '<' + tAttrs.elemAsAttr  + attrsText + '>' + innerHtml + '</' + tAttrs.elemAsAttr + '>';

        var e = hasModel ? $compile(html)($scope) : html;
        $(tElement).replaceWith(e);
      };
    }
  }
});

HTML code:

Element way:

  <tags-input ng-model="myTags" add-on-paste="true" display-property="tagname" placeholder="Add a Tag" add-from-autocomplete-only="true">
    <auto-complete max-results-to-show="10" display-property="tagname" min-length="2" source="loadTags($query)"></auto-complete>
  </tags-input>

Attribute way:

  <div elem-as-attr="tags-input" ng-model="myTags" add-on-paste="true" display-property="tagname" placeholder="Add tags here..." add-from-autocomplete-only="true">
    <div elem-as-attr="auto-complete" max-results-to-show="10" display-property="tagname" min-length="2" source="loadTags($query)"></div>
  </div>

Plunker:

https://plnkr.co/edit/9TqsXy

Note that you cannot use the input element for the tagsInput because the input element does not have the closing tag in HTML. So you will not be able to put the auto-complete element inside it.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...