So I'm fairly new to Html/Javascript and Im trying to put together a player that takes a playlistcode from youtube and produces a Videoplayer + thumbnails.
I run it via Netbeans 8.0.2, however sometimes I get the desired results (thumbnails do load) but most of the times I get nothing.
Here is my code:
Edit: Tried it in IE, Chrome, Firefox (all up to date). Sometimes I change basicly nothing (moving a variable down 1 line and it suddenly works.. once)
<!DOCTYPE html>
<html>
<head>
<title>F this S</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<a name="ytplayer"></a>
<div id="ytplayer_div2"></div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/swfobject/2/swfobject.js"></script>
<script type="text/javascript">
var string;
var ytplayer_playlist = [ ];
var ytplayer_playitem = 0;
swfobject.addLoadEvent( ytplayer_render_player );
swfobject.addLoadEvent( ytplayer_render_playlist );
function ytplayer_render_player( )
{
swfobject.embedSWF
(
'http://www.youtube.com/v/' + ytplayer_playlist[ ytplayer_playitem ] + '&enablejsapi=1&rel=0&fs=1&version=3',
'ytplayer_div1',
'440',
'330',
'10',
null,
null,
{
allowScriptAccess: 'always',
allowFullScreen: 'true'
},
{
id: 'ytplayer_object'
}
);
}
function ytplayer_render_playlist( )
{
for ( var i = 0; i < ytplayer_playlist.length; i++ )
{
var img = document.createElement( "img" );
img.src = "http://img.youtube.com/vi/" + ytplayer_playlist[ i ] + "/default.jpg";
var a = document.createElement( "a" );
a.href = "#ytplayer";
a.onclick = (
function( j )
{
return function( )
{
ytplayer_playitem = j;
ytplayer_playlazy( 1000 );
};
}
)( i );
a.appendChild( img );
document.getElementById( "ytplayer_div2" ).appendChild( a );
}
}
function ytplayer_playlazy( delay )
{
if ( typeof ytplayer_playlazy.timeoutid !== 'undefined' )
{
window.clearTimeout( ytplayer_playlazy.timeoutid );
}
ytplayer_playlazy.timeoutid = window.setTimeout( ytplayer_play, delay );
}
function ytplayer_play( )
{
var o = document.getElementById( 'ytplayer_object' );
if ( o )
{
o.loadVideoById( ytplayer_playlist[ ytplayer_playitem ] );
}
}
function onYouTubePlayerReady( playerid )
{
var o = document.getElementById( 'ytplayer_object' );
if ( o )
{
o.addEventListener( "onStateChange", "ytplayerOnStateChange" );
o.addEventListener( "onError", "ytplayerOnError" );
}
}
function ytplayerOnStateChange( state )
{
if ( state === 0 )
{
ytplayer_playitem += 1;
ytplayer_playitem %= ytplayer_playlist.length;
ytplayer_playlazy( 5000 );
}
}
function ytplayerOnError( error )
{
if ( error )
{
ytplayer_playitem += 1;
ytplayer_playitem %= ytplayer_playlist.length;
ytplayer_playlazy( 5000 );
}
}
</script>
<button onclick="urlAusgabeFunktion()"> Go </button>
<input type="text" name="txtJob" id="PlaylistUrl" value="PLAYLIST ID HERE">
<script>
function urlAusgabeFunktion()
{
gapi.client.setApiKey('API KEY HERE ');
gapi.client.load('youtube', 'v3', function() {
var request = gapi.client.youtube.playlistItems.list({
part: 'snippet',
playlistId: document.getElementById("PlaylistUrl").value,
maxResults: 50
});
request.execute(function(response) {
for (var i = 0; i < response.items.length; i++)
{
string = response.items[i].snippet.resourceId.videoId;
ytplayer_playlist.push(string);
}
swfobject.addLoadEvent( ytplayer_render_player );
swfobject.addLoadEvent( ytplayer_render_playlist );
});
});
}
</script>
<script src="https://apis.google.com/js/client.js?onload=onGoogleLoad"></script>
Related
Good day,
I have cameras that are saving videos by clips on a cloud service, with a limit on N clips. In order to save more clips, I made a scrapping script that gets all my video links from the website where I can preview them. (to be executed on page load with a JS extension)
Note that these are M3U8 files.
Here is my script:
var cameraScrapper = {
playerReady: false,
videos: {},
cameraCurrent: 0,
cameras: [
'cid1', // #1
'cid2', // #3
'cid3', // #2
],
gui: function() {
return {
calendarPreviousMonth: document.getElementById( 'event_calendar_section' ).getElementsByClassName( 'navigation' )[0].getElementsByClassName( 'prev' )[0],
calendarNextMonth: document.getElementById( 'event_calendar_section' ).getElementsByClassName( 'navigation' )[0].getElementsByClassName( 'next' )[0],
calendarSelectedDate: document.getElementById( 'event_selected_date' ),
calendarMonthDays: document.getElementById( 'event_calendar_section' ).getElementsByClassName( 'month' )[0].children,
cameras: document.getElementById( 'camera_list_dropdown' ).children,
dayClips: document.getElementById( 'event_clips_wrap' ).children,
noRecords: document.getElementsByClassName( 'no_recording' )[0]
};
},
init: function() {
document.getElementById( 'hls_player' ).addEventListener( 'canplay', function() { this.playerReady = true; }.bind( this ) );
for( let i = 0; i < this.cameras.length; i++ ) {
if( window.location.href.includes( '=' + this.cameras[i] + '&' ) ) {
this.cameraCurrent = i;
break;
}
}
while( !this.gui().calendarPreviousMonth.children[0].classList.contains( 'disabled' ) ) {
this.gui().calendarPreviousMonth.click();
}
this.scrap();
},
getVideo: function( pCamera, pTimeStamp ) {
let performance = window.performance || window.mozPerformance || window.msPerformance || window.webkitPerformance || {};
let requests = ( performance.getEntries() || {} );
for( let i = 0; i < requests.length; i++ ) {
if( requests[i].name.includes( '.ts?' ) ) {
if( this.videos[encodeURIComponent( requests[i].name )] === undefined ) {
this.videos[encodeURIComponent( requests[i].name )] = { 'camera': pCamera, 'timestamp': this.formatDate( pTimeStamp ) };
}
}
}
if( Object.entries( this.videos ).length > 25 ) {
this.save();
this.videos = {};
performance.clearMarks();
performance.clearMeasures()
performance.clearResourceTimings();
}
},
formatDate( pTimestamp ) {
let date = pTimestamp.split( ' ' )[0];
let time = pTimestamp.split( ' ' )[1];
let month = {
JAN: '01',
FEB: '02',
MAR: '03',
APR: '04',
MAY: '05',
JUN: '06',
JUL: '07',
AUG: '08',
SEP: '09',
OCT: '10',
NOV: '11',
DEC: '12'
}
return date.split( '.' )[2] + '-' + month[date.split( '.' )[1]] + '-' + date.split( '.' )[0] + ' ' + time;
},
sleep: function( pMs ) {
return new Promise( resolve => setTimeout( resolve, pMs ) );
},
scrap: async function() {
let scrapDays = true;
while( scrapDays ) {
for( let i = 0; i < this.gui().calendarMonthDays.length; i++ ) {
if( !this.gui().calendarMonthDays[i].classList.contains( 'is-disabled' ) && !this.gui().calendarMonthDays[i].classList.contains( 'is-expired' ) && !this.gui().calendarMonthDays[i].classList.contains( 'is-future' ) ) {
// console.log( 'day ' + this.gui().calendarMonthDays[i].innerText );
this.gui().calendarMonthDays[i].click();
let clipsLoaded = false;
if( this.gui().noRecords !== undefined ) {
clipsLoaded = !this.gui().noRecords.classList.contains( 'displayOff' )
}
while( !clipsLoaded && ( this.gui().dayClips.length == 0 ) ) {
await this.sleep( 99 );
if( this.gui().noRecords !== undefined ) {
clipsLoaded = !this.gui().noRecords.classList.contains( 'displayOff' )
}
}
// console.log( 'clips ' + this.gui().dayClips.length );
for( let j = 0; j < this.gui().dayClips.length; j++ ) {
this.playerReady = false;
this.gui().dayClips[j].children[0].click();
while( !this.playerReady ) {
await this.sleep( 99 );
}
this.getVideo( ( '' + this.cameraCurrent ), this.gui().calendarSelectedDate.innerText + ' ' + this.gui().dayClips[j].children[0].children[1].children[1].innerText );
}
}
}
if( !this.gui().calendarNextMonth.children[0].classList.contains( 'disabled' ) ) {
this.gui().calendarNextMonth.click();
} else {
scrapDays = false;
}
}
this.save();
this.sleep( 4999 );
let cameraNext = this.cameraCurrent + 1;
if( this.cameras.length >= cameraNext ) {
cameraNext = 0;
}
window.location.href = 'https://cloudcamerasservice.com/camera?no=' + this.cameras[cameraNext] + '&model=CAM';
},
save() {
let xhr = new XMLHttpRequest();
xhr.open( 'POST', 'http://localhost/videomonitoring/stack.php', true );
xhr.setRequestHeader( 'Content-type', 'application/x-www-form-urlencoded' );
xhr.onload = ( e ) => { /* console.log( 'saved' ); */ };
xhr.onerror = ( e ) => {};
xhr.send( 'stack=' + encodeURIComponent( JSON.stringify( this.videos ) ) );
}
};
setTimeout( function() { cameraScrapper.init() }.bind( this ), 4999 );
stack.php will handle file download from provided list, my script is mainly about getting the video links and date of recording.
But when launching my script, my browser run out of memory after 75-100 clips saved. Can you help me with this?
Not sure if it's comming from my script, indeed, I have the feeling this is due to preloading many videos on the same page.
I am using CKFinder 3 in a Web project as described on the CKFinder Website my problem is that I can't return multiple selected Images. The problem is that when I select multiple Images just the first one is returned.
Is there a way to return multiple files?
var button1 = document.getElementById( 'ckfinder-popup-1' );
var button2 = document.getElementById( 'ckfinder-popup-2' );
button1.onclick = function() {
selectFileWithCKFinder( 'ckfinder-input-1' );
};
button2.onclick = function() {
selectFileWithCKFinder( 'ckfinder-input-2' );
};
function selectFileWithCKFinder( elementId ) {
CKFinder.modal( {
chooseFiles: true,
width: 800,
height: 600,
onInit: function( finder ) {
finder.on( 'files:choose', function( evt ) {
var file = evt.data.files.first();
var output = document.getElementById( elementId );
output.value = file.getUrl();
} );
finder.on( 'file:choose:resizedImage', function( evt ) {
var output = document.getElementById( elementId );
output.value = evt.data.resizedUrl;
} );
}
} );
I have found a way how to do it.
The only bummer is that you can’t resize images.
var button1 = document.getElementById( 'ckfinder-popup-1' );
button1.onclick = function() {
selectFileWithCKFinder( 'ckfinder-input-1' );
};
function selectFileWithCKFinder( elementId ) {
CKFinder.modal( {
chooseFiles: true,
width: 800,
height: 600,
onInit: function( finder ) {
finder.on( 'files:choose', function( evt ) {
var url='';
for(i = 0; i < evt.data.files.models.length ; i++){
var file = evt.data.files.models[i];
var tempurl = file.getUrl();
url +=','+tempurl;
}
var output = document.getElementById( elementId );
output.value = url;
} );
finder.on( 'file:choose:resizedImage', function( evt ) {
var url='';
for(i = 0; i < evt.data.files.models.length ; i++){
var file = evt.data.files.models[i];
var tempurl = file.getUrl();
url +=','+tempurl;
}
var output = document.getElementById( elementId );
output.value = url;
} );
}
} );
}
</script> ```
The javascript below enables swipe left/right navigation. For some reason, I would like to call this function using URL such as <a href="javascript:function">. Certain URL will equivalent to left swipe, while another URL will equivalent to right swipe. Is this possible?
(function( window ){
var window = window,
document = window.document,
screen = window.screen,
touchSwipeListener = function( options ) {
// Private members
var track = {
startX: 0,
endX: 0
},
defaultOptions = {
moveHandler: function( direction ) {},
endHandler: function( direction ) {},
minLengthRatio: 0.3
},
getDirection = function() {
return track.endX > track.startX ? "prev" : "next";
},
isDeliberateMove = function() {
var minLength = Math.ceil( screen.width * options.minLengthRatio );
return Math.abs(track.endX - track.startX) > minLength;
},
extendOptions = function() {
for (var prop in defaultOptions) {
if ( defaultOptions.hasOwnProperty( prop ) ) {
options[ prop ] || ( options[ prop ] = defaultOptions[ prop ] );
}
}
},
handler = {
touchStart: function( event ) {
// At least one finger has touched the screen
if ( event.touches.length > 0 ) {
track.startX = event.touches[0].pageX;
}
},
touchMove: function( event ) {
if ( event.touches.length > 0 ) {
track.endX = event.touches[0].pageX;
options.moveHandler( getDirection(), isDeliberateMove() );
}
},
touchEnd: function( event ) {
var touches = event.changedTouches || event.touches;
if ( touches.length > 0 ) {
track.endX = touches[0].pageX;
isDeliberateMove() && options.endHandler( getDirection() );
}
}
};
extendOptions();
// Graceful degradation
if ( !document.addEventListener ) {
return {
on: function() {},
off: function() {}
}
}
return {
on: function() {
document.addEventListener('touchstart', handler.touchStart, false);
document.addEventListener('touchmove', handler.touchMove, false);
document.addEventListener('touchend', handler.touchEnd, false);
},
off: function() {
document.removeEventListener('touchstart', handler.touchStart);
document.removeEventListener('touchmove', handler.touchMove);
document.removeEventListener('touchend', handler.touchEnd);
}
}
}
// Expose global
window.touchSwipeListener = touchSwipeListener;
}( window ));
(function( window ){
var document = window.document,
// Element helpers
Util = {
addClass: function( el, className ) {
el.className += " " + className;
},
hasClass: function( el, className ) {
var re = new RegExp("\s?" + className, "gi");
return re.test( el.className );
},
removeClass: function( el, className ) {
var re = new RegExp("\s?" + className, "gi");
el.className = el.className.replace(re, "");
}
},
swipePageNav = (function() {
// Page sibling links like <link rel="prev" title=".." href=".." />
// See also http://diveintohtml5.info/semantics.html
var elLink = {
prev: null,
next: null
},
// Arrows, which slide in to indicate the shift direction
elArrow = {
prev: null,
next: null
},
swipeListener;
return {
init: function() {
this.retrievePageSiblings();
// Swipe navigation makes sense only if any of sibling page link available
if ( !elLink.prev && !elLink.next ) {
return;
}
this.renderArows();
this.syncUI();
},
syncUI: function() {
var that = this;
// Assign handlers for swipe "in progress" / "complete" events
swipeListener = new window.touchSwipeListener({
moveHandler: function( direction, isDeliberateMove ) {
if ( isDeliberateMove ) {
if ( elArrow[ direction ] && elLink[ direction ] ) {
Util.hasClass( elArrow[ direction ], "visible" ) ||
Util.addClass( elArrow[ direction ], "visible" );
}
} else {
Util.removeClass( elArrow.next, "visible" );
Util.removeClass( elArrow.prev, "visible" );
}
},
endHandler: function( direction ) {
that[ direction ] && that[ direction ]();
}
});
swipeListener.on();
},
retrievePageSiblings: function() {
elLink.prev = document.querySelector( "head > link[rel=prev]");
elLink.next = document.querySelector( "head > link[rel=next]");
},
renderArows: function() {
var renderArrow = function( direction ) {
var div = document.createElement("div");
div.className = "spn-direction-sign " + direction;
document.getElementsByTagName( "body" )[ 0 ].appendChild( div );
return div;
}
elArrow.next = renderArrow( "next" );
elArrow.prev = renderArrow( "prev" );
},
// When the shift (page swap) is requested, this overlay indicates that
// the current page is frozen and a new one is loading
showLoadingScreen: function() {
var div = document.createElement("div");
div.className = "spn-freezing-overlay";
document.getElementsByTagName( "body" )[ 0 ].appendChild( div );
},
// Request the previous sibling page
prev: function() {
if ( elLink.prev ) {
this.showLoadingScreen();
window.location.href = elLink.prev.href;
}
},
// Request the next sibling page
next: function() {
if ( elLink.next ) {
this.showLoadingScreen();
window.location.href = elLink.next.href;
}
}
}
}())
// Apply when document is ready
document.addEventListener( "DOMContentLoaded", function(){
document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
try {
swipePageNav.init();
} catch (e) {
alert(e);
}
}, false );
}( window ));
You cannot do that. However, if it is for organization goals why not have two JavaScript files, each one with its own namespace. If you do this, you call only that namespace expected for the link clicked.
I have a very simple menu, no sub menus and was curious how I would get the menu to close when I click on one of the links?
Plugin Home Page ->
Here is the code directly out of the .js file that comes in the zip when you download this menu. I really need to know what code to add and where to add it so that when I click a link it closes the menu.
;( function( $, window, undefined ) {
'use strict';
// global
var Modernizr = window.Modernizr, $body = $( 'body' );
$.DLMenu = function( options, element ) {
this.$el = $( element );
this._init( options );
};
// the options
$.DLMenu.defaults = {
// classes for the animation effects
animationClasses : { classin : 'dl-animate-in-1', classout : 'dl-animate-out-1' },
// callback: click a link that has a sub menu
// el is the link element (li); name is the level name
onLevelClick : function( el, name ) { return false; },
// callback: click a link that does not have a sub menu
// el is the link element (li); ev is the event obj
onLinkClick : function( el, ev ) { return false; }
};
$.DLMenu.prototype = {
_init : function( options ) {
// options
this.options = $.extend( true, {}, $.DLMenu.defaults, options );
// cache some elements and initialize some variables
this._config();
var animEndEventNames = {
'WebkitAnimation' : 'webkitAnimationEnd',
'OAnimation' : 'oAnimationEnd',
'msAnimation' : 'MSAnimationEnd',
'animation' : 'animationend'
},
transEndEventNames = {
'WebkitTransition' : 'webkitTransitionEnd',
'MozTransition' : 'transitionend',
'OTransition' : 'oTransitionEnd',
'msTransition' : 'MSTransitionEnd',
'transition' : 'transitionend'
};
// animation end event name
this.animEndEventName = animEndEventNames[ Modernizr.prefixed( 'animation' ) ] + '.dlmenu';
// transition end event name
this.transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ] + '.dlmenu',
// support for css animations and css transitions
this.supportAnimations = Modernizr.cssanimations,
this.supportTransitions = Modernizr.csstransitions;
this._initEvents();
},
_config : function() {
this.open = false;
this.$trigger = this.$el.children( '.dl-trigger' );
this.$menu = this.$el.children( 'ul.dl-menu' );
this.$menuitems = this.$menu.find( 'li:not(.dl-back)' );
this.$el.find( 'ul.dl-submenu' ).prepend( '<li class="dl-back">BACK</li>' );
this.$back = this.$menu.find( 'li.dl-back' );
},
_initEvents : function() {
var self = this;
this.$trigger.on( 'click.dlmenu', function() {
if( self.open ) {
self._closeMenu();
}
else {
self._openMenu();
}
return false;
} );
this.$menuitems.on( 'click.dlmenu', function( event ) {
event.stopPropagation();
var $item = $(this),
$submenu = $item.children( 'ul.dl-submenu' );
if( $submenu.length > 0 ) {
var $flyin = $submenu.clone().css( 'opacity', 0 ).insertAfter( self.$menu ),
onAnimationEndFn = function() {
self.$menu.off( self.animEndEventName ).removeClass( self.options.animationClasses.classout ).addClass( 'dl-subview' );
$item.addClass( 'dl-subviewopen' ).parents( '.dl-subviewopen:first' ).removeClass( 'dl-subviewopen' ).addClass( 'dl-subview' );
$flyin.remove();
};
setTimeout( function() {
$flyin.addClass( self.options.animationClasses.classin );
self.$menu.addClass( self.options.animationClasses.classout );
if( self.supportAnimations ) {
self.$menu.on( self.animEndEventName, onAnimationEndFn );
}
else {
onAnimationEndFn.call();
}
self.options.onLevelClick( $item, $item.children( 'a:first' ).text() );
} );
return false;
}
var link = $item.find('a').attr('href');
var hash = link.substring(link.indexOf('#')+1);
if( hash != "" ){
var elem = jQuery('div[data-anchor="'+hash+'"]');
autoScroll(elem);
self._closeMenu();
}else{
self.options.onLinkClick( $item, event );
}
} );
this.$back.on( 'click.dlmenu', function( event ) {
var $this = $( this ),
$submenu = $this.parents( 'ul.dl-submenu:first' ),
$item = $submenu.parent(),
$flyin = $submenu.clone().insertAfter( self.$menu );
var onAnimationEndFn = function() {
self.$menu.off( self.animEndEventName ).removeClass( self.options.animationClasses.classin );
$flyin.remove();
};
setTimeout( function() {
$flyin.addClass( self.options.animationClasses.classout );
self.$menu.addClass( self.options.animationClasses.classin );
if( self.supportAnimations ) {
self.$menu.on( self.animEndEventName, onAnimationEndFn );
}
else {
onAnimationEndFn.call();
}
$item.removeClass( 'dl-subviewopen' );
var $subview = $this.parents( '.dl-subview:first' );
if( $subview.is( 'li' ) ) {
$subview.addClass( 'dl-subviewopen' );
}
$subview.removeClass( 'dl-subview' );
} );
return false;
} );
},
closeMenu : function() {
if( this.open ) {
this._closeMenu();
}
},
_closeMenu : function() {
var self = this,
onTransitionEndFn = function() {
self.$menu.off( self.transEndEventName );
self._resetMenu();
};
this.$menu.removeClass( 'dl-menuopen' );
this.$menu.addClass( 'dl-menu-toggle' );
this.$trigger.removeClass( 'dl-active' );
if( this.supportTransitions ) {
this.$menu.on( this.transEndEventName, onTransitionEndFn );
}
else {
onTransitionEndFn.call();
}
this.open = false;
},
openMenu : function() {
if( !this.open ) {
this._openMenu();
}
},
_openMenu : function() {
var self = this;
// clicking somewhere else makes the menu close
$body.off( 'click' ).on( 'click.dlmenu', function() {
self._closeMenu() ;
} );
this.$menu.addClass( 'dl-menuopen dl-menu-toggle' ).on( this.transEndEventName, function() {
$( this ).removeClass( 'dl-menu-toggle' );
} );
this.$trigger.addClass( 'dl-active' );
this.open = true;
},
// resets the menu to its original state (first level of options)
_resetMenu : function() {
this.$menu.removeClass( 'dl-subview' );
this.$menuitems.removeClass( 'dl-subview dl-subviewopen' );
}
};
var logError = function( message ) {
if ( window.console ) {
window.console.error( message );
}
};
$.fn.dlmenu = function( options ) {
if ( typeof options === 'string' ) {
var args = Array.prototype.slice.call( arguments, 1 );
this.each(function() {
var instance = $.data( this, 'dlmenu' );
if ( !instance ) {
logError( "cannot call methods on dlmenu prior to initialization; " +
"attempted to call method '" + options + "'" );
return;
}
if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) {
logError( "no such method '" + options + "' for dlmenu instance" );
return;
}
instance[ options ].apply( instance, args );
});
}
else {
this.each(function() {
var instance = $.data( this, 'dlmenu' );
if ( instance ) {
instance._init();
}
else {
instance = $.data( this, 'dlmenu', new $.DLMenu( options, this ) );
}
});
}
return this;
};
} )( jQuery, window );
After looking at their demo, it seems you can call method plugins. Replace dl-menu with the id of your menu.
$('#dl-menu a').click(function(e) {
$('#dl-menu').dlmenu('closeMenu');
});
I am new to JavaScript. I created the webpage linked below as an exercise for a class I am taking.
JavaScript Product Catalog
It seems to work ok if it all loads correctly, but half the time either the images or buttons do not load, and sometimes the thumbnail loads but the full-size (mouseover) image does not load. In Firefox or IE, it seems to only happen on first loads, and after that (as long as browser stays open), it will successfully load every time after that. But in Chrome it continues to act random every single page reload. Just refresh the page about 10 times in a row and you will probably see a couple instances where either the buttons or the images (or both) don't load.
I am assuming this is a problem with my code, since I've never had any other problems with the server. Any ideas?
Thanks!
<!DOCTYPE html>
<html>
<head>
<meta charset = "utf-8">
<style type = "text/css">
.box { border: 1px solid black; padding: 4px }
</style>
<title>Product Catalog</title>
<script>
var catalogDiv;
var summaryRequest;
var descriptionsRequest;
var thumbsRequest;
var imagesRequest;
function showLargeImage( imageElement )
{
imageElement.style.display = "none";
imageElement.nextSibling.style.display = "inline";
}
function showThumb( imageElement )
{
imageElement.style.display = "none";
imageElement.previousSibling.style.display = "inline";
}
function showDesc( descButton )
{
if ( descButton.nextSibling.style.display == "none" ) {
descButton.nextSibling.style.display = "block";
} else {
descButton.nextSibling.style.display = "none";
}
}
function getDescriptions()
{
try
{
descriptionsRequest = new XMLHttpRequest();
descriptionsRequest.addEventListener("readystatechange",
loadDescriptions, false );
descriptionsRequest.open( "GET", "descriptions.json", true );
descriptionsRequest.setRequestHeader( "Accept",
"application/json; charset=utf-8" );
descriptionsRequest.send();
}
catch ( exception )
{
alert( "Request Failed" );
}
}
function loadDescriptions()
{
if ( descriptionsRequest.readyState == 4
&& descriptionsRequest.status == 200 )
{
var descriptions = JSON.parse( descriptionsRequest.responseText );
for ( var i = 0; i < descriptions.length; i++ ) {
var infoDiv = document.getElementById( descriptions[i].id +
"-info-inner" );
var descButton = document.createElement( "button" );
infoDiv.appendChild( descButton );
descButton.type = "button";
descButton.textContent = "show description";
descButton.setAttribute( "onclick", "showDesc( this )");
var desc = document.createElement( "fieldset" );
desc.style.display = "none";
desc.style.margin = "10px";
infoDiv.appendChild( desc );
desc.innerHTML = "<br>" + descriptions[i].text + "<br><br>" ;
}
}
}
function getImages()
{
try
{
imagesRequest = new XMLHttpRequest();
imagesRequest.addEventListener("readystatechange",
loadImages, false );
imagesRequest.open( "GET", "images.json", true );
imagesRequest.setRequestHeader( "Accept",
"application/json; charset=utf-8" );
imagesRequest.send();
}
catch ( exception )
{
alert( "Request Failed" );
}
}
function loadImages()
{
if ( imagesRequest.readyState == 4 && imagesRequest.status == 200 )
{
var images = JSON.parse( imagesRequest.responseText );
for ( var i = 0; i < images.length; i++ ) {
var imageDiv = document.getElementById( images[i].id +
"-image-inner" );
imageDiv.innerHTML += "<img style=\"display:none;\"" +
"src=\"" + images[i].filename+ "\">";
imageDiv.lastChild.setAttribute( "onmouseout",
"showThumb( this )" );
}
}
}
function getThumbs()
{
try
{
thumbsRequest = new XMLHttpRequest();
thumbsRequest.addEventListener("readystatechange",
loadThumbs, false );
thumbsRequest.open( "GET", "thumbs.json", true );
thumbsRequest.setRequestHeader( "Accept",
"application/json; charset=utf-8" );
thumbsRequest.send();
}
catch ( exception )
{
alert( "Request Failed" );
}
}
function loadThumbs()
{
if ( thumbsRequest.readyState == 4 && thumbsRequest.status == 200 )
{
var thumbs = JSON.parse( thumbsRequest.responseText );
for ( var i = 0; i < thumbs.length; i++ ) {
var imageDiv = document.getElementById( thumbs[i].id +
"-image-inner" );
imageDiv.innerHTML = "<img style=\"display:inline;\"" +
"src=\"" + thumbs[i].filename+ "\">";
imageDiv.firstChild.setAttribute( "onmouseover",
"showLargeImage( this )");
}
}
}
function setupDivsRequest()
{
try
{
summaryRequest = new XMLHttpRequest();
summaryRequest.addEventListener("readystatechange",
setupDivsResponse, false );
summaryRequest.open( "GET", "summary.json", true );
summaryRequest.setRequestHeader( "Accept",
"application/json; charset=utf-8" );
summaryRequest.send();
}
catch ( exception )
{
alert( "Request Failed" );
}
}
function setupDivsResponse()
{
if ( summaryRequest.readyState == 4 && summaryRequest.status == 200 )
{
var summary = JSON.parse( summaryRequest.responseText );
for ( var i = 0; i < summary.length; i++ ) {
var productDiv = document.createElement( "div" );
var productImageOuterDiv = document.createElement( "div" );
var productImageInnerDiv = document.createElement( "div" );
var productInfoOuterDiv = document.createElement( "div" );
var productInfoInnerDiv = document.createElement( "div" );
catalogDiv.appendChild(productDiv);
productDiv.appendChild( productImageOuterDiv );
productDiv.appendChild( productInfoOuterDiv );
productImageOuterDiv.appendChild( productImageInnerDiv );
productInfoOuterDiv.appendChild( productInfoInnerDiv );
productDiv.id = summary[i].id;
productDiv.className = "box";
productImageOuterDiv.id = summary[i].id + "-image-outer";
productImageOuterDiv.style.cssFloat = "left";
productImageInnerDiv.id = summary[i].id + "-image-inner";
productImageInnerDiv.style.height = "250px";
productImageInnerDiv.style.width = "250px";
productImageInnerDiv.style.display = "table-cell";
productImageInnerDiv.style.verticalAlign = "middle";
productImageInnerDiv.style.textAlign = "center";
productInfoOuterDiv.id = summary[i].id + "-info-outer";
productInfoOuterDiv.style.height = "250px";
productInfoInnerDiv.id = summary[i].id + "-info-inner";
productInfoInnerDiv.style.float = "left";
productInfoInnerDiv.style.padding = "10px";
productInfoInnerDiv.innerHTML = summary[i].title + "<br>";
productInfoInnerDiv.innerHTML += summary[i].price + "<br><br>";
}
}
}
function start()
{
catalogDiv = document.getElementById( "catalog" );
setupDivsRequest();
getThumbs();
getImages();
getDescriptions();
}
window.addEventListener( "load", start, false );
</script>
</head>
<body>
<h1>Mouse over a product thumbnail for a larger picture.</h1>
<div id = "catalog"></div>
</body>
</html>
It took a long time, but I finally got to the bottom of this problem. It was a race condition between multiple asynchronous requests to populate the same element. I had not considered this was possible, so the one which I expected to go first would add the first HTML to the element:
element.innerHTML = "first text";
While the request which I expected to go second would add the second HTML:
element.innerHTML += "second text";
Obviously if those requests go out of order, because of the way I used = and +=, the result will be that "second text" gets overwritten, which is essentially why my images weren't loading half the time. (Even if I had used += in both cases, I'd still have the problem of randomly ordered elements as my code below shows).
For whatever reason, the race condition never seemed to matter in Firefox or IE. Maybe there is something in those browsers to try to safeguard against such a condition, by forcing requests to finish in the order they started? Or maybe it is just dumb luck. But in Chrome, the requests would consistently finish in a random order. A much simpler code below illustrates clearly. In Chrome, half the time you will get "FOOBAR" as HTML output, but the other half of the time you will get "BARFOO." The testx.json files I reference in the script are dummy (empty) files.
The race condition is easily fixed in this situation by having my second setup function called by the first setup's callback function after completing its other tasks. In a more complicated situation I would guess the other typical race condition safeguards (mutexes and semaphores) would work as well.
<!DOCTYPE html>
<html>
<head>
<script>
var testDiv;
var request1;
var request2;
window.addEventListener( "load", start, false );
function start()
{
testDiv = document.getElementById( "test-div" );
setup1();
setup2();
}
function setup1()
{
try
{
request1 = new XMLHttpRequest();
request1.addEventListener("readystatechange",
response1, false );
request1.open( "GET", "test1.json", true );
request1.setRequestHeader( "Accept",
"application/json; charset=utf-8" );
request1.send();
}
catch ( exception )
{
alert( "Request Failed" );
}
}
function response1()
{
if ( request1.readyState == 4 && request1.status == 200 )
{
testDiv.innerHTML += "FOO";
}
}
function setup2()
{
try
{
request2 = new XMLHttpRequest();
request2.addEventListener("readystatechange",
response2, false );
request2.open( "GET", "test2.json", true );
request2.setRequestHeader( "Accept",
"application/json; charset=utf-8" );
request2.send();
}
catch ( exception )
{
alert( "Request Failed" );
}
}
function response2()
{
if ( request2.readyState == 4 && request2.status == 200 )
{
testDiv.innerHTML += "BAR";
}
}
</script>
</head>
<body>
<div id = "test-div"> </div>
</body>
</html>