So I have I have a simple structure where one purchase have a collection of expenses, and each expense have an account(plastic, cash, plastic#2...).
So the json my api gets is similar to this:
[
{"$id":"1","Id":1,"Name":"apple","Value":100.0,"AccountId":1,"Account":
{"$id":"2","Id":1,"Name":"Cash"}},
{"$id":"3","Id":2,"Name":"pear","Value":50.0,"AccountId":1,"Account":
{"$ref":"2"}},
{"$id":"4","Id":3,"Name":"raspberry","Value":10.0,"AccountId":1,"Account":
{"$ref":"2"}}
]
I see my json is not writing my cash account each time it needs it, it is refering it with
{"$ref":"2"}
where
{"$id":"2","Id":1,"Name":"Cash"}
so when I render my table with this html:
<table>
<tbody data-bind="foreach: gastos">
<tr>
<td data-bind="text: $data.id"></td>
<td data-bind="text: $data.name"></td>
<td data-bind="text: $data.value"></td>
<td data-bind="text: $data.account.Name"></td>
<td>
<button type="button" class="btn btn-xs">
<i class="glyphicon glyphicon-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
I get this, because the account for pear, and raspberry are nulls:
So how do you handle $ref in knockout?
I am mapping to 'gastos' this way:
$.getJSON('#ViewBag.GastosUrl', function (data) {
data.forEach(function(o) {
gastos.push(new gastoVM(o.Id, o.Name, o.Value, o.Account));
});
});
var gastoVM = function(Id, Name, Value, Account) {
var self = this;
self.id = Id;
self.name = Name;
self.value = Value;
self.account = Account;
};
thanks.
I'm not familiar with entity-framework but with the data as provided, a couple options available (JSFiddle):
Build up the account information alongside the gastos. And only provide the $id or $ref for later referencing.
var vm = {
gastos: [],
accounts: {},
accountLookup: function(accountId){
return this.accounts[accountId];
}
}
//... inside AJAX call
var accountId = o.Account["$id"]
if(accountId)
{
vm.accounts[accountId] = o.Account;
}
Use a ko utility method to lookup the account from within your array.
accountLookupByUtil: function(accountId) {
var gasto = ko.utils.arrayFirst(this.gastos, function(item) {
if(item.account['$id'] == accountId)
{
return item
}
});
return gasto.account;
}
From the html:
<td data-bind="text: $root.accountLookup($data.accountId).Name"></td>
<td data-bind="text: $root.accountLookupByUtil($data.accountId).Name"></td>
Note: Both methods are available in the fiddle, thus some properties are provided that would not be necessary depending upon the method used.
Related
What I want to do is a parametrized report, i would love to use SSRS or other fancy tools for this but it's sort of dangerous at this point because i don't really want to mess around with the company server and I dont have much time; Also If it's a tool it should be a free and light tool and i didn't find one by now.
So, my idea is making a simple controller with Index that will return a List to View according to parameters and the View will use that ViewModel as Model then the users can export that list to CSV or PDF, the problem is: MVC is asking for a real db model to complete the scaffolding, how can this be done then?
Controller (I call an stored proc here)
public class ReporteEntregasPresentacionController : Controller
{
private EntregaMedicamentosEntities db = new EntregaMedicamentosEntities();
public ActionResult Index(DateTime DateFrom, DateTime DateTo)
{
ReporteEntregasPresentacionViewModel rep = new ReporteEntregasPresentacionViewModel();
string sqlQuery = "[dbo].[spEntregasPorPresentacion] ({0},{1})";
Object[] parameters = { DateFrom, DateTo };
rep.LstEntregasPresentacionViewModel = db.Database.SqlQuery<ItemEntregasPresentacionViewModel>(sqlQuery, parameters).ToList();
return View(rep);
}
}
ViewModel:
public class ReporteEntregasPresentacionViewModel
{
public int index;
public List<ItemEntregasPresentacionViewModel> LstEntregasPresentacionViewModel;
}
public class ItemEntregasPresentacionViewModel {
public string idProducto { get; set; }
public string Descripcion { get; set; }
public string EntregasTotales { get; set; }
}
I don't have a View now but i should be something like this:
#model EntregaMedicamentos.Models.ReporteEntregasPresentacionViewModel
<link href="~/Content/css/styles.css" rel="stylesheet" />
#{
ViewBag.Title = "ReporteEntregasPresentacion";
}
<h2>ReporteEntregasPresentacion</h2>
#using (Html.BeginForm("Index", "Entrega", FormMethod.Post))
{
<div class="card text-white bg-secondary">
<h5 class="card-header">Search</h5>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="input-group">
#Html.TextBox("DateFrom", ViewBag.currentFilter1 as DateTime?, new { #class = "form-control", placeholder = "Desde fecha", #readonly = "true", type = "datetime" })
#Html.TextBox("DateTo", ViewBag.currentFilter2 as DateTime?, new { #class = "form-control", placeholder = "Hasta fecha", #readonly = "true", type = "datetime" })
<button id="Submit4" type="submit" style='font-size:22px ;color:blue'><i class='fas fa-search'></i></button>
</div>
</div>
</div>
</div>
</div>
}
<br>
<table class="table table-striped ">
<tr class="table-primary">
<th>
Código
</th>
<th>
Producto
</th>
<th>
Entregas Totales
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.idProducto)
</td>
<td>
#Html.DisplayFor(modelItem => item.Descripcion)
</td>
<td>
#Html.DisplayFor(modelItem => item.Valor)
</td>
</tr>
}
</table>
I ended up creating a real table/model and then it worked fine with the viewmodel. Thanks.
When entering an E-mail address, the user will have to select the E-mail
domain from the pre-defined list (e.g., gmail.com; outlook.com; hotmail.com).
The Domain dropdown will accept a value that is not part of the list as well.
I should have both select and input feature.
HTML:
<!-- Mulitple array of emails -->
<div>
<table class="table table-bordered">
<tbody data-bind='foreach: billDeliveryEmails'>
<tr>
<td><input class='required' data-bind='value: userName' /></td>
<td><select data-bind="options: $root.availableEmailDomains(), value: domainName, optionsText: 'domainName', optionsValue: 'domainName'"></select></td>
<td><a data-bind="click:'removeDeliveryEmailAddress'">Delete</a></td>
</tr>
</tbody>
</table>
<a class="atm-bloque-link" data-bind="click:'addDeliveryEmailAddress'">Agregue otra direccion de email</a>
</div>
VM:
// Domain class
function Domain(domainName) {
var self = this;
self.domainName = domainName;
}
billDeliveryEmails : [],
availableEmailDomains : ko.observableArray([
new Domain("hotmail.com"),
new Domain("yahoo.com")
])
addDeliveryEmailAddress: function ($element, data, context, bindingContext, event) {
bindingContext.$root.billDeliveryEmails.push({
userName: "",
domainName: this.viewModel.get('availableEmailDomains')[0]
});
event.preventDefault();
},
removeDeliveryEmailAddress: function ($element, data, context, bindingContext, event) {
bindingContext.$root.billDeliveryEmails.splice(0, 1)
event.preventDefault();
}
Based on the link in your comment, here's an example:
function Domain(domainName) {
var self = this;
self.domainName = domainName;
}
var vm = function () {
var self = this;
self.browser = ko.observable();
self.browserList = ko.observableArray([
new Domain('yahoo.com'),
new Domain('hotmail.com')
])
}();
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<label>Choose a browser from this list:
<input list="browsers" name="myBrowser" data-bind="value: browser" /></label>
<datalist id="browsers" data-bind="foreach: browserList">
<option data-bind="text: domainName">
</datalist>
<br />
Your browser is: <span data-bind="text: browser"></span>
An observable array is used to populate the list and a regular observable tracks the selected value.
I have this table bounded to knockout like this
<tbody data-bind="foreach: Items">
<tr class="gradeA">
<td>
<select name="MaterialId" data-bind="options: $parent.lookups.materials, optionsText: 'Name', value: Material"></select></td>
<td>
<input type="text" name="Quantity" data-bind="value: Quantity"></td>
<td>
<input type="text" name="UnitPrice" data-bind="value: UnitPrice"></td>
<td>
<input type="text" name="Discount" data-bind="value: Discount"></td>
<td>
<input type="text" readonly="readonly" data-bind="value: Price" />
</td>
</tr>
</tbody>
javascript is:
function Item() {
this.Material = ko.observable()
this.Quantity = ko.observable(1)
this.UnitPrice = ko.observable(0)
this.Discount = ko.observable(0)
this.MaterialRetailPrice = ko.computed(function () {
return this.Material() ? this.Material().RetailPrice : 0
}, this);
this.StoreId = ko.computed(function () {
return this.Store() ? this.Store().Id : 0
}, this)
this.Price = ko.computed(function () {
return this.Quantity() * this.UnitPrice() - this.Discount()
}, this)
}
function materialBuyBillViewModel() {
var self = this
self.lookups = {
stores: ko.observableArray([]),
materials: ko.observableArray([])
}
self.Items = ko.observableArray()
self.VendorBill = ko.observable()
self.Vendor = ko.observable()
self.Discount = ko.observable()
self.Vat = ko.observable()
//... Methods to fill lookups
}
$(document).ajaxStop(function (event, request, settings) {
MaterialBuyBillViewModel.AddItem()
})
$(document).ready(function () {
MaterialBuyBillViewModel = new materialBuyBillViewModel()
})
The result is fine and I can change the unit price and get price updated, but the missing thing is to get unit price changed and price updated when product select is changed (replace unitprice value with Material().RetailPrice). any one can point me how to bind unit price input properly?
I hope I got your question correctly.
You can create a ko.computed with a read and write method.
The read method enables you to check if a material is selected. If there is, you return the price of the selected material. If there isn't, you return the user defined UnitPrice value.
The write method just forwards the user input straight to the UnitPrice property. To indicate that, when a material is selected, the user cannot define the value, I've added a disabled binding on the input field.
Let me know if this solves your problem.
function Item() {
this.Material = ko.observable();
this.Quantity = ko.observable(5);
this.UnitPrice = ko.observable(1);
this.Discount = ko.observable(0);
this.ComputedUnitPrice = ko.computed({
read: function() {
return this.Material() ? this.Material().RetailPrice : this.UnitPrice();
},
write: function(val) {
this.UnitPrice(val);
},
owner: this
});
this.Price = ko.computed(function() {
return this.Quantity() * this.ComputedUnitPrice() - this.Discount()
}, this);
}
function materialBuyBillViewModel() {
var self = this;
self.lookups = {
materials: ko.observableArray([{
Name: "Material 1",
RetailPrice: 1
}, {
Name: "Material 2",
RetailPrice: 2
}, {
Name: "Material 3",
RetailPrice: 3
}])
};
self.Items = ko.observableArray([
new Item()
]);
};
ko.applyBindings(new materialBuyBillViewModel());
[disabled] {
background: #efefef
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>Material</th>
<th>Quantity</th>
<th>UnitPrice</th>
<th>Discount</th>
<th>Price</th>
</tr>
</thead>
<tbody data-bind="foreach: Items">
<tr class="gradeA">
<td>
<select name="MaterialId" data-bind="options: $parent.lookups.materials, optionsText: 'Name', optionsCaption: 'Pick a material', value: Material, allowValueUnset: true"></select>
</td>
<td>
<input type="text" name="Quantity" data-bind="value: Quantity">
</td>
<td>
<input type="text" name="UnitPrice" data-bind="value: ComputedUnitPrice, disable: Material">
</td>
<td>
<input type="text" name="Discount" data-bind="value: Discount">
</td>
<td>
<input type="text" readonly="readonly" data-bind="value: Price" />
</td>
</tr>
</tbody>
</table>
Here are the templates in question:
<template name="tables">
<div class="table-area">
{{#each tableList}}
{{> tableBox}}
{{/each}}
</div>
</template>
<template name="tableBox">
<table id="{{name}}" class="table table-condensed table-striped">
<tr>
<td>{{name}}</td>
<td> Min:</td>
<td>{{minBet}}</td>
<td>{{cPlayers}}</td>
</tr>
<tr>
<td>Dice1</td>
<td>Dice2</td>
<td>Dice3</td>
<td>Total</td>
</tr>
{{#each table [{{name}}]}}
{{> tableRow}}
{{/each}}
</table>
</template>
<template name="tableRow">
<tr>
<td>{{Roll.dice1}}</td>
<td>{{Roll.dice2}}</td>
<td>{{Roll.dice3}}</td>
<td>{{Roll.total}}</td>
</tr>
</template>
And here are the Meteor Handlebars functions:
Template.tables.tableList = function(){
return Tables.find();
}
Template.tableBox.table = function(tableID){
return Rolls.find({tableName: tableID});
}
The problem is each table that is rendered on screen has all the 'Rolls' listed (All 100 lines). Instead of being filtered for the parameter I am trying to pass through to the Roll template function which is the table name {{name}}.
In the "table id" tag of TableBox, the {{name}} gets converted correctly. i.e. "T1", "T2", "T3" etc. But it's that same TableID that i need to pass to the function to filter from the db query properly. Is there a way to do this more simply? I would like to stick to the handlebars templating way of doing things here if possible.
For reference below is the JSON initialisation code for the test data:
//initiate tables
for (i = 1; i < 11; i++) {
Tables.insert({
name: 'T' + i,
minBet: '300',
cPlayers: '(8)'
});
}
//initiate rolls within tables
for (i = 1; i < 11; i++) {
for(j=1; j<11; j++){
var die1 = ((Math.floor(Math.random() * 5) +1).toString());
var die2 = ((Math.floor(Math.random() * 5) +1).toString());
var die3 = ((Math.floor(Math.random() * 5) +1).toString());
var t = (parseInt(die1) + parseInt(die2) + parseInt(die3)).toString();
Rolls.insert({
Roll: {
tableName: 'T' + i,
rollNumber: j;
dice1: die1,
dice2: die2,
dice3: die3,
total: t
},
});
}
}
Ok - After trial and error - figured it out:
In the helper function:
Template.tableBox.table = function(tableID){
return Rolls.find({"Roll.tableName": tableID});
}
I needed to add the nested Roll.tableName property name but within brackets as the query.
And back in the tableBox template:
<template name="tableBox">
<table id="{{name}}" class="table table-condensed table-striped">
<tr>
<td>{{name}}</td>
<td> Min:</td>
<td>{{minBet}}</td>
<td>{{cPlayers}}</td>
</tr>
<tr>
<td>Dice1</td>
<td>Dice2</td>
<td>Dice3</td>
<td>Total</td>
</tr>
{{#each table name}}
{{> tableRow}}
{{/each}}
</table>
</template>
No need for the curly braces for the 'Name' argument for the function. Somehow handlebars and Meteor recognise the context you are referring to without the curly braces and applies it like it is within {{name}} for the tableBox. I.e. "T1", "T2", "T3" are passed correctly to the function and now my unique tables only contain the list of rolls specific to individual tables.
I have applied bindings to the following view model
var groupDeleteViewModel = {
group: { Id: ko.observable(), Name: ko.observable(), Members: ko.observableArray() },
load: function (item) {
debugger
},
remove: function (item) {
groupDeleteViewModel.group.Id(item.Id());
groupDeleteViewModel.group.Name(item.Name());
groupDeleteViewModel.group.Members(item.Members());
$("#groupDelete").show();
},
cancel: function () {
$("#groupDelete").hide();
}
}
the remove function loads the view with the data that has been passed in item to the remove function.
<div id="groupDelete" class="pop-window filter-view">
<h2>
Delete Group
</h2>
<table>
<tr>
<th>
Name
</th>
<td>
<span data-bind="text:group.Name" />
</td>
</tr>
<!--ko foreach: group.Members-->
<tr>
<th>
</th>
<td>
<div data-bind="text:Name" class="grey-border">
</div>
</td>
<td>
</td>
</tr>
<!--/ko-->
</table>
<span class="centeralign">
<input type="button" value="Delete" data-bind="click: remove" class="delete" />
<input type="button" value="Cancel" data-bind="click: cancel" />
</span>
</div>
I don't want to keep mapping each element of item to each element of groupDeleteViewmodel.group. I have done it at a lot of other places also in the code which has made the code really messy. I want to use the ko.mapping plugin to do the same thing.
Till now what I have tried is -
remove:function (item){
var data = ko.mapping.toJS(item);
ko.mapping.fromJS(data, groupDeleteViewModel.group);
$("#groupDelete").show();
}
But this just does not work. i really don't know why. It should have worked ideally.
Can someone tell what is the right way of using ko.mapping in such a case?
Thanks.
The mapping plugin is looking for mapping data already present in groupDeleteViewModel.group, but it doesn't find it, because you didn't initialize groupDeleteViewModel.group using the mapping plugin.
So instead of initializing group like you did:
group: { Id: ko.observable(), Name: ko.observable(), Members: ko.observableArray() }
initialize it using the mapping plugin:
group: ko.mapping.fromJS({ Id: undefined, Name: undefined, Members: [] })
And this is me fiddling around with your code: fiddle