Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

On the Fly DropDown Editing with jQuery


:P
On this page:

A couple of people have asked me now regarding a small feature on the CodePaste.net site. When you post a code snippet and you are signed in you can quickly edit most of the snippets properties by clicking on the edit icon next to each of the editable elements. I’m a big fan of in place editing which gives a nice interactive feeling to an application.

One of the editable items is the language selection list, which by default displays as plain text with an edit button next to it:

 Initial

Note if you’re playing along, that you have to be signed in and looking at one of your own snippets for this to work as editing makes sense only on snippets you own :-}.

If you click the icon you get to edit the language, but unlike the other fields of the page which are inline edited with text boxes, the language transforms into a drop down list that you can select items from. Here’s is the display after clicking the edit button:

Expanded

In this case the drop down is built dynamically with the list of items retrieved from the server, the drop down dynamically built and added to the page. Once a selection has been made the drop down is removed and the old text restored and replaced with the updated language selection and the new formatted text for the code snippet based on this selection.

Let’s take a look at how this works. The HTML is set up pretty simple through an MVC View page with some wrapping logic to conditionally display language label and value as well as the edit button if the user is allowed to edit:

<% if (!string.IsNullOrEmpty(Model.Snippet.Language))  {%>                         
  <% if (Model.AllowEdit){ %>
<div id="btnEditLanguage" class="editbuttonright" title="edit"></div>
<%} %> <div class="rightalign">
Language: <span id="Language"><%= Html.Encode(Model.Snippet.Language) %></span>
</
div> <% } %>

What’s here is basically just the plain text display – the drop-down is built on the fly only when requested for editing.

Using jQuery the logic to build this is pretty easy to do. The entire script code that handles creation and updating the dropdown and code dispay  looks like this:

$(document).ready(function() {
    $("#btnEditLanguage").click(langEdit);
…
 });
function langEdit() {
    var jLang = $("#Language");

    // set a flag to disallow double editing
    if (jLang.data("editing"))
        return;
        
    var lang = jLang.text();
    
    // Need a snippet id for Server callbacks
    var id = $("#SnippetId").val();
    
    ajaxCallMethod(serverVars.callbackHandler, "GetCodeLanguages", [], 
function(langs) { var dd = $("<select></select>") .attr("id", "LanguageDropDown") .css("font-size", "8pt"); jLang.data("editing", true); $.each(langs, function() { var opt = $("<option></option>").text(this.value).val(this.key); if (lang == this.key) { opt.get(0).selected = true; } dd.append(opt); }); // Hook up change handler that saves selection and restores text display dd.change(function() { var lang = dd.val(); ajaxCallMethod(serverVars.callbackHandler, "SaveLanguage", [id, lang], function(code) { $("#CodeDisplay").html(code).hide().fadeIn("slow"); }, onPageError); jLang.fadeIn("slow") .text(lang) .data("editing",false); dd.remove(); }); jLang.before(dd).hide(); }); }

This code uses two server callbacks to retrieve data: one to retrieve the languages to display and one to update the server and retrieve the updated code snippet with the new language selection properly formatted. These calls are handled with the ajaxCallMethod() function which is part of the West Wind Web Toolkit and a small wrapper around the jQuery .ajax function to interact with the West Wind Web Toolkit callback handler. The first call gets languages and returns an array of string language names that are displayed in the list that is returned as a dictionary from the server. The server code is ridiculously simple as the languages supported are a cached list:

/// <summary>
/// Returns a list of languages as key,value pair array to the client
/// </summary>
/// <returns></returns>
[CallbackMethod]
public Dictionary<string, string> GetCodeLanguages()
{
    return App.CodeLanguages;
}

The client receives this dictionary as an array of key, value items with the key being the ‘value’ and value being the text (hmmm… that could work either way, eh? :-}) These values are then dynamically built into a <select> list using jQuery, which makes this process very easy by creating the <select> list assigning a few attributes and then simply looping through the array and adding list items. Finally the new list is added to the DOM by dropping it before the original text string and then hiding the text string:

jLang.before(dd).hide();

Notice also the use of the .data() function to attach a flag to the language text DOM element to indicate whether the control is already being edited. Originally I forgot this logic and it was real easy to get a few drop downs inserted into the page which was kinda cool for about 2 seconds. To fix this the jQuery .data() matched set function is used to attach a pseudo property to the element which is checked when button is clicked again. If it exists and is true we’re editing and the click is ‘rejected’. Otherwise – dropdown here we come.

The final thing that happens in the block of code is that a change handler is dynamically wired to the drop down. It fires when a selection is made and in this case it causes another callback to the server, which saves the new language selection to the database and returns the formatted text for the new language to the client. The handler code looks like this:

/// <summary>
/// Updates the language edited on the client
/// </summary>
/// <param name="snippetId"></param>
/// <param name="lang"></param>
/// <returns></returns>
[CallbackMethod]
public string SaveLanguage(string snippetId, string lang)
{
    busCodeSnippet busSnippet = new busCodeSnippet();
    if (busSnippet.Load(snippetId) == null)
        throw new ArgumentException("Invalid snippetId passed.");

    if (!IsEditAllowed(busSnippet.Entity) && !UserState.IsAdmin)
        throw new AccessViolationException("You are not allowed to edit this snippet.");

    busSnippet.Entity.Language = lang;
    if (!busSnippet.Save())
        throw new InvalidOperationException("Unable to save snippet: " + busSnippet.ErrorMessage);

    string code = busSnippet.GetFormattedCode(busSnippet.Entity.Code, 
                                       lang,
                                       busSnippet.Entity.ShowLineNumbers);
    return code;
}

There’s a little more logic in this method as it has to validate the user by checking the login and ensuring the user is allowed to edit this snippet first. The rest of the logic just loads the entity updates the value and saves it back to disk. Finally the formatted code is generated by the business object and returned.

I’ve used this type of label to expanding dropdown list display in a number of places in various applications and I think it’s nice UI effect that makes it smooth and space efficient to edit selections and the process is pretty easy to accomplish. A variation of this theme is to use the label itself as a link rather than a separate button. If the selection is open to all and frequently used this code can also be simplified by simply embedding the list directly into the page and hiding it instead of dynamically loading it each time, but those are just semantics. Either way the process to do this is pretty easy using jQuery.

Code Links:

Posted in jQuery  

The Voices of Reason


 

Steve
August 24, 2009

# re: On the Fly DropDown Editing with jQuery

Interesting stuff - glad you're writing such great articles about JQuery + ASP.NET. It's a really powerful combination.

1 small idea that might be fun for you to try: I've taken the next step and instead of doing a conditional display in the ASPX page (if CanEdit { }) I simply transfer the CanEdit,etc. variables directly to JS variables and do all the conditionals in JS. You've written some articles before about how to transfer from ASP.NET -> JS globals, but I'm doing it a little bit differently, see below. End result is that I almost completely remove the <% if { %> <% } %> and replace it with client-side functionality.

The difference: for state variables like that, I use a custom object that stores a value _and_ notifies when it's changed. Then I hook the conditions to those variables with events. So instead of writing "if (app.CanEdit) { ... }" I do it as "app.CanEdit.change(function(value) { ... });" This allows me to do the DOM manipulation either at first page hit or after some subsequent AJAX event updates the container object. For instance, when a completely AJAX login updates a few page state variables, the UI adapts without needing to refresh.

It's a long-shot that you'll need this for something as fundamental as login permission changes, but it works really well for lots of other things. For instance, updating a string that shows how many items are displayed in the editable list below, which can change for many reasons. Etc.

When you make this small change suddenly there's a big shift in your perception of your ASP.NET code. With the conditionals gone, the HTML it's producing becomes a set of "DOM templates" to be used by JS code. Obviously, there's a lot of server-side validation and business logic that remains, but for the UI it feels like your program has moved fully into the browser. (I suppose this could be a bad thing if your JS is weaker than your ASP.NET templating...)

Pamela Thalacker
August 24, 2009

# re: On the Fly DropDown Editing with jQuery

That is a really great example. It opened my eyes to a number of things that hadn't really made it onto my radar screen.

Rick Strahl
August 24, 2009

# re: On the Fly DropDown Editing with jQuery

@Steve - I agree. Often client side only rendering or client side code logic can make the markup much cleaner. However, if you do this you are now truly relying on JavaScript to drive the page content which I'm not quite comfortable with.

For the most part - especially in public access sites - I continue to use AJAX as a 'nice to have' but not required feature. To facilitate certain tasks without making the functionality completely dependent on the AJAX behavior.

Not for everything though. If I do go the route of a more client centric UI then a mixture of client and server side rendering seems to be the best way to go about it, although I'd argue that conditional logic to display and hide elements on the page still is best handled as part of the template rather than code. It's part of the View IMHO and while it may be cleaner to look at in the code (Javascript or CodeBehind) , it scatters the logic into multiple places and makes the markup harder to maintain IMHO. So personally I'm Ok with conditional blocks.

All a matter of preference though I'm sure. :-}

Steve
August 24, 2009

# re: On the Fly DropDown Editing with jQuery

Sure, I was only suggesting it because in this case, your AJAX implementation won't work without Javascript -- the edit button that you conditionally display would do nothing. So you might as well not even display it in the non-JS case, and use JS to create it (or unhide a display:none block)

I wouldn't suggest eliminating 100% of ASP conditionals simply as a practice; I'm with you on the idea of progressive enhancement via JS. Towards that end I always make sure that my "DOM templates" (aka the HTML the page template puts out) are correct even if the JS doesn't run.

That does sometimes mean that the code gets duplicated across client & server. For instance, in my example if I want to keep the count of items in a list updated, I'd need to send it from server code and then update it from JS code. But that's sort of the price you pay for progressive enhancement, and it's net positive...

If code maintenance becomes too much of a bother, I find a way, as above, to not show the element at all unless JS is enabled. Once I step into AJAX, I'd much rather have the JS code be the authoritative UI code. But as you say - matter of preference! :)

Anyway, worth saying each time I get around to commenting - thanks for your amazing blog. I find answers here all the time.

Anuj Pandey
August 27, 2009

# re: On the Fly DropDown Editing with jQuery


Jeff
December 22, 2009

# re: On the Fly DropDown Editing with jQuery


I am seeing more articles appear on this type of implementation for edit in place (EIP) operations. The thing that bothers me about it is the direct injection of HTML by jQuery into the webapage. I see two problems with this: (1) it violates the unobstrusive javascript principle of jQuery, (or perhaps the Star Trek prime directive =) just/kidding) and (2) you are essentially creating virtual elements that exist but through the good grace of script, but cannot be found anywhere in the original HTML markup for the page. It is #2 that probably bothers me the most because to me it can lead to pages that are prone to scripting errors and difficult to debug because now you have to trace problems through script.

My preferred approach is to explicitly include every element you think you will need. In Rick's example this means I would initially provide a label for C# plus a hidden dropdown list with the language choices. When the language goes into edit mode I would hide the label and show the dropdown list, just like Rick is doing. I would set the dropdown list selection to the current label value. The benefit of this approach too is that you only have to bind the dropdown list once, when the page first renders (which is appropriate for content that will not change very frequently). With my approach there are more elements on the page, but that's the only difference. You still have to script show/hide on the display/edit elements. My approach does not introduce any more scripting complexity than Rick's approach. In fact, I think it produces less script and better yet, you can see every element explicitly rather than in script.

I would welcome alternate viewpoints. FYI, this is a very relevant topic to me. I am currently building an Excel-like online interface using jQuery, where you click a display row once to place it in edit mode.

Rick Strahl
December 22, 2009

# re: On the Fly DropDown Editing with jQuery

@JEff some good points. As with anything I think it depends on the circumstances. I'm not sure how injection into the page constitutes a violation of unobtrusive JavaScript - it's the polar oppposite of it I think since UOJ is about removing script references and client dependencies in the original markup.

I think this is mostly subjective. I prefer to keep the original HTML view as clean as possible so the layout is easy to understand and work with and loading data in as needed. For one thing you may never need this data. For a small dropdown like this that's hardly an issue but for other hidden content it might be. OTOH, I see nothing wrong with putting all the content into the page either, but I wouldn't call either approach better than the other.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024