How to force Meteor's Blaze to batch changes applied to #each block - meteor

I have a template which is mostly equivalent to:
<div class="line">
{{#each part in line}}
<span class="part">{part}</span>
{{/each}}
</div>
where the line: string[] is a partition of a single line into one or more parts. The problem is that when the content of line changes, the template tries to match old elements with new elements (which is fine) and applies changes one by one (which is not fine) to the DOM. In particular if the old value of line was ["Hello","world"] and the new one is ["Hello world"], then there is a short period of time when the user is presented new value of line[0] combined with old value of line[1], which is ["Hello world","world"]. Most of the time this gets unnoticed, but in case when the parts are long enough compared to the screen width, it might happen that <span class="part">Hello world</span><span class="part">world</span> does not fit into a single line, which in turn causes the whole further content to be moved one line lower, only to be later moved again one line higher when line[1] gets finally removed.
One solution which I currently use is to replace the whole #each loop with custom helper {{{ helpMeRenderThisLine line }}} which builds the HTML string manually, but obviously this violates separation of concerns and my code style.
I'm new to Meteor, but these are the directions I would like to investigate:
Is there a way to render a loop non-reactively? I've heard there was
something like #constant block, but it is no longer supported.
Is there a way to "batch" the whole updating process into a single DOM
reflow?
Is there a way to use the momentum package to remove the
flicker?
Is there a way to use a smaller Blaze template to render the
single line in non-reactive fashion?

Is there a way to render a loop non-reactively? I've heard there was something like #constant block, but it is no longer supported.
It is pretty straightforward to make a part of the the DOM non reactive: you just need to use a non reactive helper. E.g. in you template rendered or created function, you can attach your string array to your template instance (not a cursor using this.myStrings = Collection.find() but an array using this.myStrings = Collection.find().fetch(), this being your template instance).
You then return it in your helper using return Template.instance().myStrings
Is there a way to "batch" the whole updating process into a single DOM reflow?
Actually, it depends on how you refresh the original data. The above solution should do that. Keep in mind that you can nest templates to control the granularity of the DOM refreshes. Also make sure you control your subscriptions in case they are overlapping with the same published content. You can wait for every one to be ready using Template.subscribtionReady() (template level) or subscribtion.ready() (sub level)
Is there a way to use the momentum package to remove the flicker?
The flicker should go away if you refresh properly the data.
Is there a way to use a smaller Blaze template to render the single line in non-reactive fashion?
Not sure about what you ask here but a good practice would be to try to make component level templates, it might help in fine tuning the DOM rendering.
<div class="line">
{{#each part in line}}
{{> Part}}
{{/each}}
</div>

Related

Unable to get current click value through Adobe Launch

I created a click event in adobe launch which will capture the value of the link and send it to analytics. I have created a data element for saving the value and in my DOM I am saving value in local storage.
Local storage code:
$('.card').click(function() {
var name = $(this).text();
localStorage.setItem('productName', name);
});
My problem is when I click the first link no value got saved, but after when I click second link it saves the value of first link and on third link saves value of second link and so on. I want to save current link value in evar3 variable.
Data element:
Rule:
Set variables:
Thanks,
Harshit
I'm scratching my head a little bit about why your jQuery selector doesn't match your Rule selector, but that's probably not immediately related or relevant, considering you said you are seeing data pop, in general, so at face value, I'm going to ignore that.
But in general, it sounds like your jQuery code is getting executed after the Rule is evaluated, so it's effectively one step behind. I'm not sure there's much if anything you can do about that if you aim to keep two separate click event listeners like this.
You're going to have to restructure to have one chain off the other (which I don't think is actually feasible with jQuery > Launch as-is. Maybe if you write two separate custom code blocks with promise chaining but that kind of sidesteps Launch and IMO is over-complicating things to begin with (see below)). Better yet, merge them into a single click event listener. On that note..
Why do you have two separate click event listeners? Is the sole reason for this to pass off the link text? Because you can reference the clicked element in the Launch Rule itself.
You should be able to reference %this.innerText% in the Set Variables fields. Or you can reference this object in custom code boxes within the Rule.
IOW at face value I don't see why you need or should be creating/using that jQuery listener or pushing to local storage like that.
Update:
You commented the following:
I tried with %this.innerText% it is also not showing current values. I
am pushing the value first to local storage because my link values are
generating on runtime through an API. They are not hardcoded. I am
also trying to figure out why my rule is getting fired before my
jquery is evaluated. But when I check in console using
_satellite.getVar('Product Name'); it shows me the correct value, but in debugger console value is wrong. Can you show me the way you want
to create rule to getting it fired correctly ? Thanks,
Okay so your link values are generated runtime through an API call? Well okay now that sounds like where the (timing) issue is, and it's the same issue in principle I mentioned you prolly had between the jQuery and Launch code. Assuming you can't change this dynamic link functionality, you have two options:
1. Explicitly trigger a launch rule (direct call rule) in a callback from the API code that's generating the link values.
This is the better method, because you won't have race condition issues with your API vs. link tracking code. The biggest caveat about this method though is that it requires requires you to actively add code to the site, which may or may not be feasible for you.
I have no idea what your code for generating the link looks like, but presumably it's some ajax call and generated from the success callback. So in the callback, you'd add a line of code something like this:
_satellite.track('product_image_click', {
text : elem.innerText
});
Again, I don't know what your API code that generates the link looks like, but presumably you have within it some element object you append to or update the DOM, so for this example, I will refer to that as elem.
'product_image_click' - This is the value you use for the direct call rule identifier in the interface, e.g. :
And then _satellite.track() call also includes an object payload in 2nd argument that you can pass to the direct call rule. So in the code above, I set a property named text and give it a value of elem.innerText.
Then, within the direct call rule, where you set variables, the data you passed can be referenced from event.details object within custom code box (e.g. event.details.text), or using % syntax within the interface fields (e.g. %event.details.text%).
2. Make use of setTimeout to delay evaluating the link click.
The one advantage of this method over option #1 is that it is passive. You don't have to add code directly to your site to make it work.
However, this is the shadier option, because you don't really know how long it will take for your link to be generated. But generally speaking, if you can determine it takes on average say 250ms for the link generation API to do its thing, and you set the timeout to be called at say 300-500ms, then you will probably be okay most of the time. However, it's never a 100% guarantee.
In addition, if clicking on the link ultimately redirects the visitor to another page, then this solution will likely not work for you at all, since the browser will almost certainly have navigated before this has a chance to execute. Because of this, I normally I wouldn't even mention this as an option, but since you mentioned this link involves an API that generates the link values when clicked, I figured maybe this isn't a navigation / redirect link, so maybe this is an option you can go for, if you can't do option #1.
First, create a direct call rule the same as shown in option #1. This will be the rule that receives the link text and makes the Adobe Analytics (or whatever other marketing tag) calls.
Then, create a separate rule as a click event, similar to what you are currently trying to do (listening for the link click, based on your css selector). In this rule, you won't be setting any AA variables. Instead, add a custom js box with code like this:
var elem = this;
(function (elem) {
window.setTimeout(function() {
_satellite.track('product_image_click', {
text : elem.innerText
});
}, 500);
})(elem);
So when the rule is triggered, it will create a setTimeout callback to call the first rule, passing the link text in the payload. And.. hopefully the 500ms timeout shown in the code example is enough time for the API to do its thing. You may be able to adjust it up or down.
Rather than defining it in Data Element I would say its better to keep it directly in the Rule itself. Please try with this %this.#text%.
Let me know if this helped.

Meteor reactivity with helpers

Debugging an app & I stumbled upon something I never noticed before. For a quick example, I've got a simple link with 2 helpers to style it, like this:
<a class="{{tabHasError}} {{activeTab}}">Test</a>
The helpers that go into this are as follows:
tabHasError: function() {
console.log('invalidated!');
}
activeTab: function() {
if (Session.equals('activeTab', this.tabIdx)) return 'active';
}
Now, every time the Session var changes, activeTab gets invalidated, which is expected. What's not expected is that tabHasError is also invalidated. Why does this happen? Is this normal? Is it because they're both attached to the same element? Aside from merging the functions, any way to avoid this? Or even better, why did MDG make this design decision?
With iron-router, it's normal to observe the behavior you're describing.
The current template in use will be refresh as soon as there is a change into the main computation dependencies. Calling Session.set will call the refresh of the template variable. For sure, it's a lot, but it is one of the simplest way to be sure the template is always up-to-date.
If you're looking for larger app, you could have a look on React.js integration, which will give you the ability to refresh only the good variable on your template.
In fact, in your example, the value of tabHasError should not change, but the re-rendering of the template will called the function tabHasError to check if there is any change. In this case, no.
I'm around if the behavior isn't clear enough. Have a tremendous Sunday!
I noticed that this only happens in an element's attributes. I think this behaviour is very specify, according to Event Minded videos regarding the previous UI engine (Shark): it only rerenders affected DOM elements.
Having in consideration that in your code Blaze is rerendering the DOM element, it makes sense to invalidate previous computations related to it. If you place this helper inside the a element it won't be invalidated.

How to move components around

I'm using the 0.4.0 branch for the components as HTML files functionality. I'm trying to do the following: I have a component that controls the layout of a page. This component has some subcomponents as an array and displays them on different parts of the page based on some data in the subcomponent. Something akin to this (due to layout restrictions they have to be in different parts of the page):
<div id="section1">
<h1> Section 1 </h1>
{{# subcomponents}}
{{#isflagsection1(flag)}}
<subcomponent flag={{flag}}/>
{{/isflag}}
{{/subcomponents}}
</div>
<div id="section2">
<h1> Section 2 </h1>
{{# subcomponents}}
{{#isflagsection2(flag)}}
<subcomponent flag={{flag}}/>
{{/isflag}}
{{/subcomponents}}
</div>
<div id="section3">
<h1> Section 3 </h1>
{{# subcomponents}}
{{#isflagsection3(flag)}}
<subcomponent flag={{flag}}/>
{{/isflag}}
{{/subcomponents}}
</div>
The flag is updated from controls within each component. this works great (the DOM is refreshed each time I modify the flag) except for one issue. Instead of performing a move, the subcomponent is recreated every time the flag changes, e.g. it's destroyed and created a new. This is unfortunate for my use case because of two reasons:
The subcomponent has a rather heavy creation cost (specially in mobile) since it performs some graphics work.
The subcomponent stores some private data (a
history of changes made to the model) that either a) gets lost when
it's moved along to another section or b) has to be stored in the
top component polluting it's data model.
So what I would like to know is, is there a way to "move" the component without deleting/recreating it?
Regards,
V. SeguĂ­
Yes - every Ractive instance has two methods that allow you to do this: ractive.detach() and ractive.insert(). Unfortunately the documentation is currently lacking, but here's how you use it:
// remove the instance from the DOM, and store a document
// fragment with the contents
docFrag = ractive.detach();
// insert the instance into the container element, immediately
// before nodeToInsertBefore (the second argument is optional -
// if absent or `null` it means 'insert at end of container'
ractive.insert( container, nodeToInsertBefore );
If you're removing the instance and immediately reinserting it, there's no need to detach it first - you can just do ractive.insert(). The arguments can be DOM nodes, but they can also be CSS selectors or the IDs of elements.
Here'a a JSFiddle demonstrating: http://jsfiddle.net/rich_harris/Uv8WJ/
You can also do this with inline components (i.e. <subcomponent/> as opposed to new Subcomponent(). In this JSFiddle, we're using ractive.findComponent('subcomponent') method to get a reference to the instance: http://jsfiddle.net/rich_harris/f28t5/.

Fastest way to render dijit widgets

What's the fastest way to render dijit widgets?
I know that the programmatic way is faster than the declarative. (Some reference)
I have a custom widget that loads too slowly (it's a datagrid with combobox, buttons and other small dijit widgets used for adding filters, etc).
Looking at the source, I see that all the dijit widgets are created programmatically, but the DOM nodes where they are inserted into are created programmatically as well.
Is it the "right" way?
I'm trying to speed up the rendering of this widget, and currently my choice would be to combine a velocity template (my company uses struts2 + velocity) to create the DOM nodes, with programmatically created widgets (using placeAt and similar methods to insert the widgets into the already built DOM nodes).
It would work fine, but sadly all the css classes are overwritten by dijit, so I have to overwrite them again, which causes a sensible overhead.
In the template I write something like this:
<input id="idOfAnExistingDomNode" class="myCssClass" />
And to insert a FilteringSelect in that DOM node I have to write this:
var fieldSelect = new dijit.form.FilteringSelect({
store : jsonStore,
searchAttr : "caption",
labelAttr : "caption",
selectOnClick : true,
value : "Content"
}, "idOfAnExistingDomNode");
fieldSelect.domNode.className += " myCssClass";
The last line is necessary because myCssClass is overwritten during the creation of the FilteringSelect.
Is there any way to avoid this issue?
Or, perhaps, I'm trying to do this thing the wrong way? I'm not completely sure about that "velocity template" thing.
Note: Dojo version is 1.5 and no, sadly we can't upgrade it to newer versions.
Please forgive me for my TERRIBLE English.
Often one of the faster ways to instantiate widgets is to create them in a temporary div and then move that div onto the DOM. Have you tried profiling what exactly is slow in this instantiation? Part of me wonders if too much time is being spent waiting for data, as a few widgets + a grid with reasonable pagesize params shouldn't take long to load.
As for your class issue, it is strange that dojo is not mixing in the existing class. That said, you could do a lookup on the node first, get the class attribute, and then specify it as
the class attribute in your mixin object when creating the FilteringSelect. If you do so, be sure you wrap class in quotes or older IE's will reject it.

Template render callback

I'm using Handlebar's {{#each}} to render out my collection to the DOM. After each item is rendered, I want to run a script on these elements. I'm trying to find a callabck function wich fires only once, when the whole render is completed. Meteor's Template.rendered() run's each time a new item is inserted, so it runs as many times as much item I have in my collection. Is there any solution for this?
Template.foo.rendered on the outer template (that contains the {{#each}}) is what you are looking for.
The thing to be aware of is that this will only run when the {{#each}} first renders, but you can't necessarily rely on it to re-run when (for example) more items are added to the collection.
Use Template.created() instead of rendered(). As you've seen, rendered() will run every time the template is rendered and RE-rendered.

Resources