October 1, 2010

Client Side Viewstate with Cookies

Recently, I was presented with a task to implement a web page on which HTML controls would remember their state when the user navigates away and back to the page. A relatively simple task, one would think. The problem was that the page was 100% AJAX based with no post back allowed. I gave it some thought and came up with a solution. This solution could be inappropriate in some cases. However, in my case, it worked exceptionally. Ladies and gentlemen, round of applause for good old cookies!
NOTE: In this article, just like in my previous ones, I will be utilizing jQuery and jQuery-json plugin.


HTML:

<input type="button" id="btnLeavePage" value="Go To Another Page" />
<br />
<a id="lnkToggleDisplay" href="#">Show/Hide</a>
<br />
<div id="controlDiv">
  <br />
  <input id="txtInput" type="text" />
  <select id="sltInput">
    <option value="1">First</option>
    <option value="2">Second</option>
    <option value="3">Third</option>
  </select>
</div>

Simple enough. I got a button which navigates to a different page (supposedly loosing all client information about the page), a div with an input text and a select , and a link which toggles the display of the div.


JavaScript:

I will not explain the essentials of cookies. If you would like to learn more about managing cookies with JavaScript, here is a great article on Quirksmode: JavaScript - cookies.

function WriteCookie() {
  var pageState = {
    'text': $('#txtInput').val(),
    'select': $('#sltInput').val(),
    'toggle': $('#controlDiv').is(':visible')
  };
  var date = new Date();
  date.setTime(date.getTime() + 3*1000);
  document.cookie = 'PageState=' + $.toJSON(pageState) + ';expires=' + date.toGMTString() + '; path=/';
}
I need to save the controls state to the cookie. What format should I use for it? I was first thinking about custom delimiters. You know, throw in a '|' or a '~' in between the controls data and then just use JavaScript’s split function when parsing it out. This approach works in cases where the user is limited to a certain input. In my case, there is an input text without any validation attached to it, thus if a user types in my secret delimiter, I’m basically screwed… Here is where the idea of using JSON object came to my mind. I tried it out and it worked very well. In the code above, I’m constructing the JSON object with 3 values that hold my page’s state: the value of the input text, the value of the select control, and the status of the div (shown or hidden). I then convert my JSON object to a JSON string, since the cookie data is in string format. For the purpose of this demo, I’m setting the cookie to expire in 3 minutes.


function ReadCookie() {
  var cookies = document.cookie.split(';');
  for (i in cookies) {
    if (cookies[i].indexOf('PageState') == 0) {
      var pageState = $.evalJSON(cookies[i].substring(10));
      $('#txtInput').val(pageState['text']);
      $('#sltInput').val(pageState['select']);
      (pageState['toggle']) ? $('#controlDiv').show() : $('#controlDiv').hide();
    }
  }
}
To read the cookie I perform pretty much the standard JavaScript logic: split the cookie data, find my cookie and, if found, do stuff with it. I grab the JSON string out of the cookie data and evaluate it into a JSON object. Once the object is ready, I set my page controls to the data inside the object. Of course, jQuery is making it quick and simple here.


Putting JavaScript together:

$(document).ready(function() {
  $('#lnkToggleDisplay').click(function() {
    $('#controlDiv').is(':visible') ? $('#controlDiv').hide() : $('#controlDiv').show();
  });

  ReadCookie();

  $('#btnLeavePage').click(function() {
    WriteCookie();
    window.location = "navigate_to_page.htm";
  });
});
On document ready, I first assign click handler to the toggle link. This changes the visibility of the div holding the two HTML controls. Then I call ReadCookie() function to set page elements’ values. At last, I assign click handler to the navigation button. Notice that it’s on navigation, when the user is leaving the page, the page state is written to the cookie. You may check out the working example.


Conclusion:

Once again, the usage of cookies is not appropriate in every case. As for the problem that I was facing, this solution was simple and neat, did not take long time to implement and in fact works very well at the moment. I hope this helps someone.

September 10, 2010

Submit JSON data to ASMX service

Although, I wanted to write about something different for quiet some time now, I think this post is important. I could not find a good example on the Internet of how to submit JSON object to an ASMX service with AJAX call. Most of the examples show how to receive a complex JSON object on client side, how to loop through it and how to display the received information. Great! What if we have a form that a user has to fill out and submit to the server? I am not planning to go into details on why things are the way they are. I will try to emphasize on most important pieces.


HTML
<form id="MyForm">
  <input type="text" name="Text" />
  <br/>
  <input type="checkbox" value="true" name="CheckBox" />
  <br/>
  <input type="radio" value="rdo1" name="Radio" />
  <input type="radio" value="rdo2" name="Radio" />
  <br/>
  <select name="SelectOption">
    <option value="1">Option 1</option>
    <option value="2">Option 2</option>
  </select>
  <br/>
  <input type="button" id="btnSubmit" value="Submit" />
</form>
I listed 4 widely used HTML controls on this form. Note my controls have names defined and not IDs. Names are unique to controls or, in case of radio buttons, to the group of controls. Also, note that my checkbox has a value attribute defined, which is “true”.


JavaScript
$('#btnSubmit').click(function() {
  var formVals = $('#MyForm').serializeArray();
  var jsonObj = {};

  for (i in formVals)
    jsonObj[formVals[i].name] = formVals[i].value;

  var submitVals = $.toJSON({ "MyFormData": jsonObj });

  $.ajax({
    type: "POST",
    url: "WebService.asmx/SubmitFormData",
    data: submitVals,
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    success: function(result) {
      alert($.toJSON(result));
  }
});

First is the obvious: when btnSubmit is clicked, I’m taking all the values from MyForm and sending them to the web service.
I use jQuery’s serializeArray() function to automatically grab all the values from MyForm. The structure of formVals is as follows:

[{"name":"n1","value":"v1"},{"name":"n2","value":"v2"},{"name":"n3","value":"v3"}]

Where names are the name attributes of the form elements and values are their values. Remember the CheckBox with value of “true”? If a checkbox is checked, serializeArray() would pick it up from the form. If a checkbox is not checked, serializeArray() would not pick it up from the form at all. As for radio buttons, serializeArray() picks up their group name and the value of the one button that is selected. With this being said, the result of serializeArray is not in a JSON format. To create a proper JSON format, I have a simple for loop which turns formVals into a JSON object, jsonObj. (I picked this loop from comments on serializeArray() page) Now, jsonObj is in the following format:

{"n1":"v1","n2":"v2","n3":"v3"}

This is the correct JSON format and this is almost what I want to pass to the service. At last this line:

var submitVals = $.toJSON({ "MyFormData": jsonObj });

MyFormData is the parameter that my WebMethod is expecting in the web service. So, I create a more complex JSON structure:

{"MyFormData" : {"v1","n2":"v2","n3":"v3"}}

At last, this final data structure must be passed in as a JSON string and not as a JSON object. There are several ways to go about “stringifying” a JSON object. Here are some available choices:

I used jquery.json just because I already have jQuery included in the project. I will not explain why I have the $.ajax setup this way. You may find all kinds of info on it all over the wwweb.


ASMX Web Service
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.ComponentModel
Imports System.Web.Script.Services

' To allow this Web Service to be called from script, using ASP.NET AJAX,
' uncomment the following line.
<System.Web.Script.Services.ScriptService()> _
<System.Web.Services.WebService(Namespace:="http://tempuri.org/")> _
<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<ToolboxItem(False)> _
Public Class WebService
Inherits System.Web.Services.WebService

Public Class FormData
  Public Text As String
  Public CheckBox As Boolean
  Public Radio As String
  Public SelectOption As Integer
End Class

<WebMethod(EnableSession:=True)> _
<ScriptMethod(ResponseFormat:=ResponseFormat.Json)> _
Public Function SubmitFormData(ByVal MyFormData As FormData) As String
  Dim result As String
  result = MyFormData.Text & " "
  result &= MyFormData.CheckBox & " "
  result &= MyFormData.Radio & " "
  result &= MyFormData.SelectOption
  Return result
End Function

End Class

Yes, this is, in fact, VB! Converting this to C# should not be a problem. The most important piece here is the FormData class. It must be declared as public. All of its properties must exactly match the names of the form HTML controls. Function SubmitFormData takes FormData MyFormData as a parameter. This is why I had to stick the original JSON object into another array. As long as HTML control names are matching the class’s properties names, the serialization happens automatically. The only thing that I need to be careful about is the value types. The CheckBox property of the class is declared as Boolean. When the checkbox is checked, the client submits its value as “true”, which then serializes into Boolean value of true. If the checkbox is not checked, no value is submitted for it and so the Boolean variable gets a value of false. The rest is just a dummy data processing. The service returns all the values back as one string and the JavaScript alerts {"d":"… values string…"}
You may find out more about the 'd' here: the 'd'


Conclusion

Why this method? Obviously, depending on a problem, we must come up with a certain solution. This approach may not be suiting certain problems. However, here are two things that I absolutely love about it.
  1. It requires no extra work at all to handle serialization.
  2. At some point in the future, my users will want another text box on that form. So, the form will be updated, the FormData class will be updated, possibly the DataBase will be updated. But! My JavaScript will remain untouched.

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!