JQuery has been always an interesting topic, but I didn’t find time to try it… looking into MVC3 finally I get in touch with it.
I succeded in using asp.net engine together with MVC3 in the same web app.
For some reason global.asax events didn’t fire up, but I found that deleting the file and adding back fixes this problem.PaceSpeedCalculatorModel
I tried to reuse asp.net master pages, without success. I gave up when I got an error saying that System.Web.Mvc.ViewMasterPage doesn’t derive from the correct class. I‘ll wait when this will be natively supported (MVC4?) or when someone else will discover a workaround.
The solution proposed works fine for reusing aspx master pages in a razor view (extensions methods does the magic!!!)
The goal I want to achieve is a simple conversion between pace and speed (pace is how much minutes and seconds I takes to cross 1km, speed is in km/h).
So 3 textbox minutes, seconds, speed.
I created a model for this, with appropriate annotations.
PaceSpeedCalculatorModel.cs
- namespace FitCalc.Models
- {
- public class PaceSpeedCalculatorModel
- {
- [Required]
- [StringLength(2, MinimumLength = 1)]
- [Display(Name = "paceMinutes")]
- [RegularExpression(@"[0-9]{1,2}", ErrorMessage = "Minutes must be in the range 00-99")]
- [ScaffoldColumn(false)]
- [DataType(DataType.Text)]
-
- [Range(0,99)]
- public string paceMinutes { get; set; }
-
- [Required]
- [StringLength(2, MinimumLength = 1)]
- [Display(Name = "paceSeconds")]
- [RegularExpression(@"[0-9]{1,2}", ErrorMessage = "Seconds must be in the range 00-59")]
- [ScaffoldColumn(false)]
- [DataType(DataType.Text)]
- [Range(0, 59)]
- public string paceSeconds { get; set; }
-
- [Required]
- [StringLength(2, MinimumLength = 1)]
- [Display(Name = "speed")]
- [ScaffoldColumn(false)]
- [DataType(DataType.Text)]
- [Range(0,99)]
- public string speed { get; set; }
-
-
- }
- }
Well, not really appropriate… It would be better if the DataType was an integer value for minutes and seconds, but I didn’t find the corresponding value (maybe custom, but for now Text is enough).
Created the controller. I wanted to calculate values either by posting the form data and by doing calculation as you type.
CalculatorController.cs
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.Web.Mvc;
- using FitCalc.Models;
-
- namespace FitCalc.Controllers
- {
- public class CalculatorController : Controller
- {
- //
- // GET: /Calculator/
- private static PaceSpeedCalculatorModel _paceSpeed = new PaceSpeedCalculatorModel();
-
-
- public ActionResult Index()
- {
- //_paceSpeed.paceMinutes = "1";
- //_paceSpeed.paceSeconds = "0";
-
- return this.RazorView(_paceSpeed);
- }
- [HttpPost]
- public ActionResult Index(PaceSpeedCalculatorModel pscm)
- {
- _paceSpeed = pscm;
-
-
- return RedirectToAction("Index");
- }
-
- [HttpPost]
- public ActionResult PaceToSpeed(PaceSpeedCalculatorModel pscm)
- {
- if (pscm.speed == null)
- pscm.speed = RunningCalc.PaceToSpeed(new RunningTime(pscm.paceMinutes, pscm.paceSeconds)).ToString();
- else
- {
- double speed = 0;
- RunningTime rt = null;
- if (double.TryParse(pscm.speed, out speed))
- rt = RunningCalc.SpeedToPace(speed);
- pscm.paceMinutes = rt.Minutes.ToString();
- pscm.paceSeconds = rt.Seconds.ToString();
- }
- return this.View(pscm);
-
- }
- }
- }
For the first the Action is “Index”. Being an asp, asp.net folk it takes a moment to understand how the data posted by the form is handled. Normally there will be an Index.asp with the form that post to a Calculate.asp. With MVC the action is the same, differentiated by the “HttpPost” attribute. The first time (Index.asp) the browser uses a GET and calls “public ActionResult Index()”. When you submit the form (Calculate.asp) the browser uses a POST activating the “public ActionResult Index(PaceSpeedCalculatorModel pscm)” method. MVC runtime fills automagically the PaceSpeedCalculatorModel parameter, matching POST payload names with class ones.
Creating the view based on the annotated model class is really easy and everything went smooth.
Index.cshtml
- @model FitCalc.Models.PaceSpeedCalculatorModel
- <script src="../../Scripts/jquery-1.4.4-vsdoc.js" type="text/javascript"></script>
- <script src="../../Scripts/json2.js" type="text/javascript"></script>
- @{
- ViewBag.Title = "Index";
- Layout = "~/Views/Shared/_Layout.cshtml";
- }
- <script type="text/javascript">
-
- $(InitPage);
- function InitPage()
- {
-
-
- var paceSpeed = { paceMinutes: $("#paceMinutes").val(),
- paceSeconds: $("#paceSeconds").val(),
- speed: $("#speed").val()
- };
-
- $(function ()
- { setTimeout(checkSearchChanged, 0.1);
- });
-
- function checkSearchChanged()
- { var currentPaceSpeed = { paceMinutes: $("#paceMinutes").val(),
- paceSeconds: $("#paceSeconds").val(),
- speed: $("#speed").val()
- };
- if (true &&
- ((currentPaceSpeed.paceMinutes)
- && (paceSpeed.paceMinutes)
- && currentPaceSpeed.paceMinutes != paceSpeed.paceMinutes
- && currentPaceSpeed.paceMinutes != ''
- && currentPaceSpeed.paceMinutes > 0
- )
- || ((currentPaceSpeed.paceSeconds) && currentPaceSpeed.paceSeconds != paceSpeed.paceSeconds && currentPaceSpeed.paceSeconds != '' && currentPaceSpeed.paceSeconds > -1)
- || ((currentPaceSpeed.speed) && currentPaceSpeed.speed != paceSpeed.speed && currentPaceSpeed.speed != '' && currentPaceSpeed.speed.replace(',','.') > 0)
-
- )
- {
- //alert("ok inizioe");
- if ((currentPaceSpeed.speed) && (paceSpeed.speed != null) && currentPaceSpeed.speed != paceSpeed.speed)
- {
- // if speed changed
- paceSpeed =
- { paceMinutes: null,
- paceSeconds: null,
- speed: $("#speed").val()
- };
- }
- else
- {
- paceSpeed =
- { paceMinutes: $("#paceMinutes").val(),
- paceSeconds: $("#paceSeconds").val(),
- speed: null
- }
- }
-
- var jsonString = JSON.stringify(paceSpeed);
- $.ajax(
- {
- url: '/Calculator/PaceToSpeed',
- type: "POST",
- data: jsonString,
- dataType: "json",
- contentType: "application/json; charset=utf-8",
- dataFilter: function(data, dataType) {
- if (data.indexOf("<!-") > 0)
- {
- //alert(data.indexOf("<!-"));
- //alert(data.substring(0, data.indexOf("<!-")));
- return data.substring(0, data.indexOf("<!-"));
- }
- else
- return data;
- },
- success: function (objResult)
- {
- var paceSpeedResult = objResult;
-
- $("#speed").val(objResult.speed);
- $("#paceSeconds").val(objResult.paceSeconds);
- $("#paceMinutes").val(objResult.paceMinutes);
- paceSpeed = { paceMinutes: $("#paceMinutes").val(),
- paceSeconds: $("#paceSeconds").val(),
- speed: $("#speed").val()
- };
- setTimeout(checkSearchChanged, 0.1);
- },
- error: function (objResult)
- {
- alert("errore" + objResult);
- setTimeout(checkSearchChanged, 0.1);
- }
- });
- }
- else
- {
- setTimeout(checkSearchChanged, 0.1);
- }
- }
-
- }
- </script>
- <h2>
- Index</h2>
- @using (Html.BeginForm())
- {
- @Html.ValidationSummary(true)
- <fieldset>
- <legend>PaceSpeedCalculatorModel</legend>
- <div class="editor-label">
- @Html.LabelFor(model => model.paceMinutes)
- </div>
- <div class="editor-field">
- @Html.EditorFor(model => model.paceMinutes)
- @Html.ValidationMessageFor(model => model.paceMinutes)
- </div>
- <div class="editor-label">
- @Html.LabelFor(model => model.paceSeconds)
- </div>
- <div class="editor-field">
- @Html.EditorFor(model => model.paceSeconds)
- @Html.ValidationMessageFor(model => model.paceSeconds)
- </div>
- <div class="editor-label">
- @Html.LabelFor(model => model.speed)
- </div>
- <div class="editor-field">
- @Html.EditorFor(model => model.speed)
- @Html.ValidationMessageFor(model => model.speed)
- </div>
- <p>
- <input type="submit" value="Save" />
- </p>
- </fieldset>
- }
- <div>
- @Html.ActionLink("Back to List", "Index")
- </div>
Doing calculation as you type it’s a little bit tricky. I found Scott Allen “jQuery for the .NET Developer” video very interesting. Then followed Scott post when he talks about using jquery and JSON to post data to the server. In the sample it wasn’t clear where the JSON.stringify method comes from. To make it work I downloaded Json2.js from www.json.org (well, from github because Json.org wasn’t available). Here the solution found in comments.
With keypress event you didn’t catch all modification to the textbox (say mouse paste), so I switched to this solution.
With the previous view, I got what I was looking for. I send and receive JSON data to the PaceToSpeed Action. In the view I send the model JSON encoded with raw HTML.
@Html.Raw(@Json.Encode(@Model))
My hoster append some ad to the HTML stream, so I have to filter it when received by the ajax call using the dataFilter method.