How to apply a javascript function to a multiple div Classes? - javascript

I have a function that creates a gallery of flickr sets pulled from my flickr account. I am getting the set numbers from a database and using a while loop to display the first image from the set. Each element of the table has the same class and i am applying a Javascript function to them. Unfortunately each table element is displaying the same photo, the last one pulled from the database.
$(document).ready(function() {
var flickrUrl="";
$('.gallery_table_data').each(function(){
flickrUrl = $(this).attr('title');
$('.flickr_div').flickrGallery({
"flickrSet" : flickrUrl,
"flickrKey" : "54498f94e844cb09c23a76808693730a"
});
});
});
and the images dont show up at all? can anyone help??
Here is the flickr jquery in case that's the problem:
var flickrhelpers = null;
(function(jQuery) {
jQuery.fn.flickrGallery = function(args) {
var $element = jQuery(this), // reference to the jQuery version of the current DOM element
element = this; // reference to the actual DOM element
// Public methods
var methods = {
init : function () {
// Extend the default options
settings = jQuery.extend({}, defaults, args);
// Make sure the api key and setID are passed
if (settings.flickrKey === null || settings.flickrSet === null) {
alert('You must pass an API key and a Flickr setID');
return;
}
// CSS jqfobject overflow for aspect ratio
element.css("overflow","hidden");
// Get the Flickr Set :)
$.getJSON("http://api.flickr.com/services/rest/?format=json&method=flickr.photosets.getPhotos&photoset_id=" + settings.flickrSet + "&api_key=" + settings.flickrKey + "&jsoncallback=?", function(flickrData){
var length = 1;
var thumbHTML = '';
for (i=0; i<length; i++) {
var photoURL = 'http://farm' + flickrData.photoset.photo[i].farm + '.' + 'static.flickr.com/' + flickrData.photoset.photo[i].server + '/' + flickrData.photoset.photo[i].id + '_' + flickrData.photoset.photo[i].secret +'.jpg'
settings.imgArray[i] = photoURL;
settings.titleArray[i] = flickrData.photoset.photo[i].title;
}
// Get the position of the element Flickr jqfobj will be loaded into
settings.x = element.offset().left;
settings.y = element.offset().top;
settings.c = settings.x + (element.width() / 2);
settings.ct = settings.y + (element.height() / 2);
// When data is set, load first image.
flickrhelpers.navImg(0);
});
}
}
// Helper functions here
flickrhelpers = {
navImg : function (index) {
// Set the global index
currentIndex = index;
// Create an image Obj with the URL from array
var thsImage = null;
thsImage = new Image();
thsImage.src = settings.imgArray[index];
// Set global imgObj to jQuery img Object
settings.fImg = $( thsImage );
// Display the image
element.html('');
element.html('<img class="thsImage" src=' + settings.imgArray[index] + ' border=0>');
// Call to function to take loader away once image is fully loaded
$(".thsImage").load(function() {
// Set the aspect ratio
var w = $(".thsImage").width();
var h = $(".thsImage").height();
if (w > h) {
var fRatio = w/h;
$(".thsImage").css("width",element.width());
$(".thsImage").css("height",Math.round(element.width() * (1/fRatio)));
} else {
var fRatio = h/w;
$(".thsImage").css("height",element.height());
$(".thsImage").css("width",Math.round(element.height() * (1/fRatio)));
}
if (element.outerHeight() > $(".thsImage").outerHeight()) {
var thisHalfImage = $(".thsImage").outerHeight()/2;
var thisTopOffset = (element.outerHeight()/2) - thisHalfImage;
$(".thsImage").css("margin-top",thisTopOffset+"px");
}
if (settings.titleArray[currentIndex] != "") {
$(".flickr_count").append(settings.titleArray[currentIndex]);
}
});
},
toggleUp : function() {
$("#flickr_thumbs").slideUp("slow");
}
}
// Hooray, defaults
var defaults = {
"flickrSet" : null,
"flickrKey" : null,
"x" : 0, // Object X
"y" : 0, // Object Y
"c" : 0, // Object center point
"ct" : 0, // Object center point from top
"mX" : 0, // Mouse X
"mY" : 0, // Mouse Y
"imgArray" : [], // Array to hold urls to flickr images
"titleArray" : [], // Array to hold image titles if they exist
"currentIndex" : 0, // Default image index
"fImg" : null, // For checking if the image jqfobject is loaded.
}
// For extending the defaults!
var settings = {}
// Init this thing
jQuery(document).ready(function () {
methods.init();
});
// Sort of like an init() but re-positions dynamic elements if browser resized.
$(window).resize(function() {
// Get the position of the element Flickr jqfobj will be loaded into
settings.x = element.offset().left;
settings.y = element.offset().top;
settings.c = settings.x + (element.width() / 2);
settings.ct = settings.y + (element.height() / 2);
});
}
})(jQuery);

The big problem is in your $.each loop. I am going to assume the plugin will work for all the elements you are looping over although have doubts that it will.
WHen you select $('.flickr_div') on each pass it affects all the elements in page with that class...so only the last pass of loop is valid
$(document).ready(function() {
var flickrUrl="";
$('.gallery_table_data').each(function(){
flickrUrl = $(this).attr('title');
/* this is your problem , is selecting all ".flickr_div" in page on each loop*/
//$('.flickr_div').flickrGallery({
/* without seeing your html structure am assuming
next class is inside "this"
try: */
$(this).find('.flickr_div').flickrGallery({
"flickrSet" : flickrUrl,
"flickrKey" : "54498f94e844cb09c23a76808693730a"
});
});
});
EDIT This same concept of using find() should also be refactoered into code within the plugin. Plugin should have all ID's replaced with classes.
Plugin really does not look well constructed for multiple instances within a page

I might be wrong here, but won't this (in your flickrGallery object)
$("body").append('<div id="flickr_loader"></div>');`
create multiple elements with the same ID? And the same for images in flickrhelpers:
element.html('<img id="thsImage" src=' + settings.imgArray[index] + ' border=0>');

Related

Adding a counter to a prev/next image slideshow(javascript/css only)?

I have created a "prev/next" slideshow using javascript, now I want to add a counter(1/10, 2/10, 3/10...) beside my "prev/next" buttons but nothing seemed to work.
This is my first time attempting to make a website, I know nothing about jQuery, so please stick with html+javascript if possible. Here is my script
var image = new Array(
"http://i990.photobucket.com/albums/af24/callmeaaaaj/AJ/_MG_7747.jpg",
"http://i990.photobucket.com/albums/af24/callmeaaaaj/AJ/1109163s.jpg")
var imgNumber=1
var numberOfImg=2
function previousImage(){
if(imgNumber>1){
imgNumber--
}
else{
imgNumber = numberOfImg
}
document.slideImage.src = image[imgNumber-1]
}
function nextImage(){
if(imgNumber < numberOfImg){
imgNumber++
}
else{
imgNumber = 1
}
document.slideImage.src = image[imgNumber-1]
}
if(document.images){
var image1 = new Image()
image1.src = "http://i990.photobucket.com/albums/af24/callmeaaaaj/AJ/_MG_7747.jpg"
var image2 = new Image()
image2.src = "http://i990.photobucket.com/albums/af24/callmeaaaaj/AJ/1109163s.jpg"
}
Script+html: http://jsfiddle.net/nLHY9/5/
(Prev/Next buttons seem not to be working on this----they work fine when I launched them from laptop to browser.)
you could have used some existing javascript image sliders, for example, sliderman slider, for your current code, you can do like, add an element like span, to hold the count, and you could add a function like:
function changeCounter(cur, total) {
document.getElementById("counter").innerHTML = cur + "/" + total;
}
and call it in your previousImage() and nextImage() functions, as in this demo jsfiddle
There are many pure css slideshows that are beautiful and can do impressive things. However, as you try to support older browsers, the pure css slideshows get less and less impressive or even impossible. JavaScript is the most flexible and powerful way to go. That being, I wanted to help you clean up your code. I only had a few minutes, so this is a quickly thrown together plugin, but it should get you on the right track.
First, a few notes on your code:
//you're missing semicolons everywhere. ";"
/* "var image" is very unclear.
* it's an array, so it should be plural "images"
* there aren't images in this array - it's image urls or sources
* instead of "new Array" you could just use "[]"
*/
var image = new Array(
"http://i990.photobucket.com/albums/af24/callmeaaaaj/AJ/_MG_7747.jpg",
"http://i990.photobucket.com/albums/af24/callmeaaaaj/AJ/1109163s.jpg")
var imgNumber=1 //the name doesn't mean anything. I have to assume that you mean "currentImgNumber" or something to that effect
var numberOfImg=2 //this could be determined by checking the length of your array - myArray.length
And here's my exampe plugin:
Live demo here (click).
/***** This section is how you use the plugin. I start writing with the usage and then I make it mean something *****/
window.onload = function() { //when the page is loaded
var fooElem = document.getElementById('foo'); //get an element where we will attach the plugin
var foo = Object.create(slideshow); //create a new slideshow object
foo.create({ //create a slideshow with the given options
element: fooElem, //the element where the slideshow will be
sources: [ //image urls
"http://i990.photobucket.com/albums/af24/callmeaaaaj/AJ/_MG_7747.jpg",
"http://i990.photobucket.com/albums/af24/callmeaaaaj/AJ/1109163s.jpg"
]
});
//we can make more of these and with different options
var barElem = document.getElementById('bar');
var bar = Object.create(slideshow);
bar.create({
element: barElem,
sources: [
"http://eggboss.com/wp-content/uploads/2013/09/The-Gentleman-233x300.png",
"http://fc07.deviantart.net/fs71/f/2013/040/8/a/profile_picture_by_classy_like_a_sir-d5uf426.jpg"
]
});
};
/**** now let's create the plugin and make it work as it is used above *****/
var slideshow = {
currentIndex: 0,
imgs: [],
create: function(options) {
options.element.className+= ' slideshow'; //add a class to the main element for styling
this.imgs = this.getImgs(options.sources); //make img html
var controls = this.getControls(); //make controls
//add the html to the element from the options
var frag = document.createDocumentFragment();
this.imgs.forEach(function(img) {
frag.appendChild(img);
});
frag.appendChild(controls);
options.element.appendChild(frag);
},
getImgs: function(sources) {
var imgs = [];
sources.forEach(function(src, i) {
var img = document.createElement('img');
img.src = src;
imgs.push(img);
if (i > 0) {
img.style.display = 'none'; //hide all but first image
}
});
return imgs;
},
getControls: function() {
var that = this; //so that we can access "this" within the click functions
var controls = document.createElement('div');
controls.className = 'controls';
var counter = document.createElement('span');
counter.className = 'counter';
this.setCounter(counter);
var prev = document.createElement('a');
prev.textContent = 'Prev';
prev.className = 'prev';
prev.addEventListener('click', function() {
newIndex = (that.currentIndex) ? that.currentIndex-1 : that.imgs.length-1;
that.changeImg(newIndex, counter);
});
var next = document.createElement('a');
next.textContent = 'Next';
next.className = 'next';
next.addEventListener('click', function() {
newIndex = (that.currentIndex !== that.imgs.length-1) ? that.currentIndex+1 : 0;
that.changeImg(newIndex, counter);
});
controls.appendChild(prev);
controls.appendChild(next);
controls.appendChild(counter);
return controls;
},
changeImg: function(newIndex, counter) {
this.imgs[this.currentIndex].style.display = 'none';
this.imgs[newIndex].style.display = 'inline';
this.currentIndex = newIndex;
this.setCounter(counter);
},
setCounter: function(counter) {
counter.textContent = (this.currentIndex+1)+' / '+this.imgs.length;
}
};

vtip jQuery to display titles

I am using vTip plugin to display titles on hover , here is my example code:
http://jsfiddle.net/bnjSs/
I have a Div#showTitles , When i click on it I wants to toggle display of all div.vtips titles on page just like mouseover.
$("#showTitles").click(function(){
// this I am not sure how to acheive.
return false;
});
UPDATE:
http://jsfiddle.net/bnjSs/2/
I have tried this but its not showing on correct positions as mouseover but once i mouseover on .vtip and then i click on #showTitles its working fine, I also need to toggle this behavior :
$("#showTitles").click(function(e,ui){
this.xOffset = 5;
this.yOffset = 10;
this.top = (e.pageY + yOffset);
this.left = (e.pageX + xOffset);
$('.vtip').each(function(index) {
title = $(this).text();
$('body').append( '<p id="vtip"><img id="vtipArrow" />' + title + '</p>' );
$('p#vtip #vtipArrow').attr("src", 'images/vtip_arrow.png');
$('p#vtip').css("top", this.top+"px").css("left",
this.left+"px").fadeIn("slow");
});
return false;
});
Thanks for any help.
IDs should be unique.finished
I've modified your current code to get it work under the current conditions. The function logic should be obvious because of the descriptive names. Fiddle: http://jsfiddle.net/bnjSs/7/
Update: Added show/hide text + feature to the code, as requested at the comments.
Update2: Taken care of whole code, improved efficiency.
You're overusing this. this.xOffset = 5 attaches xOffset to the element. Use var xOffset = 5 to define xOffset in the scope of the function. The code below has the following scope model:
$(document).ready(function(){
// Variables defined using `var` can be read by
// any function defined within this function
function vtip(){
// variables declared here can be read by any function defined inside vtip
// but cannot be read by methods outside vtip
function(){..} at .hover and .mousemove
// These functions also have an own scope, and variables defined using
// var cannot be read by anything outside these functions, even at vtip
$("#showTitles").click(function(e){
// variables declared here cannot be read by code outside this function
// This function can read any variable which is declared using var at
// $(document).ready
Note: "Declare" means "Define using var".
Final code:
// Run when the DOM is ready
//Note: $(function(){ is short for $(document).ready(function(){
$(function(){
var xOffset = 5; // x distance from mouse
var yOffset = 10; // y distance from mouse
var frozen_vtip = false; //Define it
var vtip = function() {
$(".displaytip").unbind().hover(
function(e) {
if(frozen_vtip) return;
this.t = this.title;
this.title = '';
var top = (e.pageY + yOffset);
var left = (e.pageX + xOffset);
$('<p class="vtip"><img class="vtipArrow" src=""/>' + this.t + '</p>').css("top", top+"px").css("left", left+"px").fadeIn("slow").appendTo(this);
},
function() {
if(frozen_vtip) return;
this.title = this.t;
$("p.vtip", this).fadeOut("slow").remove();
}
).mousemove(
function(e) {
if(frozen_vtip) return;
var top = (e.pageY + yOffset);
var left = (e.pageX + xOffset);
$("p.vtip", this).css("top", top+"px").css("left", left+"px");
}
);
};
vtip();
// Second function, when text is "Hide titles" it should
// prevent runing vtip() on mouseover (above function) and
// when text is "Show titles" It should normally run vtip()
// (mouseover display title.
$("#showTitles").click(function(e){
e.preventDefault();
if($(this).text() == "Hide titles"){
$(this).text("Show titles");
$('p.vtip').fadeOut("slow").remove();
$('.displaytip').each(function(){
this.title = this.t; //Re-attach `title`
});
frozen_vtip = false;
} else {
frozen_vtip = true;
$(this).text("Hide titles");
$('.displaytip').each(function(index) {
if($('p.vtip', this).length) {return;}
this.t = this.title;
this.title = '';
var $this = $(this);
var offset = $this.offset();
var height = $this.height();
var top = offset.top + height;
var left = offset.left + height;
$('<p class="vtip"><img class="vtipArrow" src="images/vtip_arrow.png" />' + this.t + '</p>' ).appendTo(this).css("top", top+"px").css("left", left+"px").fadeIn("slow");
});
}
});
});

Javascript/jQuery : SCRIPT438 error with IE7/8, any debugging tips?

I have a javascript which works perfectly in chrome, FF2/3, and IE9
158: drop_area = $('#drop_area');
159: element = ui.helper;
however I get the following error in IE7 amd IE8:
SCRIPT438: Object doesn't support this property or method
dragdrop.js, line 158 character 2
My knowledge of IE7's debugging features is pretty limited but it doesn't look like I can really inspect the object in question from the console. Does anyone know what might be going on here or how I can better debug this error
EDIT:
Realized a little bit more context might be helpful
function on_element_drop(event, ui){
drop_area = $('#drop_area');
element = ui.helper;
on_element_drop is a callback method for a jQuery UI droppable 'drop' event
/*
* dragdrop.js
* Author: Casey Flynn
* June 10, 2011
* Global variables available to all functions
*/
//Keeps track of elements present in droppable and corresponding offsets/positions
var base_url = 'http://www.bla/';
var global_positions = new Array();
var current_item_group = 0;
var loaded = new Array();
var preloading = true;
var items = new Array(
new Array(
new Array(0, '2-Dollar'),
new Array(1, 'Cards'),
new Array(2, 'Cheese'),
new Array(3, 'Coupons'),
new Array(4, 'DogTags')),
new Array(
new Array(5, 'Doodle'),
new Array(6, 'Dreamcatcher'),
new Array(7, 'Fish'),
new Array(8, 'Fortune'),
new Array(9, 'Groucho')),
new Array(
new Array(10, 'HandTurkey'),
new Array(11, 'Key'),
new Array(12, 'Lolipop'),
new Array(13, 'LotteryTicket'),
new Array(14, 'Map')),
new Array(
new Array(15, 'Napkin'),
new Array(16, 'Notepad'),
new Array(17, 'OldPhoto'),
new Array(18, 'Oragami'),
new Array(19, 'Photo_Baby')),
new Array(
new Array(20, 'Photo_DJ'),
new Array(21, 'Photo_Dogs'),
new Array(22, 'Photo_Moustache'),
new Array(23, 'Pick'),
new Array(24, 'RabitsFoot')),
new Array(
new Array(25, 'Recipe'),
new Array(26, 'Reminder'),
new Array(27, 'Ribbon'),
new Array(28, 'SheetMusic'),
new Array(29, 'Smiley')),
new Array(
new Array(30, 'Spork'),
new Array(31, 'Tape'),
new Array(32, 'Ticket'),
new Array(33, 'USB'),
new Array(34, 'Viewmaster')
)
);
/*
* jQuery 'ready' handler, executes after DOM is ready and
* sets draggable/droppable properties of associated elements
*/
$(function(){
for(var i = 0; i < items.length; i++)
loaded[i] = false;
load_elements(0);
$('#drop_area').droppable({
//accept: '.draggable',
//hoverClass: 'vel_content_active',
drop: on_element_drop
});
$('#countdown').html((10 - global_positions.length)+'');
});
// preloads an array of images
function preload(arrayOfImages) {
$(arrayOfImages).each(function(){
console.log('Preloading ' + this);
$('<img/>')[0].src = this;
// Alternatively you could use:
// (new Image()).src = this;
});
}
/*
* Loads the first set of elements from 'items'
*/
function load_elements(arg0){
set = items[arg0];
box_handle = $('.bottom_content');
elements = '';
//construct html for elements to be added
for(i=0; i<set.length; i++){
elements += '<div class="draggable"><img alt="' + set[i][0] + '" src="' + base_url + 'application/assets/images/items/' + set[i][1] + '.png" /></div>';
}
//clear whatever was in there
box_handle.empty();
// element parent container
box_handle.html(elements);
//assign draggable status
$('.draggable').draggable({
revert: true,
revertDuration: 0,
helper: 'clone'
});
loaded[arg0] = true;
if(preloading){
var prev = next_elements(-1);
var next = next_elements(1);
if(!loaded[prev]){
preload(items[prev])
loaded[prev] = true;
}
if(!loaded[next]){
preload(items[next])
loaded[prev] = true;
}
}
}
function next_elements(arg0){
if((current_item_group + arg0) == -1){
return 6;
}else{
return ((current_item_group + arg0) % 7);
}
}
/*
* Cycles through element groups presented in .bottom_content
-1 to the left 1 to the right
*/
function cycle_elements(arg0){
if((current_item_group + arg0) == -1){
current_item_group = 6;
}else{
current_item_group = ((current_item_group + arg0) % 7);
}
load_elements(current_item_group);
}
/*
* Callback function on drop event for droppable
* Determines position of dropped element in droppable
*/
function on_element_drop(event, ui){
drop_area = $('#drop_area');
element = ui.helper;
//Find relative x/y position of element inside drop_area
var x = Math.floor((element.offset().left - drop_area.offset().left) / drop_area.width() * 100);
var y = Math.floor((element.offset().top - drop_area.offset().top) / drop_area.height() * 100);
//console.log(ui); return;
//console.log(ui.draggable.context.className.indexOf('draggable_dropped'));
if(ui.draggable.context.className.indexOf('draggable_dropped') == -1){
if(global_positions.length >= 10)
return;
//record element position and id
row = new Array(parseInt($(ui.draggable).find("img").attr('alt')),
x,
y);
//Add copy of item to drop_area at same x/y position where it was dropped
add_element_copy_to_div(row);
add_element_copy_to_global_positions(row);
}else{
//Item has already been dropped and is being adjusted, update global_positions
//update global_positions
id = ui.draggable.context.id;
update_global_positions(id, x, y);
}
//$('#countdown').html((10 - global_positions.length)+'');
console.log(global_positions);
}
/*
* Finds element in global_positions and updates x & y values
*/
function update_global_positions(id, newX, newY){
image_id = global_positions[id][0];
global_positions[id] = new Array(image_id, newX, newY);
/*
var old_array = global_positions[find_index(global_positions, index)];
var new_array = new Array(old_array[0], newX, newY);
//.splice(i,1,pos) -- remove 1 element at index i and replace with pos
global_positions.splice(index,1,new_array);
*/
}
/*
* Helper function, determines if element is already present in 'global_positions'
* Replaces if present, adds otherwise
*/
function add_element_copy_to_global_positions(pos){
global_positions.push(pos);
/*
var found = false;
for(i=0;i<global_positions.length;i++){
if(global_positions[i][0] == pos[0]){
//.splice(i,1,pos) -- remove 1 element at index i and replace with pos
global_positions.splice(i,1,pos);
found = true;
}
}
if(!found)
global_positions.push(pos);
*/
}
/*
* Helper function, adds a copy of the draggable that was dropped into the droppable
* for user visualization
*/
function add_element_copy_to_div(pos){
drop_area = $('#drop_area');
id = global_positions.length;
console.log('id: ' + id);
//Convert % x&y offsets into absolute pixel offsets based on div size
x = Math.floor(drop_area.width() * (pos[1] / 100));
y = Math.floor(drop_area.height() * (pos[2] / 100));
/*------- Find filename of image that has been dropped ------*/
index = find_index(items[current_item_group], pos[0]);
filename = items[current_item_group][index][1];
drop_area.append('<div style="position:absolute;" class="draggable_dropped" id="' + id + '"><img src="' + base_url + 'application/assets/images/items/' + filename + '.png" /></div>');
$('#'+id).css('left', x);
$('#'+id).css('top', y);
//Set the newly dropped element to draggable so it can be repositioned
$('#'+id).draggable({
stop:function(event, ui){
if(!is_valid_position(ui)) //invalid drop
delete_item(ui);
}
});
}
/*
* deletes element from global_positions and #drop_area when user drops item outside #drop_area
* also adjusts id attributes of all items
*/
function delete_item(ui){
id = ui.helper.context.id;
$('#'+id).remove();
global_positions.splice(id, 1);
$('#drop_area div').each(function(index){
if(parseInt($(this).attr('id')) > parseInt(id))
$(this).attr('id', parseInt($(this).attr('id')) - 1);
});
console.log(global_positions);
}
/*
* helper for add_element_copy_to_div
*/
function is_valid_position(ui){
drop_area = $('#drop_area');
element = ui.helper;
//Find relative x/y position of element inside drop_area
var x = Math.floor((element.offset().left - drop_area.offset().left) / drop_area.width() * 100);
var y = Math.floor((element.offset().top - drop_area.offset().top) / drop_area.height() * 100);
if( (x < -5) ||
(x > 105) ||
(y < -5) ||
(y > 105))
return false;
return true;
}
/*
* helper for add_element_copy_to_div
*/
function find_index(items, search_index){
for(i=0; i < items.length; i++){
if(items[i][0] == search_index)
return i;
}
}
/*
* Convert global_position array to JSON and submit to server via ajax along with csrf_token
*/
function update_layout(){
$.ajax({
url: '/projects/velcro/index.php/welcome/update_layout',
type: 'post',
data: {'layout_json' : $.toJSON(global_positions), 'ci_csrf_token' : $('input[name=ci_csrf_token]').val()},
success: function(data, textStatus, jqXHR){
//Redirect user to next page here...
if(data == '1'){
//alert('Layout successfully saved');
}else{
//alert('Layout save failed');
}
location.href = 'http://www.messageamplify.com/projects/velcro/index.php/welcome/create2';
},
error: function(jqXHR, textStatus, errorThrown){
console.log('error: '+jqXHR);
console.log(textStatus);
console.log(errorThrown);
}
});
}
//End file 'dragdrop.js'
drop_area = $('#drop_area');
This line will always throw a error in IE. That's because in IE every element with id will be accessible through the global window object, in this case, window.drop_area. But the fact that window is a global object makes possible to access the object without the global, in this case, just drop_area.
So, the sentence drop_area = $('#drop_area'); is not trying to assign a object to a variable, but is trying to overwrite a element reference with another object. This is the error you are seeing as a runtime exception.
To bypass this exception, you need to assign the jQuery object to a variable. To do this, you have two choices:
use a var statement to scope the variable inside the function that contains the code and hide the access to window.drop_area from the global, like var drop_area = $('#drop_area');, or
use another variable name, like var dropArea = $('#drop_area');
And, as a good advice, always give a scope to the variables you are using with var statement.
IE8 has a built in debugger. Press F12, click the Script tab, click Start Debugging, and debug away.

Loading content with ajax while scrolling

I'm using jQuery Tools Plugin as image slider (image here), but due to large amount of images I need to load them few at a time. Since it's javascript coded, I can't have the scroll position as far as I know. I want to load them as soon as the last image shows up or something like that. I have no idea where I put and event listener neither anything.
Here is my code http://jsfiddle.net/PxGTJ/
Give me some light, please!
I just had to use jQuery Tools' API, the onSeek parameter within the scrollable() method.
It was something like that
$(".scrollable").scrollable({
vertical: true,
onSeek: function() {
row = this.getIndex();
// Check if it's worth to load more content
if(row%4 == 0 && row != 0) {
var id = this.getItems().find('img').filter(':last').attr('id');
id = parseInt(id);
$.get('galeria.items.php?id='+id, null, function(html) {
$('.items').append(html);
});
}
}
});
That could be made the following way:
//When the DOM is ready...
$(document).ready(function() {
//When the user scrolls...
$(window).scroll(function() {
var tolerance = 800,
scrollTop = $(window).scrollTop();
//If the the distance to the top is greater than the tolerance...
if(scrollTop > tolerance) {
//Do something. Ajax Call, Animations, whatever.
}
}) ;
});
That should do the trick.
EDIT: Because you're not using the native scroll, we've got to do a little fix to the code:
//When the DOM is ready...
$(document).ready(function() {
//When the user scrolls...
$("div.scrollable").find(".next").click(function() {
var tolerance = 800,
// The absolute value of the integer associated
// to the top css property
scrollTop = Math.abs(parseInt($("div.items").css("top")));
//If the the distance to the top is greater than the tolerance...
if(scrollTop > tolerance) {
//Do something. Ajax Call, Animations, whatever.
}
}) ;
});
try something like this
$('#scrollable').find('img:last').load(function() {
//load the content
});
OR find the offset location/position of the last image and try loading your content when you reach the offset position on scrolling
HTML :
<div>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<span>Hello !!</span>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br>
</div>
some CSS :
div {
width:200px;
height:200px;
overflow:scroll;
}
Javascript :
$(document).ready(function() {
$('div').scroll(function() {
var pos = $('div').scrollTop();
var offset = $('span').offset().top;
if(pos >= offset ) {
alert('you have reached your destiny');
}
});
});
here's a quick demo http://jsfiddle.net/8QbwU/
Though Demo doesn't met your full requirements, I believe It does give you some light to proceed further :)
First, you'll want to use jQuery for this
Second, put a placeholder on your page to contain your data.
<table id="dataTable" class="someClass" style="border-collapse: collapse;">
<colgroup>
<col width="12%" />
<col width="12%" />
<col width="12%" />
<!-- Define your column widths -->
</colgroup>
</table>
You'll need to code your own GetData method in a webservice, but this is the general idea (And call Refresh(); from your page load)
function Refresh() {
var getData = function(callback, context, startAt, batchSize) {
MyWebservice.GetData(
startAt, //What record to start at (1 to start)
batchSize, //Results per page
3, //Pages of data
function(result, context, method) {
callback(result, context, method);
},
null,
context
);
};
$('#dataTable').scrolltable(getData);
}
The getData function variable is passed into the scrolltable plugin, it will be called as needed when the table is being scrolled. The callback and context are passed in, and used by the plugin to manage the object you are operating on (context) and the asynchronous nature of the web (callback)
The GetData (note the case) webmethod needs to return a JSON object that contains some critical information, how your server side code does this is up to you, but the object this plugin expects is the following. The Prior and Post data are used to trigger when to load more data, basically, you can scroll through the middle/active page, but when you start seeing data in the prior or post page, we're going to need to fetch more data
return new {
// TotalRows in the ENTIRE result set (if it weren't paged/scrolled)
TotalRows = tableElement.Element("ResultCount").Value,
// The current position we are viewing at
Position = startAt,
// Number of items per "page"
PageSize = tableElement.Element("PageSize").Value,
// Number of pages we are working with (3)
PageCount = tableElement.Element("PageCount").Value,
// Data page prior to active results
PriorData = tbodyTop.Html(),
// Data to display as active results
CurrentData = tbodyCtr.Html(),
// Data to display after active results
PostData = tbodyBot.Html()
};
Next is the plugin itself
/// <reference path="../../js/jquery-1.2.6.js" />
(function($) {
$.fn.scrolltable = function(getDataFunction) {
var setData = function(result, context) {
var timeoutId = context.data('timeoutId');
if (timeoutId) {
clearTimeout(timeoutId);
context.data('timeoutId', null);
}
var $table = context.find("table");
var $topSpacer = $table.find('#topSpacer');
var $bottomSpacer = $table.find('#bottomSpacer');
var $newBodyT = $table.children('#bodyT');
var $newBodyC = $table.children('#bodyC');
var $newBodyB = $table.children('#bodyB');
var preScrollTop = context[0].scrollTop;
$newBodyT.html(result.PriorData);
$newBodyC.html(result.CurrentData);
$newBodyB.html(result.PostData);
var rowHeight = $newBodyC.children('tr').height() || 20;
var rowCountT = $newBodyT.children('tr').length;
var rowCountC = $newBodyC.children('tr').length;
var rowCountB = $newBodyB.children('tr').length;
result.Position = parseInt(result.Position);
$newBodyC.data('firstRow', result.Position);
$newBodyC.data('lastRow', (result.Position + rowCountC));
context.data('batchSize', result.PageSize);
context.data('totalRows', result.TotalRows);
var displayedRows = rowCountT + rowCountC + rowCountB;
var rowCountTopSpacer = Math.max(result.Position - rowCountT - 1, 0);
var rowCountBottomSpacer = result.TotalRows - displayedRows - rowCountTopSpacer;
if (rowCountTopSpacer == 0) {
$topSpacer.closest('tbody').hide();
} else {
$topSpacer.closest('tbody').show();
$topSpacer.height(Math.max(rowCountTopSpacer * rowHeight, 0));
}
if (rowCountBottomSpacer == 0) {
$bottomSpacer.closest('tbody').hide();
} else {
$bottomSpacer.closest('tbody').show();
$bottomSpacer.height(Math.max(rowCountBottomSpacer * rowHeight, 0));
}
context[0].scrollTop = preScrollTop; //Maintain Scroll Position as it sometimes was off
};
var onScroll = function(ev) {
var $scrollContainer = $(ev.target);
var $dataTable = $scrollContainer.find('#dataTable');
var $bodyT = $dataTable.children('tbody#bodyT');
var $bodyC = $dataTable.children('tbody#bodyC');
var $bodyB = $dataTable.children('tbody#bodyB');
var rowHeight = $bodyC.children('tr').height();
var currentRow = Math.floor($scrollContainer.scrollTop() / rowHeight);
var displayedRows = Math.floor($scrollContainer.height() / rowHeight);
var batchSize = $scrollContainer.data('batchSize');
var totalRows = $scrollContainer.data('totalRows');
var prevRowCount = $bodyT.children('tr').length;
var currRowCount = $bodyC.children('tr').length;
var postRowCount = $bodyB.children('tr').length;
var doGetData = (
(
(currentRow + displayedRows) < $bodyC.data('firstRow') //Scrolling up
&& (($bodyC.data('firstRow') - prevRowCount) > 1) // ...and data isn't already there
)
||
(
(currentRow > $bodyC.data('lastRow')) //Scrolling down
&& (($bodyC.data('firstRow') + currRowCount + postRowCount) < totalRows) // ...and data isn't already there
)
);
if (doGetData) {
var batchSize = $scrollContainer.data('batchSize');
var timeoutId = $scrollContainer.data('timeoutId');
if (timeoutId) {
clearTimeout(timeoutId);
$scrollContainer.data('timeoutId', null);
}
timeoutId = setTimeout(function() {
getDataFunction(setData, $scrollContainer, currentRow, batchSize);
}, 50);
$scrollContainer.data('timeoutId', timeoutId);
}
};
return this.each(function() {
var $dataTable = $(this);
if (!getDataFunction)
alert('GetDataFunction is Required');
var batchSize = batchSize || 25;
var outerContainerCss = outerContainerCss || {};
var defaultContainerCss = {
overflow: 'auto',
width: '100%',
height: '200px',
position: 'relative'
};
var containerCss = $.extend({}, defaultContainerCss, outerContainerCss);
if (! $dataTable.parent().hasClass('_outerContainer')) {
$dataTable
.wrap('<div class="_outerContainer" />')
.append($('<tbody class="spacer"><tr><td><div id="topSpacer" /></td></tr></tbody>'))
.append($('<tbody id="bodyT" />'))
.append($('<tbody id="bodyC" />'))
.append($('<tbody id="bodyB" />'))
.append($('<tbody class="spacer"><tr><td><div id="bottomSpacer" /></td></tr></tbody>'));
}
var $scrollContainer = $dataTable.parent();
$scrollContainer
.css(containerCss)
.scroll(onScroll);
getDataFunction(setData, $scrollContainer, 1, batchSize);
});
};
})(jQuery);
You'll likely need to tweak some things. I just converted it to a jQuery plugin and it's probably still a little glitchy.

Variable scope issue in JavaScript

I have quickly coded up a sort of product display thing that gets half of its input from the page, and the other half from an AJAX query.
Here is the code...
function productDisplay() {
products = [];
this.index = 0;
setupProductDisplay();
processListItems();
showProduct();
function setupProductDisplay() {
var productInfoBoxHtml = '<div id="product-info"><h3 class="hide-me"></h3><span id="dimensions" class="hide-me"></span><div id="product-gallery"><img alt="" src="" /></div><ul id="product-options" class="hide-me"><li id="spex-sheet">Download full spex sheet</li><li id="enlarge-image">Enlarge image</li></ul><div id="product-description" class="hide-me"></div><span id="top"></span><span id="bottom"></span><span id="side"></span><span class="loading"></span></div>';
$('#products').after(productInfoBoxHtml);
}
function processListItems() {
$('#products > li')
.append('<span class="product-view">View</span>')
.filter(':even')
.addClass('even')
.end()
.each(function() {
products.push({
id: $(this).find('h3').html(),
title: $(this).find('h3').html(),
dimensions: $(this).find('.dimensions').html(),
description: $(this).find('.product-description').html()
});
})
.find('.product-view')
.click(function() {
var $thisListItem = $(this).parents('ul li');
var index = $('#products > li').index($thisListItem);
this.index = index;
showProduct();
});
};
function showProduct() {
var index = this.index;
console.log('INDEX = ' + index);
// hide current data
$('#product-info')
.show()
.find('.hide-me, #product-gallery')
.hide()
.parent()
.find('.loading')
.show();
// get data contained in the page
$('#product-info')
.find('h3')
.html(products[index].title)
.parent()
.find('#dimensions')
.html(products[index].dimensions)
.parent()
.find('#product-description')
.html(products[index].description)
// get id & then product extra info
var id = $('#products > li').eq(index).attr('id').replace(/id-/, '');
var downloadPath = PATH_BASE + 'downloads/';
var imagePath = PATH_BASE + 'images/products/'
$.getJSON(PATH_BASE + 'products/get/' + id + '/',
function(data){
var file = '';
var images = [];
file = data.file;
images = data.images;
// show file list item if there is a file
if (file) {
$('#spex-sheet').show().find('a').attr( { href: downloadPath + file } );
} else {
$('#spex-sheet').hide();
}
// image gallery
if (images.length != 0) {
$('#product-gallery').show();
// preload image thumbnails
$.each(images, function(i, image){
var img = new Image();
img.src = imagePath + 'thumb-' + image;
img = null;
});
// set first image thumbail and enlarge link
if (images[0]) {
$('#enlarge-image').show().find('a').attr({ href: imagePath + images[0] });
$('#product-gallery img').attr ( { src: imagePath + 'thumb-' + images[0]} )
}
console.log(images);
// setup gallery
var currentImage = 0;
clearInterval(cycle);
console.log(cycle);
var cycle = setInterval(function() {
console.log(currentImage + ' = ' + index);
if (currentImage == images.length - 1) {
currentImage = 0;
} else {
currentImage ++;
};
var obj = $('#product-gallery');
var imageSource = imagePath + 'thumb-' + images[currentImage];
obj.css('backgroundImage','url(' + imageSource +')');
obj.find('img').show().fadeOut(500, function() { $(this).attr({src: imageSource}) });
$('#enlarge-image a').attr({ href: imagePath + images[currentImage] });
}, 5000);
// setup lightbox
$("#enlarge-image a").slimbox({/* Put custom options here */}, null, function(el) {
return (this == el) || ((this.rel.length > 8) && (this.rel == el.rel));
});
} else {
// no images
$('#enlarge-image').hide();
$('#product-gallery').hide();
};
// show the product info
$('#product-info')
.find('.hide-me')
.remove('#product-gallery, #spex-sheet')
.show()
.parent()
.find('.loading')
.hide();
});
};
};
The important function is showProduct(). Now generally I don't write JS like this, but I decided to give it a go. My problem is, that when a user clicks a 'more' button, and it displays the prouduct, it doesn't reset the simple slideshow (the images var is reset, I think it has to do with the setInterval() maybe, or it seems it's making a new instance of showProduct() everytime).
Does anyone know what I'm doing wrong?
I had to reformat your code to really understand what was going on. Anyway, I found the problem with the code.
As you guessed correctly, problem is with the scope but not with the variable 'images' but with variable 'cycle'. Why?
This line
var cycle = setInterval(function() {
Always creates a new local cycle variable (notice the 'var') which is not accessible when showProduct gets called the second time. This means that this line
clearInterval(cycle);
is essentially useless as it always passes null to the clearInterval function and doesn't clear anything. This means that as you keep clicking on 'more', you are creating more and more setInterval function calls, never clearing the old ones.
Anyway, I have refactored your code a little bit, I think this should work as expected. The changes I did are:
Removed this.index variable. It's better to pass 'index' to showProduct instead of setting this.index before showProduct method call and making showProduct use that variable. Also, why did you prefix the variable with 'this'?
Declared cycler variable outside the scope of showProduct, local to the productDisplay method. This insures that you can access cycler during different showProduct calls.
Created smaller functions named showFile, showGallery, showProductInfo to make it easier to understand/maintain code.
Let me know if you have any questions OR if the code still doesn't work.
function productDisplay() {
//Instead of keeping this.index variable, it's better to make showProduct function
//take index variable.
products = [];
setupProductDisplay();
processListItems();
//We have to define cycler outside the showProduct function so that it's maintained
//in between showProduct calls.
var cycler = null;
showProduct(0);
function setupProductDisplay()
{
var productInfoBoxHtml = '<div id="product-info"><h3 class="hide-me"></h3><span id="dimensions" class="hide-me"></span><div id="product-gallery"><img alt="" src="" /></div><ul id="product-options" class="hide-me"><li id="spex-sheet">Download full spex sheet</li><li id="enlarge-image">Enlarge image</li></ul><div id="product-description" class="hide-me"></div><span id="top"></span><span id="bottom"></span><span id="side"></span><span class="loading"></span></div>';
$('#products').after(productInfoBoxHtml);
}
function processListItems()
{
$('#products > li')
.append('<span class="product-view">View</span>')
.filter(':even')
.addClass('even')
.end()
.each(
function()
{
products.push({
id: $(this).find('h3').html(),
title: $(this).find('h3').html(),
dimensions: $(this).find('.dimensions').html(),
description: $(this).find('.product-description').html()
});
})
.find('.product-view')
.click( function()
{
var $thisListItem = $(this).parents('ul li');
showProduct($('#products > li').index($thisListItem));
}
);
};
function showFile(file)
{
if (file)
{
$('#spex-sheet').show().find('a').attr( { href: downloadPath + file } );
}
else
{
$('#spex-sheet').hide();
}
}
function showGallery(images)
{
if(! images || !images.length || images.length == 0)
{
$('#enlarge-image').hide();
$('#product-gallery').hide();
return;
}
$('#product-gallery').show();
$.each(images,
function(i, image)
{
var img = new Image();
img.src = imagePath + 'thumb-' + image;
img = null;
});
// set first image thumbail and enlarge link
if (images[0])
{
$('#enlarge-image').show().find('a').attr({ href: imagePath + images[0] });
$('#product-gallery img').attr ( { src: imagePath + 'thumb-' + images[0]} )
}
var currentImage = 0;
clearInterval(cycler);
cycler = setInterval(
function()
{
currentImage = currentImage == images.length - 1 ? 0 : currentImage++;
var obj = $('#product-gallery');
var imageSource = imagePath + 'thumb-' + images[currentImage];
obj.css('backgroundImage','url(' + imageSource +')');
obj.find('img').show().fadeOut(500, function() { $(this).attr({src: imageSource}) });
$('#enlarge-image a').attr({ href: imagePath + images[currentImage] });
}, 5000);
$("#enlarge-image a").slimbox({/* Put custom options here */}, null, function(el) {
return (this == el) || ((this.rel.length > 8) && (this.rel == el.rel));
});
};
function showProductInfo()
{
$('#product-info')
.find('.hide-me')
.remove('#product-gallery, #spex-sheet')
.show()
.parent()
.find('.loading')
.hide();
}
function showProduct(index)
{
$('#product-info')
.show()
.find('.hide-me, #product-gallery')
.hide()
.parent()
.find('.loading')
.show();
// get data contained in the page
$('#product-info')
.find('h3')
.html(products[index].title)
.parent()
.find('#dimensions')
.html(products[index].dimensions)
.parent()
.find('#product-description')
.html(products[index].description)
// get id & then product extra info
var id = $('#products > li').eq(index).attr('id').replace(/id-/, '');
var downloadPath = PATH_BASE + 'downloads/';
var imagePath = PATH_BASE + 'images/products/'
$.getJSON(PATH_BASE + 'products/get/' + id + '/',
function(data)
{
showFile(data.file);
showGallery(data.image);
showProductInfo();
});
};
};
If you don't define your variables with var (e.g. var images = ...;) then they will be considered global variables (members of the window object).
If you define them with var then they are visible to the whole function (even before the variable is declared) they are declared in.
I can't immediately see what the problem is, but I would recommend minimizing the scope of your variables - if they don't need to be global then make sure they aren't global.

Categories