Dienstag, April 25, 2006

Moving HTML objects using drag and drop around, CSS and JavaScript

If you want to enable a custom page layout for the user or a drag & drop functionality in your solution you need a mechanism that enables moving html objects around. Here is a cross browser compatible solution that enables moving of HTML objects.

To make it easy to attach a moving functionality to a object I use the lightweight JavaScript Behaviour mechanism that is described in the AJAX eBook found here: http://www.mathertel.de/AJAX/AJAXeBook.aspx. Especially the cross browser event handling mechanism is used here.

Attaching the mouse events

Three events must be captured to drag objects around:

onmousedown

This event starts the moving scenario and you need to attach a method to this event.

It is sometimes necessary not to move the object that got this event but to identify a specific parent object that also contains other content elements. In this demo sample the title area of a web part is used to drag the whole part around. In the method that is attached to this event the parentNode references are searched until an html object is found that is marked with the className "VEPart".

Then current mouse offset to the left upper corner of the moving object is calculated. This is because you will not start dragging by using a exactly known single point of the object.

Now that we know that a specific object should be dragged around the 2 other events must be monitored and 2 other methods are attached to them.

onmousemove

This event will be thrown multiple times and we will move the object arround by using the new mouse coordinates given each time the event gets fired.

onmouseup

This event will be thrown at the end when the user wants to place the object at the new position by releasing the mouse button.

The first event can be caught on specific objects that enable the moving. I use the CSS class "VEMover" to mark these elements and attach a move cursor. The object that is moves is marked by using the CSS class "VEPart" that contains the VEMover.

The 2 other events should be caught on the document level because they will not be thrown always on the object that initiates the moving especially when the mouse pointer is moved very fast.

Because we need a reference to the object that is moved around the onmousedown event also saves a reference to the object by using properties of the MoverBehaviour element like a global variable so we can find the object again when onmousemove and onmouseup events are caught.

A simple moveable object

<div class="VEPart" style="width:180px;height:90px">
  <div class="VEMover">::: move me</div>
  I can be moved.
</div>

The JavaScript implementation

To make the implementation easier I use my JavaScript Control Library that enables writing compatible behaviours for HTML objects. All we need here is to include the 9 kByte jcl.js file and attach the behavior to the VEMover object.

The MoverBehaviour implements the 3 event handlers:

var MoverBehaviour = {
  mo: null, // reference to the movable obj,
  x: 0, y: 0,
  
  // ----- Events -----
  onmousedown: function (evt) {
    evt = evt || window.event;
    var src = jcl.FindBehaviourElement(evt.srcElement, MoverBehaviour);
    src.MoveStart(evt);
  }, // onmousedown


  // track mouse moves. This handler will be attached to the document level !
  _onmousemove: function (evt) {
    evt = evt || window.event;
    MoverBehaviour.MoveIt(evt);
  }, // onmousemove


  // track mouse button up. This handler will be attached to the document level !
  _onmouseup: function (evt) {
    evt = evt || window.event;
    MoverBehaviour.MoveEnd(evt);
  }, // onmouseup


  // ----- Methods -----
  MoveStart: function (evt) {
    // find the moving part (position:absolute or class="VEPart")
    var mo = this;
    while ((mo != null) && (mo.className != "VEPart"))
      mo = mo.parentNode;

    if (mo == null)
      return; // don't move
    MoverBehaviour.mo = mo;
      
    // calculate mousepointer-object distance
    mo.x = mo.y = 0;
    obj = mo;
    while (obj != null) {
      mo.x += obj.offsetLeft;
      mo.y += obj.offsetTop;
      obj = obj.offsetParent;
    } // while
    mo.x = evt.clientX - mo.x;
    mo.y = evt.clientY - mo.y;

    // make the moving object globally evailable when mouse is leaving this object.
    jcl.AttachEvent(document, "onmousemove", this._onmousemove);
    jcl.AttachEvent(document, "onmouseup", this._onmouseup);
  }, // MoveStart
  

  MoveIt: function (evt) {
    var mo = MoverBehaviour.mo;
    if (mo != null) {
      var p = (evt.clientX - mo.x) + "px";
      if (p != mo.style.left) mo.style.left = p;
      p = (evt.clientY - mo.y) + "px";
      if (p != mo.style.top) mo.style.top = p;
    } // if
    // cancel selecting anything
    evt.cancelBubble = true;
    evt.returnValue = false;
  }, // MoveIt
  

  MoveEnd: function () {
    var mo = MoverBehaviour.mo;
    if (mo != null) {
      MoverBehaviour.mo = null;
      jcl.DetachEvent(document, "onmousemove", this._onmousemove);
      jcl.DetachEvent(document, "onmouseup", this._onmouseup);
    } // if
  } // MoveEnd
    
} // MoverBehaviour

jcl.LoadBehaviour("moveme", MoverBehaviour);

The sample web page is available at http://www.mathertel.de/AJAXEngine/S04_VisualEffects/MoverDemo.aspx and a brief description of the JavaScript control library is available on web site in the eBook about my AJAX engine in the chapter "Building AJAX Controls" at: http://www.mathertel.de/AJAX/AJAXeBook.aspx.

The MoverBehaviour is also available as a separate include file if you want to follow my advice of reusing behaviours by separating them into a JavaScript include file. I will have a typical WebPart sample soon and will use the mover functionality there again.

Samstag, April 22, 2006

Less waiting on AJAX

It's a psychological phenomena that waiting takes less time as long as something happens.

I'm sure you have seen all these nice rotating arrows, bouncing points or bars that are commonly used for situations where a progress bar should appear to tell the user that it's time for waiting like on the windows start up screen on http://www.pageflakes.com/ or even in windows media edition. It can also be as simple as the [Loading...] text that is used by the Google-Mail user interface.

So, when I fell over http://www.ajaxload.info last week where you can easily generate "AJAX-" animated gif files I thought it is time to implement a few lines into the AJAX Engine.

The right place to start displaying a progress indicator is just before starting the webservice call to the server. Here I call StartProgress().

But it also has to be hidden after anything happens that ends the action like when the return value is there, an exception happens or when the timeout timer strikes. To identify these places I searched the code for the ajax.current gets cleared. There I call EndProgress();

The first implementation was straight forward creating and removing a html element. After some testing I found that this costs me more time than the real call over the internet and in many situations immediate responses got slower and that's definitively not that what I wanted to achieve.

In the end I came to the following solution:

  • The StartProgress function only sets a flag (ajax.progress) to true and starts a timer (ajax.progressTimer) with a timeout of 220 msec.
  • This time was chosen by some testing and many server calls do not last so long and therefore need no progress indicator.
  • When the timer strikes it calls the ajax.ShowProgress function. Here I implement the real code that creates the HTML element or just shows an existing one again.
  • The EndProgress function clears the flag and also starts the timer but with some less waiting.
  • When the timer strikes after a call has finished the existing object is just hidden.

This architecture has some advantages. First the progress indicator is not shown when short calls are made and when multiple calls are made one after the other it is not hidden. This can save a lot of flickering.

Here are the specific new functions:

// ----- show or hide a progress indicator -----

// show a progress indicator if it takes longer...
ajax.StartProgress = function() {
  ajax.progress = true;
  if (ajax.progressTimer != null)
    window.clearTimeout(ajax.progressTimer);
  ajax.progressTimer = window.setTimeout(ajax.ShowProgress, 220);
} // ajax.StartProgress


// hide any progress indicator soon.
ajax.EndProgress = function () {
  ajax.progress = false;
  if (ajax.progressTimer != null)
    window.clearTimeout(ajax.progressTimer);
  ajax.progressTimer = window.setTimeout(ajax.ShowProgress, 20);
} // ajax.EndProgress


// this function is called by a timer to show or hide a progress indicator
ajax.ShowProgress = function() {
  ajax.progressTimer = null;
  var a = document.getElementById("AjaxProgressIndicator");
 
  if (ajax.progress && (a != null)) {
    // just display the existing object
    a.style.top = document.documentElement.scrollTop + 2 + "px";
    a.style.display = "";
   
  } else if (ajax.progress) {
    // find a relative link to the ajaxcore folder containing ajax.js
    var path = "../ajaxcore/"
    for (var n in document.scripts) {
      s = document.scripts[n].src;
      if ((s != null) && (s.length >= 7) && (s.substr(s.length -7).toLowerCase() == "ajax.js"))
        path = s.substr(0,s.length -7);
    } // for
   
    // create new standard progress object
    a = document.createElement("div");
    a.id = "AjaxProgressIndicator";
    a.style.position = "absolute";
    a.style.right = "2px";
    a.style.top = document.documentElement.scrollTop + 2 + "px";
    a.style.width = "98px";
    a.style.height = "16px"
    a.style.padding = "2px";
    a.style.verticalAlign = "bottom";
    a.style.backgroundColor="#51c77d";

    a.innerHTML = "<img style='VERTICAL-ALIGN:bottom' src='" + path + "ajax-loader.gif'> please wait...";
    document.body.appendChild(a);

  } else if (a) {
    a.style.display = "none";
  } // if
} // ajax.ShowProgress

You can find the full source here and I will also include it into the next ajax.zip.

If you want to see how it looks like you can use the old prime factor sample. Try some long the numbers like: 98798798789878987. You might see it only if someone else is stressing the server too - It seems to be a powerful machine :-) and prime factors get calculated fast even with my stupid algorithm :-).

Donnerstag, April 20, 2006

Simple sliding sample to move HTML elements around

Moving HTML objects to new positions is not hard to implement. You can use absolute positioning and just set the style attributes left and top to the new values but it doesn't look cool if the objects use this kind of hyper speed: the eyes cannot follow and there may be a confusing effect of a sudden rearrangement.

Moving objects more slowly can be simulated by moving them using several steps and only a small distance at once. All we need is a timer to start the next step after the current position is rendered, a little bit of algebra and a heuristic to find the right distance for one step.

Because no local variables can be used when executing timer scripts we declare 4 global variables that hold all the information we need when the next step is to be started:

var slidingTimer = null; // the current running timer object
var slidingTarget = null; // the moving object
var slidingLeft = null; // the target left position
var slidingTop = null; // the target top position

Starting a sliding movement can be done by calling the startSlidePos function with 3 parameters: the object that should be moved, the new left and the new top coordinate. You can also refer the object by using the unique id.

onclick="startSlidePos('i', 10, 270)"

Every timer event now calculates the remaining vector but takes no more than 20 pixel length or a third of it whatever is shorter. Only if the target is very close the object will be positioned exactly:

// calc the remaining vector
dx = slidingLeft - left;
dy = slidingTop - top;

// calc the movement length along the vector
len = Math.sqrt(dx * dx + dy * dy);
delta = Math.min(20, len/3);

if (len <= 2) {
  // snap exactly
  left = slidingLeft;
  top = slidingTop;

} else {
  left += Math.round(dx * delta / len);
  top += Math.round(dy * delta / len);
} // if

Using this calculation the movement gets slower at the end when the target is almost reached.

You can see the full source code by using the view source functionality of your browser or in the right upper corner of the page.

You can find the sample live at: http://www.mathertel.de/AJAXEngine/S04_VisualEffects/SlidingDemo.aspx

Montag, April 17, 2006

HTML elements with rounded corners

HTML elements up to now have always a rectangle shape. In the upcoming CSS3 standard (still a proposal) there might be also other border shapes available including rounded corners by specifying a border radius. But we do not have to wait to get this visual effect shown up.

The trick that is shown here to get the usual rectangle corners into a round shape is to use a collection of thin HTML elements to build a non rectangle border.

The simplest solution to this is to use <div> at the top and bottom and arrange them like this:

Why not using a client side solution

The Rico framework for example is offering a client-side method to round the elements:

<div id='roundDemo' style='width:300px'>Insert content here.</div>
<script>Rico.Corner.round('roundDemo', {corners:'tl br',bgColor:'#adba8c'});</script>

By using a JavaScript solution on the client to generate these elements every div element can be transformed to a rounded corner look. The disadvantage of this approach is that there is always a delay and maybe a light flickering because the page will get rendered twice because the borders will be expanded by adding the additional elements.

Why not using graphic elements

I've also seen solutions that use 4 specific corner graphics. The drawback about this approach is that the colors of these images must correspond exactly to the colors of the other html elements. If you want to change your style then you must change the images too. Again here will be a light flickering because images are often loaded after displaying the page for the first time so it doesn't look very perfect.

A server-side solution

It is also possible to generate all the additional html elements on the server. This adds some html tags and some more data to the http response when the page gets loaded but also eliminates a lot of JavaScript or images.

For ASP.NET I've written a web control that derives from asp:Panel that does the same and avoids the flickering:

<ajax:RoundedArea runat='server' id='any' width='300'>Insert content here.</ajax:RoundedArea>

You can see a live sample of it at http://www.mathertel.de/AJAXEngine/S04_VisualEffects/RoundedDemo.aspx.

Samstag, April 08, 2006

HTML + CSS Shadow Effect with real transparency

If you want to give your objects a kind of 3D feeling then you might want to use shadows for those objects that are placed upon the web page.

Using special graphics

A often solution for a shadow effect is to use table layout around the main content and arrange a set of graphics around the right and bottom border. You can get really wonderful shadow dropping effects using this approach but because semi transparent images are not very well supported on IE you will have to pay attention to the background color you use on the page and have to mix it into your shadow images.

Using a Microsoft IE specific CSS filter attribute

When using the IE only you can also us one of the wonderful filter effects that are available in this browser. Just add a CSS filter attribute like that:

filter: progid:DXImageTransform.Microsoft.dropShadow(Color=AAAAAA,offX=8,offY=8,positive= true);

If you use IE that you can see these shadows on the entry page for the visual effects library. These shadows are also drawn by using solid colors.

Using CSS opacity

Here is a better solution that really resembles a kind of shadow because the text and graphics in the shadow are really displayed in dimmed light. The clue to this effect is a built-in opacity CSS graphics effect that is available IE, Mozilla, Firefox - but in different way.

In Internet Explorer:  style="filter: alpha(opacity= 50)"

The IE notation of the opacity effect is using a proprietary CSS attribute named filter that can be used to add various effects to a HTML element. Here we use the alpha filter.

In Mozilla/Firefox: style= "-moz-opacity:0.5"

The Mozilla / Firefox provides a proprietary css attribute named -moz-opacity that allows specifying a opacity value.

In Opera: style= "opacity:0.5"

The Opera browser also has a proprietary css attribute named opacity that allows specifying an opacity value.

These 3 CSS attributes can be combined together and every browser ignores all the attributes that are unknown:

style="filter: alpha(opacity= 50); -moz-opacity:0.5; opacity: 0.5" 

The HTML shadow object

This effect must be applied to rectangle region with a fixed width and height that is over the main content by using an absolute position by using the same size as the html part that is dropping this shadow.

Here is a simple sample that uses this trick. You can see it live at ShadowDemo.aspx.

The good about this solution is, that no graphics are used and that the shadow does not hide the text below but only seems to dim the available light.

  • The outer div element is used to do the absolute positioning of the whole group.
  • The first inner <div> element is the object that has the opacity effect applied. It is positioned some pixel to the right and down by using an absolute positioning (relative to the containing <div>) too.
  • The Content is placed inside the second inner <div> element that must have a relative position without any offset to be displayed above the shadow object.

Here is the plain HTML code:

<div class="VEPart" style="position: relative; width: 120px; top:-90px; left: 40px;">
  <div class="VEShadow" style="position: absolute; left: 10px; top: 10px;
    width: 120px; height: 84px; background-color: black;
    filter: alpha(opacity=30); -moz-opacity: 0.3; opacity: 0.3;"> </div>
  <div class="VEContent" style="position: relative; height: 80px;
    background-color: #FFFFDD;"> I am flying above the text and dropping a shadow.</div>
</div> 

You can see how it looks like on the demo website at http://www.mathertel.de/AJAXEngine/S04_VisualEffects/ShadowDemo.aspx

Montag, April 03, 2006

Why AJAX needs visual effects

Implementing a web based application by using AJAX techniques is about improving the backside, will elable the right user experience and will make your application work the way it should.

If you want that your application really looks like AJAX you also have to give your application a modern design together with all kinds of visual effects. That will really impress!

Don't you think that this is nonsense ? - I did for some time last year but I had to change my mind - at least a little bit.

Visual effects are definitvely an important aspect for AJAX applications today. Maybe the "market" will learn the technical difference in some near future. Maybe a "get-it-all-together" solution is the right approach for the "market".

So here is a list of visual effects and other "cool" looking stuff. Some of these effects can also be used in any other html based application, some of them will use AJAX. I'll explain the HTML+CSS+JavaScript that makes the effect but I will also bring the higher level approach using ASP.NET web controls that makes using them really easy.

AJAX applications look great! :-)

So here comes a library of visual effects. From time to time I will add another one and you will find them also on the demo side at http://www.mathertel.de/AJAXEngine/ see Examples -> Visual Effects .

The documentation you will find here on my blog and (with a little delay) in the AJAX eBook at http://www.mathertel.de/ajax/eBook.aspx. You will not find only a working solution but also a brief description on how it works so you can also use it in any of your projects too.