In my application I have posts, and in my post page I'm showing posted date as a minute ago using moment package.
I'm displaying dates using the following code
Template.registerHelper("postedTime",function(date){
return moment(date).fromNow();
});
and in my HTML
{{#with post}}
.............
............
{{postedTime date}}
............
............
{{/with}}
I know these dates are not reactive. In my post I have hundreds of comments also with the same date format.
What is the best way to update all those timings without much load to the client browser?
Dates are not reactive by themselves, so you need to include a reactive data source in your helper to force it to rerun.
In this example, we'll update a session variable that will force all instances of postedTime to be reevaluated every 60 seconds:
Template.registerHelper('postedTime', function(date) {
Session.get('timeToRecompute');
return moment(date).fromNow();
});
setInterval(function() {
Session.set('timeToRecompute' new Date);
}, 60 * 1000);
Related
This code in Meteor.
Template.message.helpers({
message: function() {
var message = Messages.findOne({ _id: FlowRouter.getParam('messageId')});
var curTime = new Date();
console.log(message.createdAt.getHours());
return message;
}
});
Gives me this exception:
Exception in template helper: message#http://localhost:3000/app/app.js?hash=0da6be4507e857f169e0cecb7b0874729eae4663:239:13
bindDataContext/<#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:3051:14
Blaze._wrapCatchingExceptions/<#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:1715:14
wrapHelper/</<#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:3103:14
Template._withTemplateInstanceFunc#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:3744:12
wrapHelper/<#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:3102:12
Spacebars.dot#http://localhost:3000/packages/spacebars.js?hash=ebf9381e7fc625d41acb0df14995b7614360858a:234:13
template.message.js/Template.message</<#http://localhost:3000/app/app.js?hash=0da6be4507e857f169e0cecb7b0874729eae4663:85:31
doRender#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:2086:20
viewAutorun/</<#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:1934:18
Template._withTemplateInstanceFunc#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:3744:12
viewAutorun/<#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:1932:14
Blaze._withCurrentView#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:2271:12
viewAutorun#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:1931:12
Tracker.Computation.prototype._compute#http://localhost:3000/packages/tracker.js?hash=997515fa2d5b0530ba07741da556c4b36963ef3b:339:5
Tracker.Computation#http://localhost:3000/packages/tracker.js?hash=997515fa2d5b0530ba07741da556c4b36963ef3b:229:5
Tracker.autorun#http://localhost:3000/packages/tracker.js?hash=997515fa2d5b0530ba07741da556c4b36963ef3b:613:11
Blaze.View.prototype.autorun#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:1944:14
Blaze._materializeView/<#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:2080:5
Tracker.nonreactive#http://localhost:3000/packages/tracker.js?hash=997515fa2d5b0530ba07741da556c4b36963ef3b:640:12
Blaze._materializeView#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:2079:3
materializeDOMInner#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:1532:9
Blaze._materializeDOM#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:1474:3
Blaze._materializeDOM#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:1483:7
Blaze._materializeView/<#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:2113:25
Tracker.nonreactive#http://localhost:3000/packages/tracker.js?hash=997515fa2d5b0530ba07741da556c4b36963ef3b:640:12
Blaze._materializeView#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:2079:3
Blaze.render#http://localhost:3000/packages/blaze.js?hash=f33d3dfed63a491d24e3aa07ad66c24b5fe8c761:2370:3
_render#http://localhost:3000/packages/kadira_blaze-layout.js?hash=dbd1396d04e62378fc8792cdef18869a1108cedd:204:5
render/</<#http://localhost:3000/packages/kadira_blaze-layout.js?hash=dbd1396d04e62378fc8792cdef18869a1108cedd:77:9
withValue#http://localhost:3000/packages/meteor.js?hash=27829e936d09beae3149ecfbf3332c42ccb1596f:1077:17
withoutInvocation/<#http://localhost:3000/packages/meteor.js?hash=27829e936d09beae3149ecfbf3332c42ccb1596f:464:26
Meteor.bindEnvironment/<#http://localhost:3000/packages/meteor.js?hash=27829e936d09beae3149ecfbf3332c42ccb1596f:1105:17
onGlobalMessage#http://localhost:3000/packages/meteor.js?hash=27829e936d09beae3149ecfbf3332c42ccb1596f:401:11
meteor.js:930:11
It actually gets printed twice but I felt it was large and unnecessary to show here twice.
And line 239 of app.js looks like this (it's line 7 here).
Template.message.helpers({ // 34
message: function () { // 35
var message = Messages.findOne({ // 36
_id: FlowRouter.getParam('messageId') // 36
}); // 36
var curTime = new Date(); // 37
console.log(message.createdAt.getHours()); // 38
return message; // 40
} // 41
But the hours still get printed to the console.
My goal is to use the createdAt date to subtract it from a current Date object and get a time delta. But I never got that far because whatever I try to do with message.createdAt produces this strange exception.
If I just do console.log(message.createdAt) I see TWO ISO formatted dates in the browser console, and two exceptions.
If I try something else like message.createdAt.getTime() I get one Unix timestamp.
What the heck is going on?
I was actually able to subtract the date and use it as intended, but the exceptions kept showing up in spite of the code working.
The reason you are seeing the errors is because the helper is being run, attempting to fetch data from a subscription that isn't ready yet. It eventually works because once the subscription is ready, the helper is re-run, and all is well. To fix this, simply wrap you helper with Template.subscriptionsReady:
<template name="message">
{{#if Template.subscriptionsReady}}
{{message}}
{{else}}
{{> SomeLoadingTemplate}}
{{/if}
</template>
NOTE: This template will execute the message helper only when all
template-level subscriptions are "Ready" and instead will show a
loader template executing the else part.
Regarding a comment on the question, to wrap your logic with if (date) {...}: this will get rid of the error, but isn't really solving the problem. The helper is still being run multiple times when it doesn't have to. It seems to be a bit of code smell to hide potential errors like this.
you have a template, a helper, and a local variable all called "message". even if that's not the source of your error, i'd gently suggest making them all different names.
I'm fairly confused as to which is the best way of doing this, since I didn't manage to find a package that solves this easily and other answers of similar problems don't address this properly.
I need to have a template that lists days and display all the documents created on each single day, for example:
10/27/2015
- Document A
- Document B
10/26/2015
- Document C
- Document D
- Document E
Or, less vaguely:
10/23/2014
- John Smith received 10 points on Basquet
- Paul Atreides received 20 points on Future Telling
10/21/2014
- Heisenberg received 25 points on National Trade
- etc.
Being the displayed document something like a 'Reports' collection that joins 'Players' with 'Activities', for example.
What is the proper way of achieving this functionality?
I guess creating a Days Collection is not the best option.
Thanks in advance
You can just create an array of days in your template helper, iterate over that with {{#each}} then have another helper that returns a cursor of documents for each day.
html:
<template name="docsByDay">
{{#each days}}
Date: {{this}} <!-- 'this' will be an individual date ->
{{#each documents this}}
{{content}}
{{/each}}
{{/each}}
</template>
Replace {{content}} with whatever field(s) you want to display from your collection.
js:
Template.docsByDay.helpers({
days: function(){
var arrayOfDates =[];
// create your array based on the date range and interval you want
return arrayOfDates;
},
documents: function(d){
var start = Date(getFullYear(d),getMonth(d),getDate(d));
var end = Date(getFullYear(d),getMonth(d),getDate(d)+1);
return Documents.find({ date: { $gte: start, $lt: end }});
}
});
See javascript - get array of dates between 2 dates
I thought it'd be easy but, yeah... it wasn't. I already posted a question that went in the same direction, but formulated another question.
What I want to do
I have the collection songs, that has a time attribute (the playing-time of the song). This attribute should be handled different in the form-validation and the backend-validation!
! I'd like to do it with what autoform (and simple-schema / collection2) offers me. If that's possible...
in the form the time should be entered and validated as a string that fits the regex /^\d{1,2}:?[0-5][0-9]$/ (so either format "mm:ss" or mmss).
in the database it should be stored as a Number
What I tried to do
1. The "formToDoc-way"
This is my javascript
// schema for collection
var schema = {
time: {
label: "Time (MM:SS)",
type: Number // !!!
},
// ...
};
SongsSchema = new SimpleSchema(schema);
Songs.attachSchema(SongsSchema);
// schema for form validation
schema.time.type = String // changing from Number to String!
schema.time.regEx = /^\d{1,2}:?[0-5][0-9]$/;
SongsSchemaForm = new SimpleSchema(schema);
And this is my template:
{{>quickForm
id="..."
type="insert"
collection="Songs"
schema="SongsSchemaForm"
}}
My desired workflow would be:
time is validated as a String using the schema
time is being converted to seconds (Number)
time is validated as a Number in the backend
song is stored
And the way back.
I first tried to use the hook formToDoc and converted the string into seconds (Number).
The Problem:
I found out, that the form validation via the given schema (for the form) takes place AFTER the conversion in `formToDoc, so it is a Number already and validation as a String fails.
That is why I looked for another hook that fires after the form is validated. That's why I tried...
2. The "before.insert-way"
I used the hook before.insert and the way to the database worked!
AutoForm.hooks({
formCreateSong: {
before: {
insert: function (doc) {
// converting the doc.time to Number (seconds)
// ...
return doc;
}
},
docToForm: function (doc) {
// convert the doc.time (Number) back to a string (MM:SS)
// ...
return doc;
}
}
});
The Problem:
When I implemented an update-form, the docToForm was not called so in the update-form was the numerical value (in seconds).
Questions:
How can I do the way back from the database to the form, so the conversion from seconds to a string MM:SS?
Is there a better way how to cope with this usecase (different data types in the form-validation and backend-validation)?
I am looking for a "meteor autoform" way of solving this.
Thank you alot for reading and hopefully a good answer ;-)
I feel like the time should really be formatted inside the view and not inside the model. So here's the Schema for time I'd use:
...
function convertTimeToSeconds (timeString) {
var timeSplit = timeString.split(':')
return (parseInt(timeSplit[0]) * 60 + parseInt(timeSplit[1]))
}
time: {
type: Number,
autoValue: function () {
if(!/^\d{1,2}:?[0-5][0-9]$/.test(this.value)) return false
return convertTimeToSeconds(this.value)
}
}
...
This has a small disadvantage of course. You can't use the quickForm-helper anymore, but will have to use autoForm.
To then display the value I'd simply find the songs and then write a helper:
Template.registerHelper('formateTime', function (seconds) {
var secondsMod = seconds % 60
return [(seconds - secondsMod) / 60, secondsMod].join(':')
})
In your template:
{{ formatTime time }}
The easy answer is don't validate the string, validate the number that the string is converted into.
With simpleschema, all you do is create a custom validation. That custom validation is going to grab the string, turn it into a number, and then validate that number.
Then, when you pull it from the database, you'll have to take that number & convert it into a string. Now, simpleschema doesn't do this natively, but it's easy enough to do in your form.
Now, if you wanted to get fancy, here's what I'd recommend:
Add new schema fields:
SimpleSchema.extendOptions({
userValue: Match.Optional(Function),
dbValue: Match.Optional(Function),
});
Then, add a function to your time field (stored as Date field):
userValue: function () {
return moment(this.value).format('mm:ss');
},
dbValue: function () {
return timeToNumber(this.value);
}
Then, make a function that converts a timeString to a number (quick and dirty example, you'll have to add error checking):
function timeToNumber(str) {
str.replace(':',''); //remove colon
var mins = +str.substr(0,2);
var secs = +str.substr(2,2);
return mins * 60 + secs;
}
Then, for real-time validation you can use schema.namedContext().validateOne. To update the db, just send timeToNumber(input.value).
Have been using https://github.com/acreeger/meteor-moment in meteor and it works well, however is there a way to make the output of moment reactive so that it counts up "3 seconds ago", "4 seconds ago", etc?
Rather than using a new Session variable for each individual timer, I would create a single Tracker.Dependency which is flagged for changes every second (or perhaps every 10 seconds), then depend on this whenever you want to depend on the current time.
var timeTick = new Tracker.Dependency();
Meteor.setInterval(function () {
timeTick.changed();
}, 1000);
fromNowReactive = function (mmt) {
timeTick.depend();
return mmt.fromNow();
}
Template.example.helpers({
example: function () {
return fromNowReactive(moment().startOf('hour'));
}
});
This is the approach taken by mizzao:timesync, which is a useful package you can use if those fromNows are based on server-side timestamps. One reason to not use client-generate timestamps is that these may be out of sync, resulting in strings like 5 seconds from now for a post that was just made. mizzao:timesync allows server-generated timestamps to be used everywhere, and also groups different reactivity intervals together efficiently.
You can now use the package copleykj:livestamp. (github | atmosphere)
Install it like this:
meteor add copleykj:livestamp
It has a dependency on momentjs:moment so it will bring that along automatically. It installs a universal helper that is available anywhere and can be passed a date object.
You can use it in a template like this:
<li>Regular: {{date}} </li>
<li>Livestamp: {{livestamp date}}</li>
Here's a working demo in MeteorPad
Thanks for the replies everyone, I found a mrt package which does the job atmospherejs.com/package/livestamp
Use setTimeout and Session to store your variable.
Something like this (in your lib file):
var updateTime = function () {
var time = moment().startOf('hour').fromNow(); // 22 minutes ago
Session.set('timeFromNow', time);
setTimeout(updateTime, 60 * 1000); // 60 seconds
});
updateTime();
Then, in your template:
Template.t.timeFromNow = function () {
return Session.get('timeFromNow');
}
When setTimeout triggered to update Session variable, the template is updated.
Can someone help me understand how I can pass the start date into the calendar. I have created a Delivery Scheduler calendar and I display the delivery details in a table under the calends that is feed via the database. This requires me to refresh the page when a user select a calendar day to load the table information. I can figure out how to start the calendar on a starting date that is passed into the page.
Seems like this would be easy but I am doing something wrong.
$('#calendar').fullCalendar(Options);
$('#calendar').fullCalendar('gotoDate', '2012-10-21');
Sample based on documentation http://arshaw.com/fullcalendar/docs/current_date/gotoDate/
Remember that month is 0-based, so 10 means November.
$(document).ready(function () {
var calendar = $('#calendar').fullCalendar({
events:[
{ title:'All Day Event', start:new Date(2012, 10, 20)},
{ title:'Long Event', start:new Date(2012, 10, 21), end:new Date(2012, 10, 22)}
]
});
$('#calendar').fullCalendar('gotoDate', 2012, 10, 21);
});
Thank you Biesior for your helpful answer. I was able to use your suggested code to get the behavior I was looking for.
While using the approach above, I notice that Firebug's console shows two AJAX data requests being executed simultaneously, one for the view associated with the current date, and one for the view associated with the specified gotoDate.
There doesn't appear to be any additional delay from the user's perspective, and the calendar displays the requested view from the start. However, 'loading' callbacks will be called multiple times which might cause strange behavior in certain circumstances. There may also be other undesired results associated with the superfluous AJAX request for the current date.
I was able to avoid the unnecessary AJAX request by initializing the calendar without an event source, then moving to the desired date as shown by Biesior above, and then adding the event source. The sequence is shown below. I've removed some unrelated FullCalendar options and callbacks to keep it concise. There are some additional AJAX parameters, and some PHP, but the important thing to notice is when the event source is specified.
The original code results in two simultaneous AJAX requests:
$('#calendar').fullCalendar({
events: {
url:'/Services/GetEvents.php',
type: 'POST',
data: {
lat: <?=$venLatitude?>,
lon: <?=$venLongitude?>,
userID: <?=$userID?>,
distance: <?=$distance?>
}
}
})
$('#calendar').fullCalendar('gotoDate', <?=(int)substr($startDate,0,4)?>, <?=((int)substr($startDate,5,2))-1?>);
This adjustment results in only the desired AJAX request:
$('#calendar').fullCalendar();
$('#calendar').fullCalendar('gotoDate', <?=(int)substr($startDate,0,4)?>, <?=((int)substr($startDate,5,2))-1?>);
$('#calendar').fullCalendar('addEventSource', {
url:'/Services/GetEvents.php',
type: 'POST',
data: {
lat: <?=$venLatitude?>,
lon: <?=$venLongitude?>,
userID: <?=$userID?>,
distance: <?=$distance?>
}
});