Meteor embedded templates cause re-rendering - meteor

On a single page, I'd like to display a list of editors and grouped under each editor, the list of books from this editor. Each editor have many books, and I've many editors, and the editors must be ordered using a score derived by the books associated to this editor. E.g by the first editor is the editor is most popular books (and I this is a completely different problem that I suspect I can solve with some mongodb magic aggregation. see link for associated question at the end).
I want to have a button for "more editors", and one for "more books" under each editor. I've two collections, one for editors and one for books with a link back to the editors and I've setup two subscriptions, one for each with limits. Basically my template does
<template name="onepage">
{{#each editor in AllEditors}}
{{editor.name}}
{{> bookList editor }}
{{/each}}
<button>more editors</button>
</template>
<template name="bookList">
{{#each book in AllBooks}}
{{book.name}}
{{/each}
<button>more books</button>
</template>
The problem is that clicking on "more editors" my entire page re-render.
The new editor is correctly added at the end of the list, but the result is not very user friendly. I can see the problem. Requesting one more editor (using a subscription and a limit) I force to recompute the AllEditors variable, that force to recompute each BookList template. When I ask for more books, the new book is correctly added without flickering as I ask blaze to just add one element and it is smart enough to avoid re-rendering the entire template.
How can I restructure these two templates to avoid this problem ?
Update:
this is not the actual code I'm using, but this is the main idea for the onCreated and helpers functions
Template.onepage.onCreated () ->
template = this
template.limit = new ReactiveVar(10)
template.autorun () ->
limit = template.limit.get()
template.subscribe("editors",limit)
Template.onepage.helpers
'allEditors': () ->
template = Template.instance()
limit = template.limit.get()
Editors.find({},{}, {limit:limit})
And similarly for the book template
Associated question : sort mongo collection based on the score of child documents

When I do something like this, I do not separate into different templates. I use a template helper with a parent/child relationship.
Template.page.helpers({
groups() {
return ...
},
items(parent) {
if(parent) {
return ...
} else {
return null;
}
}
});
Simple example in html:
<div>
{{#each groups}}
{{this.groupname}}
{{#each items this}}
{{_id}}
{{/each}}
{{/each}}
</div>
Just make sure the groupname matches a field in items, so they can be grouped together. Start with an initial limit, then with a click event, expand that limit. This will cause the editors list to grow without the page refreshing. I use this method in quite a few areas with success.

Related

Meteor dynamic content without routing

What is best practice to change content on a page without creating a route?
BlazeLayout.render('mainLayout', { top: 'header', menu: 'menu', main: 'dashboard', bottom: 'footer' });
How can i hide/show template components inside the dashboard without creating a new route? Should this be done in helpers using some sort of if/else logic in the html and using helper for on button click? Let's say i want to show different content inside dashboard template based on button clicks (href).
Please provide a best practice and good solution that is easy with lots of components.
How can i hide/show template components inside the dashboard without
creating a new route? Should this be done in helpers using some sort
of if/else logic in the html and using helper for on button click?
You can do that but you should be aware of some points to keep your code clean and modular:
Try to wrap parts of your dashboard into own templates to keep the code clean
Use ReactiveDict in favor of many ReactiveVar instances
Wrap recurring parts in templates, too to reduce duplicate code
Register recurring helpers globally or in the most upper template of your Dashboard
Subscribe on the parent template to data that is shared across all parts of the dashboard and subscribe to local data in the respective components
Use autorun and subscription.ready() and display a loading indicator until the subscription is ready. Don't wait to have everything loaded before rendering as this may reduce the UX dramatically.
Let's say i want to show different content inside dashboard template
based on button clicks (href).
You can attach a data attribute to the button, that has a specific id of the target to be toggled:
<template name="dashboardComponent">
<a href class="toggleButton" data-target="targetId">My Link</a>
</template>
You can then read this id and toggle it's state in your ReactiveDict:
Template.dashboardComponent.events({
'click .toggleButton'(event, templateInstance) {
event.preventDefault();
// get the value of 'data-target'
const targetId = $(event.currentTarget).attr('data-target');
// get the current toggle state of target by targetId
const toggleState = templateInstance.state.get( targetId );
// toggle the state of target by targetId
templateInstance.state.set( targetId, !toggleState );
}
});
In your template you can then ask to render by simple if / else:
<template name="dashboardComponent">
<a href class="toggleButton" data-target="targetId">My Link</a>
{{#if visible 'targetId'}}
<div>target is visible</div>
{{/if}}
</template>
And your helper is returning the state:
Template.dashboardComponent.helpers({
visible(targetName) {
return Template.instance().state.get(targetName);
 }
});
There could be the problem of sharing the state between parent and child templates and I suggest you to avoid Session where possible. However as beginner it is a lot easier to first use Session and then work towards a more decoupled (parameterized templates) solution step by step.
Please provide a best practice and good solution that is easy with
lots of components.
This is a high demand and it is your competency to work towards both! However here is a short peek into this:
Best practice is what works for you plus can work for others in other use cases. Try to share your work with others to see where it will fail for their use case.
Using routes has the advantage, that you can use query parameters to save the current view state in the url. That adds the advantage, that on reloading the page or sharing via link, the page state can be fully restored.
easy with lots of components is a contradiction and I don't know if you expect some magical puff that solves this complexity for you. As a software engineer it is your competency to abstract the complexity into smaller pieces until you can solve the problem within certain boundaries.

How to prevent firebase-query element to updating the data

I am using polymer and firebase to create a web app. To get a list in the database, I am using the "firebase-query" element of polymerfire.
Now, the list of items I am getting from the database is posts that people are posting on my app. Now I have the firebase-query element like this:
<firebase-query
id="query"
path="/posts"
limit-to-last="15"
data="{{posts}}">
</firebase-query>
and I have dom-repeat template tag to display the posts like this
<template is="dom-repeat" items="[[posts]]" as="post">
<div>[[post.content]]</div>
</template>
Now, if I have bunch of people posting a lot of contents at once, this list would be updating every second, and it would be hard to read.
Is there a way to prevent firebase-query element to stop updating once the initial load is complete, and possibly updating again when I call a certain function?
I tried to add a disabled attribute, but that just deletes whatever is inside the data attribute ({{posts}} in my case), so when disabled is set, nothing shows up on the screen.
I am not familiar with firebase-query, but you can do simple trick. On property posts set observer function.
posts: {
Type: Array,
value: function(){return []},
observer: "debouncePosts"
}
then in js you can define debounce function where I set, for example, 10 000 miliseconds (10 seconds) and called some callback function where I set this.posts to currentPosts property which will be rendered in template.
debouncePosts: function(data) {
this.debounce("debouncePost", function(){
this.set("currentPosts", this.posts);
}.bind(this), 10000)
}
and html template will be like:
<template is="dom-repeat" items="[[currentPosts]]" as="post">
<div>[[post.content]]</div>
</template>
documentation to debounce: https://www.polymer-project.org/1.0/docs/api/Polymer.Base#method-debounce
If you like to do it in different way like update data whenever user presses update, it's really easy now for you. Just set on-tap on some button and then in function you will do something like this.set("currentPosts", this.posts)

Will a publication of all my images slow down my app when I will have more content?

I am currently using EasySearch for my search solution for my app and I have a question on pub/sub of the search results.
Basically, the way the search works is that users will put into a search and a number of posts will be returned (I have a collections called Posts where I implemented the EasySearch).
I assumed EasySearch probably will automatically publish or subscribe depending on the results of the search queries, so I don't think I should worry about sending too many data to the client? (Correct if I am wrong).
However, the problem that I am having at the moment is that each posts is associated with a image from a collections called Images where my pub/sub is simply publish all and subscribe all from client and server and not related to the search at all.
I am just wondering does that mean I won't able to scale since I will be publishing every images to client regardless of the searches?
Post collection
Posts = new Mongo.Collection('posts');
Posts.initEasySearch(['firstName', 'lastName', 'degreeStudy', 'tags'], {
'limit' : 20,
'use' : 'mongo-db'
});
Posts.allow({
update: function(userId, post) { return ownsDocument(userId, post); },
remove: function(userId, post) { return ownsDocument(userId, post); },
});
Image collection
Images = new FS.Collection("images", {
stores: [new FS.Store.GridFS("images")]
});
Template page for showing Search results
<template name="postPage">
<div class="container">
{{#ifEsHasNoResults index="posts"}}
<div class="jumbotron no-results"> <h1>No results found!</h1></div>
{{/ifEsHasNoResults}}
{{#ifEsIsSearching index="posts"}}
{{>loading}}
{{else}}
{{#esEach index="posts"}}
{{> postItem}}
{{/esEach}}
{{> esLoadMoreButton index="posts"}}
{{/ifEsIsSearching}}
</div>
</template>
In short, how can I make my pub and sub of the images collections related to the easySearch result?
I think you are confusing the cursor returned by your publication with the data itself. The cursor is like a way to make dynamic queries without having to load all your data (unless you do load all of them using for example an {{# each images}}). Read that for more info
This being said (no scaling issues ahead), I assume that inside your {{> postItem}} template, you have an image helper fetching the related image in your image collection.
This will work but keep this in mind: your user can open his console and get any image in your Images collection simply by doing an Images.find() related command. So it should be ok as long as you don't have private content. However if you do, you may want to use a method to fetch your image instead of publishing/exposing the whole collection.

Need help pls: Meteor and Famous integration and creation of forms

I am currently using Meteor 0.9.2.2. I am trying to better understand how to build a form in Meteor + Famous without having to put each form element into a Famous surface.
I am using the "gadicohen:famous-views 0.1.7" and "mjnetworks:famous 0.2.2 "
I am using https://github.com/gadicc/meteor-famous-views and have looked at some of the samples of events. I can generate events on the view but seems to have lost the ability to generate events using Jquery (probably Famous alarm bells going off) for fields on the template.
(Note I fid try following What is a recommended way to get data into your meteor template from the front-end for a famous surface? but that just directed me to the examples I am following - sorry still stuck)
For example, if I wanted to have a "blur" event when a contenteditable field changed and used that to update the database, I am not sure how I do it.
BTW, I am bringing in the template via Iron-router:
this.route('someTemplate', {
path: '/',
});
Here's some sample of code of what I have been playing around with:
<template name="someTemplate">
{{#Scrollview target="content" size="[undefined,100]"}}
{{#Surface class="green-bg"}}
<h4 id="main-edit-title" class="editable" data-fieldname="title" data-resourceid="{{_id}}" contenteditable=true>Heading</h4>
<p id="main-edit-message" class="mediumEditable editable" data-fieldname="message" data-resourceid="{{_id}}" contenteditable=true>Summary</p>
{{/Surface}}
{{/Scrollview}}
</template>
Template.someTemplate.events({
'blur .editable': function (e) {
e.preventDefault();
//e.stopPropagation();
var item = $(e.currentTarget);
DO SOME UPDATE STUFF TO THE DATABASE
item.removeClass('resourceUpdated');
},
});
I looked at the 'famousEvents' too and could not seem to get that working. Ie no events fired and that would only be at the surface level, not the field level.
At the view level I was fine and code below worked fine:
Template.someTemplate.rendered = function() {
var fview = FView.from(this);
var target = fview.surface || fview.view._eventInput;
target.on('click', function() {
clickeyStuff(fview);
});
}
I tried the other variants from this page: https://famous-views.meteor.com/examples/events
So the core questions, I think, is: Do I have to move every form element to a Famous Surface? This would be a killer. I am hoping I can still use Jquery or access the DOM for stuff within the template. Note I do see stuff in the Famous FAQ http://famo.us/guides/pitfalls that says don't touch the DOM... so happy to find out how else I should be doing this???
I tried to make this clearer on the events example page, but I guess I'm still not there yet. I'll answer below but please feel free to chime in with how I can improve the documentation.
Inside of a Surface is basically regular Meteor. But outside of a Surface is the realm of famous-views. So you need to have a Meteor template inside of a Surface for events to attach themselves properly - and, as noted in the docs - that template needs to have at least one element in side of it to attach the events. So either (and in both cases, renaming the outer template wrapper but leaving Template.someTemplate.events as is):
<template name="someTemplateWrapper">
{{#Scrollview target="content" size="[undefined,100]"}}
{{#Surface class="green-bg"}}
{{> someTemplate}}
{{/Surface}}
{{/Scrollview}}
</template>
or simply:
<template name="someTemplateWrapper">
{{#Scrollview target="content" size="[undefined,100]"}}
{{>Surface template="someTemplate" class="green-bg"}}
{{/Scrollview}}
</template>
and then move all the Meteor stuff that needs events to it's own template where the events are handled:
<template name="someTemplate">
<h4 id="main-edit-title" class="editable" data-fieldname="title" data-resourceid="{{_id}}" contenteditable=true>Heading</h4>
<p id="main-edit-message" class="mediumEditable editable" data-fieldname="message" data-resourceid="{{_id}}" contenteditable=true>Summary</p>
</template>
Hope that makes sense, just rushing out... let me know if anything is not clear and I'll ammend the answer later.

Drag-and-drop Sortable List in Meteor [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
How would you make this?
The items in the list could correspond to records in a collection, and their position in the list could correspond to a field on each record ("rank" perhaps) which would have to be updated when the 'stop' event occurred.
Would Meteor play nicely with jQueryUI Sortable? What would happen if multiple users attempted to drag and sort the same list at once? Would Meteor need a customized sorting behavior?
EDIT: The below answer is out of date, #cutemachine's answer or http://blog.differential.com/sortable-lists-in-meteor-using-jquery-ui is much closer to the state of the art.
The short answer is: this isn't easy if you want it to be reactive. To make a non-reactive version, just wrap your template in a {{#constant}} tag and hook up the jquery-ui sortable in render as #bento suggested.
To make a reactive version, your sortable widget is going to have to deal with things changing under it (think about being mid-drag when data re-orders itself). Here are some thoughts about how you would go about it:
Unfortunately, it's not going be easy to make it animate, which is going to lead to poor UX. Let's leave that aside for now.
Render the items with something like:
{{#each items}}
{{> item}}
{{/item}}
This will re-order itself when data comes down from the server (without animation).
Set up each item to be draggable when it renders. You could either
i. Use something like jquery-ui draggable, and hook it up in render on the item template. You might have problems doing this as the underlying element may disappear during a drag if the ordering changes from upstream.
ii. implement your own dragging code, perhaps using a lower level library.
When an item is dragged into position, immediately reorder the list locally (that way the user should see the right thing. Hopefully the server will respect the change.. but let's not get into that either).
I think there is a big need for such a widget, done in a meteoric way. It's on my personal radar (but so are a lot of things, including a nice way to re-order with animation).
Meteor's new rendering engine Blaze now plays with jQuery much more nicely. Here is short video which shows how to implement a sortable list with Meteor and jQuery-UI-sortable.
The code can be found in the Meteor repository on GitHub in the example folder.
The most recent example of sortable lists with Meteor and jQuery UI was posted in October 2014 by Differential:
http://differential.com/blog/sortable-lists-in-meteor-using-jquery-ui
Note that previous solutions may not work after Meteor switched to the Blaze templating engine.
**Updated for 1.0
Check out MeteorPad: http://meteorpad.com/pad/TzQTngcy7PivnCCjk/sortable%20demo
Here is the version I implemented - very similar to #tomsabin's, but no Collection Behaviors needed. It has worked well for me with multiple users and is reactive.
HTML
(Probably not a good idea to make the div id the same as _id, I'm sure you'll find a better workaround.)
<template name="myList">
<div class="step_list">
{{#each card}}
{{> card_template}}
{{/each}}
</div>
</template>
<template name="card_template">
<div class="card" id="{{_id}}">
<h3>{{name}}</h3>
</div>
</template>
JS
Template.myList.helpers ({
card : function () {
return Cards.find({}, {sort: {pos: 1}}
)}
})
Template.myList.rendered = function(){
$(".step_list").sortable({
items: ".card",
delay: 100,
refreshPositions: true,
revert: true,
helper: "clone",
scroll: true,
scrollSensitivity: 50,
scrollSpeed: 35,
start: function(event, ui) {
$(ui.helper).addClass("dragging");
}, // end of start
stop: function(event, ui) {
$(ui.item).removeClass("dragging");
}, // end of stop
update: function(event, ui) {
var index = 0;
_.each($(".card"), function(item) {
Cards.update({_id: item.id}, {
$set:{
pos: index++,
}
});
});
}
}).disableSelection();
}
I managed to implement a drag and drop, sortable and editable list using jQuery UI sortable and Meteor Collection Hooks and contentEditable respectively. For a partially working example, check out this demo.
My implementation is as follows (unfortunately it won't be a simple plug in and go example, but I hope to get a demo up and running for this specific example soon):
Client JS for drag, drop and save:
Template.templateName.rendered = ->
Deps.autorun ->
$('#list').sortable
handle: '.handle'
stop: (event, ui) ->
_.each $(event.target).children('div'), (element, index, list) ->
Elements.update { _id: element.getAttribute('data-element-id') },
$set: position: index + 1
Few things to notice here, I am using a 'handle' to drag the element around, as inside each div there are other buttons and editable content. Once the user has dragged an element and dropped it in place, the 'stop' event kicks off and I update every element in that list with the new positioning.
I also have the ability to add elements to the page, which will be positioned at the bottom of the list. Otherwise you could probably get away with using the Meteor Collection Behaviours and/or Mongo Counter packages. However, I utilised Meteor Collection Hooks#.before.insert as follows:
Collection before hook
#Elements.before.insert (userId, doc) ->
highestElement = Elements.findOne({},
sort: { position: -1 }
limit: 1
)
position = if highestElement? then highestElement.position else 0
doc.position = position + 1
Here we're simply getting the highest document, sorting on the position attribute. If it doesn't exist (e.g. the first element to be created) then we initialise the positions to start at 1.
PS: if you don't understand CoffeeScript, copypasta the code to this incredible tool (Js2coffee).
Edit: please see a standalone version here: demo (very slow on Meteor's servers) and source code

Resources