Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

Friday, May 10, 2013

The benefits and horrors of third party controls

.. or how I learned to love the bomb

Don't reinvent the wheel
Don't reinvent the wheel (Photo credit: Wiertz Sébastien)
When developing web applications that should handle all sorts of data and potentially lots of it you might end up in building all kinds of controls for displaying and editing that data. Before you know it you are creating clever pieces of html and even cleverer pieces of backend code to produce that html and an added thick layer of Javascript sauce to make all this work together.
And that brings you into inventing another even cleverer mouse trap or re-inventing the wheel. And that mouse trap or wheel has to be cross browser and multi-functional, databound et cetera et cetera et cetera.
Me, I don't like re-inventing wheels, that has been done so many times already.
So, clear the stage for third party controls
Telerik
Telerik (Photo credit: martin.linkov)
One these re-inventors of wheels is Telerik. They have various suites of controls that make the life of developers easier. They have menu controls and date(time) edit boxes with calenders and grid controls that you can easily use in your applications. Set some options and bind them to your data and there you go.
I must admit that there is a bit of a learning curve, but with the help of some good examples on the demo site and the ever helpful support desk of Telerik you get everything up and running in no time.

As easy as .. whatever is easy.

But it is not all easy
When you start out all things go smoothly and then you find that on your page with a couple of nested grids you end up with many, many save buttons. As each (sub)grid has one. And thus starts the quest to use only one button to rule them all.

So here is our SaveAll Javascript function.

function SaveAll(button) {
    showLoader();

    $.ajaxSetup({
        async: false,
        cache: false
    });

    $('.t-grid').each(function (index) {
        var grid = $(this).data('tGrid');
        grid.submitChanges();
    });

    $('form').not('.t-edit-form').each(function (index) {
        $.ajax({
            url: $(this).attr('action'),
            data: $(this).serializeArray(),
            type: $(this).attr('method')
        });
    });

    hideLoader();

    $.ajaxSetup({
        async: true,
        cache: true
    });

    return false;
}
 What happens?

  • We start by showing a loader div (with spinning gif )on the page and so making it impossible for the user to do something while everything gets saved.
  • Then we make sure that all consequent ajax calls are asynchronous as we would otherwise run ito trouble.
  • Next we loop through all the grids submit all changes for each grid.
  • Then we loop through all the forms (excluding the forms with class "t-edit-form", these are are for inline editing in the grids) and we submit the forms through ajax.
  • Finally, we hide the loader div again.
There you are. That was not that difficult.
I have omitted checking for validation errors and what to do when everything is saved. Maybe we need to move to another page?
Well, it almost works, can I go home now?
Almost working is never enough for testers and clients. But is probably even more annoying for me as a developer.
We discovered a weird behavior in our grids. Because we wanted to unclutter our pages we also removed the delete buttons in the grids for removing records and added a column at the beginning of each row with a checkbox so the user can select multiple rows. Then using a delete button in the toolbar you can delete the rows in one go.
Great!
And then we discovered that the value of the first data column was not saved using our button. Other columns worked perfectly. The data was simply not in the request to the server.

After a lot of reaearch, Googling, trial and error
It was clear that the solution was just around the corner. The addition of the checkboxes was the cause. That was easily established, but a solution was more difficult.
Browsing through the Telerik Javascript code and the DOM I tried to add this to part where we are saving the grids.
    $('.t-grid').each(function (index) {
        var grid = $(this).data('tGrid');
        // save any editing rows first
        $(this).find(".t-grid-edit-row").each(function () {
            grid.updateRow(this);
        });
        grid.submitChanges();
    });
That would simply take the rows that are in editing mode and update the data. But no, this tried to update it directly on the server and that would involve additional writing specific server side code to handle those requests.
Close and still no cigar.
The problem was that the edited column was somehow not persisted in the client side data. So that was what  needed to be made sure. Persist all data in client side memory before submitting the grid changes.
But how?
More Googling
Trying evermore results from Google brought me to Paul Reynold's blog. In his post "enhanced batch editing using telerik extensions for asp .net mvc grid control" he explains a lot. Not all of it was relevant for my solutions, but I found a bit of code that helped to fix my problem. Thank you, Paul.

        $('.t-grid').each(function (index) {
        var grid = $(this).data('tGrid');
        // save any editing cells first
        $(grid.element).find(".t-grid-edit-cell").each(function () {
            grid.saveCell(this);
        });
        grid.submitChanges();
    });

I tried to update an entire row (which resulted in a server call) and the solution is to save each cell and these get persisted client side. Just what I wanted.

Lessons learned
I learned a couple of things in this quest:
  1. Third party controls are great for making life easier, because all normal functionality is there and tested.
  2. Third party controls make things really difficult at times when you want to do something slightly out of the box.
  3. Solutions can be found by hunting through multiple search results and reading all the way through these.
  4. Never give up.

Enhanced by Zemanta

Thursday, June 16, 2011

Using simple jQuery to detect changes

.. a quick solution

Updated
Today I had a small task assigned to me to prevent a user from navigating away from a web form when there are unsaved changes. As it was a rather extensive form (and old code) I had the feeling that it could be quite an extensive task.
Yes, I know this is more of a beginner's level blogpost, but maybe someone can use it. Otherwise burn me down to the ground in the comments.
General idea
A simple solution came to mind:
  1. do this on the client
  2. in comes JavaScript and yep, here comes jQuery!
  3. have a page wide boolean isDirty that starts as 'false'
  4. when an input control is changed, make isDirty 'true'
  5. when user tries to leave the page, check the value of isDirty
  6. when isDirty is 'true' show confirmation dialog
  7. if the user cancels stay on the page
  8. if the user ignores then proceed onwards
All rather simple.
Let's implement this!
Steps 1 and 2 are easy to take. We will just include the jQuery library in the page and start a block of JavaScript.
Also simple is step 3.

var isDirty = false;

Step 6,7 and 8 can be wrapped in a simple function:
function IsPageDirty() {
   if (isDirty) {
      var ignoreChanges = confirm("Unsaved changes. Proceed anyway?");
         return (ignoreChanges);
   }
   return true;
}

That was easy, as some would say.
Now the final leaps
Then I decided that it would be simple a matter of using a selector in jQuery and add a function to the change function that sets the isDirty boolean to 'true'.
I opted for a class 'trackChanges'.
This is the little snippet that I produced.
$(".trackchange").change(function() {
   isDirty = true;
});

Similarly I chose a class of 'moveaway' for those links that can lead the user away from the current page. These links should get a onclick attribute returning the the result from the IsPageDirty() function described above.
$(".moveaway").attr("onclick", "return IsPageDirty();");
And then there was a lot of work ...
Since I then had to go through the entire form. It was an aspx page and there User Controls (ascx) being loaded and I had to go through thse as well. Not very pleasant.
I got myself another cup of coffee and had a good thought. Then I decided to just select all input controls and not just certain marked with a class.
Changed the code to the following.
$("input, select").change(function() {
   isDirty = true;
});
Same was true for all the links. I opted for selecting all anchors.

$("a").attr("onclick", "return IsPageDirty();");

Then I had a huge problem, because the saving was also done using a link. Before the saving would start the user gets a warning that there are unsaved changes. A bit confusing to say the least.
I didn't want to go back to putting a class on all the other links just to get around this. I wanted filter out some of the anchors by putting a class on these.
Luckily, jQuery has a function .not() that takes a selector and filters out these from the selection it is applied to. So I put a class 'always' on the anchors that should not have the IsPageDirty() check and changed the line to:

$("a").not(".always").attr("onclick", "return IsPageDirty();");
And that was it! A very simple piece of JavaScript code and hardly any changes to the page itself. I only had to add a class to some of the links.
Bringing it all together
Even if it is a very simple scenario and the code is probably not as compact as it can be, I think it shows once again how versatile JavaScript is in combination with library like jQuery.
So to get all together

<script type="text/javascript">
   var isDirty = false;

   $(document).ready(function() {
      isDirty = false;
      $("input, select").change(function() {
         isDirty = true;
      });
      $("a").not(".always").attr("onclick", "return IsPageDirty();");
      // a with class "always" is not checked
   });

   function IsPageDirty() {
      if (isDirty) {
         var ignoreChanges = confirm("Unsaved changes. Proceed anyway?");
            return (ignoreChanges);
      }
      return true;
   }
</script>

Not earth shocking
I know, I know, but still I think it could be of some help for others somewhere on the web. And maybe to help me remember this for future reference.

Small update
The above solution worked on almost all of the pages, but one of the pages had some ASP.NET AJAX stuff on it, where dropdownlist were filled after the initial pageload. This caused some nasty problems, because these made the
change()
function go off and set the
isDirty
variable to true.
Some scratching of the head and then some Googling made find the
live()
function to be my saviour in this.

<script type="text/javascript">
   var isDirty = false;

   $(document).ready(function() {
      isDirty = false;
      $("input, select").not(".ignore").live("change", (function() {
         // control with class "ignore" does not fire the isDirty flag
         isDirty = true;
      });
      $("a").not(".always").attr("onclick", "return IsPageDirty();");
      // a with class "always" is not checked
   });

   function IsPageDirty() {
      if (isDirty) {
         var ignoreChanges = confirm("Unsaved changes. Proceed anyway?");
            return (ignoreChanges);
      }
      return true;
   }
</script>

A reader with a good eye also notices the
.not(".ignore")
function I added to exclude some controls from firing the isDirty flag. I used this class on some input controls used for search.

That's it for now!
Enhanced by Zemanta

Friday, May 07, 2010

On jQuery templating

.. isn't it just shifting functionality around?

Eventhough jQuery templating is a pretty interesting concept and I feel tempted to use it, I can not stop thinking that it does not help to simplify my code.

I develop in ASP.NET mainly and do construct all the controls by hand, meaning that I first decide what my HTML will look like and then let the control render exactly that markup.

All right, all right

Arguably, I can do that with jQuery templating equally well. But I will be forced to rebuild the ViewState principle of ASP.NET in order to make changes come back to the server. I must call web services to get the data. Meaning that for each form I must have a specific web service. That is quite a bit of work.

Also I would be doing a lot of coding in JavaScript and in C# and I ike to keep as much functionality as possible in my main development language. Just to avoid confusion.

Concluding

Then again, I may have missed some stuff and maybe things can be done that I do not see yet. It's still early in the jQuery templating model and things are bound to change. I will wait and see what comes up next in this area.
For now, I will stick with my MVC inspired, Mediator based framework.

In reference to: A few thoughts on jQuery templating with jQuery.tmpl | Encosia (view on Google Sidewiki)

Reblog this post [with Zemanta]