Wednesday, January 14, 2015

Microsoft 70-486: Design and implement UI behavior

Exam Objectives


Implement client validation, use JavaScript and the DOM to control application behavior, extend objects by using prototypal inheritance, use AJAX to make partial page updates, implement the UI by using JQuery

Quick Overview of Training Materials


Exam Ref 70-486: Developing ASP.NET MVC 4 Web Applications - Objective 2.2
Programming ASP.NET MVC 4 (OReilly) - Chapters 3 (Data Annotation, 4 (Client Programming) and 6 (AJAX)
Professional ASP.NET MVC 5 (WROX) - Chapters 6 (Data Annotation) and 8 (AJAX)


Previous blogs relevant to objective:

Quick Review

This is another one of those objectives that seems to overlap quite a bit with some of the material covered in 70-480, so I'll briefly touch on the common areas before moving on to the new and interesting stuff.

Use JavaScript and the DOM to control application behavior is covered pretty well in my posts on event handling, interacting with the DOM, and applying style.  Extend objects by using prototypal inheritance is covered pretty directly by the objects and methods post, though I included the link to the post on scope for good measure, since there are some things (such as namespacing) that take advantage of some of the same concepts.  Implement client validation I'll look at in the context of using data annotation and htmlhelper classes below, so I included the link to my post on client side validation with JS to get a different view on it.  Finally, I included my post on AJAX and JQuery which covers the basics, so I won't have to repeat them when discussing the areas covered in this objective.  With that said, moving on...  Oh, one last note... all the example code and screenshots for this post use the MVCMusicStore code (modified as it suited me, of course). With no further ado...

Validation using data annotation and htmlhelpers


In ASP.NET MVC, data validation is handled by the model through the use of attributes called data annotation.  The framework is able to use these data annotations to validate data at both the server (during model binding) and in the client (using generated JavaScript).

While I was experimenting with the various data annotations, I did find one interesting behavior.  There is a distinct difference between validation done by the framework, and that done by the browser as a result of setting html5 types on the input fields.  The two types are below:

Framework validation messages

Browser validation message in Google Chrome.


Data annotation types:
  • Required - I found it interesting that adding this notation didn't add the required attribute to the input element.  It does, however, add the attribute data-val-required="<field name> is required".  This won't trigger a validation check until you try to submit the form.
  • StringLength - Sets the maximum string length of the field.  Validates dynamically, so the character that is over the limit will trigger the validation message.  Adds the attribute data-val-length="The field <field name> must be a string with a maximum length of <value>."
  • Range - Sets the minimum and maximum values for a field.  Adds the attributes data-val-range="The field <field name> must be between <min> and <max>." data-val-range-max="<max>" data-val-range-min="<min>".   I decorated a string field with this attribute and it still performed as expected.  The attribute can take two ints or doubles out of the box, and a third overload takes a type and two strings to set limits for decimal or datetime.
  • RegularExpression - Requires the value to match the specified Regex pattern.  Adds the attributes data-val-regex="The field <field name> must match the regular expression '<regex pattern>'." and data-val-regex-pattern="<regex pattern>".  Because including the regex pattern in the error message can be pretty cryptic to end users, it is common practice to override the error message for this attribute (it's not uncommon elsewhere either, but if you did one override it would be on this one)
  • Compare - Requires that the value of a field match the specified field.  So the ConfirmPassword field has to match the Password, the ConfirmEmail has to match the Email, etc.  Adds the attribute data-val-equalto="'<field name>' and '<compared field name>' do not match."
  • DataType - While not strictly intended for validation, some DataType values will result in validation checks, such as Url and EmailAddress.  The true purpose of this attribute is to give the framework more information about what is intended to go into the field.  So if it's set to "password", the input type will be changed to password and the input will be masked.
  • CreditCard - Input type stays "text", adds attribute data-val-creditcard="The <field name> field is not a valid credit card number."
  • EmailAddress - Sets input type to "email", adds attribute data-val-email="The <field name> field is not a valid e-mail address."  Notice that this is a different validation error than you will get if you set the [DataType(DataType.EmailAddress)] attribute, which reads "Please enter a valid email address" 
  • Url - Sets input type to "url", adds attribute data-val-url="The <field name> field is not a valid fully-qualified http, https, or ftp URL."  This one acted a little funny.  I couldn't get the framework validation to trigger, but it did trigger the browser validation when I tried to submit.
  • Phone - Sets input type to "tel", adds attribute data-val-phone="The <field name> field is not a valid phone number." In order to get this to trigger a validation error, I had to use it on an int field, I couldn't ever get it to work on a string field.
  • FileExtensions - Checks that the file extension of the field value matches the list passed into the attribute.  The list is assigned to the Extentions parameter as a comma separated string. If we wanted jpg and pnd, we'd use this annotation: [FileExtensions(Extensions = "jpg,png")], which would add these attributes: data-val-extension="The <field name> field only accepts files with the following extensions: .jpg, .png" data-val-extension-extension="jpg,png"
  • Remote - This attribute takes two parameters, like this: [Remote("action", "controller")].  Instead of generating validation code for the client side, the field value is passed in an ajax request to the specified controller action.  The action should return a JSON value, true if the value validates and anything else if it fails.  These attributes are added to the input: data-val-remote-additionalfields="*.Email" data-val-remote-url="/StoreManager/test_remote". As near as I can tell this requires ClientValidationEnabled to be set to true.
It's worth pointing out that there is more than one way to validate certain fields.  Emails are a perfect example.  DataType, EmailAddress, and RegularExpression can all be used to validate an email address. The following code:

 
[DataType(DataType.EmailAddress, ErrorMessage = "DataType does validation!")]
[Required(ErrorMessage = "Phone is required")]
[StringLength(24)]
public string Phone { getset; }
 
[EmailAddress(ErrorMessage = "Suck it, DataType, I get custom error messages!")]
[Required(ErrorMessage = "Email Address is required")]
[Display(Name = "Email Address")]
//[RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}",
//ErrorMessage = "Email is is not valid.")]
public string Email { getset; }
 

Produced this result:


Notice that DataType will ignore the custom error message.  The regex pattern seen in the code also works, so if you had a very specific pattern you were after, that would be an option as well.  DataType.Url and the Url attribute behave similarly, however when I compared CreditCard and DataType.CreditCard, I didn't get any validation with DataType.

Data annotations allow you to specify custom validation error messages by including the parameter ErrorMessage = "Your error message" as shown in the example above.

I ran into a bit of wonkiness trying to play with remote validation.  I set up what amounted to a dummy validator that just echoed the input:

 
[OutputCache(Duration = 0, VaryByParam = "*", NoStore = true)]
public JsonResult test_remote(string Email)
{
    return Json("You input: " + Email, JsonRequestBehavior.AllowGet);
    //return Json(true, JsonRequestBehavior.AllowGet);
}
 

A note on the exam ref regarding remote validation. It's basically the readers digest version of the How To article on MSDN. The article includes setting two keys in the config file:

 
  <appSettings>
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  </appSettings>
 

To get it to work I had to change the value for UnobtrusiveJavaScriptEnabled to false. With both set to true it would call the action method but would not pass the field value, so I would just get "You input: " back every time. With ClientValidationEnabled set to false it didn't do anything at all.  This could just be a function of what libraries were included with my example, so it might signify nothing in particular... The WROX text points out that both these settings are set to true by default and generally only need to be set to false for backward compatibility, which makes me further think that my issues have more to do with my particular example project and not the functionality in general.

Ajax and ASP.NET MVC


Support for AJAX is built in to Razor in the form of the AjaxHelper class.  This includes asyncronous versions of ActionLink, BeginForm, and RouteLink, as well as some related functionality.  Using these functions is similar to using the HtmlHelper version, with one notable exception:  they take an AjaxOptions argument that specifies whether to use GET or POST, and what callbacks to call on various events such as OnComplete and OnFailure.  It's even possible to use the UpdateTargetId and InsertionMode options to directly replance the contents of an element.  This is what it might look like:

 
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
<div id="target_element">This text will be replaced by the ajax response</div>
    @Ajax.ActionLink("This is an Ajax Link Whoohoo!",
        "AjaxAction",
        "Home",
        new AjaxOptions
        {
            UpdateTargetId = "target_element",
            InsertionMode = InsertionMode.Replace,
            HttpMethod = "GET"
        },
        new { @class = "ui-blah-blah" })
   

The controller action is painfully simple:

 
public PartialViewResult AjaxAction()
{
    return PartialView();
}
 

And AjaxAction.cshtml is even more so:

 
This is the partial view returned!
 

The result is a quick AJAX call with little fuss:



One quick note on partial views and @RenderSection in _Layout.  The WROX text pointed out this handy feature as part of explaining partial views and JQuery, so I naturally assumed that somehow partial views could be rendered asynchronously and include script that would end up in this section, but alas, that isn't how it works.  When a controller returns a full blown view, the code is all assembled by the server, rendered into html, and sent back as a whole page.  So only the full blown view gets to use the @RenderSection area of the _Layout view...

<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <blah> @RenderBody() </blah>
 
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>
</html>


The other really useful AjaxHelper to know is Ajax.BeginForm().  This basically does the same thing as Html.BeginForm(), but makes it all AJAX-y haha.  It's probably a little superfluous, but I went ahead and did a quick throw away demo of it anyway.  It's practically the same as the one for ActionLink so I wont beat it to death.  The first code block is the helper itself, then the controller and the view:

 
@using (Ajax.BeginForm("AjaxForm""Home",
    new AjaxOptions
    {
        InsertionMode = InsertionMode.Replace,
        HttpMethod = "POST",
        OnFailure = "doh",
        OnSuccess = "yay",
        LoadingElementId = "ajax-loader",
        UpdateTargetId = "whoAmI",
    }))
    {
        <input type="text" name="myName" /><br/>
        <input type="text" name="myAge" /><br/>
        <input type="submit" value="Submit" />
        <img id="ajax-loader"
             src="@Url.Content("~/Content/Images/ajax-loader.gif")"
             style="display:none" 
             height="25"
             width="25"/>
    }
<div id="whoAmI">Who are you?</div>
 
<script>
    function doh() { console.log("What the hell just happened?") };
    function yay() { console.log("It worked! Bitchin!") };
</script>
 

[HttpPost]
public PartialViewResult AjaxForm(FormCollection items)
{
    ViewBag.myName = items["myName"];
    ViewBag.myAge = items["myAge"];
    return PartialView();
}

 
Hi @ViewBag.myName, so you say you<br/>
are @ViewBag.myAge years old, ey? <br />
Doubt it...
 

The results...

There will be situations where returning even a partial view is more overhead than is really necessary.  It is also possible to return a JSON string to render new data onto a page.  While this can be done by manually swapping out id'ed elements with JQuery, a more elegant way to do it with with a JavaScript templating library.  Both the OReilly text and the WROX text cover this using the Mustache.js.  However, since this isn't a library supported by Microsoft, I'm not going to go over it here.  But it is interesting stuff, I would recommend checking out either text (I think WROX did it better, fwiw).

JQuery UI Behavior


The exam ref doesn't go terribly deep into using JQuery for UI.  Tabs, DatePicker, and Progress bar are briefly touched on.  The JQuery UI site has a complete list of available widgets and effects, with demo's and sample code.

List of interactions (most of this is copy/pasted or paraphrased from the JQuery UI documentation):
  • Draggable - Allow elements to be moved using the mouse. Options:
    • scroll, schrollSensitivity, schrollSpeed - automatically schroll document when element dragged beyond viewport
    • containment - select a parent DOM element to limit the movement of the draggable element
    • axis - allow movement only vertically or horizontally
    • cursorAt, curson - control where on the draggable element the cursor appears, and control how it appears
    • start, drag, stop events - fired at the applicable phase of the dragging action
    • handle, cancel - specify an element or group of elements that allows dragging (handle) or doesn't allow dragging (cancel)
    • revert - return element or helper to original position when dragging stops. Values of "valid" and "invalid" change this behavior when coupled with a drop zone.
    • snap, snapMode, snapTolerance, grid - snap to another element, or to a grid
    • helper, opacity, stack, zIndex - control how the element looks as it's dragged.  Stack and zIndex will effect how overlapping draggables will appear.
  • Droppable - Create targets for draggable elements. Options
    • accept - limit which draggables can be dropped into this drop element
    • greedy - prevents propogation of a drop event (nested drop zones)
  • Resizeable - Change the size of an element using the mouse. Options:
    • animate - animate the action
    • containment - limit the size of the resizable element to a containing element
    • helper - shows an outline of the element being resized instead of the actual element
    • min/max height/width - constrain the dimentions the element can be resized to
    • aspectRatio - preserve aspect ratio
    • grid - resizing snaps to a specified grid
    • alsoResize - synchronize the resizing of multiple elements
  • Selectable - Use the mouse to select elements, individually or in a group. Options:
    • stop event - can be used to serialize part of the selected element to update other parts of the UI
    • make the elements identical dimintions and float to create a selectable grid
  • Sortable - Reorder elements in a list or grid using the mouse
    • connectWith - allow two or more lists to share elements. Often implemented by using a shared class value
    • can be made into a grid in a manner similar to that described for "Selectable"
    • placeholder - takes a class, used to style the whitespace made between sortable items when one is moved.
    • dropOnEmpty - boolean, determines whether sortables can be dropped on an empty list
    • items and cancel - exclude elements from items to render them unsortable and invalid drop targets.  Elements passed to cancel can still be drop targets but are not sortable (go to the demo, it'l make more sense...)
List of Widgets 
  • Accordion - Displays collapsible content panels
    • collapsible - allows all the items to be collapsed (by default at least one is open)
    • icons - use classed from the framework to display default and open states
    • heightStyle - set to "fill" to extend the accordion the full height of the containing element, set to "content" to make accordians only at high as what's in them.
    • panels can be made sortable
  • Autocomplete - Filters a pre-populated list as the user types. Examples include data with categories, combining with "button" to create a combobox, using remote data sources, scrollable results.
  • Button - Make standard form elements themeable, including checkboxes and radio buttons. Also possible to add icons.
  • Datepicker - Select date from popup or inline calendar.  Can apply most animation effects.  Display inline by calling .datepicker() on a div instead of an input. Other options:
    • showOtherMonths, selectOtherMonths - boolean, determine whether month besides the current month can be seen or selected.
    • showButtonPanel - adds buttons for "today" and "done" below the calendar.
    • changeMonth, changeYear - month and year can be selected from a dropdown
    • numberOfMonths - show multiple months in the same picker
    • dateFormat - change the way the date is displayed, i.e. 01/01/2015 vs. 1 Jan, 15, etc.
    • showOn, buttonImage, buttonImageOnly, buttonText - add a calendar icon next to input field and open the date picker when it is clicked.
    • locale - change the formating of the calendar according to selected culture i.e. English vs Hebrew vs. Traditional Chinese, etc.
    • altField, altFormat - give altField a selector, and the date will be inserted there in the format specified in altFormat
    • minDate, maxDate - restrict the range of selectable dates
    • showWeek - show which week of the year it is
  • Dialog - Open content in an interactive overlay.  Can be animated using UI effects. Options:
    • modal - confirm an action, add buttons such as "delete" and "cancel"
    • include form markup in content area to create modal form
    • can create modal messages (like alert()), just inlude one button for acknowledgement.
  • Menu - Themeable menu with mouse and keyboard interaction for navigation.  Used on a list.
    • use "items" to exclude headers to create categories
    • use standard icons with menu items
  • Progressbar - Displays the status of a determinate or indeterminate process. Setting the "value" property will display a progression, from 0% to 100% (value from 0 to 100).  Setting value to false results in the "indeterminate" progress bar.
  • Selectmenu - Enhanced version of the standard select element. Add icons or images to select options. Easily add callback funtionality using the "change" property.
  • Slider - Drag a handle to select a numeric value. Options:
    • orientation - vertical or horizontal
    • range, min, max - setting "range" to true renders two handles. Min and max determine the range of selectable numbers. (default is 0 - 100)
    • step - the increment of change
    • slide - callback funtion that is fired when the handle is moved.  Can use to bind slider to other elements.
  • Spinner - Enhanced input for numeric values. Up/down button and arrow key handling
    • step - increment of change
    • numberFormat - default is int, "C" for currency, "n" for decimal. Currency format determined by value of "culture" option.
    • min, max, step - valid range of values, increment of change
  • Tabs - A single content are with multiple tabs, each associated with a header in a list
    • event - default is "click", changing to "mouseover" will open tabs as you hover over them
    • collapsible - similar to accordion, clicking an open tab will close it
    • the list of links that forms the tabs can point to anchors within the page (e.g. "#tab1"), or they can be external links, in which case they are fetched with AJAX.  When doing this, it is important to handle errors.
  • Tooltip - Customizable, themeable tooltips to replace native tooltips. Style, content, and behavior of tooltips are highly configurable.  
Effects controls include funtions that show(), hide(), or toggle() an element using the selected effect. It is also possible to control the easing function.  Classes can also be added, removed, toggled, and switched, which can effect UI behavior.  I haven't had a reason do to a JSFiddle in a while, so I thought I would do one to show off the various JQuery UI effects. On a side note, since these effects switch display to none on the target element, it's important to put the event handlers on a container element if you want to be able to turn them back on again...




No comments:

Post a Comment