October 17, 2007

Portable Image Viewer

Like I said, I planned to work on some fun applications of the YUI Anywhere idea.

I sat down after school for a minute and decided it would be cool to be able to click a button on my bookmarks bar which would give me a slideshow of all the images on any web page. Then I discovered the yui-loader, which is designed to load the other yui modules, such as the animation utility. The yui-loader is still in beta phase, but it works fine in Firefox.

Here is what I came up with: (working example)

var DW = (typeof DW == 'undefined') ? {} : DW;

/* When someone clicks the bookmark, if the class is already defined, it will display the viewer */
if(typeof DW.imageViewer != 'undefined') {
DW.imageViewer.show();
} else {
DW.imageViewer = function() {

var Event;
var Anim;
var Dom;
var Overlay;

var _item;
var _image;
var _src;
var _current;
var _imageViewer;

var init = function() {
Event = YAHOO.util.Event;
Anim = YAHOO.util.Anim;
Dom = YAHOO.util.Dom;
Overlay = YAHOO.widget.Overlay;

_src = Dom.batch(_item, function(o){
return o.src;
});

_current = 0;

_imageViewer = new Overlay('imageViewer', {
effect: {effect:YAHOO.widget.ContainerEffect.FADE,duration:0.30},
zIndex: 90,
width: Dom.getViewportWidth() + 'px',
height: Dom.getDocumentHeight() + 'px',
visible: false,
x: 0,
y: 0
});

setUpHeader();
setUpBody();

_imageViewer.render(document.body);

setStyles('imageHolder', {
'width': '100%',
'textAlign': 'center',
'marginTop': '10px'
});

setStyles(_image, {
'padding': '5px',
'border': '1px solid #7E7E7E'
});

setStyles('imageViewer', {
'textAlign': 'center',
'backgroundColor': '#000'
});

Event.on(_image, 'click', linked);
Event.on(_image, 'mouseover', checkLinked);

setUpButtons();
putCurrentImage();
show();
};

var setUpHeader = function() {

var closeButton = newEl({
'el': 'input',
'id': 'closeButton',
'type': 'button',
'value': 'close'
});

_imageViewer.appendToHeader(closeButton);

var headerButtons = newEl({
'el': 'div',
'id': 'headerButtons'
});

var prevButton = newEl({
'el': 'input',
'type': 'button',
'value': 'previous',
'id': 'previousButton'
});

var nextButton = newEl({
'el': 'input',
'type': 'button',
'value': 'next',
'id': 'nextButton'
});

headerButtons.appendChild(prevButton);
headerButtons.appendChild(nextButton);

_imageViewer.appendToHeader(headerButtons);
};

var setUpBody = function() {
var imageHolder = newEl({
'el': 'div',
'id': 'imageHolder'
});

_image = newEl({
'el': 'img',
'id': 'currentImage',
'src': _src[0]
});

imageHolder.appendChild(_image);

_imageViewer.appendToBody(imageHolder);
};

var newEl = function(attrs) {
var el = document.createElement(attrs.el);
for(var i in attrs) {
el[i] = attrs[i];
};
return el;
};

var setStyles = function(el, style) {
for(var i in style) {
Dom.setStyle(el, i, style[i]);
}
};

var setUpButtons = function() {
var b = ['previousButton', 'nextButton', 'closeButton'];
Event.on(b[0], 'click', changeImage, {delta: -1});
Event.on(b[1], 'click', changeImage, {delta: 1});
Event.on(b[2], 'click', hide);

setStyles(b[2], {
'position': 'absolute',
'top': '10px',
'left': '10px'
});

setStyles('headerButtons', {
'marginTop': '10px'
});

setStyles(b, {
'color': '#2C2C2C',
'border': '1px solid #2C2C2C',
'backgroundColor': '#000',
'padding': '5px',
'margin': '5px'
});

Event.on(b, 'mouseover', function() {
Dom.setStyle(b, 'color', '#FFF');
Dom.setStyle(b, 'border', '1px solid #FFF');
});

Event.on(b, 'mouseout', function() {
Dom.setStyle(b, 'color', '#2C2C2C');
Dom.setStyle(b, 'border', '1px solid #2C2C2C');
});
};

var changeImage = function(e, o) {
_current += o.delta;

if(_current < 0) {
_current = (_src.length - 1);
}
else if(_current > (_src.length - 1)) {
_current = 0;
}
putCurrentImage();
};

var putCurrentImage = function() {
var fadeOut = new Anim(_image, {
opacity: { to: 0 }
}, 0.25, YAHOO.util.Easing.easeOut);
fadeOut.onComplete.subscribe(putCurrentImageHelper);
fadeOut.animate();
};

var putCurrentImageHelper = function() {
_image.src = _src[_current];

var fadeIn = new Anim(_image, {
opacity: { to: 1 }
}, 0.25, YAHOO.util.Easing.easeOut);

fadeIn.animate();
};

var flashBack = function() {
var flashBack = new YAHOO.util.ColorAnim(_image, {
backgroundColor: { to: '#000'}
}, 0.30);
flashBack.animate();
};

var getPar = function() {
return Dom.getAncestorByTagName(_item[_current], 'A');
};

var checkLinked = function() {
var found = false;
var par = getPar();
if(par !== null) {
for(var i = 0; i < extensionTest.length; i++) {
if(extensionTest[i].test(par.href)) {
found = true;
}
}
if(!found) {
/* yellow */
var flashYellow = new YAHOO.util.ColorAnim(_image, {
backgroundColor: { to: '#FAEB31'}
}, 0.30);
flashYellow.onComplete.subscribe(flashBack);
flashYellow.animate();
}
else {
/* green */
var flashGreen = new YAHOO.util.ColorAnim(_image, {
backgroundColor: { to: '#54A14E'}
}, 0.30);
flashGreen.onComplete.subscribe(flashBack);
flashGreen.animate();
}
}
else {
/* blue */
var flashBlue = new YAHOO.util.ColorAnim(_image, {
backgroundColor: { to: '#06e'}
}, 0.30);
flashBlue.onComplete.subscribe(flashBack);
flashBlue.animate();
}
};

var extensionTest = [new RegExp('.jpg$', 'i'), new RegExp('.gif$', 'i'), new RegExp('.png$', 'i'), new RegExp('.jpeg$', 'i')];

var linked = function() {
var found = false;
var par = getPar();
if(par !== null) {
for(var i = 0; i < extensionTest.length; i++) {
if(extensionTest[i].test(par.href)) {
found = true;
}
}
if(!found) {
window.location = par.href;
}
else {
_image.src = par.href;
}
}
else {
/* blue */
var flashBlue = new YAHOO.util.ColorAnim(_image, {
backgroundColor: { to: '#06e'}
}, 0.30);
flashBlue.onComplete.subscribe(flashBack);
flashBlue.animate();
}
};

var stripPx = function(str) {
return parseInt(str.substring(0, str.indexOf('p')));
};

/* resizeImage is not used */
var resizeImage = function(ratio) {
var w = stripPx(Dom.getStyle(_item[_current], 'width')) * ratio;
var h = stripPx(Dom.getStyle(_item[_current], 'height')) * ratio;
Dom.setStyle(_item[_current], 'width', w + 'px');
Dom.setStyle(_item[_current], 'height', h + 'px');
};

var show = function() {
Dom.setStyle('imageViewer', 'display', '');
_imageViewer.show();
};

var hide = function() {
Dom.setStyle('imageViewer', 'display', 'none');
_imageViewer.hide();
};

var loaded = function() {
_item = YAHOO.util.Dom.getElementsBy(function(){return true;},'img');
if(_item.length !== 0) {
init();
}
};

return function() {
loader = new YAHOO.util.YUILoader();
loader.require('animation', 'container');
loader.loadOptional = true;
loader.insert(loaded);

return {
show : show,
hide : hide
};
}();
}();
}
Don't let me hog all the fun! Just drag these links to your bookmarks bar, or make a bookmarks group for "YUI Anywhere"; that may be a wise approach, because I feel that these types of things are going to get very popular.

YUI Loader

DW.imageViewer

Here is a direct link to the bookmark page.

Originally I wanted to only have to click one button, but it turned out to be complicated. Plus, the two buttons provide greater extendability. The first button prepares the loader to load in the scripts that other modules will need. Then, the DW.imageViewer button will simply load itself, nothing more.

In the code, you may notice that all quotes are single quotes or escaped single quotes, this was for when I minified it to make it a link.

DW.imageViewer :
I thought I would list some features:
  • It sets the mood by fading in a dark screen.
  • If there are no images on the page, it bluntly does nothing, bluntly.
  • It allows you next/previous your way through each image on the screen.
  • When you hover the mouse over the image it will flash one of three colors:
  • Green, the image was inside of a link
  • Clicking a green flashing image will load the linked image into the viewer
  • Yellow: The image links to an external page
  • Clicking a yellow flashing image will go to that page
  • Blue: When there is no link, the image will flash blue
  • To close the viewer, click close in the top left
  • To reopen it, click the bookmark again.
There you have it.

October 15, 2007

YUI Anywhere

This is among the coolest items I have lately seen. My buddy Mike G. showed it to me, and it was originally posted on phpied.com. Now, you can bring your web2 tool kit with you everywhere.
(function(){
var yui = document.createElement('script');
yui.src='http://yui.yahooapis.com/2.3.1/build/utilities/utilities.js';
yui.type='text/javascript';
document.getElementsByTagName('head')[0].appendChild(yui);
})();

Drag this to your bookmarks bar: YUI Anywhere

The question is... what can we do with this?

The famous example: Make everything draggable:
var all = document.getElementsByTagName('*');

for(var i = 0; i < all.length; i++) {
new YAHOO.util.DD(all[i]);
}
The point is to run this code in firebug after clicking your new YUI Anywhere button.

I am currently working on a fun example of what can be done with YUI Anywhere.

October 14, 2007

My Singleton Instance Dom Hijacker

var DomHijacker = function(el) {

var init = function() {
};

return function() {

Event.onAvailable(el, init);

return {

/* public functions here */

};

}();

}('htmlElementId');

What is so special about this? After all, it looks just like what we are all used to. It uses closure, it uses self invoking functions, and like everything else, it uses the YUI. The only new and cool thing about it is that it is completely self contained. It calls its own init function. Note: A full example of this function is posted here.

The point of the whole thing is that the function will only begin to modify DOM elements after they are ready. Allow me to explain each line:

var DomHijacker = function(el) {
This line simply declares a variable DomHijacker, which will be a function. It takes one parameter, a string called el (element) which is the id of a Dom element on the page.

var init = function() {
};
Within DomHijacker, there is an init function. Init stands for initialize, because this function will run procedures on the Dom element with the id 'el'

return function(){
The function, or 'class' in this case called DomHijacker will return a function. This is called closure. Any objects that are not in this returned function will not be given back so to speak. For instance, you could not try DomHijacker.init() somewhere else. Now, DomHijacker is actually this anonymous function. This function will not be called until someone says DomHijacker(); it will simply be stored in DomHijacker, but not invoked.

Event.onAvailable(el, init);
We will use YUI's event utility to wait for our html element with the id 'el'. You can see how onAvailable works here. When our html element is ready, the init function will be called. Although I do not use it, it is important to note that the keyword 'this' will refer to the Dom element with id 'el' within init after YAHOO.util.Event.onAvailable calls it.

return {
/* public functions here */
};
Let's just perform another closure. Why not. This is the final closure, and will contain any public functions. For example, within the big brackets, we could say:

hide: function (){
Dom.setStyle(el, 'display', 'none');
}
This is a public function which uses YAHOO.util.Dom.setStyle to make our Dom element with id 'el' go away. To used it we would say DomHijacker.hide(); The naming convention of my DomHijacker is a little weird, but I will give a better example below. If I was going to 'hijack' a list and turn it into some sort of dynamic menu, I would have called DomHijacker, CustomMenu. Then saying CustomMenu.hide(); would sound a lot more acceptable.

}();
Of course, if we want init to Event.onAvailable(el, init); to get called, we must invoke the anonymous function which we were returning in the first place. Now that code will get run by JavaScript, and YAHOO.util.Event will start waiting on 'el'. This is good. The whole reason that we have to wait on 'el' is because the JavaScript gets started before the rest of the Dom elements are actually... well... Dom elements (in the Dom). If you are still with me, you are freakin' awesome! If not, well, congrats on even reading this far. Feel free to ask questions.

}('htmlElementId');
This is the final line in the function. Remember how on the first line, DomHijacker wanted a string for the 'el' which is the id of the html element? Well, we are self invoking DomHijacker and at the same time passing it that very string. 'htmlElementId' gets squished into the variable we called 'el' and is available all throughout DomHijacker, our 'class'. Remember? This is the id that onAvailable was waiting for.

The whole idea here is that our big class called DomHijacker wants to modify an html element. We cannot do that until that element is in the Dom; which is why we must wait on it; which is why YUI comes in so handy.

Lets add some functionality to our class.
We'll call it customList. First let's get a namespace to work in.

var DanW = {};
This will help keep things clean.

DanW.customList = function(el) {
I want to make that 'better example' I mentioned earlier.

var Event = YAHOO.util.Event;
var Anim = YAHOO.util.Anim;
var Dom = YAHOO.util.Dom;

var _item;
Establish some shortcuts for ease of typing. Also, make a 'private' variable, with the underscore prefixed naming convention called _item. This will be an array of the LI items in the list on the page. They will be references to the actual Dom nodes or elements.

var init = function() {
_item = Dom.getChildren(el);
Event.on(_item, 'click', action);
};
This is the init function, in all its glory. All we do is get the items in the list called el, and assign a function, action to their respective clickage.

var action = function() {
/* keyword this refers to
the li which was clicked on */
Dom.setStyle(this, 'color', '#FBB104');

var attributes = {};

attributes.fontSize = {
from: 100,
to: 200,
unit: '%'
};

var sizeUp = new Anim(
this,
attributes,
0.2,
YAHOO.util.Easing.easeOutStrong);

sizeUp.animate();
Event.purgeElement(this, false, 'click');
};
If you have been with me so far, I should really not need to explain all this. Although I will say that since I only want the items to be clicked once for the animation, I simply removed the click listener after one click. I did this by calling: Event.purgeElement(this, false, 'click');

Here is the entire function: customList, showing the self invokers at the bottom.

var DanW = {};

DanW.customList = function(el) {

var Event = YAHOO.util.Event;
var Anim = YAHOO.util.Anim;
var Dom = YAHOO.util.Dom;

var _item;

var init = function() {

_item = Dom.getChildren(el);

Event.on(_item, 'click', action);

};

var action = function() {
/* keyword this refers to
the li which was clicked on */
Dom.setStyle(this, 'color', '#FBB104');

var attributes = {};

attributes.fontSize = {
from: 100,
to: 200,
unit: '%'
};

var sizeUp = new Anim(
this,
attributes,
0.2,
YAHOO.util.Easing.easeOutStrong);

sizeUp.animate();
Event.purgeElement(this, false, 'click');
};

return function() {

Event.onAvailable(el, init);

}();

}('customList');
It has been a pleasure explaining this. Feel free to ask questions. A full example of this function is posted here.