vtip jQuery to display titles - javascript

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");
});
}
});
});

Related

I wrote a closure to add draggable elements to a map. I expected the enclosre to preserve values. It doesn't. Why not?

I want to annotate a route on a map using vanilla javascript. To that end, I have a canvas element, a draw routine to 'connect' divs to points on the map, a closure to construct small divs, and another closure to make the divs draggable, so they can be more conveniently positioned over the map. In both closures, I'm having what I think is the same problem.
js & HTML to show the problem with the make-it-draggable closure, makeDraggable, are below. The outermost function declares a variable, dragThis. The dragstart handler assigns it value. onDragOver and onDrop use it. The debugger says dragThis is in the same makeDraggable closure at every use. I expected this approach to simplify the overall structure and efficiency of the code.
The problem: When the drag and drop handlers fire, dragThis doesn't have the value assigned in dragStart. Why?(???) (In fact, the value in the drag and drop handlers seems to be the id of the element in the first call to makedraggable by test.)
Associated questions:
I use the setData/getData methods of the dataTransfer object. For the life of me, i don't understand the first argument of those functions, 'format'. It seems to have nothing whatever to do with 'format' (save that it changes 'text' to 'text/plain' internally), and everything to do with identifying a datum. The value of 'format' can be just about any string. From the W3: *The API does not enforce the use of MIME types; other values can be used as well.* 'name' would seem to be a more appropriate, um, name. Am i missing something? Shouldn't ducks be called ducks? This hardly seems deserving of a separate question, but I'm curious and don't know how else to ask.
MDN says the event object's pageX, pageY "...include any portion of the page not currently visible." They do not seem to include non-visible portions of scrolled elements, so that seems false. What am i missing? (And does my pageXY function correctly calculate a position that does take those invisible bits properly into account? It seems to work, but my example may be too simple.)
Firefox and fiddle seem happy with my code. Chrome, though, at drop time in placeEl, thinks 'el' in 'el.style.left =...' is null: TypeError: Cannot read property 'style' of null.... Nonetheless, it is happy with the next line, and magically goes on to properly position the div.
I put the code at https://jsfiddle.net/HerbX/g7zv1ok2/1/ Maybe its still there. I've hardwired illustrative divs into the html.
Apparently having referenced the fiddle, I need to put the code here as well:
var MYSTUFF = {
makeDraggable:function(el, elChart) {
var dragThis = el;
var ops = {capture:true, useCapture:true};
el.draggable = true;
el.addEventListener('dragstart', onDragStart, ops);
console.log("dragstart listener added to " + el.id);
if (elChart.dataset.dragover === undefined) {
elChart.addEventListener('dragover', onDragOver, ops);
elChart.addEventListener('drop', onDrop, ops);
elChart.dataset.dragover = 'dragover';
console.log("dragover listener added to " + elChart.id);
}
return el;
function onDragStart(ev) {
var clickXY;
dragThis = ev.target;
clickXY = MYSTUFF.pageXY(ev);
ev.dataTransfer.clearData();
ev.dataTransfer.setData('text/plain',toStr(ev.target.id, ev.target.offsetLeft, ev.target.offsetTop, clickXY.x, clickXY.y));
ev.dataTransfer.setData('foo', ev.target.id);
console.log("dragStart: dragThis.id is " + dragThis.id + ", dT = " + ev.dataTransfer.getData('text/plain'));
}
function onDragOver(ev){
var pos; // new (style.top, style.left)
var canvasid; // canvas, if px, py exist
var params, el;
var foo;
ev.preventDefault();
params = ev.dataTransfer.getData('text/plain').split(';')
foo = ev.dataTransfer.getData('foo');
el = document.getElementById(params[0]);
pos = placeEl(ev, el); // Reposition el by delta(mouse)
console.log("onDragOver: dragThis.id = " + dragThis.id + '; foo = ' + foo);
}
function onDrop(ev) {
var canvasTemp, canvasid, ctx;
var dT, params;
var el, els;
ev.preventDefault();
dT = ev.dataTransfer.getData('text/plain');
params = dT.split(';');
console.log("onDrop event: dragThis.id is " + dragThis.id + ", dT is " + dT);
el = document.getElementById(params[0]);
placeEl(ev,el); //Reposition el, ignore return.
}
function toStr() {
// arguments => ;-delimited string. Args must be scalar numbers.
var delim='';
var s = "";
for (var i = 0; i < arguments.length; i++) {
if (isNaN(arguments[i])) {
s += delim + arguments[i];
} else {
s += delim + arguments[i].toFixed(1);
}
delim = ";";
}
return s;
}
function placeEl(ev,el) {
/* Re-position el by delta(mouse position) */
var params;
var dx, dy;
var pos;
var cursorXY;
params = ev.dataTransfer.getData('text/plain').split(';');
cursorXY = MYSTUFF.pageXY(ev);
dx = cursorXY.x - parseFloat(params[3]);
dy = cursorXY.y - parseFloat(params[4]);
pos = {x:parseFloat(params[1]) + dx, y:parseFloat(params[2]) + dy};
el.style.left = pos.x + 'px';
el.style.top = pos.y + 'px';
return pos;
}
},
reportXY: function(ev) {
let x, y, abs, sAbs;
let msg = document.getElementById('msg');
let el = ev.srcElement;
let id = el.id;
if (id === "") id = el.tagName;
x = event.pageX.toFixed(0);
y = event.pageY.toFixed(0);
abs = MYSTUFF.pageXY(ev);
sAbs = "(" + abs.x + "," + abs.y + ")";
msg.innerText = "In " + id + ", cursor # Page XY: (" + x + "," + y +"). Including scrolls, cursor # " + sAbs;
},
pageXY:function(ev) {
let x = ev.pageX;
let y = ev.pageY;
let scrollX, scrollY, tagName, el;
el = ev.srcElement;
tagName = el.tagName;
scrollX = el.scrollLeft;
scrollY = el.scrollTop;
while (tagName !== 'HTML') {
el = el.parentElement;
tagName = el.tagName;
scrollX += el.scrollLeft;
scrollY += el.scrollTop;
}
return {x:x+scrollX, y:y+scrollY}
}
}
/* test1() only tests makeDraggable. It uses elements hardwired into the HTML.*/
function test1() {
var elChart = document.getElementById('chartPosRef');
var div = document.getElementById('div-0');
MYSTUFF.makeDraggable(div, elChart);
div = document.getElementById('div-1');
MYSTUFF.makeDraggable(div,elChart);
div = document.getElementById('div-2');
MYSTUFF.makeDraggable(div,elChart);
div = document.getElementById('div-3');
MYSTUFF.makeDraggable(div,elChart);
}
window.addEventListener('DOMContentLoaded', (event) => {
window.addEventListener('mousemove', MYSTUFF.reportXY);
test1();
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>connected</title>
<style>
.small {
position:absolute;
width:7em;
background-color:#e6ffe6;
}
.red {
background-color:red;
}
</style>
<script src="js/Q1.js"></script>
</head>
<body>
<div id="chartCtnr" style="text-align:center;">
<div id="chartResz" class="resizeable" style="width:500px;height:500px;display:inline-block">
<div id="chartScroll" style="overflow:auto;height:100%;width:100%;display:inline-block;">
<div id="chartPosRef" class="freezer" style="position:relative">
<canvas id="connectedCanvas" class="red" width="3718" height="2614" title="Track">This to draw the connected track</canvas>
<div id='div-0' class='small' style='top:100px;left:100px;'>this is div-0</div>
<div id='div-1' class='small' style='top:120px;left:080px;'>this is div-1</div>
<div id='div-2' class='small' style='top:140px;left:100px;'>this is div-2</div>
<div id='div-3' class='small' style='top:160px;left:080px;'>this is div-3</div>
</div>
</div>
</div>
</div>
<div id='msg'>this is a message</div>
</body>
</html>
The problem was the 'closure' wasn't a closure. Now it is, after reading https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures a little more carefully, and this works. Note that dragStart (and a couple of other variables) are defined at the top of 'dragger' (as, in effect, are makeDraggable, onDragStart and so on.)
Next to do (but not here cuz i doubt that anyone cares...) is to add variables needed to draw lines connecting the divs to points on the map (non-existent here). Most are closure variables, and setting them will add little to no overhead to the drag operation - largely the point of this exercise.
setData('foo', target.id) remains because I'd still like to know why documentation refers to that first argument as a 'format'.
var MYSTUFF = {
dragger: function() {
var dragThis; // element being dragged
var clickXY; // click-position relative to ULC of scrolled area
var elStartXY; // Initial position of dragThis rel to ULC of scrolled area.
var canvas;
return makeDraggable;
function makeDraggable (el, elChart) {
var ops = {capture:true, useCapture:true};
dragThis = el;
el.draggable = true;
el.addEventListener('dragstart', onDragStart, ops);
if (elChart.dataset.dragover === undefined) {
elChart.addEventListener('dragover', onDragOver, ops);
elChart.addEventListener('drop', onDrop, ops);
elChart.dataset.dragover = 'dragover';
}
return el;
}
function onDragStart(ev) {
dragThis = ev.target;
clickXY = MYSTUFF.pageXY(ev);
elStartXY = {x:ev.target.offsetLeft, y:ev.target.offsetTop};
ev.dataTransfer.setData('foo', ev.target.id);
}
function onDragOver(ev){
var pos; // new (style.top, style.left)
var canvasid; // canvas, if px, py exist
var params, el;
var foo;
ev.preventDefault();
pos = placeEl(ev,dragThis);
}
function onDrop(ev) {
var canvasTemp, canvasid, ctx;
var dT, params;
var el, els;
ev.preventDefault();
placeEl(ev,dragThis);
}
function toStr() {
// arguments => ;-delimited string. Args must be scalar numbers.
var delim='';
var s = "";
for (var i = 0; i < arguments.length; i++) {
if (isNaN(arguments[i])) {
s += delim + arguments[i];
} else {
s += delim + arguments[i].toFixed(1);
}
delim = ";";
}
return s;
}
function placeEl(ev,el) {
/* Re-position el by delta(mouse position) */
var params;
var dx, dy;
var pos;
var cursorXY;
cursorXY = MYSTUFF.pageXY(ev);
dx = cursorXY.x - clickXY.x;
dy = cursorXY.y - clickXY.y;
pos = {x:elStartXY.x + dx, y:elStartXY.y + dy};
el.style.left = pos.x + 'px';
el.style.top = pos.y + 'px';
return pos;
}
}, // end 'dragger'
reportXY: function(ev) {
let x, y, abs, sAbs;
let msg = document.getElementById('msg');
let el = ev.srcElement;
let id = el.id;
if (id === "") id = el.tagName;
x = event.pageX.toFixed(0);
y = event.pageY.toFixed(0);
abs = MYSTUFF.pageXY(ev);
sAbs = "(" + abs.x + "," + abs.y + ")";
msg.innerText = "In " + id + ", cursor # Page XY: (" + x + "," + y +"). Including scrolls, cursor # " + sAbs;
},
pageXY:function(ev) {
let x = ev.pageX;
let y = ev.pageY;
let scrollX, scrollY, tagName, el;
el = ev.srcElement;
tagName = el.tagName;
scrollX = el.scrollLeft;
scrollY = el.scrollTop;
while (tagName !== 'HTML') {
el = el.parentElement;
tagName = el.tagName;
scrollX += el.scrollLeft;
scrollY += el.scrollTop;
}
return {x:x+scrollX, y:y+scrollY}
}
}
/* test1() only tests makeDraggable. It uses elements hardwired into the HTML.*/
function test1() {
md = MYSTUFF.dragger();
var elChart = document.getElementById('chartPosRef');
var div = document.getElementById('div-0');
md(div, elChart);
div = document.getElementById('div-1');
md(div, elChart);
div = document.getElementById('div-2');
md(div, elChart);
div = document.getElementById('div-3');
md(div, elChart);
}
window.addEventListener('DOMContentLoaded', (event) => {
window.addEventListener('mousemove', MYSTUFF.reportXY);
test1();
});

How can I add event listener for an object?

I am having trouble with how to addEventLisener to an object when writing a member function for that object.
I have a class called Grid which has a member function create that creates a HTML div element. Now I want to add an event listener to that div, which waits for click. Here is the code.
Notice that I have another member function update for the Grid.
Grid.prototype.create = function(index) {
var newnode = document.createElement("div");
newnode.setAttribute("class", "grid");
newnode.setAttribute("id", "grid" + index);
newnode.style.width = this.width + "px";
newnode.style.height = this.height + "px";
document.getElementById("whole").appendChild(newnode);
newnode.addEventListener("click", function() {
if (this.live) {
this.live = false;
} else {
this.live = true;
}
this.update(index);
}, false);
this.update(index);
};
The problem is, this represents an HTML DIV Object inside the addEventListener block , but I need it to be a Grid Object so that the update function can be called.
How to solve this problem? Or is there another way to add listener for an object whenever the div that contains it is created?
Just use a variable that is declared outside the scope of the event handler
Grid.prototype.create = function(index) {
var grid = this,
newnode = document.createElement("div");
newnode.className = "grid";
newnode.id = "grid" + index;
newnode.style.width = this.width + "px";
newnode.style.height = this.height + "px";
newnode.addEventListener("click", function() {
grid.live = !grid.live;
grid.update(index);
}, false);
document.getElementById("whole").appendChild(newnode);
this.update(index);
};

How to create a function to center windows

I'm using the jqgrid, and to focus the popup to add, delete and edit, I need to use the parameter beforeShowForm that before this show window, shows the center of the screen. The problem is I have to always do the same code for these three functions.
The function is as follows:
{ // edit option
beforeShowForm: function(form) {
var dlgDiv = $("#editmod" + $(this)[0].id);
var parentDiv = dlgDiv.parent();
var dlgWidth = dlgDiv.width();
var parentWidth = parentDiv.width();
var dlgHeight = dlgDiv.height();
var parentHeight = parentDiv.height();
var parentTop = parentDiv.offset().top;
var parentLeft = parentDiv.offset().left;
dlgDiv[0].style.top = Math.round(parentTop / 2) + "px";
dlgDiv[0].style.left = Math.round(parentLeft + (parentWidth-dlgWidth )/2 ) + "px";
}
},
In order to reuse the same code, I would create a separate function to be always writing the same amount of code. I tried to create the following function:
Function:
function test(dlgDiv)
{
var parentDiv = dlgDiv.parent();
var dlgWidth = dlgDiv.width();
var parentWidth = parentDiv.width();
var dlgHeight = dlgDiv.height();
var parentHeight = parentDiv.height();
var parentTop = parentDiv.offset().top;
var parentLeft = parentDiv.offset().left;
dlgDiv[0].style.top = Math.round(parentTop / 2) + "px";
dlgDiv[0].style.left = Math.round(parentLeft + (parentWidth-dlgWidth )/2 ) + "px";
}
In Grid:
{ // edit option
beforeShowForm: function(form) {
var dlgDiv = $("#editmod" + $(this)[0].id);
test(dlgDiv);
}
},
But continued without giving. Says that the value dlgDiv is not defined. Does anyone know how to solve this?
The issue lies in the selector you use here:
var dlgDiv = $("#editmod" + $(this)[0].id);
Whatever element you're trying to get isn't being returned, because the selector can't find it. You should revise your selector to reflect the ID of the element you're attempting to find.
If you've done that and you still have an issue, the problem lies with your context and this doesn't mean what you think it means in the above line of code. You'll have to show us more code before I can elaborate on that, though.

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

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>');

Javascript - Designpattern suggestion needed

Hallo,
I have 3 Different function in Javascript, the first one replaces HTML Selectboxs width custom selectbox created with ULs.
and the other 2 replace Checkbox and Radio buttons respectivly.
Now I want to derive classes out of these functions, and need your suggestions, what will be the best way to organize these functions into class, whether inheretance is possible?
I really appriciate your help.
Thanks.
Here is some sample code.
function replaceSelect(formid) {
var form = $(formid);
if (!form) return;
invisibleSelectboes = document.getElementsByClassName("optionsDivInvisible");
if (invisibleSelectboes.length > 0) {
for (var i = 0; i < invisibleSelectboes.length; i++) {
document.body.removeChild(invisibleSelectboes[i]);
}
}
var selects = [];
var selectboxes = form.getElementsByTagName('select');
var selectText = "Bitte auswählen";
var selectRightSideWidth = 21;
var selectLeftSideWidth = 8;
selectAreaHeight = 21;
selectAreaOptionsOverlap = 2;
// Access all Selectboxes in Search mask.
for (var cfs = 0; cfs < selectboxes.length; cfs++) {
selects.push(selectboxes[cfs]);
}
// Replace the select boxes
for (var q = 0; q < selects.length; q++) {
if (selects[q].className == "") continue;
var onchangeEvent = selects[q].onchange;
//create and build div structure
var selectArea = document.createElement('div');
var left = document.createElement('div');
var right = document.createElement('div');
var center = document.createElement('div');
var button = document.createElement('a');
// var text = document.createTextNode(selectText);
var text = document.createTextNode('');
center.id = "mySelectText" + q;
if ( !! selects[q].getAttribute("selectWidth")) {
var selectWidth = parseInt(selects[q].getAttribute("selectWidth"));
} else {
var selectWidth = parseInt(selects[q].className.replace(/width_/g, ""));
}
center.style.width = selectWidth + 'px';
selectArea.style.width = selectWidth + selectRightSideWidth + selectLeftSideWidth + 'px';
if (selects[q].style.display == 'none' || selects[q].style.visibility == 'hidden') {
selectArea.style.display = 'none';
}
button.style.width = selectWidth + selectRightSideWidth + selectLeftSideWidth + 'px';
button.style.marginLeft = -selectWidth - selectLeftSideWidth + 'px';
// button.href = "javascript:toggleOptions( + q + ")";
Event.observe(button, 'click', function (q) {
return function (event) {
clickObserver(event, q)
}
}(q));
button.onkeydown = this.selectListener;
button.className = "selectButton"; //class used to check for mouseover
selectArea.className = "selectArea";
selectArea.id = "sarea" + q;
left.className = "left";
right.className = "right";
center.className = "center";
right.appendChild(button);
center.appendChild(text);
selectArea.appendChild(left);
selectArea.appendChild(right);
selectArea.appendChild(center);
//hide the select field
selects[q].style.display = 'none';
//insert select div
selects[q].parentNode.insertBefore(selectArea, selects[q]);
//build & place options div
var optionsDiv = document.createElement('div');
if (selects[q].getAttribute('width')) optionsDiv.style.width = selects[q].getAttribute('width') + 'px';
else optionsDiv.style.width = selectWidth + 8 + 'px';
optionsDiv.className = "optionsDivInvisible";
optionsDiv.id = "optionsDiv" + q;
optionsDiv.style.left = findPosX(selectArea) + 'px';
optionsDiv.style.top = findPosY(selectArea) + selectAreaHeight - selectAreaOptionsOverlap + 'px';
//get select's options and add to options div
for (var w = 0; w < selects[q].options.length; w++) {
var optionHolder = document.createElement('p');
if (selects[q].options[w].className == "informal") {
var optionLink = document.createElement('a');
var optionTxt = document.createTextNode(selects[q].options[w].getAttribute('text'));
optionLink.innerHTML = selects[q].options[w].getAttribute('text');
optionLink.className = "informal";
cic.addEvent(optionLink, 'click', function (event) {
Event.stop(event);
});
Event.observe(optionLink, 'mouseover', function (event) {
Event.stop(event);
});
Event.observe(optionLink, 'mouseout', function (event) {
Event.stop(event);
});
}
else {
var optionLink = document.createElement('a');
var optionTxt = document.createTextNode(selects[q].options[w].text);
optionLink.appendChild(optionTxt);
cic.addEvent(optionLink, 'click', function (id, w, q, onchangeEvent) {
return function () {
showOptions(q);
selectMe(selects[q].id, w, q, onchangeEvent);
}
}(selects[q].id, w, q, onchangeEvent));
}
//optionLink.href = "javascript:showOptions(" + q + "); selectMe('" + selects[q].id + "'," + w + "," + q + ");";
optionHolder.appendChild(optionLink);
optionsDiv.appendChild(optionHolder);
if (selects[q].options[w].selected) {
selectMe(selects[q].id, w, q);
}
}
document.getElementsByTagName("body")[0].appendChild(optionsDiv);
Event.observe(optionsDiv, 'mouseleave', function (submenuid) {
optionsDiv.className = 'optionsDivInvisible'
});
cic.addEvent(optionsDiv, 'click', function (event) {
if (event.stopPropagation) event.stopPropagation();
else event.cancelBubble = true;
});
}
form.setStyle({
visibility: 'visible'
});
}​
From the sounds of it, you're looking to create a unified API to encapsulate all of this "form enhancing" functionality. Possibly something like this:
var formEnhancement = {
SelectBox: function(){ /* ... */ },
CheckBox: function(){ /* ... */ },
RadioButton: function(){ /* ... */ }
};
formEnhancement.SelectBox.prototype = { /* ... define methods ... */ };
// etc. (other prototypes)
// Call something:
var myEnhancedSelectBox = new formEnhancement.SelectBox(
document.getElementById('id-of-a-select-box')
);
Does this answer your query?
I'd go with
var Library = (function()
{
function _selectBox()
{
// stuff
}
function _checkBox()
{
// stuff
}
function _radioButton()
{
// stuff
}
return {
SelectBox : _selectBox,
CheckBox : _checkBox,
RadioButton : _radioButton
};
})();
or
var Library = (function()
{
return {
SelectBox : function()
{
// stuff
},
CheckBox : function()
{
// stuff
},
RadioButton : function()
{
// stuff
}
};
})();
[Edit]
this way, you can actually declare "private" variables that can be accessible only from the library itself, just declaring var foo="bar"; inside Library's declaration, makes a foo variable that can't be accessed from outside, but can be accessed by anything within Library, this is why functions like _selectBox in my example remain private, but can still be accessed through Library.SelectBox, which would be the "public getter"
[/Edit]
also, instead of
var Library = (function(){})();
you could do something like this:
var Library = Library || {};
Library.UI = (function(){})();
this way, you can keep separate parts of your code library, you can keep them in separate files, which don't care about the order in which they are loaded, as long as they have
var Library = Library || {};
on top of them
the functions would then be called like this:
Library.SelectBox();
or in the case you chose to go with "subclasses"
Library.UI.SelectBox();
All the answers are general patterns I think none of them is really helpful. Just because you put your 3 huge function into an object doesn't make your code modular, reusable, maintainable.
So my first suggestion is to utilize function decomposition. You've mentioned inheritance. Now if your code is basically made of this 3 giant functions nothing can be inherited or shared. You should separate function logic by purpose into smaller, more straighforward ones.
A good example is that you've mentioned the word replacing is relevant in all your cases. Maybe you can set up a function that is responsible for DOM replacement independently of the element's type. Such function can be shared between your modules making your code more robust and allowing you to DRY.
The best way to organize this process is called wishful thinking, when you solve your problem with functions which are intuitive and helpful even though they may not even exist. This is related to how you can design effective interaces.
Put the functions in a namespace:
Declare it like this:
FormUtils = {};
and add its properties, which will be your functions
FormUtils.replaceSelect = function () {/*your code*/};
FormUtils.replaceCheckbox = function () {/*your code*/};
FormUtils.replaceRadio = function () {/*your code*/};
then you call this functions with their namespace:
FormUtils.replaceSelect();
This is a simple and very accepted design pattern to javascript

Categories