consume events in the right order - javascript

Im using channel's of pusher in a Laravel application.
So when im fired an event from my controller this is received to my client, and with the pusher function I add some text with this lib https://github.com/albburtsev/jquery.typist on my front page:
channel.bind('App\\Events\\TextAdded', function(data) {
if(data.txt){
printPhrase(data.txt);
i++;
}
});
function printPhrase(txt) {
$('<span>')
.addClass('txt_' + i)
.appendTo('.typist_dialog')
.typist({
text:txt,
speed: 15,
}).on('end_type.typist', function() {
console.log('end') ;
}).typistStop() ;
}
});
As you can see I can catch the event "end_type" (when the function stop writing).
The problem is that i cannot -or I've no idea howto- puts "channel.bind" on queue, and so waiting untill that the printPhrase(txt) is finished... so not showing more than one printing for time at screen...

You'll have to set up some sort of queue to make sure they don't fire until the previous one is done. This code isn't tested but should do the trick:
var printQueue = [];
var queueWorking = false;
channel.bind('App\\Events\\TextAdded', function(data) {
if(data.txt){
printQueue.push(data.txt);
workQueue();
}
});
function printPhrase(txt) {
i++;
$('<span>')
.addClass('txt_' + i)
.appendTo('.typist_dialog')
.typist({
text:txt,
speed: 15,
}).on('end_type.typist', function() {
queueWorking = false;
workQueue();
}).typistStop() ;
}
function workQueue(){
if(printQueue.length && !queueWorking){
queueWorking = true;
printPhrase(printQueue.shift());
}
}

Related

CasperJS: WaitFor timeout function to do rescroll?

I met some problem when I use CasperJS to scrape a website. The website is dynamically loaded like Twitter, so I want to do infinite scroll,
and thanks to #Artjom B. I found you code to do this.
var tryAndScroll = function (casper) {
try {
casper.echo('SCROLL!!');
casper.scrollToBottom();
if (casper.exists('div.loading')) {
var curItems = casper.evaluate(getCurrentInfosNum);
casper.echo(curItems);
casper.waitFor(function check() {
return curItems != casper.evaluate(getCurrentInfosNum);
}, function then() {
casper.wait(800);
tryAndScroll(casper);
}, function onTimeout() {
casper.emit('scroll.timeout',curItems);
}, 15000);
} else {
casper.echo("No more items");
return true;
}
} catch (err) {
casper.echo(err);
}
} //casper.tryAndScroll
And now, I want to continue to scroll many times when the timeout function invoked so I create my own event listener,‘scroll.timeout’.
var SRCOLL_NUM = 0;
var PreOfLoaded = 0;
casper.on('scroll.timeout', function (NumOfLoaded) {
if (SRCOLL_NUM <= 4) {
if (PreOfLoaded == NumOfLoaded)
SRCOLL_NUM++;
this.echo("Scroll Timeout,reScroll");
PreOfLoaded = NumOfLoaded;
tryAndScroll(casper);
} else {
this.echo("Scroll Timeout,reScroll times maximum");
SRCOLL_NUM = 0;
PreOfLoaded = 0;
}
});
However, when scroll timeout occurred, it printed Scroll Timeout,reScroll on the console. Then it skips tryAndScroll() and go to the next step in the main function. I want to continue to next step after retry scroll many times. What should I do?
I found CasperJS author illustrate :Automatic retry when open fails
var casper = require('casper').create();
casper.tryOpen = function(url, then) {
return this.then(function() {
this.open(url);
this.waitFor(function testStatus() {
return this.getCurrentHTTPStatus === 200;
}, then, function onFail() {
console.log('failed, retrying');
this.tryOpen(url);
}, 2000);
});
};
casper.start().tryOpen('http://failing.url.com/foo.bar', function() {
this.echo('wow, it worked, wtf');
}).run();
unfortunately, it doesn't work for me.
Try this
return this.currentHTTPStatus === 200;
I tested with the newest version of casperjs 1.1.1, it's working fine

Scraping an infinite scroll page stops without scrolling

I am currently working with PhantomJS and CasperJS to scrape for links in a website. The site uses javascript to dynamically load results. The below snippet however is not getting me all the results the page contains. What I need is to scroll down to the bottom of the page, see if the spinner shows up (meaning there’s more content still to come), wait until the new content had loaded and then keep scrolling until no more new content was shown. Then store the links with class name .title in an array. Link to the webpage for scraping.
var casper = require('casper').create();
var urls = [];
function tryAndScroll(casper) {
casper.waitFor(function() {
this.page.scrollPosition = { top: this.page.scrollPosition["top"] + 4000, left: 0 };
return true;
}, function() {
var info = this.getElementInfo('.badge-post-grid-load-more');
if (info["visible"] == true) {
this.waitWhileVisible('.badge-post-grid-load-more', function () {
this.emit('results.loaded');
}, function () {
this.echo('next results not loaded');
}, 5000);
}
}, function() {
this.echo("Scrolling failed. Sorry.").exit();
}, 500);
}
casper.on('results.loaded', function () {
tryAndScroll(this);
});
casper.start('http://example.com/', function() {
this.waitUntilVisible('.title', function() {
tryAndScroll(this);
});
});
casper.then(function() {
casper.each(this.getElementsInfo('.title'), function(casper, element, j) {
var url = element["attributes"]["href"];
urls.push(url);
});
});
casper.run(function() {
this.echo(urls.length + ' links found:');
this.echo(urls.join('\n')).exit();
});
I've looked at the page. Your misconception is probably that you think the .badge-post-grid-load-more element vanishes as soon as the next elements are loaded. This is not the case. It doesn't change at all. You have to find another way to test whether new elements were put into the DOM.
You could for example retrieve the current number of elements and use waitFor to detect when the number changes.
function getNumberOfItems(casper) {
return casper.getElementsInfo(".listview .badge-grid-item").length;
}
function tryAndScroll(casper) {
casper.page.scrollPosition = { top: casper.page.scrollPosition["top"] + 4000, left: 0 };
var info = casper.getElementInfo('.badge-post-grid-load-more');
if (info.visible) {
var curItems = getNumberOfItems(casper);
casper.waitFor(function check(){
return curItems != getNumberOfItems(casper);
}, function then(){
tryAndScroll(this);
}, function onTimeout(){
this.echo("Timout reached");
}, 20000);
} else {
casper.echo("no more items");
}
}
I've also streamlined tryAndScroll a little. There were completely unnecessary functions: the first casper.waitFor wasn't waiting at all and because of that the onTimeout callback could never be invoked.

ngInfiniteScroll "load next page" function being called repeatedly

Using the "loading remote data" example from the ngInfiniteScroll website I have tried to implement infinite scrolling. My issue;
The function nextPage() gets called continuously until there are no more records left to load (controlled by an offset value in the SQL query).
I'd appreciate any input on this as I'm rather lost.
Thanks in advance.
HTML
<tbody>
<div id="infinite_scroll" infinite-scroll='visits.nextPage()' infinite-scroll-disabled='visits.busy' infinite-scroll-distance='1'>
<tr ng-repeat="visit in visits.items" ng-class="{active: visit.uuid == data.detailItem.uuid}" ng-click="openDetailItem(visit.uuid)">
<td>{{visit.details.name}}</td>
<td>{{visit.created_at}}</td>
</tr>
</div>
</tbody>
Javascript - AngularJs Factory
angular.module('app').factory('Visits', function(VisitResource) {
// new visits object
var Visits = function () {
this.items = [];
this.busy = false;
this.offset = 0;
};
Visits.prototype.nextPage = function () {
// busy - stop
if (this.busy == true) {
// DEBUG
console.log('busy test 1.1: ' + this.busy);
return;
} else {
// DEBUG
console.log('busy test 1.2: ' + this.busy);
}
// busy now
this.busy = true;
VisitResource.getVisitations({
limit: 500,
offset: this.offset
}, function (response) {
// stop loading if no data returned
if(response.data.length == 0) {
// DEBUG
console.log('busy test 2: ' + this.busy);
return;
} else {
// DEBUG
console.log('Payload: ' + response.data.length);
}
var _this = this;
angular.forEach(response.data, function (a_visit) {
_this.items.push(a_visit);
});
// set the last acquired record value
this.offset = this.items[this.items.length - 1].id;
// not busy
this.busy = false;
}.bind(this));
};
return Visits;
});
As it turns out you can't get vanilla nginfinitescroll to trigger when the container is scrolled as nginfinitescroll is looking at the height of the window.
Here is a link to the answer on SO:
angularjs infinite scroll in a container

How to delay 5 seconds in JavaScript

I have JavaScript function:
function validateAddToCartForm(object) {
value = $(".product-detail-qty-box").val();
if(!isInt(value) || value < 1) {
$(".product-detail-error").show();
return false;
} else {
$(".product-detail-error").hide();
var product_name = $("#product_detail_name").text();
var NewDialog = $('<div id="MenuDialog">\ ' + product_name + '</div>');
NewDialog.dialog({
modal: true,
title: "title",
show: 'clip',
hide: {effect: "fadeOut", duration: 1000}
});
}
return true;
}
I need to pause 3 to 5 seconds before returning true, because I want to show a New Dialog box for a while. How can I implement this delay?
The only way to simulate delay in js is callback on timeout.
change the function to:
function validateAddToCartForm(object,callback) {
value = $(".product-detail-qty-box").val();
if(!isInt(value) || value < 1) {
$(".product-detail-error").show();
callback(false);
} else {
$(".product-detail-error").hide();
var product_name = $("#product_detail_name").text();
var NewDialog = $('<div id="MenuDialog">\ ' + product_name + '</div>');
NewDialog.dialog({
modal: true,
title: "title",
show: 'clip',
hide: {effect: "fadeOut", duration: 1000}
});
}
setTimeout(function() {callback(true);},5000);
}
where you call it you should do something like:
instead of
function somefunct() {
//code before call
if (validateAddToCartForm(object)) {
//process true
} else {
//process false
}
//rest of the function
}
place something like:
function somefunct() {
//code before call
validateAddToCartForm(object,function(ret) {
{
if (ret) {
//process true
} else {
//process false
}
//rest of the function
}
}
In to answer to your comment.
I assume:
that you want to prevent click event if validate false,
that all elements that you added onclick="..." have class ".clickme",
the element now looks like
<input type="submit" onclick="return validateAddToCartForm(this)" class="clickme" />
so 1st change the element to
<input type="submit" class="clickme" />
add to your javascript the following:
//this handle original click, drop it out, and only pass after validation
$(function () {
$('.clickme').click(function (e) {
var $t = $(this);
//if event triggered return true
if (e.isTrigger) return true;
validateAddToCartForm(this, function (ret) {
if (ret) {
$t.trigger('click');
}
});
return false;
});
});
also I suggest to use "submit" event on the form itself instead of "click" (the demo of submit)
Instead of blocking, you can use seTimeout to remove the #MenuDialog after a certain time.
function validateAddToCartForm(o){
var keep_dialog_time = 5 * 1000; // five seconds.
// Whatever...
/* Use setTimeout(function, milliseconds) to delay deletion of #MenuDialog.
This will get executed keep_dialog_time milliseconds after this call, and
won't block. */
setTimeout(function(){
$('#MenuDialog').hide(); // maybe there is another function to do this, I'm not a jQuery guy really.
}, keep_dialog_time);
return true;
}
JavaScript is single threaded. This means, when you block, you block the everything. Thus, the DOM uses an event loop model, where callbacks are assigned to events. Such a model is also present in node.js too. Above, because setTimeout does not block, code after that call will continue to run without waiting the function we passed to setTimeout to get executed.
I'd suggest to study DOM in depth to get more comfortable with web front-end programming. You may use various search engines to find out cool documentation.

jQuery adding functions to the animation queue

The problem is that when I try doing multiple animations they all happen the same time.
Is there any way to have animations go one after another without using callbacks?
Here's what I want to do:
$('#a1').click(function() { $('#div1').hide(3000); });
$('#a2').click(function() { $('#div2').hide(3000); });
$('#a3').click(function() { $('#div3').show(3000); });
If you click on #a1 and then click on #a2 then #a3 before the first animation completes then it shouldn't start right away but instead wait until the animation queue is empty then start the next one.
Take this demo for example
I want to be able to click a1 then a2 then a3 one after the another and first have it hide the first div completely, then the second completely, and then show the third.
My example is overly simple and while this can be done with callbacks, my real problem can't so callbacks aren't an option.
In essence, if you click all three the animation should complete in 9 seconds.
This DEMO should alert ('took around 9 seconds to complete')
Use .queue() on a common jQuery object:
var q = $({}); // this could also be a common parent, e.g. $('body')
$('#a1').click(function() {
q.queue(function(next) {
$('#div1').hide(3000, next);
});
return false;
});
$('#a2').click(function() {
q.queue(function(next) {
$('#div2').hide(3000, next);
});
return false;
});
$('#a3').click(function() {
q.queue(function(next) {
$('#div3').show(3000, next);
});
return false;
});​
Demo
Use .promise() to sidestep callbacks on show and hide:
The .promise() method returns a dynamically generated Promise that is resolved once all actions of a certain type bound to the collection, queued or not, have ended.
By default, type is "fx", which means the returned Promise is resolved when all animations of the selected elements have completed
Use .queue() to limit the number of animations resolved per promise (See also jsFiddle):
var promises = $({});
$('#a1').click(function() {
promises.queue(function(next) {
$('div').promise().done(function() {
$('#div1').hide(3000);
next();
});
});
return false;
});
$('#a2').click(function() {
promises.queue(function(next) {
$('div').promise().done(function() {
$('#div2').hide(3000);
next();
});
});
return false;
});
$('#a3').click(function() {
promises.queue(function(next) {
$('div').promise().done(function() {
$('#div3').show(3000);
next();
});
});
return false;
});
Try to create some array with queue, and check if there is something in it, as callback for animation, and run it again if there is. I've played with your example a little.
check it out:
http://jsfiddle.net/acrashik/nqh6x/6/
var queue = {
q: [],
run: function(elem, type, time, recall) {
if (queue.isRunning && !recall) {
console.log('pushed: ' + elem + type + time);
queue.q.push({elem:elem, type:type, time:time});
} else {
console.log('running:' + elem);
queue.isRunning = true;
if (type=='hide') {
$(elem).hide(time, function(){
queue.recall();
})
} else {
$(elem).show(time, function(){
queue.recall();
})
}
}
},
recall: function(){
console.log(queue.q.length);
if (queue.q.length > 0) {
queue.run(queue.q[0].elem, queue.q[0].type, queue.q[0].time, true);
queue.q = queue.q.splice(1,queue.q.length);
} else {
queue.isRunning = false;
queue.q = [];
}
},
isRunning: false
}
$('#a1').click(function() { queue.run('#div1','hide',2200) });
$('#a2').click(function() { queue.run('#div2','hide',2200) });
$('#a3').click(function() { queue.run('#div3','show',2200) });
I would use the animate() function as it comes with a complete function which is called when the animation finishes http://api.jquery.com/animate/.
So to use the jQuery doc example:
$('#clickme').click(function() {
$('#book').animate({
opacity: 0.25,
left: '+=50',
height: 'toggle'
}, 5000, function() {
// Animation complete this is where you call the next animation...
});
});
function another_animation () {
$('xyz').animate({
opacity: 0.25,
left: '+=50',
}5000, function() {
// Animation complete this is where you call the next animation
I think that this is the cleanest way...
You could do something like this:
(function(){
var col=(function(){
var i=1;
return function(n){alert("you clicked link nr " + n + ", total clicks: "+i++)};
})();
$('#a1').click(function(){col(1)});
$('#a2').click(function(){col(2)});
$('#a3').click(function(){col(3)});
})();
Im not gona write the entire code for you but that should give you a good idea of how to do it.
Also none of the variables or functions are accessible from the global scope or any other.
Add stop() before the animation:
$('#a1').click(function() { $('#div1').stop().hide(3000); });
$('#a2').click(function() { $('#div2').stop().hide(3000); });
$('#a3').click(function() { $('#div3').stop().show(3000); });
Use this
$(element).promise().done(function () {...})
In your case
$('#a1').click(function () {
$('#div1').hide(3000);
});
$('#a2').click(function () {
$("#div1").promise().done(function () {
$('#div2').hide(3000);
});
});
$('#a3').click(function () {
$("#div2").promise().done(function () {
$('#div3').hide(3000);
});
});
The next animation will be performed only when the previous animation on selected element is complete.

Categories

Resources