Search code examples
javascriptangularjsangular-directiveselectize.js

Render angular directives in Selectize.js items


I am using angular-selectize to use Selectize.js in my angular project.

To use custom items in Selectize.js selector, I am using Selectize.js' render option:

render: {
  item: function(item, escape) {
    var avatar = '<div>' +
        '<span avatars="\'' + escape(item._id) +'\'" class="avatars">' +
        '</span>' +
        escape(item.nick) +
      '</div>';
    var compiledAvatar =  $compile(avatar)($rootScope);
    $timeout();
    return compiledAvatar.html();
  },

where avatars is a custom directive with asychronous behaviour

The problem is that the render.item function expects an HTML string as an output but:

  • There is no way of returning a rendered or "$compileed" HTML string in a synchronous way as expected by render.item method.
  • I do not know how to render that item's elements afterwards when they have already been added to the DOM.

Note that although $compile is called, returned string would not be the expected compiled result but the string before compilation due to the asynchronous nature of $compile.


Solution

  • This answer is based in the helpful answer by @gregori with the following differences:

    1. Take into account Selectize.js' Render Cache. The standard behaviour of Selectize.js is that the items are cached as returned by the render function, and not with the modifications we have done to them. After adding and deleting some elements, the cached and not the modified version would be displayed if we do not update the render cache acordingly.
    2. Using random id's to identify the elements to select to be manipulated from DOM.
    3. Using watchers to know when the compilation has been done

    First, we define a method to modify the selectize.js render cache:

    scope.selectorCacheUpdate = function(key, value, type){
    
      var cached = selectize.renderCache[type][key];
    
      // update cached element
      var newValue = angular.element(cached).html(value);
    
      selectize.renderCache[type][key] = newValue[0].outerHTML;
    
      return newValue.html();
    };
    

    Then, the render function is defined as follows:

    function renderAvatar(item, escape, type){
    
      // Random id used to identify the element
      var randomId = Math.floor(Math.random() * 0x10000000).toString(16);
    
      var avatar = 
        '<div id="' + randomId + '">' +
          '<span customAvatarTemplate ...></span>' +
          ...
        '</div>';
    
      var compiled = $compile(avatar)($rootScope);
    
      // watcher to see when the element has been compiled
      var destroyWatch = $rootScope.$watch(
        function (){
          return compiled[0].outerHTML;
        },
        function (newValue, oldValue){
          if(newValue !== oldValue){
    
            var elem = angular.element(document.getElementById(randomId));
    
            var rendered = elem.scope().selectorCacheUpdate(item._id, compiled.html(), type);
    
            // Update DOM element
            elem.html(rendered);
    
            destroyWatch();
          }
        }
      );
    });
    
    return avatar;
    

    }

    Note: The key for the render cache is the valueField of the selectize items, in this case, _id

    Finally, we add this function as a selectize render function in the selectize configuration object:

    config = {
      ...
      render: {
        item: function(i,e){
          return renderAvatar(i, e, 'item');
        },
        option: function(i,e){
          return renderAvatar(i, e, 'option');
        }
      },
      ...
    }
    

    For more details, see how this solution has been added to the application that motivated this question: https://github.com/P2Pvalue/teem/commit/968a437e58c5f1e70e80cc6aa77f5aefd76ba8e3.