I'm working on an interactive tutorial-tool for JavaScript. The core of the tool is the script of the tutorial. The script will trigger various functions that run animations, speaker-voices load new pages etc. Three sample calls(most tutorials will have 10-100s of calls, so a neat overview of the calls is highly desired:
wrap(); //wrap the page in an iframe
playsound('media/welcome') //playing a sound (duh)
highlight('[name=firstname]'); //animation that highlights an element.
playsound('media/welcome2');
loadpage(page2); //loading a new page
All calls have something in common: they have non-normal-triggers. In this simple script for example, the second call should be triggered once the iframe in the first call is loaded. The third script is triggered once the sound is complete (ie delay). The fourth function should be triggered once the animation is complete. The fifth event should be triggered on an event (for example a click).
A technical solution to this would be to call the function in the callback of the previous function, this has the potential to get pretty messy. What I like with a solution wherer the functions are called lite this is that someone with a little bit of brains, but no coding experience could hammer up a script of their own. How would you solve this? I'm pretty new to javascript so if you could be explicit i'd appreciate it.
I'd use a per-built solution. There is bound be one that fits your needs. Something simple like jTour or if that doesn't cover it something a little more complex like Scriptio. Some of the answers to this question may also be of interest to you.
Edit
If you don't want to use a preexisting solution, I'd do something like this:
var runTutorial = (function () {
// The command object holds all the different commands that can
// be used by someone for the tutorial. Each of these commands
// will recive a callback set as their `this`. This
// callback should be called by your commands when they are done
// running. The person making the tutorial won't need to know
// about the callback, the code will handle that.
var commands = {
wrap: function () {
//wrap the page in an iframe
this();
},
playsound: function (soundPath, soundLength) {
//playing a sound (duh)
setTimeout(this, soundLength);
},
highlight: function (selector) {
//animation that highlights an element.
//I'm using jQuery UI for the animation here,
// but most animation libraries should provide
// a callback for when the animation is done similarly
$(selector).effect('highlight', 'slow', this);
},
loadpage: function (pageUrl) {
//loading a new page
setTimeout(this, 500);
},
waitForClick: function () {
// when we go into the click handler `this` will no
// longer be availble to us since we will be in a
// different context, save `this` into `that` so
// we can call it later.
var that = this;
$(document).one('click', function () {
that();
});
}
},
// This function takes an array of commands
// and runs them in sequence. Each item in the
// array should be an array with the command name
// as the first item and any arguments it should be
// called with following as the rest of the items.
runTutorial = function (commandList) {
var nextCommand = function () {
if (commandList.length > 0) {
var args = commandList.shift();
// remove the command name
// from the argument list
cmd = args.shift(1);
// call the command, setting nextCommand as `this`
commands[cmd].apply(nextCommand, args);
}
}
nextCommand();
};
return runTutorial;
}());
$('#tutorialbutton').click(function() {
runTutorial([
['playsound', 'media/welcome', 1000],
['highlight', '[name=firstname]'],
['playsound', 'media/welcome2', 1500],
['waitForClick'],
['loadpage', page2],
['playsound', 'media/page2', 100]
]);
});
The runTutorial function takes a simple array containing the commands in the order they should be run, along with their parameters. No need to bother the person writing the script with callbacks, runTutorial handles that for them. This has some big advantages over a system that requires the writer to manage callbacks. You don't need an unique name for each line in the script as you do with explicit callbacks, nor endless nesting of anonymous functions. You don't need to rewire anything to change the order that the commands are played in, you just physically rearrange them in the array.
jsfiddle you can play with
Each of your commands will need to wait for its action to be done before it calls its callback (aka this). I simulate this in the fiddle using setTimeout. For instance, if you are using jQuery's .animate for highlight, it provides a complete handler that fires when the animation is done, just stick this (with out the invocation parentheses ()) there. If you are using jQuery UI, it has a built-in 'highlight' effect, so you could implement it like this:
highlight: function (selector) {
//animation that highlights an element.
$(selector).effect('highlight', 'slow', this);
},
Most other libraries that provide animations should provide a similar callback option you can use.
Controlling the callback for the sounds may be harder depending on how you are playing them. If the method you are using doesn't provide a callback or a way of polling it to see if it is done yet you might just have to add another parameter to playsound that takes the length of the sound in ms and then waits that long before proceeding:
playsound: function (soundPath, soundLength) {
//playing a sound (duh)
setTimeout(this, soundLength);
},
Callbacks are your best bet, I think. They don't have to be messy (though it's certainly possible to make them completely incomprehensible). You could create each function to accept a callback, then use a structure like this to call them in sequence in a readable way:
var loadingSequence = {
start : function() { wrap(this.playsound); },
playsound : function() { playsound('media/welcome', this.highlight); },
highlight : function() { highlight('[name=firstname]', this.playsound2); },
playsound2 : function() { playsound('media/welcome2', this.loadpage); },
loadpage : function() { loadpage(page2); }
};
loadingSequence.start();
Related
Good day fellow coders,
After tinkering awhile, I still couldn't find a way to invoke visual effects in jQuery and trigger a function afterwards. The program either completes mere visual effects, such as a vertical flip, or solely executes the denoted function. Is there a way to firstly complete the graphical effects and trigger the included function lastly, in just one click?
Below the pertaining code:
$("#HTMLButton").click(function(){
$("#Div").slideUp(400); //Is only run
arbitraryFunction; //Ignored
});
$("#HTMLButton").click(function(){
arbitraryFunction; //Is only run
$("#Div").slideUp(400); //Ignored
});
Thank you in advance!
First thing to do is have a look at the jQuery docs
- https://api.jquery.com/slideUp/
.slideUp( [duration ] [, complete ] )
duration (default: 400)
Type: Number or String
A string or number determining how long the animation will run.
complete
Type: Function()
A function to call once the animation is complete, called once per matched element.
So rather than a Promise interface, they offer a more traditional callback interface
$("#HTMLButton").click(function(){
$("#Div").slideUp(400, () => {
arbitraryFunction()
});
});
An alternative approach (if you lacked a callback mechanism) would be a setTimeout() function:
$("#HTMLButton").click(function(){
$("#Div").slideUp(400);
setTimeout(() => {
arbitraryFunction();
}, 400);
});
I'm trying to achieve this with jQuery + JavaScript:
I have commands/functions that need to be called in a sequence, with a small delay in between them. Examples of those include changing the css property of an element, showing hiding another etc.
JavaScript does not have a sleep function as far as I know. So I'm wondering if jQuery has a plugin or something that supports this feature?
Essentially, a function like $(window).schedule(function() { /* do something here*/ }, 500); would be nice. This will push the function into the queue and will be executed as soon as all the previous functions in the queue are executed, if there are no functions in the queue, it will be executed immediately. The integer parameter specifies the delay between this function and the one previous to it.
I think I know how to build this from the ground up, but I'm hoping there is a plugin for this because it will save me from reinventing the wheel.
If there isn't.. I'll build this and release it. :)
I'm not aware of a specific plugin that already exists (although I'd be surprised if there isn't one). But if you just want a general queue that isn't associated with any particular element it is easy enough to do without jQuery, perhaps something like this:
function Scheduler() {
var queue = [],
timer,
next = function () {
var item = queue.shift();
if (item) {
timer = setTimeout(function () {
item.cb.call(item.thisObj);
timer = null;
next();
}, item.delay);
}
};
this.schedule = function (delay, cb, thisObj) {
queue.push({
cb: cb,
delay: delay,
thisObj: thisObj
});
if (!timer) next();
return this;
};
}
var scheduler = new Scheduler();
scheduler.schedule(2000, function () {
$("h1").css("color", "red");
});
scheduler.schedule(500, someFunc)
.schedule(3000, someOtherFunc)
.schedule(1500, anotherFunc);
The main .schedule() method returns the instance of the scheduler, so you can chain repeated calls as shown. And you can (optionally) pass a context for the callback function as shown in the following demo: http://jsfiddle.net/euggc0r2/1/
Use jQuery's built in queue(), dequeue(), and delay() methods, like so:
$(function() {
$('#yourElement')
.queue('myQueue', function() {
/* do stuff... */
// ...then tell jQuery to run the next method
// in the 'myQueue' queue in 2 seconds.
$(this).delay(2000, 'myQueue').dequeue('myQueue');
})
.queue('myQueue', function() {
/* do different stuff... */
// ...then tell jQuery to run the next method
// in the 'myQueue' queue in 2 seconds.
$(this).delay(2000, 'myQueue').dequeue('myQueue');
})
...
...
...
.dequeue('myQueue'); // run the first function in the queue.
})();
Typically you would queue all your functions, then make the initial dequeue() call when they're all complete.
I have been trying to chain this last bit of code to work nicely.
It calls two functions that have been preset:
slideToImage(newIndex);
setCaption();
I want the caption to be set once the image has slid.
This has always been a confusing topic for me as I have tried different ways of calling it such as:
callbacks = $.Callbacks();
callbacks.add(slideToImage(newIndex));
callbacks.add(setCaption());
callbacks.fire();
But that does not work.
slideToImage must contain some animation logic that happens over time. So slideToImage would start the animation process and then return before the animation has completed which causes setCaption to be called to early.
You should pass setCaption into slideToImage as a callback and call it once the animation has completed.
function slideToImage(newIndex, callback) {
// I assume you're using jQuery for animation
targetElement.animate({left: 100}, 200, function() {
// once animation has finished we call the callback function
callback();
});
}
slideToImage(newIndex, function() {
setCaption();
});
For "setCaption", it's easy:
callbacks.add(setCaption);
For "slideToImage", since you need to pass a parameter, it's slightly more involved:
callbacks.add(function() { slideToImage(newIndex); });
OVERVIEW
I'm working on a project and I've come across a bit of a problem in that things aren't happening in the order I want them to happen. So I have been thinking about designing some kind of Queue that I can use to organize function calls and other miscellaneous JavaScript/jQuery instructions used during start-up, i.e., while the page is loading. What I'm looking for doesn't necessarily need to be a Queue data structure but some system that will ensure that things execute in the order I specify and only when the previous task has been completed can the new task begin.
I've briefly looked at the jQuery Queue and the AjaxQueue but I really have no idea how they work yet so I'm not sure if that is the approach I want to take... but I'll keep reading more about these tools.
SPECIFICS
Currently, I have set things up so that some work happens inside $(document).ready(function() {...}); and other work happens inside $(window).load(function() {...});. For example,
<head>
<script type="text/javascript">
// I want this to happen 1st
$().LoadJavaScript();
// ... do some basic configuration for the stuff that needs to happen later...
// I want this to happen 2nd
$(document).ready(function() {
// ... do some work that depends on the previous work do have been completed
var script = document.createElement("script");
// ... do some more work...
});
// I want this to happen 3rd
$(window).load(function() {
// ... do some work that depends on the previous work do have been completed
$().InitializeSymbols();
$().InitializeBlock();
// ... other work ... etc...
});
</script>
</head>
... and this is really tedious and ugly, not to mention bad design. So instead of dealing with that mess, I want to design a pretty versatile system so that I can, for example, enqueue $().LoadJavaScript();, then var script = document.createElement("script");, then $().InitializeSymbols();, then $().InitializeBlock();, etc... and then the Queue would execute the function calls and instructions such that after one instruction is finished executing, the other can start, until the Queue is empty instead of me calling dequeue repeatedly.
The reasoning behind this is that some work needs to happen, like configuration and initialization, before other work can begin because of the dependency on the configuration and initialization steps to have completed. If this doesn't sound like a good solution, please let me know :)
SOME BASIC WORK
I've written some code for a basic Queue, which can be found here, but I'm looking to expand its functionality so that I can store various types of "Objects", such as individual JavaScript/jQuery instructions and function calls, essentially pieces of code that I want to execute.
UPDATE
With the current Queue that I've implemented, it looks like I can store functions and execute them later, for example:
// a JS file...
$.fn.LoadJavaScript = function() {
$.getScript("js/Symbols/Symbol.js");
$.getScript("js/Structures/Structure.js");
};
// another JS file...
function init() { // symbols and structures };
// index.html
var theQueue = new Queue();
theQueue.enqueue($().LoadJavaScript);
theQueue.enqueue(init);
var LJS = theQueue.dequeue();
var INIT = theQueue.dequeue();
LJS();
INIT();
I also think I've figured out how to store individual instructions, such as $('#equation').html(""); or perhaps even if-else statements or loops, by wrapping them as such:
theQueue.enqueue(function() { $('#equation').html(""); // other instructions, etc... });
But this approach would require me to wait until the Queue is done with its work before I can continue doing my work. This seems like an incorrect design. Is there a more clever approach to this? Also, how can I know that a certain function has completed executing so that the Queue can know to move on? Is there some kind of return value that I can wait for or a callback function that I can specify to each task in the Queue?
WRAP-UP
Since I'm doing everything client-side and I can't have the Queue do its own thing independently (according to an answer below), is there a more clever solution than me just waiting for the Queue to finish its work?
Since this is more of a design question than a specific code question, I'm looking for suggestions on an approach to solving my problem, advice on how I should design this system, but I definitely welcome, and would love to see, code to back up the suggestions :) I also welcome any criticism regarding the Queue.js file I've linked to above and/or my description of my problem and the approach I'm planning to take to resolve it.
Thanks, Hristo
I would suggest using http://headjs.com/ It allows you to load js files in parallel, but execute them sequentially, essentially the same thing you want to do. It's pretty small, and you could always use it for inspiration.
I would also mention that handlers that rely on execution order are not good design. I am always able to place all my bootstrap code in the ready event handler. There are cases where you'd need to use the load handler if you need access to images, but it hasn't been very often for me.
Here is something that might work, is this what you're after?
var q = (function(){
var queue = [];
var enqueue = function(fnc){
if(typeof fnc === "function"){
queue.push(fnc);
}
};
var executeAll = function(){
var someVariable = "Inside the Queue";
while(queue.length>0){
queue.shift()();
}
};
return {
enqueue:enqueue,
executeAll:executeAll
};
}());
var someVariable = "Outside!"
q.enqueue(function(){alert("hi");});
q.enqueue(function(){alert(someVariable);});
q.enqueue(function(){alert("bye");});
alert("test");
q.executeAll();
the alert("test"); runs before anything you've put in the queue.
how do I store pieces of code in the Queue and have it execute later
Your current implementation already works for that. There are no declared types in JavaScript, so your queue can hold anything, including function objects:
queue.enqueue(myfunc);
var f = queue.dequeue();
f();
how can I have the Queue do its own thing independently
JavaScript is essentially single-threaded, meaning only one thing can execute at any instant of time. So the queue can't really operate "independently" of the rest of your code, if that is what you mean.
You basically have two choices:
Run all the queued functions, one after the other, in a single go -- this doesn't even require a queue since it is the same as simply putting the function calls directly in your code.
Use timed events: run one function at a time and once it completes, set a timeout to execute the next queued function after a certain interval. An example of this follows.
function run() {
var func = this.dequeue();
func();
var self = this;
setTimeout(function() { self.run(); }, 1000);
}
If func is an asynchronous request, you'll have to move setTimeout into the callback function.
**The main functions**
**From there we can define the main elements required:**
var q=[];//our queue container
var paused=false; // a boolean flag
function queue() {}
function dequeue() {}
function next() {}
function flush() {}
function clear() {}
**you may also want to 'pause' the queue. We will therefore use a boolean flag too.
Now let's see the implementation, this is going to be very straightforward:**
var q = [];
var paused = false;
function queue() {
for(var i=0;i< arguments.length;i++)
q.push(arguments[i]);
}
function dequeue() {
if(!empty()) q.pop();
}
function next() {
if(empty()) return; //check that we have something in the queue
paused=false; //if we call the next function, set to false the paused
q.shift()(); // the same as var func = q.shift(); func();
}
function flush () {
paused=false;
while(!empty()) next(); //call all stored elements
}
function empty() { //helper function
if(q.length==0) return true;
return false;
}
function clear() {
q=[];
}
**And here we have our basic queue system!
let's see how we can use it:**
queue(function() { alert(1)},function(){ alert(2)},function(){alert(3)});
next(); // alert 1
dequeue(); // the last function, alerting 3 is removed
flush(); // call everything, here alert 2
clear(); // the queue is already empty in that case but anyway...
I've got a sequence of Javascript function calls in a function I have defined to be executed when a web doc is ready. I expected them to be executed in sequence, as one ends the next begins, but the behaviour I see doesn't match up with that.
Additionally there is manipulation of the graphical components going on in between the calls (for example, I add in a checkpoint time to draw on a div on the page inbetween each of the mentioned calls) but those redraws aren't happening in sequence... they all happen at once.
I'm a bit of a n00b with the whole javascript-in-the-browser thing, is there an obvious mistake I'm making, or a good resource to go find out how to do this stuff?
Update - sample
// called onReady()
function init() {
doFirstThing();
updateDisplayForFirstThing();
doSecondThingWithAjaxCall();
updateDisplayForSecondThing();
...
reportAllLoaded();
}
IE won't update the display until the current script is finished running. If you want to redraw in the middle of a sequence of events, you'll have to break your script up using timeouts.
If you post some code we can help refactor it.
edit: here's a general pattern to follow.
function init() {
doFirstThing();
updateDisplayForFirstThing();
}
function updateDisplayForFirstThing() {
// existing code
...
// prepare next sequence
var nextFn = function() {
// does this method run async? if so you'll have to
// call updateDisplayForSecondThing as a callback method for the
// ajax call rather than calling it inline here.
doSecondThingWithAjaxCall();
updateDisplayForSecondThing();
}
setTimeout(nextFn, 0);
}
function updateDisplayForSecondThing() {
// existing code
...
// prepare next sequence
var nextFn = function() {
// continue the pattern
// or if you're done call the last method
reportAllLoaded();
}
setTimeout(nextFn, 0);
}
This can be fixed for many cases by using callbacks, especially with AJAX calls -- for example:
function doFirstThing(fn){
// doing stuff
if(typeof fn == 'function') fn();
}
function updateDisplayForFirstThing(){
// doing other stuff
}
function init(){
doFirstThing(updateDisplayForFirstThing);
}
Another option is to use return values:
function doFirstThing(fn){
// doing stuff
if(x) return true;
else return false;
}
function updateDisplayForFirstThing(){
// doing other stuff
return true;
}
function init(){
if(doFirstThing()){ updateDisplayForFirstThing(); }
}
setting timeouts to step through your code is not really a good way to fix this problem because you'd have to set your timeouts for the maximum length of time each piece of code could possibly take to execute.
However, you may still sometimes need to use a setTimeout to ensure the DOM has properly updated after certain actions.
If you end up deciding that you would like some JavaScript threading, check out the still being drafted Web Workers API. Browser support is hit and miss though the API is implemented in most modern web browsers.
Question: exactly how did you go about determining when the "doc is ready"? The DOMContentLoaded event isn't supported in IE I'm fairly certain... if you're in need of waiting for your document to load in its entirety you could use something like this:
var onReady = function(callback) {
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", callback, false);
return true;
} else if (document.attachEvent) {
var DOMContentLoaded = function() {
if (document.readyState === "complete") {
document.detachEvent("onreadystatechange", DOMContentLoaded);
onReady();
}
};
return true;
}
};
Then of course you'll need to develop a setTimeout testing for some flags state indicating the page is loaded upon completion before continuing the execution of the rest of your code... that or any number of other methods...
Or you could just include the script at the bottom of your body...
I'm just rambling though until you have some code to show us?