- Send changes without need confirmation
- Quickly Navigate to any field that you want edit
- Provide a visual indicator of the updated fields
- Updates are sent back to the server when textboxes lost focus: using a KnockoutJs extender the temperature fields changes are captured and sent to server using ajax calls.
- Updated values are highlighted: a css binding changes the textbox background color after a field is updated successfully on server
- Key based navigation: a jQuery handler allow navigate between textboxes using the arrows keys
ko.extenders.realtime = function(target, options) { // store private vars to use as parameters on updates // cityId: the key of entity to be updated // daytime: the field of entity to be updated (Morning/Afternoon/Midnight) target.cityId = options[0]; target.daytime = options[1]; //add a sub-observable to mantain a changed flag target.hasChanged = ko.observable(false); //define a function to send updates function update(newValue) { $.ajax({ url: "/home/updateforecast", data: { cityId: target.cityId, daytime: target.daytime, value: newValue }, type: "post", success: function(result) { if(result) target.hasChanged(true); } }); } //update whenever the value changes target.subscribe(update); //return the original value return target; };
var model = @Html.Raw(new JavaScriptSerializer().Serialize(Model)); var viewModel = { forecasts : ko.observableArray(ko.utils.arrayMap(model, function(forecast) { return new ForecastModel(forecast); })) }; function ForecastModel(forecast) { this.City = ko.observable(forecast.City); // extend the observable fields that will be updated in realtime this.Morning = ko.observable(forecast.Morning).extend({realtime:[forecast.Id, "Morning"]}); this.Afternoon = ko.observable(forecast.Afternoon).extend({realtime:[forecast.Id, "Afternoon"]}); this.Midnight = ko.observable(forecast.Midnight).extend({realtime:[forecast.Id, "Midnight"]}); } ko.applyBindings(viewModel);
<h1> Realtime Weather Forecast Editor</h1> <table id="tblForecasts"> <thead> <tr> <th> City </th> <th> Morning </th> <th> Afternoon </th> <th> Midnight </th> </tr> </thead> <tbody data-bind="template: { name: 'forecastRowTemplate', foreach: forecasts }"> </tbody> </table> <script type="text/html" id="forecastRowTemplate"> <tr> <td><span data-bind="text: City"/></td> <td><input data-bind="value: Morning, css: { hasChanged: Morning.hasChanged }"/></td> <td><input data-bind="value: Afternoon, css: { hasChanged: Afternoon.hasChanged }"/></td> <td><input data-bind="value: Midnight, css: { hasChanged: Midnight.hasChanged }"/></td> </tr> </script>
$("#tblForecasts").on("keydown", "input:text", function(e) { // detect arrows pressing if (e.keyCode < 37 || e.keyCode > 40) return; e.preventDefault(); var target; var cellAndRow = $(this).parents('td,tr'); var cellIndex = cellAndRow[0].cellIndex; var rowIndex = cellAndRow[1].rowIndex; switch (e.keyCode) { // left arrow case 37: cellIndex = cellIndex - 1; break; // right arrow case 39: cellIndex = cellIndex + 1; break; // up arrow case 40: rowIndex = rowIndex + 1; break; // down arrow case 38: rowIndex = rowIndex - 1; break; } target = $('table tr').eq(rowIndex).find('td').eq(cellIndex).find("input:text"); if (target != undefined) { target.focus(); target.select(); } });
public class Forecast { public int Id { get; set; } public string City { get; set; } public int Morning { get; set; } public int Afternoon { get; set; } public int Midnight { get; set; } }
private const string C_ForecastModels = "ForecastModels"; // Expose the stored Forecasts from Session public IEnumerable<Forecast> ForecastModels { get { if (Session[C_ForecastModels] == null) { Session[C_ForecastModels] = new[] { new Forecast { Id=1, City="Barcelona", Morning=90, Afternoon=45, Midnight=40}, new Forecast { Id=2, City="Berlin", Morning=78, Afternoon=34, Midnight=0 }, new Forecast { Id=3, City="New York", Morning=45, Afternoon=35, Midnight=20 }, new Forecast { Id=4, City="Sydney", Morning=65, Afternoon=45, Midnight=40 }, new Forecast { Id=5, City="Tokio", Morning=74, Afternoon=70, Midnight=60 } }; } return Session[C_ForecastModels] as IEnumerable<Forecast>; } set { Session[C_ForecastModels] = value; } } public ActionResult Index() { // Return the Session stored Forecasts return View(ForecastModels); } // // Update Forecast value for selected daytime (Morning/Afternoon/Midnight) [HttpPost] public JsonResult Updateforecast(int cityId, string daytime, int value) { var cityForecast = ForecastModels.SingleOrDefault(forecastModel => forecastModel.Id == cityId); if (cityForecast == null) return Json(false); switch (daytime) { case "Morning": cityForecast.Morning = value; break; case "Afternoon": cityForecast.Afternoon = value; break; case "Midnight": cityForecast.Midnight = value; break; } return Json(true); }
Enjoy!



