Jquery plugin development, attach a browser event and methods - javascript

Ok so i'm learning to write plugins and the jQuery site for plugin authoring is damn confusing. I'm really trying to figure out how to add methods to my plugin that will fire based on a browser event. So basically I have a plugin that builds out a swipeable slide show for a mobile device. When the device is rotated I need it to rebuild the slideshow for the new dimensions. Right now I have it so it builds it the first time but I can't (a) figure out to add methods for different functions and (b) bind events to those specific methods. here's my code.
(function( $ ){
$.fn.jCarousel = function( options ) {
var empty = {}
var defaults = {
height: $(window).height(),
width: $(window).width(),
count: 1,
amount: $('.swiper', this).length,
thisone:this,
};
var options = $.extend(empty, defaults, options);
return this.each(function() {
options.thisone = this;
amount = $('.swiper', this).length;
holdWidth = options.width * options.amount;
$('.swiper', this).height(options.height);
$(this).height(options.height);
$('.swiper', this).width(options.width);
$(this).width(holdWidth);
$('.swipe_slide',this).width(holdWidth);
//==================================================================================================================
//==================================================================================================================
$('.swiper', this).each(function(i) {
var nwidth = options.width*i;
$(this).css('left',nwidth);
});
//==================================================================================================================
//==================================================================================================================
//==================================================================================================================
//==================================================================================================================
$('.swipe_slide', this).swipe(function(evt, data) {
if(data.direction=='left'){
//alert('count: '+options.count+" amount: "+options.amount);
if(options.count != options.amount){
moveWidth = options.count * -options.width;
$('.swipe_slide',options.thisone).css( "left" ,moveWidth );
options.count++
}else{
return
}
}else{
if(options.count!=1){
moveWidth = moveWidth+ options.width;
$('.swipe_slide',options.thisone).css( "left" ,moveWidth );
options.count--
}
else{
return
}
}
});
//==================================================================================================================
//==================================================================================================================
});
};
})( jQuery );

$(window).bind('orientationchange', $.fn.jCarousel);
You'll have to refactor your plugin a bit so that the function stores the options that are passed into it, otherwise running the function again on orientation change will reset the options to the defaults.

Related

Creating windows in Chrome App ignores bounds when launching?

I use the background.js script to create new windows, but when I set the bounds when using the chrome.app.window.create method and pass it parameters for the CreateWindowOptions object the are used the first time, but ignored whenever I relaunch the app.
On OSX, it seems that calling the methods setPosition and setSize after the create callback on the window's outerBounds object the app will then respond to the bounds I try to set. However, this doesn't appear to work on Windows 10 at all.
It seems somehow the windows I create persist when the app is closed. I'm not sure how to clear them, especially if the user closes the app uses the menu or OS to quit. Is there some way to make sure to destroy or clear these windows, or force the Chrome App to set the bounds I set without reverting to some previous state?
The windows I create are running PixiJS if that makes any difference. I do call a destroy method on the windows when the close event is triggered, but that has no affect.
Here's my background script:
(function( global ) {
"use strict";
/* ----- VARS ----- */
var displayInfo;
var isMultiDisplay;
var controlDisplayIndex;
var controlBounds;
var controlLoaded;
var controlWindow;
var gameDisplayIndex;
var gameBounds;
var gameLoaded;
var gameWindow;
/* ----- CONSTANTS ----- */
var CONTROL_WIDTH = 1920;
var CONTROL_HEIGHT = 1080;
var GAME_WIDTH = 2160;
var GAME_HEIGHT = 3840;
var FRAME_OPTIONS = {
type: "none"
};
/* ----- FUNCTIONS ----- */
function init() {
console.log( "background.js: init" );
console.info( "background.js: ", chrome.app.window.getAll() );
isMultiDisplay = false;
controlLoaded = false;
gameLoaded = false;
loadDisplayInfo();
};
function loadDisplayInfo() {
console.log( "background.js: loadDisplayInfo" );
chrome.system.display.getInfo( onDisplayInfo );
};
function createWindows() {
console.log( "background.js: createWindows" );
isMultiDisplay = ( displayInfo.length > 0 );
if ( isMultiDisplay ) {
var i = 0;
var length = 2;
for ( i; i < length; i++ ) {
if ( displayInfo[i].isPrimary )
controlDisplayIndex = i;
else
gameDisplayIndex = i;
}
gameBounds = {
height: displayInfo[ gameDisplayIndex ].workArea.height,
left: displayInfo[ gameDisplayIndex ].workArea.left,
top: displayInfo[ gameDisplayIndex ].workArea.top,
width: displayInfo[ gameDisplayIndex ].workArea.width
};
controlBounds = {
height: displayInfo[ controlDisplayIndex ].workArea.height,
left: displayInfo[ controlDisplayIndex ].workArea.left,
top: displayInfo[ controlDisplayIndex ].workArea.top,
width: displayInfo[ controlDisplayIndex ].workArea.width
};
} else {
// This assumes single monitor is in landscape.
gameBounds = {
top: displayInfo[0].workArea.top,
left: displayInfo[0].workArea.left,
height: displayInfo[0].workArea.height,
width: Math.floor( displayInfo[0].workArea.height * GAME_WIDTH / GAME_HEIGHT )
};
controlBounds = {
top: displayInfo[0].workArea.top,
left: displayInfo[0].workArea.left + gameBounds.width,
width: gameBounds.width,
height: Math.floor( gameBounds.width * CONTROL_HEIGHT / CONTROL_WIDTH )
};
}
console.info( "Game Bounds:", gameDisplayIndex, gameBounds.left, gameBounds.top, gameBounds.width, gameBounds.height );
console.info( "Control Bounds:", controlDisplayIndex, controlBounds.left, controlBounds.top, controlBounds.width, controlBounds.height );
var state = ( isMultiDisplay ) ? "fullscreen" : "normal";
// Game
chrome.app.window.create( "window-game.html", {
id: "gameWindow",
bounds: gameBounds,
frame: FRAME_OPTIONS,
resizable: true,
state: state
}, onGameWindowCreated );
// Control
chrome.app.window.create( "window-control.html", {
id: "controlWindow",
bounds: controlBounds,
frame: FRAME_OPTIONS,
resizable: true,
state: state
}, onControlWindowCreated );
};
/* ----- EVENT LISTENERS ----- */
function onLaunched() {
console.log( "background.js: onLaunched" );
init();
};
function onDisplayInfo( info ) {
console.log( "background.js: onDisplayInfo" );
displayInfo = info;
createWindows();
};
function onControlWindowCreated( obj ) {
console.log( "background.js: onControlWindowCreated", obj.id );
controlLoaded = true;
controlWindow = obj;
controlWindow.outerBounds.setPosition( controlBounds.left, controlBounds.top );
controlWindow.outerBounds.setSize( controlBounds.width, controlBounds.height );
if ( isMultiDisplay ) controlWindow.fullscreen();
//console.info( controlWindow.innerBounds.width, controlWindow.innerBounds.height );
//console.info( controlWindow.outerBounds.width, controlWindow.outerBounds.height );
controlWindow.onClosed.addListener( onControlWindowClosed );
};
function onControlWindowClosed() {
console.log( "background.js: onControlWindowClosed" );
controlWindow.onClosed.removeListener( onControlWindowClosed );
controlWindow.destroy();
controlWindow = undefined;
};
function onGameWindowCreated( obj ) {
console.log( "background.js: onGameWindowCreated", obj.id );
gameLoaded = true;
gameWindow = obj;
gameWindow.outerBounds.setPosition( gameBounds.left, gameBounds.top );
gameWindow.outerBounds.setSize( gameBounds.width, gameBounds.height );
if ( isMultiDisplay ) gameWindow.fullscreen();
//console.info( gameWindow.innerBounds.width, gameWindow.innerBounds.height );
//console.info( gameWindow.outerBounds.width, gameWindow.outerBounds.height );
gameWindow.onClosed.addListener( onGameWindowClosed );
};
function onGameWindowClosed() {
console.log( "background.js: onGameWindowClosed" );
gameWindow.onClosed.removeListener( onGameWindowClosed );
gameWindow.destroy();
gameWindow = undefined;
};
/* ----- CALL ----- */
chrome.app.runtime.onLaunched.addListener( onLaunched );
}( this ));
Disclaimer: I know that Chrome Apps are being discontinued, but due to project timings I've got to finish this app off and then look to port to Electron or alternative.
When you create a window and set the id property in the CreateWindowOptions object, the last known bounds for that window will be saved by the Chrome Apps platform and used the next a window is created with that same id.
Here's the description from the docs on the id property:
Id to identify the window. This will be used to remember the size and
position of the window and restore that geometry when a window with
the same id is later opened. If a window with a given id is created
while another window with the same id already exists, the currently
opened window will be focused instead of creating a new window.
And under the description of the outerBounds property:
Used to specify the initial position, initial size and constraints of
the window (including window decorations such as the title bar and
frame). If an id is also specified and a window with a matching id has
been shown before, the remembered bounds will be used instead.
So, the solution is to omit the id property. If you, like myself, need to do further things with that window in your script, then use the create window callback to assign a variable to that's function return parameter.
For example:
var myWindow;
chrome.app.window.create( "window.html", options, function( obj ) {
myWindow = obj;
// Do Stuff with myWindow
} );
I guess even attempting to set the size and position of the window after the callback isn't full-proof as is evident when I tried to do some under Windows 10.
The only remaining unknown to me is where is Google Chrome Apps storing all this window data when you give windows an id? And is there a way to clear that (other than uninstalling the app)?

HighCharts: How to use reflow to allow auto-resize after changing size

In our Angular app we're using highcarts-ng for our HighCharts implementation.
Here is the Chart Maximize and Minimize function, which works:
function expandChartPanel() {
vm.chartMaxed = !vm.chartMaxed;
viewHeader = ScopeFactory.getScope('viewHeader');
highChart = ScopeFactory.getScope('highChart');
var chart = highChart.chartObject;
var highChartContainer = document.getElementById("highchart-container");
var highChartContainerWidth = document.getElementById('highchart-container').clientWidth;
var highChartContainerHeight = document.getElementById('highchart-container').clientHeight;
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
if (vm.chartMaxed) {
vs.savedWidth = highChartContainerWidth;
vs.savedHeight = highChartContainerHeight;
console.log('savedWidth = ', vs.savedWidth);
console.log('savedHeight = ', vs.savedHeight);
root.chartExpanded = true;
viewHeader.vh.chartExpanded = true;
highChart.highChartMax = true;
highChartContainerHeight = document.getElementById('highchart-container').clientHeight;
windowWidth = window.innerWidth;
windowHeight = window.innerHeight;
highChart.chartConfig.size.width = windowWidth;
highChart.chartConfig.size.height = windowHeight - 220;
chart.setSize(windowWidth, windowHeight - 220);
}
else {
root.chartExpanded = false;
viewHeader.vh.chartExpanded = false;
highChart.highChartMax = false;
highChart.chartConfig.size.width = vs.savedWidth;
highChart.chartConfig.size.height = vs.savedHeight;
chart.setSize(vs.savedWidth, vs.savedHeight);
}
highChart.restoreChartSize();
}
Here is the reflow function:
function restoreChartSize() {
console.log('restoreChartSize');
if (!vs.chartObject.reflowNow) {
vs.chartObject.reflowNow = vs.chartObject.reflowNow = function() {
this.containerHeight = this.options.chart.height || window.window.HighchartsAdapter.adapterRun(this.renderTo, 'height');
this.containerWidth = this.options.chart.width || window.window.HighchartsAdapter.adapterRun(this.renderTo, 'width');
this.setSize(this.containerWidth, this.containerHeight, true);
this.hasUserSize = null;
}
}
vs.chartObject.reflowNow();
}
This reflow function above, works perfectly in this jsFiddle, but not in our app.
The full Gist file of our HighChartsDirective file.
After clicking Maximize, the chart will expand to the full size of the browser window, but then after dragging to resize the browser window, I call the restoreChartSize function, which activates the reflow.
However the size of the chart does not go to auto-size 100% 100%, it goes back to the previous size of the chart :(
Before Maximize:
After the Maximize function:
Now after resizing the browser window:
window.onresize = function(event) {
console.log('window resizing...');
highChart = ScopeFactory.getScope('highChart');
highChart.restoreChartSize();
console.log('highChart.chartConfig = ', highChart.chartConfig);
};
^ back to the smaller static sizes, not auto-size 100%
You can do this by adding a new method to chart that will manually trigger the reflow like so:
chart.reflowNow = function(){
this.containerHeight = this.options.chart.height || window.window.HighchartsAdapter.adapterRun(this.renderTo, 'height');
this.containerWidth = this.options.chart.width || window.window.HighchartsAdapter.adapterRun(this.renderTo, 'width');
this.setSize(this.containerWidth, this.containerHeight, false);
this.hasUserSize = null;
}
Then whenever you want to get away from manual resizing using setSize() just call chart.reflow()
Here's an working example: jsFiddle
Reference taken from: github-issue
UPDATE for ng-highcharts users
For doing this when using ng-highcharts library, you can simply pull out the chart object in the controller that has highcharts-ng dependency and add the reflowNow function, like so:
var chart = this.chartConfig.getHighcharts();
chart.reflowreflowNow = function (){ ... }
This is also the recommended way to pull out chart to do custom jobs by author of ng-highcharts as noted here and this fiddle.
I ended up finding an alternative solution to be the only thing I could get working, and it actually was pretty simple and straight forward to do. In case anyone else is looking for a fix for this, here's links to the resources that were useful and solved the issue for me.
You can simply add this to your chart config object, at the same level as the config.series or config.options. The comment references info but the actual solution that worked for me uses $timeout with 0 seconds, here
*For using highcharts-ng obviously
http://plnkr.co/edit/14x7gfQAlHw12XZVhWm0?p=preview
$scope.chartConfigObject = {
// function to trigger reflow in bootstrap containers
// see: http://jsfiddle.net/pgbc988d/ and https://github.com/pablojim/highcharts-ng/issues/211
func: function(chart) {
$timeout(function() {
chart.reflow();
//The below is an event that will trigger all instances of charts to reflow
//$scope.$broadcast('highchartsng.reflow');
}, 0);
}
};

VideoJS player overlay buttons in touch devices

I am using VideoJs player 4.2
When i open a videojs player in safari browser in ios device , it works on native controls by default . When i pause the player, i display overlay buttons (links to go for other page) on video . If i click buttons it does not fire in ios . It works well in desktop browsers .
If i set nativeControlsForTouch to false it displays custom controls . Here overlay buttons and custom controls works but it will be sluggish and i am not able scroll over video and it will be not smooth as compared with native controls .
Please suggest me any idea
If i use native controls , then overlay buttons has to work
If i use custom controls , then video player will be as smooth as compared with native controls
Here is my code
videojs("ex_video",{"controls":true, "preload":"metadata","width":"auto", "height":"auto",
"poster":"/images/test.png"},function(){
var current_player=this;
current_player.imageOverlay({
opacity: 0.5,
height: '100%',
width: '50%'
});
current_player.on("pause", function(){
current_player.bigPlayButton.show();
});
current_player.on("play", function(){
current_player.bigPlayButton.hide();
});
});
overlay button code
(function(vjs) {
var
extend = function(obj) {
var arg, i, k;
for (i = 1; i < arguments.length; i++) {
arg = arguments[i];
for (k in arg) {
if (arg.hasOwnProperty(k)) {
obj[k] = arg[k];
}
}
}
return obj;
},
defaults = {
image_url: '',
click_url: '',
start_time: null,
end_time: null,
opacity: 0.7,
height: '15%',
width: '100%'
},
imageOverlay = function(options) {
var player = this,
settings = extend({}, defaults, options || {}),
showingImage = false;
if (settings.start_time === null)
settings.start_time = 0;
if (settings.end_time === null)
settings.end_time = player.duration() + 1;
overlay = {
showImage: function() {
if (showingImage) {
return;
}
showingImage = true;
var holderDiv = document.createElement('div');
holderDiv.id = 'vjs-image-overlay-holder';
holderDiv.style.height = settings.height;
holderDiv.style.width = settings.width;
holderDiv.style.opacity=settings.opacity;
var overlayLbl1= document.createElement('label');
overlayLbl1.setAttribute('class','heading-overlay-video');
overlayLbl1.innerHTML= "Like this class? Why not:";
var topVideoP=overlay.createEl(overlayLbl1);
topVideoP.className=topVideoP.className+' top-video-control-overlay';
holderDiv.appendChild(topVideoP);
//Save to queue
var overlayBtn1=document.createElement('button');
overlayBtn1.value = 'Save to queue';
overlayBtn1.innerHTML='Save to queue';
overlayBtn1.setAttribute('class','white medium');
holderDiv.appendChild(overlay.createEl(overlayBtn1));
//Add to series button
var overlayBtn2=document.createElement('button');
overlayBtn2.value = 'Add to series';
overlayBtn2.innerHTML='Add to series';
overlayBtn2.setAttribute('class','white medium');
holderDiv.appendChild(overlay.createEl(overlayBtn2));
var overlayLbl2= document.createElement('label');
overlayLbl2.innerHTML= "music credits";
overlayLbl2.setAttribute('class','msg-blue');
var btmVideoP=overlay.createEl(overlayLbl2);
btmVideoP.className=btmVideoP.className+' bottom-video-control';
holderDiv.appendChild(btmVideoP);
player.el().appendChild(holderDiv);
},
hideImage: function() {
if (!showingImage) {
return;
}
showingImage = false;
player.el().removeChild(document.getElementById('vjs-image-overlay-holder'));
},
createEl:function(ele_obj){
var overlayP=document.createElement('p');
overlayP.setAttribute('class','overlay-video-content-in');
overlayP.appendChild(ele_obj);
return overlayP ;
}
};
var setTimeoutControl;
player.on('pause', function(){
setTimeoutControl= setTimeout(function(){overlay.showImage()},5000);
});
player.on('play', function(){
clearTimeout(setTimeoutControl);
overlay.hideImage();
});
};
vjs.plugin('imageOverlay', imageOverlay);
}(window.videojs));
By default HTML5 video in browser will open in native player in iPod,IPhone.
You have to wrap the HTML content in webview and run it like a native application to view video in iPod,Iphone.
See this HTML5 inline video on iPhone vs iPad/Browser
So these two changes has to be done HTML
<video id="xxx" width="xx" height="xx" webkit-playsinline>
Obj-C
webview.allowsInlineMediaPlayback = YES;
In all other devices it will work fine with out these
It is better to use HammerJS(https://github.com/hammerjs/hammer.js). For EmberJS we have an add-on https://github.com/runspired/ember-gestures

Autoscaling an svg on window resize

I have this code:
function myClient() {
if (!(this instanceof arguments.callee)) {
return new arguments.callee(arguments);
}
var self = this;
this.init = function() {
self.viewResized();
self.drawSvg();
};
this.viewResized = function () {
var width = $('body').width(),
windowHeight = $(window).height(),
svgCanvasHeight = width * (369.0 / 567.0);
$('#svg').css({
'margin-top': 10
});
}
this.drawSvg = function() {
// ...
}
var myClient;
jQuery(function() {
myClient = new myClient();
$(window).resize(function() {
console.log("window resized");
myClient.viewResized();
});
});
How do I get the svgCanvasHeight in drawSvg dynamically so that when the window is resized, so does the svg's viewBox and svg?
Answered here: Get the real size of a SVG/G element
With regards to viewBox:
I have had a lot of problems with SVG and jQuery.
While html attributes are case-insensitive the svg ones (like viewBox) aren't. I'd try using the element.setAttribute(name, value) native JS function. This worked for me, and make sure you're using viewBox with the capital B.

Asset.images slow? How do I execute functions so they don't freeze the browser?

I recently downloaded a nice mootools plugin to provide a rating system for search results on my site: MooStarRating.
It works quite well, but it is very slow to initialize. Here are the execution times I have logged (for pulling back 50 search results).
======== starrating ========
init: 0.06ms 50
img: 5.40ms 50
str: 0.54ms 50
each: 3.04ms 50
inject: 0.86ms 50
end: 1.52ms 50
subtotal: 11.42ms 50
-----------------
total: 571.00ms
Here is the initialize function these logs refer to (just for reference):
initialize: function (options) {
lstart("starrating");
// Setup options
this.setOptions(options);
// Fix image folder
if ((this.options.imageFolder.length != 0) && (this.options.imageFolder.substr(-1) != "/"))
this.options.imageFolder += "/";
// Hover image as full if none specified
if (this.options.imageHover == null) this.options.imageHover = this.options.imageFull;
lrec("init");
// Preload images
try {
Asset.images([
this.options.imageEmpty,
this.options.imageFull,
this.options.imageHover
]);
} catch (e) { };
lrec("img");
// Build radio selector
var formQuery = this.options.form;
this.options.form = $(formQuery);
if (!this.options.form) this.options.form = $$('form[name=' + formQuery + "]")[0];
if (this.options.form) {
var uniqueId = 'star_' + String.uniqueID();
this.options.form.addClass(uniqueId);
this.options.selector += 'form.' + uniqueId + ' ';
}
this.options.selector += 'input[type=radio][name=' + this.options.radios + "]";
// Loop elements
var i = 0;
var me = this;
var lastElement = null;
var count = $$(this.options.selector).length;
var width = this.options.width.toInt();
var widthOdd = width;
var height = this.options.height.toInt();
if (this.options.half) {
width = (width / 2).toInt();
widthOdd = widthOdd - width;
}
lrec("str");
$$(this.options.selector).each(function (item) {
// Add item to radio list
this.radios[i] = item;
if (item.get('checked')) this.currentIndex = i;
// If disabled, whole star rating control is disabled
if (item.get('disabled')) this.options.disabled = true;
// Hide and replace
item.setStyle('display', 'none');
this.stars[i] = new Element('a').addClass(this.options.linksClass);
this.stars[i].store('ratingIndex', i);
this.stars[i].setStyles({
'background-image': 'url("' + this.options.imageEmpty + '")',
'background-repeat': 'no-repeat',
'display': 'inline-block',
'width': ((this.options.half && (i % 2)) ? widthOdd : width),
'height': height
});
if (this.options.half)
this.stars[i].setStyle('background-position', ((i % 2) ? '-' + width + 'px 0' : '0 0'));
this.stars[i].addEvents({
'mouseenter': function () { me.starEnter(this.retrieve('ratingIndex')); },
'mouseleave': function () { me.starLeave(); }
});
// Tip
if (this.options.tip) {
var title = this.options.tip;
title = title.replace('[VALUE]', item.get('value'));
title = title.replace('[COUNT]', count);
if (this.options.tipTarget) this.stars[i].store('ratingTip', title);
else this.stars[i].setProperty('title', title);
}
// Click event
var that = this;
this.stars[i].addEvent('click', function () {
if (!that.options.disabled) {
me.setCurrentIndex(this.retrieve('ratingIndex'));
me.fireEvent('click', me.getValue());
}
});
// Go on
lastElement = item;
i++;
}, this);
lrec("each");
// Inject items
$$(this.stars).each(function (star, index) {
star.inject(lastElement, 'after');
lastElement = star;
}, this);
lrec("inject");
// Enable / disable
if (this.options.disabled) this.disable(); else this.enable();
// Fill stars
this.fillStars();
lrec("end");
return this;
},
So, the slowest part of the function is this:
// Preload images
try {
Asset.images([
this.options.imageEmpty,
this.options.imageFull,
this.options.imageHover
]);
} catch (e) { };
Which is strange. What does Asset.images do? Does the script block until these images have been loaded by the browser? Is there a way to preload images that runs faster?
How can I make the scripts on my page execute faster? It is a big problem for them to take 800ms to execute, but 200ms is still quite bad. At the moment, my search results all pop into existence at once. Is it possible to make it so that individual search results are created separately, so that they don't block the browser while being created? Similarly, is it possible to do this for the individual components of the search results, such as the MooStarRating plugin?
no. Asset.images is non-blocking as each of these gets loaded separately and a singular event is being dispatched when all done.
the speed for loading will be dependent on the browser but it be will multi-threaded to whatever capability it has for parallel downloading from the same host.
https://github.com/mootools/mootools-more/blob/master/Source/Utilities/Assets.js#L115-129
immediately, it returns an Element collection with the PROMISE of elements that may still be downloading. that's fine - you can use it to inject els, attach events, classes etc - you just cannot read image properties yet like width, height.
each individual image has it's own onload that fires an onProgress and when all done, an onComplete for the lot - i would advise you to enable that, remove the try/catch block and see which image is creating a delay. you certainly don't need to wait for anything from Asset.images to come back.
you also seem to be using it as a 'prime the cache' method, more than anything - as you are NOT really saving a reference into the class instance. your 'each' iteration can probably be optimised so it uses half the time if objects and functions are cached and so are references. probably more if you can use event delegation.
To answer your questions about not freezing the browser due to the single-threaded nature of javascript, you defer the code via setTimeout (or Function.delay in mootools) with a timer set to 0 or a 10ms due to browser interpretations. You also write the function to to exec a callback when done, in which you can pass the function result, if any (think ajax!).

Categories

Resources