meteor app - is there a way to create observable objects - meteor

In my meteor app I have the following handler, returning a "contributor" record:
Template["IntroductionWizard_Step_1"].helpers({
contributor: function(n) {
return ContributorCollection.findOne({contributorName: ""});
}
});
This record is being used in a reactive template:
<input type="text" id="name" name="name" class="form-control-element" value="{{contributor.contributorName}}"
As I understand, the reason for this template to track changes to this record is because it came from a reactive source.
What I was wondering is whether it would make sense to create an actual Contributor object, and return that instead of just a record. But, if I was to do that, this object would not be observed for changes, or would it?
Another words, can a more traditional object-oriented approach be used with meteor, having such Model objects as observable and reactive (two-way bindings) as those Collection records?

You can do whatever you want – Javascript is a prototype based, so it's enough to get the right prototype and modify it.
To enhance behavior of collection element, you need to use transform method:
Contributor = function(doc) {
_.extend(this, doc); // initialize object with contents of doc
...
};
Contributors = new Meteor.Collection('contributors', {
transform: function(doc) {
return new Contributor(doc);
},
});
Now you can add methods to contributor's prototype:
_.extend(Contributor.prototype, {
someFunction: function() {...},
otherFunction: function() {...},
...
});
If you want to adjust collection methods, it's even simpler:
Contributors._findOne = Contributors.findOne;
Contributors.findOne = function() {
var contributor = Contributors._findOne.apply(this, arguments);
if(!contributor) {
// initialize and save new contributor
...
}
return contributor;
};
With these techniques you can inject the desired behavior to collection and its elements.

Related

Meteor Iron-Router: Wait for Subscription sequential

I have the following route defined in my iron-router:
this.route("/example/:id", {
name: "example",
template: "example",
action: function () {
this.wait(Meteor.subscribe('sub1', this.params.id));
this.wait(Meteor.subscribe('sub2', <<data of sub1 needed here>>));
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
I want to wait for sub1 and sub2 before rendering my actual template. The problem is that I need a piece of data which is part of the result of sub1 for the sub2 subscription.
How can I wait sequential for subscriptions? So that I can split the wait in two steps and wait for my first subscription to be finished. Then start the second subscription and then set this.ready() to render the template?
A workaround that I thought of was to use Reactive-Vars for the subscriptions and dont use .wait and .ready which is provided by iron-router. But I would like to use a more convenient solution provided by iron-router or Meteor itself. Do you know a better solution for this?
Thanks for your answers!
Publish Composite Package:
If the second subscription is reactively dependent on certain fields from the first dataset -- and if there will be a many-to-many "join" association, it might be worth looking into reywood:publish-composite package:
It provides a clean and easy way to manage associated subscriptions for collections with hierarchical relations.
Publication:
Meteor.publishComposite('compositeSub', function(id) {
return {
find: function() {
// return all documents from FirstCollection filtered by 'this.params.id' passed to subscription
return FirstCollection.find({ _id: id });
},
children: [
find: function(item) {
// return data from second collection filtered by using reference of each item's _id from results of first subscription
// you can also use any other field from 'item' as reference here, as per your database relations
return SecondCollection.find({ itemId: item._id });
}
]
}
});
Subscription:
Then you can just subscribe in the router using:
Meteor.subscribe('compositeSub', this.params.id);
Router hooks:
As a suggestion, hooks in iron-router are really useful, as they take care of a lot of things for you. So why not use the waitOn hook that manages this.wait and loading states neatly?
this.route('/example/:id', {
name: "example",
template: "example",
// this template will be rendered until the subscriptions are ready
loadingTemplate: 'loading',
waitOn: function () {
// return one handle, a function, or an array
return Meteor.subscribe('compositeSub', this.params.id);
// FYI, this can also return an array of subscriptions
},
action: function () {
this.render();
}
});
You can use the configure option to add a template for loading event:
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading'
});
Note regarding the comment in question:
If both subscriptions only depend on the same id parameter passed to it, you can use the following, as mentioned by #abj27 in the comment above -- however, this does not seem to be the case, going by your example:
Publication:
Meteor.publish("subOneAndTwo", function (exampleId) {
check(exampleId, String);
return [
FirstCollection.find({ _id: exampleId });
SecondCollection.find({ firstId: exampleId })
];
});
Subscription:
Meteor.subscribe('subOneAndTwo', this.params.id);
So just check what you need and use a solution accordingly.
https://github.com/kadirahq/subs-manager
With this package, you can assign a subscription to a variable. Then, you can check that variable's ready state. I just got this working... after years of trying to understand.
Here is my code snippet that works, but oddly I had to wrap it in a 1ms timeout to work...
```
Router.route('/activity/:activityId', function (params) {
var params = this.params;
setTimeout(function(){
var thePage = window.location.href.split("/");;
window.thePage = thePage[4];
dbSubscriptionActivites.clear();
window.thisDbSubscriptionActivites = "";
window.thisDbSubscriptionActivites = dbSubscriptionActivites.subscribe("activityByPage", window.thePage);
Tracker.autorun(function() {
if(window.thisDbSubscriptionActivites.ready()) {
dbSubscriptionComments.clear();
window.thisDbSubscriptionComments = "";
window.thisDbSubscriptionComments = dbSubscriptionComments.subscribe('fetchComments', "activity", Activities.findOne({})._id);
BlazeLayout.render("activity");
$('body').removeClass("shards-app-promo-page--1");
}
});
},1); // Must wait for DOM?
});
```
Examine: window.thisDbSubscriptionActivites = dbSubscriptionActivites.subscribe("activityByPage", window.thePage);
I'm setting as a window variable, but you could do a const mySub = ...
Then, you check that in the autorun function later.
You can see there is where I am doing subscriptions.
I suppose I really should move the BlazeLayout render in to another .ready() check for the comments.

Meteor.js: template.<html>.events vs Template.<template>.events 'this' binding seems inconsistent

I'm looking through the Meteor simple tutorial and the way that 'this' binding in the different Template objects works seems inconsistent to me in my unknowledgeable state.
Template.body.events({
"submit .new-task": function(event) {
console.log(this); // Logs an empty object
}
})
Template.task.events({
"click .toggle-checked": function() {
console.log(this); // logs a task
}
});
I can see that task is an xml template defined in the view, which is a visual representation of the items returned by a function in the Template.body.helpers object.
I guess that the task objects are bound the html representation of each object (though I can't see how as there doesn't seem to be any identifying property within the li elements??)
Anyhow. When I click the task, this is the task. But when I submit the form, I was expecting this to be the body. Why is it not?
I was expecting Meteor to handle Template.body and Template.task in a similar way
In Meteor this referes to the data context. You define it with helpers or with the route controller ( IronRouter or FlowRouter)
Example:
{{#with myData}}
<h1>{{title}}</h1>
{{/with}}
js
Template.yourTemplate.helpers({
myData : function(){
return {
title : "My title"
}
}
})
You need to use the "event" argument
Template.task.events({
"click .toggle-checked": function( event , instance ) {
console.log( event );
}
});
The instance argument is also very useful. You have access to a jQuery selector like: instance.$() and it will only search for elements on your template and also child templates.
Personally I use the instance a lot. My Favorite pattern is:
Template.task.onCreated(function(){
this.vars = new ReactiveDict();
this.data = "some data";
});
Later if you want to access vars or data:
Events - You get this on the arguments
Helpers - var instance = Template.instance();
With instance you avoid storing states in the global namespace, like Session, and your code is a lot easier to maintain and understand. I hope this helps you to understand how template works in Blaze.

How can I be updated of an attribute change in Meteor?

I have a template that subscribes to a document. Everything works fine in the DOM and Blaze updates as soon as an attribute used in the template helpers is changed.
I also have some custom logic that doesn't appears in the DOM and depends on the document attributes. How can I call a function to change that logic when an attribute is updated?
I'm looking for something like this.data.attr.onChanged where this would refer to the template and this.data is the data send to the template, as usual; or a Meteor function that is rerun on change where I could put my callback in.
I hoped that template.onRendered would be recalled, but that's not the case.
I've read a lot about reactive variables, but could not find how they could be useful here.
[edit] the change is coming from the server that is communicating with another service
I've tried Tracker.autorun like this:
Template.editItem.onRendered(function() {
var self = this;
Tracker.autorun(function () {
console.log("tracker", self.data.item.socketId);
});
});
And the corresponding route is:
Router.route('editItem', {
path: '/edit/:_id',
waitOn: function () {
var sub = Meteor.subscribe('item', this.params._id);
return [sub];
},
data: function () {
return {item: Items.findOne(this.params._id)};
},
action: function () {
if (this.ready())
this.render();
}
});
At some point, the property socketId gets removed from the corresponding document by the server and I'm sure of that since I've checked in the shell, but the tracker doesn't rerun.
Use Template.currentData().item.socketId instead of self.data.item.socketId, this will give you reactivity.
And in templates generally, use self.autorun instead of Tracker.autorun (unlike Tracker.autorun, this will ensure that the autorun is stopped when the template is destroyed). Likewise, if you want to subscribe in a template, use self.subscribe instead of Meteor.subscribe.
Code to see if Template.currentData() works for you:
Template.editItem.onRendered(function() {
var self = this;
self.autorun(function () {
console.log("tracker", Template.currentData().item.socketId);
});
});
I'm not sure if I got you right, you just want to observe your html inputs and apply the new value to your helper method(s) on change?!
If so, you could use session variables to store your temporary UI state:
// observe your input
Template.yourTemplate.events({
"change #inputA": function (event) {
if(event.target.value != "") {
Session.set("valueA", event.target.value);
}
}
}
// apply the changed value on your helper function
Template.yourTemplate.helpers({
getSomeData: function() {
var a = Session.get("valueA");
// do something with a ..
}
}
In meteor's official todo app tutorial this concept is also used.
If you need to re-run something which is not part of DOM/helper, you can use Tracker.autorun. According to meteor docs, Run a function now and rerun it later whenever its dependencies change.
here's the docs link
Try moving the subscription into Tracker.autorun
Template.editItem.onRendered(function() {
var self = this;
Tracker.autorun(function () {
Meteor.subscribe('item', this.params._id);
console.log("tracker", self.data.item.socketId);
});
});
Of course you can't use this.params there so you can store this as a Session variable

Passing a Blaze Template as a Parameter to a function creates Famous.js surface

I am trying to update my famous.js surfaces' content by using Meteor's Blaze.toHTMLWithData(template, data), like Blaze.toHTMLWithData(Template.roomIlanSpecsTemplate, data), with a custom template in a function creating a famous surface inside a famous view. I want to pass the template in the cursorToArray function depending on the type of document returned to its callbacks. But I cannot have a rendered page on the browser, even there is no error in the console. If I use hardcoded version like having createFn function for each different template and then defininig and cursorToArray fucntion with that function it works.
What can be the thing I miss here?
cursorToArray = function(cursor, renderablesArray, template, createFn){
//each callback should decide which createFn to use based on result document, cos each result has a different template so a createFn.
cursor.observe({
addedAt: function(document, atIndex, before) {
renderablesArray.splice(atIndex, 0, createFn(document, template));//createRoomIlanView, createRoomRenterIlanView, createFriendLookupIlanView
},
changedAt: function(newDocument, oldDocument, atIndex) {
renderablesArray[atIndex] = createFn(newDocument, template);
},
});
}
cursorToArray(Ilans.find(), ilanViews, Template.roomIlanSpecsTemplate, createIlanView);
portion of the createFn definiton:
function createIlanView(data, template){
var ilanSpecsSurface = new Surface({
content: Blaze.toHTMLWithData(template, data),
properties: {
fontSize: "14px"
}
});
return ilanSpecsSurface;
}
If it is all about older Famous what about using Reactive Surface from https://stackoverflow.com/a/30445791/2288496
var ReactiveTemplate = famodev.ReactiveTemplate;
var reactive = new ReactiveTemplate({
template: Template.mytemplate,
data: Collection.find().fetch(),
properties: {}
});
A good example how to implement Routing, Subscriptions etc. https://github.com/sayawan/flowwy

meteor,Dynamically change template helper data

I have a small doubt,Is it possible to change the template helper data dynamically.
Here is my template
{{#each post}}
<h4>{{postyname}}</h4>
<h4>{{Headline}}</h4>
<h4>{{Location}}</h4>
{{/each}}
and this is my helper data
post:function()
{
posts.find({},{sort: {createdAt: -1}}).fetch();
}
and it is displaying results in my homepage and i have search bar at the top of this page and whenever user clicks search button the same template must be render but with different data depend on user search.
I've tried like this on my onclick event,looks like it is not working
'onclick #searchBtn':function()
{
var all_posts=posts.find({'Headline': search},{sort: {createdAt: -1}}).fetch();
Template.postDisplay.post=function(){
return all_posts;
}
How to do this?
I believe using Dep Dependency is what you want to use:
Create a variable on the top of your js file
var _deps = new Tracker.Dependency;
var searchCriteria = {};
In your helper:
post:function() {
_deps.depend(); // creates a dependency between the accessor of "post" and the _deps
posts.find(searchCriteria,{sort: {createdAt: -1}}).fetch();
}
On your button click:
'onclick #searchBtn':function() {
searchCriteria = {'Headline': search};
_deps.changed(); // notifying everyone that is dependent on _deps that it has changes
}
Based on what I see from your question I believe this should handle your situation. Let me know if I misunderstood something in your problem.
Why not just use a Session variable? They are reactive, just what you need.
Instead of manually handling a dependency, using Session.set and Session.get will register and fire dependencies automatically..
post:function()
{
var searchCriteria = Session.get("searchCriteria") || {};
posts.find(searchCriteria,{sort: {createdAt: -1}}).fetch();
}
'onclick #searchBtn':function()
{
Session.set("searchCriteria", {'Headline': search});
}
The Session variable would also be preserved after a hot code reload. A variable would not.

Resources