Using Knockout bindings in MVC ActionLink - asp.net-mvc

I am attempting to utilise KnockoutJS and MVC 4 in order to display a table with ActionLink definitions in the first column of the table. Displaying the data itself is extremely straight-forward and I'm not having any problem there. The problem I have is in the generation of the ActionLink's.
I have taken a look at Use MVC helpers inside jquery.tmpl templates, but the solution there does not utilise knockout templates and inserting the Url into the model object is not feasible (the app domain model objects used to create the view model will be used extensively through out the application).
The table definition:
<table>
<tbody data-bind="template: { name: 'dmuTableDetail', foreach: tables() }"></tbody>
</table>
(tables is an observable array, hence the parens).
The knockout template definition:
<script id="dmuTableDetail" type="text/html">
<tr>
<td>#Html.ActionLink("Details", "Details", "DMUTableCategory", new { #Id = ??? } )</td>
<td data-bind="text:TableId"></td>
<td data-bind="text:TableName"></td>
</tr>
</script>​
The View Model definition:
var PageViewModel = function () {
self = this;
self.tables = ko.observableArray([]);
self.readItems = function () {
self.tables(jQuery.parseJSON('[{"TableId":1001, "TableName":"Table#1"},{"TableId":1002, "TableName":"Table#2"}]'));
}
}
$(document).ready(function () {
vm = new PageViewModel();
self.readItems('');
ko.applyBindings(vm);
});
(the actual code performs an Ajax call to retrieve the data, but the code above also demonstrates the issue).
Regardless of what I replace the ??? with, I am unable to get the value of the TableId field to be inserted into the href.
Any help would be greatly appreciated.
Thankyou.

Thankyou Eric, you got me thinking about an anchor element and binding the href attribute.
It seems the answer is a little easier than expected (it usually is!).
The table definition: (same as original question)
<table>
<tbody data-bind="template: { name: 'dmuTableDetail', foreach: tables() }"></tbody>
</table>
The knockout template definition: (change to the binding of the href attribute).
<script id="dmuTableDetail" type="text/html">
<tr>
<td><a data-bind="attr: { 'href': '#Url.Action("Details", new RouteValueDictionary() { { "Controller", "DMUTableCategory" } } )/' + TableId }">Details</a></td>
<td data-bind="text:TableId"></td>
<td data-bind="text:TableName"></td>
</tr>
</script>?
The View Model definition: (same as original question)
var PageViewModel = function () {
self = this;
self.tables = ko.observableArray([]);
self.readItems = function () {
self.tables(jQuery.parseJSON('[{"TableId":1001, "TableName":"Table#1"},{"TableId":1002, "TableName":"Table#2"}]'));
}
}
$(document).ready(function () {
vm = new PageViewModel();
self.readItems('');
ko.applyBindings(vm);
});
You dont actually need to RootValueDictionary but I've included it so people can see how to change the controller the request is sent to.

Knockout binds completely on the client side, which is after MVC has rendered the HTML for your page and sent it back to the original browser.
If you want your Knockout template to be able to use a URL that is generated on the server, then you'll have to employ some clever strategy similar to the following:
CSHTML:
#{
// create a dummy URL that you can use as a template
string hrefFormat = Url.Action("Details", "DMUTableCategory", new { id = "{ID}" });
}
<script type="javascript">
// a global string (but you can put it where ever you need to)
var _hrefFormat = #Html.Raw(hrefFormat)
<script>
JS:
self.readItems = function () {
self.tables(jQuery.parseJSON('[{"TableId":1001, "TableName":"Table#1"},{"TableId":1002, "TableName":"Table#2"}]'));
// loop through the 'tables' and add a new 'href' property to each for binding
ko.utils.arrayForEach(self.tables(), function(table){
table.href = _hrefFormat.replace("{ID}", table.TableId);
});
}
Your KO Tmpl where you bind the 'href' property of each table object to the a tag's href attribute:
<script id="dmuTableDetail" type="text/html">
<tr>
<td><a data-bind="attr: { 'href': href }">Details</a></td>
<td data-bind="text:TableId"></td>
<td data-bind="text:TableName"></td>
</tr>
</script>​

Related

Binding with multiple PartialViews in Knockout

I've got a jQuery Accordion and each panel contains a form. All of the forms are the same, and the inputs share the same IDs, names, and data-bind attributes.
Assuming each form has a different binding context (using ko with:), this is how I would set up the Knockout.js ViewModel if there were two forms.
However, I don't know in advance how many forms there will be. I'm rendering a PartialView (which contains the form) for every form object in the MVC ViewModel's collection of forms.
#model ViewModel
<div class="container-fluid">
<div id="jQueryAccordion">
#foreach (var form in Model.AllForms.ToList())
{
<!-- ko with: items[#form.Key] -->
Html.RenderPartial("_Form", form);
<!-- /ko -->
}
// etc.
How would I set up the Knockout.js ViewModel if I don't know how many forms there are going to be?
I suggest that you can load your partial view dinamically via ajax call & bind data knockout binding accordingly as follows:
//C# XxxController: return partial view:
public ActionResult MyView()
{
return PartialView("_MyView");
}
//Ajax call to load partial view at client side:
$.get('Xxx/MyView', function(view){
_contentHolder.html(view);
ko.applyBinding(self, _contentHolder[0]);
})
You can loop through your model collection and apply knockout binding dynamically.
As Anh Bui suggested, I would create them dynamically in the browser. Using applyBindings with markup you created with ASP.net on the server-side is a bit of a hack and means your working against Knockout, not with it.
It would be better to let Knockout take care of actually creating the forms. This means
supplying it only the data it needs to create each form as JSON
creating a Knockout template for the form markup
looping over the data with the forEach binding
The template:
<script type="text/html" id="form-template">
<form action="/target-url">
<label for="user_name">What's your name?</label>
<input type="text" data-bind="value: user_name" name="user_name" />
<label for="user_location">Where are you from?</label>
<input type="text" data-bind="value: user_location" name="user_location" />
</form>
</script>
Next you output your relevant form data as a JSON array on the server side. I haven't used ASP.net, so I can only offer you pseudo-code here:
<script type="application/javascript">
window.form_json_from_server = "
#foreach (var form in Model.AllForms.ToList())
{
// .. ASP.net JSON output magic goes here
}
";
</script>
so that the end result in your markup looks like
<script type="application/javascript">
window.form_json_from_server = "[
{ user_name: "Foo1", user_location: "Bar1" },
{ user_name: "Foo2", user_location: "Bar2" },
{ user_name: "Foo3", user_location: "Bar3" }
]";
</script>
(note that JS strings cannot contain line breaks. I've formatted it here with line breaks for easier reading)
Now we have our form data formatted as JSON, saved in a Javascript string. Next up: your Knockout view model:
var ViewModel = function ViewModel() {
var that = this,
raw_forms_object;
// we reconstitute our JSON string into a Javascript object
raw_forms_object = JSON.parse(window.form_json_from_server);
// this is where the objects made from our JSON will end up in
this.forms = ko.observableArray([]);
ko.utils.arrayForEach(raw_forms_object, function(f) {
// f contains one of our form objects, such as { user_name: "Foo1", user_location: "Bar1" }
// instead of adding f directly to the array, we make a new object in which the
// properties are observables
var form = {
user_name: ko.observable(f.user_name),
user_location: ko.observable(f.user_location),
};
// add our new form object to our observableArray
// make sure to use 'that', because 'this' is the scope of the arrayForEach callback we're in
that.forms.push(form);
});
}
Now we have an observableArray called 'forms' on our view model with our form objects in it. We use the forEach binding to make as many forms as we have form objects:
<div data-bind="template: { name: 'form-template', foreach: forms }"></div>
All that's left is applying an instance of our view model to the page:
ko.applyBindings( new ViewModel() );
If you like, you can try it out in this runnable code snippet:
var ViewModel = function ViewModel() {
var that = this,
raw_forms_object;
// we reconstitute our JSON string into a Javascript object
raw_forms_object = JSON.parse(window.form_json_from_server);
// this is where the objects made from our JSON will end up in
this.forms = ko.observableArray([]);
ko.utils.arrayForEach(raw_forms_object, function(f) {
// f contains one of our form objects, such as
// { user_name: "Foo1", user_location: "Bar1" }
// instead of adding f directly to the array, we make a new object in which the
// properties are observables
var form = {
user_name: ko.observable(f.user_name),
user_location: ko.observable(f.user_location),
};
// add our new form object to our observableArray
// make sure to use 'that', because 'this' is the scope
// of the arrayForEach callback we're in
that.forms.push(form);
});
}
ko.applyBindings( new ViewModel() );
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script type="text/html" id="form-template">
<form action="/target-url">
<label for="user_name">What's your name?</label>
<input type="text" data-bind="value: user_name" name="user_name" />
<label for="user_location">Where are you from?</label>
<input type="text" data-bind="value: user_location" name="user_location" />
</form>
</script>
<div data-bind="template: { name: 'form-template', foreach: forms }"></div>
<script type="application/javascript">
window.form_json_from_server = '[{"user_name": "Foo1","user_location": "Bar1"},{"user_name": "Foo2","user_location": "Bar2"},{"user_name": "Foo3","user_location": "Bar3"}]';
</script>

add item to observable array in viewmodel from another partial view (MVC)

I am new to knockoutJS. I am working on an MVC application where I want to implement knockoutJS but the scenario is bit different.
I have a page where I am showing a list. I have 3 links on the page and on click of them I am adding partial views to page accordingly. What I want to do is that whenever I add values/data to partial views, the list which is on page should be updated with knockout. In other words I want to add value to observable array when I save data from partial view.
Please let me know if this is possible or I should keep it in jquery only.
Here is the code:
Main view:
<input type="button" value="Add Partial View" onclick="LoadPartial();" />
<div id="dvContent"></div>
<h4>People</h4>
<ul data-bind="foreach: people">
<li>
Name at position <span data-bind="text: $index"> </span>:
<span data-bind="text: name"> </span>
Remove
</li>
</ul>
<button data-bind="click: addPerson">Add</button>
<script src="~/Scripts/jquery-1.7.1.js"></script>
<script src="~/Scripts/knockout-2.1.0.js"></script>
<script>
function LoadPartial() {
$.ajax({
url: "/home/index",
dataType:"html",
type: "GET",
success: function (data) {
$("#dvContent").html(data);
}
});
}
</script>
<script>
function AppViewModel() {
var self = this;
self.people = ko.observableArray([
{ name: 'Bert' },
{ name: 'Charles' },
{ name: 'Denise' }
]);
self.addPerson = function () {
self.people.push({ name: "New at " + new Date() });
};
self.removePerson = function () {
self.people.remove(this);
}
}
ko.applyBindings(new AppViewModel());
</script>
Partial View:
<table>
<tr>
<td>Add new Row</td>
<td><input type="button" value="Add" data-bind="click: addPerson"/></td>
</tr>
</table>
Thanks,
JsHunjan
It is easy to accomplish with Knockout. You need to show some code that you have tried though if you want to get some help. I will post a general answer but it isn't going to fix your use case exactly, just basically -
Create an object to hold your new item, you can do this either in the parent or the child view model, but if you do it in the child you need to pass it back to the parent.
Once you hit a save button or add or whatever in the child view model just do a .push() into the observableArray that you created ex... - myObservableArray.push(newItem());
Knockout will recognize all of the changes taking place and perform the actions you want automatically.
Hope this helps.

ASP.NET MVC 3 EF Code First - Master Details CRUD

I am using ASP.NET MVC 3 EF Code First with Razor + SQLserver and Want to implement Master Details scenario (like Order, Orderlines) with CRUD operations. I have come across some online examples like http://hasibulhaque.com/index.php/2011/master-detail-crud-operations-ef-asp-net-mvc-3/ but they heavily depends on JQuery or other complex implementations. Can somebody suggest me some step by step approach with a clean code?
there are good tutorials at the asp.net site.
and i recommend you switch to mvc4 learning.
here is a link:
http://www.asp.net/mvc/tutorials/mvc-4/getting-started-with-aspnet-mvc4/intro-to-aspnet-mvc-4
If you want scaffolding do it for you, unfortunately it's not possible and you can't do that simply. Besides, you must use jquery and ajax to implement what you want.
I think the best and simplest way for you is that you have a view for creating Form and at the bottom of it put a fieldset to assign FormFields to it.
For the fieldset, you should have two partial views: One for create and another for edit. The partial view for creating should be something like this:
#model myPrj.Models.Form_FormFieldInfo
#{
var index = Guid.NewGuid().ToString();
string ln = (string)ViewBag.ListName;
string hn = ln + ".Index";
}
<tr>
<td>
<input type="hidden" name="#hn" value="#index" />
#Html.LabelFor(model => model.FormFieldID)
</td>
<td>
#Html.DropDownList(ln + "[" + index + "].FormFieldID",
new SelectList(new myPrj.Models.DbContext().FormFields, "ID", "FieldName"))
</td>
<td>
<input type="button" onclick="$(this).parent().parent().remove();"
value="Remove" />
</td>
</tr>
By calling this partial view in the create place view ajaxly, you can render some elements for each tag. Each line of elements contains a label, a DropDownList containing tags, and a remove button to simply remove the created elements.
In the create place view, you have a bare table which will contain those elements you create through the partial view:
<fieldset>
<legend>Form and FormFields</legend>
#Html.ValidationMessageFor(model => model.FormFields)</label>
<table id="tblFields"></table>
<input type="button" id="btnAddTag" value="Add new Field"/>
<img id="imgSpinnerl" src="~/Images/indicator-blue.gif" style="display:none;" />
</fieldset>
and you have the following script to create a line of elements for each tag:
$(document).ready(function () {
$("#btnAddField").click(function () {
$.ajax({
url: "/Controller/GetFormFieldRow/FormFields",
type: 'GET', dataType: 'json',
success: function (data, textStatus, jqXHR) {
$("#tblFields").append(jqXHR.responseText);
},
error: function (jqXHR, textStatus, errorThrown) {
$("#tblFields").append(jqXHR.responseText);
},
beforeSend: function () { $("#imgSpinnerl").show(); },
complete: function () { $("#imgSpinnerl").hide(); }
});
});
});
The action method GetFormFieldRow is like the following:
public PartialViewResult GetFormFieldRow(string id = "")
{
ViewBag.ListName = id;
return PartialView("_FormFieldPartial");
}
and your done for the create... The whole solution for your question has many codes for views, partial views, controllers, ajax calls and model binding. I tried to just show you the way because I really can't to post all of them in this answer.
Here is the full info and how-to.
Hope that this answer be useful and lead the way for you.

Get value from selected row/celll

I'm creating table like this:
<table>
<tr>
<th>Column</th>
</tr>
#foreach (var item in someList)
{
foreach (var item1 in item)
{
<tr>
<td onclick="myMethod();">#item1.name</td>
</tr>
}
}
</table>
And here is the method which is called when the row is selected:
<script type="text/javascript" language="javascript">
function myMethod() {
var clickedCell = $(this);
alert(clickedCell.text());
}
</script>
But it's not working! How can I get the text from the row/cell which is selected/clicked on?
I also tried:
<script type="text/javascript" language="javascript">
function myMethod() {
alert($(this).html());
}
</script>
and it's giving me null althought the table is full.
I'm inferring from this line that you're using jQuery:
var clickedCell = $(this);
If that's the case, let's take a step back for a moment and separate your JavaScript from your HTML. Instead of this:
<td onclick="myMethod();">#item1.name</td>
which has in-line JavaScript (which is generally frowned upon), try something like this:
<td class="clickableCell">#item1.name</td>
Now it's just markup, which is a bit cleaner. Next you need to attach click events to your rendered cells:
$(document).ready(function() {
$('td.clickableCell').click(function() {
alert($(this).text());
});
});
Now this refers to the element to which jQuery is binding the click event, so it can be easily referenced in the code, as opposed to having to pass a self-reference from the click event being bound within the HTML (which is another approach, but continues down the road of mixing markup with code).

Building a form from json

I'm fetching an array of viewmodels from my controller using jquery+json. I then build a form where each row in a table represents one viewmodel.
My question is: How should I name each form element so that I can get it to my controller action like this:
public ActionResult Update(MyViewModel[] models)
{
}
Edit: I'm using jquery-tmpl to generate the form, and I'm also trying to figure out how to get an index variable in it (if that's needed for the form generation).
I managed to get it working.
My jquery template:
<script id="wagonTemplate" type="text/x-jquery-tmpl">
<tr>
<td>
<input type="checkbox" value="true" class="wagoncheck" name="wagons[${$item.getIndex()}].IsSelected" />
</td>
<td>
<input type="text" name="wagons[${$item.getIndex()}].WagonId" value="${WagonId}" style="width:120px" />
</td>
<td>
<input type="text" name="wagons[${$item.getIndex()}].WagonNumber" value="${WagonNumber}" style="width:20px" />
</td>
</script>
Method that loads the template:
function loadWagons(trainId, partId) {
$.getJSON('/train/wagons/' + escape(trainId) + '?partNo=' + partId, function (data) {
$wagons = $('#wagons tbody');
$wagons.empty();
// the function used in the template to get an index.
var tmplOptions = {
getIndex: function getIndex() {
return $.inArray(this.data, data);
}
};
$("#wagonTemplate").tmpl(data, tmplOptions).appendTo($wagons);
});
}
In other words:
To get a YourModel[] items argument in your controller action you need to name the items as items[0].MyProperty' where0` should correspond to the index in the array.
To get an index in a jquery template, just use pass a method in the options to the template function. I'm using a slightly modified version of the answer found here. Passing the item as done in that answer is not necessary as this points on the current item.

Resources