jQuery select not working in Meteor 1.1.0.2 onRend

2019-08-14 04:30发布

问题:

I am working on an app I had deployed, and trying to get everything up to the latest version and update the code to take advantage of the latest processes, like subscribing in the Template.onRendered, but I have seemingly broken my sortable.

My template (simplified somewhat)

<template name="formEdit">
  <div id="formContainer">
    {{#if Template.subscriptionsReady}}
      {{#with form this}}
        <table id="headerFieldsTable" class="table">
          <tbody id="headerFields">
            {{#each headerFields}}
              <tr class="headerFieldRow">
                <td>{{> headerFieldViewRow }}</td>
              </tr>
            {{/each}}
          </tbody>
        </table>

        <h5>Form Fields</h5>
        <table id="formFieldsTable" class="table">
          <tbody id="formFields">
            {{#each formFields}}
              <tr class="formFieldRow">
                <td>{{> formFieldViewRow }}</td>
              </tr>
            {{/each}}
          </tbody>
        </table>

      {{/with}}
    {{else}}
      Loading...
    {{/if}}
  </div>
</template>

And the template's onRendered():

Template.formEdit.onRendered(function() {
  var formsSubscription = this.subscribe('formById', this.data);
  var headerFieldsSubscription = this.subscribe('headerFieldsForForm', this.data);
  var formFieldsSubscription = this.subscribe('formFieldsForForm', this.data);

  var formEditTemplate = this;
  this.autorun(function() {
    if (formsSubscription.ready() && headerFieldsSubscription.ready() && formFieldsSubscription.ready()) {
formEditTemplate.$(''));
      formEditTemplate.$('#headerFields').sortable({
        axis: "y",
        stop: function(event, ui) {
          var headersToSave = [];
          $('#headerFieldsTable div.headerField').each(function(idx, headerFieldDiv) {
            var header = Blaze.getData(headerFieldDiv);
            header.sequence = idx;
            headersToSave.push(header);
          });
          _.each(headersToSave, function(header) { header.save(); });
        }
      });

      formEditTemplate.$('#formFields').sortable({
        axis: "y",
        stop: function(event, ui) {
          var feildsToSave = [];
          $('#formFieldsTable div.formField').each(function(idx, formFieldDiv) {
            var field = Blaze.getData(formFieldDiv);
            field.sequence = idx;
            feildsToSave.push(field);
          });
          _.each(feildsToSave, function(field) { field.save(); });
        }
      });
    }
  });
});

But for both the headers and footers, the formEditTemplate.$('#headerFields') and formEditTemplate.$('#formFields') both seem to return no results. It seems like the DOM is not actually present. I thought the .ready() call on all the subscriptions would correct that, but think there is a timing issue where Blaze hasn't fixed up the DOM yet, but the subscriptions are indeed done. I say this because I put a breakpoint in Chrome right at the first line of the if, and the browser was still showing "Loading...".

I also attempted to hot-wire things by having a helper that setup the sortable placed at the end of the {{#with}} block, hoping that maybe it would be rendered last, but that didn't work either.

I found some articles on the Meteor forums that seemed to suggest adding a timer, but this seems very "hackish". Is there a new pattern for running JS that requires the DOM to be fully initialized?

回答1:

Instead of the time delay hack, I recommend you use Tracker.afterFlush() to guarantee that the DOM has been created and updated. Here is a description from Meteor docs:

Schedules a function to be called during the next flush, or later in the current flush if one is in progress, after all invalidated computations have been rerun. The function will be run once and not on subsequent flushes unless afterFlush is called again.

So inside of your if statement, you can wrap the code block like so

if (formsSubscription.ready() && headerFieldsSubscription.ready() && formFieldsSubscription.ready()) {
  Tracker.afterFlush( function () {
    //Code block to be executed after subscriptions ready AND DOM updated
  });
}

Here is a reference with examples using Tracker.afterFlush.