2

I'm trying to think of a way round this in a bigger project but have put together this small example in the hope of finding a solution..

The example was put together by creating a new meteor project (using meteor 1.0.3.1) and adding mizzao:bootstrap-3 and using the following files:

test.html

<head>
  <title>test</title>
</head>

<body>
  <div class="container-fluid">
    <div class="row">
      {{> example}}
    </div>
    {{> controls}}
  </div>
</body>

<template name="example">
  <div class="col-xs-{{zoom}}">
    <div class="panel panel-default">
      <div class="panel-heading">
        <h3 class="panel-title"><span data-toggle="tooltip" data-placement="auto" title="Some text that will appear in the tooltip (which should be centered above or below this span) when hovering over the this span">Text that can spill out</span></h3>
      </div>
      <div class="panel-body">
        <p>Foo</p>
      </div>
    </div>
  </div>
</template>

<template name="controls">
  <button id="in">+</button>
  <button id="out">-</button>
</template>

test.css

.panel-title > span {
  display: inline-block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.panel-title div.tooltip {
  white-space: initial;
}

test.js

if (Meteor.isClient) {
  var minZoom = 1;
  var maxZoom = 4;
  Session.setDefault('zoom', 4);

  Template.example.helpers({
    zoom: function () {
      return Session.get('zoom');
    }
  });

  Template.controls.events({
    'click button': function(event) {
      switch(event.currentTarget.id) {
        case 'in':
          if (Session.get('zoom') < maxZoom)
            Session.set('zoom', Session.get('zoom') + 1);
          break;
        case 'out':
          if (Session.get('zoom') > minZoom)
            Session.set('zoom', Session.get('zoom') - 1);
          break;
      }
    }
  })

  test = function() {
    $('.panel-heading').each(function(index) {
      var h3 = $(this).find('h3');
      var span = $(this).find('span');
      span.width('initial');
      if (span.width() > h3.width()) {
        span.width('100%');
      }
    });
    $('[data-toggle="tooltip"]').tooltip();
  }

  Template.example.rendered = function() {
    test();
  }
}

Hopefully it's clear(ish) what I'm trying to achieve which is that the span (which toggles to tooltip on hover) is set to the same width as the h3 when the span would otherwise exceed the width of the h3 - or if the h3 is bigger, then the span is set to it's default (inline / initial width). The reason for this is so that when the tooltip is initialised, the tooltip should appear centered above (or below) the span text. The call to test() when the template is rendered works as expected (you can change the default value of the 'zoom' session to alter the width of the panel when the page is first loaded - I've been using 1 and 4 to test with. However I need to make a call to test() when the DOM updates as a result of the reactive session variable 'zoom' changing which occurs when clicking the + or - buttons. When this happens the panel class col-md-# changes where # is the value of the zoom session variable.

So to summarise, the DOM changes but this isn't in response to a JavaScript call that I'm making directly and isn't due to subscribed data changing so I don't believe I can use the Tracker.afterFlush callback. Since the zoom helper has to return before the DOM is updated, I can't see a way to call 'test()' triggered by any particular event and adding something to the click of the buttons would occur too early does anyone have any ideas?

Also from what I have read, Mutation Observers aren't that widely supported yet so this probably isn't an option.

Thanks

gratz
  • 1,506
  • 3
  • 16
  • 34
  • did you ever figure out a good solution to this? Putting a setTimeout inside a tracker autorun is disgusting :( – xaxxon May 25 '15 at 10:06
  • Unfortunarely not, timeout is ugly but there isn't a trigger/event for updating the DOM. – gratz May 25 '15 at 13:37
  • did you post this to any official meteor place? get any response about their thoughts on how to do it? I'm trying to call focus() on an element that is hidden until after a class is changed on the element.. and a Meteor.setTimeout 50 works, but I wrote the most apologetic comment I've ever written when doing it. – xaxxon May 26 '15 at 07:07
  • I don't think I asked anywhere other than stackoverflow, although there's the new meteor forums now so you could try there.. – gratz May 26 '15 at 08:23
  • I'm posting something now to their forums.. – xaxxon May 26 '15 at 11:08
  • Possible duplicate of [Callback after the DOM was updated in Meteor.js](http://stackoverflow.com/questions/10109788/callback-after-the-dom-was-updated-in-meteor-js) – Cees Timmerman Jan 11 '16 at 16:31

2 Answers2

2

If I got it right you're essentially changing a session variable and would like to have a callback when it changes? If this is correct then wrapping that variable in an autorun function will execute it every time the variable changes.

Additionally if there still are racing issues you could use a setTimeout with a small delay before calling test().

Tracker.autorun(function () {
  Session.get('zoom');
  test();
});
Nick Lammertyn
  • 396
  • 1
  • 5
  • Thanks for the suggestions Nick.. I had already tried both of those before the post, the autorun generally worked but I did encounter race conditions, sometimes it would work, sometimes not. After that I put in a timeout on the click on the zoom buttons (or it could have been in the autorun), which works but this is always a nasty workaround. I was hoping for something event driven / consistent but couldn't think of anything so posted here. Reassuring to know you came up with the same two options though. – gratz Jan 22 '15 at 15:30
  • 1
    Currently there's no way to detect when {{zoom}} has been rendered in the DOM (at least not in your case), so I'm afraid there's no other choice for now. I wouldn't put the setTimeout on the click event though but in the autorun, that way at least you know the variable has been updated. You'll probably be fine with using a really small delay (50ms should be more than plenty), so it shouldn't give any problems. I agree it feels a bit hacky, but from time to time we have no choice ;) – Nick Lammertyn Jan 22 '15 at 19:42
1

Here's my shameless answer after posting to the Meteor forums.

After you've made the changes to cause the reactive change, put your javascript in a Tracker.afterFlush callback.

http://docs.meteor.com/#/full/tracker_afterflush

xaxxon
  • 19,189
  • 5
  • 50
  • 80