August 24, 2010

Upload Image Preview (pure JavaScript)

A few weeks ago, I came across a post on a forum where the original poster asked if he could preview images before upload. The original poster also indicated that he wanted to accomplish it purely with JavaScript on the front-end without any posts to the server. Unfortunately, I cannot find the above mentioned post. Therefore, I based my search on what I remember from it… pure JavaScript solution.


HTML:

Let’s make a simple HTML markup. All I need is an <input> @type=file and an <img> with no @src defined for image preview. Also, I bind an onchange event for the <input> @type=file which calls the PreviewImg() function.

<input id="myFile" type="file" onchange="PreviewImg(this)"/>
<img id="image" src="" style="height:50px;width:50px;" alt="Image Preview" />

JavaScript for Internet Explorer:

I first thought there should be an easy way to retrieve the physical location of image file on user’s hard drive. Once the value is retrieved, set the source of the empty image element to it. As I googled for a solution, here is what I saw about 90% of the time.

function PreviewImg(elem) {
  var myImage = document.getElementById('image');
  myImage.src = elem.value;
}

Basically, the function receives the <input> @type=file as a parameter. It then selects the image element and sets its source to the value of the received element. Nice and simple. I tried this solution in FireFox… and, oops, it doesn’t work! A little bit of research revealed the following:

"file
Creates a file select control. User agents may use the value of the value attribute as the initial file name."

http://www.w3.org/TR/html401/interact/forms.html#h-17.4.1

Apparently, the value attribute of <input> @type=file should not reveal the path to the selected file on user’s computer. I’ll leave it up to the reader to judge the IE’s implementation.


JavaScript Cross-Browser Implementation (Yeah, Right!):

The remaining 10% of posters kept referring to getAsDataURL(). So, I searched around for getAsDataURL(). It seems to me that various posters are saying that it’s an overall standard and if called on a selected file, supposed to return “the contents of the file as a base64 encoded text”.

http://help.dottoro.com/ljbnqsqf.php

I searched more, but could not find any solid definition for getAsDataURL(). If anyone has more info on it, please share! Let’s try it in action:

function PreviewImg(elem) {
  var myImage = document.getElementById('image');
  myImage.src = elem.files[0].getAsDataURL();
}

I tried it in FireFox and it worked like a charm! I went back to Internet Explorer… didn’t work! At that point, I pulled up Chrome and to my surprise, it didn’t work in Chrome either.


Conclusion:

Well, I don’t have a solid one. Googling for Chrome specific solution didn’t get me anywhere. Also, I haven’t tested this in Safari and Opera. Here is a quick and nasty piece of code that I put together. I use jQuery here to check for a specific browser (not a good practice). The browser check if statement could be changed to a try/catch kind of deal (also nasty).

function PreviewImg(elem) {
  var myImage = document.getElementById('image');

  if ($.browser.mozilla) //works in FF
    myImage.src = elem.files[0].getAsDataURL();
  else if ($.browser.msie) //works in IE 6, 7, 8
    myImage.src = elem.value;
}

If anyone could shine more light on this, please leave a comment. I would also be interested in learning more about getAsDataURL().

Cheers!

August 19, 2010

Draggable HTML element

By now, probably everyone knows about the Draggable from jQuery UI. I recently thought to myself, what would it take to make my own Draggable? I gave it some thought and here is what I came up with. I'm not thrilled by this solution in terms of performance. However, it is small and simple and could be used as a starting point for further work on your own Draggable. Since jQuery UI Draggable uses jQuery (obviously!), I'm going to use it as well!


HTML:

Let's start with HTML markup. For the purpose of simplicity, I'm using a div tag with @class "drag".

<div class="drag">Drag me!</div>


CSS:

A draggable div must appear out of the context, so position:absolute should do the trick. Also, I need the div to stand out on the page. Therefore, I gave it a size and some background color.

.drag
{
   position:absolute;
   width:100px;
   height:100px;
   background-color:#FDDA95;
}


JavaScript:

Now here comes the fun part. What makes up a drag action?
  1. I depress the mouse button on an element.
  2. While holding the mouse button, I move the pointer.
  3. When the element is in a desired place, I release the mouse button.

It looks like, I got 3 events to deal with here: mousedown, mousemove and mouseup.

Mousedown needs to happen on the element itself, since I'm "grabbing" only the draggable element. Mousemove is a little more tricky. Binding it to a certain element will determine the boundaries of the draggable element. In other words, it determines the area inside which the draggable could be dragged. Again, for simplicity, I'll just bind it to the whole document. On mouseup, I would have to unbind the mousemove event, so that the element would remain in place when the mouse button is released. I first thought to bind mouseup event to the draggable element. However, that produces a bug. If I drag the element to a side of the browser window and without releasing the mouse button move the cursor out of the window, then release the button (mouseup), then move it back into browser window, the element would "jump" right to it, since mouseup event never fired on it. The proper way is to bind mouseup event to the whole document. This way, if the mouse button is released outside the browser's window, the event still fires up.

The events are figured out, what else? I need to determine the position of the mouse cursor and make sure the element "sticks" to it. More precisely, I need to know how far mouse cursor is from the edges of the draggable element, so while dragging the element remains in the same position relatively to the cursor. To accomplish that, I'm going to get X and Y coordinates of the cursor on mousedown and subtract position left and top from them.

Now for the final part, how does the element actually move? I already know the cursor's position within the element. Now, I need to change the element's top and left CSS properties. To do that, I'm taking the cursor's position and subtracting the distance between the cursor and element's edges from the position.

Little more advanced: I wanted to set hard boundaries for the draggable element. So, I added the "if" check before changing the left and top properties of the element. I also added 2 more variables to store document's width and height in the on mousedown. I perform the boundaries check against these variables.

Enough talk! Here is the code:

$(document).ready(function() {

  $('.drag').mousedown(function(event) { //assign mousedown to element with class .drag
    var docWidth = $(document).width(); //store document width
    var docHeight = $(document).height(); //store document height
    //get distance between cursor and element's left edge
    var elemMouseX = event.pageX - $(this).position().left;
    //get distance between cursor and element's top edge
    var elemMouseY = event.pageY - $(this).position().top;

    $(document).mousemove(function(event) {//bind mousemove to document
      //check to make sure the element isn't dragged outside it's boundaries
      if ((event.pageX - elemMouseX) >= 0 &&
        (event.pageY - elemMouseY) >= 0 &&
        (event.pageX - elemMouseX + $('.drag').width()) <= docWidth &&
        (event.pageY - elemMouseY + $('.drag').height()) <= docHeight)
         //set new top and left
         $('.drag').css({ 'top': event.pageY - elemMouseY, 'left': event.pageX - elemMouseX });
    });
  });

  $(document).mouseup(function(event) {//bind mouseup to the document
    $(document).unbind('mousemove');//remove mousemove event
  });

});


Working example:

draggable


In conclusion:

Like I said in the beginning, I'm not thrilled with the performance. I would need to rethink the way to check for boundaries. Overall, I think this is as simple as it gets. If you have any suggestions on how to improve this, please share!

This is my first post here, I will figure out a better formatting technique for presenting the code. I hope this was helpful to someone.

Cheers!