Search

Categories

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Send mail to the author(s) E-mail

# Tuesday, 27 September 2011
( jQuery | VidPub )

git mergetool

git checkout –b “admin_home”

Html helpers won’t work with dynamics

Make some Helpers

  • less abstraction
  • more control
@helper TextBox(string name, object value=null) {
    //if value is null, set to empty string
    var val = value ?? "";
    <p>
        <label>@name</label>
        <input type="text" name="@name" id="@name" value="@val" />
    </p>
}

then

<legend>Production</legend>
        @Form.TextBox("Title", @Model.Title)

However we want the Title property to be required:

In MVC3 this would be:"  Html.TextBoxFor(x=>x.Title);

and put a Required DataAnnotation attribute on the Production class.

could also tell it to do in unobtrusively with jquery, and jquery validations.

Getting rid of js files:

image

So now I just have:

image

Scripts loader for js

@helper Scripts(){
    @Assets.Script("jquery.validate.min.js")
    @Assets.Script("jquery.validate.unobtrusive.min.js")
}

in the Form.cshtml helpers

Validations


@helper ValidationMessage(string name) {
    <span class="field-validation-valid" data-valmsg-for="@name" data-valmsg-replace="true"></span>
}

@helper TextBoxRequired(string name, object value = null, string requiredMessage = "Required") {
    var val = value ?? "";
            //using HTML5 and jQuery validations
    <p>
        <label>@name</label>
        <input type="text" name="@name" id="@name" value="@val" data-val="true" data-val-required="@requiredMessage" />
        @ValidationMessage(name)
    </p>
}

then on the front end:

        <legend>Production</legend>
        @Form.TextBoxRequired("Title", @Model.Title)

nice.

FInally:

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Production</legend>
        @Form.TextBoxRequired("Title", @Model.Title)
        @Form.TextArea("Description", @Model.Description)
        @Form.TextBoxNumber("Price", @Model.Price)
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

and helpers:

@helper Scripts(){
    @Assets.Script("jquery.validate.min.js")
    @Assets.Script("jquery.validate.unobtrusive.min.js")
}

@helper ValidationMessage(string name) {
    <span class="field-validation-valid" data-valmsg-for="@name" data-valmsg-replace="true"></span>
}

@helper TextBox(string name, object value = null) {
    //if value is null, set to empty string
    var val = value ?? "";
    <p>
        <label>@name</label>
        <input type="text" name="@name" id="@name" value="@val" />
    </p>
}

@helper TextBoxRequired(string name, object value = null, string requiredMessage = "Required") {
    var val = value ?? "";
            //using HTML5 and jQuery validations
    <p>
        <label>@name *</label>
        <input type="text" name="@name" id="@name" value="@val" data-val="true" data-val-required="@requiredMessage" />
        @ValidationMessage(name)
    </p>
}

@helper TextBoxNumber(string name, object value = null, string numberMessage = "Should be a number") {
    var val = value ?? "";
            //using HTML5 and jQuery validations
    <p>
        <label>@name *</label>
        <input type="text" name="@name" id="@name" value="@val" data-val="true" data-val-number="@numberMessage" />
        @ValidationMessage(name)
    </p>
}

@helper TextArea(string name, string attributes = "", object value = null) {
    var val = value ?? "";
    <p>
        <label>@name</label>
        <textarea name="@name" id="@name" @attributes >@val</textarea>
    </p>
}

More Security

Not using ASP Net Membership, so how can control security on controllers?

We could just add [Authorize(Role)] attribute if we were using ASP Net Membership on each controller class or method.

[Authorize(Roles = "asdf")]

no notion of roles in this site

We are going to go simple – create an Action Filter.

 

Can’t pass a dynamic argument to an extension method.

Roll own Simple Authorization

make an attribute / ActionFilter

namespace VidPub.Web.Infrastructure {
    public class RequiredAdminAttribute : ActionFilterAttribute {
        public override void OnActionExecuting(ActionExecutingContext filterContext) {
            //pull the controller off the context passed in
            //and cast it as own ApplicationController
            var controller = (ApplicationController)filterContext.Controller;

            //user logged in?
            if (!controller.IsLoggedIn) {
                controller.TempData["Error"] = "You need to be logged in to do that";
                filterContext.HttpContext.Response.Redirect("/account/logon");
                return;
            }

            //is the user an admin
            var adminEmails = new string[] { "robconery@gmail.com", "rob@tekpub.com", "davemateer@gmail.com" };
            //CurrentUser is a field in our ApplicationController
            //can't use var... as it would be ynamic object..need to tell the compiler
            string userEmail = controller.CurrentUser.Email;
            if (!adminEmails.Contains(userEmail)) {
                controller.TempData["Error"] = "You are not authorized";
                filterContext.HttpContext.Response.Redirect("/account/logon");
            }
        }
    }
}

this wired up to the field on ApplicationController, which tells us if a user is logged in, and then which user.

then wire up on CruddyController the actions that need to be authorised.

   [RequiredAdmin]
        public virtual ActionResult Create() {
            return View(_table.Prototype);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        [RequiredAdmin]
        public virtual ActionResult Create(FormCollection collection) {
            dynamic item = _table.CreateFrom(collection);
            try {
                _table.Insert(item);
                return RedirectToAction("Index");
            }
            catch {
                TempData["alert"] = "There was an error adding this item";
                return View();
            }
        }

        [RequiredAdmin]
        public virtual ActionResult Edit(int id) {
            var model = _table.Get(ID: id);
            model._Table = _table;
            return View(model);
        }

        [HttpPost]
        [RequiredAdmin]
        public virtual ActionResult Edit(int id, FormCollection collection) {
            var model = _table.CreateFrom(collection);
            try {
                _table.Update(model, id);
                return RedirectToAction("Index");
            }
            catch (Exception ex) {
                TempData["Error"] = "There was a problem editing this record";
                return View(model);
            }
        }

A Little jQuery

Firebug

Why:

  • Write less server code
  • More response app
  • More compelling app

Add in json serialization, so we can talk javascript!

Usually we’d go with something like:

return Json(_table.All(), JsonRequestBehavior.AllowGet);

json.net a lot of people use as a serialiser.

However lets see if can solve it on own without taking another dependency

Getting Json dates to displayed correctly is a long running problem.  Write own, then use strings everywhere to make life simpler.  Dave Ward http://encosia.com/ write this.

Couldn’t get json to output correctly, problem was glimpse…

image

Fiddler was very useful here.

Our new way of calling json serializer:

[HttpGet]
        public virtual ActionResult Index(string query) {
            //sidestep viewgeneration and return json
            //
            IEnumerable<dynamic> results = null;
            results = _table.All();

            return VidpubJSON(results);
        }

        public ActionResult VidpubJSON(dynamic content) {
            var serializer = new JavaScriptSerializer();
            serializer.RegisterConverters(new JavaScriptConverter[] { new ExpandoObjectConverter() });
            var json = serializer.Serialize(content);
            //Response.ContentType = "application/json";
            return Content(json);
        }

and Dave Wards converter which cracks open the IDictionary backing the Expando, explicitly assigns key value bindings that are required…

so then we can parse down to a shortDateString (the dates)

namespace VidPub.Web.Infrastructure {
    public class ExpandoObjectConverter : JavaScriptConverter {
        public override IEnumerable<Type> SupportedTypes {
            get { return new ReadOnlyCollection<Type>(new List<Type>(new Type[] { typeof(ExpandoObject) })); }
        }

        public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) {
            ExpandoObject expando = (ExpandoObject)obj;

            if (expando != null) {
                // Create the representation.
                Dictionary<string, object> result = new Dictionary<string, object>();
                foreach (KeyValuePair<string, object> item in expando) {
                    var value = item.Value ?? "";
                    if (value.GetType() == typeof(DateTime))
                        result.Add(item.Key, ((DateTime)value).ToShortDateString());
                    else
                        result.Add(item.Key, value.ToString());
                }
                return result;
            }
            return new Dictionary<string, object>();
        }
        public override dynamic Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) {
            dynamic result = new ExpandoObject();

            if (dictionary != null) {
                // Create the representation.
                var dc = (IDictionary<string, object>)result;
                foreach (KeyValuePair<string, object> item in dictionary) {
                    var value = item.Value ?? "";
                    dc.Add(item.Key, value);
                }
            }
            return result;
        }
    }
}

Put verbs HttpPut on Edit post, and HttpDelete on a delete post.

jQuery

Making a searchbox.

<script type="text/javascript">
    $(document).ready(function () {

        $("#searchForm").submit(function () {
            var val = $("#search").val();
            alert("You're looking for " + val);
            return false;
        });

    });
</script>

<h2>Vidpub Admin</h2>
<form id="searchForm">
    <label>Find:</label>
    <input type="text" id="search"/>
    <input type="submit" value="go" />
</form>

 

image

want to search over:

  • Productions
  • Episodes
  • Customers
namespace VidPub.Web.Models {
    public class Customers : DynamicModel {
        public Customers()
            : base("VidPub", "Customers", "ID") {
        }

        public dynamic FuzzySearch(string query) {
            return this.Query(@"select ID, email from customers
                            where email LIKE('%'+@0+'%')", query);
        }
    }
}

Lucene or full text indexing would be better if we get lots of data.. but for now it will be fine.

image

<script type="text/javascript">
    $(document).ready(function () {

        $("#searchForm").submit(function () {
            var val = $("#search").val();
            //alert("You're looking for " + val);
            //third is a callback
            $.getJSON("/productions", { query: val }, function (data) {
                alert(data.length);
            });
            return false;
        });

    });
</script>
[HttpGet]
        public virtual ActionResult Index(string query) {
            //sidestep viewgeneration and return json
            IEnumerable<dynamic> results = null;
            if (!string.IsNullOrEmpty(query)) {
                results = _table.FuzzySearch(query);
            }
            else {
                results = _table.All();
            }
            if (Request.IsAjaxRequest()) {
                return VidpubJSON(results);
            }
            return View(results);
        }

it works – we can pass back search results back to the browser using json.

jQuery Templates

using jQuery template:

<script type = "text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
<script type="text/javascript">
    $(document).ready(function () {

        $("#searchForm").submit(function () {
            var val = $("#search").val();
            //alert("You're looking for " + val);
            //third is a callback
            $.getJSON("/productions", { query: val }, function (data) {
                //alert(data.length);
                $("#searchTemplate").tmpl(data).appendTo("#searchResults");
            });
            return false;
        });
    });
</script>

the template:

<script id="searchTemplate" type="text/html">
    <li>
        ${Title}
    </li>
</script>

where to display results:

<div id="searchResults">
</div>

want a link, so need the controller passed in so we can give it that name:

however this will break the templating as there is an array inside…blah something:

this will fix using looping:

<script type="text/javascript">
    $(document).ready(function () {

        $("#searchForm").submit(function () {
            var val = $("#search").val();

            $("#searchResults").empty();

            $.getJSON("/productions", { query: val }, function (data) {
                var results = { controller: "productions", items: data };
                $("#searchTemplate").tmpl(results).appendTo("#searchResults");
            });
            return false;
        });
    });
</script>

<script id="searchTemplate" type="text/html">
    <table>
        {{each items}}
            <tr>
                <td><a href="${controller}/edit/${ID}">${Title}</a></td>
            </tr>
        {{/each}}
    </table>
</script>

 

image

Putting it All Together

Console in Google Chrome is Ctrl+Shift+I

abstracting code out to vidpub-search.js

image

debugging in console.  undefined means jQuery can’t fire a method on the selector I’ve defined.

Instead of selecting by ID, going to select by class:

var vidpubSearch = {
    resetResults: function (controller) {
        $(".search-results").empty();
    },
    getResults: function(query, controller) {
        $.getJSON("/"+controller, { query: query }, function (data) {
            var results = { controller: controller, items: data };
            $("#searchTemplate").tmpl(results).appendTo("#"+controller+"-search-results");
        });
    }
};
jQuery(function () {
    $("#searchForm").submit(function () {
        var val = $("#search").val();
        vidpubSearch.resetResults();
        vidpubSearch.getResults(val, "productions");
        return false;
    });
});

front end:

<script type = "text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
@Assets.Script("vidpub-search.js")

<script id="searchTemplate" type="text/html">
    <table>
        {{each items}}
            <tr>
                <td><a href="${controller}/edit/${ID}">${Title}</a></td>
            </tr>
        {{/each}}
    </table>
</script>


<h2>Vidpub Admin</h2>
<form id="searchForm">
    <label>Find:</label>
    <input type="text" id="search"/>
    <input type="submit" value="go" />
</form>

<div id="productions-search-results" class="search-results">
</div>

testing

image

Review

dynamics a possible problem..

we don’t have much code.. nice!

admin ui is simple.

| | # 
# Friday, 23 September 2011
( jQuery | MVC | VidPub )

Razor automatically encodes HTML for us

@Snippets.FooterText()

public static dynamic FooterText() {
        return string.Format("&copy;{0} VidPub, Inc.", DateTime.Now.Year);
    }

image

This is bad..

Most of the Html helpers eg Html.Action, Html.ActionLink return an MvcHtmlString which inherits from HtmlString.  This is around to control the encoding of html.

@functions {
    public static dynamic FooterText() {
        return new HtmlString(string.Format("&copy;{0} VidPub, Inc.", DateTime.Now.Year));
    }
}

better.  This means we sidestep mvc’s always on encoding.

Partials / Helpers / Functions

Do a quotes class and return a string array.

@Html.Partial got confused when we were handing off dynamics stuff.  So easier to use a helper (and a function in the same file…nice)

@functions{
    public static dynamic Scrub(string quote) {
        return new HtmlString(quote);
    }
}

@helper User(string quote) {
    <div class="quote">
        @Scrub(quote)
    </div>    
}

 

<div class="container">
    <div class="column span-24">
        @foreach (dynamic item in VidPub.Web.Models.Quotes.FromUsers()) {
            @Quotes.User(item)
        }
    </div>
</div>

image

public class Quotes {
        public static dynamic FromUsers() {
            var quotes = new string[] {"I just rocked the interview blah blah"
                ,"This is really great"
                ,"Bough mastering jquery from vidpub and so far it rocks"
                ,"2Bough mastering jquery from vidpub and so far it rocks"
                ,"3Bough mastering jquery from vidpub and so far it rocks"
                ,"4Bough mastering jquery from vidpub and so far it rocks"
            };
            Random rnd = new Random();
            return quotes.OrderBy(x => rnd.Next()).ToList();
        }
    }

Rob has html encoding in the quotes… ie <b> in the acutal quote string, which is why he’s use a Scrub function.  I dont need it.

Jquery Cycle

@HTML.CSS("jquery_cycle.js")

in css

id is a #

class is a .

#user_quotes
{
    margin-bottom:32px;
    padding: 24px;
}

.quote
{
    font-family:"georgia";
    font-style:italic;
    font-size:2.1em;
    color:#4d4d4d;
}

.main
{
    min-height:320px;
}

image

<script>
    $(document).ready(function () {
        $("#user_quotes").cycle({ fx: 'fade', pause: 1, speed: 900, timeout: 4000 })
    });
</script>
<div class="container">
    <div id = "user_quotes" class = "column span-24">
        @foreach (dynamic item in VidPub.Web.Models.Quotes.FromUsers()) {
            @Quotes.User(item)
        }
    </div>
</div>

image

then it cycles through – cool!

Rendering Cycle

image

 

_ViewStart.cshtml

Index.cshtml  (this is the view or view template).. so render view before layout!

_Layout.cshtml (top level component that loads css, js, footer etc..).. so two chunks of html already done are injected into @RenderBody method

then all put into a stringbuilder, then put into the ResponseStream and then we have a webpage!

Review

Partials..hmmm.. unless paired with modelBinder which he’s going to discuss later.

Helpers are simple and paired with functions are great.

| | # 
# Friday, 19 February 2010

Pairing with Michael, we did our first spike from here:

http://ericdotnet.wordpress.com/2009/04/09/jquery-search-box-and-aspnet-mvc/

worked fine locally, but then going live getting some issues.  Looked like it was mateerit.co.nz/search

<script src=" <%=Url.Content("~/Scripts/jquery-1.3.2.js") %>" type="text/javascript"></script>

The above seemed to work, however the javascript seemed harder:

<script type="text/javascript">

$(document).ready(function() {
$("#searchTerm").autocomplete("/Home/getAjaxResult/");
});

</script>

so I cheated and went onto a subdomain:  jsearch.mateerit.co.nz

All works now with firebug too.

image

| | #