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:
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:
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:
Other Posts you might also like