After Thought

Posts Tagged ‘Jquery

Track changes if form fields are changed on a web page

leave a comment »

A very useful feature to have on a web page is to remind the user of unsaved changes when he/she navigates away from the page without saving. A easy way to implement this is to set a global flag when a form is changed. Based on the flag value, pop a dialog when the user tries to navigate away from the web page without saving. This approach has several flaws, the obvious one being the usage of a global variable. It also fails if the user reverts his changes.

A better solution is to keep track of all the fields on the page and display the dialog only when there is a change. If we were to implement such a tracker, it  has to do the following.

Step 1: Scan all the form fields like textbox, textarea, checkbox etc to and store the existing values when the page loads.

Step 2: When the user makes a change, attach an event on all the links to pop a dialog when the user clicks on it without saving.

Step 3: Remove the attached events, if the user reverts those changes.

Here is an example web page.

If the user edits Name field and clicks on My Blog link without saving, it should pop a dialog as shown below:

Dialog in the example is dialog from jqueryui .

track.js keeps track of changes on the form and makes it possible to hook before change and after change events.  Assuming the form id is editForm, call trackchanges (with events you want to associate with) in your document ready function. Hook a dialog open event in the afterChangeAction and remove it in beforeChangeAction.

Below is the code listing.

jQuery(document).ready(function($) {
	var beforeChangeAction = function () {
        $("a[id^='lnk']").each(function() {
            $("#" + this.id).die('click');
        });

    };

    var afterChangeAction = function () {
        $("a[id^='lnk']").each(function() {
            var id = this.id;
            $("#" + id).live('click', function (e) {
                bindModalConfirmationDialogFor(id, redirectAction);
                openConfirmationDialog(e, "Any changes made will be lost. Are you sure you want to continue?");
            });
        });
    };

	$("#editForm").trackchanges({events: "change blur keyup mousedown", exclude: "exclude", beforeChangeAction: beforeChangeAction, afterChangeAction: afterChangeAction});
});

function bindModalConfirmationDialogFor(fld, action) {
    var dialogOpts = {
        modal: true,
        autoOpen: false,
        buttons: {
            'No': function() {
                $(this).dialog('close');
            },
            'Yes': function() {
                action(fld);
                $(this).dialog('close');
            }
        }
    };
    $("#dialog-confirm").dialog(dialogOpts);
}

function openConfirmationDialog(e, message) {
    e.preventDefault();
    $("#dialog-confirm").html("<p><span class='ui-icon ui-icon-alert' style='float:left; margin: 0 7px 20px 0;'></span>" + message + "</p>");
    $("#dialog-confirm").dialog("open");
}

function redirectAction(fld) {
    window.location.href = $("#"+fld).attr("href");
}

The problem with this approach is, it is obtrusive. The user is forced to click twice to navigate without saving. What would be really cool is if we can change the color of the banner on the page as the user edits any form field. Further, we can change the color back to the original state when the user reverts those changes without ever refreshing the page. It would be a seamless unobtrusive experience to users reminding them to save the page before navigating to a different page (Some of the websites out there already have this nifty feature). As an example let us change the color of the Banner shown below from blue to yellow when a user edits any form field.

If we were to implement such a tracker, it  has to do the following.

Step 1: Scan all the form fields like textbox, textarea, checkbox etc to and store the existing values when the page loads.

Step 2: When the user edits any field check if the new value of that field is different from the old value; change the color of the Banner from blue to yellow.

Step 3: Revert the color back to blue, if the user reverts those changes.

Below is the code listing:

jQuery(document).ready(function($) {
	var beforeChangeAction = function () {
        $("#buttons").css("background-color", "#bcd3e9");
        if ($("#changed-text").length > 0) {
            $("#changed-text").remove();
        }
    };

    var afterChangeAction = function () {
        $("#buttons").css("background-color", "#fbec88");
        if ($("#changed-text").length === 0) {
            $("#buttons").prepend("<span id='changed-text'>Changes not saved</span>");
        }
    };

	$("#editForm").trackchanges({events: "change blur keyup mousedown", exclude: "exclude", beforeChangeAction: beforeChangeAction, afterChangeAction: afterChangeAction});
});

You can choose to exclude a field or a form with a class. In the example above we excluded Exclude Changes field by adding class “exclude”.

<input type='text' id='exclude_changes' name='exclude_changes' class='exclude' value=''/>

FormChangeTracker project is available on github. You can find code for both the above examples along with track.js.

Written by shashankshetty

November 4, 2011 at 5:16 pm

Posted in javascript, Uncategorized

Tagged with ,

Switch between View Result and Json Result without sweat in FubuMVC

with 3 comments

Kazi Manzur Rashid wrote a great post on Adaptive Rendering in ASP.net MVC that is used to return the result based on the request using the same action method. The clear advantage here is that you avoid duplicating your code in the action method just to cater to different request types (like ViewResult, JsonResult).

Chad Myers recently added a feature to FubuMVC that makes adaptive rendering seamless. We will continue to use FubuMvcSampleApplication to demonstrate this feature. First let us add the action convention wire_up_JSON_URL and behaviour output_as_json_if_requested to ControllerConfiguration.Configure() method that is called from our BootStrapper class. Adding wire_up_JSON_URL will simply add another URL (*.json) for every action. For example, if we have a save action, this will add another url save.json during the setup. Now our action responds to url save as well as save.json. output_as_json_if_requested overrides the conventional result and displays a Json result if it is a Json request.

// Action conventions
x.ActionConventions(convention =>
 convention.Add<wire_up_JSON_URL>());

// Default behaviour for all actions
x.ByDefault.EveryControllerAction(action =>
 action.Will<execute_the_result>());

// add a behaviour to output as Josn if it is a json request
x.ByDefault.EveryControllerAction(action =>
 action.Will<output_as_json_if_requested>());

We will now go ahead and implement our action method. For simplicity sake, we will implement a Display action that simply returns UserDetails for the given UserId. Let us add a small piece in action method for the sake of demonstration that populates the Message with either Json Result or View Result based on where the request is coming from.

public UserEditViewModel Display(UserEditViewModel userEditViewModel)
{
  User user = _userRepository.GetUser(userEditViewModel.UserId);
  return new UserEditViewModel(user)
  {
    Message = userEditViewModel.IsAjaxRequest() ? "Json Result" : "View Result"
  };
}

Now let us add a view Display.aspx to demonstrate this feature.

<%= this.FormFor((UserController controller) => controller.Display(null))%>
<div>
 <fieldset>
 <legend>User Details</legend>

Enter john_doe or user2

 <%= this.TextBoxFor(user => user.UserId).ElementId("UserId").Required().WithLabel("UserId")%>
 <input type="button" id="DisplayUsingJson" name="DisplayUsingJson" value = "Display Details Using Json" />                    
 <%= this.SubmitButton("Display", "Display").Class("button") %>

 <%= this.TextBoxFor(user => user.LastName).ElementId("LastName").ReadOnly().WithLabel("Last Name") %>

 <%= this.TextBoxFor(user => user.FirstName).ElementId("FirstName").ReadOnly().WithLabel("First Name") %>                    

 </fieldset></div>
</form>

Enter a user_id and click on Display button to display user details using default behaviour (View Result).

ViewResult

Add the following javascript (uses jquery) that is fired when a user clicks on Display Details using Json button.

function Display() {
 var user = {};
 user.UserId = $("#UserId").val();

 $.getJSON("Display.json", user, function(user) {
 $("#LastName").val(user.LastName);
 $("#FirstName").val(user.FirstName);
 $("#message").html(user.Message);
 });
};

Clicking on Display Details using Json button calls the action method Display using the other url “Display.json” that is used by Fubu to identify where the request is coming from.

JsonResult

We just saw how easy it is to switch between JsonResult or ViewResult. It is features like these, that make FubuMvc a very effective and powerful alternative to ASP.net MVC. I have added the above feature to FubuMVCSampleApplication project and you can download the source code here.

Written by shashankshetty

May 25, 2009 at 7:20 pm

Using JsonResult with jquery in ASP.net MVC

with 17 comments

Action methods on controllers return JsonResult (Java Script Object Notation result) that can be used in AJAX application. With the growing popularity of jquery,  jquery is becoming the number 1 choice for AJAX. With this in mind, let us build a simple ASP.net MVC application that returns a JsonResult and we will use jquery to parse the Json and display it accordingly on the web page. Let’s say, we have a page that has 3 fields UserId, LastName and FirstName and we want to populate LastName and FirstName based on the UserId entered.

user_screen

If we type in a UserId and then press Populate User Details, we should see the LastName and FirstName textboxes filled in with the appropriate information if the UserId is found in the repository.

user_screen

If the UserId is not found in the repository, we should get an error message “No UserId found for TestUser”.

user_screen

First let us look at the javascript function that would call the controller action to get the user details. Make sure you have included the latest jquery library in your application. Add the below script to your page.


	function populateUserDetails() {
		var user = {};
		user.UserId = $("#UserId").val();
		$.getJSON("PopulateDetails", user, updateFields);
	};

	updateFields = function(data) {
		$("#LastName").val(data.LastName);
		$("#FirstName").val(data.FirstName);
		$("#Message").html(data.Message);
	};

getJSON method will load Json data using HTTP Get request.  It takes in 3 parameters:

  • url or your controller action in this case
  • data (UserId for this example)
  • callback function that is called when the data is loaded. In the above example updateFields is called when the controllerAction returns  Json data.

We can also use post instead of getJSON if your action responds to POST requests. Then your call would look something like this, with everything else remaining same:

$.post(“PopulateDetails”, user, updateFields, ‘json’);

Now let us go ahead and look at our ControllerAction method:

public JsonResult PopulateDetails(UserModel model)
{
	UserResultModel userResultModel = new UserResultModel();
	if (String.IsNullOrEmpty(model.UserId))
	{
		userResultModel.Message = "UserId can not be blank";
		return Json(userResultModel);
	}

	User user = _userRepository.GetUser(model.UserId);

	if (user == null)
	{
		userResultModel.Message = String.Format("No UserId found for {0}", model.UserId);
		return Json(userResultModel);
	}

	userResultModel.LastName = user.LastName;
	userResultModel.FirstName = user.FirstName;
	userResultModel.Message = String.Empty; //success message is empty in this case

	return Json(userResultModel);
}

The above method would return a JsonResult that would be interpreted on the client side by the script that we wrote earlier with the help of jquery library.

This is as easy as it gets. Combination of jquery and JsonResult enables us to create applications that is AJAX enabled with minimal effort. You can get the source code for the application that is discussed above here.

Written by shashankshetty

March 4, 2009 at 4:25 am