【问题标题】:Render angular directives in Selectize.js items在 Selectize.js 项目中渲染角度指令
【发布时间】:2016-11-01 13:31:49
【问题描述】:

我在我的 Angular 项目中使用 angular-selectize 来使用 Selectize.js

要在 Selectize.js 选择器中使用自定义项,我使用的是 Selectize.js 的 render 选项:

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();
  },

其中 avatars 是具有异步行为的自定义指令

问题在于render.item 函数需要一个 HTML 字符串作为输出但是

  • 无法按照render.item 方法的预期以同步方式返回呈现的或“$compileed”的 HTML 字符串。
  • 当项目的元素已经添加到 DOM 后,我不知道如何渲染它们。

请注意,虽然调用了$compile,但由于$compile的异步特性,返回的字符串不会是预期的编译结果,而是编译前的字符串。

【问题讨论】:

  • 在 angular-selectize 的 github 中作为问题添加:github.com/machineboy2045/angular-selectize/issues/142
  • 可能不是首选的做事方式,但你可以使用 Promise 来获得你想要的同步行为吗?
  • @Pytth,我不知道promise 在这里有什么帮助,请注意render.item 函数需要返回一个html string,而不是promise。请分享有关如何在此问题上使用 Promise 的任何想法。谢谢!
  • 你真的需要使用$compile()和$timeout()吗?很确定它在 render.item 中是不可能的,因为 selectize 期望它是同步功能。您是否尝试过简单地将头像作为 html 字符串返回?
  • 你是对的@andree,不需要 $compile 和 $timeout。我已将它们包含在示例中,以便可以看到问题。我已更新问题以更好地反映这一点。

标签: javascript angularjs angular-directive selectize.js


【解决方案1】:

一个想法是使用 DOM 操作,这不是最推荐的 Angular 方式,但我知道了 working on this plunker.second one with custom directive and randomized data to simulate your compiled avatar.

为了模拟您的异步调用,我使用了 ngResource。我的渲染函数返回一个带有特殊类标记compiledavatar 的字符串"&lt;div class='compiledavatar'&gt;Temporary Avatar&lt;/div&gt;"。在一两秒钟内,您将在选择元素时看到临时头像。当 ngResource 调用完成时,我会查找类为 compiledavatar 的元素,然后将 html 替换为我下载的内容。完整代码如下:

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

app.controller('MainCtrl', function($scope, $resource, $document) {
  var vm = this;
  vm.name = 'World';
  vm.$resource = $resource;
  vm.myModel = 1;
  vm.$document = $document;

  vm.myOptions = [{
    id: 1,
    title: 'Spectrometer'
  }, {
    id: 2,
    title: 'Star Chart'
  }, {
    id: 3,
    title: 'Laser Pointer'
  }];

  vm.myConfig = {
    create: true,
    valueField: 'id',
    labelField: 'title',
    delimiter: '|',
    placeholder: 'Pick something',
    onInitialize: function(selectize) {
      // receives the selectize object as an argument
    },
    render: {
      item: function(item, escape) {
        var label = item.title;
        var caption = item.id;
        var Stub = vm.$resource('mydata', {});
        // This simulates your asynchronous call

        Stub.get().$promise.then(function(s) {
          var result = document.getElementsByClassName("compiledavatar")
          angular.element(result).html(s.compiledAvatar);

          // Once the work is done, remove the class so next time this element wont be changed
          // Remove class
          var elems = document.querySelectorAll(".compiledavatar");
          [].forEach.call(elems, function(el) {
              el.className = el.className.replace(/compiledavatar/, "");
          });

        });

        return "<div class='compiledavatar'>Temporary Avatar</div>"
      }
    },
    // maxItems: 1
  };
});

为了模拟 JSON API,我刚刚在 plunker mydata 中创建了一个文件:

{
    "compiledAvatar": "<div><span style='display: block; color: black; font-size: 14px;'>an avatar</span></div>"
  }

当然,你编译的函数应该在每次调用时返回不同的东西。我它给了我同样的证明原理。

此外,如果您的动态代码是 Agular 指令,这里有一个 second plunker 带有自定义指令和随机数据,以便您更好地查看解决方案:

数据包含自定义指令my-customer

[{
    "compiledAvatar": "<div><span style='display: block; color: black; font-size: 14px;'>an avatar  #1  <my-customer></my-customer></span></div>"
  },
  {
    "compiledAvatar": "<div><span style='display: block; color: black; font-size: 14px;'>an avatar  #2  <my-customer></my-customer></span></div>"
  },
(...)

指令定义为:

app.directive('myCustomer', function() {
  return {
    template: '<div>and a custom directive</div>'
  };
});

该应用程序的主要区别在于您必须在替换 HTML 时添加 $compile 并且文本应显示 An avatar #(number) and a custom directive。我得到一个 json 值数组并使用一个简单的随机数来选择一个值。替换 HTML 后,我会删除该类,因此下次只会更改最后添加的元素。

 Stub.query().$promise.then(function(s) {
      var index = Math.floor(Math.random() * 10);
      var result = document.getElementsByClassName("compiledavatar")
      angular.element(result).html($compile(s[index].compiledAvatar)($scope));

      // Remove class
      var elems = document.querySelectorAll(".compiledavatar");
      [].forEach.call(elems, function(el) {
        el.className = el.className.replace(/compiledavatar/, "");
      });
});

另外,我查看了 selectize 库,你不能返回一个承诺......因为它对渲染返回的值执行 html.replace。这就是为什么我去了一个临时字符串的路线,以便稍后检索和更新。 让我知道这是否有帮助。

【讨论】:

  • 不幸的是,此解决方案存在一些问题,并且由于 Selectize.js 缓存渲染元素而无法完全正常工作。请参阅我的以下答案以获取此问题的解决方案:stackoverflow.com/a/38640167/4928558
  • @atfornes 很好。我在测试中没有遇到缓存问题,但感谢您改进解决方案并与我们分享。
【解决方案2】:

此答案基于@gregori 的有用答案,但有以下区别:

  1. 考虑 Selectize.js 的渲染缓存。 Selectize.js 的标准行为是缓存项作为渲染函数的返回值,而不是我们对它们所做的修改。添加和删​​除一些元素后,如果我们不相应地更新渲染缓存,则会显示缓存而不是修改的版本。
  2. 使用随机 id 来标识要从 DOM 中选择以进行操作的元素。
  3. 使用观察者了解编译何时完成

首先,我们定义一个修改selectize.js渲染缓存的方法:

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();
};

那么,render函数定义如下:

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;

}

注意:渲染缓存的键是选择项的valueField,在本例中为_id

最后,我们在selectize配置对象中添加这个函数作为selectize渲染函数:

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

有关更多详细信息,请参阅如何将此解决方案添加到引发此问题的应用程序中:https://github.com/P2Pvalue/teem/commit/968a437e58c5f1e70e80cc6aa77f5aefd76ba8e3

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-18
    • 1970-01-01
    • 2019-04-29
    • 2022-06-19
    • 1970-01-01
    • 2016-01-08
    • 1970-01-01
    • 2014-01-04
    相关资源
    最近更新 更多