【问题标题】:AngularJS filter causes IE8 to not render two-way bindingAngularJS 过滤器导致 IE8 不呈现双向绑定
【发布时间】:2012-11-14 17:01:25
【问题描述】:

我对 IE8 有一个奇怪的问题,如果我尝试通过 AngularJS 的双向数据绑定在模板中呈现 $scope 变量,它不会用正确的值替换 {{child.name}}。这肯定与以下过滤器的低效率有关:

  filter('truncate', function() {
    return function(name) {
      // It's just easier to use jQuery here
      var windowWidth = $(window).width(),
          nameElement = $('a:contains("' + name + '")'),
          truncPnt = Math.floor(name.length * 0.9);

      while (nameElement.width() > windowWidth * 0.75 && truncPnt > 6) {

        truncPnt = Math.floor(truncPnt * 0.9);
        name = name.substring(0, truncPnt);
        nameElement.text(name + ' ...');
      }
      return name;
    }
  });

然后我将此过滤器与ng-repeat 一起使用:

<a class="entity-name" href="{{child.url}}" title="{{child.name}}" ng-cloak>{{child.name|truncate}}</a>

总体目标是根据屏幕宽度将传入过滤器的变量截断,用“...”替换任何截断的字符。我相当有信心这个过滤器是原因,因为我有一个类似的函数在$(window) 处理程序的.resize() 上调用,如果我要使用 IE8,并调整浏览器窗口的大小,它会导致 @987654328 @ 渲染为正确的值,但前提是我调整浏览器的大小。

更新:


所以我已经摆脱了上面的过滤器,并用一个非常相似的指令代替了它。这是我第一次尝试创建自定义指令,所以我相当肯定它可以做得更好,减去我目前似乎无法解决的明显缺陷。指令如下:

.directive('truncate', function() {

  return {
    restrict: 'A',
    replace: true,
    template: '<a class="entity-name" href="{{child.url}}" title="{{child.name}}">{{child.display}}</a>',
    link: function(scope, element, attr) {
      var widthThreshold = $(element[0]).parent().parent().width() * 0.85;

      scope.$watch('child', function(val) {
        var elementWidth = $(element[0]).width(),
            characterCount = scope.child.name.length;

        while ($(element[0]).width() > widthThreshold || characterCount > 5) {
          characterCount--;
          scope.child.display = scope.child.name.substring(0, characterCount) + ' ...';
        }
      });
    }
  }
});

我将部分替换为简单:

<a truncate="child"></a>

这与过滤器的区别如下(减去明显的过滤器与指令):

  1. windowWidth 替换为widthThreshold,通过将jQuery 的.parent() 链接两次来识别值(本质上,当获取父(x2) 元素而不是窗口的宽度时,它的值更准确)。
  2. child 添加了一个名为display 的附加密钥。这将是用于显示的 child.name 的截断版本,而不是使用 jQuery 的 .text() 并仅使用截断的 child.name 进行渲染。
  3. truncPnt 变为 characterCount(尽量记住不要缩写变量)

现在的问题是 jQuery 冻结了浏览器,直到我终止了 javascript(如果出现提示)。 Firefox 可能会显示它,Chrome 还没有挂起,虽然我还没有在 IE 中进行测试,但我想比前者更糟糕。

可以做些什么来正确地获取两个父元素上方的值,并截断child.display 使其不会包裹/延伸到父 div 之外?

更新 2:


我决定放弃主要基于 DOM 的计算的想法,转而使用数学,考虑父 div 的宽度、字体大小和天知道的比例。我认真地插入了一个公式,直到我得到了无论字体大小都始终给出相似结果的东西。媒体查询确实会影响相关字符串的字体大小 CSS,因此我需要考虑这一点,否则不同字体大小的截断字符串的长度会有一些巨大差异:

.directive('truncate', function() {
  return {
    restrict: 'A',
    replace: true,
    // {{child.display}} will be a truncated copy of child.name
    template: '<a class="entity-name" href="{{child.url}}" title="{{child.name}}">{{child.display}}</a>',
    link: function(scope, element, attr) {
      var widthThreshold = $(element).parent().parent().width() * 0.85,
          // get the font-size without the 'px' at the end, what with media queries effecting font
          fontSize = $(element).css('font-size').substring(0, $(element).css('font-size').lastIndexOf('px')),
          // ... Don't ask...
          sizeRatio = 29760/20621,
          characterCount = Math.floor((widthThreshold / fontSize) * sizeRatio);

      scope.$watch('child', function(val) {
        // Truncate it and trim any possible trailing white-space
        var truncatedName = scope.child.name.substring(0, characterCount).replace(/^\s\s*/, '').replace(/\s\s*$/, '');
        // Make sure characterCount isn't > the current length when accounting for the '...'
        if (characterCount < scope.child.name.length + 3) {
          scope.child.display = truncatedName + '...';
        }
      });
    }
  }
});

有趣的是,我相信我又回到了 Brandon Tilley 关于修改 DOM 与修改范围内的属性的评论。现在我已将其更改为修改属性,它可能会更好地用于过滤器?是否应该在过滤器和指令中处理此类操作的决定因素通常是什么?

【问题讨论】:

  • 由于您直接操作 DOM (nameElement.text) 而实际上并没有返回要在文本中使用的新值,我相信这应该是一个指令。
  • 是的,它看起来确实很老套。我不熟悉 AngularJS 中的指令。我去研究一下。
  • "// ... 不要问... sizeRatio = 29760/20621," .. 哈哈!

标签: javascript jquery angularjs


【解决方案1】:

我参考了文档:

指令

指令是一种教授 HTML 新技巧的方法。在 DOM 编译期间,指令与 HTML 匹配并执行。这允许指令注册行为,或转换 DOM。

http://docs.angularjs.org/guide/directive

过滤器

Angular 过滤器格式化数据以显示给用户。除了格式化数据,过滤器还可以修改 DOM。这允许过滤器处理诸如有条件地将 CSS 样式应用于过滤后的输出等任务。

http://docs.angularjs.org/guide/dev_guide.templates.filters

我只会将过滤器用于更改数据格式的目的,而不是其他目的。老实说,我相信为您的目的使用过滤器是合适的。正如文档所说,过滤器可以修改 DOM 我看不出你应该使用指令的理由,过滤器似乎就是你要找的东西。 (除了一个错误可能会迫使你使用指令)

【讨论】:

  • 大约一个月前,我在 dev_guide.templates.filters 页面添加了以下评论:“我认为应该删除此文本'过滤器也可以修改 DOM。这允许过滤器处理诸如有条件地将 CSS 样式应用于过滤后的输出。我认为过滤器不再这样做了(他们曾经这样做过,请参阅 code.angularjs.org/0.9.19/docs-0.9.19/#!/guide/…)。”
  • @MarkRajcok 可能是这种情况,但由于某些原因,没有为我显示讨论,我只是引用了文档;)我也不明白为什么过滤器不应该在全部?如果我看一下这个 (docs.angularjs.org/guide/…) 示例;它不是必须以某种方式操纵 DOM 吗?
  • 是的 Disqus 问题...我经常需要重新加载页面才能加载 cmets。 angular.filter 的 0.9 文档说编码人员可以在方法中使用以下内容:“this.$element — 包含绑定的 DOM 元素。$element 变量允许过滤器操作 DOM。”然后示例显示“this.$element.css('color', color);”。如今,这种操作是在指令中完成的,而不是过滤器。过滤器现在真的只做 3 件事:1)格式化数据/输出 2)选择数据子集 3)排序。这些都不是真正的 DOM 操作,IMO。
  • @MarkRajcok 选择数据子集正是 Scott 想要做的,所以过滤器是他应该做的事情,对吧?我确实认为这已经包括了 DOM 操作,因为您正在更改 DOM 元素的值(innerHTML() 是 DOM 的一部分,对吗?)。不过,您不会更改 DOM 结构,这是肯定的 :)
  • 好吧,子集部分听起来确实很适合过滤器,但“确定父元素的宽度”部分却不是。这就是为什么我没有尝试回答这个问题的一个原因:) 关于 innerHTML(),我倾向于这样看:过滤器格式/子集/对数据进行排序,基本上只有元素的文本被更改为过滤器的结果。当 Angularians 谈论“DOM 操作”时,我的印象是这种类型的操作被排除在外。 DOM 结构更改(如您所述),或类似 jQuery 的 DOM 遍历或操作似乎符合条件。
猜你喜欢
  • 1970-01-01
  • 2015-03-20
  • 2014-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-24
相关资源
最近更新 更多