具有约束力的风格'计算属性

时间:2014-12-06 11:09:13

标签: ember.js

我有一个组件,它被插入到DOM中作为''标签(例如,默认行为)。该组件的工作是包装第三方jQuery工具,我试图确保它响应"调整大小"事件所以我想明确设置 width height 样式属性。

在组件中,很容易成为style属性:

attributeBindings: ['style'],
style: function() {
    return "width: auto";
}.property('widthCalc'),

在这种情况下,这样可行,但没有做任何有用的事情,因为样式只返回一个静态字符串(width: auto)。

相反,我想做的是 - 根据对计算属性widthCalc的任何更改 - 根据新值设置宽度。所以这是下一个合乎逻辑的步骤:

style: function() {
    var width = $('body')[0].offsetWidth;
    return 'width: ' + width + 'px';
}.property('widthCalc'),

这也是有效的,动态地将DIV设置为身体宽度的宽度(注意:这不是我想要的,但它确实证明了这个简单的绑定有效)。现在我真正想要的是从组件上的计算属性中获取宽度值,但我甚至不必走那么远就遇到麻烦;请注意,我切换到本地化的组件范围选择器而不是全局jQuery选择器:

style: function() {
    var width = this.$().offsetWidth;
    return 'width: ' + width + 'px';
}.property('widthCalc'),

不幸的是,这会导致页面无法加载并出现以下错误:

  

未捕获错误:您执行的某些操作导致视图在渲染后但在插入DOM之前重新渲染。

我想这是Ember run-loop juju,但我不确定如何继续。任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:1)

由于在将组件添加到dom之前无法在组件中调用this.$(),因此请提供初始值,直到组件准备就绪。 例如,

为属性styledidInsertElement事件设置默认值会重新打开该类,并使用style

this.$()定义为计算属性

http://emberjs.jsbin.com/delexoqize/1/edit?html,js,output

<强> JS

App.MyCompComponent = Em.Component.extend({
  attributeBindings:["style"],
  style:"visibility:hidden",
  prop1:null,
  initializeThisStyle:function(){
    this.set("style","visibility:visible");
    this.reopen({
      style:function(){
//          var thisOffsetWidth = this.$().get(0).offsetWidth;
        return "visibility:visible;color:red;background-color:lightgrey;width:"+this.get("prop1")+"px";
      }.property("prop1")
    });
  }.on("didInsertElement")
});

或者处理this.$()引发的错误并提供默认值。之后,当添加组件时,将按计划计算属性。

http://emberjs.jsbin.com/hilalapoce/1/edit?html,js,output

<强> JS

App.MyCompComponent = Em.Component.extend({
  attributeBindings:["style"],
  style:function(){
    try{
      this.$();//this will throw an erro initialy 

        return "visibility:visible;color:red;background-color:lightgrey;width:"+this.get("prop1")+"px";
    }catch(e){
      return "color:blue";
    }
  }.property("prop1"),
  prop1:null
});

答案 1 :(得分:1)

对于我试图解决的组件,我结束了一个对我来说似乎有效的解决方案,我将在下面分享。要了解我收到错误的原因以及如何更直接地解决该错误,请参阅上面@melc的评论。

我的解决方案

我正在解决的是调整包含在Ember组件中的jQuery组件的大小。在许多情况下,仅通过CSS优雅地处理调整大小,但是一些jQuery组件 - 包括来自knob的非常好的aterrien组件 - 具有直接涉及的JS,因此需要容器宽度和高度属性由Ember组件明确设置,以便它做出适当的反应。

解决这个问题时,我意识到我的用例有两个问题:

  1. 解决页面调整大小事件
  2. 调整我的旋钮组件的事实 - 有时 - 在DOM中但在DOM的一部分中是不可见的(更明确的是它在Bootstrap选项卡中没有' t visible)。
  3. 调整大小监听器

    解决方案的第一部分是监听页面的页面级调整大小。我这样做有以下几点:

    resizeListener: function() {
        var self = this;
        self.$(window).on('resize', Ember.run.bind(self, self.resizeDidHappen));
    }.on('didInsertElement'),
    

    页面调整大小处理程序

    当在“页面”级别完成调整大小时,我现在希望我的组件检查调整大小对组件的影响:

    resizeDidHappen: function() {
        Ember.run.debounce(this, function() {
            // get dimensions
            var newWidth = Number(this.$().parent().get(0).offsetWidth);
            var newHeight = Number(this.$().parent().get(0).offsetHeight);
            // set instance variables
            this.set('width', newWidth);
            this.set('height', newWidth);
            // reconfigure knob
            this.$('.knob').trigger(
                'configure',
                {
                    width: newWidth,
                    height: newWidth
                }
            );                          
        }, 300);
    }
    

    这解决了页面调整大小问题,如果它存在于隔离状态,但为了使组件成为一个好主意,也可以解决可见性用例(当然在我的情况下它很关键) 。

    可见性处理程序

    为什么呢?好吧,我能想到两个原因:

    1. 如果没有加载
    2. ,许多jQuery组件都会拒绝加载或执行错误
    3. 当在DOM中不可见时,ember组件似乎无法建立“resize”事件
    4. 一个问题是,可见性更改没有DOM级事件,那么我们如何在不轮询间隔的情况下对可见性的变化做出反应?在大多数情况下,将会有一个控制可见性状态的UI元素。在我的例子中,它是Bootstrap的标签栏,在这种情况下,它们具有在标签可见时触发的事件。大。这是Bootstrap选择器的选择器(假设您在新显示的选项卡的内容区域内):

      visibilityEventEmitter: function(context) {
          // since there is no specific DOM event for a change in visibility we must rely on 
          // whatever component is creating this change to notify us via a bespoke event
          // this function is setup for a Bootstrap tab pane; for other event emmitters you will have to build your own   
          try {
              var thisTabPane = context.$().closest('.tab-pane').attr('id');
              var $emitter = context.$().closest('.tab-content').siblings('[role=tabpanel]').find('li a[aria-controls=' + thisTabPane + ']');
              return $emitter;
          } catch(e) {
              console.log('Problem getting event emitter: %o', e);
          }
      
          return false;
      },
      visibilityEventName: 'shown.bs.tab',
      

      然后我们只需要添加以下代码:

      _init: function() {
          var isVisible = this.$().get(0).offsetWidth > 0;
          if (isVisible) {
              this.visibilityDidHappen();
          }
      }.on('didInsertElement'),
      visibilityListener: function() {
          // Listen for visibility event and signal a resize when it happens
          // note: this listener is placed on a DOM element which is assumed 
          //       to always be visibile so no need to wait on placing this listener
          var self = this;
          Ember.run.schedule('afterRender', function() {
              var $selector = self.get('visibilityEventEmitter')(self);
              $selector.on(self.get('visibilityEventName'), Ember.run.bind(self, self.visibilityDidHappen ));         
          });
      }.on('didInsertElement'),
      visibilityDidHappen: function() {
          // On the first visibility event, the component must be initialised
          if(!this.get('isInitialised')) {
              this.initiateKnob();
          } else {            
              // force a resize assessment as window sizing may have changed 
              // since last time component was visible
              this.resizeDidHappen();
          }
      },
      

      请注意,这也会导致我们调整大小侦听器的微小重构,从didInsertElement事件中删除它的触发器,而是由initiateKnob触发,这不会在Ember组件加载时发生,而是懒惰在DOM的第一个可见点加载。

      initiateKnob: function() {
          var self = this;
          this.set('isInitialised', true);
          var options = this.buildOptions();
          this.$('.knob').knob(options);
          this.syncValue(); 
          this.resizeDidHappen(); // get dimensions initialised on load
          console.log('setting resize listener for %s', self.elementId);
          self.resizeListener(); // add a listener for future resize events
      },
      resizeListener: function() {
          this.$(window).on('resize', Ember.run.bind(this, this.resizeDidHappen));
      },
      

      有效吗?

      在很大程度上但并非完全。这是有效的:

      • 在加载时可见的第一个“标签”按需调整大小
      • 所有标签在切换到时都会调整大小(即,当他们获得可见性时)

      什么行不通的是:

      • 第一个标签以外的标签不会调整大小(也就是说,onresize回调显示已损坏)

      我得到的错误是:

      vendor.js:13693 Uncaught TypeError: undefined is not a function
            Backburner.run        vendor.js:13716 
            Backburner.join       vendor.js:34296 
            run.join          vendor.js:34349 
            run.bind          vendor.js:4759 
            jQuery.event.dispatch     vendor.js:4427 
            jQuery.event.add.elemData.handle
      

      不知道该怎么做...任何帮助将不胜感激。完整代码可以在这里找到:

      https://gist.github.com/295e7e05c3f2ec92fb45.git