Trouble with OOJS - javascript

I have a variable number of sliders on a page. Each slider contains a variable number of slides, and a pair of prev/next buttons to navigate sequentially through its respective slides.
Here is the markup for a typical slider:
<section class="foo">
<button class="prev"></button>
<ul class="container slider">
<li class="slide" id="a01"></li>
<li class="slide" id="a02"></li>
<li class="slide" id="a03"></li>
</ul>
<button class="next"></button>
</section>
Each slider should know the index of the slide it is currently showing. This leads me to believe that on page load, I should loop through all $('.slider') elements and instantiate an object for each. How do I write the class that defines the slider? It should have a property that contains the index of the current slide being shown, and a method that increments or decrements that property depending on whether the user clicked 'next' or 'prev'. I've come as far as this:
var slider = function () {
this.pager = 0;
this.incrementPager = function () {
console.log(this.pager++);
};
};
$(function () {
$('.prev, .next').click(slider);
});
... But my code doesn't do much :)

Here's how to make a jQuery plugin, which I think is a better way since you're using jQuery, which is not really meant for Class based OO.
$.fn.slider = function() {
$(this).each(function() {
var wrapper = $(this);
var index = 0;
var listItems = wrapper.find('.slide');
wrapper.find('.prev').click(function(){
if (index > 0) {
$(listItems[index]).removeClass('active');
index--;
$(listItems[index]).addClass('active');
}
});
wrapper.find('.next').click(function(){
if (index < listItems.length - 1) {
$(listItems[index]).removeClass('active');
index++;
$(listItems[index]).addClass('active');
}
});
});
};
And you could use it like
$('section').slider();
Working Example http://jsfiddle.net/MR8wE/
If you really want some OO in there you could do the following (but I think it goes against how jQuery works).
/**
* Adds behavior to the HTML for a slider
*/
function Slider(wrapper) {
var me = this;
me.wrapper = $(wrapper);
me.index = 0;
me.listItems = wrapper.find('.slide');
wrapper.find('.prev').click(function(){
me.prev();
});
wrapper.find('.next').click(function(){
me.next();
});
}
Slider.prototype.next = function() {
this.go(1;)
}
Slider.prototype.previous = function() {
this.go(-1);
}
Slider.prototype.go = function(steps) {
var oldIndex = this.index;
var newIndex = oldIndex + steps
if (newIndex >= 0 && newIndex < this.listItems.length ) {
$(this.listItems[oldIndex]).removeClass('active');
$(listItems[new]).addClass('active');
this.index = newIndex;
}
}
Then you would use it like
var sliders = [];
$('section').each(function(){
sliders.push(new Slider(this));
});
// Control the slider without user intervention
sliders[0].next();
sliders[1].go(2);
Example http://jsfiddle.net/MR8wE/2/

In response to the question in the comments:
function Slider(elm)
{
this.domElement = elm;
this.pager = 0;
}
Slider.prototype.incrementPager = function()
{
this.pager++;
};//all instances share this method ==> only 1 function object in mem.
$(function()
{
var objectCollection = (function(elements)
{
var toReturn = [];
var scanElementsFunction = function()
{//declared in this scope, to have access to elements var
'use strict';//avoid setting globals
if (this.allElems === undefined)
{
this.allElems = elements;
}
return this.allElems;
};
elements.each(function(idx,el)
{
toReturn[idx] = new Slider(el);//<-- pass DOMElement to constructor
toReturn[idx].getAll = scanElementsFunction;//<-- reference, no new function object
});
return toReturn;
}($('.prev, .next')));//this scope preserves the elements returned by jQuery selector
console.log(objectCollection);
});
There's nothing really useful in there, but I tried to incorporate as much tricks and tips while keeping the snippet as short as possible. If some of the code is unclear, let me know and I'll provide some more explanation (which might take an hour or two, 'cause I'm going to grab a bite now :))

The following approach uses the more formal constructor function and prototype which keeps it quite lightweight rather than stacking enclosures. The constructor is...
function Slider(slides, transition) {
// Keeps a reference to the current slide index
this._current = 0;
// An array of slides
this._slides = slides;
// A transition function
this._transition = transition;
}
It accepts an array of slides and a function which will be given two slides when we transition between them. This means we keep control of our transition effect by externalizing it. It is also framework agnostic and has no dependencies to jQuery or any other framework. The constructor itself doesn't do much. The following method is our meat...
// The function that swaps slides
Slider.prototype.goto = function(i) {
// Do a bit of sense checking
if(i > this._slides.length || i < 0)
throw new Error("Slide does not exist");
// Swap the slides by passing them to the transition function
var currentSlide = this._slides[this._current];
var nextSlide = this._slides[i];
this._transition(currentSlide, nextSlide);
// Update the current index
this._current = i;
};
It takes the new index for a slide and passes the old and the new slide to the transition function. It then updates the index it uses for tracking the current slide. We then want to implement a rotating previous and next function so the following methods describe how we can do that using modulus, note we have to add the length of the slides because negative modulus does not work how we want it to for this function.
// Calculate the next index as a rotating index
Slider.prototype.getNextIndex = function() {
return (this._current + 1) % this._slides.length;
};
// Calculate the previous index as a rotating index
Slider.prototype.getPrevIndex = function() {
return (this._current + this._slides.length - 1) % this._slides.length;
};
Then we add some sugar...
// Sugar to go next and prev
Slider.prototype.next = function() {
this.goto(this.getNextIndex());
};
Slider.prototype.prev = function() {
this.goto(this.getPrevIndex());
};
You may have a problem with associating the prev and next buttons with their sliders. You can find them before and after the slider element or as I have done below have them contained in the slider element. To set up sliders using jQuery you could do the following...
$(".slider").each(function() {
var slider = new Slider($(this).find(".slide"), function(a, b) {
$(a).hide();
$(b).show();
});
$(this).data("slider", slider);
$(this).find(".prev").click(function() {
slider.prev();
});
$(this).find(".next").click(function() {
slider.next();
});
});
EDIT Here is it in action http://jsfiddle.net/w8u69/
And because the logic for transitioning is exposed you can quite easily add in transitioning effects without modifying the original Slider "class". http://jsfiddle.net/w8u69/1/
EDIT Just to show the power in this approach, without modifying the original slider "class" you can add in additional logic to automatically move between slides. You can do this with a decorator, or with inheritance, but this example shows how it can be done with composition. http://jsfiddle.net/w8u69/4/
One last thing and this is possibly the most important thing about the pure OO approach, by simply changing the integration code and keeping the OO "classes" untouched, we can reuse the logic we have written for the slider and plug it into a completely different framework. This fiddle shows it working with MooTools http://jsfiddle.net/w8u69/5/

Related

Best Way to Hide/Show Multiple Surfaces with RenderControllers

What's the best practice to show/hide multiple surfaces simultaneously? Do I group all the surfaces under one render controller? Or, does each surface have a render controller assigned to it?
I am currently doing the latter, but am left with the distinct difficulty of attempting to trigger follow-up transitions. This is hard for me because my implementation doesn't provide a clear indication of when all the surfaces have been hidden. It is even harder because the hide transition is triggered with a bounded random time interval (between 200 to 2000 millisecond).
Any solutions? Code below:
for (var i = 0; i < surfaces.length; i += 1) {
var surface = surfaces[i][0];
var renderController = surfaces[i][1];
if (s.id !== clickedSurface.id) {
var fn = (function (s, rc) {
return function () { Timer.setTimeout(function () {rc.hide()}, getRandomArbitrary(200,2000)); };
})(surface, renderController);
s.colored ? Timer.setTimeout(fn, 2500) : fn();
}
}
If you wanted to iterate through your surfaces and hide one at a time, you could do something like the following code shows.
Example jsBin Here
function _hideNext(index) {
if (index === surfaces.length) {
//do something final, now complete
_showNext(0);
} else {
var rc = surfaces[index][1];
var surface = surfaces[index][0];
var nextIndex = index + 1;
rc.hide(surface, _hideNext.bind(this, nextIndex));
}
}
_hideNext(0);
Just change your RenderController options to your needs.

Self-created fadeIn() function not working correctly

I am trying to create the fadeIn() function using Javascript. I am having trouble, when I click the fadeIn button, it does not perform a fadeIn animation, instead I have to click it several times to fadeIn. Would anyone know how I can fix this issue?
jsFiddle
// Created a jQuery like reference
function $(selector) {
if (!(this instanceof $)) return new $(selector); // if new object is not defined, return new object
this.selector = selector; // setting selector attribute
this.node = document.querySelector(this.selector); // finds single element from the DOM
};
var fInFrom = 0, fOutFrom = 10;
$.prototype.fadeIn = function() {
var target = this.node,
newSetting = fInFrom / 10;
// Set Default styles for opacity
target.style.display = 'block';
target.style.opacity = newSetting;
// fadeInFrom will increment by 1
fInFrom++;
var loopTimer = setTimeout('this.fadeIn', 50);
if (fInFrom === 10) {
target.style.opacity = 1;
clearTimeout(loopTimer);
fInFrom = 0;
return false;
}
return this;
}
$('#fadeIn').node.addEventListener('click', function() {
$('#box').fadeIn();
});
This line is your problem:
setTimeout('this.fadeIn', 50)
That will set a timeout to evaluate the expression this.fadeIn in the global scope in approximately 50 milliseconds from the current time. There's two problems with that:
It's in the global scope; this is window, not an instance of $, so this.fadeIn is undefined.
Even if it were resolved correctly, you're only evaluating this.fadeIn; you're not calling it. You would need to use this.fadeIn() for it to do anything. (If you do that with the current code, this will reveal your first problem.)
To solve this, pass not a string but a function that does what you want it to do. You might naïvely do this:
setTimeout(function() {
this.fadeIn();
}, 50);
Unfortunately, while we now have lexical scoping for variables, this in JavaScript is dynamic; we have to work around that. Since we do have lexical scoping for variables, we can utilize that: [try it]
var me = this; // store the current value of this in a variable
var loopTimer = setTimeout(function() {
me.fadeIn();
}, 50);
After that's solved, you might want to look into:
Not using global variables to hold the fade state. Even after that fix, running two fade animations at once on different elements won't work as expected. (Try it.)
Only setting the timeout if you need to; right now, you always set it and then clear it if you don't need it. You might want to only set it if you need it in the first place.

Swipe.js 2.0 - bind prev and next functions to different Swipe instance callback

I would like two separate elements to slide off a single swipe instance, as far as I'm aware you cant get the touch movement on both at the same time so the solution is to bind the prev and next functions to one of the Swipe callbacks, but I'm not sure how to do this?
If I have two swipe instances, one with a callback:
var slider = new Swipe(document.getElementById('swipe'), {
callback: function(index, elem) {
//WHAT GOES HERE???
}
});
var slider2 = new Swipe(document.getElementById('swipeTitles'));
... how exactly would I bind the second swipe to imitate what the first does?
you could store slider into global variable (this can be achieved by loosing var part) and call next() or prev() manually, based on previously stored current index
var slider = new Swipe(document.getElementById('swipe'), {
callback: function(index, elem) {
if(currentIndex<index)
slider2.next();
else
slider2.prev();
currentIndex = index;
}
});
/*NO VAR HERE!*/
slider2 = new Swipe(document.getElementById('swipeTitles'));

Creating a loop from a series of onMouseOver Events

How can I create a loop out of this function:
window.onload = function makeHalo() {
document.getElementById("d1").onmouseover = function() {
this.id ="d1On";
this.className="hover";
document.getElementById("menu1").style.color="#6DC5E6";
};
document.getElementById("menu1").onmouseover = function() {
this.style.color="#6DC5E6";
document.getElementById("d1").className="hover";
document.getElementById("d1").id="d1On";
};
document.getElementById("d1").onmouseout = function() {
this.id ="d1";
this.className="";
document.getElementById("menu1").style.color="#FFFFFF";
};
document.getElementById("menu1").onmouseout = function() {
this.style.color="#FFFFFF";
document.getElementById("d1On").className="";
document.getElementById("d1On").id="d1";
};
document.getElementById("d2").onmouseover = function() {
this.id ="d2On";
this.className="hover";
document.getElementById("menu2").style.color="#6DC5E6";
};
document.getElementById("menu2").onmouseover = function() {
this.style.color="#6DC5E6";
document.getElementById("d2").className="hover";
document.getElementById("d2").id="d2On";
};
document.getElementById("d2").onmouseout = function() {
this.id ="d2";
this.className="";
document.getElementById("menu2").style.color="#FFFFFF";
};
document.getElementById("menu2").onmouseout = function() {
this.style.color="#FFFFFF";
document.getElementById("d2On").className="";
document.getElementById("d2On").id="d2";
};
}
The function pretty much learns the ID of an image when its hovered, changes the ID of that element, adds a class to the element, and changes the color of another element
The second part learns the ID of a list item when its hovered, changes its color, and changes the ID of the other image element and adds a class to that element as well.
The onmouseout simply resets everything.
On the actual HTML page, it is a menu page with lists. Below there a continent map, which is a background image. When you hover over a list item, it swaps out a point on a map with another picture for an indicator. You can also hover the points on the map to change the color of the links on the lists.
I tried doing something like this, but the loop only goes to the last iteration for some of the elements. The links change color fine, but it will only swap the picture for "d43" regardless of what link I hover over.
window.onload = function makeHalo() {
var i = 1;
for (i=1; i<44; i++) {
var menu = "menu"+i;
var d = "d"+i;
var On = "d"+i+"On";
document.getElementById(d).onmouseover = function() {
this.id = On;
this.className="hover";
document.getElementById(menu).style.color="#6DC5E6";
};
document.getElementById(menu).onmouseover = function() {
this.style.color="#6DC5E6";
document.getElementById(d).className="hover";
document.getElementById(d).id=On;
};
document.getElementById(d).onmouseout = function() {
this.id = d;
this.className="";
document.getElementById(menu).style.color="#FFFFFF";
};
document.getElementById(menu).onmouseout = function() {
this.style.color="#FFFFFF";
document.getElementById(On).className="";
document.getElementById(On).id=d;
};
}
}
Any help will be greatly appreciated.
The primary technical issue you're facing is that you're creating closures in a loop. Each one of those callbacks closes over the same i variable, whose value will be the same for each of the callbacks (its value after the final iteration). This is fixed by wrapping the body of the loop in its own function that receives i as an argument, thus creating a local copy on each iteration.
There are a number of style and performance issues, as well:
The bodies of those callbacks are in many cases exactly the same (the mouseover and mouseout pairs end up dong the same work in each block).
You're retrieving the same elements by ID repeatedly. This is unnecessary; you should save a reference.
You're identifying the state of an element by changing its ID. This isn't generally how you want to handle this. An ID shouldn't change.
I would write it more like this (addressing the closure issue and the first two bullet items above (not addressing the ID problem)):
for (var i = 1; i <= 2; i++) {
(function(i) {
var d = document.getElementById("d" + i);
var menu = document.getElementById("menu" + i);
d.onmouseover = menu.onmouseover = function() {
d.id = "d" + i + "On";
d.className = "hover";
menu.style.color = "#6DC5E6";
};
d.onmouseout = menu.onmouseout = function() {
d.id = "d" + i;
d.className = "";
menu.style.color = "#FFFFFF";
};
})(i);
}
This handles just two elements; simply change the loop max to make it work for more.
You can see a working demo here:
http://jsfiddle.net/ezYtq/
Is your last div in your HTML "d43" or is it "d44"? Your loop will run through d1 through d43 because you have i<44 which means when i is 44 it will exit the loop so it will stop at d43.
If you want it to get to d44, then either change the condition to: i <= 44
or change it to i < 45
By the way is there is reason you are not using jQuery it's design to make things like this much easier, in several ways. Maybe you listed what you were actually trying to accomplish with this code for example whether it's a menu system or something we might be able to suggest better approaches.
No need for JavaScript here... just use the CSS :hover pseudo-class.
But, to answer your question:
Do not change the id of your element. This seems fundamentally wrong. Change, add, or remove a class instead. What are you trying to accomplish by changing the id?
Don't keep track of id's, just keep track of element references directly.
Most importantly, when you are doing your loop, by the time the functions are called, the value of i is 45, for all elements. Solve this by passing i to a function that creates your event handlers:
window.onload = function makeHalo() {
for (var i = 1; i < 44; i++) {
(function (i) {
var menu = document.getElementById("menu" + i);
var d = document.getElementById("d" + i);
function over () {
d.className = "hover";
menu.style.color = "#6DC5E6";
}
d.onmouseover = over;
menu.onmouseover = over;
function out () {
d.className = "";
menu.style.color = "#FFFFFF";
}
d.onmouseout = out;
menu.onmouseout = out;
})(i);
}
}

Write a wrapper object in Javascript

First off, let me apologize if my question isn't worded correctly - I'm not a professional coder so my terminology might be weird. I hope my code isn't too embarrassing :(
I have a fade() method that fades an image in and out with a mouse rollover. I would like to use a wrapper object (I think this is the correct term), to hold the image element and a few required properties, but I don't know how to accomplish this. fade() is called from the HTML, and is designed to be dropped into a page without much additional setup (so that I can easily add new fading images to any HTML), just like this:
<div id="obj" onmouseover="fade('obj', 1);" onmouseout="fade('obj', 0);">
The fade(obj, flag) method starts a SetInterval that fades the image in, and when the pointer is moved away, the interval is cleared and a new SetInterval is created to fade the image out. In order to save the opacity state, I've added a few properties to the object: obj.opacity, obj.upTimer, and obj.dnTimer.
Everything works okay, but I don't like the idea of adding properties to HTML elements, because it might lead to a future situation where some other method overwrites those properties. Ideally, I think there should be a wrapper object involved, but I don't know how to accomplish this cleanly without adding code to create the object when the page loads. If anyone has any suggestions, I would greatly appreciate it!
Here's my fader method:
var DELTA = 0.05;
function fade(id, flag) {
var element = document.getElementById(id);
var setCmd = "newOpacity('" + id + "', " + flag + ")";
if (!element.upTimer) {
element.upTimer = "";
element.dnTimer = "";
}
if (flag) {
clearInterval(element.dnTimer);
element.upTimer = window.setInterval(setCmd, 10);
} else {
clearInterval(element.upTimer);
element.dnTimer = window.setInterval(setCmd, 10);
}
}
function newOpacity(id, flag) {
var element = document.getElementById(id);
if (!element.opacity) {
element.opacity = 0;
element.modifier = DELTA;
}
if (flag) {
clearInterval(element.dnTimer)
element.opacity += element.modifier;
element.modifier += DELTA; // element.modifier increases to speed up fade
if (element.opacity > 100) {
element.opacity = 100;
element.modifier = DELTA;
return;
}
element.opacity = Math.ceil(element.opacity);
} else {
clearInterval(element.upTimer)
element.opacity -= element.modifier;
element.modifier += DELTA; // element.modifier increases to speed up fade
if (element.opacity < 0) {
element.opacity = 0;
element.modifier = DELTA;
return;
}
element.opacity =
Math.floor(element.opacity);
}
setStyle(id);
}
function setStyle(id) {
var opacity = document.getElementById(id).opacity;
with (document.getElementById(id)) {
style.opacity = (opacity / 100);
style.MozOpacity = (opacity / 100);
style.KhtmlOpacity = (opacity / 100);
style.filter = "alpha(opacity=" + opacity + ")";
}
}
You are right, adding the handlers in your HTML is not good. You also loose the possible to have several handlers for event attached to one object.
Unfortunately Microsoft goes its own way regarding attaching event handlers. But you should be able to write a small wrapper function to take care of that.
For the details, I suggest you read quirksmode.org - Advanced event registration models.
An example for W3C compatible browsers (which IE is not): Instead of adding your event handler in the HTML, get a reference to the element and call addEventListener:
var obj = document.getElementById('obj');
obj.addEventListener('mouseover', function(event) {
fade(event.currentTarget, 1);
}, false);
obj.addEventListener('mouseout', function(event) {
fade(event.currentTarget, 0);
}, false);
As you can see I'm passing directly a reference to the object, so in you fade method you already have a reference to the object.
You could wrap this in a function that accepts an ID (or reference) and every time you want to attach an event handler to a certain element, you can just pass the ID (or reference) to this function.
If you want to make your code reusable, I suggest to put everything into an object, like this:
var Fader = (function() {
var DELTA = 0.05;
function newOpacity() {}
function setStyle() {}
return {
fade: function(...) {...},
init: function(element) {
var that = this;
element.addEventListener('mouseover', function(event) {
that.fade(event.currentTarget, 1);
}, false);
element.addEventListener('mouseout', function(event) {
that.fade(event.currentTarget, 0);
}, false);
}
};
}())
Using an object to hold your functions reduces pollution of the global namespace.
Then you could call it with:
Fader.init(document.getElementById('obj'));
Explanation of the above code:
We have an immediate function (function(){...}()) which means, the function gets defined and executed (()) in one go. This function returns an object (return {...};, {..} is the object literal notation) which has the properties init and fade. Both properties hold functions that have access to all the variables defined inside the immediate function (they are closures). That means they can access newOpacity and setStyle which are not accessible from the outside. The returned object is assigned to the Fader variable.
This doesn't directly answer your question but you could use the jQuery library. It's simple, all you have to do is add a script tag at the top:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js">
Then your div would look like:
<div id="obj" onmouseover="$('#obj').fadeIn()" onmouseout="$('#obj').fadeOut()">
jQuery will handle all the browser dependencies for you so you don't have to worry about things like differences between firefox and mozilla etc...
If you want to keep your HTML clean, you should consider using JQuery to set up the events.
Your HTML will look like this:-
<div id="obj">
Your JavaScript will look "something" like this:-
$(document).ready(function() {
$("#obj").mouseover(function() {
Page.fade(this, 1);
}).mouseout(function(){
Page.fade(this, 0);
});
});
var Page = new function () {
// private-scoped variable
var DELTA = 0.05;
// public-scoped function
this.fade = function(divObj, flag) {
...
};
// private-scoped function
var newOpacity = function (divObj, flag) {
...
};
// private-scoped function
var setStyle = function (divObj) {
...
};
};
I introduced some scoping concept in your Javascript to ensure you are not going to have function overriding problems.

Categories

Resources