Search code examples
javascriptjquerytwitter-bootstrapmeteorspacebars

Where should a function be called containing Collection data from a Meteor subscription as well as jQuery DOM manipulation?


I'm new to web applications and stumbled across a problem in Meteor I haven't found a good solution to (please let me know if the answer already exists on https://stackoverflow.com/).

I have a profileEdit Template page I want to update with default values from the Meteor.users Collection as it is rendered. This works fine using Template helpers for <input type="text">, but for:

<select class="form-control" name="profileNamePrefix" id="profileNamePrefix">

and

<label><input type="radio" name="profileGender">Male</label>

I had to use jQuery DOM manipulation to get it to work (this could be my real problem):

JS

// GLOBAL FUNCTIONS
updateSelectedOption = function() {
    var currentUserId = Meteor.userId();
    var currentUser = Meteor.users.findOne(currentUserId);
    var profileTitleOptions = $('#profileNamePrefix').children();
    var status = false;
    for (var i = 0; i < profileTitleOptions.length; i++) {
      if (currentUser && currentUser.profile &&
          currentUser.profile.title === profileTitleOptions[i].value) {
        profileTitleOptions[i].selected = true;
        statue = true;
        break;
      };
    };
    return status;
};

updateCheckedRadio = function() {
    var currentUserId = Meteor.userId();
    var currentUser = Meteor.users.findOne(currentUserId);
    var profileGenderRadios = $('input:radio[name="profileGender"]');
    var status = false;
    for (var i = 0; i < profileGenderRadios.length; i++) {
      if (currentUser && currentUser.profile && 
          currentUser.profile.gender === $(profileGenderRadios[i].closest('label'))[0].innerText) {
        $(profileGenderRadios[i]).attr("checked", "checked");
        status = true;
        break;
      };
    };
    return status;
};

These functions work, but I don't know where to place the function calls. Since they get data from the Meteor.users Collection , they should be called as helpers (as discussed here):

JS

Template.profileEdit.helpers({
  'selectOptionIfTitleIs': function() {
    return updateSelectedOption();
  },
  'checkRadioIfGenderIs': function() {
    return updateCheckedRadio();
});

HTML

<template name="profileEdit">
  <form role="form">
    <div class="row">
      <div class="col-sm-2">
        <div class="form-group">
          <label>Title</label>
          <select class="form-control" name="profileNamePrefix" id="profileNamePrefix">
            <option value="Mr">Mr.</option>
            <option value="Ms">Ms.</option>
            <option value="Mrs">Mrs.</option>
            <option value="Dr">Dr.</option>
            <option value="Prof">Prof.</option>
            <option value="Sir">Sir</option>
          </select>
          {{#if selectOptionIfTitleIs}}
          {{/if}}
        </div>
      </div><!-- /.col-sm-2 -->
      <div class="col-sm-2">
        <div class="radio">
          <label><input type="radio" name="profileGender">Male</label>
        </div>
        <div class="radio">
          <label><input type="radio" name="profileGender">Female</label>
        </div>
      </div><!-- /.col-sm-2 -->
      {{#if checkRadioIfGenderIs}}
      {{/if}}
    </div>
  </form>
</template>

This works when the page is refreshed, but when it is redirected from the Navigation bar via Router.go('profileEdit'); it does not update the selected/checked values because the jQuery calls are empty (I'm suspecting this is because they are performed before the HTML is properly rendered). Adding the function calls to onRendered() solves this, but doesn't work when the page is refreshed.

JS

Template.profileEdit.onRendered(function() {
  updateSelectedOption();
  updateCheckedRadio();
});

Is there a way to unify these two function calls and keep the form updated when the page is redirected as well as refreshed? Any tips on how to implement the update functionality in a nicer way in Meteor is also appreciated.

Edited

After a bit of thinking and some help from Peter's solution I ended up solving this issue by using only Spacebars and Template helpers instead:

HTML

<form role="form">
    <div class="col-sm-2">
      <div class="form-group">
        <label>Title</label>
        <select class="form-control" name="profileNamePrefix" id="profileNamePrefix">
        {{#each namePrefixes}}
          <option value="{{value}}" selected="{{prefixIsSelected}}">{{name}}</option>
        {{/each}}
        </select>
      </div>
    </div><!-- /.col-sm-2 -->
    <div class="col-sm-2">
      {{#each genders}}
        <div class="radio">
          <label><input type="radio" name="profileGender" checked="{{genderIsChecked}}">{{name}}</label>
        </div>
      {{/each}}
    </div><!-- /.col-sm-2 -->
</form>

JS

Template.profileEdit.helpers({
  namePrefixes: [{
      name: "Mr.",
      value: "Mr"
    },
    {
      name: "Ms.",
      value: "Ms"
    },
    {
      name: "Mrs.",
      value: "Mrs"
    },
    {
      name: "Dr.",
      value: "Dr"
    },
    {
      name: "Prof.",
      value: "Prof"
    },
    {
      name: "Sir",
      value: "Sir"
    }
  ],
  genders: [{name: "Male"},
            {name: "Female"}
  ],
  'prefixIsSelected': function() {
    var currentUserId = Meteor.userId();
    var currentUser = Meteor.users.findOne(currentUserId);
    return currentUser && currentUser.profile && currentUser.profile.title === this.value;
  },
  'genderIsChecked': function() {
    var currentUserId = Meteor.userId();
    var currentUser = Meteor.users.findOne(currentUserId);
    return currentUser && currentUser.profile && currentUser.profile.gender === this.name;
  }
});

This way the form updates properly when redirected or refreshed. The only question that remains is if I would be forced to update something through jQuery that relies on a data Collection, where would one call that code from? Or is it always possible to find a 'Meteor way' using Spacebars?

Thanks in advance,


Solution

  • You can use template helpers that return true/false directly in the template. The following explanation assumes your current data context (this) is your user or profile which has the data:

    Radio Buttons and Check Boxes

    <input name="gender" type="radio" checked="{{genderCheck 'male'}}"/> Male
    

    and then declare a template helper:

    genderCheck: function(value) {
        return (this.profile.gender == value);
    }
    

    The disabled attribute works in the same way. Blaze (Meteor Templates) removes obsolete checked and disabled attributes when delivering the DOM.

    Selects

    <select name="salutation">
      <option selected="{{checkSalutation 'Mr'}}">Mr</option>
      <option selected="{{checkSalutation 'Mrs'}}">Mrs</option>
      ..
    </select>
    

    And the helper:

    checkSalutation: function(value) {
        return (this.profile.salutation == value);
    }
    

    If you have an {{#each ..}} loop you can pass the element or an attribute of the element to the helper if you like. If you don't you can still access it with this inside the Javascript code. Template helpers refer to this as the current data context (e.g. inside a loop the current loop element).