I have the following columnDefs
self.columnDefs = [
{ width: 150, field: 'timeReceived', displayName: 'Time Received', cellFilter: function (data) { return moment(data).format('DD/MM/YYYY h:mm a') } },
{ width: 500, field: 'name', displayName: 'Name' },
{ width: 150, field: 'LinkToTimeSent', displayName: 'Time SentX', cellTemplate: '<a data-bind="text:$parent.entity.timeSent, attr: { href: $parent.entity.linkToTimeSent}" ></a>' },
];
My problem is with the Time SentX. I'd like this to display the content of entity.timeSent but converted for human consumption using the moment library.
How can I call the function moment($parent.entity.timeSent).format('DD/MM/YYYY h:mm a') from within my columnDefs?
In the following plunk, line 96 needs to contain something like
text:moment($parent.entity.TimeSent, "DD/MM/YYYY h:mm a") but I can't get it to work!
:https://plnkr.co/edit/jNn3knbnCCbBQu9NgKze?p=preview
Edit: My answer was a bit too general. An attempt to be more specific.
Map your WorkflowRules to their own "viewmodels", and you can do anything you like:
this.workflowRules = ko.observableArray(sampleData.WorkflowRules
.map(function(jsonRule) {
// Add UI helpers (create a sort of viewmodel)
var timeSentMoment = moment(jsonRule.TimeSent);
// Purely for display purposes:
jsonRule.timeSentLabel = timeSentMoment.format('DD/MM/YYYY h:mm a');
// Do what ever you want to create the right url
jsonRule.href = jsonRule.TimeSent;
return jsonRule;
}));
Then, in your template:
<div data-bind="with: $parent.entity">
<a data-bind="text: timeSentLabel,
attr: {href: href}"></a>
</div>';
Which is defined in js:
var timeSentTemplate = '<div data-bind="with: $parent.entity"><a data-bind="text: timeSentLabel, attr: {href: href}"></a></div>';
var columnDefs = [
{ cellTemplate: timeSentTemplate, /* etc. */ }
];
I hope I finally got your question correctly...
(https://plnkr.co/edit/93ucvDLk5bUFtU4dB1vn?p=preview, moved some stuff around)
Previous, more general answer:
When you create a knockout binding, knockout automatically wraps the second part of the binding in a function. For example:
data-bind="text: myTextObservable"
Is processed as:
text: function (){ return myTextObservable }
Additionally, knockout uses this function to create a computedObservable. This will create a subscription to any observable used inside the function, making sure the data-bind is re-evaluated if any of them changes.
This means that in your case, you can define your format rule inside your data-bind like so (assuming timeSent is an observable`):
data-bind="text: moment($parent.entity.timeSent()).format('DD/MM/YYYY h:mm a') "
Knockout will see that the timeSent observable is called and make sure the whole binding gets updated correctly. Here's an example:
var date = ko.observable(new Date());
var addDay = function() {
date(moment(date())
.add(1, "day")
.toDate()
);
};
ko.applyBindings({
date: date,
addDay: addDay
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<strong>The "raw" observable</strong>
<div data-bind="text: date"></div>
<br/>
<strong>A "computed" text defined in the data-bind</strong>
<div data-bind="text: moment(date()).format('DD-MM-YY')"></div>
<button data-bind="click:addDay">add a day</button>
My advice however is to create a separate computed observable inside your viewmodel. After all, this is what viewmodels are meant to do, and it will help you out a great deal when fixing bugs. I.e.:
// Add to your entity view model:
this.timeSentLabel = ko.computed(function() {
return moment(this.timeSent())
.format('DD/MM/YYYY h:mm a');
}, this);
Related
I use a cell filter to show multiple properties of a bound entity in one cell. Therefore there is no one field name because it is a computed field. How can I force the grid to reevaluate the cell filter if one of the involved properties changes?
The column definition:
columnDefs: [
{ field: 'xxx', displayName: 'Something', cellFilter: 'concatSomeProps:this' }
]
The filter:
myApp.filter('concatSomeProps', function () {
return function (cellValue, scope) {
var entity = scope.row.entity;
return entity.prop1 + ", " + entity.prop2;
};
});
If have tried to use notifyDataChanged or the refresh function of the grid api but it doesn't work.
I'd probably use a cellTemplate, and not a filter in this case:
{ field: 'xxx', displayName: 'Something', cellTemplate:'template.html' }
And
<script type="text/ng-template" id="template.html">
<div>
{{row.entity.name + row.entity.age}}
</div>
</script>
See this Plnkr I made for you to see what I'm talking about. Edit either one of the first two fields and you will see the concat field change. You'd also change your template to use row.entity.prop1 + row.entity.prop2 to make it work, as my template is for my columns.
I trying to develop a little app.
I have made a login view and it works, I am able to bind the data written by the user. After this, I show two select box. The first is binded with a list (correctly) and the second has to be bind with a list populated after a value is selected from the first.
I'm not able to read the value selected from the first list.
I have this html:
<div class="dx-fieldset">
<div class="dx-field">
<div class="dx-field-label">Rete</div>
<div class="dx-field-value"
data-bind="dxLookup: { dataSource: is_retistiSource, value: rete, displayExpr: 'NOME', title: 'Retisti associati', placeholder: 'Selezionare rete',
onSelectionChanged:setRete }" />
</div>
<div class="dx-field">
<div class="dx-field-label">Impianto</div>
<div class="dx-field-value"
data-bind="dxLookup: { dataSource: is_impiantiSource, value: impianto, displayExpr: 'NOME', title: 'Impianti associati', placeholder: 'Selezionare impianto' }" />
</div>
</div>
and this javascript:
OverviewAPP.afterLogin = function (params) {
var isReady = $.Deferred();
var viewModel = {
rete: ko.observable(""),
impianto: ko.observable(""),
is_retistiSource: OverviewAPP.listaReti,
is_impiantiSource: OverviewAPP.listaImpianti,
setRete: function () {
console.log(viewModel.rete);
var nRetisti = OverviewAPP.listaRetiImpianti.length;
for (i = 0; i < nRetisti; i++) {
if (OverviewAPP.listaRetiImpianti[i]["retista"]["NOME"] == this.rete)
{
OverviewAPP.listaImpianti = listaRetiImpianti[i]["listaImpianti"];
break;
}
}
is_impiantiSource = OverviewAPP.listaImpianti;
},
close: function () {
OverviewAPP.app.back();
}
};
return viewModel;
};
In the setRete function, with the line "console.log(viewModel.rete);", I see this output:
d(){if(0<arguments.length)return d.Wa(c,arguments[0])&&(d.X(),c=arguments[0],d.W()),this;a.k.Ob(d);return c}
Why? How can I bind and read the selected value?
UPDATE: I've done in this way, it works:
setRete: function (e) {
OverviewAPP.IDrete = e.value;
var nRetisti = OverviewAPP.listaRetiImpianti.length;
for (i = 0; i < nRetisti; i++) {
if (OverviewAPP.listaRetiImpianti[i]["retista"]["NOME"] == e.value["NOME"])
{
OverviewAPP.listaImpianti = OverviewAPP.listaRetiImpianti[i]["listaImpianti"];
break;
}
}
//ko.applyBindings(viewModel);
},
But I don't know how update my second list, "is_impiantiSource".
Observables are functions. That's why you get a function in the console. Call the rete function to get its value:
viewmodel.rete();
Also see the Knockout: Observables help topic that describes this (under "Reading and writing observables").
This is how you can obtain a new value. Then, you need to update the dependent lookup data source. For this, make the is_impiantiSource property an observable array:
is_impiantiSource: ko.observableArray(OverviewAPP.listaImpianti),
After this, modify it in setRene like:
viewModel.is_impiantiSource(OverviewAPP.listaImpianti)
Also see Observable Arrays for how to work with arrays in Knockout
I have a column in a Kendo grid that I want to perform some specific logic for when rendering, and am using Angular. I have the grid columns set up using the k-columns directive.
After looking at the documentation, it seemed simple: I could add the template option to my column, define the function to perform my logic, and pass the dataItem value in. What I have looks something like this:
k-columns='[{ field: "Name", title: "Name",
template: function (dataItem){
// Perform logic on value with dataItem.Name
// Return a string
}
}]'
However, running this causes a syntax error complaining about the character '{' that forms the opening of the block in my function.
I have seen several examples of defining a template function in this format. Is there something else that needs to be done for this to work? Am I doing something incorrectly? Is there another way of defining the template as a function and passing the column data to it? (I tried making a function on my $scope, which worked, except I couldn't figure out how to get data passed into the function.)
Thank you for your help.
It appears that defining a column template in this fashion isn't supported when using AngularJS and Kendo. This approach works for projects that do not use Angular (standard MVVM), but fails with its inclusion.
The workaround that a colleague of mine discovered is to build the template using ng-bind to specify a templating function on the $scope, all inside of a span:
template: "<span ng-bind=templateFunction(dataItem.Name)>#: data.Name# </span>"
This is the default column templating approach that is implemented by Telerik in their Kendo-Angular source code. I don't know yet if the data.Name value is required or not, but this works for us.
Warning: Don't have access to Kendo to test the code at the moment, but this should be very close
In your case, you are assigning a a string to the value of k-columns and that string contains the the word function and your curly brace {
You need to make sure the function gets executed ... something like this:
k-columns=[
{
field: "Name",
title: "Name",
template: (function (dataItem){
// Perform logic on value with dataItem.Name
// Return a string
}())
}
];
Note the difference:
We create an object -- a real honest-to-goodness object, and we use an IIFE to populate the template property.
Maybe, it will be useful for someone - this code works for me too:
columns: [
{
field: "processed",
title:"Processed",
width: "100px",
template: '<input type="checkbox" ng-model="dataItem.processed" />'
},
and you get the two-way binding with something like this:
<div class="col-md-2">
<label class="checkbox-inline">
<input type="checkbox" ng-model="vm.selectedInvoice.processed">
processed
</label>
</div>
This can be done via the columns.template parameter by supplying a callback function whose parameter is an object representing the row. If you give the row a field named name, this will be the property of the object you reference:
$("#grid").kendoGrid({
columns: [ {
field: "name",
title: "Name",
template: function(data) {
return data.name + "has my respect."
}
}],
dataSource: [ { name: "Jane Doe" }, { name: "John Doe" } ]
});
More information is available on Kendo's columns.template reference page.
After hours of searching. Here is the conclusion that worked:
access your grid data as {{dataItem.masterNoteId}} and your $scope data as simply the property name or function.
Example
template: '<i class="fa fa-edit"></i>',
I really hope this safes somebody life :)
just use like my example:
}, {
field: "TrackingNumber",
title: "#T("Admin.Orders.Shipments.TrackingNumber")",
//template: '<a class="k-link" href="#Url.Content("~/Admin/Shipment/ShipmentDetails/")#=Id#">#=kendo.htmlEncode(TrackingNumber)#</a>'
}, {
field: "ShippingMethodName",
title: "#T("Admin.Orders.Shipments.ShippingMethodName")",
template:function(dataItem) {
var template;
var ShippingMethodPluginName = dataItem.ShippingMethodPluginName;
var IsReferanceActive = dataItem.IsReferanceActive;
var ShippingMethodName = dataItem.ShippingMethodName;
var CargoReferanceNo = dataItem.CargoReferanceNo;
var ShipmentStatusId = dataItem.ShipmentStatusId;
if (ShipmentStatusId == 7) {
return "<div align='center'><label class='label-control'><b style='color:red'>Sipariş İptal Edildi<b></label></div>";
} else {
if (ShippingMethodPluginName == "Shipping.ArasCargo" || ShippingMethodPluginName == "Shipping.ArasCargoMP") {
template =
"<div align='center'><img src = '/content/images/aras-kargo-logo.png' width = '80' height = '40'/> <label class='label-control'><b>Delopi Aras Kargo Kodu<b></label>";
if (IsReferanceActive) {
template =
template +
"<label class='label-control'><b style='color:red; font-size:20px'>"+CargoReferanceNo+"<b></label></div>";
}
return template;
}
I work in durandal project (where javascript and html written in separated pages).
I have kendo-combo, and I give it width by declare the wrraper-input width.
It works well. But when I change it to be binded- it does not work.
here is my code (which is not working):
html:
<input id="kendoCombo" data-bind=" value:'444', style:{width:width},
kendoDropDownList: { dataSource: data,
dataValueField:itemValue,
dataTextField: itemText,
value:selectedId,
template: template,
change:onChange}" />
javascript:
width:ko.observable('100px')
When my width has not been binded yet, it works well. Here is my previous html code:
<input style="width:100
id=" kendoCombo "
data-bind=" value: '444',
kendoDropDownList: { dataSource: data,
dataValueField:itemValue,
dataTextField: itemText,
value:selectedId,
template: template,
change:onChange} " />
The problem is that Kendo only set the width of the DropDownList once when it is initialized, so when Knockout updates the width in the style binding it has no effect on the DropDownList.
However you can set width on the wrapper property (requires: Q1 2013 (version 2013.1.319) or newer) of the kendoDropDownList and you can put this logic into a custom bindingHandler:
ko.bindingHandlers.kendoDropDownListWidth = {
update: function (element, valueAccessor) {
var dropdownlist = $(element).data("kendoDropDownList");
dropdownlist.wrapper[0].style["width"] =
ko.utils.unwrapObservable(valueAccessor());
}
};
And use it like this:
<input id="kendoCombo" data-bind=" value:'444',
kendoDropDownListWidth: width,
kendoDropDownList: { dataSource: data,
dataValueField:itemValue,
dataTextField: itemText,
value:selectedId,
template: template,
change:onChange}" />
Demo JSFiddle.
Update
My original post is pretty long - here's the tl;dr version:
How do you update all properties of a knockout model after a single property has changed? The update function must reference an observableArray in the viewModel.
-- More details --
I'm using KnockoutJS. I have a Zoo and a Tapir model and three observables in the viewmodel - zoos, tapirCatalog and currentTapir. The tapirCatalog is populated from the server and the currentTapir holds the value of whichever tapir is being edited at the time.
Here's what I'm trying to accomplish: A user has added a tapir from a list of tapirs to his/her zoo. When viewing the zoo, the user can edit a tapir and replace it with another. To do this a popup window is shown with a select form populated by tapir names and a span showing the currently selected GoofinessLevel.
So, when the select element changes this changes the TapirId in currentTapir. I want that to trigger something that changes the currentTapir's Name and GoofinessLevel.
I tried subscribing to currentTapir().GoofinessLevel but cannot get it to trigger:
function Zoo(data) {
this.ZooId = ko.observable(data.ZooId);
this.Tapirs = ko.observableArray(data.Tapirs);
}
function Tapir(data) {
this.TapirId = ko.observable(data.TapirId);
this.Name = ko.observable(data.Name);
this.GoofinessLevel = ko.observable(data.Name);
}
function ViewModel() {
var self = this;
// Initializer, because I get an UncaughtType error because TapirId is undefined when attempting to subscribe to it
var tapirInitializer = { TapirId: 0, Name: "Template", GoofinessLevel: 0 }
self.zoos = ko.observableArray([]);
self.tapirCatalog = ko.observableArray([]);
self.currentTapir = ko.observable(new Tapir(tapirInitializer));
self.currentTapir().TapirId.subscribe(function (newValue) {
console.log("TapirId changed to: " + newValue);
}); // This does not show in console when select element is changed
};
Oddly enough, when I subscribe to the Goofiness level inside the Tapir model I get the trigger:
function Tapir(data) {
var self = this;
self.TapirId = ko.observable(data.TapirId);
self.Name = ko.observable(data.Name);
self.GoofinessLevel = ko.observable(data.Name);
self.TapirId.subscribe(function (newId) {
console.log("new TapirId from internal: " + newId);
}); // This shows up in the console when select element is changed
}
I suspect that this is a pretty common scenario for people using KO but I haven't be able to find anything. And I've searched for a while now (it's possible that I may not have the correct vocabulary to search with?). I did find this solution, but he references the viewmodel from the model itself -- which seems like back coding since I would think the Tapir should not have any knowledge of the Zoo: http://jsfiddle.net/rniemeyer/QREf3/
** Update **
Here's the code for my select element (the parent div has data-bind="with: currentTapir":
<select
data-bind="attr: { id: 'tapirName', name: 'TapirId' },
options: $root.tapirCatalog,
optionsText: 'Name',
optionsValue: 'TapirId',
value: TapirId">
</select>
It sounds like what you need to do is bind the select to an observable instead of the Id
<select
data-bind="attr: { id: 'tapirName', name: 'TapirId' },
options: $root.tapirCatalog,
optionsText: 'Name',
optionsValue: 'TapirId',
value: currentTapir">
</select>