I have problems understanding how exactly meteor's ReactiveVar works. What i think i should do :
Template.template_canvas.onCreated(function() {
this.config = new ReactiveVar([{
name: 'FPS',
value: Engine.mFPS
}, {
name: 'ElapsedTime',
value: Engine.mElapsedTime
}, {
name: 'LagTime',
value: Engine.mLagTime
}, {
name: 'PreviousTime',
value: Engine.mPreviousTime
}]);
});
Template.template_canvas.helpers({
displayConfigs: function() {
return Template.instance().config.get();
}
});
ALL Engine variables are updated every 40ms.
And when i try to display them :
<template name="template_canvas">
<div id='info_log' draggable="true" >
<ul>
{{#each displayConfigs}}
<li> {{name}} {{value}} </li>
{{/each}}
</ul>
</div>
<canvas id='canvas_app'></canvas>
</template>
ONLY the default variables of Engine are displayed.
Which means the variables arent changing at all.
However they are changing according to console.log !
EDIT :
export default class Engine {
static start(application) {
Engine.Application = application;
Engine.mPreviousTime = Date.now();
Engine.mLagTime = 0.0;
Engine.mIsRunning = true;
requestAnimationFrame(function() {
Engine.run.call(Engine.Application);
});
}
static run() {
if (Engine.mIsRunning) {
requestAnimationFrame(
function() {
Engine.run.call(Engine.Application);
}
);
Engine.mCurrentTime = Date.now();
Engine.mElapsedTime = Engine.mCurrentTime - Engine.mPreviousTime;
Engine.mPreviousTime = Engine.mCurrentTime;
Engine.mLagTime += Engine.mElapsedTime;
// LagTime is the sum of the elapsed Time based on the last Frame.
// So if the time passed is bigger than the given time, which bigger than milliseconds per frame
// then we have to update.
while ((Engine.mLagTime >= Engine.mMPF) && Engine.mIsRunning) {
this.update();
Engine.mLagTime -= Engine.mMPF;
}
// Should be called every 25ms, since we have 25ms per Frame.
//
this.draw();
}
}
}
// Frames per second.
//
Engine.mFPS = 40;
// Milliseconds per Frame 25ms/Frame with 40 FPS
Engine.mMPF = 1000 / Engine.mFPS;
Engine.mPreviousTime = 0;
Engine.mLagTime = 0;
Engine.mCurrentTime = 0;
Engine.mElapsedTime = 0;
Engine.mIsRunning = false;
Engine.Application = null;
Related
So bootstrap datepicker return blank date if I click the same date again. However, how I know which date is again clicked by the user?
For example in below calendar, I clicked on 24th of April. Then changeDate event will return me below the array. Which is fine for me:
So anyone has any idea on how to know which date is again clicked by the user? Like how I can detect that the user is reclicked on the 24th?
My code:
Init of datepicker:
var DatePicker = {
hideOldDays: function(){ // hide days for previous month
var x = $('.datepicker .datepicker-days tr td.old');
if(x.length > 0){
x.css('visibility', 'hidden');
if(x.length === 7){
x.parent().hide();
}
}
},
hideNewDays: function(){ // hide days for next month
var x = $('.datepicker .datepicker-days tr td.new');
if(x.length > 0){
x.hide();
}
},
hideOtherMonthDays: function(){ // hide days not for current month
DatePicker.hideOldDays();
DatePicker.hideNewDays();
}
};
var arrows;
if (KTUtil.isRTL()) {
arrows = {
leftArrow: '<i class="la la-angle-right"></i>',
rightArrow: '<i class="la la-angle-left"></i>'
}
} else {
arrows = {
leftArrow: '<i class="la la-angle-left"></i>',
rightArrow: '<i class="la la-angle-right"></i>'
}
}
var date = new Date();
var active_dates_1 = ['13/3/2020','14/3/2020','15/3/2020','16/3/2020','17/3/2020','18/3/2020','20/3/2020','21/3/2020','22/3/2020','23/3/2020','24/3/2020','25/3/2020','26/3/2020','27/3/2020','28/3/2020','29/3/2020','30/3/2020','31/3/2020','2/4/2020','3/4/2020','5/4/2020','16/4/2020','17/4/2020',];
var active_dates_2 = ['1/4/2020','6/4/2020','7/4/2020','8/4/2020','9/4/2020','10/4/2020','11/4/2020','12/4/2020','15/4/2020','20/4/2020',];
var active_dates_3 = ['4/4/2020','13/4/2020','14/4/2020','24/4/2020','24/4/2020',];
date.setDate(date.getDate()+1);
$('#manage_datepicker_1').datepicker({
rtl: KTUtil.isRTL(),
todayHighlight: true,
templates: arrows,
startDate: date, //disable all old dates
setDate: date, //tomorrow's date allowed
multidate: true,
format: 'dd/mm/yyyy',
endDate: "25/04/2020",
beforeShowDay: function(date){
var d = date;
var curr_date = d.getDate();
var curr_month = d.getMonth() + 1; //Months are zero based
var curr_year = d.getFullYear();
var formattedDate = curr_date + "/" + curr_month + "/" + curr_year
if ($.inArray(formattedDate, active_dates_1) != -1){
return {
classes: 'disabled bookedDates1'
};
}
if ($.inArray(formattedDate, active_dates_2) != -1){
return {
classes: 'disabled bookedDates2'
};
}
if ($.inArray(formattedDate, active_dates_3) != -1){
return {
classes: 'disabled bookedDates3'
};
}
return;
}
//maxDate: '28/12/2019'
});
$('#manage_datepicker_1').datepicker().on('show', function(event) {
DatePicker.hideOtherMonthDays(); //hide other months days from current month view.
}).on('changeDate', function(event) {
var storage = new Array();
console.log(event);
for (var i = 0; i < event.dates.length; i++) {
var formatted_date = moment(event.dates[i]).format('DD/MM/YYYY');
storage.push(formatted_date);
}
});
I think that having the clicked date is a reasonable requirement, that worth introducing a change into the source code of bootstrap-datepicker.
So, assuming you're using the minified version, there's a part that goes (ctrl+f for element.trigger):
this.element.trigger({
type: b,
date: e,
viewMode: this.viewMode,
dates: a.map(this.dates, this._utc_to_local),
...
})
Just add a clickedDate property:
this.element.trigger({
type: b,
date: e,
clickedDate: this.viewDate, // <-- added
viewMode: this.viewMode,
dates: a.map(this.dates, this._utc_to_local),
...
})
So that now, inside your changeDate handler, you could access event.dates as well as event.clickedDate.
I am trying to load 12 items only each time, until the user scroll all the way down and load another 12 elements
For some reason my code doesn't work. When i upload another item, i can see it in the admin panel so it is successfully uploaded but i can't see it in the normal user view. i can only view the first 12 items uploaded and it doesn't load anymore items when i scroll.
Here is my code in the client side
if (Meteor.isClient) {
var ITEMS_INCREMENT = 12; //this one refers to the number of elements to load
Session.setDefault('itemsLimit', ITEMS_INCREMENT);
Deps.autorun(function() {
Meteor.subscribe('items', Session.get('itemsLimit'));
});
Template.list_products.helpers({
applications: function () {
var limit = Session.get("itemsLimit");
//return Products.find({}, { sort: {createdAt: -1},limit: limit }); // render latest first
return Products.find({}, { sort: {createdAt: 1},limit: limit }); // render first first
}
});
Template.list_products.moreResults = function() {
// If, once the subscription is ready, we have less rows than we
// asked for, we've got all the rows in the collection.
return Products.find({}, { sort: {createdAt: -1},limit: limit });
}
// whenever #showMoreResults becomes visible, retrieve more results
function showMoreVisible() {
var threshold, target = $("#showMoreResults");
if (!target.length) return;
threshold = $(window).scrollTop() + $(window).height() - target.height();
if (target.offset().top < threshold) {
if (!target.data("visible")) {
// console.log("target became visible (inside viewable area)");
target.data("visible", true);
Session.set("itemsLimit",
Session.get("itemsLimit") + ITEMS_INCREMENT);
}
} else {
if (target.data("visible")) {
// console.log("target became invisible (below viewable arae)");
target.data("visible", false);
}
}
}
// The below line is to run the above func every time the user scrolls
$(window).scroll(showMoreVisible);
}
Here how i solved it:
if(Meteor.isClient) {
Session.set("itemsLimit", 9); // to set the limit to 9
lastScrollTop = 0;
$(window).scroll(function(event){
if($(window).scrollTop() + $(window).height() > $(document).height() - 100) { // to detect scroll event
var scrollTop = $(this).scrollTop();
if(scrollTop > lastScrollTop){ // detect scroll down
Session.set("itemsLimit", Session.get("itemsLimit") + 9); // when it reaches the end, add another 9 elements
}
lastScrollTop = scrollTop;
}
});
}
It works like a charm now :)
You can implement it like this:
HTML File:
<template name="yourTemplateName">
<div id="divId">
{{#each dataArr}}
//your view here.
{{/each}}
</div>
{{#if noMoreItem}}
<span>No more items to show</span>
{{/if}}
</template>
JS File:
var pageNumber = new ReactiveVar(0);
var noMoreItem = new ReactiveVar(false);
var mainContainer = // Your element here example: document.getElementById('divId')
mainContainer.addEventListener('scroll', function(){
if(mainContainer.scrollHeight - mainContainer.scrollTop === mainContainer.clientHeight) {
getMoreItems();
}
});
var getMoreItems = function () {
if(pageNumber.get() < Math.floor(Counts.get('countItems')/12)) {
pageNumber.set(Number(pageNumber.get())+1);
Meteor.subscribe('pubName', pageNumber.get(), 12);
} else {
noMoreItem.set(true);
}
}
Template.yourTemplateName.rendered = function () {
pageNumber.set(0);
Meteor.subscribe('pubName', pageNumber.get(), 12);
}
Template.yourTemplateName.helpers({
'dataArr': function () {
return CollectionName.find();
},
'noMoreItem': function () {
return noMoreItem.get();
}
})
Publication:
Meteor.publish("pubName", function (pageNumber, pageSize) {
Counts.publish(this, 'countItems', Meteor.users.find(filter), {
noReady: true
});
return CollectionName.find({}, {
skip: pageNumber > 0 ? ((pageNumber) * pageSize) : 0,
limit: pageSize
})
});
Say my Ractive data looks like this:
items: [
{ id: 16, name: "thingy" },
{ id: 23, name: "other thingy"}
]
I know I can do this to get the first item:
ractive.get('items.0')
But how do I get (or delete, or update, for that matter) the item who's id is 23?
Mostly a javascript issue, but you could put methods on your ractive instance or on the prototype generally. Assuming your array was not too large and using find and findIndex, you could do something like:
Ractive.prototype.getIndexById = function(keypath, id){
this.get(keypath).findIndex(function(each){
return each.id === id;
});
}
Ractive.prototype.getById = function(keypath, id){
return this.get(keypath).find(function(each){
return each.id === id;
});
}
Ractive.prototype.delete = function(keypath, id){
return this.splice(keypath, this.getIndexById(id), 1);
}
Ractive.prototype.update = function(keypath, id, data){
return this.set(keypath + '.' + this.getIndexById(id), data);
}
But if you're just trying to get a handle an item from which an action occurred, you should use the context:
{{#items:i}}
<li on-click='selected'>{{name}}</li>
<!-- or -->
<li on-click='selected(this, i)'>{{name}}</li>
{{/items}}
in your code
new Ractive({
...
selected: function(item, index){
// in lieu of passing in, you can access via this.event:
var item = this.event.context // current array member
var index = this.event.index.i // current index
},
oninit: function(){
this.on('selected', function(){
// same as method above
}
}
If you want to use jQuery, it can be done like this:
Ractive.prototype.getKeyById = function(keypath, id) {
var key;
key = -1;
$.each(this.get(keypath), function(i, data) {
if (data.id === id) {
key = i;
return false;
}
});
return key;
};
I'm writing a small application that shows live hits on a website. I'm displaying the hits as a table and passing each one to a template helper to determine the row's class class. The idea is that over time hits will change colour to indicate their age.
Everything renders correctly but I need to refresh the page in order to see the helper's returned class change over time. How can I make the helper work reactively?
I suspect that because the collection object's data isn't changing that this is why and I think I need to use a Session object.
Router:
Router.route('/tracked-data', {
name: 'tracked.data'
});
Controller:
TrackedDataController = RouteController.extend({
data: function () {
return {
hits: Hits.find({}, {sort: {createdAt: -1}})
};
}
});
Template:
{{#each hits}}
<tr class="{{ getClass this }}">{{> hit}}</tr>
{{/each}}
Helper:
Template.trackedData.helpers({
getClass: function(hit) {
var oneMinuteAgo = Date.now() - 1*60*1000;
if (hit.createdAt.getTime() > oneMinuteAgo) {
return 'success';
} else {
return 'error';
}
}
});
I've managed to get this working though I'm not sure it's the 'right' way to do it. I created a function that is called every second to update Session key containing the current time. Then, in my helper, I can create a new Session key for each of the objects that I want to add a class to. This session key is based upon the value in Session.get('currentTime') and thus updates every second. Session is reactive and so the template updates once the time comparison condition changes value.
var updateTime = function () {
var time = Date.now();
Session.set('currentTime', time);
setTimeout(updateTime, 1 * 1000); // 1 second
};
updateTime();
Template.trackedData.helpers({
getClass: function(hit) {
var tenMinutesAgo = Session.get('currentTime') - 10*1000,
sessionName = "class_" + hit._id,
className;
className = (hit.createdAt.getTime() > tenMinutesAgo) ? 'success' : 'error';
Session.set(sessionName, className);
return Session.get(sessionName);
}
});
Update
Thanks for the comments. The solution I ended up with was this:
client/utils.js
// Note that `Session` is only available on the client so this is a client only utility.
Utils = (function(exports) {
return {
updateTime: function () {
// Date.getTime() returns milliseconds
Session.set('currentTime', Date.now());
setTimeout(Utils.updateTime, 1 * 1000); // 1 second
},
secondsAgo: function(seconds) {
var milliseconds = seconds * 1000; // ms => s
return Session.get('currentTime') - milliseconds;
},
};
})(this);
Utils.updateTime();
client/templates/hits/hit_list.js
Template.trackedData.helpers({
getClass: function() {
if (this.createdAt.getTime() > Utils.secondsAgo(2)) {
return 'success';
} else if (this.createdAt.getTime() > Utils.secondsAgo(4)) {
return 'warning';
} else {
return 'error';
}
}
});
Consider this hypothetical example. Html:
{{#each items}}
{{>item}}
{{/each}}
Price = {{itemTotals.totalPrice}}
Qty = {{itemTotals.totalQty}}
Js:
Template.cart.items = function () {
var items = Items.find();
var cartTotals = [];
items.forEach(function(item){
..somecode...
cartTotals.price += item.price;
cartTotals.qty += 1;
});
return items;
}
And of course, we want a function "itemTotals". If we do price = {{items.itemTotals.totalPrice}}, and same for qty, we have to traverse items 3 times. I've tried to store the totals as a reactive variable as follows, but haven't been able to make it work:
Template.cart.rendered = function () {
this.totals = new ReactiveVar([]);
}
Template.cart.items = function () {
var items = Items.find();
var cartTotals = [];
items.forEach(function(item){
...
});
this.totals.set(cartTotals);
return items;
}
Template.cart.totals = function () {
return this.totals.get();
}
I'd like to 1) understand why the reactivity is not happening 2) find out if there is a way to have a template function wait on another - that would be more efficient than loading and reloading the values from a reactive variable
The other option suggested by Walter Zalazar would work, however, using Session forces us to store each value(price, quantity, etc) as a separate variable due to limitations of Session. After some trial and error, I've figured out the following way to store the entire totals array as one reactive variable:
totals = new ReactiveVar([]);
Template.cart.items = function () {
//foreach loop as before, calculate cartTotals, etc
totals.set(cartTotals);
return items;
}
Template.cart.totals = function () {
return totals.get();
}
and in html:
{{totals.totalPrice}}
{{totals.qty}}
you can to try something like this
Your .html
<template name="itemsList">
{{#each items}}
{{>item}}
{{/each}}
Price = {{totalPrice}}
Qty = {{totalQty}}
</template>
Your .js
Template.itemsList.helpers({
totalPrice: function(){
var price= 0;
var items= Items.find({});
items.forEach(function(item){
price+=item.price;
});
return price;
},
totalQty: function(){
return Items.find({}).count();
},
items:function(){
return Items.find({});
}
})
Just works!
Other option...
Tracker.autorun(function () {
Session.set("totalPrice");
Session.set("totalQty");
})
Template.itemsList.helpers({
totalPrice: function(){
return Session.get("totalPrice");
},
totalQty: function(){
return Session.get("totalQty");
},
items:function(){
var price= 0;
var items= Items.find({});
var count= items.count();
items.forEach(function(item){
price+= Number(item.price);
});
Session.set("totalPrice",price);
Session.set("totalQty",count);
return items;
}
});