In my Meteor app, I've successfully published data server-side and subscribed to that data client-side. Now, rather than pushing raw data straight to the client's screen, I want to store it as a javascript object, perform some calculations on it (number crunching), and render the result on the client's screen (within an HTML5 canvas element). Every time Mongo is updated, the javascript code should re-run (i.e. the js object is re-set, calculations are performed again from that object, and the new results are rendered on the canvas).
I can grab hold of Mongo data using the Template.example.helpers block and show that directly in the client as follows:
Meteor.subscribe('collection','query');
Template.example.helpers({
sampleData: function(){
return Collection.findOne({query:`query`});
}
});
<template name="example">
<div>
{{sampleData.last}}
</div>
<canvas id="test-canvas"></canvas>
</template>
But what I'm trying to do is grab hold of this data before pushing to the client's screen, within the Template.example.rendered block:
Meteor.subscribe('collection','query');
Template.example.rendered = function(){
// define HTML5 canvas and context variables
var canvas = $("#test-canvas")[0];
var context = canvas.getContext("2d");
// store Mongo data as Javascript variable
// loop over this variable and perform calculations
// draw results to the canvas
}
Am I approaching this the right way? If so, how can I achieve it? Thanks!
It would be probably easier to store the data and perform the calculations in a seperate object and render the template from the object and not straight from the mongo, you can easily keep your templates reactive using "Deps" and it will make you code much more maintainable.
I was able to figure out my own answer to the question posed above. My template was loading before the data was retrieved by the client, therefore I kept getting cannot read property <blank> of undefined in the browser's javascript console and code would interrupt. You need to use the iron-router package in order to
1) set the "data context" of the template you are working on (package up an object containing data sources you need for that specific template), and
2) force the template not to render until the data has been retrieved. Once the data is retrieved, iron-router loads the template and you now have full javascript control of the data object you created above.
Steps at a high-level:
1) Install iron-router
2) Define a route for the template
3) Use the waitOn method to tell iron-router which data source you are subscribing to (which data it is waiting on)
4) Define a "loading" template (aka splash screen) to show while data is being retrieved before template load. As per https://github.com/EventedMind/iron-router/issues/554, you can achieve this by inserting this block of code in your iron-router router.js file:
Router.onBeforeAction(function(pause) {
if (!this.ready()) {
this.render('loading'); // wait
pause(); // ready
}
});
Just make sure you create a loading template to go along with this.
5) Use the data method to set the data context for your template (create the data object to be used in the template)
Steps at a detailed-level:
http://www.manuel-schoebel.com/blog/iron-router-tutorial
Your approach is good.
The key here is to observe collection and update canvas when items in collections are:
added(document) or addedAt(document, atIndex, before)
removed(oldDocument) or removedAt(oldDocument, atIndex)
changed(newDocument, oldDocument) or changedAt(newDocument, oldDocument, atIndex)
movedTo(document, fromIndex, toIndex, before)
There is also more performant way to observe changes in collection.
Code similar to below should help you:
if (Meteor.isClient) {
Template.example.rendered = function () {
var canvas = $("#test-canvas")[0];
var context = canvas.getContext("2d");
Collection.find().observe({
added: function (doc) { // draw sth on canvas },
changed: function (doc) { // draw sth on canvas },
movedTo: function (doc) { // draw sth on canvas },
removed: function (doc) { // remove sth from canvas }
});
};
}
Example: https://github.com/Slava/d3-meteor-basic
Related
I am building an application for customizing the product grid order of an e-commerce website. Being able to present product order in responsive and flexible ways, would really make it easier for project managers to create a better experience for their customers.
Part of this grid order application is based on an article by Ryan Glover (to whom I owe many thanks), called Importing CSV's https://themeteorchef.com/tutorials/importing-csvs, using PapaParse.
Even though this the templates and build is in Meteor, this is a bit of a vanilla JavaScript question.
Here's my pretty stripped down question: How do I get the event object into the template? I get the event object fine from the Papa.parse() function, and could console.log(it). But, how do I pass the event object into the template.
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import './main.html';
// NOTE: trying to figure out how to pass event object to template
Template.upload.events({
'change [name="uploadCSV"]' ( event, template ) {
// Handles the conversion and upload
var fileInput = document.querySelector('[name="uploadCSV"]');
// Parse local CSV file
Papa.parse(fileInput.files[0], {
header: true,
complete: function(results) {
// console.log(results); // includes data, error, and misc
// NOTE: This is the data object to send to the template
let itemData = results.data;
console.log(itemData) // here is the data object
// This test correctly iterates over the object, but should be done in the template
itemData.forEach(function(item) {
console.log(item)
// console.log(item['itemcode'])
});
// HELP! How do I send the object itemData to the template?
} // END complete
}); // END parse
} // END change
}) // END events
Here is a link to the repo. https://github.com/dylannirvana/gridorderapp/tree/master/meteor/vanilla
This has got to be embarrassingly easy. Many thanks in advance!
looking on your project will make clear that this piece will not be ready to go. Your question is not somethin about events but understanding meteor. The are essentials missing.
First check your grid template, there is no data defined. Second, understand Papa.parse on client and on server via Meteor.method
Not sure how you concept should run, but lets assume, that someone should be able to upload an CSV file with data to show in page.
Is this something which is persistant data? Means, should the data be stored or will it be uploaded all the time?
In case of persistant, you should use Meteor.method and a publish and subscribe collection from server to client. You may have a look at the meteor blaze todo app tutorial.
In case of temporary data, you may create a client only minimongo collection and subscribe the client to that.
While getting data from Parser you have to insert or add that content to your local or global collection.
When doing this and having a reactive (subscribed) template to the collection it will automtically show its content.
Again, you should get familar with the meteor blaze todo tutorial, to understand your needs.
Hope that will guide you the right way.
Good luck
Tom
I am working on a Backbone/Marionette project. This project implements a way to cache data on local memory after loading them from server. Therefore data can be access anytime, anywhere within the project.
This makes me wonder what is the better way to populate data to view in my case:
const ChildView = marionette.View.extend({/*...*/});
const ParentView = marionette.View.extend({
// ...
onRender() {
// 1: pass data to child view from parent view
const childView = new ChildView({
data: this.options.data,
}));
// 2: initialize data when creating new child view
const childView = new ChildView({
data: SomeModel.new({/* some properties */}),
}));
},
// ...
});
new ParentView({
data: SomeModel.new({/* some properties */}),
}).render();
Both methods work correctly. However, the project view structure is pretty deep and complicated so I prefer the second way because with the first one I would need to go up and down a lot to check what data is and where it comes from.
Do you think if there are any possible problems with this method?
I prefer the 1st way, passing data from parent to child, but it depends on what your views are doing.
For me, a big advantage of sharing a data object is that updating it within one view updates it in all other views (this will work if you pass an existing backbone Model, or any object as data). This can save a lot of work... when a user updates their background color (for example), you can update it once in your BackgroundColorChoose view, and know that it is already updated everywhere else that data is in use.
In a sense, it doesn't matter where the data came from, only what it represents (because it can be accessed/modified from within any of your views).
I can imagine scenarios where this approach is not good, but I've found it makes a good baseline to start from (and avoids the need to trust browser-caching)
I want to update the data source json via
hot = new Handsontable(container,
.....
in somewhere in the code:
hot.getData().push({/*...*/ });
I want to redraw the hot in html-page after this line. How to force do it?
There are a few ways of doing what you're asking. Here I present two of them, pulled almost straight out of the documentation page.
The first is using the loadData function:
loadData (data: Array)
Reset all cells in the grid to contain data from the data array
This one is self explanatory. The thing with it is that it will trigger hot.render() automatically, as well as a few other events (like afterChange).
If you didn't want to do that, you can use the second method which is more "raw". This one involves basic javascripts principles. Handsontable is initialized with a reference to your data object. If you were to mutate this object, and then render, it would be the same as using loadData but without triggering the events.
I have a Meteor template that should be displaying some data.
Template.svg_template.rendered = function () {
dataset_collection = Pushups.find({},{fields: { date:1, data:1 }}, {sort: {date: -1}}).fetch();
a = moment(dataset_collection[0].date, "YYYY/M/D");
//more code follows that is also dependent on the collection being completely loaded
};
Sometimes it works, sometimes I get this error:
Exception from Deps afterFlush function: TypeError: Cannot read property 'date' of undefined
I'm not using Deps in any context. As I understand it, the collection is being referenced before it is completely finished loading.
I therefore would like to figure out how to simply say "wait until the collection is found before moving on." Should be straightforward, but can't find an updated solution.
You are right, you should ensure that code depending on fetching the content of a client-side subscribed collection is executed AFTER the data is properly loaded.
You can achieve this using a new pattern introduced in Meteor 1.0.4 : https://docs.meteor.com/#/full/Blaze-TemplateInstance-subscribe
client/views/svg/svg.js
Template.outer.onCreated(function(){
// subscribe to the publication responsible for sending the Pushups
// documents down to the client
this.subscribe("pushupsPub");
});
client/views/svg/svg.html
<template name="outer">
{{#if Template.subscriptionsReady}}
{{> svgTemplate}}
{{else}}
Loading...
{{/if}}
</template>
In the Spacebars template declaration, we use an encapsulating outer template to handle the template level subscription pattern.
We subscribe to the publication in the onCreated lifecycle event, and we use the special reactive helper Template.subscriptionsReady to only render the svgTemplate once the subscription is ready (data is available in the browser).
At this point, we can safely query the Pushups collection in the svgTemplate onRendered lifecycle event because we made sure data made its way to the client :
Template.svgTemplate.onRendered(function(){
console.log(Pushups.find().fetch());
});
Alternatively, you could use the iron:router (https://github.com/iron-meteor/iron-router), which provides another design pattern to achieve this common Meteor related issue, moving subscription handling at the route level instead of template level.
Add the package to your project :
meteor add iron:router
lib/router.js
Router.route("/svg", {
name: "svg",
template: "svgTemplate",
waitOn: function(){
// waitOn makes sure that this publication is ready before rendering your template
return Meteor.subscribe("publication");
},
data: function(){
// this will be used as the current data context in your template
return Pushups.find(/*...*/);
}
});
Using this simple piece of code you'll get what you want plus a lot of added functionalities.
You can have a look at the Iron Router guide which explains in great details these features.
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md
EDIT 18/3/2015 : reworked the answer because it contained outdated material and still received upvotes nonetheless.
This is one of those problems that I really wish the basic meteor documentation addressed directly. It's confusing because:
You did the correct thing according to the API.
You get errors for Deps which doesn't point you to the root issue.
So as you have already figured out, your data isn't ready when the template gets rendered. What's the easiest solution? Assume that the data may not be ready. The examples do a lot of this. From leaderboard.js:
Template.leaderboard.selected_name = function () {
var player = Players.findOne(Session.get("selected_player"));
return player && player.name;
};
Only if player is actually found, will player.name be accessed. In coffeescript you can use soaks to accomplish the same thing.
saimeunt's suggestion of iron-router's waitOn is good for this particular use case, but be aware you are very likely to run into situations in your app where the data just doesn't exist in the database, or the property you want doesn't exist on the fetched object.
The unfortunate reality is that a bit of defensive programming is necessary in many of these cases.
Using iron-router to wait on the subscription works, but I like to keep subscriptions centrally managed in something like a collections.js file. Instead, I take advantage of Meteor's file load order to have subscriptions loaded before everything else.
Here's what my collections.js file might look like:
// ****************************** Collections **********************************
Groups = new Mongo.Collection("groups");
// ****************************** Methods **************************************
myGroups = function (userId) {
return Groups.find({"members":{$elemMatch:{"user_id":userId}}});
};
// ****************************** Subscriptions ********************************
if(Meteor.isClient){
Meteor.subscribe("groups");
}
// ****************************** Publications *********************************
if(Meteor.isServer){
Meteor.publish("groups", function () {
return myGroups(this.userId);
});
}
I then put collections.js into a lib/ folder so that it will get loaded prior to my typical client code. That way the subscription is centralized to a single collections.js file, and not as part of my routes. This example also centralizes my queries, so client code can use the same method to pull data:
var groups = myGroups(Meteor.userId());
I've just started to look into using Meteor for an upcoming project, and have a question about data persistence. It sounds like you have two options: First, you can declare a "name" when instantiating a new Collection which will create a database collection which will be saved upon alteration.
Chatrooms = new Meteor.Collection("chatrooms");
The other option is to create an anonymous collection, which won't be saved.
Chatrooms = new Meteor.Collection();
But what do I do if I want to populate a Collection from the database, but not save it upon alteration on the client-side? For instance, I might want to create a collection of user Movies that will be displayed in a grid -- each having their own absolute positioning based upon the sorting and filtering applied to the collection. Upon changes to the collection, the associated views (or templates) will be re-rendered to reflect those changes. But I don't necessarily want these absolute positions to be stored in a database...
Any ideas?
I'm not very clear about your question. But perhaps, you can bind the absolute position into the collection data? They are just normal javascript objects. And the collection data will only be changed through insert/update/remove function call.
I ended up doing something like this:
movies: function() {
var movies = Movies.find().fetch();
_.each(movies, function(movie, index){
movie.left = index * 2;
movie.top = index * 2;
});
return movies;
},
Basically, 'fetch()' allows me to deal with pure JSON objects, making it easier to iterate through them and alter them without performing 'update' commands.