I am using Knockout and have the ViewModel bound to my data object in my ASP.Net MVC 4 project quite nicely like so:
$(document).ready(function() {
properties = #Html.Raw(Json.Encode(Model));
selectedProperty = properties[0];
viewModel = { properties: ko.mapping.fromJS(#Html.Raw(Json.Encode(Model))), selectedProperty: ko.observable()};
viewModel.setItem = function(item) {
viewModel.selectedProperty(item);
}
ko.applyBindings(viewModel);
Now I want to refactor my JavaScript so that the logic is encapsulated inside a class:
RealEstate.Search = function (properties) {
this.properties = properties;
this.selectedProperty = this.properties[0];
this.viewModel = { properties: ko.mapping.fromJS(this.properties), selectedProperty: ko.observable()};
this.viewModel.setItem = function(item) {
viewModel.selectedProperty(item);
}
ko.applyBindings(this.viewModel);
}
And I am instantiating that object in my HTML page like so:
$(document).ready(function() {
search = new RealEstate.Search(#Html.Raw(Json.Encode(Model)));
}
Now, I am getting the following error:
Error: Unable to parse bindings.
Message: ReferenceError: 'properties' is undefined;
Bindings value: foreach: properties
Here is the snipped HTML for the table bound to the ViewModel:
<div id="divDataTable" data-bind="with: properties">
<table id="dataTable" class="tablesorter">
<thead>
<tr>
<th>Address
</th>
<th>
Suburb
</th>
<th>Price
</th>
<th>Beds
</th>
<th>Baths
</th>
<th>Days Listed
</th>
</tr>
</thead>
<tbody data-bind="foreach: properties">
<tr data-bind="click: $root.setItem">
<td>
<label data-bind="text: $data.Street"></label>
<input data-bind="attr: { value : $index(), id : $index(), name : $index() }" type="hidden" />
</td>
<td data-bind="text: $data.Suburb"></td>
<td data-bind="text: $data.PriceFormatted"></td>
<td data-bind="text: $data.NumOfBedrooms"></td>
<td data-bind="text: $data.NumOfBathrooms"></td>
<td data-bind="text: $data.DaysListed"></td>
</tr>
</tbody>
</table>
</div>
</section>
<div id="divProperty">
<aside class="float-right" data-bind="with: selectedProperty">
<table>
<tr>
<td>
<label data-bind="text: $data.Street"></label>
</td>
<td>
<label data-bind="text: $data.PriceFormatted"></label>
</td>
</tr>
<tr>
<td colspan="2">
<img src="#" /></td>
</tr>
<tr>
<td>Beds:
<label data-bind="text: $data.NumOfBedrooms"></label>
</td>
<td>On OZMite:
<label data-bind="text: $data.DaysListed"></label>
</td>
</tr>
<tr>
<td>Baths:
<label data-bind="text: $data.NumOfBathrooms"></label>
</td>
<td>Year built:</td>
</tr>
</table>
</aside>
I would appreciate it if someone could shed some light on what I am doing wrong.
With the data-bind="with: properties" you are already "in the context" of the properties property inside your div.
So when you write <tbody data-bind="foreach: properties"> KO tries to find the properties property inside your properties array.
What you need is to use to reference the current binding context with the $data.
So your foreach should look like this:
<tbody data-bind="foreach: $data">
...
</todby>
Related
I want to implement bootstrap accordion in my application. Currently, If I click on any rows, it gives me first #Html.Raw(item.D) and #Html.Raw(item.B).
What i want is that, if i click on 2nd row then its respective #Html.Raw(item.D) and #Html.Raw(item.B) should displayed and so on.
Please suggest me where I am going wrong.
<div class="panel-body">
<table class="table">
#foreach (var item in Model)
{
<tr class="accordion-toggle" data-toggle="collapse" data-target="#12345-cores">
<td align="left">
#Html.Raw(item.H)
</td>
<td align="right">
#Html.Raw(item.E)
</td>
</tr>
<tr>
<td class="accordion-body collapse" id="12345-cores">
<table>
<tbody>
<tr>
<td>
#Html.Raw(item.D)
</td>
<td></td>
<td>
#Html.Raw(item.B)
</td>
</tr>
</tbody>
</table>
</td>
</tr>
}
</table>
You need to specify id uniquely, to do so you need to append raw id(or something unique for each row) to id and data-target attributes,
<div class="panel-body">
<table class="table">
#foreach (var item in Model)
{
<tr class="accordion-toggle" data-toggle="collapse" data-target="#12345-cores#(item.id)">
<td align="left">
#Html.Raw(item.H)
</td>
<td align="right">
#Html.Raw(item.E)
</td>
</tr>
<tr>
<td class="accordion-body collapse" id="12345-cores#(item.id)">
<table>
<tbody>
<tr>
<td>
#Html.Raw(item.D)
</td>
<td></td>
<td>
#Html.Raw(item.B)
</td>
</tr>
</tbody>
</table>
</td>
</tr>
}
</table>
updated : data-target="#12345-cores#(item.id) & id="12345-cores#(item.id)"
I am trying to pass data from view to controller. I used BeginForm and i can pass data which users enter to textbox. But I want to also pass label data because label is filled automatically and i need to save this label text to database. How can i do?
View:
#using (Html.BeginForm("Room", "Booking", FormMethod.Post))
{
<table>
<tr>
<td align="left"><lable for="eventName">Description:</lable></td>
<td><input name="eventName" id="eventName"></td>
</tr>
<tr>
<td align="left"><lable for="startDate">Start Date : </td>
<td align="left"><label id="startDate" name="startDate" /></td>
</tr>
<tr>
<td></td>
</tr>
<tr>
<td align="right" colspan="2">
<button type="submit" class="btn-primary" name="submit" id="submit">Save</button>
</td>
</tr>
<tr>
</tr>
</table>
}
Controller:
[HttpPost]
public ActionResult Room(FormCollection form)
{
using (BookingEntities ent = new BookingEntities ())
{
ReservationTBL Tbl = new ReservationTBL();
Tbl.Description = form["eventName"].ToString();
Tbl.startDate= form["startDate"].ToString();
ent.BookingTBL.Add(Tbl);
ent.SaveChanges();
}
return View();
}
The label data doesn't get send when you post your form. What you can do however is to add an input with type hidden that contains your label data.
Something like this:
<tr>
<td align="left"><label for="startDate">Start Date : </td>
<td align="left">
<label>#Model.StartDate</label>
<input type="hidden" name="startDate" value="#Model.StartDate" id="startDate"/>
</td>
</tr>
My page is as follows:
<button id="add">Add Data</button>
<button id="show">show</button>
<table>
<tr style="vertical-align:top">
<td>
<table border="1">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
</thead>
<tbody data-bind="foreach: students">
<tr>
<td data-bind="text: id"></td>
<td>
<input type="text" data-bind="value: name" />
</td>
<td> Select
</td>
</tr>
</tbody>
</table>
</td>
<td>
<table id="data">
<tbody data-bind="with: selectedData">
<tr>
<td>Id</td>
<td>
<input type="text" data-bind="value: id" />
</td>
</tr>
<tr>
<td>Name</td>
<td>
<input type="text" data-bind="value: name" />
</td>
</tr>
<tr>
<td></td>
<td>
<input type="button" value="Close" />
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
The javascript is as follows:
function ViewModel() {
var self = this;
self.students = ko.observableArray([]);
self.showData = function (dt) {
if (window.console) console.log(dt);
self.selectedData(dt);
$('#data').show();
}
this.selectedData = ko.observable();
}
$(function () {
window.appViewModel = new ViewModel();
ko.applyBindings(window.appViewModel);
$('#add').click(function () {
var model = window.appViewModel;
$.each(students, function (idx, student) {
if (window.console) console.log(student);
model.students.push(student);
});
$('table').show();
});
$('table').hide();
$('input').click(function () {
$('#data').hide();
});
$('#show').click(function () {
var s = JSON.stringify(window.appViewModel.students());
alert(s);
});
});
Preview:
In pic, I click on the select corresponding to student with id = 3. The other table shows up with the selected student details. Suppose I enter something in textbox 1, textbox 2 doesn't update, and vice versa.
What to do to make that happen?
Fiddle: http://jsfiddle.net/deostroll/YdrQf/1/
Your inputs aren't updating because the id and name values are not being stored or bound against observables, which are the special object that knockout provides specifically for this purpose. You can easily solve this with your code by adding a new Student type:
function Student(data) {
this.id = ko.observable(data.id);
this.name = ko.observable(data.name);
};
and use it to populate your students array with:
$.each(students, function (idx, student) {
if (window.console) console.log(student);
model.students.push(new Student(student));
});
With those properties now being observables, their changes will propagate to the UI. Here is the fiddle, with these two minor changes.
That being said, I think you have largely missed the point of Knockout. I strongly suggest you go through the Knockout tutorials, if you haven't done so already.
You're use of jQuery to create click functions for your viewmodel really goes against the model that Knockout encourages. Please take a look at this fiddle, which converts your code into 100% Knockout, using viewmodel functions, and drops all the jQuery.
I am using knockout js to acheive a task. My model is like:
var ServiceLevelRates = function(data, availableClasses) {
return {
TaxTypeID: ko.observable(data.Key),
TaxTypeName: ko.observable(data.Name),
ExtendedTaxTypeName: data.Name.replace(/\s+/g, ''),
ApplyAfter: ko.observable(-1),
TaxClasses: ko.observableArray(availableClasses)
};
};
var TaxClass = function(data, availableServices) {
return {
ServiceClassID: data.ServiceClassID,
ServiceClassName: ko.observable(data.ServiceClassName),
TaxServices: ko.observableArray(availableServices)
};
};
var TaxService = function(data) {
return {
ServiceID: ko.observable(data.ServiceID),
ServiceName: ko.observable(data.ServiceName),
ServiceRate: ko.observable(data.ServiceRate > 0 ? data.ServiceRate : "").extend({ numeric: 2 })
};
};
and my html is like:
<tbody data-bind="foreach: ServiceLevelRates">
<tr>
<td style="width:100%;">
<table width="100%">
<tr>
<td style="width:2%;">
<img src="../../Images/del_up.gif" onclick="HideMyChilds(this);" />
</td>
<td data-bind="text: TaxTypeName">
</td>
</tr>
<tr>
<td></td>
<td>
<table width="100%">
<tr>
<td style="width:20%;">
<label id="lblApplyAfter" myId="lblApplyAfter" runat="server">Apply After</label>
</td>
<td></td>
</tr>
<tr>
<td>
<select id="sltApplyAfter" SkinID="drpFields" name="sltApplyAfter" runat="server" myId="sltApplyAfter">
<option value="-1">Charge</option>
</select>
</td>
<td>
<input type="checkbox" />Apply for All Services<input type="text" onkeypress="ValidateDecimalValue(event,this)"; onblur="ApplyForAllServices(this);" data-bind="attr: { 'class': ExtendedTaxTypeName }" /> %
</td>
</tr>
<tr>
<td colspan="2">
<table width="100%">
<tbody data-bind="foreach: TaxClasses">
<tr>
<td style="width:2%;">
<img src="../../Images/del_up.gif" onclick="HideMyChilds(this);" />
</td>
<td style="width:100%;" class="tdRepeaterHeaderBG" data-bind="text: ServiceClassName">
</td>
</tr>
<tr>
<td></td>
<td>
<table width="100%">
<thead>
<tr>
<td style="width:1%;">
<td style="width:24%;" class="tdRepeaterHeaderBG">Service Name</td>
<td style="width:75%;" class="tdRepeaterHeaderBG">Amount</td>
</tr>
</thead>
<tbody data-bind="foreach: TaxServices">
<tr>
<td style="width:1%;">
<td style="width:24%;" data-bind="text: ServiceName"></td>
<td style="width:75%;">
<input type="text" data-bind="value: ServiceRate, attr: { 'class': $parents[1].ExtendedTaxTypeName, 'id': $parents[1].ExtendedTaxTypeName + ServiceID }" />%
</td>
</tr>
<tr>
<td></td>
<td colspan="2">
<div style="font-size: 11px; width:98%;height:5px; border-top: 1px dotted gray;"> </div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
The problem is when I provide ServiceRate for a taxservice in one class, it is updated into text field of same service in all other classes. Any help on it will be great.
Your code have several issues.
First, mostly a cosmetic one. You are using tables for layout. They should only be used when you truly need tabular data. Div's or lists are much better in most cases, and if you need to layout something, you could use css margins.
You are mixing, and mixing up, different object schemes.
One is to return an object literal:
function Foo() {
return {
Property: ko.observable(),
}
}
This schema could, but shouldn't, be called with the new operator.
The other one is prototype-based:
function Foo() {
var self = this;
self.Property = ko.observable();
}
This schema must be called with the new operator.
It is easiest to stick to one schema. With knockout, the latter is easier to use in some cases.
You are not using observables for all properties. It is a little confusing to be using observables for some properties, and not for others. You have to go back to the source-code to confirm for each property.
Your object model does not take into account object reuse. You are passing the same objects to each ServiceLevelRate, so when you are updating one TaxService, the same TaxService in all other TaxClass will also be updated.
One simple solution for this, is to factor out the fields that needs updating into mapping objects.
// This part is constructed once, based on server data.
function TaxService(data) {
var self = this;
self.ServiceID = ko.observable(data.ServiceID);
self.ServiceName = ko.observable(data.ServiceName);
}
// This part is constructed for each TaxClassMapping
function TaxServiceMapping(svc) {
var self = this;
self.TaxService = ko.observable(svc);
self.ServiceRate = ko.observable("");
}
Lastly; To conditionally update the rates based on the check-box, you can bind the it with the checked-binding. In the subscription for the ServiceLevelRate-wide rate, you just check if the check-box was checked, before proceeding to update the other fields.
self.ApplyForAll.subscribe(function (newValue) {
if (self.ApplyForAllCheckBox()) {
ko.utils.arrayForEach(self.Classes(), function (clsMapping) {
ko.utils.arrayForEach(clsMapping.ClassServices(), function (svcMapping) {
svcMapping.ServiceRate(newValue);
});
});
}
});
Here is an updated fiddle:
http://jsfiddle.net/MizardX/V8DTj/
I scaled down the models to the essential parts, to make them easier to work with.
To make the TaxServices show only for certain TaxClasses, you could filter which TaxService-objects you want to include for each TaxClass.
function TaxClassMapping(taxClass, availableServices) {
var self = this;
self.TaxClass = ko.observable(taxClass);
var classID = taxClass.ServiceClassID();
var filtered = ko.utils.arrayFilter(availableServices, function (svc) {
// svc.ServiceClassID is a new property in TaxService
return svc.ServiceClassID() === classID;
});
var mapped = ko.utils.arrayMap(filtered, function (svc) {
return new TaxServiceMapping(svc);
});
self.ClassServices = ko.observableArray(mapped);
}
I have 2 forms on a single razor view that open in seperate dialog boxes. The forms are submitted using jquery post..
First form works perfectly but the second form isnt recognised at all and when I try to serializr the data it returns an empty string.
Code below:
#using (Html.BeginForm("SaveElectricReading", "Store", FormMethod.Post, new { id = "frmSaveElectricReading" }))
{
<div id="electricDialog" style="display: none;" title="Electric Readings">
<p>
Please ensure the electric readings have been entered!
</p>
<table width="100%">
<tr>
<th>
Serial:
</th>
</tr>
<tr>
<td>
<input type="text" name="Serial" />
</td>
</tr>
<tr>
<th>
Reading:
</th>
</tr>
<tr>
<td>
<input type="text" name="Reading" />
</td>
</tr>
<tr>
<th>
Comment:
</th>
</tr>
<tr>
<td>
<div class="textwrapper">
<textarea rows="5" cols="10" name="Comment"></textarea>
</div>
</td>
</tr>
</table>
</div>
}
#using (Html.BeginForm("SaveWaterReading", "Store", FormMethod.Post, new { id = "myform" }))
{
<div id="waterDialog" style="display: none;" title="Water Readings">
<p>
Please ensure the water readings have been entered!
</p>
<table width="100%">
<tr>
<th>
Serial:
</th>
</tr>
<tr>
<td>
<input type="text" name="WaterSerial" />
</td>
</tr>
<tr>
<th>
Reading:
</th>
</tr>
<tr>
<td>
<input type="text" name="WaterReadingsdfsdf" />
</td>
</tr>
<tr>
<th>
Comment:
</th>
</tr>
<tr>
<td>
<div class="textwrapper">
<textarea rows="5" cols="10" name="WaterComment"></textarea>
</div>
</td>
</tr>
</table>
</div>
}
function showWaterDialog() {
var $dialog = $('#waterDialog').dialog({
autoOpen: false,
modal: true,
width: 400,
height: 400,
buttons: {
Submit: function () {
// $('#frmCreateStore').submit();
var data = $('#frmSaveWaterReading');
$.post("/Store/SaveWaterReading",
$("#frmSaveWaterReading").serialize(),
function () {
$("#waterDialog").dialog("close");
});
//$(this).dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
}
});
$dialog.dialog('open');
}
In the jquery function for showing the dialog with the form in it, I needed to add that dialog content to the second form on the page as follows:
$("#waterDialog").parent().appendTo($("form").eq(1)[0]);