HTML Dynamic
HTML Dynamic
HTML Dynamic
Dynamic HTML
Dynamic HTML (DHTML) is an all-in-one word for web pages that use
Hypertext Markup Language (HTML), Cascading Style Sheets (CSS), and rely
on JavaScript to make the web pages interactive. DHTML is a feature of
Netscape Communicator 4.0, and Microsoft Internet Explorer 4.0 and 5.0
and is entirely a "client-side" technology. It relies only the browser
for the display and manipulation of the web pages and is unrelated to
other client-side technologies like Java, Flash.
W3C CSS-Positioning
http://www.w3.org/TR/WD-positioning.html
JavaScript
JavaScript Reference
http://developer.netscape.com/docs/manuals/communicator/jsref/index.htm
Microsoft JScript
http://msdn.microsoft.com/scripting/default.htm
The Dynamic Duo, is a tutorial written by me, Dan Steinman, and is the
result of my experimentation and successes in creating Cross-Browser
DHTML.
particular task, but rather the things that I have tried and have had
good results with.
If you are not unfamiliar with JavaScript and CSS, this tutorial may not
be the best starting point for you. However, I do start out slowly and
cover much of the ground knowledge needed to understand how DHTML works.
The programming concepts in this this tutorial are not extremely
complex. However, cross-browser DHTML requires a level of debugging
skills that can be quite daunting to a beginner. You will be working
with browsers that are only partially compatible, and computer languages
that are only partially implemented. You will encounter bugs and
limitations not only between the 2 browsers, but between different
operating systems, as well as between incremental versions of the
browsers. This tutorial only scratches the surface of the problems that
you will encounter with building your own DHTML pages... and do believe
me on this one, I am not kidding.
With that said, I have done my best to lay down a set of guidelines that
makes cross-browser DHTML feasible. Following the tips and techniques in
this tutorial you can create just about anything you can think of. By
time you are finished reading, you will understand nearly all the
concepts involved in DHTML, learn a rich set of JavaScript programming
techniques using my DHTML API (The DynLayer), as well as learn how to
build your very own DHTML objects for creating reusable widgets and
components for your website.
I'd appreciate any comments or suggestions you have about this tutorial,
I'm always looking for ways to improve it. Also, I'd be very gracious of
any contributions you have, including modifications to any code in this
tutorial, or any DHTML objects that you've created and want to share
with other people. And if you have built a website, game, or application
using this tutorial, I would be more than happy to provide a link from
my DHTML Resources links page.
4
Cascading Style Sheets (CSS) are the basis for Dynamic HTML in both
Netscape Navigator 4.0 and Internet Explorer 4.0. CSS allows a way to
create a set of "styles" that define how the elements on your page are
rendered. Cascading Style Sheets Positioning (CSS-P) is an extension to
CSS that gives a developer pixel-level control over the location of
anything on the screen. Due to the fact there are already good CSS and
CSS-P documentation and tutorials I won't be duplicating them - rather
I'll be building on top of them.
Here are 2 documents/tutorials that explain the syntax of CSS and CSS-P:
W3C CSS-Positioning
http://www.w3.org/TR/WD-positioning.html
Those sites will give a complete overview of CSS and how to implement
it. But I'll just quickly re-iterate the parts of CSS that will be used
throughout this tutorial.
Using DIV tag by itself has the same results as using <P></P>
But by applying CSS to DIV tags we can define where on the screen this
piece of HTML will be displayed, draw squares or lines, or how to
display the text that's inside it. You do this by first giving the DIV
an ID (sort of like a name):
What you use for your ID is up to you. It can be any set of characters
(a-z,A-Z,0-9, and underscore), but starting with letter.
Inline CSS:
5
Inline is the way most commonly used. And it is the way I will begin
showing how to write DHTML and JavaScript.
Using the external method will work as well, however there are a few
issues involved with writing CSS like this, so I suggest you wait until
you get to the Nesting Layers lesson before trying it on your own. For
right now just take a look to see how it is done...
Notice how the ID is used in the STYLE tag to assign the CSS styles.
Because the goal of this site is to produce DHTML that works in both
Netscape and Internet Explorer, we are somewhat limited to which CSS
styles/properties we can use. Generally, the following properties are
the ones that work (fairly closely) to the standards as defined by the
W3C.
position: Defines how the DIV tag will be positioned - "relative" means
that the DIV tag will flow like any other HTML tag, whereas "absolute"
means the DIV will be positioned at specific coordinates. Absolute
positioning will be the topic of most of this tutorial.
left: Left location (the number of pixels from the left edge of the browser
window).
top: Top location (the number of pixels from the top edge of the
browser window).
width: Width of the DIV tag. Any text/html that is
inserted into the DIV will wrap according to what this value is. If
width is not defined it will all be on one line.
Important: When using layers for animation you should always define the
width. This is because in IE the default is the entire width of the
screen. If you move the layer around the screen a scrollbar will appear
at the bottom, which is annoying and causes the animation to slow down.
height: Height of the DIV tag. This property is rarely needed unless you
also you want to clip the layer
clip: Defines the clipping (crop)
rectangle for the layer. Makes the DIV into a precisely defined square.
You define the size of the rectangle with the values of the four edges:
clip:rect(top,right,bottom,left);
visibility: Determines whether the DIV will be "visible", "hidden", or "inherit" (default).
z-index The stacking order of DIV tags.
background-color: Background color of the DIV. In Netscape this property only
applies to the background color of the text. When you want to draw squares with
CSS you must also define the layer-background-color property to the same value.
layer-background-color: Background color of the DIV for Netscape.
6
The syntax for CSS differs from HTML, you use colons to separate the
property and it's value, and semi-colons to separate the different
properties:
position: absolute;
left: 50px;
top: 100px;
width: 200px;
height: 100px;
clip: rect(0px 200px 100px 0px);
visiblity: visible;
z-index: 1;
background-color:#FF0000;
layer-background-color:#FF0000;
background-image:URL(filename.gif);
layer-background-image:URL(filename.gif);
You have a bit of flexibility when assigning CSS properties. You do not
have to define all of them. White space is ingored so you can either
have them all on the same line, or on separate lines, tabs between
values etc. As well, the default unit value is pixels, so you do not
necessarily have to have the "px" after the left, top, width and height
values, although it is recommended to do so.
Inline Example:
External Example:
<STYLE TYPE="text/css">
<!-- #divname {position: absolute; left:50px;
top:100px; width:200px; height:100px; clip:rect(0px 200px 100px 0px);
visiblity:visible; z-index:1;} -->
</STYLE>
Cross-Browser JavaScript
You can use JavaScript to access and change the properties of your CSS-P
element. However, some of the syntax differs between Netscape 4.0 and
Internet Explorer 4.0. By knowing where the differences lie, I'll show
you an easy way to create cross-browser JavaScripts - scripts that will
work in both N4 and IE4.
Browser Checking:
I'm now using ns4 and ie4 for browser checking instead of n and ie
First things first: we have to know how to check which browser someone
is using. This little chunk of code will be the standard browser check
in nearly all the examples in this tutorial:
function check() {
if (ns4) {
// do something in Netscape Navigator 4.0
}
if (ie4) {
// do something in Internet Explorer 4.0
}
}
For Netscape the general way to access the CSS-P properties is like
this:
document.blockDiv.propertyName
or
8
document.layers["blockDiv"].propertyName
blockDiv.style.propertyName
or
document.all["blockDiv"].style.propertyName
I've found that the best way to make cross-browser scripts is to have a
variable, that depending on whether you're in Netscape or IE, points
directly to either document.blockDiv or blockDiv.style, look below. I
call these variables, pointer variables.
You see, after you do this, you can now access the properties using a
shortcut way. For example if you wanted to check the left coordinate of
our DIV tag called "blockDiv", it would simply be:
block.left
A Full Example:
This example will pop up an alert of the left, top and visiibilty
properties of a CSS-P element.
The script:
<SCRIPT LANGUAGE="JavaScript">
<!-- ns4 = (document.layers)? true:false
ie4 = (document.all)? true:false
function init() {
9
//-->
</SCRIPT>
The HTML:
<BODY onLoad="init()">
<A HREF="javascript:alert(block.left)">left</A> -
<A HREF="javascript:alert(block.top)">top</A> -
<A HREF="javascript:alert(block.visibility)">visibility</A>
</BODY>
The Differences
If you open up both Netscape and IE and try that last example in each,
you'll notice that you don't recieve the same values.
These differences can cause some problems but in the next few sections
I'll show how to get around them.
10
You might ask yourself: "Why does Netscape display the visibility as
'show'?"
Well the answer is that Netscape's CSS properties are based around it's
proprietory LAYER tag. However, even Netscape is now downplaying it's
LAYER tag in favour of the W3C's recommended CSS-P. So the "show" and
corresponding "hide" values of the visibility property are left-overs
from Netscape's layers. I believe this is the only glaring defect in how
Netscape represents CSS-P.
For Netscape
document.divName.visibility = "show"
document.divName.visibility = "hide"
divName.style.visibility = "visible"
divName.style.visibility = "hidden"
Instead of always rewriting the same code over and over again to show
and hide elements, you can use the following functions:
function showObject(obj) {
if (ns4) obj.visibility = "show"
else if (ie4) obj.visibility = "visible"
}
function hideObject(obj) {
if (ns4) obj.visibility = "hide"
else if (ie4) obj.visibility = "hidden"
}
These functions must be used along with pointer variables - see the code
in the following example.
11
Whenever you want to change the visibility of an element, you just go:
showObject(objectName)
or
hideObject(objectName)
I've been finding that it's not always necessary, and sometimes
cumbersome if you have a lot of layers that need only to be hidden and
shown. I've showed the pointer variable technique first because that is
general idea that I'll be building on to make more powerful JavaScripts
in further lessons. But on occasions where you won't need to have any
more functionality, the following simplified functions can also be used:
function show(id) {
if (ns4) document.layers[id].visibility = "show"
else if (ie4) document.all[id].style.visibility = "visible"
}
function hide(id) {
if (ns4) document.layers[id].visibility = "hide"
else if (ie4) document.all[id].style.visibility = "hidden"
}
To use these are similar except now the exact name of the layer must be
used and it also must be in quotes:
show("divID")
or
hide("divID")
Where divID is the ID of the DIV tag that you want to show/hide.
12
Moving
function init() {
if (ns4) myelement = document.myelementDiv
if (ie4) myelement = myelementDiv.style
}
From now on, this will be inherent in all examples so don't forget!
To get rid of the "px" you can parse the value into an integer.
myelement.left
parseInt(myelement.left)
For example, if you wanted to pop up an alert of the current left and
top location you'd write:
Now believe me, to always have to write parseInt() before all your
variables will tend to get very annoying. You will soon ask yourself if
there is a better way... and I think I have a pretty good answer to
that.
There is nothing stopping you from adding more properties onto our
pointer variable, or object, as I will tend to call it from now on.
What I suggest you do, is keep the current location of the element in
separate properties aside from the left and top properties. To make
13
these new properties you just directly assign them. I'd start by setting
them to actual location:
myelement.xpos = parseInt(myelement.left)
myelement.ypos = parseInt(myelement.top)
Now after this point, if you ever need to find out the left or top
position, you just check the value of myelement.xpos and myelement.ypos
respectively. Our new alert() would look like so:
When you want to change the location of the element, you FIRST have to
change the values of xpos and ypos. THEN you set the left and top values
equal to xpos and ypos respectively. For example:
function move() {
myelement.xpos = 200
myelement.ypos = -40
myelement.left = myelement.xpos
myelement.top = myelement.ypos
}
You must always keep the xpos and ypos values in synch with the left and
top values. That way when you check myelement.xpos, you know that it
will always be the same as myelement.left.
Not too difficult right? This idea will be the basis for everything I'll
show in future examples. It may seem a little dumb to have these extra
variables but once you get into more complicated things you'll find this
technique does help smooth out your code.
Aside: You may be wondering why am using xpos and ypos as my properties
instead of just x and y... Well I did that for a reason. It is a little
known fact that Netscape has already included these properties into
CSS-P. I found that if you use x and y then your values will always be
stored as integers. Now you may think "who cares?"... but there are
instances where you need to store the current left and top positions
with more than just integers (ie. real numbers with decimals and
everything) and this is just not possible if you use x and y.
new location.
function moveTo(obj,x,y) {
obj.xpos = x
obj.left = obj.xpos
obj.ypos = y
obj.top = obj.ypos
}
moveTo(mysquare,50,100)
MoveBy works exactly the same but instead of moving it directly to a new
location it shifts the layer by a given number of pixels.
function moveBy(obj,x,y) {
obj.xpos += x
obj.left = obj.xpos
obj.ypos +=y
obj.top = obj.ypos
}
moveBy(mysquare,5,-10)
15
Sliding
Which moves the layer 5 pixels to the right. Then you stick that code
into a looping function:
function slide() {
if (block.xpos < 300) {
block.xpos += 5
block.left = block.xpos
setTimeout("slide()",30)
}
}
Of course it's not much more difficult to move on a diagonal - you just
change both the xpos (left) and the ypos (top) values.
Using some high school trigonometry we can figure out how to move an
element on any angle. In case you forgot, here's a quick diagram to
refresh your memory:
Now to initialize your object to include angles you'll need 4 new properties:
object.angle = 30
object.xinc = 5*Math.cos(object.angle*Math.PI/180)
object.yinc = 5*Math.sin(object.angle*Math.PI/180)
object.count = 0
We calculate the x and y incrementation and use them to determine how far to move
the left and top values. You have to multiply the angle by Math.PI/180 to convert the
angle into radians - sin and cos are always calculated in radians. The count property
will be used in the iterating function to determine how many times to loop.
16
Using my block example again, here's some full code to move an element at a given
angle.
function init() {
if (ns4) block = document.blockDiv
if (ie4) block = blockDiv.style
block.xpos = parseInt(block.left)
block.ypos = parseInt(block.top)
block.angle = 30
block.xinc = 5*Math.cos(block.angle*Math.PI/180)
block.yinc = 5*Math.sin(block.angle*Math.PI/180)
block.count = 0
}
function slide() {
if (block.count < 25) {
block.xpos += block.xinc
block.ypos -= block.yinc
block.left = block.xpos
block.top = block.ypos
block.count += 1
setTimeout("slide()",30)
}
else block.count = 0
}
The if (block.count < 25) means that the function will execute 25 times before
stopping - ie4. the block will slide a total 125 pixels units.
17
Using clever mouse events we can use a single hyperlink to start and
stop an animation. When pressed the hyperlink will slide a block and
when released the slide will stop.
The slide script is nothing new. We'll need an active variable in there
again, and the move function is a carbon copy of previous functions:
function init() {
if (ns4) block = document.blockDiv
if (ie4) block = blockDiv.style
block.xpos = parseInt(block.left)
block.active = false
}
function slide() {
if (block.active) {
block.xpos += 5
block.left = block.xpos
setTimeout("slide()",30)
}
}
The onMouseDown sets the active variable to true, and then calls the
slide() function which begins our animation. While the link is held,
nothing changes. It continues to slide until you release the hyperlink -
and hence execute whatever is in the onMouseUp handler. It sets the
active variable to false which stops the slide.
The onMouseOut also sets the active variable to false for error proofing
reasons. I found that if you move the mouse off the link and then
release, it wouldn't stop the animation - because you're not executing
an MouseUp over the link. But if you include the onMouseOut it accounts
for this loop-hole.
18
Keystroke Events
The first thing that you have to understand is how to initialize your
events. Here is a basic initialization for the "onkeydown" event.
document.onkeydown = keyDown
When this code is read by the browser it will know that whenever a key
is pressed, the keyDown() function will be called. It doesn't matter
what function you call, and the code does not need the brakets after the
funtion name.
To capture what key was pressed works a little bit differently between
the browsers. So I'll first show each individually.
Netscape
Your keyDown() has to pass a hidden variable - I'll use the letter "e"
because that is what's commonly used.
function keyDown(e)
This "e" represents the key that was just pressed. To find out out what
key that is, you can use the which property:
e.which
This will give the index code for the key - not what letter or number
was pressed. To convert the index to the letter or number value, you
use:
String.fromCharCode(e.which)
function keyDown(e) {
19
document.onkeydown = keyDown
document.captureEvents(Event.KEYDOWN)
Internet Explorer
IE works similarly except you don't need to pass the "e" value.
function keyDown() {
var keycode = event.keyCode
var realkey = String.fromCharCode(event.keyCode)
alert("keycode: " + keycode + "\nrealkey: " + realkey)
}
document.onkeydown = keyDown
document.onkeydown = keyDown
Now, if you were to open both browsers and compare the examples, you'll
realize the results are not always the same. The keycodes are different
because each browser uses a different character set. Because of this
you'll always have to make separate code for each browser - there's no
way around it.
What I'd suggest is totally forgetting about the real key values
entirely, and only work with the keycodes. The following chunk of code
will assign nKey to the keycode and ieKey to 0 if you're using Netscape
or or it will set ieKey to the keycode and nKey to 0 if you're using
Internet Explorer. Then it shows an alert of both values:
function keyDown(e) {
if (ns4) {var nKey=e.which; var ieKey=0}
if (ie4) {var ieKey=event.keyCode; var nKey=0}
alert("nKey:"+nKey+" ieKey:" + ieKey)
}
document.onkeydown = keyDown
if (ns4) document.captureEvents(Event.KEYDOWN)
Now you can activate your movement functions from the keyboard. You do a
check of which key was pressed, and then call the appropriate function
to move your object. For the following example I use the "A" key to
initiate a sliding function. For the "A" key, the nKey value is 97, and
the ieKey is 65. So I do a check for those values in order to call the
"slide" function.
function init() {
if (ns4) block = document.blockDiv
if (ie4) block = blockDiv.style
block.xpos = parseInt(block.left)
document.onkeydown = keyDown
if (ns4) document.captureEvents(Event.KEYDOWN)
}
function keyDown(e) {
if (ns4) {var nKey=e.which; var ieKey=0}
if (ie4) {var ieKey=event.keyCode; var nKey=0}
if (nKey==97 || ieKey==65) { // if "A" key is pressed
slide()
}
}
function slide() {
block.xpos += 5
block.left = block.xpos
status = block.xpos // not needed, just for show
setTimeout("slide()",30)
}
function slide() {
if (myobj.active) {
myobj.xpos += 5
myojb.left = myobj.xpos
setTimeout("slide()",30)
}
}
21
In this case, the slide() function will only operate when the
myobj.active value is true. Once you set myobj.active to false the
movement function will stop. Knowing this, we can insert some code into
our script that will give us more control of what's happening.
The onkeyup event works exactly the same way the onkeydown did. You can
initialize both keydown and keyup with the following:
document.onkeydown = keyDown
document.onkeyup = keyUp
if (ns4) document.captureEvents(Event.KEYDOWN | Event.KEYUP)
And the keyUp() function is the same too. But we want to make so that
when a key is released, it will stop whatever movement is currently
running. To do that we can set our block's active variable to 0:
function keyUp(e) {
if (ns4) var nKey = e.which
if (ie4) var ieKey = window.event.keyCode
if (nKey==97 || ieKey==65) block.active = false
}
But to totally "error" proof our code, we have to put some more checks
into the other functions. Take a look at the code below and see if you
can understand what I'm doing. In the keyDown function, the &&
!block.active is to make sure that we can only call the function if the
block is not active. In other words, if the block is moving we do not
execute the slide() function again. Then we set the active value to true
and move the block. The slide() function has the if (block.active)
statement so that it only moves when the block.active value is true -
that way when we release a key it will stop executing.
function init() {
if (ns4) block = document.blockDiv
if (ie4) block = blockDiv.style
block.xpos = parseInt(block.left)
block.active = false
document.onkeydown = keyDown
document.onkeyup = keyUp
if (ns4) document.captureEvents(Event.KEYDOWN | Event.KEYUP)
}
function keyDown(e) {
if (ns4) {var nKey=e.which; var ieKey=0}
if (ie4) {var ieKey=event.keyCode; var nKey=0}
if ((nKey==97 || ieKey==65) && !block.active) { // if "A" key is pressed
block.active = true
slide()
}
22
}
function keyUp(e) {
if (ns4) {var nKey=e.which; var ieKey=0}
if (ie4) {var ieKey=event.keyCode; var nKey=0}
if (nKey==97 || ieKey==65) {
block.active = false // if "A" key is released
}
}
function slide() {
if (block.active) {
block.xpos += 5
block.left = block.xpos
status = block.xpos // not needed, just for show
setTimeout("slide()",30)
}
}
Clipping Layers
Clipping refers to what part of the layer will be visible. You have
understand the difference between the clip values, and the width and
height - they are not the same. Width and Height really just tell the
browser how to wrap the HTML elements inside it. Whereas clipping makes
a window to view the layer through - it has no effect on any of the
other properties of the layer (left or top location, width, height,
visibility, etc.).
The clipping region is defined as a square by setting the clip value for
each of the 4 edges (top, right, bottom, left). For each edge you can
clip a portion of the viewing space away, or to add extra viewing space.
All the clip values are with respect to that layer - the values are
taken from the top-left location of the layer.
clip:rect(top,right,bottom,left)
Where the top, right, bottom, and left values are in pixels. And don't
forget the order that they go in - it will be confusing if you mess them
up.
In this case it creates an extra 10 pixel border around the edge of the
layer because clip top is -10 and clip left is -10. The clip right is
110 which is 10 more than our width, and the clip bottom is 60 which is
10 more than the height.
I put a few extra CSS properties in there too. The background-color (for
ie4) and layer-background-color (for Netscape) are used to do just that
- color the entire layer in whatever color you wish. This enables us to
see our layer as a square and will help to visualize what's going on
when we clip it. Usually you don't have to have the height of the layer,
24
but when you're using clipping you should put it in because if you don't
IE won't color the extra space below the last element in the layer.
You can also have a background image in your layer. The CSS for IE is
background-image:URL(filename.gif) and for Netscape it's
layer-background-image:URL(fildname.gif). But in order for Netscape to
display it properly you must have one more CSS property repeat:no.
Here's the full CSS for a layer with a background images:
Once you've clipped the layer can then obtain or change those clip
values using JavaScript just like we can the location.
Clipping in Netscape:
document.divName.clip.top
document.divName.clip.right
document.divName.clip.bottom
document.divName.clip.left
alert(document.divName.clip.top)
document.divName.clip.top = 50
In IE you have to obtain all the clip values at the same time. For
example you could pop up an alert showing the clip value:
alert(divName.style.clip)
That will return a string which represents what the clip values were
defined as. Here's an example of what would be returned:
When you want the change the clip values you cannot just clip one of the
edges like you can in Netscape - you must reset all your clip values at
the same time:
The clipValues() function can be used to obtain the clip values for each
edge of a layer.
function clipValues(obj,which) {
if (ns4) {
if (which=="t") return obj.clip.top
if (which=="r") return obj.clip.right
if (which=="b") return obj.clip.bottom
if (which=="l") return obj.clip.left
}
else if (ie4) {
var clipv = obj.clip.split("rect(")[1].split(")")[0].split("px")
if (which=="t") return Number(clipv[0])
if (which=="r") return Number(clipv[1])
if (which=="b") return Number(clipv[2])
if (which=="l") return Number(clipv[3])
}
}
alert(clipValues(block,"t"))
The edge that you want to check only has to be the first letter in
quotes - "t" (top), "r" (right), "b" (bottom), "l" (left).
To change the clip values I've written 2 generic functions that can be
used pretty easily.
function clipTo(obj,t,r,b,l) {
if (ns4) {
obj.clip.top = t
obj.clip.right = r
obj.clip.bottom = b
obj.clip.left = l
}
else if (ie4) obj.clip = "rect("+t+"px "+r+"px "+b+"px "+l+"px)"
26
To use it you must tell it what layer/object to use, and the clip value
for each edge - top, right, bottom, left respectively.
clipTo(block,0,100,100,0)
clipBy() allows you to shift the clip value by a given amount of pixels
- it adds or subtracts from the current clip value for the edges:
function clipBy(obj,t,r,b,l) {
if (ns4) {
obj.clip.top = clipValues(obj,'t') + t
obj.clip.right = clipValues(obj,'r') + r
obj.clip.bottom = clipValues(obj,'b') + b
obj.clip.left = clipValues(obj,'l') + l
}
else if (ie4) obj.clip = "rect("+( this.clipValues(obj,'t')+t)+"px
"+(this.clipValues(obj,'r')+r)+"px "+Number(this.clipValues(obj,'b')+b)+"px
"+Number(this.clipValues(obj,'l')+l)+"px)"
}
Similar to the clipTo() function you just set how much you want to clip
each edge by:
clipBy(block,0,10,5,0)
This will add 10 pixels to the right and 5 pixels to the bottom.
Netscape will always show the layer's color no matter where it's been clipped. But in
IE when you clip outside of the layer's boundaries (adding extra borders) you can't
see the edges of the layer.
By putting clipBy() commands into looping functions you can create all
those neat wiping effects that I'm sure you've seen before. I'll tell
you right now it's probably easiest if you make your own wipe()
functions. It is possible to make a generic wipe function but I've
included that into the Dynamic Layer Object as an add-on method (read
the Wipe Method lesson for more info). The truth is though it's probably
easier and much less code if you just write your own little function to
wipe your layers. You do it in the same manner as you do slides with.
Create a looping function that re-clips the layer:
function wipe1() {
clipBy(block,0,5,0,0)
setTimeout("wipe1()",30)
}
But we have to have a way of stopping the wipe so just do a check to see
if the edge has reached the desired value:
27
function wipe1() {
if (clipValues(block,'r')<200) {
clipBy(block,0,5,0,0)
setTimeout("wipe1()",30)
}
}
28
Nesting Layers
I chose to leave the topic of nesting layers until now because they will
effect your scripts in a number of places.
Nesting refers to when you want to group several layers together into
one uniform element. In other words, nested layers are "layers within
layers".
When you do this the child layers will be positioned with respect to
their parent layer. Also, if the parent layer is clipped the child
layers will appear as if in a window or like a plugin. If the child
layers go outside the boundaries (the clip edges) of the parent layer
they will become invisible - their visibility property will not change
but they will appear as if they are offscreen:
I've found nesting comes in handy when you get into more complicated
positioning. Since all the locations of the child layers are with
respect to the parent layer, they are permanently "locked" into
position. If you later want to move the location of the parent layer,
you don't have to change the locations of the child layers because they
will move accordingly. This is also true in sliding animations - all the
child layers will slide in unison.
To nest layers all you do is wrap the parent DIV around all the child
DIV's:
<DIV ID="parent1Div">
<DIV ID="child1Div"></DIV>
<DIV ID="child2Div"></DIV>
29
</DIV>
I have noticeably left out the styles for these DIV tags. This is because
Netscape does not let you nest layers if the styles have been defined
using the "inline" method like I have been using for this tutorial up
until now. Netscape seems to only allow one set of nested layers, if you
use any more then it will totally ignore all the styles for all the
layers after it. So right off the bat we're going to ALWAYS define the
styles using the STYLE tag. All the examples from this point forward
will be done this way.
The CSS is basically the same except it's separated from the DIV tags:
<STYLE TYPE="text/css">
<!--
#parent1Div {position:absolute; left:100; top:80; width:230; height:120;
clip:rect(0,230,120,0); background-color:#C0C0C0; layer-background-
color:#C0C0C0;}
#child1Div {position:absolute; left:-20; top:40; width:70; height:70;
clip:rect(0,70,70,0); background-color:#FF0000; layer-background-color:#FF0000;}
#child2Div {position:absolute; left:180; top:70; width:70; height:70;
clip:rect(0,70,70,0); background-color:#0000FF; layer-background-color:#0000FF;}
-->
</STYLE>
<DIV ID="parent1Div">
<DIV ID="child1Div"></DIV>
<DIV ID="child2Div"></DIV>
</DIV>
I also included the clip regions to define the squares. In most cases
where you use nesting you usually have to define the clip values and
color the layers.
childLayer.style.properyName
document.parentLayer.document.childLayer.propertyName
The extra "document" before the layer names are due to the fact Netscape
30
<DIV ID="parent1Div">
<DIV ID="child1Div">
<DIV ID="child2Div"></DIV>
</DIV>
</DIV>
document.parent1Div.document.child1Div.document.child2Div.propertyName
function init() {
if (ns4) {
parent1 = document.parent1Div
child1 = document.parent1Div.document.child1Div
child2 = document.parent1Div.document.child2Div
}
if (ie4) {
parent1 = parent1Div.style
child1 = child1Div.style
child2 = child2Div.style
}
}
alert(parent1.left)
You will find that you don't receive any value. This is true for all the
CSS properties (left, top, width, height, visibility etc.).
I am still unclear why Microsoft made IE this way. It only occurs when
31
you use the STYLE tag, and only effects the initial values of
properties. Once you start changing the properties in JavaScript you can
then access them without problems.
How does this affect our situation? Well, if we want to assign other
properties as we did earlier (xpos and ypos) we need a way to find the
current location of the layer in some different way for IE4. It's
fortunate that Microsoft included some extra non-standard CSS properties
into IE4:
offsetX
offsetY
offsetWidth
offsetHeight
These extra properties are not effected by the IE4 STYLE tag problem so
we can use those to obtain the current location of the layer. So here's
our new code to add our xpos and ypos properties onto our pointer
variables:
function init() {
if (ns4) {
parent1 = document.parent1Div
parent1.xpos = parent1.left
parent1.ypos = parent1.top
child1 = document.parent1Div.document.child1Div
child1.xpos = child1.left
child1.ypos = child1.top
child2 = document.parent1Div.document.child2Div
child2.xpos = child2.left
child2.ypos = child2.top
}
if (ie4) {
parent1 = parent1Div.style
parent1.xpos = parent1.offsetX
parent1.ypos = parent1.offsetY
child1 = child1Div.style
child1.xpos = child1.offsetX
child1.ypos = child1.offsetY
child2 = child2Div.style
child2.xpos = child2.offsetX
child2.ypos = child2.offsetY
}
}
Once you've done that you can change the locations of the layers as
before.
Again, if you use the STYLE tag to define your layers you will not be
able to obtain the original visibility value in IE4. But in my
experience, obtaining the visibility is very rarely necessary. Usually
you already know if a layer is visible or not. And remember that only
32
Showing and hiding nested layers works pretty much the way you'd expect.
Once you've defined the pointer variables you can use the same show/hide
functions that I explained in the Showing and Hiding lesson.
But there is one thing that I should point out. If you don't define the
visibility for the child layers, their visibility is "inherited" - it
takes on the value of the parent layer's visibility. In that case when
if you then hide or show the parent layer, all the child layers do the
same. BUT... in Netscape if you either define the visibility for the
child layers, or you start changing the visibility in JavaScript you
lose the ability to hide or show all the child layers at once. In that
case when you hide the parent layer, any child layer that is visible
will still show through.
mychild.visibility = "inherit"
Putting it back to inherit means if the parent is shown, the child layer
will also be shown, and if the parent is hidden, it will also be hidden.
33
Changing Images
To create truly dynamic animations and demos you will eventually have to
master the art of changing images on command.
imageA.gif imageB.gif
You must make sure that both images are exactly the same dimensions,
otherwize when you change them, the new image will stretch itself to fit
in the same area. In situations where you want to change images that are
of different sizes you will not be able to use this type of code - you
will have to resort to simply hiding and showing separate layers.
<DIV ID="imgDiv">
<IMG NAME="myImg" SRC="imageA.gif" WIDTH=75 HEIGHT=75
BORDER=0>
</DIV>
Notice that I assigned a NAME to the image (myImg), this name will be
used when changing the image. The name must be totally unique, ie4.
don't name the image the same as the DIV that it's inside otherwise it
won't work. Usually what I do is append "Img" to the end of it as I
append "Div" to the ID of a layer so that no naming conflicts occur.
Preloading Images
Before you can change the image, you have to preload the image into the
browsers cache. This is the basic code to preload an image:
What this does is create an image object. Nothing to it really, just now
we have an object by which we can access the image at any time. Whenever
we need to switch an image it will already be available - you won't have
to wait for the image to download because it will be cached. Since we'll
be needing both imageA and imageB in the cache I have to have code to
preload both of them:
The more images you have to preload, the more you'll dislike having to
rewrite the two lines each time. So instead of writing two lines, let's
cut that down to one by using a generic preload() function:
function preload(imgObj,imgSrc) {
if (document.images) {
eval(imgObj+' = new Image()')
eval(imgObj+'.src = "'+imgSrc+'"')
}
}
where:
Examples:
preload('imageA','imageA.gif')
preload('imageB','imageB.gif')
It's best to preload your images while the page is loading rather than
waiting until after the page loads, so I'd recommend always calling the
preload function immediately after defining it.
Once you've preloaded the images you can the access and change any image
on the page. Changing images that are inside layers works a little
differently between Netscape and IE so first I'll show the explicit code
for changing each, then I will show a generic function that you can use
in any situation.
document.images["imageName"].src = imageObject.src
Where imageName is the name you supplied in the IMG tag, and imageObject
is the name of the preloaded image object.
document.images["myImg"].src = imageB.src
The extra "document" between the name of the DIV and the images is
necessary because Netscape treats DIV's as a totally separate document.
But in Internet Explorer you don't have to do this, you just access it
as if it weren't in a layer at all:
And there you have it. All you gotta do now is put that code into a
separate function and call that function when you want to change it:
function changeToA() {
if (ns4) document.imgDiv.document.images["myImg"].src = imageA.src
if (ie4) document.images["myImg"].src = imageA.src
}
function changeToB() {
if (ns4) document.imgDiv.document.images["myImg"].src = imageB.src
if (ie4) document.images["myImg"].src = imageB.src
}
function changeImage(layer,imgName,imgObj) {
if (document.layers && layer!=null)
eval('document.'+layer+'.document.images["'+imgName+'"].src = '+imgObj+'.src');
else document.images[imgName].src = eval(imgObj+".src");
}
changeImage('imgDiv','myImg','imageA')
changeImage('imgDiv','myImg','imageB')
Notes:
The changeImage() function can also be used for nested layers, for the
layer argument you can insert parentLayer.document.childLayer similarly
to how the Dynamic Layer object handles nested layers.
You can use this function for images thar aren't even in layers, just
36
changeImage(null,'myImg','imageB')
function changeImage(layer,imgName,imgObj) {
if (document.images) {
if (document.layers && layer!=null)
eval('document.'+layer+'.document.images["'+imgName+'"].src = '+imgObj+'.src')
else document.images[imgName].src = eval(imgObj+".src")
}
}
Both the changeImage() and preload() functions are part of the DynAPI
and are in the images.js file:
Mouse Rollovers
I figured that the topic of mouse rollovers has kinda been beaten to
death so I didn't bother covering it previously. However due to the
number of people that asked me about it I'll quickly show how to do it
using my changeImage() function.
The idea behind rollovers is dead simple, when you put the mouse over an
image, it changes to a different image, and when you move your mouse out
of the image, it changes back. To accomplish this you have to surround
the IMG tag with an an anchor/hyperlink and call the changeImage()
function using the onMouseOver and onMouseOut events. The onMouseOver
and onMouseOut events have to be called from the hyperlink because in
Netscape the IMG tag does not have those events built into it.
Remember though, you have to point the anchor to somewhere before you
can use it. Most often rollovers are used in toolbars so you just stick
in the page you want it to go to. But in situations where you don't want
the hyperlink to go anywhere, you can instead insert
javascript:void(null) for the HREF. That is is just a command that does
absolutely nothing. The hyperlink will still exist - it just executes a
javascript command that does nothing.
<DIV ID="imgDiv">
<A HREF="javascript:void(null)"
onMouseOver="changeImage('imgDiv','myImg','imageB')"
onMouseOut="changeImage('imgDiv','myImg','imageA')">
<IMG NAME="myImg" SRC="imageA.gif" WIDTH=75 HEIGHT=75 BORDER=0></A>
</DIV>
37
Layer Writing
The contents of your layers (the text and HTML) can be re-written like a
variable by using a trick. It's well known that Internet Explorer has
the ability to change what's inside a DIV tag, but you can do a similar
thing in Netscape - and that's to use document.write's to re-write the
entire layer.
In Explorer, you can access the HTML inside a DIV tag (or any other tag
for that matter) by writing:
Where divID is the ID of the DIV tag you want to change. You can also
write it another way which I prefer more:
This way it evaluates "divID" as a string which will be more useful for
making it cross-browser capable.
Since each layer is essentially it's own document, it has most of the
capabilities that the main document does. Importantly for this lesson
you can re-write what's in that document.
For a CSS layer, you can use the Netscape Layers() array before using
document.write() and document.close():
document.layers["divID"].document.open()
document.layers["divID"].document.write("my text here")
document.layers["divID"].document.close()
function layerWrite(id,nestref,text) {
if (ns4) {
var lyr = (nestref)?
eval('document.'+nestref+'.document.'+id+'.document') :
document.layers[id].document
38
lyr.open()
lyr.write(text)
lyr.close()
}
else if (ie4) document.all[id].innerHTML = text
}
Using this function all you have to do is tell it what layer to change,
and what text to change it too:
Changing Styles
document.layer["layerName"].document.bgColor = "red"
document.all["layerName"].style.backgroundColor = "red"
function setBGColor(id,nestref,color) {
if (ns4) {
var lyr = (nestref)?
eval('document.'+nestref+'.document.'+id):document.layers[id]
lyr.document.bgColor = color
}
else if (ie4) {
document.all[id].style.backgroundColor = color
}
}
Font Colors
document.all[id].style.color = "#FF0000"
And then using the layerWrite() function, rewrite that layer with a new
color:
However, I will pass that over and avoid the FONT tag altogether. The
other general way to accomplish the task, is to predefine the styles
you're going to use:
<STYLE TYPE="text/css">
<!--
.orange {color:#FF8000;}
.green {color:#008000;}
#mytext {position:absolute; left:50; top:100;}
-->
</STYLE>
And instead of using the FONT tag, I'll use the SPAN tag:
Then my javascript function rewrites the layer and has the CSS CLASS as
an argument:
function mytextColor(color) {
layerWrite('mytext',null,'<SPAN CLASS="'+color+'">My Text</SPAN>')
}
mytextColor('orange') mytextColor('green')
Text Rollovers
Like the font color example, I just manually coded each of the styles I
wanted to use. In my case I have 4 layers each containing the link which
will change color when you roll over and off them:
<STYLE TYPE="text/css">
<!--
A.red {color:red;}
A.blue {color:blue;}
#link0Div {position:absolute; left:50; top:50;}
#link1Div {position:absolute; left:50; top:70;}
#link2Div {position:absolute; left:50; top:90;}
#link3Div {position:absolute; left:50; top:110;}
-->
</STYLE>
41
Since this is just a demo, I've used generic links and names for the
layers:
Notice in the links I've only stated the onMouseOver events. This was a
little trick I came up with. First it displays the onMouseOver, and when
the link changes color, I rewrite the contents of the layer but replace
the onMouseOver with an onMouseOut. That way there's never any
interuption in the rollover sequence.
function linkOver(id,link,text) {
layerWrite(id,null,'<A CLASS="red" HREF="'+link+'"
onMouseOut="linkOut(\''+id+'\',\''+link+'\',\''+text+'\')">'+text+'</A>')
}
function linkOut(id,link,text) {
layerWrite(id,null,'<A CLASS="blue" HREF="'+link+'"
onMouseOver="linkOver(\''+id+'\',\''+link+'\',\''+text+'\')">'+text+'</A>')
}
The format of each of my links are the same, so I only needed to make
the layerName (id), hyperlink location (link), and the displayed text
(text), variables.
function linkOver(num) {
if (ns4) {
for (var i=0;i<link.length;i++) {
if (link[i][3]==true) linkOut(i)
}
}
link[num][3] = true
layerWrite(link[num][0],null,'<A CLASS="red" HREF="'+link[num][1]+'"
onMouseOut="linkOut('+num+')">'+link[num][2]+'</A>')
}
function linkOut(num) {
link[num][3] = true
layerWrite(link[num][0],null,'<A CLASS="blue" HREF="'+link[num][1]+'"
onMouseOver="linkOver('+num+')">'+link[num][2]+'</A>')
}
The HTML for the links also have to be updated, the linkOver() and
linkOut() functions only need to pass the index of the link now.
Of course, this technique can be used to change more than just the
color, you can change other properties of the text by just defining your
CSS values differently. The following makes the underline disappear, and
when the link is on it makes the text in italics:
Font Scaling
The concept of font scaling is exactly the same as the technique for
changing colors. You first pre-define the CSS styles you're going to
use:
.s10 {font-size:10pt;}
.s15 {font-size:15pt;}
.s20 {font-size:20pt;}
.s26 {font-size:26pt;}
function fontScale(size) {
layerWrite('welcomeDiv',null,'<SPAN
CLASS="s'+size+'">Welcome</SPAN>')
}
For the following example I just have simple links that make the text
bigger or smaller:
But of course that's not very fun. We can of course animate our font
scaling so that the text appears to grow, or shrink if we want. To do
that we'll need a lot of styles defined - one for each step in the
sequence. It's easiest to do that in a loop to write out each style. I
decided to have a font-scaling which goes from 10 to 50 points:
I kept the function for cycling through the styles pretty simple. I just
have a variable size which keeps track of which style is currently
shown, and increment that by one each iteration. The if statement (if
(size<50)) will determine when to end the loop.
var size = 10
function scaleWelcome() {
if (size<50) {
size++
44
layerWrite('welcomeDiv',null,'Welcome')
setTimeout('scaleWelcome()',30)
}
}
Then just activate the function and it'll make the text grow
incrementally larger.
That example has the text stuck to the left - as the text grows it
doesn't re-adjust to the new size and ends up looking a little awkward.
To solve that problem you can center the text by using either <DIV
ALIGN="CENTER"> or the old <CENTER> tag. Although when you do this it's
probably best to have a container layer with which you control the
position with, then your text layer resides within it and centered. In
the following example I've shown how to set that up.
45
The content shown in your layer can be called from an external file.
However, the way you do it in Netscape is totally different than for IE
so you will have to create totally browser specific code to do it.
The CSS 1 standard does not have the ability to have it's contents to be
initially called from an external file. But both browsers have a way
that you can accomplish the same thing without using CSS at all.
Netscape's antiquated LAYER tag has an attribute (SRC) by which you can
call an external file to be it's contents. Here's an example:
Layers work exactly the same way that CSS works, except the styles are
made into attributes of the Layer tag.
By combining these 2 techniques you can have a page which loads the
contents automatically.
In Netscape, to change the source for the Layer you change the .src
property:
document.layerName.src = "newfile.html"
In IE, you change the src of the frame as if it were a normal frame:
parent.frameName.document.location = "newfile.html"
For the last example we can make one unified function that will do both
46
function load(page) {
if (ns4) document.textLayer.src = page
else if (ie4) parent.textFrame.document.location = page
}
load("newpage.html")
Advantages:
For example, to call a function in the main document you have to use
parent.functionName() instead of just functionName(). This is because
remember in IE, the contents are in another frame even though it doesn't
look like it.
Disadvantages:
• the IFRAME has the same disadvantages as using normal frames, they are totally
opaque and you cannot lay other layers on top of them
• for the same reason the IFRAME-layer cannot be transparent, and cannot be
nicely manipulated (slid, clipped, etc.) because it causes flickering when IE tries to
redraw it
Hopefully the next version of the browsers they'll include a new CSS
property like source:URL(filename.html) which will solve these problems.
But for a neat hack to get around using IFRAME in the normal manner you
can use technique 2...
First off we need an IFRAME that will be the "buffer". You could do it
in this manner:
<SCRIPT LANGUAGE="JavaScript">
if (ie4) document.write('<IFRAME STYLE="display:none"
NAME="bufferFrame"></IFRAME>')
</SCRIPT>
47
Then you place one DIV which the content file will be loaded into:
<DIV ID="contents"></DIV>
function loadSource(id,nestref,url) {
if (ns4) {
var lyr = (nestref)? eval('document.'+nestref+'.document.'+id) :
document.layers[id]
lyr.load(url,lyr.clip.width)
}
else if (ie4) {
parent.bufferFrame.document.location = url
}
}
function loadSourceFinish(id) {
if (ie4) document.all[id].innerHTML =
parent.bufferFrame.document.body.innerHTML
}
The main part of the function will load the DIV directly with the
external file in Netscape. But in IE will it will load the buffer frame
named bufferFrame with the external file. The next obstacle is that you
need a way to determine when the external file is completely loaded, so
that you can then transfer the contenst of the frame to the DIV. There
is hack that will work in IE (see Inside DHTML), but it won't work in
Netscape. I forsee that it will be necessary for this to be done in
Netscape so I will resort to using BODY onLoad in the external file. You
merely call the loadSourceFinish() function and pass what DIV needs to
be updated:
<BODY onLoad="parent.loadSourceFinish(id)">
This is done in the external file. This is the external file I used in
the example below.
<HTML>
<HEAD>
<TITLE>Content Page</TITLE>
</HEAD>
<BODY onLoad="parent.loadSourceFinish('contents')">
</BODY>
</HTML>
For Netscape, forms and layers don't work so well together. Again since Netsape
layers are essentially a whole different document, a form that crosses over several
layers cannot be accomplished. For example, the following will work fine in IE, but in
Netscape it won't work:
<FORM>
<DIV ID="layer1">
<INPUT TYPE="Text" NAME="field1" SIZE="10">
</DIV>
<DIV ID="layer2">
<INPUT TYPE="Text" NAME="field2" SIZE="10">
</DIV>
</FORM>
The solution is that you have put a form into each layer:
<DIV ID="layer1">
<FORM NAME="form1">
<INPUT TYPE="Text" NAME="field1" SIZE="10">
</FORM>
</DIV>
<DIV ID="layer2">
<FORM NAME="form2">
<INPUT TYPE="Text" NAME="field2" SIZE="10">
</FORM>
</DIV>
But this poses a problem when you want to capture the values of the fields in
JavaScript or if you want to send the information to a CGI program. What you end up
having to do is "glue" the data together using JavaScript and then doing whatever
want with that information.
Remember in Netscape you have to reference the form with whatever layer it's within:
document.layerName.document.formName.fieldName.value
document.formName.fieldName.value
Using this idea you can create some code that will extract the data from each field:
if (ns4) {
field1value = document.layer1.document.form1.field1.value
field2value = document.layer2.document.form2.field2.value
}
if (ie4) {
50
field1value = document.form1.field1.value
field2value = document.form2.field2.value
}
But what if you want to then send that information to a CGI program? CGI's can only
accept values from one form. So what you can do is create yet another form
with hidden fields:
<DIV ID="layer1">
<FORM NAME="form1">
<INPUT TYPE="Text" NAME="field1" SIZE="10">
</FORM>
</DIV>
<DIV ID="layer2">
<FORM NAME="form2">
<INPUT TYPE="Text" NAME="field2" SIZE="10">
</FORM>
</DIV>
The hidden fields in mainForm can be assigned the values from the other forms. Then
you can send that form to a CGI to interperet the data:
function sendForm() {
if (ns4) {
document.mainForm.field1.value =
document.layer1.document.form1.field1.value
document.mainForm.field2.value =
document.layer2.document.form2.field2.value
}
if (ie4) {
document.mainForm.field1.value = document.form1.field1.value
document.mainForm.field2.value = document.form2.field2.value
}
document.mainForm.submit()
}
I've made a simple demo that should show how this can be used in a real application.
It's a simple 6-element form that could be used in a feedback form of some sort. I've
also created a Perl script that simply returns back what was sent to it in a generated
HTML page.
forms1.html - has each element split up into different forms and a function to glue the
data together and send it to the Perl script. This was just to make sure my JavaScript
and Perl scripts were working properly.
forms2.html - has each form in different layers and then allows you to flip between
them by simply hiding and showing the appropriate layers. Then when you get to
the last field you can submit the form.
51
Page Templates
Using JavaScript/CSS page templates makes it easier to create an entire web site
that has consistent features that are "site-wide" such as toolbars and default styles.
By linking each of your pages to external stylesheets (.css files) and using external
JavaScript files (.js files) to write out your layers you can assemble your pages on
the fly and have a central location for changing parts of your pages throughout your
website. This is similar to what Server-Side Includes (SSI) do but when you use
JavaScript files you have a lot more control over what gets written to the browser.
For example you can determine what browser is being used and change the look
of the page accordingly, or you can do other things like center all the layers (as in
the Generating Layers lesson).
Also page templates, if used properly, can render the use of frames almost totally
obsolete. When you use frames in unison with layers, you are limited in that your
layers cannot cross over the frame borders. But by dynamically writing layers
throughout your site you can do anything that you can in a single layers page.
Your CSS can be linked from one source file by using the LINK tag:
That file can contain any stylesheet information that needs to be implemented across
any number of html files (each of which must contain that LINK tag).
And similarly you can assign the source file for your JavaScript by using the SRC
attribute:
When you're developing page templates, I'd suggest you develop them as normal (all
in one file) and then once you've got everything working, cut and paste the
pieces into separate files. The following page is setup so that it's easy to extract the
styles and the JavaScript which writes out the standard links for the page:
<HTML>
<HEAD>
<TITLE>The Dynamic Duo - Page Templates Demo 1</TITLE>
<STYLE TYPE="text/css">
<!--
#title {position:absolute; left:100; top:10; width:300; font-size:18pt; font-
weight:bold;}
#links {position:absolute; left:10; top:40; width:100; font-size:12pt;}
#content {position:absolute; left:100; top:55; width:400; font-size:10pt;}
BODY {font-family:"Arial";}
-->
</STYLE>
</HEAD>
<BODY>
53
<SCRIPT LANGUAGE="JavaScript">
document.writeln('<DIV ID="links">');
document.writeln('<B>Links:</B>');
document.writeln('<BR><A HREF="#">Page 1</A>');
document.writeln('<BR><A HREF="#">Page 2</A>');
document.writeln('<BR><A HREF="#">Page 3</A>');
document.writeln('</DIV>');
</SCRIPT>
<DIV ID="content">
<P>This is the body content....
</DIV>
</BODY>
</HTML>
Once that works, you can then make the CSS and JavaScript separate files that you
link to:
<HTML>
<HEAD>
<TITLE>The Dynamic Duo - Page Templates Demo 2 [External Files]</TITLE>
<LINK REL=STYLESHEET HREF="mystyles.css" TYPE="text/css">
</HEAD>
<BODY>
<DIV ID="content">
<P>This is the body content...
</DIV>
</BODY>
</HTML>
Object oriented programming (OOP) takes a little to get used to, but I
assure you if you've understood everything up until now, you (probably)
won't have any difficulty understanding this lesson. Using Object
Oriented DHTML is the next progression and will allow you to build more
sophisticated scripts in an organized way.
You want to use objects whenever you need to create more than one of
something. By creating one generic object, you can use it to create any
number of them without duplicating much of your code. This has a big
impact on your coding because it means you don't have to write as much
code, everything is organized nicely, and the JavaScript will execute
faster.
OOP Overview
When you create objects, really what you are doing is collecting a lot
of different variables into a nice neat package which you can then
easily access at a later time. Each object is it's own package, and the
variables in that package are referred to as properties. You can then
write functions which manipulate only the variables (properties) of that
object, these are referred to as methods.
Using objects are helpful because they are designed so that they can be
cloned to make many different objects that will be used similarly.
Without confusing you yet, say we had a generic object named "block".
Then we decided we want to have several blocks (block1, block2, block3
etc.) that all had similar variables (height, width, depth etc.). Using
object oriented programming, the variables in the blocks are all
collected together in this format:
So to create this generic "block" object, the code would look something
like this:
function block(width,height,depth) {
this.width = width
this.height = height
55
this.depth = depth
}
function block(width,height,depth) {
this.width = width
this.height = height
this.depth = depth
this.color = "red"
}
...would make the width, height, and depth change depending on how it's
initialized, but all the blocks would have a color property of "red".
Once this generic object function is created, you can clone objects
based on this code, this is referred to as creating an instance of an
object. The format for creating instances of a self-defined object is as
follows:
So to create the block objects named block1, block2, and block3 you'd
write:
After they're defined you can do whatever you'd like with the object,
check it's properties, or change them. Properties of an object work
exactly the same way as normal variables do.
Once you've got the object function itself created, you can extend the
functionality of the object by creating methods for it. The format for
creating a method is as follows:
function objectName(arguments) {
this.propertyName = somevalue
this.methodName = methodFunction
}
The methodFunction() works like any other function, except when you call
the function you add the name of the object you want to apply the
function to:
newObjectName.methodFunction()
56
So using this idea, we can add a method to the block object that
calculates the volume of the block and returns the value:
function block(width,height,depth) {
this.width = width
this.height = height
this.depth = depth
this.color = "red"
this.volume = blockVolume
}
function blockVolume() {
return this.width*this.height*this.depth
}
block1.volume()
newobjects1.html has this simple block object and allows you to check
the values of the properties and the volume using alert's.
Using the concepts from previous lessons, we can apply object oriented
programming to make working with layers a much easier task. Instead of
always re-writing initialization code for pointer variables, you can
create generic objects that do the same thing with just one line of
code.
function layerObj(id) {
if (ns4) this.css = document.layers[id]
else if (ie4) this.css = document.all[id].style
}
To use this layerObj object, you can now initialize your pointer
variables with only:
57
Once the layer1 object is initialized you can access the css properties
of "layer1Div" by using:
So all that's really changed is the extra css between everything. We can
further extend the layerObj object by automatically defining x and y
properties which will represent the left and top properties of the
layer:
function layerObj(id) {
if (ns4) {
this.css = document.layers[id]
this.x = this.css.left
this.y = this.css.top
}
else if (ie4) {
this.css = document.all[id].style
this.x = this.css.pixelLeft
this.y = this.css.pixelTop
}
}
layer1.css
layer1.x
layer1.y
Note that we can now use x and y as our properties because this is our
own object we're working with. I was using xpos and ypos before because
Netscape already included those properties as part of it's own Layer
object. Since we're creating a new object we can name the properties
whatever we like.
Making Methods
function moveBy(obj,x,y) {
obj.xpos += x
obj.left = obj.xpos
obj.ypos += y
obj.top = obj.ypos
}
58
function moveTo(obj,x,y) {
obj.xpos = x
obj.left = obj.xpos
obj.ypos = y
obj.top = obj.ypos
}
Those functions translated into methods of the layerObj object look like
this:
function layerObj(id) {
if (ns4) {
this.css = document.layers[id]
this.x = this.css.left
this.y = this.css.top
}
else if (ie4) {
this.css = document.all[id].style
this.x = this.css.pixelLeft
this.y = this.css.pixelTop
}
this.moveBy = layerObjMoveBy
this.moveTo = layerObjMoveTo
}
function layerObjMoveBy(x,y) {
this.x += x
this.css.left = this.x
this.y += y
this.css.top = this.y
}
function layerObjMoveTo(x,y) {
this.x = x
this.css.left = this.x
this.y = y
this.css.top = this.y
}
We can then move the layer by using either the moveBy() or moveTo()
methods:
This tiny object would only be used in very specific instances where all
you need to do is move the layer around in a simple manner. The way I've
made this object doesn't account for nested layers, so if you need to
use nested layers you'd have to change the code to include the extra
parent layers. The above object is actually derivative of The Dynamic
59
BrowserCheck Object
is = new BrowserCheck()
is.b - (String) browser name, converted to "ns" if Netscape, "ie" if Internet Explorer
is.v - (integer) version number (2,3,4,5 etc.)
is.ns - (boolean) Netscape 4 or greater
is.ns4 - (boolean) Netscape 4
is.ns5 - (boolean) Netscape 5
is.ie - (boolean) Internet Explorer 4 or greater
is.ie4 - (boolean) Internet Explorer 4
is.ie5 - (boolean) Internet Explorer 5
is.min - (boolean) Netscape 4 or 5, or Internet Explorer 4 or 5
This combination of properties will serve almost all your needs with
respect to DHTML - that's all this object is designed to do. You could
extend this to check for operating systems and mime-plugins or whatever.
Or, if you are already using the DynLayer you don't need to do anything.
The DynLayer has the BrowserCheck Object in its source.
60
The Dynamic Layer Object API (DynLayer) is a lightweight object that provides a
highly flexible manner of working with layers. Not only does it have common
properties and methods for manipulating layers, it is an object based API which opens
up a new way of working with layers that far exceeds the traditional way of coding
DHTML. I've found it to be the ideal foundation for nearly every application of DHTML
including animation, applications, and gaming environments.
All of the next lessons in this tutorial will use the DynLayer as the basis for
accomplishing some other task, so it is important that you understand how it works
and how to use it.
Instead of using the true commands for manipulating layers, you make a DynLayer
object and tell it the name of the layer that it will be used for. The DynLayer object
will have 3 main properties:
This manner gives us a similar way to access properties and methods of layers no
matter which browser is being used.
The DynLayer then has other properties such as (x,y) to hold various information
needed to operate the DynLayer, and has methods such as hide() show() and
slideBy() and slideTo() to manipulate the layer. Read the next sections of the
DynLayer for specific usage of these methods.
61
Initialization of DynLayers
The DynLayer can be applied to any layer using this general format:
Where:
objectName Name of the object - how you will reference the DynLayer object.
id ID of the layer to which this DynLayer is being applied to, this cannot be the same
as the objectName
iframe Name of the iframe that the layer is contained. This is used when you need to
manipulate a layer inside an IFrame. Currently IFrame is only a feature of is.ie.
Let's say you have a very simple layer with this structure:
<DIV ID="mylayerDiv"></DIV>
If you wanted to use the nestref parameter you could initialize both of
them like this::
However, nestref is now optional, so you if you don't send the nestref
parameter for the nested layer it will still work:
DynLayer('mylayerDiv')
Notice the name of the parent layer is passed for the nestref argument.
<DIV ID="myparent1Div">
<DIV ID="myparent2Div"> <DIV
ID="mylayerDiv"></DIV>
</DIV> </DIV>
In this case if you need to use the nestref parameter you must pass the
names of all layers in that hierarchy separated by '.document.':
mylayer = new
DynLayer('mylayerDiv','myparent1Div.document.myparent2Div')
The DynLayer can be used to work with layers that are withing separate
Frames, or IFrames (for IE only). In this case sending a nestref
parameter for nested layers is manditory.
Note: This has changed, you must now send the frame reference as an
object, no longer as a string
<DIV ID="mylayer1"></DIV>
<DIV ID="mylayer2"></DIV>
<DIV ID="mylayer3"></DIV> <DIV ID="mylayer4"></DIV>
DynLayerInit() Function
<STYLE TYPE="text/css">
#blueDiv {position:absolute; left:50; top:50;}
</STYLE>
<DIV ID="blueDiv"></DIV>
Also note, the names of your layers may be something other than with the
"Div" extension. However, DynLayerInit() will not automatically define
these, although it will find it's nestref value so that you don't have
to pass it it.
64
DynLayer Properties
Once you have applied the DynLayer to a layer, you have a unified way of
referencing the layer for both Netscape and IE. From that point on you
can use the DynLayer to retrieve and change the properties of the layer
using a single command.
Core Properties
The css property is the way you directly reference the CSS properties of
the layer. It is the equivalent to how I've used pointers in previous
lessons. For Netscape it points to document.layer[id], and for IE it
points to document.all[id].style
When you need to retrieve or change any of the CSS properties you
reference the property in this manner:
objectName.css.propertyName
mylayer.css.zIndex
This can be done for any of the CSS properties. However in practice it
is rarely necessary to call the css property manually because the
DynLayer takes care of most of them: left, top, visibility, and clip
value. The only property that you'd ever really need to access using the
css property is zIndex.
The x and y properties always contain the current location of the layer.
They are equivalent to how I've been using the xpos and ypos properties
in previous lessons. Using these separate properties is a personal
preference of mine because I fell it is cleaner (as well as more
efficient) to access the location of a layer as a property.
objectName.x or objectName.y
As before, you have to ensure that the x and y properties are in synch
with the actual left and top properties by copying the values:
But you often never have to do that because I've included the moveTo()
or moveBy() methods which change the location of the layer for you.
Instead of having the x and y properties you could optionally write your
own methods like getLeft() or getTop() for extracting the current
location. But you can do that on your own if you want to.
Just as you can retrieve the location of the layer using x and y, you
can retrieve the width and height of the layer using w and h:
objectName.w or objectName.h
The doc property can be used when you want to access other elements
inside the layer such as images and forms. In Netscape it points to
document.layers[id].document, but in IE it points just to the main
document. This is necessary because Netscape handles contents in layers
as separate documents.
mylayer.doc.elementName
Read the Changing Images and Working With Forms lessons for more
thorough explanations on those topics. There is also the image() method
extenson.
setTimeout(this + ".methodName()",100)
setTimeout(this.obj + ".methodName()",100)
The obj property is used by addon methods such as the slide and wipe
methods, as well as other objects that use the DynLayer, and all my
widget objects. It's extremely useful.
Pre-requisite: You should read the Document Mouse Events lesson before
reading this one.
Using the elm property you can define events for your layer such as
onMouseOver, onMouseOut, onMouseDown, onMouseUp, onMouseMove, and
onClick.
In Netscape you can't mark up the event handlers like you can with an
anchor:
However, you can define handlers directly using JavaScript. For Netscape
you use:
document.layer[id].captureEvents(Event.MOUSEDOWN)
document.layer[id].onmouseover = downHandler
For IE:
document.all[id].onmouseover = downHandler
For a cross-browser solution you can define the handlers using the
DynLayer elm property (which points to the actual element rather than
the CSS properties):
objectName.elm.onmousedown = layerDownHandler
objectName.elm.onmouseup = layerUpHandler
objectName.elm.onmouseover = layerOverHandler
objectName.elm.onmouseout = layerOutHandler
objectName.elm.onmousemove = layerMoveHandler
objectName.elm.onclick = layerClickHandler
if (is.ns) objectName.elm.captureEvents(Event.MOUSEDOWN | Event.MOUSEUP |
Event.MOUSEMOVE)
67
Make sure you define the mouse events using all lowercase. The handler
names can be whatever you choose.
For Netscape you have to manually capture the events that you want to
use. You can see in the captureEvents line how to set them up. If you
don't need one of them just remove it from the command. You do not need
to capture the mouseOver and mouseOut events as it appears they are
captured by default.
function layerDownHandler(string) {
status = string
}
There is yet another way to pass parameters, you can temporarily define
sub-properties to the dynlayer event property, and retrieve them in the
handler function:
function layerDownHandler() {
status = this.string
}
In the handler functions you can retrieve the location of the mouse by
using the following commands:
The x and y variables can then be used to do whatever you wish. For
example, here's some code that will display the location of the mouse
while over the layer:
function init() {
mylayer = new DynLayer("mylayerDiv")
mylayer.elm.onmousemove = layerMoveHandler
if (is.ns) mylayer.elm.captureEvents(Event.MOUSEMOVE)
}
function layerMoveHandler(e) {
var x = (is.ns)? e.layerX:event.offsetX
var y = (is.ns)? e.layerY:event.offsetY
status = x+","+y
68
The core methods (moveTo(), moveBy(), show(), and hide()) are by default
included in the DynLayer. They are always available because they are
used quite often.
objectName.moveTo(x,y)
If you need to only change one of x or y why you can send null for the
value:
objectName.moveTo(x,null) or objectName.moveTo(null,y)
Examples:
objectName.moveBy(x,y)
Example:
mylayer.moveBy(5,0)
For changing the visibility I've included the methods show() and hide().
Their are no parameters to pass so their usage is simple:
objectName.show() objectName.hide()
Slide Methods
objectName.slideTo(endx,endy,inc,speed,fn)
Where:
endx - the final x coordinate
endy - the final y coordinate
inc - the incrementation amount (the number pixel units to move each repetition)
speed - the speed of repetition in milliseconds
fn - (optional) the function or statement to be executed when the slide is complete
If you want the DynLayer to slide in a horizontal line pass null for the
endy value. And if you want the DynLayer to slide in a vertical line
pass null for the endx value.
Examples:
mylayer.slideTo(100,50,10,20)
mylayer.slideTo(80,null,5,30)
When using the fn property from a hyperlink you must do a trick with the
quotes:
objectName.slideBy(distx,disty,inc,speed,fn)
70
Where:
distx - the amount of pixels to shift horizontally
disty - the amount of pixels to shift vertically
inc - the incrementation amount (the number pixel units to move each repetition)
speed - the speed of repetition in milliseconds
fn - (optional) the function or statement to be executed when the slide is complete
If you want the DynLayer to slide in a horizontal line pass 0 for the
endy value. And if you want the DynLayer to slide in a vertical line
pass 0 for the endx value.
Examples:
mylayer.slideBy(-40,60,5,20)
mylayer.slideBy(50,0,5,20)
Making Sequences:
I left the fn property so that you always have a way of determining when
the slide is complete. By taking advantage of this feature you can link
a series of slide()'s together to make a sequence of animations. Here's
an easy way to accomplish a sequence:
seq1 = 0
function sequence1() {
seq1++
if (seq1==1) {
// commands for first part of sequence
// link the slide back to this function to keep it going
mylayer.slideBy(50,0,10,20,'sequence1()')
}
else if (seq1==2) {
// commands for seconds part of sequence
}
else seq1 = 0 // reset to 0 so you can play the sequence again
}
onSlide Handlers
I have not put these handlers to large use, but it seems to work pretty
well, and are perhaps better to use than the "fn" parameter in the
slideBy() and slideTo() methods.
mylayer.slideInit()
mylayer.onSlide = mylayerOnSlide // some function that runs each step in the slide
mylayer.onSlideEnd = mylayerOnSlideEnd // some function that runs when completed
the slide
Clip Methods
The clip methods give you a simple way to clip your DynLayers in the
same manner that I used the clip functions in the Clipping Layers
lesson.
Note: although the clip methods are now automatically assigned to all
DynLayers, you may still have to call the clipInit() method to initially
clip the layer so that IE will have base clip values to work from. I
will be looking into this further to hopefully remove this necessity.
This occurs when you have either a) have defined no clip values in your
CSS, or b) the values you have defined in your CSS are equal to that of
the width and height of the layer. In either case, to use the clip
methods you must define the width and the height of the layer in your
CSS. To initialize the clip methods in situation 1, you can use:
objectName.clipInit()
If you have clipped your layers other than that of the default values
you must initialize the clip methods in a different manner. For example
if your layer has a width of 100 and a height of 50, but you have
clipped your layer to (-10,-10,60,110), then you must pass those values
to the clipInit() method:
objectName.clipInit(clipTop,clipRight,clipBottom,clipLeft)
Example:
mylayer.clipInit(-10,-10,60,110)
Once you have initialized the clip methods, the DynLayer adds 3
additional methods by which you can retrieve or change the clip values.
72
The clipValues() method is used to retrieve the current clip value for
any of the 4 edges.
objectName.clipValues(which)
For the which argument, you pass the letter in quotes signaling which of
the edges you want to find the value of. So there are four different
ways of calling the method:
The clipTo() method will clip the edges of the layer to the specified
values:
objectName.clipTo(t,r,b,l)
Where: t - new clip top value r - new clip right value b - new clip
bottom value l - new clip left value
For any of the values which you do not want to change pass null for the
value.
Examples:
mylayer.clipTo(0,25,50,0) mylayer.clipTo(null,50,null,null)
The clipBy() method clips the edges based on their current value (as
moveBy() is to moveTo()). The usage is the same as clipTo():
objectName.clipBy(t,r,b,l)
IE 5.0 Notes:
(only for IE because Netscape cannot change the true dimensions on the
fly).
Write Method
The DynLayer Write method allows you to re-write the contents of the
layers by using the document.write() command for Netscape, and the
innerHTML property for IE. Please read the Layer Writing lesson for a
full explanation of what's really going on when writing a layer.
Example:
DynLayer Functions
I'm including the css() function in the DynLayer because many of the
later lessons in this tutorial are using it. This is more a personal
preference than a necessity. Read the Generating Layers lesson for an
explanation of this function.
74
It is quite easy to add you own methods to the DynLayer. Just create
your own function:
function DynLayerMyNewMethod() {
// code for this method
}
This method is not available to the DynLayer until you "attach" it.
There are 3 ways to do this
1. Use the prototype command (recommended) This way your method will be
available to all DynLayers that you define
DynLayer.prototype.myNewMethod = DynLayerMyNewMethod
You can either make your own .js file and include both the function and
the prototype call in that function, or include these in the dynlayer.js
source file itself.
function DynLayer(id,nestref,frame) {
... code in constructor
this.myNewMethod = DynLayerMyNewMethod
}
mylayer.myNewMethod = DynLayerNewMethod
If you require an addition to the DynLayer which contains it's own set
of properties and several methods, you may want to make it it's own
object and append it to the DynLayer. What I suggest you do is pass the
new object information so that it is still able to update the DynLayer.
Do do this the object will require the name of the DynLayer, as well as
75
function MyObject(dynlayer,name) {
this.dynlayer = dynlayer
this.name = name
this.value = eval(this.dynlayer+'.x') + 100 // use eval's to capture data
from the Dynlayer
this.method = MyObjectMethod
this.repeat = MyObjectRepeatMethod // repeats MyObjectMethod using
setTimeout
}
function MyObjectMethod() {
eval(this.dynlayer+'.moveBy(10,10)') // use eval's to assemble the name
of the DynLayer
}
function MyObjectRepeat() {
setTimeout(this.dynlayer+'.'+this.name+'.method()',50) // use eval's to
assemble the name of the object's method
}
Then to use the add-on object you use this general format:
objectName.myobject.method() or
objectName.myobject.repeat() etc.
This tactic is used by the Geometric Objects, and the Path Object.
If you want one object to control multiple layers, your best bet is to
assign properties which are in fact DynLayers.
Option 1: Send the object the names of the layers, and let the object
define the DynLayers
function MyObject(lyr1,lyr2) {
this.lyr1 = new DynLayer(lyr1)
this.lyr2 = new DynLayer(lyr2)
}
This way, the main object (MyObject) can control both those layers by
using the properties and methods of those DynLayers. For example you
could create a method by which it slides both layers in unison:
function MyObject(lyr1,lyr2) {
this.lyr1 = new DynLayer(lyr1)
this.lyr1.slideInit()
76
Option 2: Pre-define your DynLayers and send the object the names of the
DynLayers
function MyObject(dynlayer) {
this.dynlayer = dynlayer // do something with this.dynlayer
}
Note: As of the June 23 update to the DynLayer you must also set the
prototype of your object to the Dynlayer's prototype in order to attach
the methods.
Be aware, this is not the same thing as the above section. The above
section makes the DynLayer a property of an object. Encapsulation means
that this object actually becomes a DynLayer that has it's own set of
properties and methods.
function MyObject(id,nestref) {
this.dynlayer = DynLayer
this.dynlayer(id,nestref)
}
MyObject.prototype = DynLayer.prototype
77
What this does is assigns all the properties and methods of the DynLayer
to this object. It is in fact a DynLayer itself because you work with it
in the same manner...
So what advantage does this have? Well this is the ultimate way of
extending the DynLayer because you can add much more functionality to
this object. This technique is the ideal way to make a back-end to a
DHTML game, where you need many types of objects that do different
tasks, yet they all need to control layers like the Dynlayer does.
These are commonly used additions that you may want to use. Note: these
examples are shown for a DynLayer that is named 'mylayer' but they will
work for all DynLayers of any name.
When you want to use these functions all you have to do is include the
dynlayer-common.js file after the dynlayer file:
mylayer.load('myfile.html',fn)
In the main HTML document you must have an hidden IFrame named
"bufferFrame", this is used to copy the contents of the external file
into the layer.
The external html file must call the DynLayer's loadFinish() method.
Since in IE, the main document is in a different frame, we must call it
as "parent". Fortunately this is simultaneously compatible for Netscape
because in Netscape it is all the same document, and therefore in that
case "parent" is synonymous with "document".
<BODY onLoad="parent.mylayer.loadFinish()">
Warnings: This method will not work "as-is" if these these files are all
to be contained within another frameset. In that case you'd need to send
an additional parameter for the name of the frame instead of "parent".
78
Nor will this work if you want to load multiple files simultaneously
into separate layers. This function assumes there's only one IFrame, and
hence only one file in a buffer-zone. If you wanted multiple files to be
buffered like this you'd have to have separate IFrames, and yet another
parameter to determine which frame to take the contents from.
Simply sets the background color of the layer. Watch out though, you
usually have to have the layer clipped, and you'll sometimes run into
problems with text that's contained within the layer. I'll leave you to
encounter all the "fun" with this function :)
mylayer.setbg('#ff0000')
mylayer.img('myImg','myImgObject')
// image must have a NAME assigned, index values won't work between both
browsers
<div id="mylayerDiv"><img src="myimg.gif" name="myImg"></div>
This function can be used to find the actual left location of the layer
(relative to the document). This only has to be used when you've
positioned your layer relatively
In order to set your CSS to make a relatively positioned layer you can
use either of the following:
Then to find the actual left location use the getRelativeX() method:
var x = mylayer.getRelativeX()
var y = mylayer.getRelativeY()
When you don't specify a height in your CSS, you can still obtain what
the actual height of the contents of that layer is by using the
getContentHeight() method. Note this uses the same tactic as shown in
the Scrolling Concepts lesson, however that lesson does the true call
for this value explicitly.
var h = mylayer.getContentHeight()
var w = mylayer.getContentWidth()
Wipe Methods
The wipe methods are animated versions of the clip methods (as slide is
to the move methods).
When you want to use these functions all you have to do is include the
dynlayer-wipe.js file after the dynlayer file:
The wipeTo() method will wipe (clip incrementally) the DynLayer's edges
from their current value to specific new value. It can do this for any
single edge, or multiple edges at the same time.
objectName.wipeBy(endt,endr,endb,endl,num,speed,fn)
Where:
endt - final clip top value
endr - final clip right value
endb - final clip bottom value
endl - final clip left value num - the total number of steps in the wipe sequence
speed - speed of repetition in milliseconds
fn - (optional) function or statement to execute when the wipe is complete
For any of the edges which you do not wish to be clipped, pass null for
80
it's value.
Examples:
To wipe the DynLayer's top edge to 0, right to 100, bottom to 100, and
left to 0 (making a square box 100x100), in 10 steps, at 30 milliseconds
per step:
mylayer.wipeTo(0,100,100,0,10,30)
mylayer.wipeTo(null,100,null,null,10,30)
Again the wipeBy() is the same as the wipeTo() except the edges are
shifted a given number of pixels:
objectName.wipeBy(distt,distr,distb,distl,num,speed,fn)
Where:
distt - clip top increment
distr - clip right increment
distb - clip bottom increment
distl - clip left increment
num - the total number of steps in the wipe sequence
speed - speed of repetition in milliseconds
fn - (optional) function or statement to execute when the
wipe is complete
For any of the edges that you do not wish to be clipped pass 0 for it's
value.
Examples:
mylayer.clipBy(-20,20,20,-20,5,30)
mylayer.clipBy(20,-20,-20,20,5,30)
mylayer.clipBy(0,100,0,0,5,30)
When working with the wipe methods you have to keep your orientation
correct. Remember how positive and negative values will effect each of
the edges:
Glide Methods
The Glide methods are almost the same as the Slide Methods except they
use a different formula for moving the layer. The Slide methods are
simple straight-line animations, whereas the Glide methods use
trigonometric math to create a subtle acceleration or deceleration
effect. The result is some very slick looking animations.
As with the Wipe methods, I've made the Glide library a separate
javascript file, dynlayer-glide.js. You must call include this file in
any code that uses the glide methods:
objectName.glideTo(startSpeed,endSpeed,endx,endy,angleinc,speed,fn)
Where
startSpeed - "slow" to begin slowly (acceleration), "fast" to begin fast (deceleration)
endSpeed - "slow" to end slowly (decelaration), "fast" to end fast (acceleration)
endx - final x-coordinate
endy - final y-coordinate
angleinc - the angle incrementation (read below)
speed - speed of repetition in milliseconds
fn - (optional) function or statement to execute when complete
The angleinc parameter is probably the only one which isn't obvious. The
glide methods use a Sine wave as the basis for the acceleration, and the
angleinc simply determines how many degrees to jump each time. The
bigger the angleinc, the bigger the jumps it will make. So it is similar
to the inc value in the Slide methods - usually a value from 5 to 10 is
good to use.
mylayer.glideTo("slow","slow",50,50,10,20)
Same as all the others, glideBy() shifts the location by a given number
of coordinates:
82
objectName.glideBy(startSpeed,endSpeed,distx,disty,angleinc,speed,fn)
Where distx and disty, are now the amount it will shift by.
Geometric Objects
Note: I'm told there's a few bugs in these, so use at your own risk :)
The Geometric Objects provide a solution for doing animation along a path of a
geometric shape. They could be used in demos and games if need be. They are built
as add-on objects to the DynLayer. For example, any DynLayer that you have can
also have a geometric object added on to it.
Circle Object
Initialization:
The Circle Object is an addon object to the DynLayer. You must make a
new property onto the DynLayer and make that the Circle Object:
Example:
You must pass the name of the DynLayer and the name of the new circle
object (which would usually be "circle") to the Circle Object. These 2
pieces of information are needed in order for the circle object to
access the DynLayer.
The play() method begins the slide along a circular path. You must pass
information to the play() method that define the shape and properties of
the circle:
objectName.circle.play(radius,angleinc,angle,endangle,speed,fn)
Where:
83
Examples:
mylayer.circle.play(100,5,0,90,20)
mylayer.circle.play(100,5,0,null,20)
The pause() method will pause the current circular path. The next time a
play() method is called it will continue along the same circular path:
objectName.circle.pause()
The stop() method will stop the current circular motion. The next time a
play() method is called it will slide along a new circular path.
objectName.circle.stop()
Hover Object
Initialization:
Example:
84
objectName.hover.play(amplitude,angleinc,angle,cycles,orientation,speed,
fn)
Where:
amplitude - the amplitude of the hover motion
angleinc - angle incremention
angle - starting angle
cycles - the number of cycles to move before stopping
orientation - 'v' for vertical, 'h' for horizontal
speed - speed of repetition
fn - (optional) function or statement to execute when complete
Notes: If you want the hover to keep looping then pass null for the
cycles value.
Examples:
mylayer.hover.play(60,8,0,1,'v',10)
The SineWave Object will slide the layer along a sine wave path.
Initialization:
Example:
objectName.sinewave.play(amplitude,wavelength,angleinc,angle,cycles,
direction,speed,fn)
Where:
amplitude - the amplitude of the wave
wavelength - length of one wave
angleinc - angle incremention
angle - starting angle
cycles - the number of cycles
direction - use 1 for right, -1 for left
speed - speed of repetition
fn - (optional) function or statement to execute when complete
Notes:
If you want the sinewave to keep looping then pass null for the cycles value.
Examples:
mylayer.sinewave.play(60,200,15,0,2,1,20)
Parabola Object
The Parabola Object will slide the layer along a parabolic path.
Initialization:
Example:
objectName.parabola.play(type,distx,disty,xinc,speed,fn)
Where:
type - 1 is a dropping parabola, 2 is an arcing parabola
distx - horizontal distance
disty - vertical distance
xinc - the number of pixels to move horizontally each repetition
speed - speed of repetition
fn - (optional) function or statement to execute when complete
Examples:
86
mylayer.parabola.play(1,200,200,5,20)
Work the same as in the circle() method. I didn't bother with a pause
method cause I can't see it being useful.
objectName.sinewave.stop()
Gif Animation
When it comes to making dynamic web pages and animations, using animated
GIF's would seem to help things. Unfortunately, animated GIF's have no
built in controls - you can't start, stop, or pause the animation on
command. Although it is possible to mix animated GIF's and non-animated
GIF's to mimic the effect, it really doesn't work that well. This makes
them unsuitable when trying to do anything complex with them. That is
why I made my own Gif Animation Object (GifAnim). It is a piece of code
which gives you the kind of control that is necessary for any type of
gif animation sequence you can think of.
The GifAnim Object requires that you have a series of preloaded image
objects already created that are named something like image0, image1,
image2 etc. where 0 is the first frame of the animation, 1 is the second
and so on.
num0.gif
num1.gif
num2.gif
num3.gif
num4.gif
num5.gif
To preload this Image Series I can use the preload function from the
previous lesson.
function preload(imgObj,imgSrc) {
eval(imgObj+' = new Image()')
eval(imgObj+'.src = "'+imgSrc+'"')
}
preload('num0','num0.gif')
preload('num1','num1.gif')
87
preload('num2','num2.gif')
preload('num3','num3.gif')
preload('num4','num4.gif')
preload('num5','num5.gif')
That way it can be used for any number of images by changing the for
loop arguments.
Whatever is the consistant name in the series is the Image Series name -
in my case it would be "num" because each image object name starts with
num.
Remember you initially have to show one of the images and define the
image name, this is what I'll be using:
If for some reason I wanted the animation to begin on the 3rd frame
(image num2.gif) I'd instead use:
play() Method:
88
The play() method begins the animation. It has the following arguments
(in bold is the default):
objectName.play()
it doesn't loop, doesn't reset, and does nothing when complete. But you
can assign the ones you want:
objectName.play(true) objectName.play(true,false)
objectName.play(false,true,'alert(\'done\')') // alert(\'done\') can be
replaced with anything
stop() Method:
objectName.stop()
goToFrame() Method:
The goToFrame() method brings the animation to whichever frame you want.
objectName.goToFrame(index)
Where index is the index number of the image Series - 0 is the first
image in the series, 1 is the second and so on.
There is another method, run(), which is the logic behind the GifAnim
object, but you should never have to call that method because it doesn't
do any error checking to make sure that multiple instances of an
animation get executed.
layer to follow, and it just loops through each point by just moving it
there rather than calculating where to move it. This has some big
advantages because it takes little CPU time (so they go really fast) and
you can move the layer in any way you can think of. The disadvantage
though, is that you cannot easily change the path like you can a
calculative animation. If you need to change it, you have to go back and
change all the points in your path. However, with that said, using a
path is still a decent way of making animation.
The basic technique for path animation is easy to understand. Once you
have all the co-ordinates of where to move the layer, you just need a
function to loop through them and move the layer to those points. For
this purpose I've written a Path Object which will take care of this for
you. You just have to add the object to your DynLayers and use the
appropriate methods.
The only problem then is to get your coordinates for your path. There
are several commercial products that will do this for you such as
Macromedia Dreamweaver or mBed Interactor. These pieces of software will
auto-generate all code for you and require their own gigantic library
files. And if you want to edit the JavaScript code by yourself you'll
have to invest quite a bit of time to fiqure out exactly what it going
on. If all you want to do is make a simple path animation in a small
demo, then paying a couple of hundred dollars for it seems a little
extreme. This is why I've designed my own little tool, called DuoPath,
which a) doesn't cost anything, b) makes it easy to get all the
co-ordinates, and c) lets you do all the coding so you understand what's
going on and can easily change it to your needs. The next lesson will
show how to use DuoPath, so I'll just continue on assuming that we've
already created our path and have all the co-ordinates ready to go.
Here's all the coordinates for a path that I made with DuoPath:
x-coords:
101,105,113,122,131,140,149,156,157,156,155,157,163,172,181,188,196,203,
208,215,221,227,234,243,252,261,270,278,287,296,303,310,315,319,321,322,322,322
,322,323,328,335,345,355,368,377,385,390,392,393,394,391,387,381,377,376,376,37
8,382,386,391,398,406,414,424,434,442,453,461,468,471,474,475,476,476,
475,474,473,472,472,476,481,488,498,508,515,523,529,536,539,542,542,541,539,535
,529,523,517,514,513,513,520,530,540,552,564,574,579,581,580,576,567,555,540,52
1,501,479,459,441,422,404,384,366,349,330,310,291,272,254,238,222,206,188,172,1
57,141,128,113,102,92,82,72,63,55,48,42,36,32,29,27,29,32,39,
48,59,67,74,81,87,91,97
y-coords:
285,271,255,242,230,225,225,233,242,253,265,275,281,284,280,269,256,239,224208,
194,181,168,154,143,135,129,126,124,125,127,130,135,140,149,161,174,192,208,229
,250,269,281,286,284,277,269,259,248,237,222,202,183,164,147,133,120,108,99,92,
86,81,78,76,77,82,90,100,116,136,157,177,200,225,249,270,289,306,323,341,354,36
5,371,376,376,374,368,361,351,340,324,309,295,281,269,257,245,231,218,202,185,1
70,159,153,148,142,133,120,106,92,80,69,59,50,44,40,37,35,34,34,34,36,37,39,42,46
,50,54,60,67,76,84,95,105,116,128,140,153,166,176,187,200,212,224,237,250,264,27
9,295,310,326,341,351,357,355,350,342,331, 320,310,299
90
As you see there's about a million of them. But it doesn't really matter
how many points there are because remember path animation takes no CPU
time, so making a lot of points is no problem. Each set of co-ordinates
represent one point in the path. The first x value and the first y value
make up the first point and so on. By assinging these numbers to an
array we will be able to access any single set by using their indexes:
path1x = new
Array(101,105,113,122,131,140,149,156,157,156,155,157,163,172,181,188,
196,203,208,215,221,227,234,243,252,261,270,278,287,296,303,310,315,319,321,322
,322,322,322,323,328,335,345,355,368,377,385,390,392,393,394,391,387,381,377,37
6,376,378,382,386,391,398,406,414,424,434,442,453,461,468,471,474,475,476,476,4
75,474,473,472,472,476,481,488,498,508,515,523,529,536,539,542,542,
541,539,535,529,523,517,514,513,513,520,530,540,552,564,574,579,581,580,576,567
,555,540,521,501,479,459,441,422,404,384,366,349,330,310,291,272,254,238,222,20
6,188,172,157,141,128,113,102,92,82,72,63,55,48,42,36,32,29,27,29,32,
39,48,59,67,74,81,87,91,97)
path1y = new
Array(285,271,255,242,230,225,225,233,242,253,265,275,281,284,280,269,
256,239,224,208,194,181,168,154,143,135,129,126,124,125,127,130,135,140,149,161
,174,192,208,229,250,269,281,286,284,277,269,259,248,237,222,202,183,164147,133
,120,108,99,92,86,81,78,76,77,82,90,100,116,136,157,177,200,225,249,270,289,306,
323,341,354,365,371,376,376,374,368,361,351,340,324,309,295,281,
269,257,245,231,218,202,185,170,159,153,148,142,133,120,106,92,80,69,59,50,44,4
0,37,35,34,34,34,36,37,39,42,46,50,54,60,67,76,84,95,105,116,128,140,153,166,176,
187,200,212,224,237,250,264,279,295,310,326,341,351,357,355,350,342,
331,320,310,299)
Now if we wanted to know the 10th x value and the 10th y value, you
check value of path1x[9] and path1y[9]. The index number is always 1
minus the point we check because arrays start at zero. So path1x[9]=156
and path1y[9]=253.
With this knowledge in mind, you can apply these coordinates to the Path
Object which will allow you to turn your coordinates into a path
animation.
Initialization:
Once you've created a DynLayer, you create the Path Object on top of it.
You have to pass the Path Object the name of the DynLayer, and the name
of the Path Object along with the path information. This is necessary so
that the Path Object can manipulate the DynLayer.
91
Where:
dynlayer - name of the DynLayer
name - name of the path (in quotes)
arrayX - array of the x-coordinates
arrayY - array of the y-coordinates
Example:
Optionally you could predifine the arrays and just send their names for
the arrayX and arrayY values. Once the Path Object is initialized, the
parameters become properties of the DynLayer/Path Object and can be
changed at any time if need be (ie. mylayer.path1.arrayX = new
Array(5,10,15,20)).
objectName.pathName.play(loop,speed,fn)
Where:
loop - boolean value determining whether to loop the animation
speed - speed of repetition in milliseconds
fn - function or statement to execute when complete
All the parameters of the play() method are optional, if you do not
specify them it will default to play(false,30,null)
Example:
mylayer.path1.play(true,50)
Self-explanitory:
objectName.pathName.pause() or
objectName.pathName.stop()
lesson - it'll explain the concepts of how DuoPath can be used for your
own purposes.
First of all, launch DuoPath and follow this lesson and switch between
the windows to understand what I'm talking about.
DuoPath is included along with the tutorial when you download it.
Again updated the output, now requires you to manually include the
dynlayer.js and path.js files from the DynAPI
Version 1.11:
Version 1.1:
DuoPath 1.1 is basically the same as 1.0 but with a few enhacements:
• Curve Mode - A very powerful set of tools to create smooth curved paths. All you
do is set a few control points and DuoPath will draw in a smooth curve based on
those points.
• Full Drag and Drop - The "move" tools are now full drag and drop instead of double
click as in version 1.0
• Updated Output - When you generate the HTML DuoPath will now use the updated
path scripts that are documented in the Path Animation lesson.
Notes:
Inserting a point is not the same as making a new point. A new point is
appended to the end of the path. Inserting a point puts another point
between 2 points already in the path. You first have to click the point
that comes after the point you are inserting. This will attach a point
to the cursor, then you click again to drop that point into the path.
For the line mode, first click where you want the line to begin. Then
click again for where you want the line to end. A dialog will pop up and
ask how many pixels apart the points will be spaced, then it'll insert
them for you.
If you're not going to take my advice and use Netscape, then you will
have to be aware that in IE when you click on the scrollbars it will
insert a point if you are in "New" or "Line" mode. I guess IE thinks
that the scrollbars are part of the document for some reason. To avoid
putting points where you don't want them just go into one of the other
modes before scrolling.
Curve Mode:
Curve mode allows you to draw a perfectly smooth curve of points based
on just a few control points which you must define.
You work with curve points just as you would the normal path points.
However, once you put down 4 curve points DuoPath will draw in a series
of path points based on where your curve points are. As you continue to
add more curve points it will redraw the curve accordingly. The curve
points (in purple) are used to influence how the curve bends. You can
move the curve points and reshape the curve to your liking.
Here's a "before" shot of some points labeled in the order they were
clicked:
The first 3 points won't do anything, but once the forth one is down
it'll draw the curve for you:
By positioning the curve points closer or farther apart you can create
an acceleration effect. The picture below shows a path that will start
off slow and speed up as it swooshes away:
Special Buttons
Preview - allows you to preview the animation to make sure if the locations of the
points are correct and the speed is suitable
Generate - generates an HTML page with all the coding already done for you
Load Source - loads an HTML page into the workspace (Netscape only)
Load Path - allows you to insert a path that you created in the past
New Path - erases all the points
Inputing Points:
This is a way to work on a path that you created earlier, and want to
edit some more. 2 dialog boxes will appear, the first asking for the x
values, and the second for the y values. You have to manually cut and
paste the numbers from your previous path into these dialogs. To
95
demonstrate this feature just copy the first line of numbers below
(including the commas) by putting your cursor in front of the first
number. Then hold shift and press the END key. Then copy the numbers
(Ctrl-C, depending on your platform). Then switch to DuoPath and in the
Input Points dialog paste the numbers in (Ctrl-V). Then hit "Okay" and
do the same for the y values (the second row of numbers). DuoPath will
then draw out all the points and you can continue editing them.
Preview
The preview mode is pretty cool. You can instantly see what your
animation is going to look like by playing the animation right inside
DuoPath. You can change characteristics of the animation - whether it
loops or not, or change the speed of repetition.
Generate HTML
With a simple click and a few options to set you can have all the path
animation code generated for you on the fly. It has options for setting
the speed, the name of the object and so on. The code is basically the
same as shown in the Path Animation lesson but can also contain links
for playing, pausing, and stopping the animation - just as in DuoPath's
preview mode. Remember that by no means you have to use this code, you
can still just take the co-ordinates themselves and do whatever else you
want with them.
Other Tips:
Don't hesitate to make tons and tons of points. It makes the animation
smoother and DuoPath is designed so that there's no limit to how many
points you can have. Every 100 points you lay down it'll refresh itself
to hold more - don't worry, all your points will remain in tact.
Just as you can capture keyboard events, you can capture mouse events
like onMouseDown, onMouseUp, and onMouseMove - there are other if you
want to read up on them but these are the most important ones. For each
of these events, you can obtain the location of the mouse and use those
coordinates to move a layer.
function init() {
document.onmousedown = mouseDown
document.onmousemove = mouseMove
document.onmouseup = mouseUp
if (is.ns) document.captureEvents(Event.MOUSEDOWN |
Event.MOUSEMOVE | Event.MOUSEUP)
}
function mouseDown(e) {
}
function mouseMove(e) {
}
function mouseUp(e) {
}
The function names can be whatever you want, but to keep it consitant I
will always use mouseDown(e), mouseMove(e), and mouseUp(e). Netscape 4
has a different event model than IE which requires you to "capture"
events before they can be used. In IE they are always captured.
The "e's" in each function represent the built-in Event object for
Netscape. It is how Netscape obtainss the location of the mouse:
function mouseDown(e) {
if (is.ns) {
var x = e.pageX var y = e.pageY
}
}
IE will ignore the e's because it uses a slightly different system for
capturing mouse events. IE has an "window.event" object which handles
all events. The window.event object contains the properties x and y
which represent the location of where the mouse event occured:
function mouseDown(e) {
if (is.ns) {
var x = e.pageX
var y = e.pageY
}
If (is.ie) {
var x = event.x
var y = event.y
}
}
Those values reflect where in IE's browser window the mouse event
occured - it does not necessarily reflect exactly where on the document
has been clicked. If you scroll down the window.event.y value isn't in
synch with the document, so we have to account for that discrepency
ourselves. You add the amount that the document has been scrolled by
using document.body.scrollTop.
97
var x = event.x+document.body.scrollLeft
var y = event.y+document.body.scrollTop
Now when any of the events occur we can work with the x and y variables
(the current location of the mouse) and do some neat things with them.
function mouseDown(e) {
if ((is.ns && e.which!=1) || (is.ie && event.button!=1)) return true
var x = (is.ns)? e.pageX : event.x+document.body.scrollLeft
var y = (is.ns)? e.pageY : event.y+document.body.scrollTop
}
I like to do this because the default action when right clicking your
mouse is to pop open the command menu (Back, Forward etc). This
interferes when coding mouse events, so I don't do anything when you
right click.
Return Values:
The return values in the mouse events are very important for Netscape.
When you click the mouse somewhere on a page, the document recieves the
event first (and thus the mouseDown event is triggered). Even if you are
clicking on a hyperlink the mouseDown event will be called. By returning
true in the event handler you allow other elements on the page use that
event. If you return false you stop the page from doing anything else
with that event. So for example if you were to always return false in
your mouseDown handler:
function mouseDown(e) {
return false
}
So you must carefully place a return false only when you are
specifically making use of the event, all other times you return true.
Notice I return true when checking if the left mouse button was not
clicked (I let the document use the mouseDown event).
In the end, these are the mouse functions that work pretty good
function init() {
document.onmousedown = mouseDown
document.onmousemove = mouseMove
document.onmouseup = mouseUp
if (is.ns4) document.captureEvents(Event.MOUSEDOWN |
Event.MOUSEMOVE | Event.MOUSEUP)
}
function mouseDown(e) {
if ((is.ns && e.which!=1) || (is.ie && event.button!=1)) return true
var x = (is.ns)? e.pageX : event.x+document.body.scrollLeft
var y = (is.ns)? e.pageY : event.y+document.body.scrollTop
// your code goes here
return true
}
function mouseMove(e) {
var x = (is.ns)? e.pageX : event.x+document.body.scrollLeft
var y = (is.ns)? e.pageY : event.y+document.body.scrollTop
// your code goes here
return true
}
function mouseUp(e) {
var x = (is.ns)? e.pageX : event.x+document.body.scrollLeft
var y = (is.ns)? e.pageY : event.y+document.body.scrollTop
// your code goes here
return true
}
To replace the need to manually insert the above functions, I've created
a default JavaScript file that I'll be using in demos that require
document mouse events. The particular sections are the Drag object and
the Scroll2 object.
To use the DynAPI mouse events include the mouseevents.js file after the
dynlayer.js and BEFORE the drag.js and scroll2.js if you will be using
them:
These files will take care of retrieving the co-ordinates of the mouse,
and the handling required to operate the Scroll2 and the Drag object. To
initialize these you'll call the initMouseEvents() function in the
init():
function init() {
initMouseEvents()
}
These functions are optional. If the mouse events are only to be used by
a Scroll2 or the Drag, then they don't even have to be included in your
source code because the above empty default functions are already in the
MouseEvents code.
Drag and drop scripts are entirely based around the mouse events. I will
be using the DynAPI Mouse Event code, so if you haven't already, read
that lesson to know what I'm talking about. This lesson will show step
by step how to make one layer draggable. The next lesson will show how
to make any number of layers draggable with a generic Drag Object.
mouseDown - check if the mouse has click on a layer, activate the mouseMove
mouseMove - move the layer to cooincide with the location of the mouse
mouseDown - stop the mouseMove thus ending the drag sequence
function init() {
dragObject = new DynLayer("square")
dragObject.dragActive = false
initMouseEvents()
}
The first stage in the drag sequence is to check if you have clicked on
the layer or not. To do this you simply compare the x-y coordinate of
the mouse to the edges of the layer:
Notice that I have placed a return false in the block if we have clicked
on a layer. This stops Netscape from using the mouseDown event for
anything else (including a MacIntosh mouse-hold).
function DynMouseDown(x,y) {
if (x>=dragObject.x && x<=dragObject.x+dragObject.w &&
y>=dragObject.y && y<=dragObject.y+dragObject.h) {
dragObject.dragActive = true
return false
}
else return true
}
The mouseDown handler on it's own won't do anything to the layer, but by
setting the dragActive flag to true we have a way of turnging the
mouseMove action on and off as we please. The mouseMove event simply
checks if the dragActive flag is true, and if so moves the layer to the
coordinates of the mouse:
function DynMouseMove(x,y) {
if (dragObject.dragActive) {
dragObject.moveTo(x,y) return false
}
else return true
}
text).
To end the drag sequence all you need to do is set the dragActive flag
back to false. This stops the mouseMove function from moving the layer:
function DynMouseUp(x,y) {
dragObject.dragActive = false
return true
}
You'll notice in that example that if the layer is moved directly to the
location of the layer it doesn't look right. The layer pops to the
corner regardless of where it was click on. We can account for this
situation and correct it by capturing the difference between the
location of the layer, and the coordinate of the mouse (the offset
values). This is done in the mouseDown function:
if (dragObject.dragActive) {
dragObject.moveTo(x-dragObject.dragOffsetX,y-dragObject.dragOffsetY)
return false
}
Drag Object
Revision:
I pulled the drag mouse events into a "mouseevents.js" file, and created
onDragStart(), onDragMove(), and onDragEnd() event handlers. built a
"dropping" mechanism to track when you've dropped a layer on top of
another layer (a drop target)
The Drag object is based around the DynLayer and the DynAPI Mouse
Events. You simply add DynLayers to the Drag Object to make them
draggable, and remove them from the drag object to make them static
again.
However, being that this is an object you could create multiple drag
objects to define different groups of draggable layers.
The mouse event handling for the Drag Object is now taken care of by the
new DynAPI Mouse Events code. All you have to do is include the
mouseevents.js file and call the initMouseEvents() function:
function init() {
// initialize DynLayers here
initMouseEvents()
}
The mouse handling only takes care of the default "drag" object. If you
have other Drag objects you'll have to include a call to
yourdrag.mouseDown(x,y), yourdrag.mouseMove(x,y), and
yourdrag.mouseUp(x,y) into each of the DynMouseDown() Move() and Up()
functions.
The Drag Object's add() method is what you use to make your layers
draggable. The usage is pretty simple:
function init() {
// initialize DynLayers
blue = new DynLayer("blueDiv")
red = new DynLayer("redDiv")
green = new DynLayer("greenDiv")
purple = new DynLayer("purpleDiv")
initMouseEvents()
}
initMouseEvents()
That's all that's necessary to get your drag and drop layers working.
Extra Functionality
remove() Method:
You can remove a DynLayer from the Drag Object, and therefore making it
no-longer draggable, by using the remove() method. It's syntax is the
same as the add() method:
resort Property:
The resort property determines whether the layer that is being dragged
will be layered on top of all the other layers. By default, when you
click a draggable layer the Drag Object will make the z-index of that
layer higher than all the rest. This may not be what you want, so you
can turn it off by calling:
drag.resort = false
setGrab() Method:
drag.setGrab(dynlayer,top,right,bottom,left)
This method is entirely optional, if you don't call it the entire layer
will be "grabbable"
checkWithin(x,y,left,right,top,bottom) Function:
104
checkWithin(x,y,left,right,top,bottom)
checkWithinLayer(x,y,lyr) Function:
Drag Events
If you want to "do stuff" before you drag a layer, while you're dragging
a layer, or when you're finished dragging the layer, you'll need to
implement the onDragStart, onDragMove, and onDragEnd event handlers
respectively.
drag.onDragStart = startHandler
drag.onDragMove = moveHandler
drag.onDragEnd = endEndler
In your handler functions you can use any of the properties of the drag
object to manipulate
Drop Targets
Making drop targets allow you to easily determine if your dragging layer
has been dropped on top of another. For example if you had a shopping
cart and wanted to drop an item onto the basket you'd make the layer
with the basket a drop target.
Then to do something when a drag layer has been dropped on the target,
you must implement a onDrop event handler:
drag.onDrop = dropHandler
If you have multiple targets, you can determine which target was dropped
on with the drag.targetHit property:
function dropHandler() {
105
if (this.targetHit == target1)
alert("you hit target 1")
}
Creating and Destroying Layers
In both the browsers there is a way to add new layers to the page on the
fly (after the page is fully loaded). Again this is a situation where
the solution is completely different between the browsers. However there
is one crippling obstacle that I haven't been able to get around which
makes it impossible to use this technique to it's full potential.
Netscape:
In Netscape, to add a new layer to the page you simply create a new
Layer object:
For a nested layer you must call the new Layer command like this:
document.layers[parentLayer].document.layers[childLayer] = new
Layer(width, document.layers[parentLayer])
After the layer is created you can then assign the properties and add
the content to the layer using JavaScript:
Once all this is done, you can use the layer as normal.
document.layers[id].visibility = "hide"
Want a challenge?
ones.
Internet Explorer
To add another layer (or any other HTML for that matter) to the body of
the document, you call the method in this manner:
document.body.insertAdjacentHTML(string)
document.all[id].insertAdjacentHTML(string)
Initially I had though that the only way to delete a layer in IE was to
do string manipulation to the document.body.innerHTML property of the
page. However this creates some severe problems as "phantom" HTML
elements get introduced. Fortunately, as a few other JavaScripters (Erik
Arvidsson and Thomas Brattli) mentioned, there indeed is a pretty easy
way to delete a layer in IE. You can use a combination of innerHTML and
outterHTML on that particular layer only. It does work, and does not
cause the problem seen when using document.body.innerHTML.
createLayer(id,nestref,left,top,width,height,content,bgColor,visibility,
zIndex)
destroyLayer(id,nestref)
The usage should be obvious - id and nestref they are the same as for a
Dynlayer. The left, top, and width properties are required, the rest are
107
optional. After you create the layer you could assign DynLayers to them
and work with them that way.
CGI Communication
Hidden Frame
It's been long known that if you hide a frame you can target your form
values to that frame, and the other page will stay loaded. You could
submit a form, and have the CGI submit back a page containing JavaScript
code that updates the other frame by rewriting layers or whatever. This
is the easy way to do it, it's pretty straight-forward, so I won't cover
it for now.
This would probably work very well. However you have to be using a
server that supports Servlets and it would require a fair amount of work
to accomplish. I may play around with creating a servlet and seeing if
it indeed would work. The process would be like this:
JavaScript
Use LiveConnect execute functions in the applet
Applet
applet communicates directly with the server-side java
Servlet
servlet processes request (writes files, calls database, whatever) and
then communicates back to the applet
Applet
applet sends the JavaScript the results
JavaScript
display the results from the process by writing layers
Your CGI script must be able to accept query strings instead of regular
form parsed values. We will use query strings to pass all the relevant
data to the CGI - I will be using a simple Perl script to accomplish
this task.
The easiest way to gather your data is through HTML Forms. But you must
realize that this is not the only way to gather data. You could create
your own GUI elements to switch widget-like images on and off or any
other crazy ideas you have. For simplicity in this example I'll just use
a simple form that asks what your favorite operating system is:
<form name="myform">
<p>My Favourite Operating System is:
<p><input type="Radio" name="os" value="win9x">Windows 95/98
<br><input type="Radio" name="os" value="winnt">Windows NT 3.5/4.0
<br><input type="Radio" name="os" value="mac">MacOS 7/8
<br><input type="Radio" name="os" value="linux">Linux
<br><input type="Radio" name="os" value="solaris">Solaris
<br><input type="Radio" name="os" value="freebsd">FreeBSD
<br><input type="Radio" name="os" value="beos">BeOS
<br><input type="Radio" name="os" value="handheld">Handheld (PalmOS/WinCE)
<br><input type="Radio" name="os" value="otherunix">Other Unix-based
<p>
<input type="button" value="Submit" onClick="submitForm()">
</form>
Notice there is not ACTION associated with the Form tag, that's a no-no.
You must create a JavaScript function of some sort to collect your data
into individual variables. In my case, I just need to know which
operating system was selected. So I created a submitForm() function to
get that value when the Submit button is clicked:
function submitForm() {
for (var i=0;i<document.myform.os.length;i++) {
if (document.myform.os[i].checked) {
var os = document.myform.os[i].value
break
}
}
109
alert(os)
}
writeCSS ( css('resultsText',250,30)+
css('resultsDiv',250,50,200,100,'#c0c0c0') )
The Div's:
In order to use the DynLayer load() method, we must have both the
dynlayer.js and the dynlayer-common.js (which contains the load
function) in the page:
function init() {
DynLayerInit() results.load = DynLayerLoad
}
When the form is submitted, we must change the location - by using the
load() method - directly to the Perl script:
function submitForm() {
for (var i=0;i<document.myform.os.length;i++) {
if (document.myform.os[i].checked) {
var os = document.myform.os[i].value
break
}
}
results.load("/cgi-bin/dynduo/cgicomm-test.pl")
}
110
For this "test" case, the cgicomm-test.pl script can be simple, it just
writes out a simple page that does what all external files must do -
call back to the layer it's being loaded to, to complete the loading
sequence.
#!/usr/local/bin/perl
As you can see this process works fine, so lets finish it up...
For this example the Perl script doesn't really do anything except
gather the query strings (only one string actually - "os"), and then
prints out a page.
#!/usr/local/bin/perl
# get the full name of the OS that was selected and sent as a query string 'os'
$os = $osNames{$qstr{'os'}};
To test this script out you could point the browser to the script with a
query string manually attached: /cgi-bin/dynduo/cgicomm.pl?os=win9x.
It'll cause a JavaScript error when it tries to find the "results"
layer, but you can see the script works fine otherwise.
if (os) results.load("/cgi-bin/dynduo/cgicomm.pl?os="+os)
And Voila! We have DHTML and JavaScript working together with Perl!
As you can see it's really not that difficult. This general idea could
be extrapolated significantly to open up a wide range of possibilities.
I will be doing some more work on this, I may try to build a DHTML
interface to my Forum. And I'll expand on this technique further in the
future.
Warning: If you are using IE4 or IE5, and you have installed Microsoft
Media Player, the following code won't work. This is because Microsoft
was kind enough to break the compatibility of IE's multimedia controls
(on purpose perhaps?). I haven't bothered to comb through the latest IE
documentation for how they now want people to do audio controls. Also
beware, some later versions of Netscape seem to screw up when checking
for the LiveConnect plugin - it's there, but for some reason you still
get errors. I'll eventually get back to this aspect of JavaScript and
clean everything up. Anyone want to help?
Controlling audio is quite simple once you know what the commands are.
The easiest way I've found to do it is by using the EMBED tag to load
the audio file (wav, au, midi etc.). Then using the appropriate commands
you can either play or stop the file.
document.myaudio.play()
document.myaudio.run()
In IE the file will loop depending on the LOOP property in the EMBED
tag.
112
Finally, the code to stop playing the file is the same across all the
browsers:
document.myaudio.stop()
Generated Layers
I'll show how you can accomplish each of these tasks by following a few
simple guidelines.
The Basics
Usually there's no problems in IE, but I've found a few problems with
Netscape that you'll want to avoid to save yourself a lot of headache.
• write the CSS in a script in either the head of the document, or immediately
following the BODY tag.
113
• avoid writing a STYLE tag inside another layer, this only works if the layer is going
to be relatively positioned, I won't bother covering this but feel free to
experiment.
• It's a lot cleaner and more efficient to write all of your CSS at the same time by
creating a string of the text.
• ABSOLUTELY DO NOT put a corresponding \n at the end of </STYLE>. An early
Netscape (4.0-4.05) bug associated when you do this had me baffled for months,
it took me forever to figure it out. I don't know why but if you stick a \n at the
end of a STYLE tag and document.write() it within a page with lots of text, you
get a line break somewhere in the middle of the page. If you're a regular reader of
this website you may have noticed this.
Often its not necessary to SCRIPT the writing of the DIV's. You'll only
need to do this if you're planning on making a widget of some sort, or
write many DIV's that are alike in some manner. It works as expected:
<SCRIPT LANGUAGE="JavaScript">
str = '<DIV ID="mylayerDiv">my layer</DIV>'
document.write(str)
</SCRIPT>
<HTML>
<HEAD>
<TITLE>The Dynamic Duo - Generated Layers Demo [Simple]</TITLE>
<SCRIPT LANGUAGE="JavaScript">
<!--
var str = '<STYLE TYPE="text/css">\n'+
'#mylayerDiv {position:absolute; left:50; top:70; width:100; height:20;
clip:rect(0,100,20,0); background-color:yellow; layer-background-color:yellow;}\n'+
'</STYLE>'
document.write(str)
//-->
</SCRIPT>
</HEAD>
<BODY BGCOLOR="#FFFFFF">
<SCRIPT LANGUAGE="JavaScript">
<!--
//-->
</SCRIPT>
</BODY>
114
</HTML>
css.js
css(id,left,top,width,height,color,vis,z,other)
The most notable is how I worked with the height and clip values. 99% of
the time you want to set the height of your layer, you also want to clip
the layer to that same value. For example, if you want to make a colored
square, you'd have the width and the height the same as the clip right
and clip bottom values. On the other hand, if you are just placing some
text or an image you don't need to clip it and you don't have to set the
height either. So what I've done with the CSS function is when you set
the height, it also sets the clip values to (0,width,height,0) - which
is the most common situation.
However, in the cases where you want the clip values to be different
than the width and height, you may use the other property and send your
own 'clip:rect()' CSS. When you do this it will write your clip CSS
instead of making it's own based on the width and height.
You can also make the layer positioned relatively by sending null for
both the left and top values. In fact any of the values you don't want,
just send a null value for and it won't write them. And by sending an ID
of "START" or "END" it writes the appropriate STYLE tag to start or end
the CSS syntax.
Examples:
You just set up a string containing all the CSS needed, and then
document write it to the browser. It is recommended that you only write
one set of CSS. If you try to do 2 of the above writing it will hang
Netscape 4.0 and 4.01. If it's absolutely necessary you can separate
each css writing into separate <script> tags.
writeCSS(str,showAlert)
writeCSS ( css('mylayerDiv',50,100) )
writeCSS (
css('mylayer1Div',50,100)+ // must add css() calls together
css('mylayer2Div',50,100)+
css('mylayer3Div',50,100) ,1) // send true (or 1) to display a dialog
<html>
<head>
<title>The Dynamic Duo - Generated Layers Demo [CSS Function]</title>
<script language="JavaScript" src="../dynapi/css.js"></script>
<script language="JavaScript">
<!--
116
writeCSS( css('mylayerDiv',50,70,100,20,'yellow') )
//-->
</script>
</head>
<body bgcolor="#FFFFFF">
</body>
</html>
Now that we have a good way to go about generating layers, we can start
getting to the whole reason for doing this. By generating layers in this
manner we have a great way to substitute static numbers in your CSS for
variables and begin getting into the real meat of dynamically generated
pages using DHTML.
Example:
writeCSS( css('mylayer',x,y) )
By doing loops you can use this technique to generate any number of
layers in any way you want. You could generate dozens of layers in
random positions, or create grids of layers.
Even with the advent of the screen object, this can't be used reliably
to determine the actual size of the browser window. It is important to
know the exact width and height (to the pixel) of the browser to give us
the ability to generate layers based on these values. We can use those
value to generate layers that stretch to the width of the browser,
center layers, or right align them etc., thus giving layers extra
flexibility even though they are absolutely positioned.
The best way that I know to find the width/height of the browser is by
checking the following properties after the BODY tag. You must place
whatever code that is dependent on the width/height in SCRIPT located
after the body because in IE the body element is used:
In Netscape:
window.innerWidth window.innerHeight
In IE:
117
document.body.scrollWidth document.body.scrollHeight
<BODY>
<SCRIPT LANGUAGE="JavaScript">
</SCRIPT>
<div id="mylayer"></div>
</BODY>
Note: this is the only situation where you should ever have to write CSS
within the body.
At any time after the winW and winH variables have been defined can they
be used (eg. init() function can use them)
Centering Layers
To use these values to center a layer you can do a little math to find
where the left/top co-ordinate should be. For example if your layer is
100px wide and 50px tall, you'll need to use the (winW-100)/2 for the
left coordinate, and (winH-50)/2 for the top coordinate.
writeCSS ( css('centerDiv',(winW-100)/2,(winH-50)/2,100,50,'blue') )
if (document.layers) {
widthCheck = window.innerWidth
heightCheck = window.innerHeight
window.onResize = resizeFix
}
function resizeFix() {
if (widthCheck != window.innerWidth || heightCheck !=
window.innerHeight)
document.location.href = document.location.href
}
This piece of code can be inserted into pages which suffer the common
problem when you resize the Netscape browser, all your layers loose
their positioning. That code will reload the page, again using the
browser width and height to check if the size has changed.
That file contains the findWH() function which you may use to find the
winW and winH properties. However those properties have been tweaked so
that when you draw layers they will fit *exactly* to the width and
height of the window - not including the scrollbars or anything.
The real trick with making the page liquid is that the page must reload
in order for all the layers to be redrawn (to keep the layers
stretched). What you do is add the onResize event to the body tag:
And presto, your page will reload when resized, and therefore redraw all
the layers to their desired positions and dimensions.
Cookie Functions
how many times you've visited the site how you navigate the site (to
119
In the DynAPI there is a cookies.js file which you can use to easily
save, read, and delete cookies. Include the file and you'll have 3
functions:
deleteCookie('favourite cookie')
The example below will count the number of times that you've read the
page. When you reload the number will go up. I read the cookie value,
and write a sentence depending on the value of the cookie, then I
increment the counter using parseInt() to make sure it's a number, and
then re-save the cookie:
if (count==null) { // if no cookie
document.write("<li>never visiting this page before")
count = 0 // set counter to 0
}
else { // if cookie exists
document.write("<li>visted this page "+count+" times before")
}