【问题标题】:Access DOM elements of asynchronously loaded and rendered data (Ember)访问异步加载和渲染数据的 DOM 元素 (Ember)
【发布时间】:2014-09-14 10:45:51
【问题描述】:

在我的 ember 应用程序中,我使用异步加载的数据(使用 ember 数据和其余适配器)与把手一起显示。加载并渲染数据后,我想操纵这些数据的呈现方式(计算它们所在的位置)。这也需要在视口调整大小时完成。

目前我正在尝试使用中建议的方法来解决这个问题 How to catch whether array was inserted in handlebar in Ember?Run jquery at the end of Ember.CollectionView rendering 。 我什至尝试使用 schedule 而不是 scheduleOnce 导致只调用一次我的显示更新方法,并且仍然没有呈现异步加载的数据,并且在加载和呈现数据后该方法不会再次调用。

export default Ember.View.extend({
templateName: 'calendar',
didInsertElement: function() {
    this._super();
    this.$('#calender_button_new').click(function(){
        document.location.href= "index.html#/add-appointment/select-employee";
    });
    this.$('#calender_button_home').click(function(){
        document.location.href= "index.html#/home";
    });
},

updateCalendar: function() {
    console.log(this.$('.appointment').length);
    var view = this;
    this.$('.appointment').each(function(index, item){
        var id = $(item).attr('data-appointment');
        view.controller.store.find('appointment', id).then(function(appointment){
            var beginning_base = Math.floor(appointment.get('beginning'));
            var end_base = Math.floor(appointment.get('end'));
            // get cell where appointment starts, each cell can be identified by the attributes data-time and data-employee
            var beginning_block = $('td[data-employee="' + appointment.get('doneBy').get('id') + '"][data-time="' + beginning_base + '"]');
            var end_block = $('td[data-employee="' + appointment.get('doneBy').get('id') + '"][data-time="' + end_base + '"]');
            // calculate exact position in the cell when the appointment starts, one cell covers an hour but the appointment might not start at full hour
            var beginning_offset = beginning_block.outerHeight()*(appointment.get('beginning')-beginning_base);
            var end_offset = end_block.outerHeight()*(appointment.get('end')-end_base);
            $(item).css({top:beginning_block.offset().top+beginning_offset-59, 
                        left:beginning_block.offset().left, 
                        width: beginning_block.outerWidth(), 
                        height:end_block.offset().top-beginning_block.offset().top+end_offset, 
                        background:'red'});
        });
    });
},

init: function() {
    this._super();
    $(window).bind('resize', $.proxy(this.updateCalendar, this));
},

willDestroy: function() {
    this._super();
    $(window).unbind('resize');
}

});

有了这种方法,就不必寻找要兑现的承诺 (?)。另一方面,这是我能想到为什么这不起作用的唯一原因。 (注意:在手动调整窗口大小时一切正常。只是初始渲染给我带来了麻烦。) 我实际上必须听什么事件才能使其正常工作?

编辑

也许我应该提到我正在使用 ArrayController 并且视图是数组元素的容器。 结构如下:

 -Calendar (ArrayController, View with afterRender event)
 -Day (Item of Calendar, does not have a view - uses calendar view/template to be displayed)

calendar.hbs-template的相关部分是:

{{#each}}
    {{#each appointment in appointments}}
         <div class="appointment" {{bind-attr data-appointment="appointment.id"}}>{{appointment.title}}</div>
    {{/each}}
{{/each}}

Calendar 的模型只是请求天数(CalendarRoute 的一部分)

export default Ember.Route.extend({
model: function() {
    return this.store.find('Day');
},
});

日模型本身:

export default DS.Model.extend({
date: DS.attr('string'),
appointments: DS.hasMany('Appointment', {
    async: true,
    inverse: null
}),
employees: DS.hasMany('Employee', {
    async: true,
    inverse: null
}),
opening: DS.attr('number'),
closing: DS.attr('number')
});

预约模式

export default DS.Model.extend({
title:  DS.attr('string'),
comment: DS.attr('string'),
doneBy: DS.belongsTo('Employee'),
beginning: DS.attr('number'),
end: DS.attr('number')
});

日模型由控制器支持,但不包含任何相关内容(仅一个计算字段,用于根据日模型的开始和结束时间创建时间段)。

编辑 2

我更新了所有源代码。如果您想知道这是什么样子,请访问http://arshaw.com/fullcalendar/(周视图)。而不是不同的日子,我将员工放在 y 轴上。没有拖放计划,因此我只使用整小时(而不是 30 分钟)的 tiemslots。

【问题讨论】:

  • 你会展示你的模板,以及支持模板的模型(可能还有路由和控制器)
  • 我编辑了我的第一篇文章。如上所述,也许我应该说我使用 ArrayController...

标签: jquery ember.js render


【解决方案1】:

经过更多研究,几天前我终于想出了如何解决这个问题。在此来源 (http://yoranbrondsema.com/loading-animations-asynchronous-models-ember-js/) 中,它描述的正是我所寻找的,但用例略有不同。即使他们的用例的解决方案可能不是最好的(如他们的 cmets 中所述,如果可能的话,最好使用加载路线)。但是,我可以将代码完全用于我的设置。关键部分是我的数据中的 hasMany-relation 与异步加载。所以我所要做的就是在 hasMany-relation 解析后运行我的 jQuery 代码,然后在下一次渲染后调用我的代码:

日历路线

setupController: function(controller, day) {
    this._super(controller, day);
    // Pre-load the comments
    // The 'get' call will result in an AJAX call to get
    // the comments and returns a promise
    Ember.RSVP.makePromise = function(maybePromise) {
        // Test if it's a promise
        if (maybePromise.then) {
            // Then return it
            return maybePromise;
        } else {
            // Wrap it in a Promise that resolves directly
            return Ember.RSVP.resolve(maybePromise);
        }
    };
    var appointments = day.get('appointments');

    // Wait until the promise has been resolved
    appointments.then(function() { 
        // Wait until all templates have finished rendering
        Ember.run.scheduleOnce('afterRender', this, function() {
            // Here goes my jQuery code
        }
    }
}

【讨论】:

    【解决方案2】:

    在 Ember 中观察数组的长度就像观察 property.[] 一样简单。考虑到 JS 正在向 DOM 写入数据,您永远不必真正地观察 DOM!

    第 1 部分:绑定位置

    我认为一个好的 Emberish 方法是对 arrayController 中的每个项目使用 itemController 并将样式属性绑定到 div.appointment 或任何想要重新定位的元素。 style 属性本身将是基于返回数据的计算属性。这是一个简化的例子:

    让我们假设元素的位置与月份有关。

    App.CalendarController = Em.ArrayController.extend({
      itemController: 'day'
    });
    
    App.DayController = Em.ObjectController.extend({
      positioning: function() { 
        var top = 'top: ' + this.get('month') / 2 + 'px;';
        var left = 'left: ' + this.get('month') * 5 + 'px;';
    
        return top + left;
      }.property('month'),
    });
    

    还有你的模板:

    {{#each controller}}
      <div {{bind-attr style=positioning}}>{{title}}</div>
    {{/each}}
    

    或者,您可以将 Em.ColectionView 与 itemViewClass 一起使用。

    第 2 部分:调整大小时重新计算

    至于调整窗口大小,利用百分比定位可能意味着您不必注意调整大小,您可以使用位置和负百分比边距来移动元素相对于其宽度(即创建相对于窗口的位置移动和/或带有 css 的元素宽度)。但是,这不是 JS 解决方案。

    相反,基于 Ember 的解决方案是使用以下方法来触发事件、消除抖动(因此您不会进行过多调用)并重新计算属性 - 请注意:以下内容尚未适应您决定管理视图/控制器的任何方式,但该理论是正确且适用的。同样,这是一个简化的示例:

    App.IndexView = Em.View.extend({
      positionTrigger: false,
      
      watchForResize: function() {
        $(window).bind('resize', $.proxy(this.resize, this));
      }.on('didInsertElement'),
      
      resize: function() {
        // No repeat calls within 200ms
        Em.run.debounce(this, this.toggleProperty, 'positionTrigger', 200);
      },
      
      positioning: function() {
        // Whatever you want to do to the position here
        var top = 'top: ' + this.get('month') / 2 + 'px;';
        var left = 'left: ' + this.get('month') * 5 + 'px;';
    
        return top + left; // Updates style binding in template
      }.property('positionTrigger', 'month'),
    });
    

    请注意:您结合上述两种方法的方式可能会有所不同(例如,在集合上使用 itemViewClass 或在 itemController 中完成所有操作),但您将能够实现您想要的通过结合第一部分和第二部分的方法来实现您想要的效果。

    根据用例,您可能只想在 positionTrigger 属性上有一个观察者来做其他事情。

    【讨论】:

    • 非常感谢您的详细解答。我真的认为第一部分可能是解决方案,直到我意识到我不可能不访问 DOM,因为我需要计算中其他元素的位置。我再次更新了初始帖子。我真的希望我不需要像现在一样做到这一点。您现在可以看到完整的源代码。我希望这可以防止对我尝试做的事情产生任何进一步的混淆。无论如何,我一定会使用第二部分!
    • 没问题。但是您可以访问 javascript 中所有其他同级元素的所有数据。我真的不明白为什么你需要去 DOM。如果您正在谈论基于其他对象的高度(例如表头行)的定位,您可以告诉positioning 属性来观察设置高度的第三个属性,例如propName: function() {return this.$().find('.header').height()}.property('month','positionTrigger').on('didInsertElement')。然而,一个好的 CSS 布局可能是你的救星。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-10
    • 2014-08-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多