I am trying to code with DRY principle. I have multiple templates that should share the same onRender code:
Template.cardA.onRendered(function () {
coolLogic()
});
Template.cardB.onRendered(function () {
coolLogic()
});
Is it possible for me to avoid having to repeat coolLogic()?
The only drier solution I can think of, is to drop the anonymous function around coolLogic.
Template.cardA.onRendered(coolLogic);
Template.cardB.onRendered(coolLogic);
Template is an object, so you can iterate its members:
_.each(["cardA", "cardB"], function(t) {
Template[t].onRendered(function () {
coolLogic()
});
});
Related
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.
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
I am using webdriverJS and Jasmine to perform an end-to-end testing of a web page. I would like to test if an element has class under certain circumstances, but I would like to do it using methods from pure jasmine.
This is the part of the code where the issue is located:
describe('Header bar', function() {
it('should show/hide elements accoding to the window position', function() {
this.driver.executeScript('scroll(0, 1000)');
var elemSearch = this.driver.findElements(webdriver.By.id('animatedElement, animatedElement2, animatedElement3'));
expect(elemSearch).toContain('appear-2');
});
})
Do you know if there's a way to solve this issue, or a couple of examples I could look at, without using extensions like jasmine-jquery?
Thanks in advance for your replies!
If you don't want having jasmine-jquery or other third-party packages introducing custom jasmine matchers as a dependency, you can always extract the toHaveClass() matcher implementation and use it. Note that having your assertion logic encapsulated inside custom matchers helps to follow the DRY principle and make your tests cleaner.
FYI, here is toHaveClass implementation we are currently using:
beforeEach(function() {
jasmine.addMatchers({
toHaveClass: function() {
return {
compare: function(actual, expected) {
return {
pass: actual.getAttribute("class").then(function(classes) {
return classes.split(" ").indexOf(expected) !== -1;
})
};
}
};
},
});
});
Refer to the code below please:
Router.route('/posts/:_id', function () {
this.render('Post', {
to: 'content',
data: function () {
return Posts.findOne({id: this.params._id});
}
});
});
If a Post object has title and body fileds in MongoDB, I can access them from Post.html template like
<h4>Post title: {{title}}</h4>
<h3>Post body: {{body}}</h4>
I would like to access Post object from Post.js in a template helper function. Is it possible?
Update:
According to this question: Meteor data-context with iron-router, I can access the data variable like this:
Template.Post.rendered = function() {
console.log(this.data)
}
Is there a way to do this inside Template.Post.events ?
Seems like you are looking for the Template.currentData() method.
Template.example.events({
'click #test':function(e,t){
console.log(Template.currentData())
}
})
update Seems like using currentData have differents behaviors depending the case check this
So it seems like if you want to use it, you it should be inside a DOM element.
Template.post.events({
'click h4':function(){
console.log(Template.currentData()) // and should return the title.
}
})
based on the stubalio says.
Inside an event handler, returns the data context of the element that
fired the event.
I am new to Meteor and have a slight problem with Meteor.
I created a link which is
http://localhost:3000/game?id=7lJ8F
how would I get the id value in the query string and return it to a helper.
I have looked for answers but found none.
Router.route('/game/:_id', function(){
Session.set("gameid",this.params.query.id);
});
Template.gamebefore.helpers({
ids: function () {
return Session.get("gameid");
}
});
I know this is all wrong but I am quite desperate to find an answer so any will help. Thank You!
How to handle and get the parameters of an url are described in the guide of iron router
You might miss the _:
this.params.query.id
should be
this.params.query._id
or vice versa, since in your url is
/game?id=7lJ8F
in your route its
Router.route('/game/:_id', function(){
You don't need an id in the route:
Router.route('/game', function(){
Session.set("gameid",this.params.query.id);
});
Template.gamebefore.helpers({
ids: function () {
return Session.get("gameid");
}
});
As a note, instead of using http://..../game?id=somegameid, you might want to use a more REST approach by using http://.../game/somegameid, in which case, you need this route:
Router.route('/game/:_id', function(){
Session.set("gameid",this.params._id);
});
Template.gamebefore.helpers({
ids: function () {
return Session.get("gameid");
}
});