{{#each}} and spinner DOM timing - meteor

I have list of ~150 entries with various filters/sorts.
By default when I have 10 shown response times are OK (it is not slow JS code, as the limiting to 10 is done as last step).
But when I hit expand button it takes 2s to show all entries.
And same time to apply filters/sort etc.
The helper used in {{#each}} is ReactiveVar which returns array.
I want to show spinner before re-rendering of #each starts and turn it off after it finishes.
Something like this pseudocode if it waits for every step to finish and also update DOM.
Tracker.autorun ->
spinner.set(true)
showFeed.set(resultFeed.get())
spinner.set(false)
But if I dont use Meteor.setTimeout to defer that showFeed.set, the spinner is never shown. I dont want to rely on setTimeout. But I dont understand whole flush() afterFlush() enough to pull this off. How can I time it that it waits to finish every of these steps (also with DOM update) before proceeding to next ?

I'm not sure about how to show spinner while DOM is updating, but I would like to suggest you another option: you can improve DOM updates performance by hiding/showing DOM nodes instead of creating/destroying them.
Let me show an example:
Expensive DOM updates (create/destroy):
HTML:
<template name="items">
{{#each filteredItems as item}}
<div class="item">
{{item.content}}
</div>
{{/each}}
</template>
COFFEE:
Template.helpers.items
filteredItems: -> Session.get('filteredItems')
onFiltering ->
Session.set('filteredItems', computeFilteredItems());
Why is this expensieve? Because everytime you do filtering, Blaze will create/destroy DOM nodes and this operation is rather expensieve.
Cheap DOM updates (show/hide):
HTML:
<template name="items">
{{#each allItems as item}}
<div class="item {{#if isItemVisible item}}item--visible{{/if}}">
{{item.content}}
</div>
{{/each}}
</template>
COFFEE:
Template.helpers.items
allItems: -> allItems
isItemVisible: -> Session.get('filteredItemsIds').indexOf(item._id) !== -1
onFiltering ->
Session.set('filteredItemsIds', computeFilteredItems().map((item) -> item._id))
CSS:
.item {
display: none;
}
.item--visible {
display: block;
}
Why is it cheap? Because no DOM nodes will be created/destroyed dynamically, all required nodes will be created on the first rendering. We just add/remove CSS classes dynamically and this operation is way more cheaper than create/destroy.
P.S. I have not perfomed any tests, so I'm not 100% sure, but theretically it should work.

Related

angular material drag&drop calls callback after css transition end

I have implemented list of elements which can be drag and dropped via angular material drag and drop feature
like in tutorial https://material.angular.io/cdk/drag-drop/overview
I've implemented function drop(event) however in my case i need not just move elements in angular model. I need to send request to server and when response from server will come update it. So my function is not changing anything, it creates request to server.
Problem here is that because angular model is not changed at drop function there is an element "jumping" occurs: after element drop element returns to the position where it was and when response from server comes and model is updated element move to new position. For user it's very painful to see that
I want to hide somehow from user that some work is going in the background.
Idea to have duplicated list which is for user and another one for server and update them (user list at the moment of drop and server list at the moment of response) I left as last decision because it's hard to maintain.
I'm trying to resolve it with css animations. As you can see in drag&drop example it uses
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
"transition: transform" to show smooth animation when user drops element. So i want to show user animation about 0.5s. This time should be enough for response from server and when animation is finished model is already updated.
What i'm seeing is that
(cdkDropListDropped)="drop($event)"
drop function is called AFTER transition is ended. So even if I do transform 2000ms drop function will be called after transition end, so after 2s. Is it expected? I thought function should be called after mouse release.
Do you have any thoughts for initial problem(hide model update) and for this css animation transition problem in particular?
Update:
I've created example of my problem
https://stackblitz.com/edit/angular-3nhsxx?file=src%2Fapp%2Fcdk-drag-drop-disabled-sorting-example.css,src%2Fapp%2Fcdk-drag-drop-disabled-sorting-example.ts
drop function is called AFTER transition is ended. So even if I do transform 2000ms drop function will be called after transition end, so after 2s. Is it expected? I thought function should be called after mouse release.
Yes, it is expected. The reason for this has a technically background. cdkDrag create a deep copy of the dragged element and set the position of the other items with position fixed. If the drag finished (mouse up) the animation starts and first when the animation is finished Angular will "resort" the items for a good transitions. Otherwise the elements will "jump" back to start order and then to new order.
You can use cdkDragReleased which fires directly before the animation ends. I think this is what you want. Stackblitz is updated.
Additional informations (container, index, item) can found in cdkDragExited and cdkDragEntered as example. More in the official docs.
Demo:
<div cdkDropListGroup>
<div class="example-container">
<h2>Available items</h2>
<div
cdkDropList
id="test"
[cdkDropListData]="items"
class="example-list"
cdkDropListSortingDisabled
(cdkDropListDropped)="drop($event)"
>
<div class="example-box" *ngFor="let item of items" (cdkDragEntered)="cdkDropListEntered($event)"
(cdkDragExited)="cdkDropListExited($event)" (cdkDragReleased)="dragEnd($event)" cdkDrag>{{item}}</div>
</div>
</div>
<div class="example-container">
<h2>Shopping basket</h2>
<div
id="super"
cdkDropList
[cdkDropListData]="basket"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
<div class="example-box" *ngFor="let item of basket" (cdkDragEntered)="cdkDropListEntered($event)"
(cdkDragExited)="cdkDropListExited($event)" (cdkDragReleased)="dragEnd($event)" cdkDrag>{{item}}</div>
</div>
</div>
</div>
Code
dragEnd(event: any) {
console.log('End', event);
}
cdkDropListEntered(event: any) {
console.log(event);
}
cdkDropListExited(event: any) {
console.log(event);
}

Work with complex animations on Blaze

I actually looking for a good way to play animations on my app context with Blaze.
To be more explicit, I wrote this super simple example:
<template name="global">
<h1>Hi guys!</h1>
{{> foo}}
</template>
<template name="foo">
<h2>I'm a foo!</h2>
<ul>
{{#each elements}}
{{> bar}}
}}
</ul>
<button name="btnAdd">Add new elem</button>
<button name="btnDel">Delete an elem</button>
</template>
<template name="bar">
<li>{{name}}</li>
</template>
Let's assume we got an Iron-router route which render the global Template.
On this particular render (from "navigate") I want each templates to render with fadeIn.
When click on btnAdd button, a new element created. I wish it would render with SlideInLeft effect.
When click on btnDel button, an element is deleted. I wish it would be destroyed with SlideOutRight effect.
When user navigate to another route, I want all template disappear with fadeOut effect.
Every of my attempt so far wouldn't allow me to do this kind of distinction... I couldn't find any package resolving this problem neither.
I'm actually just playing animation by adding/removing Animate.css class (pretty simple to use and good looking!)
To resume, I want a different animation played depending on the source of the rendering.
Does someone had already face this problem?
BONUS QUESTION: Do you know how to chain animations, like:
render global with fadeIn Effect >> then >> render foo with rotateIn Effect >> then >> render every bar with bounceIn effect
For timing, you can use Meteor.setInterval. For example, you can do $('.elementClass').hide('fast') outside setInterval. It will run first and setInterval will run when you want it to.
For the initial effects, you can use:
Template.templateName.onRendered(function(){
$('.elementClass').fadeIn('fast') //note that element is initially hidden (display:none in CSS). you can use effects from jquery and jquery-ui for more effects. You have to add jquery-ui additionally
})
You can also use jQuery in your router.js, using iron:router.
Router.route('/the-url', function() {
this.render('templateName', {
data: function () {
$('.htmlElement').toggle('slide', {direction:'right'}, 200); //note that the element is initially invisible
}
});
}, {
name: 'routeName'
});

Ractive transition not working for partial on update

UPDATE:
Here's a fiddle. It works a little better than my own real app, but still highlights a potential bug (or my misunderstanding). Notice that the "outro" transition doesn't work.
http://jsfiddle.net/k4a81fza/1/
Original:
This is a partial I'm using inside of a parent Ractive:
<script id="session_tpl" type="text/ractive">
<div>
<a href="#" on-tap="showDetail">
{{#if p.project !== null}}
<p intro-outro="fly">
{{project}}
</p>
{{/if}}
</a>
</div>
</script>
Here's how I'm trying to then update the data (which is changed from a different ractive that represents a detail view):
daysRac.set('days[1].sessions[2].project', null);
The <p> tag in in the template successfully disappears, but without the transition. I've tried other transitions and tweaked duration and delay, but it always is just instantly removed.
Ideally I want different outro and intro transitions, which I thought I could achieve with something like this:
daysRac.set(keypathToProject, null, function(){
daysRac.set(keypathToProject, "The New Value");
});
Again, that works to update the project value displayed in the <p>, but without transitions.
Is there a way to accomplish what I'm after?
This is probably a bug, I submitted an issue on GitHub.
The problem is that Ractive updates {{description}} to null before the transition starts. It works correctly if you don't use an expression, i.e. if you change {{#if description !== null}} to {{#if describtion}}.

Meteor templates with CSS transitions

Meteor seems to skip CSS transitions when these are triggered through a template helper.
Is there a way to work around this?
Example:
<template name="example-template">
<div class="example {{myhelper}}"></div>
</template>
Then, "myhelper" would get assigned, through a template helper, a classname that triggers a css transition. But, for some reason, the class is applied but skipping the transition.
I assume this conflicts with Meteor's auto-rendering when the template data sources change, but I don't know how to get around it (I'd like to avoid using jquery for this).
CSS transitions after rendering a new template aren't yet supported by Meteor. The reason is that when rendering the template example-template again, the new HTML is just appended to the DOM with the new classname. Since the DOM changes, the transition doesn't happen.
Your best bet is to use the rendered event in combination with a loading classname:
<template name="example-template">
<div class="example loading"></div>
</template>
Template['example-template'].rendered = function() {
// remove the loading classname here, and have that trigger a transition
}
This is supposed to get easier after new Meteor UI lands (see http://www.youtube.com/watch?v=pGQ-ax5cFnk), but until then you can do this with a preserve directive for your template:
HTML:
<template name="example">
<div id="example-div" class="example {{myhelper}}"></div>
</template>
JS:
Template.example.preserve(['#example-div']);
See http://docs.meteor.com/#template_preserve for more info.

Re-rendering list template causes page to scroll to top

I have some templates that look roughly like this:
<template name="items">
<div class="item-list">
{{#each items}}
{{> item}}
{{/each}}
<div>
{{> loadMore}}
</template>
<template name="item">
<div class="item" id="{{unique_string}}">
<!-- stuff here -->
</div>
</template>
<template name="loadMore">
Load more...
</template>
With associated javascript:
Template.items.items = function() {
return Items.find({}, {limit: Session.get("itemCount")});
}
Template.loadMore.events({
"click": function() {
Session.set("itemCount", Session.get("itemCount") + 10);
}
})
All that together more-or-less gives me something that pretty much works like an infinite scrolling section. (The actual code has a few more moving parts, but this is the important bit.)
Whenever I click on loadMore, though, it both pulls more data down and scrolls me back to the top of the page, rather defeating the purpose of infinite scroll. I can throw in some javascript to scroll back down to where it should be, but that leaves a nasty flicker as the page hops around quicly.
I've tried using preserve on the entire list as well as on each item div to keep them from getting updated, but that didn't seem to stop the scrolling. I've also tried putting {{#isolate}} blocks around just about any and everything, without any luck.
Is there something I can do here to make the page not scroll around while it re-renders? Composing templates differently? Some aspect of preserve or {{#isolate}} that I've missed?
The page scrolls to top because your
Load more... will make the page scroll to top. When your href links to "#" the page will scroll to the DOM element with #"element id". Clicking a link with only "#" will scroll to top.
You have two options:
Prevent the default behaviour on the click event (easy option):
Template.loadMore.events({
"click": function(event) {
event.preventDefault();
Session.set("itemCount", Session.get("itemCount") + 10);
} })
This will stop the page reload
Even better: make the Load more... link to "#{{_id}}" then the page will automatically scroll to the element with the id you provided. This will require some restructuring of the templates and maybe a helper method in the template to give you the id of the last item. But it will make your page load exactly where you want.

Resources