I have created a collection which should be accessible to client side and server side. But when I try to use it in browser it gives me undefined.
var lists = new Meteor.Collection("Lists");
//lists.insert({Category:"DVDs", items: {Name:"Mission Impossible",Owner:"me",LentTo:"Alice"}});
if (Meteor.isClient) {
// counter starts at 0
Session.setDefault('counter', 0);
Template.hello.helpers({
counter: function () {
return Session.get('counter');
}
});
Template.hello.events({
'click button': function () {
// increment the counter when button is clicked
Session.set('counter', Session.get('counter') + 1);
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
Now when I use lists in client side browser console it gives me undefined.
Define the collection without var keyword. It will make a global variable accessible in the whole application. And define collections uppercase:
Lists = new Meteor.Collection("lists");
It's a good practice.
If you has removed autopublish package, you should subscribe on collection at the client-side
Meteor.subscribe("lists");
publish it at server-side
Meteor.publish("lists", function () {
return Lists.find({});
});
and use lowercase for collection name.
Related
I'm going to be honest. I'm way in over my head here.
I need to scrape data from a dynamic site for my employer. Before the data is visible on the page, there are some clicks and waits necessary. Simple PHP scraping won't do. So I found out about this NodeJS + PhantomJS combo. Quite a pain to set up, but I did manage to load a site, run some code and get a result.
I wrote a piece of jQuery which uses timeout loops to wait for some data to be loaded. Eventually I get a js object that I want to write to a file (JSON).
The issue I'm facing.
I build up the the js object inside the PhantomJS .evaluate scope, which runs in a headerless browser, so not directly in my Node.JS server scope. How do I send the variable I built up inside evaluate back to my server so I can write it to my file?
Some example code (I know it's ugly, but it's for illustrative purposes). I use node-phantom-simple as a bridge between Phantom and Node
var phantom = require('node-phantom-simple'),
fs = require('fs'),
webPage = 'https://www.imagemedia.com/printing/business-card-printing/'
phantom.create(function(err, ph) {
return ph.createPage(function(err, page) {
return page.open(webPage, function(err, status) {
page.onConsoleMessage = function(msg) {
console.log(msg);
};
console.log("opened site? ", status);
page.evaluate(function() {
setTimeout(function() {
$('.price-select-cnt').eq(0).find('select').val('1266').change()
timeOutLoop()
function timeOutLoop() {
console.log('looping')
setTimeout(function() {
if ($('#ajax_price_tool div').length != 6) {
timeOutLoop()
} else {
$('.price-select-cnt').eq(1).find('select').val('25')
$('.price-select-cnt').eq(2).find('select').val('Premium Card Stock')
$('.price-select-cnt').eq(3).find('select').val('Standard').change()
timeOutLoop2()
}
}, 100)
}
function timeOutLoop2() {
console.log('looping2')
setTimeout(function() {
if ($('.pricing-cost-cnt').text() == '$0' || $('.pricing-cost-cnt').text() == '') {
timeOutLoop2()
} else {
var price = $('.pricing-cost-cnt').text()
console.log(price)
}
}, 100)
}
}, 4000)
});
});
});
});
function writeJSON(plsWrite) {
var key = 'file'
fs.writeFile('./results/' + key + '.json', plsWrite, 'utf8', function() {
console.log('The JSON file is saved as');
console.log('results/' + key + '.json');
});
}
So do do I write the price this code takes from the website, get it out of the evaluate scope and write it to a file?
I follow a tutorial with Meteor I try to create a collection, both for client and server. Here is my code:
var lists = new Meteor.Collection("Lists");
if (Meteor.isClient) {
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
As tutorial I have read, when run on server, if I open chrome console and type lists I will receive Meteor.Collection. But when I tried on my machine, I received error:
Reference error. lists is not define
Have I done something wrong? Please tell me.
Thanks :)
Also you can put all your collections inside the /lib/collection.js route (for better practices).
So with that we ensure that meteor loads first the collections, and they will be available on both client/server.
you should remove Autopublish/insecure package, to avoid meteor sends all the collections when load and to control who can or not insert/remove/update on the collections.
meteor remove autopublish
meteor remove insecure.
So a simple collection will look like this.
//lib/collection.js
Example = new Mongo.Collection("Example") //we create collection global
if(Meteor.isClient) {
Meteor.subscribe('Example') //we subscribe both after meteor loads client and server folders
}
now on /server/collections.js
Meteor.publish('Example', function(){
return Example.find(); //here you can control whatever you want to send to the client, you can change the return to just return Example.find({}, {fields: {stuff: 1}});
});
// Here we control the security of the collections.
Example.allow({
insert: function(userId, doc) {
if(Meteor.userId()){
return true; //if the user is connected he can insert
} else{
return false// not connected no insert
}
},
update: function(userId, doc, fields, modifier) { //other validation },
remove: function(userId, doc) { //other validation },
});
Just to try to explain a little more deep the Collection here on meteor, hope it help you GL
I think you have autopulish/autosubscribe turned off. Try
if (Meteor.isClient) {
Meteor.subscribe('lists');
}
if (Meteor.isServer){
Meteor.publish('lists',function(){
return Lists.find();
});
}
For your naming, I'd also recommend you reverse the way you're capitalizing your collections. So instead it would be
var Lists = new Meteor.Collection("lists");
And finally, look at https://github.com/matteodem/meteor-boilerplate for your directory structure so you don't have to do the if meteor.is stuff anymore.
Edit
Full code should look like:
var Lists = new Meteor.Collection("lists");
if (Meteor.isClient) {
Meteor.subscribe('lists');
}
if (Meteor.isServer){
Meteor.publish('lists',function(){
return Lists.find();
});
}
All of your script source files are wrapped in a function closure as part of the build process. In order for your collection to be visible outside of that file (or in your case - attached to the window object) you will need to declare it as a global variable:
Lists = new Meteor.Collection('lists');
Note the lack of var. As #thatgibbyguy pointed out, the accepted pattern is to capitalize collection variables, and camelcase collection names.
I am using node.js / express and returning an empty object if no data exists on server (i.e.: res.send({}) on the express side).
However, since I am using Backbone myCollection.fetch(....)
I am still get a model back, only that it's empty.
As in:
_getLines: function () {
var self = this;
self.m_linesCollection.fetch({
success: function (data) {
$(Elements.FASTERQ_CUSTOMER_LINES).empty();
if (_.size(data.models["0"].attributes) == 0)
return;
},
error: function () {
log('error loading collection data');
}
});
}
as you can see I am doing a dirty check on client side via:
if (_.size(data.models["0"].attributes) == 0)
which works fine... to check if no real models came back, but I am sure there is a better way to check if model is empty, or send something else from server side :/ ?
Just seems so trivial... I must be missing something...
thx,
Sean.
It's a matter of preference, but the easiest way around this is to have your server return an empty array instead of an array of empty objects.
Marionettejs is an opinionated Backbone framework. In it, they define the utility function:
isEmpty: function() {
return !this.collection || this.collection.length === 0;
},
which you would simply add to your view. You could define it in your initialize, as this.isEmpty, or by extending into your view
var MyView = Backbone.View.extend({ ... });
_.extend( MyView, { isEmpty: function() { ... } });
If you return an empty array from your server, the length property of your collection will equal zero and your isEmpty funtion will return true.
Of course, you could just use
if (this.collection.length == 0) { ... }
I'm trying to share a Marionette App with some of its Views. I've read the wiki here, but the example leaves me with a question.
I've got a file with a couple of views in it that will all need to use the request/response system and possibly the commands. I don't want to do var MyApp = require('app'); in all of the Views in the file. I came up with the following, but I think there's probably a better way to do it.
Example:
//Views.js
define( ["marionette"], function (Marionette) {
var App = function(){
return require('app');
};
var ExampleItemView = Marionette.ItemView.extend({
initialize: function(){
App().request("getInfo", "aboutStuff");
}
});
return Marionette.CollectionView.extend({
itemView: ExampleItemView,
initialize: function(){
App().request("getInfo", "aboutStuff");
}
});
Is there a better way to do this?
I'd definitely not inject your app into your views since it can create circular dependencies which are ALWAYS a code smell (regardless of whether they can be solved or not) The simples solution by far is to create a separate (singleton) reqres object which is handled by the app and injected into the views.
//reqres.js
define(['backbone.wreqr'], function( Wreqr ){
return new Wreqr.RequestResponse();
});
//app
define(['reqres'], function(reqres){
reqres.setHandlers({
'getInfo' : function(){
return 'foo';
}
});
});
//Views.js
define( ["marionette", "reqres"], function (Marionette, reqres) {
var ExampleItemView = Marionette.ItemView.extend({
initialize: function(){
reqres.request("getInfo", "aboutStuff");
}
});
return Marionette.CollectionView.extend({
itemView: ExampleItemView,
initialize: function(){
reqres.request("getInfo", "aboutStuff");
}
});
Backbone Wreqr is to be replaced by Backbone Radio in the next major release of Marionette, v3.
To use Backbone Radio you could create a channel and do the following:
/**
* App.js (for example)
*/
var Radio = require('backbone.radio');
// Use the 'data' channel - this could be whatever channel name you want
var dataChannel = Radio.channel('data');
// Handler for a request
dataChannel.reply('getMessage', function(name) {
return 'Hello ' + name + '. Alba gu brath!';
});
/**
* View.js (for example)
*/
var Radio = require('backbone.radio');
var dataChannel = Radio.channel('data');
// Make the request
console.log( dataChannel.request('getMessage', 'Craig') ); // -> Hello Craig. Alba gu brath!
I want to create in the Server script a function that can return a collection plus some extra value.
For example:
Meteor.publish("users", function () {
var users;
users = Meteor.users.find();
users.forEach(function (user){
user.profile.image = "some-url";
});
return users;
});
But this don't work proper. My question is: What is the right way to add a value to a collection reponse in a publish function.
There are 2 ways you can implement a publish function:
By returning a cursor (or an array of cursors)
By using this.added(), this.changed() and this.removed().
Only method 2 allows to modify returned documents.
Please refer to Meteor documentation here. However, since the provided sample code might look complex, here is another one:
// server: publish the rooms collection
Meteor.publish("rooms", function () {
return Rooms.find({});
});
is equivalent to:
// server: publish the rooms collection
Meteor.publish("rooms", function () {
var self = this;
var handle = Rooms.find({}).observeChanges({
added: function(id, fields) { self.added("rooms", id, fields); },
changed: function(id, fields) { self.changed("rooms", id, fields); },
removed: function(id) { self.added("rooms", id); },
}
});
self.ready();
self.onStop(function () { handle.stop(); });
});
In the second sample, you can modify the 'field' parameter before sending it for publication, like this:
added: function(id, fields) {
fields.newField = 12;
self.added("rooms", id, fields);
},
Source: this post.
Is this important to do with the server? You could use the transform function on the client:
Client JS
//Somewhere where it can run before anything else (make sure you have access to the other bits of the document i.e services.facebook.id otherwise you'll get a services is undefined
Meteor.users._transform = function(doc) {
doc.profile.image = "http://graph.facebook.com/" + doc.services.facebook.id + "/picture";
return doc;
}
Now when you do:
Meteor.user().profile.image
=> "http://graph.facebook.com/55592/picture"
I have opened an issue before with regards to sharing a transform onto the client: https://github.com/meteor/meteor/issues/821