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
Related
I have db structure like this:
datas
-data1
--name
--city
--date
--logs
---log1
---log2
---log3
-data2
--name
...
Now, I released putting 'logs' inside 'data' parent was a huge mistake because its user generated child and growing up fast (so much data under it) and causes delay on downloading 'data1' parent naturally.
Normally I am pulling 'data1' with this:
database().ref('datas/' + this.state.dataID).on('value', function(snapshot) {
... })
I hope i could explain my problem, I just basically ignore 'logs' child (I need name,city,date)
As there project started and users already using this, I need a proper way.
Is there a way to do this on firebase side ?
I don't think you'll have an easy way out of this one...
Queries are deep by default: they always return the entire subtree.
https://firebase.google.com/docs/firestore/rtdb-vs-firestore#querying
I can see only two options:
Migrate the logs to a different location (if it's really a huge amount of data, you could use something like BiqQuery https://cloud.google.com/bigquery or if it's events, you could store them in Google Analytics, it really depends on the volume and type of logs)
Attach multiple listeners instead of a single one (depending on the amount of entries that might be a viable interim solution):
let response={
name:null,
city:null,
date:null
}
const refs = ['name', 'city', 'date'].map(key=>database().ref(`datas/${this.state.dataID}/${key}')
refs.forEach(ref=>ref.on('value',snapshot=>{
})
I'm using Redux in a vanilla JS project. I have a bunch of small modular UI files and controllers and such. In those UI files I might have code like:
const ExampleForm = function (StoreInstance) {
return $('<form />', {
submit: () => {
StoreInstance.dispatch({
type: 'EXAMPLE_DISPATCH',
post: {
message: $TextareaComponent.val()
}
})
return false
}
})
}
The issue is I have a lot of simple view files like this and many of them are nested and I'm finding it to be ugly and error prone to have the store passed as a param to everything.
For example, I trimmed it for brevity but the form component has form element components such as a textarea. Currently I see two options of managing the Store:
Setting it to window when creating it in my entry file (index.js) and then just accessing Store globally. This seems the nicest, although not "best practice" and makes unit testing and server side rendering a bit harder.
Passing it to every component tediously. This is my example above. This I'd consider as "best practice" but it's pretty annoying to do for every file you make almost.
I'm wondering if there's any alternatives or tricks to passing the store instance. I'm leaning towards just making it global.
You could use the constructor pattern and create every view as new ConnectedView(). The ConnectedView would have a memoized instance of the store (this.store within the view), so it doesn't need to be global.
I am making a project with Meteor and I'm having some issues trying to get data out of mongodb in JavaScript. I have the following in a function:
console.log(Time.find({today: "Saturday"}).fetch());
In my publish.js file on the server side I have the following:
Meteor.publish("time", function () {
var currentUserId = this.userId;
return Time.find({user: currentUserId});
});
And In my subscriptions file I have the following:
Meteor.subscribe("time");
This function gets called later down in the code but it returns an empty array. If I run this code in my browsers console it returns an array with 2 objects in it, which is correct. This leads me wondering if I can use the .fetch() function from within my code? As if I leave off the .fetch() it returns what looks like the usual giant object. My real problem is I need the data in the form that .fetch() gives it to me in. I think it's because the function gets triggered before the data gets a chance to load in, as if I switch out the .fetch() for a .count() it returns 0.
Is there any way around this or a fix?
Where are you you running that console.log?
There are a couple fundementals here that I believe you may have glossed over.
1 Pub / Sub
This is how we get data from the server, when we subscribe to a publication i becomes active and begins to send data, this is neither instant or synchronous, (think of it more like turning on a hose pipe), so when you run your console.log, you may not yet have the data on the client.
2 Reactive contexts
One of the fundamental aspects to building anything in meteor is its reactivity. and it helps to start thinking in terms of reactive and non reactive contexts. A reactive context is one that re-runs each time the data it depends on changes. Using an autorun (Tracker.autorun or this.autorun insdie a template lifecycle callback) or a template helper are good examples. By placing it in a template helper it will re-run when the data is available.
Template.Whatever.helpers({
items: function() {
// ...do your find here.....
}
});
As items is a reactive context, depending on the collection data, it re-run when that changes, giving you access to the data when the client has them.
3 Retrieving Non Reactive Data
Alternatively it is also possible to retrieve data non-reactively by using Meteor.call with a meteor method, and then doing something with the result, in the callback to the Meteor.call. Depending on what you're doing, Meteor.wrapAsync may also be your friend here.
a simple example (out of my head, untested) :
// on the server
Meteor.methods({
gimmeStuff: function() {
return "here is your stuff kind sir!";
}
});
// on the client
Meteor.call('gimmeStuff', function(err, result) {
if (err || !result) {
console.log("there was an error or no result!");
return false;
}
console.log(result);
return result;
});
4 Its Unlikely that you actually need ithe .fetch()
If you're working with this in a template, you don't need a fetch.
If you want this to be non-reactive you don't need a fetch
As one of the commenters mentioned, a cursor is just a wrapper around that array, giving you convenient methods, and reactivity.
5 Go Back to the Begining
If you haven't already, I would highly recommend working through the tutorial on the meteor site carefully and thoroughly, as it covers all of the essentials you'll need to solve far more challenging problems than this, as well as, by way of example, teach you all of the fundamental mechanics to build great apps with Meteor.
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
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());