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;
}
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 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);
I want to display a single number (that would change, that's why I'm not hard coding it) from am object. This, however, doesn't display anything.
var game_data = {
amount: 1,
yes_left: 3,
nos_left: 1
};
var game_panel = {
view: "form", id:"game_panel",
rows:[
{cols:[
{
template:'<span class="main_title">Money: $#amount#.00 </span>',
data:{amount:game_data.amount},align:"center"
},
}
]
};
I've also tried returning it as a variable:
template: function(game_data) {return '<span class="main_title">Money: ' + game_data.amount + '.00 </span>'}
Any ideas how to get that to display?
The code that you are using is correct. You can check the the next working snippet http://webix.com/snippet/82566e82
If you plan to update this number dynamically, it will be better to use a bit different syntax
id:"t1",
template:'<span class="main_title">Money: $#amount#.00 </span>',
data: game_data, align:"center"
and for number updating, be sure to call template.refresh, as template doesn't track data changes automatically.
game_data.amount += 2;
$$("t1").refresh();
http://webix.com/snippet/e3b0450d
I have a table with ng-grid, and the problem is that i'm not sure how to collect the selected row(s) id or variable to pass into my delete function.
here is a quick mockup of what i'm trying to do
http://plnkr.co/edit/zy653RrqHmBiRJ7xDHlV?p=preview
the following code is from my html, a clickable delete button that takes in 2 parameters, the array of checkbox ids and the index at the corresponding table. This delete method was obtained from this tutorial : http://alexpotrykus.com/blog/2013/12/07/angularjs-with-rails-4-part-1/
<div class="btn-group">
<button class="my-btn btn-default button-row-provider-medical-services" ng-click="deleteProviderMedicalService([], $index)">Delete</button>
</button>
</div>
<div class="gridStyle ngGridTable" ng-grid="gridOptions">
</div>
The following code grabs the json data from a url, queries it and returns it. It also contains the delete function that gets called from the controller in the html page.
app.factory('ProviderMedicalService', ['$resource', function($resource) {
function ProviderMedicalService() {
this.service = $resource('/api/provider_medical_services/:providerMedicalServiceId', {providerMedicalServiceId: '#id'});
};
ProviderMedicalService.prototype.all = function() {
return this.service.query();
};
ProviderMedicalService.prototype.delete = function(providerId) {
this.service.remove({providerMedicalServiceId: providerId});
};
return new ProviderMedicalService;
}]);
The following is my controller(not everything, just the most important bits). $scope.provider_medical_services gets the json data and puts it into the ng-grid gridOptions.
After reading the ng-grid docs, i must somehow pass the checkbox ids from the selectedItems array and pass it into html doc to the delete function. Or, i'm just doing this completely wrong, as i hacked this together. Solutions and suggestions are greatly appreciated
(function() {
app.controller('ModalDemoCtrl', ['$scope', 'ProviderMedicalService', '$resource', '$modal', function($scope, ProviderMedicalService, $resource, $modal) {
var checkBoxCellTemplate = '<div class="ngSelectionCell"><input tabindex="-1" class="ngSelectionCheckbox" type="checkbox" ng-checked="row.selected" /></div>';
$scope.provider_medical_services = ProviderMedicalService.all();
$scope.deleteProviderMedicalService = function(ids,idx) {
$scope.provider_medical_services.splice(idx, 1);
return ProviderMedicalService.delete(ids);
};
$scope.gridOptions = {
columnDefs: [
{
cellTemplate: checkBoxCellTemplate,
showSelectionCheckbox: true
},{
field: 'name',
displayName: 'CPT Code/Description'
},{
field: 'cash_price',
displayName: 'Cash Price'
},{
field: 'average_price',
displayName: 'Average Price'
},
],
data: 'provider_medical_services',
selectedItems: []
};
i think the easiest option is pass an (array index) as data-id to your dom, which u can pick it from there.
{{$index}} is a variable you can use in ng-repeat
======= ignore what i said above, since i normaly writes my own custom stuff ======
I just had a look at ng-grid, i took their example. i've added a delete all selected function, as well as someone elses delete current row function ( these is pure angular way ) to see the code, hover over the top right corner < edit in jsbin >
http://jsbin.com/huyodove/1/
Honestsly i don't like it this way, you would be better off use something like lodash to manage your arrays and do your own custom grid. Using foreach to find the row index isn't good performance.
In their doc, it says you can change the row template, and which you should, so you can add the {{index}} to that row, and filter your data through that index rather which is a little bit better. anyway beware of deleting cells after you have filter your table.
I don't quite get much your question, but you can access to selectedItems of ng-grid as following: $scope.gridOptions.$gridScope.selectedItems (see ng-grid API for more information, but technically this array holds the list of selected items in multiple mode - or only one item in single mode)
For your case, the deleteAll() function could be someething like this:
$scope.deleteAll = function() {
$scope.myData = [];
}
The delete() function which delete selected items can be:
$scope.delete = function() {
$.each($scope.gridOptions.$gridScope.selectedItems, function(index, selectedItem) {
//remove the selected item from 'myData' - it is 2-ways binding to any modification to myData will be reflected in ng-grid table
//you could check by either name or unique id of item
});
}
I am trying to find out why the filter function isn't working, but I am stucked. This is the first time I am using Dojo but I am not really familliar with that framework. I am trying and searching for maybe 2 or 3 hours but I can't find a solution.
Waht I want, is to implement a filter or search mechanism. But it is not working, yet...
This is my code:
dojo.require('dojo.store.JsonRest');
dojo.require('dijit.layout.ContentPane');
dojo.require("dijit.form.Button");
dojo.require('dojox.grid.DataGrid');
dojo.require('dojo.data.ObjectStore');
dojo.require('dijit.form.TextBox');
dojo.require('dojox.data.AndOrReadStore');
dojo.require('dojo._base.xhr');
dojo.require('dojo.json')
dojo.require('dojo.domReady');
dojo.ready(
function(){
var appLayout = new dijit.layout.ContentPane({
id: "appLayout"
}, "appLayout");
var textBox = new dijit.form.TextBox({
name: "searchbox",
placeHolder: "Search ..."
});
var filterButton = new dijit.form.Button({
label: 'Filter',
onClick: function () {
searchWord = textBox.get('value');
query = "id: '"+searchWord
+"' OR date_A: '"+searchWord
+"' OR dateB: '"+searchWord
+"' OR product: '"+searchword+"'";
grid.filter({complexQuery: query}, true);
}
});
store = new dojo.store.JsonRest({target:'products/'});
grid = new dojox.grid.DataGrid(
{
store:dojo.data.ObjectStore({objectStore: store}),
structure:
[
{name:'id', field: 'id'},
{name:'date_A', field: 'dateA'},
{name:'date_B', field: 'dateB'},
{name:'product' , field: 'product'},
],
queryOptions: {ignoreCase: true}
});
textBox.placeAt(appLayout.domNode);
filterButton.placeAt(appLayout.domNode);
grid.placeAt(appLayout.domNode);
appLayout.startup();
}
);
Would be very nice if u can tell me what's wrong with this dojo code...
The result is, that the loading icon appears and after a while the unfiltered data is shown... There is no exception thrown.
Thanks in advance.
Ok, I have solved it with the AndOrReadWriteStore. You can also use an AndOrReadStore. The problem was, that the JSON data wasn't in the right format. You can see the right format here: dojotoolkit.org/api/dojox/data/AndOrReadStore. The other change is: I used the url instead the data attribute inside the store. So finally it is working now. Thx anyway.
Here's an example of a filter that uses both an AND and an OR:
grid.filter("(genre: 'Horror' && (fname: '" + searchWord + "' || lname:'" + searchWord + "'))")
So the users search word is filtered across fname and lname as an OR but it also searches for genre = Horror as an AND.
This document has other examples...
http://livedocs.dojotoolkit.org/dojox/data/AndOrReadStore