Wednesday, 13 October 2010

SP2010 AJAX - Part 1: Boiling jQuery down to the essentials

This is the first article in a series which looks at building AJAX-style applications on SharePoint 2010. As you may have read in my last post, I recently gave a talk at the UK’s first SharePoint Saturday event on this topic, and this series goes into detail on what I covered there.

Series contents (note the ‘technique’ and ‘tip’ article types):

  1. Boiling jQuery down to the essentials (technique) - this article
  2. Using the JavaScript Client OM to work with lists (technique)
  3. Using jQuery AJAX with a HTTP handler (technique)
  4. Returning JSON from a HTTP handler (technique)
  5. Enable Intellisense for Client OM and jQuery (tip)
  6. Debugging jQuery/JavaScript (tip)
  7. Useful tools when building AJAX applications (tip)
  8. Migrating existing applications to jQuery/AJAX

If a developer is to have any hope of building AJAX-style applications, he/she needs to be able to change content on the page without a page refresh. After the initial learning curve, devs came to love .Net, but that oh-so-easy button_OnClick() event came at a price – a postback every time the page needs to be updated. For a time this was fine – both devs and end users effectively had low expectations on user experience for web applications. Now, however, things are different – postbacks are bad and SharePoint developers are arguably behind the curve (compared to ASP.Net devs) when it comes to building “modern-day” applications. Possibly the most popular tool for updating page content dynamically is jQuery. There’s no doubt about it, jQuery is a big topic. I don’t class myself as a guru, however my supposition is that jQuery can be boiled down to a handful of methods which give you a huge amount of power – this post covers these, and provides the foundation for building AJAX-style apps.

Core jQuery actions


Type

Code

Notes

Selector

$(‘#someId’)

Select an element by HTML ID e.g. <button ID=”someId” />

Selector

$(‘.someClass’)

Select an element by HTML/CSS class e.g. <button class=”someClass” />

Event

$(document).ready()
- jQuery also provides a shortcut for this: $()

Put code which should run on page load here

Event

.click()

Put code which should run when an element is clicked here (e.g. $(‘#myButton’).click())

Event

.change()

Put code which should run when an element is changed here (e.g. $(‘#myDropdown’).change())

Method

.html()

Sets the HTML of an element – extremely powerful as the element can be a single span or a huge main content div.

Method

.show()/.hide()

Shows or hides an element.

I genuinely think those tools give you most of what you need from jQuery, at least for page manipulation (but not AJAX – that comes later). Of course, you’re always going to need to do other things and once you understand how to use the basics, “the next tier” down of jQuery actions could be things like (N.B. no notes here, but you can guess what each statement does):

Type

Code

Selector

$(‘.someClass’)[0]
$(‘.someClass’).parent()
$(‘.someClass:visible’)

Event

$(document).ready()
.onmouseover()
.onkeyup()

Method

.fadeIn()/.fadeOut()
.append()
.wrap()
.addClass()

Going beyond these initial levels, I recommend jQuery In Action as the book to use to go deeper with jQuery.

Examples

To illustrate the ‘core’ jQuery tools, these are the examples I used in the “jQuery for page manipulation” section of my talk. For each example, I’ll give a quick overview, show the screenshots and show the HTML/jQuery code. Of course, flat screenshots don’t really convey what’s happening on the page, but just remember that there are no postbacks here.

  1. Showing/hiding elements

    Possibly *the* most fundamental thing we need to do when manipulating the page is to show and hide things (without a postback of course). In this example, clicking a button shows a spinner image – something which is often part of what happens when using jQuery with AJAX methods. Notice that, on page load (document.ready) jQuery is hiding the image immediately – an alternative would be to to add a style=”display:none” to the <img> element :

    jQuery_Demo1_Off
    <fieldset id="fldDemo1">
        <legend>Demo 1 - showing/hiding</legend>
        <div class="demoRow">
                <button id="btnDemo1" type="button">Show/hide image</button>
                <img src="../images/COB.SPSaturday.Demos/ajax-loader.gif" id="spinnerImg" />
        </div>
    </fieldset>
    <script type="text/javascript">
        $(function () {
            $('#spinnerImg').hide();
        });
     
        $('#btnDemo1').click(function () {
            if ($('#spinnerImg').is(':visible')) {
                $('#spinnerImg').hide();
            }
            else {
                $('#spinnerImg').show();
            }
        });
    </script>

  2. Setting the HTML of an element

    Setting the HTML content of an element on the page is equally important, and is as simple as getting a reference to the element and passing the HTML string to the .html() method. Although my example is trivial, the HTML contains images and elements with CSS applied to them – hopefully showing that the HTML could be anything between a small DIV on the page to a huge panel containing most of the page content:



    <fieldset id="fldDemo2"> 
        <legend>Demo 2 - setting HTML</legend> 
        <div class="demoRow"> 
            <button id="btnDemo2" type="button">Set HTML</button> 
            <span id="demo2Span"></span> 
        </div> 
    </fieldset> 
    <script type="text/javascript"> 
        $('#btnDemo2').click(function () { 
            $('#demo2Span').html('The <i>quick</i> <span id=\'brown\'>brown</span> <img src=\'/_layouts/images/COB.SPSaturday.Demos/fox.jpg\' /> jumped over the lazy <img src=\'/_layouts/images/COB.SPSaturday.Demos/dog.jpg\' />'); 
        }); 
    </script>

  3. Cascading dropdowns

    Although people sometimes make a big deal of cascading dropdowns (seems like there’s a new article on the topic every week), jQuery simplifies things considerably – the key is responding to the .change() event of the parent dropdown. Since we’re not covering AJAX yet, my example here uses dropdown items which are hardcoded in JavaScript rather than fetched from a query to the server. Note also that this code uses jQuery to populate the items in the parent dropdown even – the code would be simpler still if only the 2nd dropdown loaded items dynamically.


    <fieldset id="fldDemo2">
        <legend>Demo 3 - cascading dropdowns</legend>
        <div class="demoRow">
            <div><span class="demoLabel">Car manufacturer:</span><select id="carManufacturer"></select></div>
            <div><span class="demoLabel">Car model:</span><select id="carModel"></select></div>
        </div>
    </fieldset>
    <script type="text/javascript">
        $(function () {
            var output = [];
     
            var manufacturers = ['Select..', 'Ford', 'Vauxhall', 'Honda'];
            $.each(manufacturers, function (index, value) {
                output.push('<option value="' + value + '">' + value + '</option>');
            });
     
            $('select#carManufacturer').html(output.join(''));

        });
     
        $('select#carManufacturer').change(function () {
            var fordModels = ['Fiesta', 'Focus', 'Mondeo', 'Galaxy'];
            var vauxhallModels = ['Corsa', 'Astra', 'Insignia'];
            var hondaModels = ['Jazz', 'Pilot', 'Civic', 'Accord'];
     
            var selectedArray;
            if ($('select#carManufacturer').val() == 'Ford') {
                selectedArray = fordModels;
            }
            if ($('select#carManufacturer').val() == 'Vauxhall') {
                selectedArray = vauxhallModels;
            }
            if ($('select#carManufacturer').val() == 'Honda') {
                selectedArray = hondaModels;
            }
     
            var models = [];
            $.each(selectedArray, function (index, value) {
                models.push('<option value="' + value + '">' + value + '</option>');
            });
     
            $('select#carModel').html(models.join(''));
     
        });
    </script>
  4. Dimmed panel

    The .addClass() method is useful to change the style of elements without reloading the page. Of course, applying different CSS styles can cause dramatic changes to the page. To illustrate this I picked the idea of showing a dialog and ‘dimming’ the background – a UI technique very much in vogue and used, of course, by SharePoint 2010’s own dialog framework. If the CSS classes are already defined, a simple use of .addClass()/.removeClass() is the main thing that the code needs to do: 
     


    <!-- DIV for grey background, CSS makes larger than whole page -->
    <div id="dimmer"></div>  
    <!-- DIV for progress panel, CSS layers above page -->
    <div id="progressPanel">
        <a class="close" href="#" >
           <img id="close" src="/_layouts/images/COB.SPSaturday.Demos/close.jpg" />
        </a> 
        This is an important message!   
    </div>  
     
    <fieldset id="fldDemo4">
        <legend>Demo 4 - dimmed panel</legend>
        <div class="demoRow">
            <button id="btnDemo4" type="button">Dimmed panel</button>
       </div>
    </fieldset>
    <script type="text/javascript">
        $(function () {
            $('a.close').click(function () {
                $('div#progressPanel').fadeOut();
                $('div#dimmer').fadeOut().removeClass('dimmed');
            });
        });
     
        $('#btnDemo4').click(function () {
            $('div#progressPanel').fadeIn();
            $('div#dimmer').addClass('dimmed').fadeIn();
        });
    </script>
     
    Here’s the associated CSS:
    div#progressPanel
    {
        position:absolute; 
        width:200px; 
        height:150px; 
        z-index:20; 
        border:2px solid #222; 
        background: #FFF; 
        top: 50%; 
        left: 50%; 
        margin-top: -150px; 
        margin-left: -200px; 
        opacity: 1.0;
        -moz-opacity: 0.00;
        filter: alpha(opacity=0);
        display:none;
        padding:10px;   
        font-size:1.5em;
        font-weight:bold;
    }
     
    .dimmed
    {
        height: 100%;
        width: 100%;
        position:fixed;
        top: 0px;
        left: 0px;
        background-color: rgb(0, 0, 0);
        background-repeat:repeat;
        -moz-opacity: 0.70;
        opacity: 0.7;
        filter: alpha(opacity=70);
        z-index: 10;
    }
     
    .close 
    {
        top:0px; 
        float:right; 
    }
     
    .msgbox 
    {
        position:absolute; 
        width:300px; 
        height:200px; 
        z-index:200; 
        border:5px solid #222; 
        top: 50%; 
        left: 50%; 
        margin-top: -100px; 
        margin-left: -150px; 
    }   
     
    img#close 
    {
        border:none; 
        margin:5px;
    }

  5. Fetching HTML via AJAX

    Although this post attempts to focus purely on manipulating the page on the client side, here’s a quick example which does talk to the server (to whet your AJAX appetite). Here we are using one of jQuery’s AJAX methods to fetch the content of a page and put it within a DIV of the current page. This works fine for most pages but I ran into an issue with SharePoint admin pages which I didn’t get to the bottom of (see comment in code) – hopefully it proves how easy doing something like this is however, and consider that I could easily parse the result and do something more useful with it.


     

    <fieldset id="Fieldset1">
        <legend>Demo 5 - fetch HTML via AJAX</legend>
        <div class="demoRow">
            <button id="btnDemo5" type="button">Fetch HTML</button>
            <span id="demo5Span"></span>
       </div>
    </fieldset>
    <script type="text/javascript">
        // Some smoke and mirrors here unfortunately, SP pages 
        //  have some weird JavaScript redirect which would need to be solved 
        //  for this technique, so I'm using a (copied) flat HTML page..
        $('#btnDemo5').click(function () {
            $.get("/_layouts/COB.SPSaturday.Demos/settings_aspx.htm", function (data) {
                $('#demo5Span').html(data);
            });
        });
    </script>

These code samples (and those in the next articles) can be downloaded from http://db.tt/rUq5lm9

Next time - using the JavaScript Client OM + jQuery to work with lists.

5 comments:

MOSSBUDDY said...

Awesome explanation waiting for next articles, awesome Chris!!! keep up the great work

Christophe said...

Chris, I think your last example is incorrect, as you are creating invalid markup by re-injecting a full html page in an existing page. The jQuery load() function would be more appropriate here.

As for the JavaScript issue, what I have been doing is fetch the page markup but block the evaluation of scripts. Maybe this could help in your case...

Chris O'Brien said...

@Christophe,

Yes, I'd agree with that - the resulting HTML would actually be invalid as we'd have 2 head/body tags etc. Really we'd need to either only fetch part of the page content or do some post-processing on the full HTML. However, the example hopefully illustrates that such techniques are very easy with jQuery though.

What's the trick for blocking scripts? That's exactly what would be needed if script tags are present in the returned HTML.

Many thanks for the feedback :)

Chris.

Christophe said...

What I do is overwrite the jQuery globalEval function responsible for evaluating the scripts.
I have never talked to a jQuery guru about this, but it has worked for me so far. I use this technique in SharePoint for Ajax tooltips for example.

Paul Hunt said...

Interesting behaviour issue on the modal dialogue box. If I click on it once, it works fine with nice 40% opacity.

Close the dialog and do it again without the page reloading and this time opacity is at 100%.

Not sure if this is an IE8 issue or not yet.