Preloading a background-image with a temporary gif image - javascript

I have a couple of divs with background images. I would like to know how I can preload those background-images with a gif image since some of the background images are quite large. Doing the following does not work:
HTML:
<div id="glykopeels" onload="loadImage()">Glykopeels Content</div>
<div id="facials" onload="loadImage2()">Facials Content</div>
CSS:
#glykopeels{
background: #ebebeb url(http://lamininbeauty.co.za/images/products/preloader.gif) no-repeat top right;
background-size: contain;
}
#facials{
background: #ebebeb url(http://lamininbeauty.co.za/images/products/preloader.gif) no-repeat top right;
background-size: contain;
}
JS:
function loadImage(){
document.getElementById('glykopeels').style.background = '#ebebeb url(http://lamininbeauty.co.za/images/products/glykopeel.jpg);';
}
function loadImage2(){
document.getElementById('facials').style.background = '#ebebeb url(http://lamininbeauty.co.za/images/products/facial.jpg);';
}
I guess defining a different ID for that element in the onload function and defining css for that new ID is another possibility? Thus changing only the id of that element inside the onload function?
Thank you

First: there is no onload attribute for div's. EDIT: please read comments below, very interesting!
Secondly, you should place the url between quotes (escaping them if needed): url('http://lamininbeauty.co.za/images/products/facial.jpg')
Third, there was no image called preloader.gif, yet there was a image called loader.gif, so I used that one to 'fix' your css part for my solution in the jsfiddle demo link at the bottom.
During SO's server-move, I wrote a simple custom function for you that does exactly what you want.
Tested in IE6 and FF12.
To test this: please clear your browsers buffer, otherwise you can't SEE it in action (it would go too fast), since the images will probably be buffered on second view (again, perfect for your goal)!
JavaScript:
var repBg=function(a, t){ t=t||'*'; // by GitaarLAB
var c=document.getElementsByTagName(t), i=c.length, r=[];
while(i--){if (c[i].getAttribute(a)){r.push(c[i]);}} c=r; i=c.length;
repBg.exec=function(){
c[this['data-i']].style.background="#ebebeb url('"+this.src+"') no-repeat top right";
};
while(i--){ if (c[i].getAttribute(a)) {
r=new Image();
r.onload=repBg.exec;
r['data-i']=i;
r.src=c[i].getAttribute(a);
}}
};
// one could run repBg onload, but better to run it when the image has actually loaded, see html!
// window.onload=function(){ repBg('data-bg_img','div'); };
In your BODY: Add the attribute 'data-bg_img' (as per html5 convention, start with data-) to the elements you want to use this technique on and have it contain your background url, like this:
<div id="glykopeels" data-bg_img="http://lamininbeauty.co.za/images/products/glykopeel.jpg">Glykopeels Content</div>
The 'optional' initialization in your BODY:
<!--
trigger the replace background function when the loader image has actually loaded!
rewriting the onload with nothing to prevent infinite loop in IE6 (and greater?) !!
-->
<img src="http://lamininbeauty.co.za/images/products/loader.gif" style="display:none;" onload="this.onload=null; repBg('data-bg_img','div');">
Manual/explanation:
Images DO have a onload-event, so we place a loading-image in the html (at the bottom), that will trigger it's own onload-event, calling repBg() as soon as the browser has actually downloaded this loading-image!!!
The function repBg() takes up to 2 arguments:
the first mandatory string that contains the attribute that should be selected,
the second optional argument can define tagname (to limit the first argument).
When invoked, function repBg() will then search the body for elementTagNames that adhere to the second argument or * and then filter them with the first argument.
For each htmlObject that remains in the filtered htmlObjectCollection, a new image is created (not appended to the body) with the htmlObject's attribute-value (url) corresponding to the function's first argument as image-source, together with the htmlObjectCollection's referring id (attribute data-id) for reference.
As soon as these images load, they fire their onload event: calling repBg's exec method that replaces the background of the referenced htmlObject with the new freshly loaded (big) background-image (and the rest of your css). For further modularity you could expand on that function.
Lastly, note: the background images load in order they appear in source, aka the way you expect things to work!!
You can see it in action in this fiddle: http://jsfiddle.net/epdDa/
UPDATE VERSION 2: GRACEFUL FALLBACK!! AND COPY-PASTE NOBRAIN SOLUTION
It annoyed the living daylights out of me that my first solution did not provide graceful fallback. So I made a different solution that provides graceful fallback.
Also fully tested in IE6 and FF12
It works like this:
In your BODY: SIMPLY set your div's class to 'preload' and set it's style-attribute to the backgroundimage it should normally load. Like this:
<div id="facials" class="preload" style="background: #ebebeb url('http://lamininbeauty.co.za/images/products/facial.jpg') no-repeat top right;">Facials Content</div>
That was easy right?
Then place the following script in the HEAD (this is important) of the HTML:
// getElementsByClass original by dustin diaz, modified by GitaarLAB
document.getElementsByClassName=document.getElementsByClassName||function(searchClass,node,tag) {
var classElements = [], i=0, j=0;
if (!node){node = document;}
if (!tag){tag = '*';}
var els = node.getElementsByTagName(tag);
var elsLen = els.length;
var pattern = new RegExp('(^|\\\\s)'+searchClass+'(\\\\s|$)');
for (; i < elsLen; i++) {
if ( pattern.test(els[i].className) ) {
classElements[j] = els[i]; j++;}
} return classElements;
};
var repBg=(function(n,u,p,a,i,r){ // by GitaarLAB
window.onload=function(){repBg(1);};
i=new Image(); i.onload=function(){this.onload=null; repBg(2);};
document.write("<style>."+n+"{background:"+p+" url("+u+") "+a+
" !important; background-size: contain !important;}"+
"</style>");
i.src=u; r=0;
return function(t){
r=r+t; if(r>2){
var c=document.getElementsByClassName(n), i=0, l=c.length, s;
repBg.exec=function(){
document.getElementById(this['data-id']).className='';
};
for(;i<l;i++){
r=new Image();
r.onload=repBg.exec;
r['data-id']=c[i].getAttribute('id');
s=c[i].getAttribute('style');
try { // sane browsers
r.src=s.match(/url\('?([^'"]*)'?\)/i)[1];
} catch(e) { // <IE8
r.src=s.cssText.match(/url\('?([^'"]*)'?\)/i)[1];
}
}
}
};
})('preload','http://lamininbeauty.co.za/images/products/loader.gif','#ebebeb','no-repeat top right');
Explanation:
It took me all night.. but I found a way.
If javascript is enabled, function repBg will start by writing an extra style-block to the documents head (where it is located, note to place it after your last css script), that sets the loader-background-image for all elements with the class 'preload' (thus displaying the load-image at pageload).
If a load-test image for the loading-image is loaded AND the window is loaded (to get to all the elements in the body), then it does basically the same as version 1. Only this time we fetch and match the url from the element's style-atribute and onload subsequently empty the element's style-attribute.
Since this function auto-executes and overwrites itself with a version similar to version 1 (as above), you can simply adjust parameters at the last line of function 'repBg'.
Note that: in it's initial sate repBg accepts a maximum of 4 arguments: className, Url, cssPrepend and cssAppend.
To see it in action (don't forget to clean your browsers buffer as explained),
click this fiddle: http://jsfiddle.net/epdDa/1/
Whoever uses this function, I would greatly appreciate it if you credit me!
UPDATE:
Extra explanations and answers to comments.
Main differences between the 2 versions
Technically both versions use almost the same techniques so there is no real difference there.
With version 1 the javascript is the glue that IS NEEDED to make the page work, but works in valid true xhtml and plain html.
However, people with javascript turned off will get a nonfunctional site (with only loading-gifs displayed). Note that all other current answers, including the direction you where going, suffer from this problem!
With version 2 the javascript is only the spice that enhances the page-interaction (the way websites should be coded), but only works in html (or invalid xhtml).
However this should make sure that people with javascript turned off still see a normal functioning page. IE: javascript is NOT NEEDED to display the site correctly. This concept is called 'graceful fallback' or 'degrading gracefully'. My vote no 1 for version 2.
Extra bonus: this path gives you plain vanilla validating and SEMANTIC html since you use ancient trusty in-line style, id and class. My vote no 2 for version 2
Why did I choose to use in-line css? Why 'must' you use in-line css for this to work?
First of all, I spent hours to avoid in-line css. (I did not loose them, I learned way's that did not work, just as useful). Next, I want to point out that again all current answers including the direction you were going, had the actual background image url separated from the css, while in the css you were setting the loader image on each div separately, something where a class would have made more sense. Version 2 simply uses a configurable classname.
Both reading and writing css blocks in the document's HEAD is kind of a mess..
And did I mention linked external css files..??
In my opinion, all this would need so much extra code and cpu-cycles AND blocking/halting the browser on every pageload, in order for the core-priciple to work: the last valid css-rule applies. So the loading image is displayed as soon as possible since it is the specified background image at pageload, exactly what one would want from such a feature. And if the element is no longer part of the 'preload' class? Right: it's inline css takes effect, updated as fast as the browsr can render (if the image is already loaded). Nice.
So if you sacrifice (true) xhtml-support by simply using document.write, it currently still turns out this way is the 'best' way to go (as you can read in the previous 2 links). AND it would still work with an external linked css. My third (KISS-)vote for version 2.
My fourth vote for version 2 is because: the repBg function is prepared to have it's exec method(=function) 'upgraded' so you can only take out the 'preload' value from the class' valuelist. A simple replace() would suffice (currently left out for speed).
My fifth and final vote for version 2 is that because of it's graceful fallback setup, it is also relatively easy to fix for or block some browsers to use the extra 'spice'.
Finally, as for speed: I think version 2 will always feel snappier: onload-image is displayed almost as fast as the browser can fetch it (as if this extra css was always there to begin with), the loading-animations load snappy since: their load is already initiated in the head, and the browser will not download the overruled images until called for by the function. Plus they look interactive without distraction. But.. when the actual background images are loaded and the css updates: bam! the image is there, without the top-to-bottom-scanning'-effect'. That effect feels damn snappy to. Actually I'm convinced and will be doing an adaptation for images in the galary, for the snap-feel and increased perceived initial pageload.. Note, this is my opinion. Your mileage may vary haha.
Good luck!!
(and please vote if you like/use this idea/function, thank you!!)

1) div elements doens't have a load event, this event is only for body, images and script tags.
EDIT: Like pointed by #icktoofay, in the HTML spec the onload exists for all elements, however this is not supported by the major browsers (yet).
2) place this script tag at the end of your html page:
<script>
function loadImages() {
var glykopeels = document.getElementById('glykopeels');
var facials = document.getElementById('facials');
glykopeels.style.backgroundImage = 'url(http://lamininbeauty.co.za/images/products/glykopeel.jpg)';
facials.style.backgroundImage = 'url(http://lamininbeauty.co.za/images/products/facial.jpg)';
​
3) You can set style.background like you did, but do not put the ; at the end of the string, otherwise it will not work.
fiddle: http://jsfiddle.net/pjyH9/
EDIT
Seems like the loader image does not show because once the browser receive the first bytes of the new image it removes the loader.gif from the background. Let's take another approach.
Here is a function that will load the image to cache and then - when image is loaded - set the image to the background of the element with the specified id.
function loadImageToBackground(elementId, imageUri) {
var img = new Image();
img.onload = function() {
document.getElementById(elementId).style.backgroundImage = "url('" + imageUri + "')";
};
img.src = imageUri;
}
The on the elements that you want the loader:
// past the element id and the image url to the function
loadImageToBackground('glykopeels', ​'http://image....');
I'm pretty sure that this will work. The function loadImageToBackground do the basic work, you can extend and add more functionalies if you want.
Here is fiddle with a working example: http://jsfiddle.net/pjyH9/19/
(It loads 2 images with 1.5mb each, so you can see the loader in action).

I think what you're trying to do is get the background image to switch out to the big JPG image after it's loaded. You should be able to adapt something like this to work for you:
<html>
<head>
<title>Image Load Test</title>
<script type="text/javascript">
function loadImage(preloader, imageDiv) {
document.getElementById(imageDiv).style.background = '#ebebeb url('+preloader.src+') no-repeat top right';
// I think resetting the background property also resets backgroundSize.
// If you still want it 'contained' then you need to set the property again.
document.getElementById(imageDiv).style.backgroundSize = 'contain';
}
</script>
<style type="text/css">
#testImage {
background: #ebebeb url(small-image.gif) no-repeat top right;
background-size: contain;
}
#preloads { display: none; }
</style>
</head>
<body>
<div id="testImage">Some Content</div>
<div id="preloads">
<img src="full-image.jpg" onload="loadImage(this, 'testImage')">
</div>
</body>
</html>
The main difference here is that I'm preloading the JPG image in an <img> that's hidden in a <div> with the display: none property to keep it hidden. I'm not sure exactly what the onLoad event does for divs, but I'm pretty sure it's not what you're wanting. Putting an onLoad event in an img element causes the event to fire once the image has fully loaded, which I believe is what you want.
EDIT: I added a line in the JavaScript to reset the background-size property. If that's not what you wanted then just ignore that part.

Related

Applying CSS to items outside of an iFrame (or other element)

I'm trying to inject some css to the body of my SharePoint webpage for a background image. The .js is essentially this:
document.getElementsByTagName( "Body" )[ 0 ].style.backgroundImage = <My Image URL>
document.getElementsByTagName( "Body" )[ 0 ].style.backgroundSize = "50%"
document.getElementsByTagName( "Body" )[ 0 ].style.backgroundPosition = "50% 50%"
And it works as I expect, unless there's an iFrame in the middle (or any other 'body' tag). Then the image gets put inside the iFrame as well as the body of my page. These iFrames are essentially popups that come up whenever you need to upload documents, change some settings on the site, etc. They're not up all the time but I don't want the image there, regardless.
When I call document.getElementsByTagName('Body') I always get the Body tag of whatever iFrame is currently up, or I'll get the document's main body. But it's always an HtmlCollection array of 1 item. Same thing happens if I use document.body
All of the body tags have the same generic setup ( <body class="ms-backgroundImage" style="..." spellcheck="false"> ) regardless of if they're the site's actual Body tag or if it's the iFrame's body.
Is there a way with Javascript to say "Apply to the main body, but not to any others"?
Your description contradicts the documentation (see https://developer.mozilla.org/en-US/docs/Web/API/Document/body). Are you really sure it behaves as you described? Your described behavior is nothing I have ever experienced as a developer.
Can you check again and maybe updates this question?
First, the reason you're getting an HtmlCollection when calling getElementsByTagName is because that function returns an array regardless of whether it matches zero, one or many items (observe the plural "s" in "Elements").
The different JS web api "get"-functions are pretty self explanatory that way, in that
getElementById (no plural s) returns a single DOM-node, whereas getElementsBy[Name|ClassName|TagName] all return an array of nodes.
Now to your main question:
By iFrames I take it you mean modal dialogs?
Modal dialogs in SharePoint have one thing in common, which is that they all have the url parameter "&IsDlg=1". This means you can check whether you're in a dialog or not, and have your code act accordingly.
Here's the code you're looking for, which will apply your style to all non-modal pages.
// could have used GetUrlKeyValue("IsDlg") === "1",
// but not sure if sp.js is loaded, so this is safer
var isDialog = document.location.search.indexOf("IsDlg=1") > -1;
// for non-dialog pages
if(!isDialog) {
var docBody = document.body;
// if for some crazy reason document.body returns an array??
if(document.body.length) { docBody = document.body[0] }
// styling
docBody.style.backgroundImage = "[Your Image URL]";
docBody.style.backgroundSize = "50%";
docBody.style.backgroundPosition = "50% 50%";
}
PS: Customizing the master page is not reccomended practice in newer (>2007) versions of SharePoint.
There are several other methods (CustomActions, for instance) of customizing the platform without touching the out-of-the box master page, which you should familiarize yourself with if you want to work professionally with sharepoint.
To make the change I was trying to do, I just had to add an image to the "Look and Feel" of the page via Site Settings -> Look and Feel. Quick and easy, doesn't require any code... Thankfully.

Using addEventListener on multiple elements, avoid TypeError when particular element not found

I'm using two simple addEventListener mouseenter and mouseleave functions respectively to play and stop animations (Bodymovin/SVG animations, though I suspect that fact is irrelevant).
So, the following works fine:
document.getElementById('animationDiv').addEventListener('mouseenter', function(){
animation.play();
})
(The HTML couldn't be simpler: The relevant part is just an empty div placeholder filled by script - i.e., <div id="animationDiv"></div>.
I can place that in the same file as the one that operationalizes the animation code, or I can place it in a separate "trigger" file, with both files (and other others necessary to processing) loaded in the site footer.
The problem arises when I need to be able to set triggers for any of multiple similar animations that may or may not appear on a given page.
If only one of two animatable elements are present on a page, then one of two sets of triggers will throw an error. If the first of two such triggers is not present, then the second one will not be processed, meaning that the animation will fail. Or at least that's what it looks like to me is happening.
So, just to be clear, if I add the following two triggers for the same page, and the first of the following two elements is present, then the animation will play on mouseenter. If only the second is present, its animation won't be triggered, apparently because of the error thrown on the first.
document.getElementById('firstAnimationDiv').addEventListener('mouseenter', function(){
firstAnimation.play();
})
document.getElementById('secondAnimationDiv').addEventListener('mouseenter', function(){
secondAnimation.play();
})
At present I can work around the problem by creating multiple trigger files, one for each animation, and setting them to load only when I know that the animatable element will be present, but this approach would get increasingly inefficient when I am using multiple animations per page, on pages whose content may be altered.
I've looked at try/catch approaches and also at event delegation approaches, but so far they seem a bit complicated for handling this simple problem, if appropriate at all.
Is there an efficient and flexible standard method for preventing or properly handling an error for an element not found, in such a way that subsequent functions can still be processed? Or am I missing something else or somehow misreading the error and the function failure I've been encountering?
WHY I PICKED THE ANSWER THAT I DID (PLUS WORKING CODE)
I was easily able to make the simple, directly responsive answer by Baoo work.
I was unable to make the answers below by Patrick Roberts and Crazy Train work, though no doubt my undeveloped js skills are entirely at fault. When I have the time, or when the issue next comes up for me in a more complex implementation (possibly soon!), I'll take another look at their solutions, and see if I can either make them work or if I can formulate a better question with fully fledged coding examples to be worked through.
Finally, just to make things clear for people who might be looking for an answer on Bodymovin animations, and whose js is even weaker than mine, the following is working code, all added to the same single file in which a larger set of Bodymovin animations are constructed, relieving me of any need to create separate trigger files, and preventing TypeErrors and impaired functionality.
//There are three "lets_talk" animations that can play - "home," "snug," and "fixed"
//and three types of buttons needing enter and leave play and stop triggers
let home = document.getElementById('myBtn_bm_home');
if (home) home.addEventListener('mouseenter', function() {
lets_talk_home.play();
});
if (home) home.addEventListener('mouseleave', function() {
lets_talk_home.stop();
});
let snug = document.getElementById('myBtn_bm_snug');
if (snug) snug.addEventListener('mouseenter', function() {
lets_talk_snug.play();
});
if (snug) snug.addEventListener('mouseleave', function() {
lets_talk_snug.stop();
});
let fixed = document.getElementById('myBtn_bm_fixed');
if (fixed) fixed.addEventListener('mouseenter', function() {
lets_talk_fixed.play();
});
if (fixed) fixed.addEventListener('mouseleave', function() {
lets_talk_fixed.stop();
});
At typical piece of underlying HTML (it's generated by a PHP function taking into account other conditions, so not identical for each button), looks like this at the moment - although I'll be paring away the data-attribute and class, since I'm not currently using either. I provide it on the off-chance that someone sees something significant or useful there.
<div id="letsTalk" class="lets-talk">
<a id="myBtn" href="#"><!-- a default-prevented link to a pop-up modal -->
<div class="bm-button" id="myBtn_bm_snug" data-animation="snug"></div><!-- "snug" (vs "fixed" or "home" is in both instances added by PHP -->
</a>
</div>
Obviously, a more parsimonious and flexible answer could be - and probably should be - written. On that note, correctly combining both the play and stop listeners within a single conditional would be an obvious first step, but I'm too much of a js plodder even to get that right on a first or second try. Maybe later/next time!
Thanks again to everyone who provided an answer. I won't ask you to try to squeeze the working solution into your suggested framework - but I won't ask you not to either...
Just write your code so that it won't throw an error if the element isn't present, by simply checking if the element exists.
let first = document.getElementById('firstAnimationDiv');
if (first) first.addEventListener('mouseenter', function() {firstAnimation.play();});
You could approach this slightly differently using delegated event handling. mouseover, unlike mouseenter, bubbles to its ancestor elements, so you could add a single event listener to an ancestor element where every #animationDiv is contained, and switch on event.target.id to call the correct play() method:
document.getElementById('animationDivContainer').addEventListener('mouseover', function (event) {
switch (event.target.id) {
case 'firstAnimationDiv':
return firstAnimation.play();
case 'secondAnimationDiv':
return secondAnimation.play();
// and so on
}
});
You could also avoid using id and use a more semantically correct attribute like data-animation as a compromise between this approach and #CrazyTrain's:
document.getElementById('animationDivContainer').addEventListener('mouseover', function (event) {
// assuming <div data-animation="...">
// instead of <div id="...">
switch (event.target.dataset.animation) {
case 'first':
return firstAnimation.play();
case 'second':
return secondAnimation.play();
// and so on
}
});
First, refactor your HTML to add a common class to all of the placeholder divs instead of using unique IDs. Also add a data-animation attribute to reference the desired animation.
<div class="animation" data-animation="first"></div>
<div class="animation" data-animation="second"></div>
The data- attribute should have a value that targets the appropriate animation.
(As #PatrickRobers noted, the DOM selection can be based on the data-animation attribute, so the class isn't really needed.)
Since your animations are held as global variables, you can use the value of data-animation to look up that variable. However, it would be better if they weren't global, but were rather in a common object.
const animations = {
first: null, // your first animation
second: null, // your second animation
};
Then select the placeholder elements by class, and use the data attribute to see if the animation exists, and if so, play it.
const divs = document.querySelectorAll("div.animation");
divs.forEach(div => {
const anim = animations[div.dataset.animation];
if (anim) {
anim.play(); // Found the animation for this div, so play it
}
});
This way you're guaranteed only to work with placeholder divs that exist and animations that exist.
(And as noted above, selection using the data attribute can be done const divs = document.querySelectorAll("div[data-animation]"); so the class becomes unnecessary.)

Is it possible to make a change with jQuery and then immediately reverse that change?

I have a pretty specific scenario where I would like to select all elements with jQuery, make a CSS change, save the elements, then reverse the change I made.
The Goal
I created a jQuery plugin called jQuery.sendFeedback. This plugin allows the user to highlight areas of the screen, as shown in this demo. When they submit their feedback the plugin grabs all the HTML on the page and dumps it into a callback function. Like so:
$('*').each(function ()
{
$(this).width($(this).width());
$(this).height($(this).height());
});
var feedbackInformation = {
subject: $feedbackSubject.val(),
details: $feedbackDetails.val(),
html: '<html>' + $('html').html() + '</html>'
};
if (settings.feedbackSent)
settings.feedbackSent(feedbackInformation);
The callback function accepts this feedback information and makes an AJAX call to store the page HTML on the server (this HTML includes the red box highlights the user drew on the screen). When someone from tech support needs to view the user's "screen shot" they navigate to a page that serves up the stored HTML so the developer can see where the user drew their highlights on the screen.
My original problem was that different screen resolutions made the elements different sizes and the red highlights would highlight the wrong areas as the screen changed. This was fixed pretty easily by selecting all elements on the page and manually setting their height and width to their current height and width when the user takes the snap shot. This makes all the element sizes static, which is perfect.
$('*').each(function ()
{
$(this).width($(this).width());
$(this).height($(this).height());
});
The Problem
The issue with this is that when the plugin is done transmitting this HTML the page currently being viewed now has static heights and widths on every element. This prevents dropdown menus and some other things from operating as they should. I cannot think of an easy way to reverse the change I made to the DOM without refreshing the page (which may very well end up being my only option). I'd prefer not to refresh the page.
Attempted Solution
What I need is a way to manipulate the HTML that I'm sending to the server, but not the DOM. I tried to change the above code to pull out the HTML first, then do the operation on the string containing the HTML (thus not affecting the DOM), but I'm not quite sure what I'm doing here.
var html = '<html>' + $('html').html() + '</html>';
$('*', html).each(function ()
{
$(this).width($(this).width());
$(this).height($(this).height());
});
This did not work. So either I need to be able to manipulate the string of HTML or I need to be able to manipulate the DOM and undo the manipulation afterward. I'm not quite sure what to do here.
Update
I employed the solution that I posted below it is working beautifully now. Now I am wondering if there is a way to statically write all the css for each element to the element, eliminating the need for style sheets to be referenced.
I think you are mostly on the right track by trying to make the modifications to the HTML as a string rather than on the current page for the user.
If you check this post, you might also want to follow the recommendation of creating a temporary <div> on the page, cloning your intended content to the new <div> ensuring it is invisible using "display:none." By also putting a custom Id on the new <div> you can safely apply your static sizing CSS to those elements using more careful selectors. Once you have sent the content to the server, you can blow away the new <div> completely.
Maybe?
After much pain and suffering I figured a crude but effective method for reverting my modifications to the DOM. Though I hadn't gotten around to trying #fdfrye's suggestion of cloning, I will be trying that next to see if there is a mroe elegant solution. In the meantime, here is the new code in case anyone else can benefit from it:
$('*').each(function () {
if ($(this).attr('style'))
$(this).data('oldStyle', $(this).attr('style'));
else
$(this).data('oldStyle', 'none');
$(this).width($(this).width());
$(this).height($(this).height());
});
var html = '<html>' + $('html').html() + '</html>';
$('*').each(function () {
if ($(this).data('oldStyle') != 'none')
$(this).attr('style', $(this).data('oldStyle'));
else
$(this).removeAttr('style');
});
When I'm looping through every element and modifying the css, I log the original value onto the element as data. After I assign the DOM HTML to a variable I then loop through all elements again and restore the style attribute to its original value. If there was no style attribute then I log 'none' to the element data and then remove the style attribute entirely when looping through again.
This is more performance heavy than I wish it was since it loops through all elements twice; it takes a few seconds to finish. Not horrible but it seems like a little much for such a small task. Anyway, it works. I get a string with fixed-sized HTML elements and the DOM goes back to normal as if the plugin never touched it.

What is the best way to implement multiway toggle using javascript/jQuery?

I have a div that can display 3 images (in the background) each indicating the 'state' of some variable: i.e., partial, full and none. For each of these states I have images: partial.gif, full.gif and none.gif (i.e., these are background images of that div)
Need: Circular queue like toggling effect for changing the images in this order partial -> full -> none -> partial
So if the current image is 'partial.gif' and the user clicks the div the background image changes to the next one in the sequence i.e., full.gif (and if it is currently full.gif it changes to none.gif and that to partial.gif and so on).
Naive solution: have a bunch of if/else's or switch-case and check the current one (image) and then decide based on array look up which is the next one. Is this the best way of doing it? Can I leverage jQuery's toggle function somehow?
(PS: It need not be restricted to images, but could also be for different background color sequences etc., I'd like to know what it is a good 'generic' way of doing it i.e., The example may be specific for background images but if I changed part of that code for background-color or font it should still work. I don't want it to be purely generic, but just enough so it is easy to modify for other attributes. If not, that's fine too. Just a thought :)
http://api.jquery.com/toggle-event/
To be precise http://api.jquery.com/toggle-event/#example-0
does exactly what you wanted...
$("#div1").toggle(
function() {
$(this).css("background-image","url(full.png)")
},
function() {
$(this).css("background-image","url()")
},
function() {
$(this).css("background-image","url(partial.png)")
}
});
UPDATE fn.toggle was removed from jQuery
Here are relevant posts
Where has fn.toggle( handler(eventObject), handler(eventObject)...) gone?
Toggle stopped working after jquery update
As long as it's a CSS-based solution (where you can just switch classes), you could do something like this (untested code):
$('#element').click(function() {
// get current css class value.
var class = $(this).attr('class');
// determine/increment number.
var nextNumber = parseInt(class.charAt(class.length - 1)) + 1;
// if too high, reset to first one.
if (nextNumber > 3) {
nextNumber = 1;
}
// remove old class and add new class.
$(this).removeClass(class).addClass('my_class' + nextNumber);
});
Assumption being made here that you only have one CSS class applied to the element at a time. But if that's not the case, I'm sure you can find a workaround/tweak for this.
And this is just generic enough where you can swap out your CSS class definitions without impacting the script functionality.

images created dynamically do not appear on screen! -> Javascript

I'm trying to do something simple to practice my Javascript (which I learned some recently) and I'm trying to do a game on it (pacman to be precise).
I am trying to build that game board on the browser by creating images dynamically. I've done an array like this:
var images= new Array(25);
for(i=0;i<25;i++)
images[i]= new Array(25);
And, for the game board I used a matrix done with 0 and 1's with 25x25 size (not going to post it here cause is too big and would make my text hard to read) called board.
For the images that I am using right now I have something like this:
var image_empty = new Image();
image_empty.src="Images/empty.jpg";
var image_wall = new Image();
image_wall.src="Images/wall.jpg";
For the initialization function I have something like this:
function drawField()
{
for(i=0;i<board.length;i++)
{
for(j=0;j<board[i].length;j++)
{
if(board[i][j] == 0)
draw(i,j,image_empty);
else if(board[i][j] == 1)
draw(i,j,image_wall);
}
}
}
And for drawing the images themselves I am using this:
function draw(x,y,img)
{
images[x][y] = new Image(22,22);
images[x][y].src = img.src;
images[x][y].style.position = 'absolute';
images[x][y].style.left = 40+x*22;
images[x][y].style.top = 40+y*22;
}
Every time I run this code nothing appears on the screen. I've tried several times use a load of things but nothing happens. I am saving the pictures (at least I think I am) and still nothing.
Can someone give me some pointers of what could be wrong?
PS: Some people pointed me out using the appendChild method would solve the problem, still, since pacman will be moving around I can't use it to store my images (and I was planning to use the draw function to draw anything).
And btw nor Web Developer plugin or firebug point out errors (the code is correct from their perspective).
Creating an Image in the method you describe doesn't actually display the image. Even putting attributes and styling to make it appear a certain way doesn't add it to the DOM. The advice about append child is correct. For example, if you had:
<div id="main"></div>
and you called
document.getElementById("main").appendChild(images[x][y]);
this would insert the image inside the div. You could do this repeatedly to generate the equivalent of...
<div id="main">
<img src... />
<img src... />
...and so on
</div>
Then, your CSS styling and positioning would work.
There's nothing wrong with your script, but Firebug does display a rendered version of the DOM. As you run the script, you will actually see the HTML tab of Firebug changing with the images you've added to the page.
Also, keep in mind that the DOM must complete loading before you are able to run this. You can accomplish this by doing a simple:
<body onload="drawImages()">
UPDATE: Once you've actually added the 25x25 images, the array still references the elements - they're just now part of the DOM. So, you can change their source via:
images[x][y].src = "newImage.jpg";
If you, for some reason, wanted to remove an image from the board, leaving a gap, you can remove it from the DOM
document.getElementById("main").removeChild(images[x][y]);
or just hide it via CSS.

Categories