Documentation

Working with KnockoutContext

Main object of forming View is instance of the KnockoutContext class. It is necessary to create it in the beginning of the view using the following line:

var ko = Html.CreateKnockoutContext();

At the end of view, it is necessary to apply the formed data binding. You can do it in the following way:

@ko.Apply(Model)

If you have much data and need some time to load it, it makes sense to use lazy analogue (in this case a page will load first, and then additional ajax query will load all data):

@ko.LazyApply(Model, "GetDataAction", "ControllerName")

So, common view template looks in the following way:

@using PerpetuumSoft.Knockout
@model <!-- You model -->
@{
  var ko = Html.CreateKnockoutContext();
}
<!-- Your page -->

@ko.Apply(Model)

Basic data binding

It is possible to bind data using ko.Bind.Lambda expressions that view the initial model as some its property or expression are passed to the methods of this object. Here and further in the documentation we will use source Razor constructions and then show resulting html they are converted to:

Razor: <span @ko.Bind.Text(m => m.MyText)>
Html:  <span data-bind="text: MyText">

It is possible to apply multiple data bindings to the element:

Razor: <span @ko.Bind.Text(m => m.MyText).Visible(m => m.MyVisible).Style("fontSize", m => m.MyFontSize)>
Html:  <span data-bind="text: MyText, visible: MyVisible, style: {fontSize : MyFontSize}">

You can define the following properties using data bindings (every property features reference to its official Knockout specification):

  • Visible (Knockout)
    Razor: @ko.Bind.Visible(m => m.MyVisible)
    Html:  data-bind="visible: myVisible"
    
    Razor: @ko.Bind.Visible(m => !Convert.ToBoolean(m.MyInt))
    Html:  data-bind="visible: !MyInt"
    
  • Text (Knockout)
    Razor: @ko.Bind.Text(m => m.MyText)
    Html:  data-bind="text: MyText"
    
  • Html (Knockout)
    Razor: @ko.Bind.Html(m => m.MyHtml)
    Html:  data-bind="html: MyHtml"
    
  • Value (Knockout)
    Razor: @ko.Bind.Value(m => m.MyValue)
    Html:  data-bind="value: MyValue"
    
  • Disable (Knockout)
    Razor: @ko.Bind.Disable(m => m.MyDisable)
    Html:  data-bind="disable: MyDisable"
    
  • Enable (Knockout)
    Razor: @ko.Bind.Enable(m => m.MyEnable)
    Html:  data-bind="enable: MyEnable"
    
  • Checked (Knockout)
    Razor: @ko.Bind.Checked(m => m.MyChecked)
    Html:  data-bind="checked: MyChecked"
    
  • Options (Knockout)
    Razor: @ko.Bind.Options(m => m.MyOptions)
    Html:  data-bind="options: MyOptions"
    
  • SelectedOptions (Knockout)
    Razor: @ko.Bind.SelectedOptions(m => m.MySelectedOptions)
    Html:  data-bind="selectedOptions: MySelectedOptions"
    
  • OptionsCaption (Knockout)
    Razor: @ko.Bind.OptionsCaption(m => m.MyOptionsCaption)
    Html:  data-bind="optionsCaption: MyOptionsCaption"
    
  • OptionsText (Knockout)
    Razor: @ko.Bind.Disable(m => m.MyDisable)
    Html:  data-bind="disable: MyDisable"
    
  • UniqueName (Knockout)
    Razor: @ko.Bind.UniqueName()
    Html:  data-bind="uniqueName: true"
    
  • ValueUpdate (Knockout)
    Razor: @ko.Bind.ValueUpdate(KnockoutValueUpdateKind.AfterKeyDown)
    Html:  data-bind="valueUpdate: afterkeydown"
    
    Razor: @ko.Bind.ValueUpdate(KnockoutValueUpdateKind.Change)
    Html:  data-bind="valueUpdate: change"
    
    Razor: @ko.Bind.ValueUpdate(KnockoutValueUpdateKind.KeyUp)
    Html:  data-bind="valueUpdate: keyup"
    
    Razor: @ko.Bind.ValueUpdate(KnockoutValueUpdateKind.KeyPress)
    Html:  data-bind="valueUpdate: keypress"
    
  • Css (Knockout)
    Razor: @ko.Bind.Css("class1", m => m.MyCondition1).Css("class2", m => m.MyCondition2)
    Html:  data-bind="css: {class1: MyCondition1, class2: MyCondition2}"
    
  • Style (Knockout)
    Razor: @ko.Bind.Style("color", m => m.MyColor).Style("fontSize", m => m.MyFontSize)
    Html:  data-bind="style: {color: MyColor, fontSize: MyFontSize}"
    
  • Attr (Knockout)
    Razor: @ko.Bind.Attr("href", m => m.MyHref).Attr("title", m => m.MyTitle)
    Html:  data-bind="attr: {href: MyHref, title: MyTitle}"
    
  • Click (Knockout)
    Razor: @ko.Bind.Click("ActionName", "ControllerName", new { parameter = "Key" })
    Html:  data-bind="click : function() {executeOnServer(viewModel, '/ControllerName/ActionName?parameter=Key');}"
    
  • Submit (Knockout)
    Razor: @ko.Bind.Submit("ActionName", "ControllerName", new { parameter = "Key" })
    Html:  data-bind="submit : function() {executeOnServer(viewModel, '/ControllerName/ActionName?parameter=Key');}"
    
  • Custom (Knockout)
    Razor: @ko.Bind.Custom("customName", m => m.MyCustom)
    Html:  data-bind="customName: MyCustom"
    

Form objects

Various objects can be created using ko.Html. Sample:

Razor: @ko.Html.TextBox(m => m.StringValue)
Html:  <input data-bind="value: StringValue" />

Every method has optional parameter defining a set of html attributes:

Razor: @ko.Html.RadioButton(m => m.RadioSelectedOptionValue, new { value = "Alpha" })
Html:  <input data-bind="checked : RadioSelectedOptionValue" type="radio" value="Alpha" />

Additional data bindings can be applied to the created objects:

Razor: @ko.Html.TextBox(m => m.StringValue).ValueUpdate(KnockoutValueUpdateKind.AfterKeyDown)
Html:  <input data-bind="value : StringValue,valueUpdate : 'afterkeydown'" />

You can create the following html objects:

  • TextBox
    Razor: @ko.Html.TextBox(m => m.StringValue)
    Html:  <input data-bind="value: StringValue" />
    
  • Password
    Razor: @ko.Html.Password(m => m.PasswordValue)
    Html:  <input data-bind="value: StringValue" type="password"/>
    
  • Hidden
    Razor: @ko.Html.Hidden()
    Html:  <input type="hidden"/>
    
  • RadioButton
    Razor: @ko.Html.RadioButton(m => m.RadioSelectedOptionValue) 
    Html:  <input data-bind="value: StringValue" type="radio"/>
    
  • CheckBox
    Razor: @ko.Html.CheckBox(m => m.BooleanValue)
    Html:  <input data-bind="value: StringValue" type="checkbox"/>
    
  • TextArea
    Razor: @ko.Html.TextArea(m => m.StringValue)
    Html:  <textarea data-bind="value : StringValue"></textarea>
    
  • DropDownList
    Razor: @ko.Html.DropDownList(m => m.OptionValue)
    Html:  <select data-bind="options : OptionValue"></select>
    
    Razor: @ko.Html.DropDownList(m => m.OptionValue, null, item => item.Key)
    Html:  <select data-bind="options : OptionValue, optionsText : function(item) { return item.Key; }"></select>
    
    Razor: @ko.Html.DropDownList(m => m.OptionValue, null, (m, item) => m.Prefix + item.Key)
    Html:  <select data-bind="options : OptionValue, optionsText : function(item) { return Prefix + item.Key; }"></select>
    
  • ListBox
    Razor: @ko.Html.ListBox(m => m.OptionValue)
    Html:  <select data-bind="options : OptionValue" multiple="multiple"></select>
    
    Razor: @ko.Html.ListBox(m => m.OptionValue, null, item => item.Key)
    Html:  <select data-bind="options : OptionValue, optionsText : function(item) { return item.Key; }" multiple="multiple"></select>
    
    Razor: @ko.Html.ListBox(m => m.OptionValue, null, (m, item) => m.Prefix + item.Key)
    Html:  <select data-bind="options : OptionValue, optionsText : function(item) { return Prefix + item.Key; }" multiple="multiple"></select>
    
  • Span
    Razor: @ko.Html.Span(m => m.StringValue)
    Html:  <span data-bind="text: StringValue"></span>
    
    Razor: @ko.Html.Span("text")
    Html:  <span>text</span>
    
  • SpanInline
    Razor: @ko.Html.SpanInline("new Date().getSeconds()")
    Html:  <span data-bind="text : new Date().getSeconds()"></span>
    
  • Button
    Razor: @ko.Html.Button("Caption", "ActionName", "ControllerName", new { parameter = "Key"})
    Html:  <button data-bind="click : function() {executeOnServer(viewModel, '/ControllerName/ActionName?parameter=Key');}">Caption</button>
    
  • HyperlinkButton
    Razor: @ko.Html.HyperlinkButton("Caption", "ActionName", "ControllerName", new { parameter = "Key"})
    Html:  <a href="#" data-bind="click : function() {executeOnServer(viewModel, '/ControllerName/ActionName?parameter=Key');}">Caption</a>
    
  • Form
    Razor: @using (ko.Html.Form("ActionName", "ControllerName", new { parameter = "Key"}))
           {
             Text
           }
    Html:  <form data-bind="submit : function() {executeOnServer(viewModel, '/ControllerName/ActionName?parameter=Key');}">
             Text
           </form>
    

Nested contexts

Nested contexts allows developers to wrap a part of view in a special construction offering some additional behavior.

It is possible to create the following types of nested contexts:

  • Foreach (Knockout) — walks through all collection elements providing easy access to the elements:
    Razor: @using (var items = ko.Foreach(m => m.Items))
           {
             <tr>
               <td @items.Bind.Text(items.GetIndex())>
               </td>
               <td @items.Bind.Text(m => m)>
               </td>
             </tr>
           }
    Html:  <!-- ko foreach: Items -->
             <tr>
               <td data-bind="text : $index()">
               </td>
               <td data-bind="text : $data">
               </td>
             </tr>
           <!-- /ko -->
    
  • With (Knockout) — easily addresses to some sub-entity of the main model
    Razor: @using (var subModel = ko.With(m => m.SubModel))
           {
             using (var subSubModel = subModel.With(m => m.SubSubModel))
             {
               @subSubModel.Html.Span(m => ko.Model.ModelName + " " + subModel.Model.SubModelName + " " + m.SubSubModelName)
             }
           }
    Html:  <!-- ko with: SubModel -->
             <!-- ko with: SubSubModel -->
               <span data-bind="text : $parents[1].ModelName()+' '+$parent.SubModelName()+' '+SubSubModelName()"></span>
             <!-- /ko -->
           <!-- /ko -->
    
  • If (Knockout) — the way some element is displayed is defined by truth of the condition:
    Razor: @using (ko.If(model => model.Condition1 || model.Condition2))
           {
             <p>Text</p>
           }
    Html:  <!-- ko if: Condition1()||Condition2() -->
             <p>Text</p>
           <!-- /ko -->
    

Sending requests to server

It is assumed that main logic of model processing is hosted on the server. Every action we perform on the model should correspond to method in the controller:

public class FooController : KnockoutController {
  public ActionResult FooAction(FooModel model)
  {
    model.FooAction();
    return Json(model);
  }
}

As you see from the sample, method can take model parameter to which the main model sent from the client side will map. In other words, model parameter contains status of the client model as of the moment the request is sent. It is possible to perform necessary manipulations over the model and then send the updated model in JSON format with the help of return Json(model);. Pay attention to the fact that base class for the controller is KnockoutController. This base class contains extended logic for work with JSON format.

You can create a special control to send requests to server:

Razor: @ko.Html.Button("Display", "FooAction", "Foo")
Html:  <button data-bind="click : function() {executeOnServer(viewModel, '/FooController/FooAction');}">Display</button>
Razor: @ko.Html.HyperlinkButton("Display", "FooAction", "Foo")
Html:  <a href="#" data-bind="click : function() {executeOnServer(viewModel, '/FooController/FooAction');}">Display</a>
Razor: @using (ko.Html.Form("FooAction", "Foo"))
       {
         Display
       }
Html:  <form data-bind="submit : function() {executeOnServer(viewModel, '/FooController/FooAction');}">
         Display
       </form>

It is possible to insert request code directly into the page:

<script type="text/javascript">
  function foo() {
    @ko.ServerAction("FooAction", "Foo");
  }      
</script>

All these constructions can be extended with parameters:

public class FooController : KnockoutController {
  public ActionResult FooParametersAction(FooModel model, int value, string name)
  {
    model.FooParametersAction(value, name);
    return Json(model);
  }
}
Razor: @ko.Html.Button("Display", "FooParametersAction", "Foo", new { value = 0, name = "Sanderson"})
Html:  <button data-bind="click : function() {executeOnServer(viewModel, '/FooController/FooParametersAction?value=0&name=Sanderson');}">Display</button>
Razor: @ko.Html.HyperlinkButton("Display", "FooParametersAction", "Foo", new { value = 0, name = "Sanderson"})
Html:  <a href="#" data-bind="click : function() {executeOnServer(viewModel, '/FooController/FooParametersAction?value=0&name=Sanderson');}">Display</a>
Razor: @using (ko.Html.Form("FooParametersAction", "Foo", new { value = 0, name = "Sanderson"}))
       {
         Display
       }
Html:  <form data-bind="submit : function() {executeOnServer(viewModel, '/FooController/FooParametersAction?value=0&name=Sanderson');}">
         Display
       </form>
<script type="text/javascript">
  function foo() {
    @ko.ServerAction("FooParametersAction", "Foo", new { value = 0, name = "Sanderson"});
  }      
</script>

Please review the following sample to get more information on this ability: Parameters to server.

Adding user-defined scripts

If necessary, you can add your own knockout code to the one generated automatically. It is necessary to initialize generated model by the following code:

@ko.Initialize(Model)

This line will create a model named viewModel and fill it with data taken from the Modelparameter that is defined by the standard C# code. Then you can add custom JavaScriptcode and extend a model:

<script type="text/javascript">
  viewModel.someProperty = someValue;  
</script>

After necessary custom script is added it is necessary to activate Knockout and display model in the view using the following code:

@ko.Apply(Model)

For more detailed information of this feature please review this sample User script.

Forming model and using computed properties

Besides binding interface elements to definite model properties, you can bind to expressions. Samples:

Razor: <button type="submit" @ko.Bind.Enable(m => m.StringValue.Length > 0)>Add</button>
Html:  <button type="submit" data-bind="enable : StringValue().length>0">Add</button>
Razor: @ko.Bind.Enable(m => m.Items.Count > 0)
Html:  data-bind="enable : Items().length>0"
Razor: @using (ko.If(model => model.Condition1 && model.Condition2))
Html:  <!-- ko if: Condition1()&&Condition2() -->
Razor: @ko.Html.Span(m => m.Price).Style("color", m => m.Price > 0 ? "black" : "red")
Html:  <span data-bind="text : Price,style : {color : Price()>0 ? 'black' : 'red'}"></span>

In addition, computed properties can be created directly as model part (review Hello world and Inner computed properties) samples:

Razor: public Expression<Func<string>> FullName() { return () => FirstName + " " + LastName; }
Html:  viewModel.FullName = ko.computed(function() { try { return this.FirstName()+' '+this.LastName()} catch(e) { return null; }  ;}, viewModel);

As you see from the sample, standard viewModel model will be extended with a special computed-property. Advantage of such approach is that the declared method can be used not only on server side, but also as data binding on client side:

Razor: @ko.Html.Span(m => m.FullName())
Html:  <span data-bind="text : FullName"></span>

Inner computed properties sample shows that nested properties can be described not only for main model, but also for sub-models (if you use pure Knockout.js you need to write some sophisticated code, but when you use Knockout MVC no such problem arises).

Special addressing forms: $parent, $data, $index и т.д.

When you use Knockout.js you have to use some specific keywords. Let’s review their Knockout MVC analogue:

  • $parent, $parents, $root, $parentContext — these properties allowed access to different data binding contexts. Using Knockout MVC you don’t need to think about scopes hierarchy and peculiarities of addressing between them — now every context has a name! Sample: Combine context:
    Razor: @using (var items = ko.Foreach(m => m.Items))
           {
             using (var subItems = items.Foreach(m => m.SubItems))
             {
               @subItems.Html.Span(m => ko.Model.Key + " " + items.Model.Caption + " " + m.Name)<br />
             }
           }
    Html:  <!-- ko foreach: Items -->
             <!-- ko foreach: SubItems -->
               <span data-bind="text : $parents[1].Key()+' '+$parent.Caption()+' '+Name()"></span><br />
             <!-- /ko -->
           <!-- /ko -->
    
  • $index - – makes it possible to get index of the collection element in the contexts Foreach of type. To get the corresponding property each context has method GetIndex(). Sample: Region (if, foreach, with):
    Razor: @using (var items = ko.Foreach(m => m.Items))
           {    
             <span @items.Bind.Text(items.GetIndex())></span>      
           }
    Html:  <!-- ko foreach: Items -->
             <span data-bind="text : $index()"></span>
           <!-- /ko -->
    
  • $data - makes it possible to get collection element in the contexts ForeachForeach of type. To get the corresponding property it is necessary to set in the data binding lambda expression that will get a model itself for the model. Sample: Region (if, foreach, with):
    Razor: @using (var items = ko.Foreach(m => m.Items))
           {    
             <span @items.Bind.Text(m => m)></span>      
           }
    Html:  <!-- ko foreach: Items -->
             <span data-bind="text : $data()"></span>
           <!-- /ko -->