JSFiddle - React, Tailwind, and code Playground

by robertfalken

HTML

<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.3/underscore-min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.0/jquery-ui.min.js"></script>
<div ng-app="Tagish">
    <div ng-controller="TagCtrl">
        <taginput ng-model="tags"/>
    </div>
</div>

CSS

.taginput {
  font-family: sans-serif;
  width: 200px;
  border: 1px solid #eaeaea;
  overflow: hidden;
  padding: 10px;
  margin: 30px;
}

  input {
    width: 50px;
    float: left;
    outline: none;
    border: none;
    font-size: 0.8em;
    margin-top: 7px;
  }

  .tag {
    background: #cbf3ff;
    border: 1px solid #9dd9ea;
    float: left;
    margin: 4px 4px 0 0;
    display: inline-block;
    border-radius: 15px;
    padding: 2px 6px;
    font-size: 14px;
    color: #629eaf;
  }

.tag a {
    color: #629eaf;
    font-weight: bold;
    font-size: 0.7em;
    position: relative;
    top: -1px;
}

.active {
    background: #629eaf;
    color: #cbf3ff;
}

JavaScript

app = angular.module("Tagish", []);

function TagCtrl($scope){
    $scope.tags = ["Foo", "Bar"];
}

app.directive("taginput", function($compile,$rootScope){
  return {
    replace: true,
    restrict: 'E',
    template: "<div class='taginput'></div>",
    require: "?ngModel",
    link: function(scope,element,attrs,ngModel){
      if (!ngModel) return;
        element = $(element);
      var input = $("<input type='text' placeholder='Add tag...'/>").appendTo(element);
      element.click(function(){ input.focus(); });
      scope.$watch(attrs.ngModel, function(tags){
        element.find(".tag").remove();
        _(tags).each(function(tag,index){
          input.before("<span class='tag' data-index='"+index+"'>"+tag+" <a class='pictos'>x</a></span>");
        });
      }, true);

      $(".taginput .tag a").on("click", function(e){
        e.preventDefault();
        var index = $(this).parent().attr("data-index");
        scope.$apply(function(){ rm(index); });
      });

      input.keydown(function(e){
        var val = input.val();
        if (e.which === 13 || e.which === 188) { // return or ,
          if (!val.length) return false;
          var tags = ngModel.$viewValue;
          var exist_at = -1;
          _(tags).find(function(tag,i){
            if (tag.toLowerCase() === val.toLowerCase()) {
              exist_at = i;
              return true;
            }
          });
          if (exist_at >= 0) {
            $("[data-index='"+exist_at+"']").not(":animated").effect("highlight");
            return false;
          }
          scope.$apply(function(){ add(val); });
          input.val("");
          return false;
        }
        else if (e.which === 8) { // backspace
          if (!val.length) {
            active = element.find(".active");
            if (active.size()) {
              index = active.attr("data-index");
              scope.$apply(function(){ rm(index); });
            } else {
              scope.$apply(rm);
            }
    ...