KnockoutJS multiple data-bindings - value and javascript function - javascript

I want to have multiple data-bindings on my view so my text box contains the right value and when the value changes it calls a function. Basically I want to use amplify.js local storage every time a value on my form changes.
Agency view
<section class="view">
<header>
<button class="btn btn-info btn-force-refresh pull-right"
data-bind="click: refresh">
<i class="icon-refresh"></i>Refresh</button>
<button class="btn btn-info"
data-bind="click: save">
<i class="icon-save"></i>Save</button>
<h3 class="page-title" data-bind="text: title"></h3>
<div class="article-counter">
<address data-bind="text: agency().length"></address>
<address>found</address>
</div>
</header>
<table>
<thead>
<tr>
<th>Agency Name</th>
<th>Category</th>
<th>URL</th>
<th>Number of employees</th>
</tr>
</thead>
<tbody data-bind="foreach: agency">
<tr>
<td>
<!--<input data-bind="value: agencyName" /></td>-->
<input data-bind="value: agencyName, onchange: test()"/>
<td>
<input data-bind="value: category" /></td>
<td>
<input data-bind="value: Url" /></td>
<td>
<input data-bind="value:numberOfEmployees" /></td>
</tr>
<tr>
<td>Activities</td>
<td>Declared Billings</td>
<td>Campaigned Billings</td>
</tr>
<tr>
<td>
<input data-bind="value: activities" /></td>
<td>
<input data-bind="value: declaredBillings" /></td>
<td>
<input data-bind="value: campaignBillings" /></td>
</tr>
</tbody>
</table>
</section>
Agency ViewModel
define(['services/datacontext'], function (dataContext) {
//var myStoredValue = amplify.store("Agency"),
// myStoredValue2 = amplify.store("storeExample2"),
// myStoredValues = amplify.store();
var agency = ko.observableArray([]);
var initialized = false;
var save = function (agency) {
return dataContext.saveChanges(agency);
};
var vm = { // This is my view model, my functions are bound to it.
//These are wired up to my agency view
activate: activate,
agency: agency,
title: 'agency',
refresh: refresh, // call refresh function which calls get Agencies
save: save
};
return vm;
function activate() {
if (initialized) {
return;
}
initialized = true;
return refresh();
}
function refresh() {
return dataContext.getAgency(agency);
}
function test() {
alert("test");
}
});
Every time I type a new value, for example
<input data-bind="value: agencyName, onchange: test()"/>
I want to fire the function test. I then want to store the view model latest data into local storage.
Does anyone know how to do multiple bindings for this?

You should use this binding:
<input data-bind="value: agencyName, event: { change: $parent.onAgencyNameChanged}"/>
Note that I used $parent to refer to the vm Object.
And add an handler to your viewModel.
var vm = {
....
onAgencyNameChanged: function(agency){
// do stuff
}
};
return vm;
Another solution could be to subscribe on the agencyName of all agencies. But I think this isn't suited to this case. After creating the vm you could do this :
ko.utils.arrayForEach(vm.agency(), function(a){
a.agencyName.subscribe(function(){
// do stuff
});
});
I hope it helps.

Try to subscribe your object manually for each element that you have to bind.
Have a look at the explanation and the example in the knockout documentation here.

Related

MVC/Javascript: how to post an array of integers in a form submission?

Before doing a form submit for my MVC webpage, I want to see a list of key fields to the controller. However the parameter on the controller is null.
My JavaScript is as follows;
$("#savePropertiesToBeAssigned").click(function (e) {
e.preventDefault();
var $propertyIds = $("#propertyRows").find($(".selectProperty:checked"));
var propertyIds = [];
$.each($propertyIds, function () {
var propertyId = $(this).data("msurvey-property-id");
propertyIds.push(propertyId);
});
var $form = $(this).closest("form")[0];
$form.action += "?PropertyIds=" + propertyIds + "&page=" + GetHiddenField("msurvey-page");
$form.submit();
});
The MVC Action is;
[HttpPost]
public async Task<ActionResult> AddFromContract(int[] PropertyIds, int? page)
{
var pageNumber = page.GetValueOrDefault(1);
return RedirectToAction("AddFromContract", new { page = pageNumber });
}
The PropertyIds comes through wrongly as null, whilst the page has the correct value.
EDIT - I am displaying the View in response to a comment:
#{
ViewBag.Title = "Add properties from the contract into the survey";
}
#using (Html.BeginForm("AddFromContract", "PropertySurvey", FormMethod.Post))
{
<div id="hiddenFields"
data-msurvey-page="#ViewBag.Page"></div>
<input type="hidden" id="propertyIds" name="propertyIds" />
<fieldset>
<legend>#ViewBag.Title</legend>
#if (ViewBag.PagedList.Count > 0)
{
<section id="PropertyList" style="margin-right: 28px;">
<p>
The properties below have already been added to the contract <b>#SessionObjectsMSurvey.SelectedContract.ContractTitle</b>, but are NOT in this survey
<b>#SessionObjectsMSurvey.SelectedContract.SurveyTitle.</b>
</p>
<table class="GridTbl">
<thead>
<tr>
<th>UPRN</th>
<th>Block UPRN</th>
<th>Address</th>
<th style="text-align: center;">
Select All<br/>
<input type="checkbox" id="selectAll"/>
</th>
</tr>
</thead>
<tfoot>
<tr>
<td colspan="3" style="text-align: right;">Select the properties you want to include in the survey, and click on the Save button.</td>
<td style="text-align: center;">
<button id="savePropertiesToBeAssigned" class="btn-mulalley">
Save
</button>
</td>
</tr>
</tfoot>
<tbody id="propertyRows">
#foreach (var property in ViewBag.PagedList)
{
<tr>
<td>#property.UPRN</td>
<td>#property.BlockUPRN</td>
<td>#property.Address</td>
<td style="text-align: center;">
<input type="checkbox"
name="selectProperty"
class="selectProperty"
data-msurvey-property-id="#property.PropertyId"/>
</td>
</tr>
}
</tbody>
</table>
#Html.PagedListPager((IPagedList) ViewBag.PagedList, page => Url.Action("AddFromContract", new {page}))
</section>
}
else
{
<p>Either no properties have been entered, or all of them have been assigned to the survey.</p>
}
</fieldset>
}
#section scripts
{
#Scripts.Render("~/bundles/page/propertyTransfer")
}
There is no need for any javascript to solve this. You already have checkbox elements in you form, and if they have the correct name and value attributes, they will bind to your model in the POST method.
Delete the <input type="hidden" id="propertyIds" name="propertyIds" /> and change the the view to
#foreach (var property in ViewBag.PagedList)
{
<tr>
<td>#property.UPRN</td>
....
<td style="text-align: center;">
<input type="checkbox" name="propertyIds" value="#property.PropertyId" />
</td>
</tr>
}
and delete the script.
When the form submits, the value of all checked checkboxes will be sent to the controller, and because the checkbox has name="propertyIds" they will bind to your int[] PropertyIds in the POST method.
You will also need to change <div id="hiddenFields" data-msurvey-page="#ViewBag.Page"></div> to
<input type="hidden" name="page" value="#ViewBag.Page" />
Side note: I recommend you start using view models rather that using ViewBag, including a view model for the collection, which would contain properties int ID and bool IsSelected so that you can strongly type your view to your model.

Knockout Validation - Validation not binding to correct instance of object

I'm using knockout-validation and am having a problem with the error messages not showing correctly after changing what observable an input field is bound to.
I have the following html
<div id="editSection" data-bind="if: selectedItem">
<div>
<label>First Name</label>
<input data-bind="value:selectedItem().FirstName" />
</div>
<div>
<label>Last Name</label>
<input data-bind="value:selectedItem().LastName" />
</div>
</div>
<br/>
<table data-bind='if: gridItems().length > 0'>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
</tr>
</thead>
<tbody data-bind='foreach: gridItems'>
<tr>
<td data-bind='text: FirstName' ></td>
<td data-bind='text: LastName' ></td>
<td><a href='#' data-bind='click: $root.editItem'>Edit</a></td>
</tr>
</tbody>
</table>
And JavaScript
var lineViewModel = function(first, last) {
this.FirstName = ko.observable(first).extend({required:true});
this.LastName = ko.observable(last).extend({required: true});
this.errors = ko.validation.group(this);
}
var mainViewModel = function() {
this.gridItems = ko.observableArray();
this.gridItems([new lineViewModel('first1'), new lineViewModel(null,'last2')]);
this.selectedItem = ko.observable();
this.editItem = function(item){
if (this.selectedItem()) {
if (this.selectedItem().errors().length) {
alert('setting error messages')
this.selectedItem().errors.showAllMessages(true);
}
else{
this.selectedItem(item)
}
}
else
this.selectedItem(item)
}.bind(this)
}
ko.applyBindings(new mainViewModel());
Reproduce
Use this JSFiddle
Click Edit on the first line
Click Edit on the second line - You will be alerted of validation
message being shown and then it will show
Fill out required field
Click Edit on the second line again - You will see "First Name" go
blank and "Last Name" change to "last2"
Click Edit on the first line - You will be alerted of validation
message being shown, BUT it's not show (BUG)
Should I take another approach to this or should I do something different with the way that I am using ko.validation.group?
The validation is fine... your edit section has problems.
Use the with binding. Never use someObservable().someObservableProperty in a binding, it will not work like you might expect it. You should change the binding context.
<div id="editSection" data-bind="with: selectedItem">
<div>
<label>First Name</label>
<input data-bind="value: FirstName" />
</div>
<div>
<label>Last Name</label>
<input data-bind="value: LastName" />
</div>
</div>

Knockout: Unable to bind change in quantity to total cost in shopping cart

I am new to Knockout JS and I am working on a simple shopping cart. The user enters in the name, price, and quantity of an item.
That information outputs into the 'Items in Cart' area, an observable array, and the total price is displayed under that.
The user can change the quantity in the 'Items in Cart' area, but that change doesn't change the total cost.
How do I bind a change in quantity in the 'Items in Cart' area to the total cost?
Thank you in advance.
var viewModel = {
newItemName: ko.observable(),
newItemPrice: ko.observable(0),
newItemQuantity: ko.observable(1),
addNewItem: function() {
var newItem = {
name: this.newItemName(),
price: this.newItemPrice(),
quantity: this.newItemQuantity()
};
this.itemsInCart.push(newItem);
this.newItemName("");
this.newItemPrice(0);
this.newItemQuantity(1);
},
removeItem: function() {
viewModel.itemsInCart.remove(this);
},
itemsInCart: ko.observableArray([{
newItemQuantity()
}])
};
viewModel.addNewItemEnabled = ko.pureComputed(function()
{
var name = this.newItemName(),
price = this.newItemPrice(),
quantity = ko.observable(viewModel.newItemQuantity(1));
return name && name.length;
},
viewModel);
viewModel.getTotalCost = ko.computed(function()
{
var total = 0;
arr = viewModel.itemsInCart();
for (i = 0; i < arr.length; i++)
total += arr[i].price * arr[i].quantity;
return total;
},
viewModel);
ko.applyBindings(viewModel);
<h1>Shopping Cart</h1>
<hr />
<h3>Add New Item</h3>
<label>Name:</label>
<input type="text" data-bind="value: newItemName, valueUpdate: 'keyup'" />
<br />
<label>Unit Price:</label>
<input type="number" min="0" step="0.25" data-bind="value: newItemPrice, valueUpdate: 'keyup'" />
<br />
<label>Quantity:</label>
<input type="number" min="1" step="1" data-bind="value: newItemQuantity, valueUpdate: 'keyup'" />
<br />
<button data-bind="click: addNewItem, enable: addNewItemEnabled">Add Item</button>
<hr />
<h3>Items in Cart</h3>
<table>
<thead>
<tr>
<th>Name</th>
<th>Unit Price</th>
<th>Quantity</th>
</tr>
</thead>
<tbody data-bind="foreach: itemsInCart">
<tr>
<td data-bind="text: name"></td>
<td>$<span data-bind="text: price"></span>
</td>
<td>
<input type="number" data-bind="value: quantity, valueUpdate: 'keyup'" />
</td>
<td>
<button data-bind="click: $parent.removeItem">remove</button>
</td>
</tr>
</tbody>
</table>
<h3 data-bind="visible: getTotalCost() > 0 "> Your total will be $<span data-bind="text: getTotalCost"></span></h3>
point to remember: while dealing with ko if something is not getting updated means it is not a observable
Here in you case var newItem = { you are simply assigning rather you should assign the values to observable like below
var newItem = {
name: ko.observable(this.newItemName()),
price: ko.observable(this.newItemPrice()),
quantity: ko.observable(this.newItemQuantity())
};
Modified fiddle here
Just in case if you are looking for neat and clean one and most importantly a completeko one check this fiddle here
You might try looking at this part of the Knockout documentation for guidance: http://learn.knockoutjs.com/#/?tutorial=collections.
In particular, watch how they make the Total surcharge change when the Meal type changes on each row. It's essentially the same thing you're trying to do with your quantity for each item in your cart.
Also, I noticed that you set up your view model as a var. I usually make mine a function, like this:
function myViewModel(){
// cool view model code here
}
Then, when you're ready to apply bindings, you do this:
ko.applyBindings(new myViewModel());
The big thing I've noticed when working with Knockout is that one needs to pay very close attention to how parentheses are used with the observable arrays and other bindable model properties. For example, there's a big difference between
this.myObservableArray().push(item);
and
this.myObservableArray.push(item);
Good luck!

CRUD operations in angularjs with REST API

The CRUD operations with AngularJS are working fine. On click of submit button values get submitted in db, but its not getting reflected on view with at the same time.
Either I required separate button for showing values from database or I need to click twice.
My view:
<div style="float:left; margin-left:50px;">
<div><label for="id">Update Records</label></div><br>
<table>
<tr >
<td><label for="id">Id:</label> </td>
<td><input type="text" name="id" ng-model="user.id"/></td>
</tr>
<tr >
<td><br><label for="uname">Name:</label> </td>
<td><input type="text" name="uname" ng-model="user.uname"/></td>
</tr>
<tr>
<td><br><label for="ucity">City:</label> </td>
<td><input type="text" name="ucity" ng-model="user.ucity"/></td>
</tr>
<tr>
<td>
<a ng-click="updatecust(user.id,user.uname,user.ucity)" class="btn btn-primary">Update</a>
</td>
</tr>
<!--<tr>
<td><br>
<a ng-click="viewtable()" class="btn btn-primary">View All</a>
</td>
</tr> -->
</table>
</div>
My controller:
var phonecatControllers = angular.module('phonecatControllers', ['templateservicemod', 'navigationservice','restservice']);
$scope.updatecust=function(id,name,city){
console.log("update is clicked");
$scope.user={id:id,uname:name,ucity:city};
RestService.update(id,name,city).success( RestService.vieww().success(viewdata));
};
REST API:
var restservice = angular.module('restservice', [])
.factory('RestService', function ($http) {
return{
update: function(id,name,city){
console.log(id+name+city);
return $http.get("http://localhost/code/index.php/demo/updatedemo?id="+id+"& uname="+name+"&ucity="+city,{});
}
}
});
RestService.update(id,name,city).success( RestService.vieww().success(viewdata));
this should be changed to
RestService.update(id,name,city).success(function(){
RestService.vieww().success(function(data){
$scope.viewdata = data.data;
}));
});
by considering that, you have RestService function vieww to get the data array.
and in your HTML, you need to do ngRepeat for viewdata to display the table.

KO ReferenceError: Unable to process binding

Uncaught ReferenceError: Unable to process binding "foreach: function (){return Educations }"
Uncaught ReferenceError: Unable to process binding "foreach: function (){return WorkExperience }"
I couldn't figure out why the binding is failing.
i have the following two tables one for Education and other for Work Experience, They give the error when i'm trying to bind the both table in one view, If i remove the binding (JS + HTML code) it works fine
HTML:
<div id=divtable1 class="widget widget-simple widget-table">
<table id="Table1" class="table table-striped table-content table-condensed boo-table table-hover">
<thead>
<tr id="Tr1" class="filter">
<th>University<span class="required">*</span></th>
<th>Location <span class="required">*</span></th>
<th></th>
</tr>
</thead>
<tbody data-bind='foreach: Educations'>
<tr>
<td><input type="text" class='span11 required' data-bind="value: SchoolName" /></td>
<td><input type="text" class='span11 required' data-bind="value: Location" /></td>
<td><a href='#' data-bind='click: $root.removeEducation'>Delete</a></td>
</tr>
</tbody>
</table>
<button data-bind='click: $root.addEducation' class="btn btn-blue">Add Education</button>
</div>
<div id="divtable2">
<table id="Table2">
<thead>
<tr id="Tr2" class="filter">
<th>Employer Name<span class="required">*</span></th>
<th>EmployerAddress <span class="required">*</span></th>
<th></th>
</tr>
</thead>
<tbody data-bind='foreach: WorkExperience'>
<tr>
<td><input type="text" class='span11 required' data-bind="value: EmployerName" /></td>
<td><input type="text" class='span11 required' data-bind="value: EmployerAddress" /></td>
<td><a href='#' data-bind='click: $root.removeWorkExperience'>Delete</a></td>
</tr>
</tbody>
</table>
<button data-bind='click: $root.addWorkExperience' class="btn btn-blue">Add Work Experience</button>
</div>
Java Script:
<script type="text/javascript">
var Educations = function (educations) {
var self = this;
self.Educations = ko.mapping.fromJS(educations);
self.addEducation = function () {
self.Educations.push({"SchoolName": ko.observable(""), "Location": ko.observable("")});
};
self.removeEducation = function (education) {
self.Educations.remove(education);
};
};
var viewModel = new Educations(#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.Educations)));
ko.applyBindings(viewModel);
</script>
<script type="text/javascript">
var WorkExperience = function (workexperiences) {
var self = this;
self.WorkExperience = ko.mapping.fromJS(workexperiences);
self.addWorkExperience = function () {
self.WorkExperience.push({ "EmployerName": ko.observable(""), "EmployerAddress": ko.observable("")});
};
self.removeWorkExperience = function (workexperience) {
self.WorkExperience.remove(workexperience);
};
};
var viewModel = new WorkExperience(#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.WorkExperience)));
ko.applyBindings(viewModel);
</script>
Also I tried to bind Table1 but it didn't work
ko.applyBindings(viewModel, $('#Table1')[0]);
try adding this <pre data-bind="text: ko.toJSON($data, null, 2)"></pre> to your view. it will output the data that knockout contains in the current context.
Also you have one view and two view models that are trying to bind to it. create one viewmodel with both Educations and WorkExperience as properties.
something like
var vm = {
Educations : educationViewModel,
WorkExperience: workExperienceViewModel
}
ko.applyBindings(vm);
If you want to bind two view-models separately, you must define which section of your view to bind to. You do this by providing the element to ko.applyBindings as the second parameter.
ko.applyBindings(viewModel, document.getElementById("divtable1"));

Categories