Hello and sorry for my broken English.
I have a method "updateCounterState" for incrementing and updating a number every second in my mongodb-Collection and showing this number in my template in my html-file. And it seems to work, but I get every time I use this function two errors. For three days I am trying to figure out how to fix these errors. I believe I have to use this code block with a Meteor.bindEnvironment-Wrapper because of my asynchronous updates. However, I don't know how to use this to fix these errors. Or maybe I am completely wrong and these errors have another cause.
EDIT #2:
client/main.html
<head>
<title>test-timer</title>
</head>
<body>
{{> timeTrackerTemplate}}
</body>
<template name="timeTrackerTemplate">
{{#each showCounterState}}
<p class="counter-state">{{state}}</p>
<button class="start-counting">Start</button>
{{/each}}
</template>
client/main.js
import { Template } from 'meteor/templating';
Template.timeTrackerTemplate.events({
'click .start-counting': function(e) {
Meteor.call('updateCounterState', this._id);
}
});
server/main.js
import { Meteor } from 'meteor/meteor';
Meteor.startup(() => {
// code to run on server at startup
});
methods.js (root folder)
Meteor.methods({
'updateCounterState': function(id) {
Meteor.setInterval(function() {
TimeTracker.update(
{_id: id},
{
$inc: {state: 1},
},
);
}, 1000);
}
});
ttcollection.js (root folder)
TimeTracker = new Mongo.Collection('testtracker');
if (Meteor.isClient) {
Template.timeTrackerTemplate.helpers({
showCounterState: function () {
return TimeTracker.find();
}
});
}
meteor:PRIMARY> db.testtracker.find({})
{ "_id" : ObjectId("57ee677227a0af6b59dc12ce"), "state" : 147 }
{ "_id" : ObjectId("57ee677a27a0af6b59dc12cf"), "state" : 148 }
{ "_id" : ObjectId("57ee6e6027a0af6b59dc12d0"), "state" : 73 }
Error every time I press a button:
Exception while simulating the effect of invoking 'updateCounterState' Error: Can't set timers inside simulations
at withoutInvocation (http://localhost:3000/packages/meteor.js?hash=ae8b8affa9680bf9720bd8f7fa112f13a62f71c3:463:13)
at bindAndCatch (http://localhost:3000/packages/meteor.js?hash=ae8b8affa9680bf9720bd8f7fa112f13a62f71c3:471:33)
at Object.setInterval (http://localhost:3000/packages/meteor.js?hash=ae8b8affa9680bf9720bd8f7fa112f13a62f71c3:498:24)
at updateCounterState (http://localhost:3000/app/app.js?hash=f641538433c68c8f8b820f0e05cebb12531cb357:66:20)
at http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:3973:25
at withValue (http://localhost:3000/packages/meteor.js?hash=ae8b8affa9680bf9720bd8f7fa112f13a62f71c3:1077:17)
at Connection.apply (http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:3964:54)
at Connection.call (http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:3840:17)
at Object.clickStartCounting (http://localhost:3000/app/app.js?hash=f641538433c68c8f8b820f0e05cebb12531cb357:47:20)
at http://localhost:3000/packages/blaze.js?hash=a9372ce320c26570a2e4ec2588d1a6aea57de9c1:3718:20 Error: Can't set timers inside simulations
at withoutInvocation (http://localhost:3000/packages/meteor.js?hash=ae8b8affa9680bf9720bd8f7fa112f13a62f71c3:463:13)
at bindAndCatch (http://localhost:3000/packages/meteor.js?hash=ae8b8affa9680bf9720bd8f7fa112f13a62f71c3:471:33)
at Object.setInterval (http://localhost:3000/packages/meteor.js?hash=ae8b8affa9680bf9720bd8f7fa112f13a62f71c3:498:24)
at updateCounterState (http://localhost:3000/app/app.js?hash=f641538433c68c8f8b820f0e05cebb12531cb357:66:20)
at http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:3973:25
at withValue (http://localhost:3000/packages/meteor.js?hash=ae8b8affa9680bf9720bd8f7fa112f13a62f71c3:1077:17)
at Connection.apply (http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:3964:54)
at Connection.call (http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:3840:17)
at Object.clickStartCounting (http://localhost:3000/app/app.js?hash=f641538433c68c8f8b820f0e05cebb12531cb357:47:20)
at http://localhost:3000/packages/blaze.js?hash=a9372ce320c26570a2e4ec2588d1a6aea57de9c1:3718:20
Where is the updateCounterState function defined? Is it a helper on a Template? What are the allow/deny statuses on the TimeTracker collection?
EDIT (after question updated)
First, your method takes as a single parameter the id of the TimeTracker, but when you call your method, you don't pass anything. You should either create a TimeTracker object from the client and then pass its _id, or take no arguments in your function and create it on the server (in this case, your method should return the _id to keep track of it.
Then, putting code in the root folder is not the best practice, you should use the /imports folder and then import { foo } from 'foo.js', or put your code in /server or /client.
Your Template helpers returns a Mongo cursor which is of no use. If you want to return a single TimeTracker, use TimeTracker.findOne({ _id: id }) which will return the object. Basically, you should probably have something like:
Template.timeTrackerTemplate.onCreated(function () {
this.trackerId = null;
});
Template.timeTrackerTemplate.helpers({
showCounterState: function() {
const trackerId = Template.instance().trackerId;
return trackerId ? TimeTracker.findOne({ _id: trackerId }).state : '';
}
});
Template.timeTrackerTemplate.events({
'click .start-counting': function(e, instance) {
instance.trackerId = TimeTracker.insert({ state: 0 });
Meteor.call('updateCounterState', instance.trackerId);
}
});
I solved my errors. It is important to call methods with collections on server side only - especially without "insecure" package.
if (Meteor.isServer) {
//Meteor.methods()
}
Related
I am adding a new page to a website, and I am copying the code that already exists and is currently working in the website. Why is the FlowRouter.getParam coming up undefined when it works everywhere else?
client/JobInvoice.js
import { Invoices } from '../../../imports/api/Invoice/Invoice';
Template.InvoicePage.onCreated(function(){
const user = FlowRouter.getParam('_id');
console.log(user);
this.subscribe('invoices', user);
});
lib/router.js
Accounts.onLogout(function(){
FlowRouter.go('home');
});
FlowRouter.notFound = {
action: function() {
FlowRouter.go('/404');
}
};
const loggedIn = FlowRouter.group({
prefix: '/secure'
});
loggedIn.route( '/invoice', {
name: 'invoice',
action() {
BlazeLayout.render('FullWithHeader', {main:
'InvoicePage'});
}
});
What am I missing?
FlowRouter allows you to define routes with dynamic attributes (path-to-regexp), which are often representing document ids or other dynamic attributes.
For example
FlowRouter.route('/invoice/:docId', { ... })
would define a route that matches a pattern like /invoice/9a23bf3uiui3big and you usually use it to render templates for single documents.
Now if you want to access the document id as param docId inside the corresponding Template you would use FlowRouter.getParam('docId') and it would return for the above route 9a23bf3uiui3big.
Since your route definitions lacks a dynamic property, there is no param to be received by FlowRouter.getParam.
A possible fix would be
loggedIn.route( '/invoice/:_id', {
name: 'invoice',
action() {
BlazeLayout.render('FullWithHeader', {main:
'InvoicePage'});
}
});
to access it the same way you do for the other templates.
Readings
https://github.com/kadirahq/flow-router#flowroutergetparamparamname
Here is what I ended up doing and it works.
loggedIn.route( '/invoice/:id', {
name: 'invoice',
action() {
BlazeLayout.render('FullWithHeader', {main: 'InvoicePage'});
}
});
When writing tests for my web app; I have to first simulate login before the rest of my tests can run and see inner pages. Right now I'm working on modulating the code, so that way I can just make an 'include' for the common function; such as my login. But as soon as I move the below code in a separate file, and call the include via require - it no longer runs as expected.
ie. the below logs in and allows my other functions, if, included in the same file. above my other inner screen functions.
// Login screen, create opportunity
this.LoginScreen = function(browser) {
browser
.url(Data.urls.home)
.waitForElementVisible('#login', 2000, false)
.click('#login')
.waitForElementVisible('div.side-panel.open', 4000, false)
.waitForElementVisible('input#email', 2000, false)
.waitForElementVisible('input#password', 2000, false)
.click('input#email')
.pause(500)
.setValue('input#email', Data.ProjMan.username)
.click('input#password')
.pause(500)
.setValue('input#password', Data.ProjMan.password)
.click('input#email')
.pause(500)
.click('div.form.login-form .btn')
.pause(5000)
Errors.checkForErrors(browser);
};
// Inner functions run after here, sequentially
But as soon as I move the above in a separate file, for instance; Logins.js, then call it at the top of the original test file with. (yes, correct path).
var Logins = require("../../lib/Logins.js");
It just doesn't simulate the login anymore. Any thoughts? Should I remove the this.LoginScreen function wrapper, and call it differently to execute from the external file, or do I need to fire it from the original file again, aside from the external require path?
I have also tried wrapping 'module.exports = {' around the login function from separate file, but still failing.
Nightwatch allows you to run your Page object based tests i.e you can externalize your common test functions and use them in your regular tests. This can be achieved using 'page_objects_path' property. I have added the common 'login' functionality and used it in sample 'single test' in the project here.
Working:
Place your common function in .js file and place it under a folder(ex: tests/pages/login.js) and pass the folder path in nighwatch config file as below:
nightwatch_config = {
src_folders : [ 'tests/single' ],
page_objects_path: ['tests/pages'],
Below is an example of common login function (login.js):
var loginCommands = {
login: function() {
return this.waitForElementVisible('body', 1000)
.verify.visible('#userName')
.verify.visible('#password')
.verify.visible('#submit')
.setValue('#userName', 'Enter Github user name')
.setValue('#password', 'Enter Github password')
.waitForElementVisible('body', 2000)
}
};
module.exports = {
commands: [loginCommands],
url: function() {
return 'https://github.com/login';
},
elements: {
userName: {
selector: '//input[#name=\'login\']',
locateStrategy: 'xpath'
},
password: {
selector: '//input[#name=\'password\']',
locateStrategy: 'xpath'
},
submit: {
selector: '//input[#name=\'commit\']',
locateStrategy: 'xpath'
}
}
};
Now, in your regular test file, create an object for the common function as below and use it.
module.exports = {
'Github login Functionality' : function (browser) {
//create an object for login
var login = browser.page.login();
//execute the login method from //tests/pages/login.js file
login.navigate().login();
//You can continue with your tests below:
// Also, you can use similar Page objects to increase reusability
browser
.pause(3000)
.end();
}
};
The above answer is absolutly correct however I did struggle with how to supply login user details.
This is what I ended up using:
var loginCommands = {
login: function() {
return this.waitForElementVisible('body', 1000)
.setValue("#email", "<some rnd email address>")
.setValue('#password', "<some rnd password>")
.click('button[type=submit]')
.pause(1000)
}
};
module.exports = {
commands: [loginCommands],
url: function() {
return 'https://example.com/login';
}
};
This can be used in the same way as the accepted answer just posting for others who come searching.
Im struggling with an issue using Meteor JS.
I call an api wich return me a Json array wich look like the one returned on this url (I don't put the whole array here cause of the size): https://blockchain.info/address/12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?format=json&offset=0
I call it server side like :
if (Meteor.isServer) {
Meteor.methods({
getWalletPreviousTx: function() {
var url = "https://blockchain.info/address/12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?format=json&offset=0";
var result = Meteor.http.get(url);
if(result.statusCode==200) {
var tx = JSON.parse(result.content);
return tx;
} else {
console.log("Response issue: ", result.statusCode);
var errorJson = JSON.parse(result.content);
throwError("Couldn't fetch wallet balance from Blockchain, try again later !");
}
}
});
}
And i retrieve it to my view via an helper in a specific template :
Template.wallet.helpers({
addrTxs: function () {
Meteor.call('getWalletPreviousTx', function(err, tx) {
console.log(tx);
return [tx];
});
}
});
The console.log in the helper actually log my Json array wich mean it have access to it.
Now the part im struggling with is to retrieve this Json to my view, i've tried a lot of way and none of them works, actually i have this in my view :
<template name="wallet">
<table>
{{#each addrTxs}}
<ul>
{{> addrTx}}
</ul>
{{/each }}
</table>
</template>
The part of the Json I want to display is the "addr" and "value" of each transactions :
"inputs":[
{
"sequence":4294967295,
"prev_out":{
"spent":true,
"tx_index":97744124,
"type":0,
"addr":"1AWAsn8rhT555RmbMDXXqzrCscPJ5is5ja",
"value":50000,
"n":0,
"script":"76a914683d704735fd591ba9f9aebef27c6ef00cbd857188ac"
}
}
]
Fact is, i never managed to display anything from this Json array in my view, even puting directly this in my view doesn't show anything :
{{addrTxs}}
What am I doing wrong ? Can anyone help with this ?
Thanks for reading.
----------------------- Edit ---------------------
I think the problem is more that my helper and template are loaded before the api call is finished (because the console.log appear in my console like 3seconds after my page is rendered). How can i make my helper wait until the api call is finished before rendering it in the view ? I use iron router.
I have tried to add a waitOn action on my route in order to wait until my api call is finished :
Router.route('/wallet', {
name: 'wallet',
template: 'wallet',
loadingTemplate: 'loading',
waitOn: function () {
Meteor.call('getWalletPreviousTx', function(error, result) {
if(!error) {
Ready.set(result)
}
});
return [
function () { return Ready.get(); }
];
},
action: function () {
if (this.ready())
this.render();
else
this.render('loading');
}
});
The above code with the waitOn action seems to work (i have no errors) but i don't know the way to display in my view the specific result from :
if(!error) {
Ready.set(result)
}
Transactions are contained in tx.txs, iterates through that.
Template.wallet.helpers({
addrTxs: function () {
Meteor.call('getWalletPreviousTx', function(err, tx) {
console.log(tx);
return tx.txs;
});
}
});
You're right, you need to use the sessions variables with async call.
First, call method on created :
Template.wallet.created = function () {
Meteor.call('getWalletPreviousTx', function(err, tx) {
console.log(tx.txs);
Session.set('tx', tx.txs);
});
};
Helper should look like this :
Template.wallet.helpers({
addrTxs: function () {
return Session.get('tx');
}
});
Can someone explain to me why when I have collections code inside router will cause the route to be called when a method is called?
Consider the following code:
home.html
<template name="home">
{{ duplicate }}
<form>
<input type="text" name="test" value="somevalue">
<input type="submit" value="Submit">
</form>
</template>
script.js
Template.home.events({
'submit form': function (e) {
e.preventDefault();
console.log('Enter Meteor call');
Meteor.call('createDoc', { 'test': e.target.test.value });
}
});
route.js
Router.onBeforeAction(function () {
console.log('Enter onBeforeAction');
$('#loading').show();
this.next();
});
Router.route('/', function () {
console.log('Enter action');
var foo = collection.findOne({ test: 'somevalue' }) ? 'true' : 'false';
this.render('home', {
data: {
'duplicate' : foo
}
});
Template.home.rendered = function () {
console.log('Enter rendered');
$('#loading').hide();
};
});
methods.js
collection = new Mongo.Collection('collection');
Meteor.methods({
createDoc: function (data) {
console.log('Enter createDoc');
collection.insert(data);
}
});
The problem is that if I press submit on the form, after the method is called the router will activate, even though e.preventDefault() presents. The console log shows this behaviour clearly:
"Enter Meteor call" script.js:4:3
"Enter createDoc" methods.js:5:3
"Enter onBeforeAction" routes.js:2:2
"Enter action" routes.js:8:2
"Enter onBeforeAction" routes.js:2:2
"Enter action" routes.js:8:2
Furthermore, you can see that the router is called twice and that it never enters Template.home.rendered. This causes the loading div to appear and never leaves. I can confirm that data are being inserted correctly.
If I remove collection.findOne() in routes.js, however, this behaviour will disappear and everything works as expected.
Questions
Why is the route being called only when I have collection.findOne() inside the route?
Why collection.findOne({ test: 'somevalue' }) never returns anything inside the route? (I know how I can get around this by using Session variables and helpers in script.js, but I want to know exactly why)
This is causing a lot of unexpected behaviour in my app. Thank you very much in advance.
As answered by others the problem you have arises from the fact that Meteor will reactively re-run code that runs in a reactive context, if and only if, that code issues a call to a reactive data source.
In your case, the call to findOne is a call to a reactive data source and the context in Router.route('/', function () { // context }); is a reactive context.
There are two important tools that let you control this behavior: one is good design. Be aware of the reactivity and try to design your code around it.
The other is checking Tracker.active and using Tracker.nonreactive to avoid reactivity inside a reactive data context.
This should answer your first question. As to why your findOne query never finds anything: have you published the data from the server to the client? Please check out Publish-Subscribe. You basically need:
// on the server
Meteor.publish('myPublication', function(author) {
return collection.find();
});
// on the client
Meteor.subscribe('myPublication');
The call to collection.findOne() inside the route is listening to any new changes on the database, every time text is saved on the database the query is run.
A possible solution:
Router.js
Router.onBeforeAction(function () {
console.log('Enter onBeforeAction');
$('#loading').show();
this.next();
});
Router.route('/', {
template: 'home',
waitOn: function() {
return Meteor.subscribe('collection');
},
data: function() {
var foo = collection.findOne({ test: 'somevalue' }) ? 'true' : 'false';
return {
'duplicate': foo
};
},
action: function() {
this.render();
}
});
And a publish file on server/publish.js
Meteor.publish('collection', function () {
return collection.find();
});
I hope this can help you solving your problem.
Best.
I'm trying to push new data on to my clients array each time the loadMoreClients method is called. The publication is expecting this back_to parameter and knows how to handle it. My problem is that I can't seem to call these methods from my Template helpers .
I logged Iron and Iron.controller to the console and both of those exist and are showing me what I expected to see. I just can't seem to find current docs or examples of how to access Iron.controller() methods/properties from my Template helpers
Here is my RouteController code:
ClientController = ApplicationController.extend({
action : function(){
this.render(Router.current().route.getName())
},
data : function(){
if( this.params._id ){
return Clients.findOne({ _id:this.params._id })
}
},
waitOn : function(){
return [
Meteor.subscribe('directory'),
Meteor.subscribe('clients')
]
},
loadMoreClients : function(){
this.months_back += 3
this.back_to = moment().subtract(this.months_back,'months').startOf('day')
this.clients.push(Meteor.subscribe('clients', {back_to:this.back_to, skip:this.clients.length}))
},
loadAllClients : function(){
this.clients.push(Meteor.subscribe('clients', {back_to:this.start_of_time, skip:this.clients.length}))
},
// we'll use these properties to 'load more' client data
clients : [],
back_to : moment().subtract(3,'months').startOf('day'),
months_back : 3,
start_of_time : moment(new Date(0))
})
Here is my helpers code:
Template.client_list.helpers({
clients : function(){
var clients = []
Iron.controller().clients.forEach(function(client){
// ... some stuff here...
clients.push(client)
})
return clients
},
earliestClientLoaded : function(){
var controller = Iron.controller()
return controller.clients[controller.clients.length - 1].createdAt
}
})
Template.client_list.events({
'click .btn-load-more' : function(e){
e.preventDefault()
Iron.controller().loadMoreClients()
},
'click .btn-load-all' : function(e){
e.preventDefault()
Iron.controller().loadAllClients()
}
})
I'm getting undefined function errors on my Iron.controller() calls to loadMoreClients and loadAllClients methods.
What am I doing wrong here?
I have changed the approach to this problem by simply updating the subscription to subscribe to posts earlier than the currently loaded date.
It is working, although seems like there should be a better way than having to waitOn the subscription when I want to load more.