Can’t call View#subscribe from inside the destroyed callback - meteor

I got an error and strange behavior inside template.onDestoyed;
I have code for infinite scroll subscribtion (it stored in special subscribtion-template) It work fine, until i switch to another route, and create a new instance of subscriber-template.
Code:
Template.subscriber.onCreated(function() {
var template = this;
var skipCount = 0;
template.autorun(function(c) {
template.subscribe(template.data.name, skipCount, template.data.user);
var block = true;
$(window).scroll(function() {
if (($(window).scrollTop() + $(window).height()) >= ($(document).height()) && block) {
block = false;
skipCount = skipCount + template.data.count;
console.log(template.data);
console.log("skip_count is "+skipCount);
template.subscribe(template.data.name, skipCount, template.data.user, {
onReady: function() {
block = true;
},
onStop: function() {
console.log('route switched, subscribtion stopped');
}
});
}
});
})
});
When i "scroll down" on a page, subscriber work fine, when i go in another page and "scroll down" first i get a data from old subscriber template (what must be destroyed in theory) in first time. In second time (scroll down again) new instance of subscriber starts works normally.
PIRNT SCREEN CONSOLE
What i doing wrong?

Owch!
The good guy from meteor forums helped me.
Actually the problem is in jquery.scroll event. It not cleaned up when template is destroyed. (Is it a bug? Or it is normal behavior?). I just needed to unbind the scroll event in onDestroyed section.

Related

Find out when full calendar loaded events

I have a fullCalendar widget created somewhere. I cannot change the code that initialize it. So I cannot add any callbacks in the first call.
Anything like this:
$(elem).fullCalendar({
complete: function () {
...
}
})
Actually creates a new fullCalendar instead of modifying the actual fullCalendar to change/add the complete callback.
Is there an other way to find out when events are loaded I was thinking about polling clientEvents but I realize that I could have no events in one month so I cannot expect the array to always have something in it.
By the way, it's fullCalendar 1.6.
You can define callbacks after the calendar object has been initialized, and to determine when all events have been rendered, use the eventAfterAllRender event. Here's how:
var calendar = $('#calendar').fullCalendar('getCalendar');
calendar.on('eventAfterAllRender', function(view) {
alert('all events rendered!');
});
Nevermind, this feature is only available starting in version 2.4.
Instead, you could poll the DOM for fullcalendar element existence, like this:
function checkForInit() {
if($(".fc-view").children().length) {
alert("initialized!");
} else {
setTimeout(function() {
checkForInit();
}, 10);
}
}
$(document).ready(function() {
checkForInit();
});
You can use eventAfterAllRender event, available from version 1.6:
$(elem).fullCalendar({
eventAfterAllRender: function (view) {
...
}
})
Ok I found a solution that seems to be working!
var waitPrint = true
function autoPrint() {
var elem = $('.fc')
var calendar = elem.data('fullCalendar')
console.log('Searching calendar')
if (calendar && waitPrint) {
waitPrint = false
console.log('Bund new event')
var _super = calendar.options.eventAfterAllRender
calendar.options.eventAfterAllRender = function (event, element, view) {
if (_super) {
_super.apply(this, arguments)
}
window.print()
}
} else if (waitPrint) {
setTimeout(autoPrint, 100)
}
}
autoPrint()
Here I'm polling for an element with the fc class. As soon as I find one, I check for the existence of the "data" named fullCalendar. If it returns a dict, then it means that the fullCalendar instance has been created. This is pretty much what Dane proposed for version 2.4 but this in 1.6 there is no getter. We have to get it ourselves. Luckily, it's stored in the data of the element and not in some other cryptic places.
Move on to the next step, fullCalendar isn't an eventEmitter in 1.x, but we still have access to options which seems to be just a reference to the options that were passed at first. I override the eventAfterAllRender. Call the method that was already defined if present and call my print method when it's done.
Technically from there, we can override almost any defined method from there. The only problem is that you have to do it faster than fullCalendar get initialized.
I believe that if we dig deeper, we could potentially patch the calendar library directly to remove the timing issues. Polling isn't very great.
Best Solution So far
var oldFullCalendar = $.fn.fullCalendar
function newFull(options) {
var _super_func = null
if (typeof options == 'string') {
oldFullCalendar.apply(this, arguments)
} else {
options = options || {}
if (options.loading) {
_super_func = options.loading
}
options.loading = function (loading) {
console.log(loading, 'loading')
if (_super_func) {
_super_func.apply(this, arguments)
}
if (!loading) {
window.print()
}
}
oldFullCalendar.apply(this, arguments)
}
}
$.fn.fullCalendar = newFull
The first solution could probably be improved by overriding loading instead. Since it's the method that notify when loading has been processed and which is also apparently called after the eventAfterAllRender callback.

Tracker.autorun() not executing jQuery functions

I am updating some Session variables with Router.onAfterAction whenever my route changes. Then I have a Tracker.autorun() waiting for a the change so it can run some logic and update some DOM classes. I checked the Sessions are set correctly and that the element to-be appended is targeted correctly. But my addClass() does not trigger. No console errors either. What am I missing?
Tracker.autorun(function (c) {
var colActive = Session.equals('navColumnActive', true);
var colVisible = Session.equals('navColumnVisible', true);
var $col = $("#nav-column");
console.log($col);
if (colActive) {
console.log("if triggered");
$col.addClass( "active" );
} else {
console.log("else triggered");
$col.removeClass("active");
}
if (colVisible) {
$col.addClass("visible");
} else {
$col.removeClass("visible");
}
});
I had the same problem just recently. In my case the reactive values triggering the computation where being executed before the DOM and the elements where properly rendered.
I would check if the session vars are being set after the DOM has been rendered. Therefore selectors like $col = $("#nav-column") came back empty, though perfectly worked when executed in the browser console.
You could try the following:
Template.<template_name>.onRendered(function() {
Session.set('navColumnActive', true);
Session.set('navColumnVisible', true);
})

Meteor Iron Router - Router.go callback possible?

I've got a link that I want the user to press. When they press it, the router will go to a certain template and then run Smoothscroll.js code to animate and scroll down to the anchor tag.
//When the user clicks on the link to get to the #contact anchor...
//The code below does not work.
Router.go('index');
$('html,body').animate({
scrollTop: $('#contact').offset().top
}, 1200);
Router.go('index') works just fine.
$('html,body').animate({
scrollTop: $('#contact').offset().top
}, 1200);
Works as well by itself on the index template.
But when I try to run them together, the router goes to index, but the scrolling does not work.
Any idea how I can do this?
EDIT
This is what I have for the latest Meteor 1.0+ and a path like /#contact
Router.route('/', {
name: 'index'
});
Router.onAfterAction(function() {
var self = this;
// always start by resetting scroll to top of the page
$(window).scrollTop(0);
// if there is a hash in the URL, handle it
if (this.params.hash) {
// now this is important : Deps.afterFlush ensures that iron-router rendering
// process has finished inserting the current route template into DOM so we
// can manipulate it via jQuery, if you skip this part the HTML element you
// want to scroll to might not yet be present in the DOM (this is probably
// why your code fails in the first place)
Tracker.afterFlush(function() {
if (typeof $("#" + self.params.hash).offset() != "undefined"){
var scrollTop = $("#" + self.params.hash).offset().top;
$("html,body").animate({
scrollTop: scrollTop
});
}
});
}
});
Unless you're doing something fancy you probably don't want to use Router.go and instead let iron-router manage routing on anchor click as it normally does.
As far as scrolling to an element is concerned, this is the onAfterAction hook I'm using, it supports any route and any hash (/anyroute#anyhash).
Router.onAfterAction(function() {
// always start by resetting scroll to top of the page
$(window).scrollTop(0);
var hash=this.params.hash;
// if there is a hash in the URL, handle it
if (hash) {
// now this is important : Tracker.afterFlush ensures that iron-router
// rendering process has finished inserting the current route template
// into DOM so we can manipulate it via jQuery, if you skip this part
// the HTML element you want to scroll to might not yet be present in
// the DOM (this is probably why your code fails in the first place)
Tracker.afterFlush(function() {
var element=$("#"+hash);
var scrollTop = element.offset().top;
$("html,body").animate({
scrollTop: scrollTop
});
});
}
});

google maps API v3 events

I have the following situation:
A polyline is added on the map and when the user clicks over it its state changes to editable. Also i have event where if the user clicks the last vertext of the polyline and starts moving the mouse to be able to extend the polyline with the mouse path the user is drawing.
However it seems that when i have an event and inside this event i try to add another one it simply does not work and i don't kwow why.
Just in case to make things simpler to undrstand i will paste a part of my code.
google.maps.event.addListener(polyLine, "mousedown", function(event){
if(polyLine.getEditable() === true)
{
if(typeof event.vertex !== "undefined")
{
if(event.vertex === polyLine.getPath().getLength() - 1)
{
polyLine.setEditable(false);
if(mouseMoveDrawingEvent === null)
{
map.setOptions({draggable:false});
polyLine.setOptions({clickable:false});
mouseMoveDrawingEvent = google.maps.event.addListener(map, "mousemove", function(event)
{
alert("1"); // <== this never fires
polyLine.getPath().push(event.latLng);
drawingLabel.setPoint(event.latLng);
drawingLabel.setContents("<div style='background-color:white'>" + (google.maps.geometry.spherical.computeLength(polyLine.getPath()) / 1000).toFixed(2) + " км.</div>");
});
}
map.getDiv().onmouseup = function(ev) {
polyLine.setOptions({clickable:true});
map.getDiv().onmousedown = null;
map.getDiv().onmouseup = null;
google.maps.event.removeListener(mouseMoveDrawingEvent);
mouseMoveDrawingEvent = null;
};
}
}
}
});
.....
thre is another event here that listens for 'mouseup'....
Do you guys have any idea how to make this peace of code works.
I found a solution to my questions.
The problem was that when i set the polyline {clickable:false} the api removes the event ( and obviously everyhing inside it:)

Binding Keyup to an Element using jQuery extension and jquery.on

This seems very simple, but for some reason it's not working as expected.
I am trying to make a very simple jQuery extension/plugin which allows me to simply reduce my lines of code when requiring a trigger on an enter key (and a similar for an escape)
Here's my code:
$.fn.enterListen = function (callBack) {
$(this).on('keyup', function (e) {
if (e.keyCode == 13) {
callBack;
// $(this).enterListen(callBack); // trying to rebind...
};
})
};
Then when an element is dynamically created with jquery we might do something like:
var input = $('<input'>).enterListen(function (){ alert("Enter was pressed"); });
$(input).appendTo('body');
Now we've added an input element to the page, in which we can type and when enter is pressed it triggers the alert.
This works, except, only once.
You can see a commented out line in my code above where I am trying to rebind the function the the element after the enter trigger is activated, and even that doesn't make it work a second time.
You can press as many other keys as you like before pressing Enter, but as soon as you do, it seems to unbind the keyup event.
IF... however, I run it like this:
function isEnter(e, ele) {
if ((e * 1) == 13) {
$(ele).click();
};
};
Called by:
var input = $('<input'>).on('keyup', function (e) { isEnter(e.keyCode, $(ok)) });
$(input).appendTo('body');
It works fine, but to me it is clumsier in the code, I am trying to create a library of extensions to make the inner coding of this project a bit shorter... perhaps I am just putting too much time into something I needn't...
Anyway, if anyone could shed any light on why the event becomes unbound, that'd be lovely!
Inside a jQuery plugin, this is the jQuery object, no need to rewrap it. e.which is normalized in jQuery. To execute a function you need parenthesis (). And most importantly, you need to return this otherwise the input variable will be undefined, and if you intend to do stuff inside your plugin with selectors containing multiple elements, you need to return this.each(function() { ... }) etc. as explained in the plugin authoring documentation from jQuery.
$.fn.enterListen = function (callBack) {
return this.each(function() {
$(this).on('keyup', function (e) {
if (e.which == 13) {
e.preventDefault();
callBack();
}
});
});
};
var input = $('<input />').enterListen(function (){
alert("Enter was pressed");
});
input.appendTo('body');
FIDDLE

Resources