I'm using Twitter Bootstrap together with knockout.js.
I have an orders page where the cashier can choose a customer and products that the customer wants to buy. However, I get some very strange behavior. When I add one product to the cart the correct function addToCart is called, but also the function removeFromCart is called without me telling the program to call it. I guess something is happening because I use Bootstrap modals.
Please help. Here is the fiddle http://jsfiddle.net/rY59d/4/ .
HTML Code:
<div id="create-order-main">
<h2>Create new order</h2>
<a class="btn btn-primary" data-toggle="modal" data-target="#select-products2"><b>+</b> Add products</a>
<div>
<div id="create-order-select-products" data-bind="with: productVM">
<div class="modal fade" id="select-products2" tabindex="-1" role="dialog" aria-labelledby="selectProducts2Label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
×
</button>
<h4 class="modal-title">Add products</h4>
</div>
<div class="modal-body">
<table class="table table-bordered table-with-records" data-bind="triggerUpdate: Products, visible: Products().length > 0">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Price</th>
<th>Quantity</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: filteredProducts2">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: name"></td>
<td data-bind="text: price"></td>
<td><input type="number" min="0" step="1" value="1"></td>
<td data-bind="attr: { value: $index }, click: $parent.selectedProduct2"><a class="btn btn-primary" data-bind="click: $parent.addToCart">Add to cart</a></td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel
</button>
<button type="submit" class="btn btn-primary" data-dismiss="modal">
Choose
</button>
</form>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
</div>
<div data-bind="with: productVM">
<table class="table table-bordered table-with-records" data-bind="triggerUpdate: cart, visible: cart().length > 0">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Price</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: cart">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: name"></td>
<td data-bind="text: price"></td>
<td data-bind="attr: { value: $index }, click: $parent.selectedProductInOrder"><a class="btn btn-primary" data-bind="click: $parent.removeFromCart($data)">Remove</a></td>
</tr>
</tbody>
</table>
</div>
</div>
JavaScript Code:
/**
* -----------
* Viewmodels.js
* -----------
*
* Contains Knockout.js Viewmodels
*
*/
// CustomerViewModel starts here
var CustomerViewModel = function () {
var self = this;
var stringStartsWith = function (string, startsWith) {
string = string || "";
if (startsWith.length > string.length)
return false;
return string.substring(0, startsWith.length) === startsWith;
};
self.name = ko.observable("");
self.surname = ko.observable("");
self.email = ko.observable("");
self.query = ko.observable();
self.Customers = ko.observableArray();
self.filterId = ko.observable("");
self.filterName = ko.observable("");
self.filterSurname = ko.observable("");
// Used for search in "Create Order" view
self.filterId2 = ko.observable("");
self.filterName2 = ko.observable("");
self.filterSurname2 = ko.observable("");
function Customer(id, name, surname, email) {
this.id = id;
this.name = name;
this.surname = surname;
this.email = email;
}
self.selectedCustomer = ko.observable(null);
// Used for search in "Create Order" view
self.selectedCustomer2 = ko.observable(null);
self.getId = function () {
var idCounter;
if (self.Customers().length === 0) {
idCounter = 0;
} else {
idCounter = self.Customers()[self.Customers().length - 1]['id'];
}
return (++idCounter);
};
$.getJSON("api/customers", function (data) {
self.Customers(data);
});
self.Customers.push(new Customer(1,"John","Smith","john#smith.com"));
self.Customers.push(new Customer(2,"Maria","Jones","maria#jones.com"));
self.Customers.push(new Customer(3,"Alexander","Stevenson","alexander#stevenson.com"));
self.clearSearchCustomers = function () {
self.filterId("");
self.filterName("");
self.filterSurname("");
};
// Used in the "Create new Order" view
self.clearSearchCustomers2 = function () {
self.filterId2("");
self.filterName2("");
self.filterSurname2("");
self.selectedCustomer2("");
};
self.selectCustomer = function () {
self.selectedCustomer(this);
};
self.chooseCustomerInSearch = function () {
$('#select-customer2').modal('toggle');
};
self.createNewCustomer = function () {
var customer = new Customer(self.getId(), self.name(), self.surname(), self.email());
$.ajax({
type: "POST",
url: 'api/customers',
data: ko.toJSON({
data: customer
}),
success: function (result) {
self.Customers.push(customer);
self.name("");
self.surname("");
self.email("");
},
error: function (err) {
alert(err.status + " - " + err.statusText);
}
});
$('#create-customer').modal('toggle');
};
self.deleteItem = function ($this) {
$.ajax({
type: "DELETE",
url: 'api/customers/' + this.id,
success: function (result) {
self.Customers.remove($this);
$('#delete-customer').modal('toggle');
},
error: function (err) {
alert(err.status + " - " + err.statusText);
}
});
};
self.callEditCustomerFromViewCustomer = function () {
$('#display-customer').modal('toggle');
$('#edit-customer').modal('toggle');
};
self.editCustomer = function ($this) {
var customer = self.selectedCustomer();
$.ajax({
type: "PUT",
url: 'api/customers/' + this.id,
contentType: 'application/json',
data: ko.toJSON({
data: customer
}),
success: function (result) {
self.Customers.remove($this);
self.Customers.push($this);
$('#edit-customer').modal('toggle');
},
error: function (err) {
alert(err.status + " - " + err.statusText);
}
});
};
self.filteredCustomer = ko.computed(function () {
var filterTextId = self.filterId().toLowerCase(),
filterTextName = self.filterName().toLowerCase(),
filterTextSurname = self.filterSurname().toLowerCase();
if (!filterTextId && !filterTextName && !filterTextSurname) {
return self.Customers();
} else {
if (self.Customers() != 'undefined' && self.Customers() !== null && self.Customers().length > 0) {
return ko.utils.arrayFilter(self.Customers(), function (item) {
return (stringStartsWith(item.id.toLowerCase(), filterTextId) && stringStartsWith(item.name.toLowerCase(), filterTextName) && stringStartsWith(item.surname.toLowerCase(), filterTextSurname));
});
}
}
});
// Used for the "Create New Order" view
self.filteredCustomer2 = ko.computed(function () {
var filterTextId2 = self.filterId2().toLowerCase();
var filterTextName2 = self.filterName2().toLowerCase();
var filterTextSurname2 = self.filterSurname2().toLowerCase();
if (!filterTextId2 && !filterTextName2 && !filterTextSurname2) {
return self.Customers();
} else {
if (self.Customers() != 'undefined' && self.Customers() !== null && self.Customers().length > 0) {
return ko.utils.arrayFilter(self.Customers(), function (item) {
return (stringStartsWith(item.id.toLowerCase(), filterTextId2) && stringStartsWith(item.name.toLowerCase(), filterTextName2) && stringStartsWith(item.surname.toLowerCase(), filterTextSurname2));
});
}
}
});
};
// Product View Model starts here
var ProductViewModel = function () {
var stringStartsWith = function (string, startsWith) {
string = string || "";
if (startsWith.length > string.length)
return false;
return string.substring(0, startsWith.length) === startsWith;
};
function Product(id, name, price) {
this.id = id;
this.name = name;
this.price = price;
}
var self = this;
self.name = ko.observable("");
self.price = ko.observable("");
self.filterId = ko.observable("");
self.filterName = ko.observable("");
self.filterPrice = ko.observable("");
self.selectedProduct = ko.observable(null);
self.Products = ko.observableArray();
// used for "create new order - add items" view
self.filterProductId2 = ko.observable("");
self.filterProductName2 = ko.observable("");
self.filterProductPrice2 = ko.observable("");
self.selectedProduct2 = ko.observable(null);
self.selectedProductInOrder = ko.observable("");
self.cart = ko.observableArray("");
self.addToCart = function () {
alert("Item added to cart");
self.cart.push(this);
};
self.removeFromCart = function ($this) {
alert("this is a test");
// self.cart.remove($this);
};
self.getId = function () {
var idCounter;
if (self.Products().length === 0) {
idCounter = 0;
} else {
idCounter = self.Products()[self.Products().length - 1]['id'];
}
return (++idCounter);
};
self.clearSearchProducts = function () {
self.filterId("");
self.filterName("");
self.filterPrice("");
};
self.clearSearchProducts2 = function () {
self.filterProductId2("");
self.filterProductName2("");
self.filterProductPrice2("");
};
$.getJSON("api/products", function (data) {
self.Products(data);
});
self.Products.push(new Product(1,"product 1", "300"));
self.Products.push(new Product(2,"product 2", "400"));
self.Products.push(new Product(3,"product 3", "500"));
self.Products.push(new Product(4,"product 4", "600"));
self.createNewProduct = function () {
var product = new Product(self.getId(), self.name(), self.price());
$.ajax({
type: "POST",
url: 'api/products',
data: ko.toJSON({
data: product
}),
success: function (result) {
self.Products.push(product);
self.name("");
self.price("");
},
error: function (err) {
alert(err.status + " - " + err.statusText);
}
});
$('#create-product').modal('toggle');
};
self.deleteItem = function ($this) {
$.ajax({
type: "DELETE",
url: 'api/products/' + this.id,
success: function (result) {
self.Products.remove($this);
$('#delete-product').modal('toggle');
},
error: function (err) {
alert(err.status + " - " + err.statusText);
}
});
};
self.callEditProductFromViewProduct = function () {
$('#display-product').modal('toggle');
$('#edit-product').modal('toggle');
};
self.editProduct = function ($this) {
var product = self.selectedProduct();
$.ajax({
type: "PUT",
url: 'api/products/' + this.id,
contentType: 'application/json',
data: ko.toJSON({
data: product
}),
success: function (result) {
self.Products.remove($this);
self.Products.push($this);
$('#edit-product').modal('toggle');
},
error: function (err) {
alert(err.status + " - " + err.statusText);
}
});
};
self.filteredProducts = ko.computed(function () {
var filterTextId = self.filterId().toLowerCase(),
filterTextName = self.filterName().toLowerCase(),
filterTextPrice = self.filterPrice().toLowerCase();
if (!filterTextId && !filterTextName && !filterTextPrice) {
return self.Products();
} else {
if (self.Products() !== 'undefined' && self.Products() !== null && self.Products().length > 0) {
return ko.utils.arrayFilter(self.Products(), function (item) {
return (stringStartsWith(item.id.toLowerCase(), filterTextId) && stringStartsWith(item.name.toLowerCase(), filterTextName) && stringStartsWith(item.price.toLowerCase(), filterTextPrice));
});
}
}
});
// used for "create new order - add item" view
self.filteredProducts2 = ko.computed(function () {
var filterProductTextId2 = self.filterProductId2().toLowerCase(),
filterProductTextName2 = self.filterProductName2().toLowerCase(),
filterProductTextPrice2 = self.filterProductPrice2().toLowerCase();
if (!filterProductTextId2 && !filterProductTextName2 && !filterProductTextPrice2) {
return self.Products();
} else {
if (self.Products() !== 'undefined' && self.Products() !== null && self.Products().length > 0) {
return ko.utils.arrayFilter(self.Products(), function (item) {
return (stringStartsWith(item.id.toLowerCase(), filterProductTextId2) && stringStartsWith(item.name.toLowerCase(), filterProductTextName2) && stringStartsWith(item.price.toLowerCase(), filterProductTextPrice2));
});
}
}
});
};
// CustomerOrderViewModel starts here
var CustomerOrderViewModel = function () {
function CustomerOrder(id, date, customer, details) {
this.id = id;
this.date = name;
this.customer = customer;
this.details = details;
}
var self = this;
self.id = ko.observable("");
self.date = ko.observable();
self.customer = ko.observable("");
self.details = ko.observable("");
self.selectedOrder = ko.observable(null);
self.CustomerOrders = ko.observableArray("");
var newOrder = {
id: 1,
date: "10/10/20",
customer: "ThecUstomeRhere",
details: "sajdasdj"
};
self.createOrder = function () {
alert("Order is created!")
};
self.CustomerOrders.push(newOrder);
self.callEditOrderFromViewOrder = function () {
$('#display-order').modal('toggle');
$('#edit-order').modal('toggle');
};
self.deleteItem = function ($this) {
$.ajax({
type: "DELETE",
url: 'api/orders/' + this.id,
success: function (result) {
self.CustomerOrders.remove($this);
$('#delete-order').modal('toggle');
},
error: function (err) {
$('#delete-order').modal('toggle');
alert(err.status + " - " + err.statusText);
}
});
};
self.editOrderItem = function ($this) {
var selectedCustomerOrder = self.selectedOrder();
$.ajax({
type: "PUT",
url: 'api/orders/' + this.id,
contentType: 'application/json',
data: ko.toJSON({
data: selectedCustomerOrder
}),
success: function (result) {
self.CustomerOrders.remove($this);
self.CustomerOrders.push($this);
$('#edit-order').modal('toggle');
},
error: function (err) {
alert(err.status + " - " + err.statusText);
}
});
};
};
var masterVM = {
customerVM: new CustomerViewModel(),
productVM: new ProductViewModel(),
customerOrderVM: new CustomerOrderViewModel()
};
ko.applyBindings(masterVM);
Your binding for the removeFromCart is wrong. It calls the function when you bind, which happens when the cart observable array changes as it is in a foreach binding.
Replace click: $parent.removeFromCart($data)
With click: $parent.removeFromCart
Demo
Related
Using Knockout and Semantic UI.
I'm trying to figure out how to get the values selected for my multi select dropdown. The first dropdown works with just single values, but the multi select one dosent. I have an observable array inside another collection:
<tbody id="tbodyelement" data-bind="foreach: groupUserCollection">
<tr>
<td>
<div class="ui selection dropdown fluid">
<input type="hidden" name="groupDD" data-bind="value: group.name">
<i class="dropdown icon"></i>
<div class="default text">Select Group</div>
<div class="menu" data-bind="foreach: $parent.groupCollection">
<div class="item" data-bind="text: $data.name(), attr: {'data-value': $data.id()}"></div>
</div>
</div>
</td>
<td>
<div class="ui multiple selection dropdown long-width" id="multi-select">
<div data-bind="foreach: user">
<input type="hidden" name="userDD" data-bind="value: firstLastName">
</div>
<div class="default text">Select User</div>
<div class="menu" data-bind="foreach: $parent.userCollection">
<div class="item" data-bind="text: $data.firstLastName(), attr: {'data-value': $data.id()}"></div>
</div>
<i class="dropdown icon"></i>
</div>
</td>
</tr>
</tbody>
I have one model groupuser that has a group model in it and a collection of roles.
var groupUser = function (data) {
var self = this;
self.group = ko.mapping.fromJS(data.group),
self.user = ko.observableArray([]),
self.id = ko.observable(data.id),
self.group.subscribe = function () {
showButtons();
},
self.user.subscribe = function () {
// self.user.push(data.user);
showButtons();
}
};
var group = function (data) {
var self = this;
self.id = ko.observable(data.id),
self.name = ko.observable(data.name),
self.project = ko.observable(data.project),
self.projectId = ko.observable(data.projectId),
self.role = ko.observable(data.role),
self.roleId = ko.observable(data.roleId)
};
var user = function (data) {
var self = this;
self.id = ko.observable(data.id),
self.accountId = ko.observable(data.accountId),
self.email = ko.observable(data.email),
self.firstName = ko.observable(data.firstName),
self.lastName = ko.observable(data.lastName),
self.firstLastName = ko.pureComputed({
read: function()
{
return self.firstName() + " " + self.lastName();
}
,
write: function(value)
{
var lastSpacePos = value.lastIndexOf(" ");
if (lastSpacePos > 0) {
self.firstName(value.substring(0, lastSpacePos));
self.lastName(value.substring(lastSpacePos + 1));
}
console.log("firstname: " + self.firstName());
}
}),
};
groupViewModel = {
groupUserCollection: ko.observableArray(),
userCollection: ko.observableArray(),
groupCollection: ko.observableArray()
}
I add the data using this function:
$(data).each(function (index, element) {
var newGroup = new group({
id: element.group.id,
name: element.group.name,
project: element.group.project,
projectId: element.group.projectId,
role: element.group.role,
roleId: element.group.roleId
});
newGroup.id.subscribe(
function () {
newGroupUser.showButtons();
}
);
newGroup.name.subscribe(
function () {
newGroupUser.showButtons();
}
);
var newGroupUser = new groupUser({
group: newGroup,
id: element.id,
});
ko.utils.arrayForEach(element.user, function (data) {
var newUser = new user({
id: data.id,
accountId: data.accountId,
email: data.email,
firstName: data.firstName,
lastName: data.lastName,
});
newUser.id.subscribe(
function () {
newGroupUser.showButtons();
}
);
newUser.firstName.subscribe(
function () {
newGroupUser.showButtons();
}
);
newUser.lastName.subscribe(
function () {
newGroupUser.showButtons();
}
);
newGroupUser.user.push(newUser);
});
groupViewModel.groupUserCollection.push(newGroupUser);
});
I ended up adding in a custom bind to the data-bind on the hidden input and it worked. But now my subscription dosent work when I add values or remove them.
Code that worked:
ko.bindingHandlers.customMultiBind = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
ko.utils.arrayForEach(bindingContext.$data.user(), function (data) {
if (element.value === "")
{
element.value = ko.utils.unwrapObservable(data.id)
}
else {
element.value = element.value + "," + ko.utils.unwrapObservable(data.id)
}
});
}
};
I have a drop down that's dynamically created, when it first get's created it has 9 objects with different properties. When you click on an option it goes out and grabs more info and in this case gets 3 new objects. But when it updates it goes from this:
Initial drop down Selection
to This:
Updated List after click
The objects are updated and when console logged they both look good. However, it appears to be updating by how many items are in the array
view:
#if (Model.SubMenu.Count > 8)
{
<li style="display:inline;" id="MenuSearch">
<input id="MenuSearchInput" class="form-control" type="text" style="margin:9px; width:90%" placeholder="Search" data-bind="value: query, valueUpdate: 'keyup'">
</li>
<li class="divider"></li>
<li id="MenuSearchItem" role="menuitem" data-bind="foreach: FilteredItems">
<a data-bind="text: name, click: Inventory.GetDomainFacilities"></a>
</li>
}
ViewModel:
(function (i) {
"use strict";
var vm = i.ViewModels || (i.ViewModels = {});
vm.DomainSwitchViewModel = function (menuItem) {
var self = this;
var data = menuItem;
self.query = ko.observable('');
self.AllMenuItems = ko.observableArray([]);
self.Update = function (data) {
var mappedItems = _.map(data, function (item) {
if (item.FacilityID) {
return {
id: item.FacilityID,
name: item.FacilityName,
type: 'Facility',
href: '/Home/SwitchDomain?' + 'domainID=' + item.DomainID + 'facilityID=' + item.FacilityID
}
}
else {
return {
id: item.DomainID,
name: item.Name,
type: 'Domain',
href: ''
}
}
})
self.AllMenuItems(mappedItems);
};
self.FilteredItems = ko.computed(function () {
var allItems = self.AllMenuItems();
var search = self.query().toLowerCase();
return ko.utils.arrayFilter(allItems, function (item) {
return item.name.toLowerCase().indexOf(search) >= 0;
});
});
self.Update(menuItem);
}
window.Inventory = i;
})(window.Inventory || {});
GetDomainFacilities:
i.GetDomainFacilities = function (menuItem) {
$.ajax({
type: 'GET',
url: '/InventoryBase/GetDomainFacilities',
data: { domainID: menuItem.id },
contentType: 'application/json',
dataType: 'json',
success: function (data) {
var updatedMenuItem = JSON.parse(data);
vm.Update(updatedMenuItem);
ko.cleanNode(document.getElementById("DropDownNavMenus"))
ko.applyBindings(vm, document.getElementById("DropDownNavMenus"));
}
});
}
I have problem loading children to my option list.
I create an order but I cannot Edit. It will not load the dropdownlists.
My goal is when I press Edit on an order.
It will have selected the current product in the dropdownlist on the orderitem.
And I want to put the price from Product Model to the view(when choosing an item from dropdownlist)
Is there any other way to populate the dropdownlist?
I will upload pictures.
Here is my code javascript code.
var Products = [];
//fetch categories from database
function LoadProducts(element) {
if (Products.length == 0) {
//ajax function for fetch data
$.ajax({
type: "GET",
url: '/Sales/GetProducts',
success: function (data) {
Products = data;
//render catagory
renderProducts(element);
}
})
}
else {
alert("else");
//render catagory to the element
renderProducts(element);
}
}
function renderProducts(element) {
var $ele = $(element);
$ele.empty();
$ele.append($('<option/>').val('0').text('Select'));
$.each(Products, function (i, val) {
$ele.append($('<option/>').val(val.ProductId).text(val.ProductName));
})
}
var Seats = [];
//fetch categories from database
function LoadSeats(element) {
if (Seats.length == 0) {
//ajax function for fetch data
$.ajax({
type: "GET",
url: '/Sales/GetSeats',
success: function (data) {
Seats = data;
//render catagory
renderSeats(element);
}
})
}
else {
//render catagory to the element
renderSeats(element);
}
}
function renderSeats(element) {
var $ele = $(element);
$ele.empty();
$ele.append($('<option/>').val('0').text('Select'));
$.each(Seats, function (i, val) {
$ele.append($('<option/>').val(val.SeatId).text(val.SeatPlace));
})
}
var Employees = [];
//fetch categories from database
function LoadEmployees(element) {
if (Employees.length == 0) {
//ajax function for fetch data
$.ajax({
type: "GET",
url: '/Sales/GetEmployees',
success: function (data) {
Employees = data;
//render catagory
renderEmployees(element);
}
})
}
else {
//render catagory to the element
renderEmployees(element);
}
}
function renderEmployees(element) {
var $ele = $(element);
$ele.empty();
$ele.append($('<option/>').val('0').text('Select'));
$.each(Employees, function (i, val) {
$ele.append($('<option/>').val(val.EmployeeId).text(val.EmployeeName));
})
}
var PaymentMethods = [];
//fetch categories from database
function LoadPaymentMethods(element) {
if (PaymentMethods.length == 0) {
//ajax function for fetch data
$.ajax({
type: "GET",
url: '/Sales/GetPaymentMethods',
success: function (data) {
PaymentMethods = data;
//render catagory
renderPaymentMethods(element);
}
})
}
else {
//render catagory to the element
renderPaymentMethods(element);
}
}
function renderPaymentMethods(element) {
var $ele = $(element);
$ele.empty();
$ele.append($('<option/>').val('0').text('Select'));
$.each
(PaymentMethods, function (i, val) {
$ele.append
($('<option/>')
.val(val.PaymentMethodId)
.text(val.PaymentMethodType));
})
}
var ObjectState = {
Unchanged: 0,
Added: 1,
Modified: 2,
Deleted: 3
};
LoadProducts($('#productCategory'));
var salesOrderItemMapping = {
'SalesOrderItems': {
key: function (salesOrderItem) {
// alert("Salesorderitem mapping key");
return ko.utils.unwrapObservable(salesOrderItem.SalesOrderItemId);
},
create: function (options) {
console.log(options);
return new SalesOrderItemViewModel(options.data);
}
}
};
//var productItemMapping = {
// 'Products': {
// key: function(product) {
// return ko.utils.unwrapObservable(product.ProductId);
// },
// create: function(options) {
// return new SalesOrderViewModel(options.data);
// }
// }
//};
// ko.mapping.fromJS(data, productItemMapping, SalesOrderViewModel);
SalesOrderItemViewModel = function (data) {
//alert("salesorder item view"); // funkade
var self = this;
ko.mapping.fromJS(data, salesOrderItemMapping, self);
//dd: ko.observableArray(Products);
self.itemss = ko.observableArray(Products);
self.selectedItem = ko.observable(Products.ProductId);
//self.product1 = ko.observableArray(Products());
//self.dd = ko.observableArray(function() {
// //data.ProductId = data.Products.ProductId;
// return self.Products();
//});
self.flagSalesOrderAsEdited = function() {
if (self.ObjectState() !== ObjectState.Added) {
self.ObjectState(ObjectState.Modified);
}
// alert("salesorder item view if");
return true;
};
};
SalesOrderViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, salesOrderItemMapping, self);
//alert("salesorder view model"); // funkade
self.save = function () {
$.ajax({
url: "/Sales/Save/",
type: "POST",
data: ko.toJSON(self),
contentType: "application/json",
success: function (data) {
if (data.salesOrderViewModel !== null)
ko.mapping.fromJS(data.salesOrderViewModel, {}, self);
if (data.newLocation !== null)
window.location = data.newLocation;
}
});
},
self.flagSalesOrderAsEdited = function () {
if (self.ObjectState() !== ObjectState.Added) {
self.ObjectState(ObjectState.Modified);
}
return true;
},
self.deleteSalesOrderItem = function(salesOrderItem) {
self.SalesOrderItems.remove(this);
if (salesOrderItem.SalesOrderItemId() > 0 &&
self.SalesOrderItemsToDelete.indexOf
(salesOrderItem.SalesOrderItemId()) === -1)
self.SalesOrderItemToDelete.push(SalesOrderItemId());
}
self.addSalesOrderItem = function () {
// alert(" add salesorder item"); // funkade
var salesOrderItem = new SalesOrderItemViewModel(
{ SalesOrderItemId: 0, ProductId: 1, Quantity: 1,
ObjectState: ObjectState.Added });
self.SalesOrderItems.push(salesOrderItem);
};
};
//UnitPrice: 1
LoadSeats($('#SeatId'));
LoadEmployees($('#EmployeeId'));
LoadPaymentMethods($('#PaymentMethodId'));
Here is my Edit Partial view.
<table class="table table-striped">
<tr>
<th>Product Name</th>
<th>Quantity</th>
#*<th>Unit Price</th>*#
<th><button class="btn btn-info btn-xs"
data-bind="click: addSalesOrderItem">Add</button></th>
</tr>
<tbody data-bind="foreach: SalesOrderItems">
<tr>
<td>
#*<select id="productCategory" class="pc form-control"
data-bind="value: ProductId">
<option>Select</option>
</select>*#
#*<select data-bind="options: $parent.product1,
optionsText: 'ProductName', optionsValue: 'ProductId',
value: ProductId"></select>*#
<select data-bind=
"options: itemss, optionsText: 'ProductName',
value: ProductId, optionsValue: 'ProductId',
selectedOption: selectedOption"> </select>
</td>
#*<td class="form-group">
<input class="form-control input-sm" data-bind="value: ProductId" /></td>*#
<td class="form-group">
<input class="form-control input-sm" data-bind="value: Quantity"/></td>
#*<td class="form-group">
<input class="form-control input-sm" data- bind="text: UnitPrice"/></td>*#
<td class="form-group">Delete</td>
</tr>
</tbody>
</table>
Here it is when I create
Here it is when I Edit an order
And I get this problem when I save
I have problem loading children to my option list.
I create an order but I cannot Edit. It will not load the dropdownlists.
My goal is when I press Edit on an order.
It will have selected the current product in the dropdownlist on the orderitem.
And I want to put the price from Product Model to the view(when choosing an item from dropdownlist)
I will upload pictures.
I would like a road to follow. I am new to knockout/mvc and I cant find examples on mapping. I would appriatiate any feedpack or steps I can use.
If you need more from me, just write.
Thank you!!!
This is my Create View btw(how it is linked to knockout)
#model TheSolution.Domain.viewModels.SalesOrderViewModel
#using System.Web.Script.Serialization
#{
ViewBag.Title = "Create Sales Order";
}
#{
string data = new JavaScriptSerializer().Serialize(Model);
}
#section scripts
{
<script src="~/Scripts/knockout-3.4.0.js"></script>
<script src="~/Scripts/knockout.mapping-latest.js"></script>
<script src="~/Scripts/salesorderviewmodel.js"></script>
<script type="text/javascript">
var salesOrderViewModel = new SalesOrderViewModel(#Html.Raw(data));
ko.applyBindings(salesOrderViewModel);
</script>
}
#Html.Partial("_EditSalesOrder");
Since Ajax is loading asyc. It didnt had the time to load.
To fix the Edit problem with loading my Dropdown list. I used an ajaxStop in the Views(Create and Edit) It waits until ajax have loaded before GET the view
Here is the code
$(document).ajaxStop(function (event, request, settings) {
var salesOrderViewModel = new SalesOrderViewModel(#Html.Raw(data));
ko.applyBindings(salesOrderViewModel);
});
And with the price I had to do an ko ArrayFirst to match the ProductId observable with the ProductId in the Products Array(the one I loaded with Ajax)
And it would return the UnitPrice value from that row in the model.
This is how it looks like.
self.findItem = function () {
console.log(self.itemss().length);
var thePrice = ko.utils.arrayFirst(self.itemss(), function (item) {
return item.ProductId === self.ProductId();
}).UnitPrice;
console.log(thePrice);
return thePrice * self.Quantity();
}
please help me with this issue that I have.
Embedded in CRM on Demand I have a view that needs to take values from CRM input fields to perform a search against CRM through web service and to show a view if duplicate records are found.
Here I have the code into some libraries against CRM {crm.context.ts}:
/*
* Context Helpers
*/
declare var epmcrm: any;
class context {
private getParameterByName(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
config: { objects: { [key: string]: any } } = {
objects: {
"Homepage": {
name: "Homepage"
},
"Task": {
name: "Task",
idParam: "TaskDetailForm.Id",
screens: {
"/OnDemand/user/TaskDetail": "Detail",
"/OnDemand/user/TaskEdit": "Edit"
}
},
"Account": {
name: "Account",
idParam: "AccountDetailForm.Id",
screens: {
"/OnDemand/user/TaskDetail": "Detail",
"/OnDemand/user/TaskEdit": "Edit"
}
},
"User": {
name: "User",
idParam: "UserDetailForm.Id",
screens: {
"/OnDemand/user/UserDetail": "Detail",
"/OnDemand/user/UserEdit": "Edit"
}
},
"Opportunity": {
name: "Opportunity",
idParam: "OpportunityDetailForm.Id",
screens: {
"/OnDemand/user/OpportunityDetail": "Detail",
"/OnDemand/user/OpportunityEdit": "Edit"
}
},
"Contact": {
name: "Contact",
idParam: "ContactDetailForm.Id",
screens: {
"/OnDemand/user/ContactDetail": "Detail",
"/OnDemand/user/ContactEdit": "Edit",
// "/OnDemand/user/ContactInsert": "Create"
}
}
}
};
private knownPaths: { [key: string]: any } = {
"/OnDemand/user/Homepage": this.config.objects["Homepage"],
"/OnDemand/user/TaskDetail": this.config.objects["Task"],
"/OnDemand/user/TaskEdit": this.config.objects["Task"],
"/OnDemand/user/AccountDetail": this.config.objects["Account"],
"/OnDemand/user/AccountEdit": this.config.objects["Account"],
"/OnDemand/user/ContactDetail": this.config.objects["Contact"],
"/OnDemand/user/ContactEdit": this.config.objects["Contact"],
// "/OnDemand/user/ContactInsert": this.config.objects["Contact"],
"/OnDemand/user/UserDetail": this.config.objects["User"],
"/OnDemand/user/UserEdit": this.config.objects["User"],
"/OnDemand/user/OpportunityEdit": this.config.objects["Opportunity"]
//"/OnDemand/user/CustomObj2": mapping to custom objects here is important!
};
webServiceUrl: string = null;
screen: string = null;
objectType: string = null;
objectId: string = null;
ssoToken: string = null;
moduleRoot: string = null;
rowId: string = null;
commentsAction: string = null;
status: string = null;
//crm parameters to built the task link;
account: string = null;
accountId: string = null;
contact: string = null;
contactId: string = null;
quote: string = null;
quoteId: string = null;
order: string = null;
orderId: string = null;
oppty: string = null;
opptyId: string = null;
lead: string = null;
leadId: string = null;
step: string = null;
//crm contact
lastName: string = null;
firstName: string = null;
email: string = null;
constructor() {
// pick out the info from the url
this.webServiceUrl = "https://" + window.location.hostname + "/Services/Integration";
// get the SSO token from the global variable defined in the web applet
this.ssoToken = epmcrm.ssoToken;
// get the module root from the global variable defined in the web applet
this.moduleRoot = epmcrm.moduleRoot;
this.rowId = epmcrm.rowId;
this.commentsAction = epmcrm.commentsAction;
this.status = epmcrm.status;
this.step = epmcrm.step;
//crm parameters to built the task link;
this.account = epmcrm.account;
this.accountId = epmcrm.accountId;
this.contact = epmcrm.contact;
this.contactId = epmcrm.contactId;
this.quote = epmcrm.quote;
this.quoteId = epmcrm.quoteId;
this.order = epmcrm.order;
this.orderId = epmcrm.orderId;
this.oppty = epmcrm.oppty;
this.opptyId = epmcrm.opptyId;
this.lead = epmcrm.lead;
this.leadId = epmcrm.leadId;
//crm Contact
$("#ContactEditForm\\.First\\ Name").on("change", function () {
this.firstName = (<HTMLInputElement>document.getElementById("ContactEditForm.First Name")).value;
});
$("#ContactEditForm\\.Email\\ Address").on("change", function () {
this.email = (<HTMLInputElement>document.getElementById("ContactEditForm.Email Address")).value;
});
$("#ContactEditForm\\.Last\\ Name").on("change", function () {
this.lastName = (<HTMLInputElement>document.getElementById("ContactEditForm.Last Name")).value;
});
// attempt to discover contextual information
var pathname = window.location.pathname;
if (this.knownPaths[pathname]) {
var obj = this.knownPaths[pathname];
this.objectType = obj.name;
if (obj.idParam) {
this.objectId = this.getParameterByName(obj.idParam);
}
if (obj.screens) {
this.screen = obj.screens[pathname];
}
}
}
}
export = context;
In the view models I have what should give me the results into knockout observables which should than mirror CRM field and with this results I would perform a search and return or not some results:
`contactSearch.ts`
import ko = require("knockout");
import context = require("libs/crm.context");
import contacts = require("libs/crm.contacts");
$("#BTN_TB_ContactNewForm_Save").hide();
$("#BTN_TB_ContactNewForm_Save_idx_1").hide();
//$("#ContactEditForm\\.First\\ Name").on("change", assignFName);
//$("#ContactEditForm\\.Last\\ Name").on("change", assignLName);
//function assignFName() {
// var firstName = (<HTMLInputElement>document.getElementById("ContactEditForm.First Name")).value;
// alert(firstName);
//}
//function assignLName() {
// var lastName = (<HTMLInputElement>document.getElementById("ContactEditForm.Last Name")).value;
// alert(lastName);
//}
//function assignEmail() {
// var Email = (<HTMLInputElement>document.getElementById("ContactEditForm.Email Address")).value
// alert(Email);
//}
//var contactViewModel = function () {
// var self = this;
// self.validContacts = ko.observableArray([]);
// self.addContact = function (validContact) {
// self.validContacts.puch(validContact);
// $.ajax({
// data: ko.toJSON(this),
// contentType: 'application/json',
// success: function (result) {
// validContact.fName(result.
// }
// });
// }
//}
class contactSearch {
private _context: context = new context();
private _contacts: contacts = new contacts(this._context.webServiceUrl);
private _firstName = this._context.firstName;
private _lastName = this._context.lastName;
private _email = this._context.email;
vFName = ko.observable(this._firstName);
vLName = ko.observable(this._lastName);
vEmail = ko.observable(this._email);
//email = ko.computed({
// read: () => $("#ContactEditForm\\.Email\\ Address").on("change", function () {
// })
//})
////})
//lName = ko.observable("");
////email = ko.computed(function () {
//// assignEmail();
//})
isSearching: KnockoutObservable<boolean> = ko.observable(false);
searchValue = ko.computed({
read: () => ("[ContactEmail]~=" + "'" + "" + "'" + " AND [ContactFirstName]~=" + "'" + this.vFName() + "'" + " AND [ContactLastName]~=" + "'" + this.vLName() + "'")
});
contactSearchResults: KnockoutObservableArray<IContact> = ko.observableArray([]);
doValidation() {
$("#ContactEditForm\\.Email\\ Address").on("change", function () {
})
}
doContactSearch() {
this.isSearching(true);
this.contactSearchResults([]);
this._contacts
.find(this.searchValue(), ["Id", "ContactFirstName", "ContactLastName", "ContactEmail", "AccountId", "AccountName"])
.done((results: IContact[]) => {
if (results.length > 0) {
this.contactSearchResults(results);
this.isSearching(false);
}
else {
$("#BTN_TB_ContactNewForm_Save").show();
$("#BTN_TB_ContactNewForm_Save_idx_1").show();
alert("# of matching results= " + results.length);
}
});
}
bindTR(element): void {
/*
* Replicate the CRMOD method of hover styles
*/
var $element = $(element);
$element.hover(
() => {
$element.attr("_savedBGColor", $element.css("background-color"));
$element.css("background-color", "#d3dde6");
},
() => {
$element.css("background-color", $element.attr("_savedBGColor"));
$element.attr("_savedBGColor", "");
}
);
}
bindLink(element): void {
var $element = $(element);
$element.click(
() => {
window["doNavigate"]('ContactDetail?ocTitle=' + encodeURIComponent(this.vLName()) + '&ContactDetailForm.Id=' + this.contactSearchResults["Id"] + '&OCTYPE=', true, this, null)
},
() => {
$element.css("text-decoration", "underline");
}
);
}
}
export = contactSearch;
David,
I have created the observables and I'm binding those here in the HTML view model, but the source for my data are those HTMLInputElement and I don't know how pass the values to the observables.
<p>Email <input data-bind="value: vEmail" />
<span data-bind="text: vEmail"></span>
<span data-bind="text: vFName"></span>
<span data-bind="text: vLName"></span>
<p>Enter the search spec: <textarea data-bind="value: searchValue" />
<button type="button" data-bind="click: validation, click: doContactSearch">Go</button></p>
<table class="list clist" cellspacing="0">
<thead>
<tr>
<th class="m">
<th class="m">
<th>Id</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Account Id</th>
<th>Account</th>
</tr>
</thead>
<tbody data-bind="visible: isSearching">
<tr>
<td colspan="99" style="text-align:center;">Searching, please wait...</td>
</tr>
</tbody>
<tbody data-bind="foreach: contactSearchResults, afterRender: bindTR, click: bindLink">
<tr>
<td class="lrml"> </td>
<td> </td>
<td data-bind="text: Id"></td>
<td data-bind="text: ContactFirstName"></td>
<td data-bind="text: ContactLastName"></td>
<td data-bind="text: ContactEmail"></td>
<td data-bind="text: AccountId"></td>
<td data-bind="text: AccountName"></td>
</tr>
</tbody>
</table>
I have also this file that create the dependency with CRM:
var epmcrm;
((epmcrm) => {
if (!epmcrm["moduleRoot"])
throw new Error("epmcrm global variable not configured");
require.config({
baseUrl: epmcrm.moduleRoot + "/scripts/app",
paths: {
// define the libs here
// 1. External
"jquery": "//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.1/jquery.min",
"jquery-ui.theme": "//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.2/jquery-ui.theme.css",// recently added
"knockout": "//cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min",
"text": "//cdnjs.cloudflare.com/ajax/libs/require-text/2.0.10/text",
"json2": "//cdnjs.cloudflare.com/ajax/libs/json2/20130526/json2.min",
"knockout.mapping": "//cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping", // added by daniel
// 2. Internal
"koExtensions": "../libs/knockout-extensions",
"libs/crm.tasks": "../libs/crm.tasks",
"libs/crm.accounts": "../libs/crm.accounts",
"libs/crm.contacts": "../libs/crm.contacts",
"libs/crm.users": "../libs/crm.users",
"libs/crm.session": "../libs/crm.session",
"libs/crm.context": "../libs/crm.context",
"libs/crm.objects": "../libs/crm.objects",
"libs/crm.utilities": "../libs/crm.utilities",
"libs/crm.viewEngine": "../libs/crm.viewEngine",
"libs/crm.viewmodelEngine": "../libs/crm.viewmodelEngine"
},
shim: {
"knockout": {
deps: ["json2"]
}
}
});
require(["knockout", "knockout.mapping", "koExtensions"],
(ko: KnockoutStatic, komap: KnockoutMapping) => {
ko.mapping = komap;
ko.applyBindings({}, document.getElementById("QuoteWebAppletContainer"));
});
})(epmcrm || (epmcrm = {}));
I'm coming with the solution eventually, because I got what I need.
This were changed like this:
$("#ContactEditForm\\.First\\ Name").on("change", () => {
this.firstName((<HTMLInputElement>document.getElementById("ContactEditForm.First Name")).value);
});
$("#ContactEditForm\\.Email\\ Address").on("change", () => {
this.email((<HTMLInputElement>document.getElementById("ContactEditForm.Email Address")).value);
});
$("#ContactEditForm\\.Last\\ Name").on("change", () => {
this.lastName((<HTMLInputElement>document.getElementById("ContactEditForm.Last Name")).value);
});
this three variables I changed to be observable properties:
lastName: KnockoutObservable<string> = ko.observable("");
firstName: KnockoutObservable<string> = ko.observable("");
email: KnockoutObservable<string> = ko.observable("");
And here I've made some other changes:
vFName = this._context.firstName;
vLName = this._context.lastName;
vEmail = this._context.email;
isSearching: KnockoutObservable<boolean> = ko.observable(false);
searchValue = ko.computed(() => {
return ("[ContactEmail]~=" + "'" + ko.unwrap(this._context.email) + "'" + " AND [ContactFirstName]~=" + "'" + ko.unwrap(this._context.firstName) + "'" + " AND [ContactLastName]~=" + "'" + ko.unwrap(this._context.lastName) + "'")
});
I am trying to add the Rich Text Editor to my Survey system using CKeditor and knockout. I have my ViewModel, which has an observerable array of quesitons. I want to make the Name in each question use the ckeditor. I have look at the post Knockout.js: array parameter in custom binding. And have immplemented that but my OnBlur is not working. The ValueAccessor() is not returning an observable object. So I get an error that string is not a function() on this line of code..
var observable = valueAccessor();
observable($(element).val());
Here is my Html, I am just using a static Id for now on question, and was going to change that after I got this to work for just one question in the array.
<tbody data-bind="foreach: questionModel">
<tr>
<td>
<button data-bind='click: $root.addQuestion' class="btn btn-success" title="Add Question"><i class="icon-plus-sign fontColorWhite"></i></button>
<button data-bind='click: $root.removeQuestion' class="btn btn-danger" title="Remove Question"><i class="icon-minus-sign fontColorWhite"></i></button>
</td>
<td><textarea id="question123" class="RichText" data-bind="richText: Name"></textarea></td>
<td><input type="checkbox" data-bind="checked: AllowComment" /></td>
<td><button data-bind="click: $root.addAnswer" class="btn btn-success" title="Add Answer"><i class="icon-plus-sign fontColorWhite"></i></button></td>
<td>
<div data-bind="foreach: possibleAnswerModel">
<input style="width: 278px" style="margin-bottom: 5px;" data-bind='value: Name' />
<button data-bind='click: $root.removeAnswer' class="btn btn-danger" title="Remove Answer"><i class="icon-minus-sign fontColorWhite"></i></button>
</div>
</td>
</tr>
<tr>
</tbody>
Below is my ViewModel as well as my custom binding....
ko.bindingHandlers.richText = {
init: function (element, valueAccessor, allBindingsAccessor, ViewModel) {
var txtBoxID = $(element).attr("id");
console.log("TextBoxId: " + txtBoxID);
var options = allBindingsAccessor().richTextOptions || {};
options.toolbar_Full = [
['Bold', 'Italic'],
['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent'],
['Link', 'Unlink']
];
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
if (CKEDITOR.instances[txtBoxID]) {
CKEDITOR.remove(CKEDITOR.instances[txtBoxID]);
};
});
$(element).ckeditor(options);
//wire up the blur event to ensure our observable is properly updated
CKEDITOR.instances[txtBoxID].focusManager.blur = function () {
console.log("blur");
console.log("Value: " + valueAccessor());
console.log("Value: " + $(element).val());
var observable = valueAccessor();
observable($(element).val());
};
},
update: function (element, valueAccessor, allBindingsAccessor, ViewModel) {
var value = valueAccessor();
console.log("Value Accessor: " + value);
var valueUnwrapped = ko.utils.unwrapObservable(value);
//var val = ko.utils.unwrapObservable(valueAccessor());
console.log("Value: " + valueUnwrapped);
$(element).val(valueUnwrapped);
}
};
function ViewModel(survey) {
// Data
var self = this;
self.StartDate = ko.observable(survey.StartDate).extend({ required: { message: 'Start Date is required' } });
self.EndDate = ko.observable(survey.EndDate).extend({ required: { message: 'End Date is required' } });
self.Name = ko.observable(survey.Name).extend({ required: { message: 'Name is required' } });
self.ButtonLock = ko.observable(true);
self.questionModel = ko.observableArray(ko.utils.arrayMap(survey.questionModel, function(question) {
return { Id: question.QuestionId, Name: ko.observable(question.Name), Sort: question.Sort, IsActive: question.IsActive, AllowComment: question.AllowComment, possibleAnswerModel: ko.observableArray(question.possibleAnswerModel) };
}));
// Operations
self.addQuestion = function () {
self.questionModel.push({
Id: "0",
Name: "",
AllowComment: true,
Sort: self.questionModel().length + 1,
possibleAnswerModel: ko.observableArray(),
IsActive:true
});
};
self.addAnswer = function (question) {
question.possibleAnswerModel.push({
Id: "0",
Name: "",
Sort: question.possibleAnswerModel().length + 1,
IsActive:true
});
};
self.GetBallotById = function (id) {
for (var c = 0; c < self.BallotProjectStandardList().length; c++) {
if (self.BallotProjectStandardList()[c].BallotId === id) {
return self.BallotProjectStandardList()[c];
}
}
return null;
};
self.removeQuestion = function(question) { self.questionModel.remove(question); };
self.removeAnswer = function(possibleAnswer) { $.each(self.questionModel(), function() { this.possibleAnswerModel.remove(possibleAnswer) }) };
self.save = function() {
if (self.errors().length == 0) {
self.ButtonLock(true);
$.ajax("#Url.Content("~/Survey/Create/")", {
data: ko.toJSON(self),
type: "post",
contentType: 'application/json',
dataType: 'json',
success: function(data) { self.successHandler(data, data.success); },
error: function() {
self.ButtonLock(true);
self.errorHandler();
}
});
} else {
self.errors.showAllMessages();
}
};
}
ViewModel.prototype = new ErrorHandlingViewModel();
var mainViewModel = new ViewModel(#Html.Raw(jsonData));
mainViewModel.errors = ko.validation.group(mainViewModel);
ko.applyBindings(mainViewModel);
I figured what I was doing wrong. When I define the observableArray() I was defining the object as ko.observable, however, when I add a question to the array, I was initializing it as a string. So I change that to match and it worked like a champ. Here is the change push.
self.questionModel = ko.observableArray(ko.utils.arrayMap(survey.questionModel, function(question) {
return { Id: question.QuestionId, Name: ko.observable(question.Name), Sort: question.Sort, IsActive: question.IsActive, AllowComment: question.AllowComment, possibleAnswerModel: ko.observableArray(question.possibleAnswerModel) };
}));
// Operations
self.addQuestion = function () {
self.questionModel.push({
Id: "0",
Name: ko.observable(),
AllowComment: true,
Sort: self.questionModel().length + 1,
possibleAnswerModel: ko.observableArray(),
IsActive:true
});
};