Back again with another ExtJS query. I have a grid with a SummaryGroup feature that is toggle-able (enabled/disabled) from a tool button on the panel header. Enabling once displays it properly, disable then try enable the feature. The grouping happens but the Summery totals of the groups doesn't come back again?
JS fiddle here: http://jsfiddle.net/hD4C4/1/
In the fiddle it will show the group totals then if you disable and re-enable again they disappear?
Here is a picture of pressing the button once:
Here is the same grid after disabling it then re-enabling it again:
Below is the toggle code on the panel header tool button:
xtype: 'tool',
type: 'expand',
tooltip: 'Enable grouping',
handler: function(e, target, panelHeader, tool){
var serviceGridView = Ext.getCmp('serviceOverview').getView('groupingFeature'),
gridFeature = serviceGridView.getFeature('serviceOverviewGroupingFeature');
if (tool.type == 'expand') {
gridFeature.enable();
tool.setType('collapse');
tool.setTooltip('Disable grouping');
} else {
gridFeature.disable();
Ext.getCmp('serviceOverview').setLoading(false,false);
Ext.getCmp('serviceOverview').getStore().reload();
tool.setType('expand');
tool.setTooltip('Enable grouping');
}
}
And here is my grid code (with the feature function at the top:
var groupingFeature = Ext.create('Ext.grid.feature.GroupingSummary', {
groupHeaderTpl: Ext.create('Ext.XTemplate',
'',
'{name:this.formatName} ({rows.length})',
{
formatName: function(name) {
return '<span style="color: #3892d3;">' + name.charAt(0).toUpperCase() + name.slice(1) + '</span>';
}
}
),
hideGroupedHeader: false,
startCollapsed: true,
showSummaryRow: true,
id: 'serviceOverviewGroupingFeature'
});
Ext.define('APP.view.core.grids.Serviceoverview', {
extend: 'Ext.grid.Panel',
alias: 'widget.gridportlet',
height: 'auto',
id: 'serviceOverview',
cls: 'productsGrid',
viewConfig: {
loadMask: true
},
features: [groupingFeature, {ftype: 'summary'}],
initComponent: function(){
var store = Ext.create('APP.store.Serviceoverview');
Ext.apply(this, {
height: this.height,
store: store,
stripeRows: true,
columnLines: true,
columns: [{
id :'service-product',
text : 'Product',
flex: 1,
sortable : true,
dataIndex: 'PACKAGE',
summaryType: function(records) {
if (typeof records[0] !== 'undefined') {
var myGroupName = records[0].get('LISTING');
if (this.isStore) {
return '<span style="font-weight: bold;">Total of all</span>';
}
return '<span style="font-weight: bold;">'+myGroupName.charAt(0).toUpperCase() + myGroupName.slice(1)+' Totals</span>';
//return '<span style="font-weight: bold;">Totals</span>';
}
},
renderer: function(value, metaData ,record) {
return value;
}
},{
id :'service-listing',
text : 'Listing',
flex: 1,
sortable : true,
dataIndex: 'LISTING',
renderer: function(value, metaData ,record){
return value.charAt(0).toUpperCase() + value.slice(1);
}
},{
id :'service-total',
text : 'Running Total',
flex: 1,
sortable : true,
dataIndex: 'TOTAL',
summaryType: function(values) {
var total=0.0;
Ext.Array.forEach(values, function (record){
if (record.data.TOTAL !== null) {
total += parseFloat(record.data.TOTAL);
}
});
return '<span style="font-weight: bold;">£' + numeral(total.toFixed(2)).format('0,0.00') + '</span>';
},
renderer: function(value, metaData ,record){
if (value == null) {
return '£0.00';
}
return '£' + numeral(value).format('0,0.00');
}
},{
id :'service-total-paid',
text : 'Total Paid',
flex: 1,
sortable : true,
dataIndex: 'TOTAL_PAID',
summaryType: function(values) {
var total=0.0;
Ext.Array.forEach(values, function (record){
if (record.data.TOTAL_PAID !== null) {
total += parseFloat(record.data.TOTAL_PAID);
}
});
return '<span style="font-weight: bold;">£' + numeral(total.toFixed(2)).format('0,0.00') + '</span>';
},
renderer: function(value, metaData ,record){
if (value == null) {
return '£0.00';
}
return '£' + numeral(value).format('0,0.00');
}
},{
id :'service-outstanding',
text : 'Outstanding',
flex: 1,
sortable : true,
dataIndex: 'OUTSTANDING',
summaryType: function(values) {
var total=0.0;
Ext.Array.forEach(values, function (record){
if (record.data.OUTSTANDING !== null) {
total += parseFloat(record.data.OUTSTANDING);
}
});
return '<span style="font-weight: bold;">£' + numeral(total.toFixed(2)).format('0,0.00') + '</span>';
},
renderer: function(value, metaData ,record){
if (value == null) {
return '£0.00';
}
return '£' + numeral(value).format('0,0.00');
}
},{
id :'service-properties',
text : 'No. of Clients',
flex: 1,
sortable : true,
dataIndex: 'CLIENTS',
summaryType: function(values) {
var total=0.0;
Ext.Array.forEach(values, function (record){
if (record.data.CLIENTS !== null) {
total += parseFloat(record.data.CLIENTS);
}
});
return '<span style="font-weight: bold;">' + total + '</span>';
}
},{
id :'service-average-total',
text : 'Av. Total',
flex: 1,
sortable : true,
dataIndex: 'AVERAGE_TOTAL',
summaryType: function(values) {
var total=0.0;
Ext.Array.forEach(values, function (record){
if (record.data.AVERAGE_TOTAL !== null) {
total += parseFloat(record.data.AVERAGE_TOTAL);
}
});
return '<span style="font-weight: bold;">£' + numeral(total.toFixed(2)).format('0,0.00') + '</span>';
},
renderer: function(value, metaData ,record){
if (value == null) {
return '£0.00';
}
return '£' + numeral(value).format('0,0.00');
}
},{
id :'service-average-total-paid',
text : 'Av. Total Paid',
flex: 1,
sortable : true,
dataIndex: 'AVERAGE_TOTAL_PAID',
summaryType: function(values) {
var total=0.0;
Ext.Array.forEach(values, function (record){
if (record.data.AVERAGE_TOTAL_PAID !== null) {
total += parseFloat(record.data.AVERAGE_TOTAL_PAID);
}
});
return '<span style="font-weight: bold;">£' + numeral(total.toFixed(2)).format('0,0.00') + '</span>';
},
renderer: function(value, metaData ,record){
if (value == null) {
return '£0.00';
}
return '£' + numeral(value).format('0,0.00');
}
},{
id :'service-average-outstanding',
text : 'Av. Outstanding',
flex: 1,
sortable : true,
dataIndex: 'AVERAGE_OUTSTANDING',
summaryType: function(values) {
var total=0.0;
Ext.Array.forEach(values, function (record){
if (record.data.AVERAGE_OUTSTANDING !== null) {
total += parseFloat(record.data.AVERAGE_OUTSTANDING);
}
});
return '<span style="font-weight: bold;">£' + numeral(total.toFixed(2)).format('0,0.00') + '</span>';
},
renderer: function(value, metaData ,record){
if (value == null) {
return '£0.00';
}
return '£' + numeral(value).format('0,0.00');
}
}]
});
this.callParent(arguments);
}
});
Thank you in advance :)
Nathan
It looks like bug.
I have analyzed code a bit, and I discovered that this issue is caused by generateSummaryData method in feature. In this method you can find this code:
if (hasRemote || store.updating || groupInfo.lastGeneration !== group.generation) {
record = me.populateRecord(group, groupInfo, remoteData);
if (!lockingPartner || (me.view.ownerCt === me.view.ownerCt.ownerLockable.normalGrid)) {
groupInfo.lastGeneration = group.generation;
}
} else {
record = me.getAggregateRecord(group);
}
When grid is firstly rendered first branch is executed for all groups, and after reenabling - second branch. Calling getAggregateRecord instead of populateRecord produces empty summary record. I didn't go any deeper, so for now I can only give you dirty hack to override this (it forces code to enter first branch):
store.updating = true;
feature.enable();
store.updating = false;
JSfiddle: http://jsfiddle.net/P2e7s/6/
After some more digging I've found out that most likely this issue occurs because group.generation is not incremented when populateRecord is called. As a result group.generation always equals to 1, so record is populated only when lastGeneration is empty (first code pass). After re-enabling feature, new groups are created, but they also have generation set to 1.
So I've came up with less dirty hack:
Ext.define('Ext.override.grid.feature.AbstractSummary', {
override: 'Ext.grid.feature.GroupingSummary',
populateRecord: function (group, groupInfo, remoteData) {
++group.generation;
return this.callParent(arguments);
}
});
With that override, you can simply enable feature, and it should work.
JSFiddle: http://jsfiddle.net/P2e7s/9/
Related
I have a javascript grid (ag grid)
var columnDefinitions = [
{
headerName: 'Item Number',
field: 'ItemNumber',
width: 140,
editable: editable && status !== 'Open',
cellClass: 'ag-autocomplete',
cellEditor:Grids.CellEditors.ItemEditor({
updateCallback: function (rowData, selectedItem) {
rowData.ItemId = selectedItem.Id;
rowData.Description = selectedItem.Description;
},
getInitialFilters: function () {
return [
{ Identifier: "VId", Values: [$("#Id").val()] }
];
},
searchDefinition: 'InvItems.json',
autocompleteSearchDefinition: 'InvDetail.json'
})
},
.....
{
headerName: 'Tracking Number',
field: 'TrackingNumber',
width: 120,
cellRenderer: function (params) {
if (params.data.TrackingNumber != null) {
var url;
if (params.data.Carrier == 'UPS') {
url = 'https://wwwapps.ups.com/tracking/tracking.cgi?tracknum=';
}
if (params.data.Carrier == 'USPS') {
url = 'https://tools.usps.com/go/TrackConfirmAction.action?tLabels=';
}
return "<a target='_blank' href='" + url
+ params.value
+ "'>" + params.value + "</a>";
}
else {
return '';
}
}
},
I want to make the column "TrackingNumber" copyable. I don't want to make it editable. anything I try that make it like a textbox and I can copy the value I can edit it too that I don't want that.
Add enableCellTextSelection: true to your grid options. Docs here
The sorting function will no longer work on column that is using itemTemplate and headerTemplate.
You can see a fiddle from here.
As you can see, in the column "Client ID", the sorting works really well. But on column "Client Name", the sorting doesn't work as I am using itemTemplate and headerTemplate for customization.
Any workaround is really appreciated.
Here's the code:
$("#jsGrid").jsGrid({
width: "100%",
sorting: true,
paging: true,
data: [{
ClientId: 1,
Client: "Aaaa Joseph"
},
{
ClientId: 2,
Client: "Zzzz Brad"
},
{
ClientId: 3,
Client: "Mr Nice Guy"
}
],
fields: [{
width: 80,
name: "ClientId",
type: "text",
title: "Client ID"
},
{
width: 80,
itemTemplate: function(value, item) {
return "<div>" + item.Client + "</div>";
},
headerTemplate: function() {
return "<th class='jsgrid-header-cell'>Client Name</th>";
}
},
]
});
In jsgrid name is a property of data item associated with the column. And on header click this _sortData function will call in jsgrid.js for sorting data. And this name config will use here. So for this you have to provide this config other it will blank and no data sorting on header click.
Please search this below function in jsgrid.js
_sortData: function() {
var sortFactor = this._sortFactor(),
sortField = this._sortField;
if (sortField) {
this.data.sort(function(item1, item2) {
return sortFactor * sortField.sortingFunc(item1[sortField.name], item2[sortField.name]);
});
}
},
In above code sortField.name as column config and it is must mandatory.
DEMO
$("#jsGrid").jsGrid({
width: "100%",
sorting: true,
paging: true,
data: [
{ ClientId : 1, Client: "Aaaa Joseph"},
{ ClientId : 2, Client: "Zzzz Brad"},
{ ClientId : 3, Client: "Mr Nice Guy"}
],
fields: [
{
width: 80,
name: "ClientId",
type: "text",
title: "Client ID"
},
{
width: 80,
name:'Client',
itemTemplate: function (value, item) {
return "<div>"+item.Client+"</div>";
},
headerTemplate: function () {
return "<th class='jsgrid-header-cell'>Client Name</th>";
}
},
]
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.css" />
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid-theme.min.css" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.js"></script>
<div id="jsGrid"></div>
Another way you can make manually sorting on header click.
Way to sort JS grid on column after grid load :
onRefreshing: function (args) {
fundCodeList = [];
jsonNumLst = [];
jsonNANLst = [];
if(this._visibleFieldIndex(this._sortField) == -1
|| this._visibleFieldIndex(this._sortField)==1){
$.each(filteredData, function(inx, obj) {
if($.isNumeric(obj.fundCode)){
jsonNumLst.push(obj);
}else{
jsonNANLst.push(obj);
}
});
if(this._sortOrder == undefined || this._sortOrder == 'asc'){
jsonNumLst.sort(sortByNumFCAsc);
jsonNANLst.sort(sortByNANFCAsc);
}else if(this._sortOrder == 'desc'){
jsonNANLst.sort(sortByNANFCDesc);
jsonNumLst.sort(sortByNumFCDesc);
}
if(jsonNumLst.length>0 || jsonNANLst.length>0){
filteredData = [];
if(this._sortOrder == undefined || this._sortOrder == 'asc'){
$.each(jsonNumLst, function(inx, obj) {
filteredData.push(obj);
});
if(filteredData.length == jsonNumLst.length){
$.each(jsonNANLst, function(inx, obj) {
filteredData.push(obj);
});
}
}else if(this._sortOrder == 'desc'){
$.each(jsonNANLst, function(inx, obj) {
filteredData.push(obj);
});
if(filteredData.length == jsonNANLst.length){
$.each(jsonNumLst, function(inx, obj) {
filteredData.push(obj);
});
}
}
}
if((filteredData.length>0) && filteredData.length==(jsonNumLst.length+jsonNANLst.length)){
$("#measureImportGrid3").data("JSGrid").data = filteredData;
//isSortGrid = false;
//saveEffectControlData = $('#saveEffectiveControlGrid').jsGrid('option', 'data');
}
}
}
//Ascending order numeric
function sortByNumFCAsc(x,y) {
return x.fundCode - y.fundCode;
}
//Ascending order nonnumeric
function sortByNANFCAsc(x,y) {
return ((x.fundCode == y.fundCode) ? 0 : ((x.fundCode > y.fundCode) ? 1 : -1 ));
}
//Descending order numeric
function sortByNANFCDesc(x,y) {
return ((x.fundCode == y.fundCode) ? 0 : ((y.fundCode > x.fundCode) ? 1 : -1 ));
}
//Descending order non-numeric
function sortByNumFCDesc(x,y) {
return y.fundCode - x.fundCode;
}
I have a KendoTreeview with spriteclass. I want to highlight the nodes (root as well as child nodes) with my search term. I have implemented the search functionality. But the issue when i search it is highlighting the term in the nodes but missing the SpriteClass in the nodes after first search. Any idea ?
jsFiddle code
$('#search-term').on('keyup', function () {
$('span.k-in > span.highlight').each(function () {
$(this).parent().text($(this).parent().text());
});
// ignore if no search term
if ($.trim($(this).val()) == '') {
return;
}
var term = this.value.toUpperCase();
var tlen = term.length;
$('#treeview-sprites span.k-in').each(function (index) {
var text = $(this).text();
var html = '';
var q = 0;
while ((p = text.toUpperCase().indexOf(term, q)) >= 0) {
html += text.substring(q, p) + '<span class="highlight">' + text.substr(p, tlen) + '</span>';
q = p + tlen;
}
if (q > 0) {
html += text.substring(q);
$(this).html(html);
$(this).parentsUntil('.k-treeview').filter('.k-item').each(
function (index, element) {
$('#treeview-sprites').data('kendoTreeView').expand($(this));
$(this).data('search-term', term);
});
}
});
$("#treeview-sprites").kendoTreeView({
dataSource: [{
text: "My Documents",
expanded: true,
spriteCssClass: "rootfolder",
items: [{
text: "Kendo UI Project",
expanded: true,
spriteCssClass: "folder",
items: [{
text: "about.html",
spriteCssClass: "html"
}, {
text: "index.html",
spriteCssClass: "html"
}, {
text: "logo.png",
spriteCssClass: "image"
}]
}, {
text: "New Web Site",
expanded: true,
spriteCssClass: "folder",
items: [{
text: "mockup.jpg",
spriteCssClass: "image"
}, {
text: "Research.pdf",
spriteCssClass: "pdf"
}, ]
}, {
text: "Reports",
expanded: true,
spriteCssClass: "folder",
items: [{
text: "February.pdf",
spriteCssClass: "pdf"
}, {
text: "March.pdf",
spriteCssClass: "pdf"
}, {
text: "April.pdf",
spriteCssClass: "pdf"
}]
}]
}]
})
;
Kendo's tree view widget doesn't like it if you muck around in its HTML, so I suggest modifying the data source instead (this will require the encoded option for all items in the DS).
In the keyup handler, you reset the DS whenever you search to clear highlighting, then instead of replacing the element's HTML directly, you set the model's text property:
$('#search-term').on('keyup', function () {
var treeView = $("#treeview-sprites").getKendoTreeView();
treeView.dataSource.data(pristine);
// ignore if no search term
if ($.trim($(this).val()) == '') {
return;
}
var term = this.value.toUpperCase();
var tlen = term.length;
$('#treeview-sprites span.k-in').each(function (index) {
var text = $(this).text();
var html = '';
var q = 0;
while ((p = text.toUpperCase().indexOf(term, q)) >= 0) {
html += text.substring(q, p) + '<span class="highlight">' + text.substr(p, tlen) + '</span>';
q = p + tlen;
}
if (q > 0) {
html += text.substring(q);
var dataItem = treeView.dataItem($(this));
dataItem.set("text", html);
$(this).parentsUntil('.k-treeview').filter('.k-item').each(
function (index, element) {
$('#treeview-sprites').data('kendoTreeView').expand($(this));
$(this).data('search-term', term);
});
}
});
$('#treeview-sprites .k-item').each(function () {
if ($(this).data('search-term') != term) {
$('#treeview-sprites').data('kendoTreeView').collapse($(this));
}
});
});
The tree definition needs the encoded option for this to work:
var pristine = [{
encoded: false,
text: "Kendo UI Project",
expanded: true,
spriteCssClass: "folder",
items: [{
encoded: false,
text: "about.html",
spriteCssClass: "html"
}, {
encoded: false,
text: "index.html",
spriteCssClass: "html"
}, {
encoded: false,
text: "logo.png",
spriteCssClass: "image"
}]
}, {
encoded: false,
text: "New Web Site",
expanded: true,
spriteCssClass: "folder",
items: [{
encoded: false,
text: "mockup.jpg",
spriteCssClass: "image"
}, {
encoded: false,
text: "Research.pdf",
spriteCssClass: "pdf"
}, ]
}, {
encoded: false,
text: "Reports",
expanded: true,
spriteCssClass: "folder",
items: [{
encoded: false,
text: "February.pdf",
spriteCssClass: "pdf"
}, {
encoded: false,
text: "March.pdf",
spriteCssClass: "pdf"
}, {
encoded: false,
text: "April.pdf",
spriteCssClass: "pdf"
}]
}];
$("#treeview-sprites").kendoTreeView({
dataSource: [{
text: "My Documents",
expanded: true,
spriteCssClass: "rootfolder",
items: pristine
}]
});
(demo)
Good job guys, just what I neeeded!
Using your code I did a small tweak (actually added just two lines of jquery filtering), so that now when searching for a keyword, the treeview shows only the branches that contain highlighted texts. Easy peasy! :)
Other branches are hidden if they do not contain the higlighted text. Simple as that.
This means we now have a VisualStudio-like treeview search (see the Visual Studio Solution Explorer Search and Filter: http://goo.gl/qr7yVb).
Here's my code and demo on jsfiddle: http://jsfiddle.net/ComboFusion/d0qespaz/2/
HTML:
<input id="treeViewSearchInput"></input>
<ul id="treeview">
<li data-expanded="true">My Web Site
<ul>
<li data-expanded="true">images
<ul>
<li>logo.png</li>
<li>body-back.png</li>
<li>my-photo.jpg</li>
</ul>
</li>
<li data-expanded="true">resources
<ul>
<li data-expanded="true">pdf
<ul>
<li>brochure.pdf</li>
<li>prices.pdf</li>
</ul>
</li>
<li>zip</li>
</ul>
</li>
<li>about.html</li>
<li>contacts.html</li>
<li>index.html</li>
<li>portfolio.html</li>
</ul>
</li>
<li>Another Root</li>
</ul>
CSS
span.k-in > span.highlight {
background: #7EA700;
color: #ffffff;
border: 1px solid green;
padding: 1px;
}
JAVASCRIPT
function InitSearch(treeViewId, searchInputId) {
var tv = $(treeViewId).data('kendoTreeView');
$(searchInputId).on('keyup', function () {
$(treeViewId + ' li.k-item').show();
$('span.k-in > span.highlight').each(function () {
$(this).parent().text($(this).parent().text());
});
// ignore if no search term
if ($.trim($(this).val()) === '') {
return;
}
var term = this.value.toUpperCase();
var tlen = term.length;
$(treeViewId + ' span.k-in').each(function (index) {
var text = $(this).text();
var html = '';
var q = 0;
var p;
while ((p = text.toUpperCase().indexOf(term, q)) >= 0) {
html += text.substring(q, p) + '<span class="highlight">' + text.substr(p, tlen) + '</span>';
q = p + tlen;
}
if (q > 0) {
html += text.substring(q);
$(this).html(html);
$(this).parentsUntil('.k-treeview').filter('.k-item').each(function (index, element) {
tv.expand($(this));
$(this).data('SearchTerm', term);
});
}
});
$(treeViewId + ' li.k-item:not(:has(".highlight"))').hide();
$(treeViewId + ' li.k-item').expand(".k-item");
});
}
var $tv = $("#treeview").kendoTreeView();
InitSearch("#treeview", "#treeViewSearchInput");
Another tweak from me :)
What I did was change the highlight code in order to preserve anything else that might exist in the node html (such as sprite span for example).
I also implemented it as a TypeScript class wrapper around the TreeView.
If you don't want TypeScript stuff just copy the code out and it should work fine :)
export class SearchableTreeView {
TreeView: kendo.ui.TreeView;
emphasisClass: string;
constructor(treeView: kendo.ui.TreeView) {
this.TreeView = treeView;
this.emphasisClass = "bg-warning";
}
search(term: string): void {
var treeElement: JQuery = this.TreeView.element;
var tv: kendo.ui.TreeView = this.TreeView;
var emphClass = this.emphasisClass;
this.resetHighlights();
// ignore if no search term
if ($.trim(term) === '') { return; }
var term = term.toUpperCase();
var tlen = term.length;
$('span.k-in', treeElement).each(function (index) {
// find all corresponding nodes
var node = $(this);
var htmlContent = node.html();
var text = node.text();
var searchPosition = text.toUpperCase().indexOf(term);
if (searchPosition === -1) {
// continue
return true;
}
var generatedHtml = '<span class="highlight-container">' + text.substr(0, searchPosition) + '<span class="' + emphClass + '">' + text.substr(searchPosition, tlen) + '</span>' + text.substr(searchPosition + tlen) + '</span>';
htmlContent = htmlContent.replace(text, generatedHtml);
node.html(htmlContent);
node.parentsUntil('.k-treeview').filter('.k-item').each(
function (index, element) {
tv.expand($(this));
$(this).data('search-term', term);
}
);
});
$('.k-item', treeElement).each(function () {
if ($(this).data('search-term') != term) {
tv.collapse($(this));
}
});
}
resetHighlights(): void {
this.TreeView.element.find("span.k-in:has('." + this.emphasisClass + "')")
.each(function () {
var node = $(this);
var text = node.text();
$(".highlight-container", node).remove();
node.append(text);
});
}
}
$("#textBox").on("input", function () {
var query = this.value.toLowerCase();
var dataSource = $("#Treeview").data("kendoTreeView").dataSource;
filter(dataSource, query);
});
function filter(dataSource, query) {
var uidData = [];
var data = dataSource instanceof kendo.data.DataSource && dataSource.data();
for (var i = 0; i < data.length; i++) {
var item = data[i];
var text = item.text.toLowerCase();
var isChecked = item.checked;
var itemVisible =
query === true
|| query === ""
|| text.indexOf(query) >= 0;
uidData.push({ UID: item.uid, Visible: itemVisible });
}
if (query != "") {
$.each(uidData, function (index, datavalue) {
if (datavalue.Visible) {
$("li[data-uid='" + datavalue.UID + "']").addClass("highlight");
}
else {
$("li[data-uid='" + datavalue.UID + "']").removeClass("highlight");
}
});
}
else {
$.each(uidData, function (index, datavalue) {
$("li[data-uid='" + datavalue.UID + "']").removeClass("highlight");
});
}
}
CSS :
.highlight {
background:#0fa1ba;
color:white;
}
For Angular 2+ you need to create a pipe for this feature.
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
import { NGXLogger } from 'ngx-logger';
#Pipe({
name: 'highlight'
})
export class TypeaheadHighlight implements PipeTransform {
constructor(private readonly _sanitizer: DomSanitizer, private readonly logger: NGXLogger) { }
transform(matchItem: any, query: any): string {
let matchedItem: any;
if (matchItem) {
matchedItem = matchItem.toString();
}
if (this.containsHtml(matchedItem)) {
this.logger.warn('Unsafe use of typeahead please use ngSanitize');
}
matchedItem = query ? ('' + matchedItem).replace(new RegExp(this.escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchedItem; // Replaces the capture string with a the same string inside of a "strong" tag
if (!this._sanitizer) {
matchedItem = this._sanitizer.bypassSecurityTrustHtml(matchedItem);
}
return matchedItem;
}
escapeRegexp = (queryToEscape) => queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
containsHtml = (matchItem) => /<.*>/g.test(matchItem);
}
Use of this pipe in html template....
<input name="searchTerm" type="text" [(ngModel)]="searchTerm" (keyup)='onkeyup(searchTerm)'
/>
</div>
<div style="height: 70vh;overflow: auto">
<kendo-treeview style="margin-top: 50px" id="availableColumns" [nodes]="availableColumns"
textField="displayName" kendoTreeViewExpandable kendoTreeViewFlatDataBinding idField="id"
parentIdField="parentId">
<ng-template kendoTreeViewNodeTemplate let-dataItem>
<span [tooltip]="dataItem.columnDescription"
[innerHtml]="dataItem.displayName | highlight:searchTerm "></span>
</ng-template>
</kendo-treeview>
</div>
I have a fine working jquery multicolumn autocomplete. Now i have to add a column which should be hidden. Basically its a ID of the values. So when the user selects the value i could able to get the ID of the selected row.
//Code:
<script type="text/javascript">
var autocompleteSource;
var colValues = [];
var columns = [{ name: 'Workflow Name', width: '200px' }, { name: 'Workflow Category', width: '150px' }, { name: 'Status', width: '100px' }, { name: 'Workflow Owner', width: '150px' }];
$.ajax({
url: "/Home/LoadWorkflowDropdown",
datatype: 'json',
mtype: 'GET',
success: OnComplete,
error: OnFail
});
function OnComplete(result) {
autocompleteSource = $.parseJSON(result)
$.each(autocompleteSource, function () {
colValues.push([this.WorkflowName, this.WorkflowCategory, this.StatusName, this.UserName]);
});
$.widget('custom.mcautocomplete', $.ui.autocomplete, {
_renderMenu: function (ul, items) {
var self = this,
thead;
if (this.options.showHeader) {
table = $('<div class="ui-widget-header" style="width:100%"></div>');
$.each(this.options.columns, function (index, item) {
table.append('<span style="padding:0 4px;float:left;width:' + item.width + ';">' + item.name + '</span>');
});
table.append('<div style="clear: both;"></div>');
ul.append(table);
}
$.each(items, function (index, item) {
self._renderItem(ul, item);
});
},
_renderItem: function (ul, item) {
var t = '',
result = '';
$.each(this.options.columns, function (index, column) {
t += '<span style="padding:0 4px;float:left;width:' + column.width + ';">' + item[column.valueField ? column.valueField : index] + '</span>'
});
result = $('<li></li>').data('item.autocomplete', item).append('<a class="mcacAnchor">' + t + '<div style="clear: both;"></div></a>').appendTo(ul);
return result;
}
});
$("#search").mcautocomplete({
showHeader: true,
columns: columns,
source: colValues,
select: function (event, ui) {
this.value = (ui.item ? ui.item[0] : '');
return false;
}
});
}
</script>
Working Fiddle here
Here i have modified js code to add unique id to each record and to get that value when user selects a particular option from the auto-suggest list. Fiddle
HTML: Create a hidden field to store the id of selected option
<input type="hidden" name="selectedId" id="selectedId" />
JS: Added ids in the array and retrieved those ids in select function by index value.
var columns = [{
name: 'Color',
width: '100px'},
{
name: 'Hex',
width: '70px'}],
colors = [['Red', '#f00', '1'], ['Green', '#0f0', '2'], ['Blue', '#00f', '3']];
$("#search").mcautocomplete({
showHeader: true,
columns: columns,
source: colors,
select: function(event, ui) {
$('#selectedId').val(ui.item[2]);
this.value = (ui.item ? ui.item[0] : '');
return false;
}
});
I use multi-row grouping and put the totals in the grouping headers.
I'm not using the totals in the totals rows. I see the the rows are grouped and where the totals would be there are empty rows. In my case there is an empty row after each of the child grouping and at the end there is an empty row for the parent totals.
How do I remove these totals rows?
Thank you!
.cshtml:
<div id="gridList" class="grid" style="width: 100%; height: 500px"></div>
.js
$(function() {
var columns = [
{
id: "isExcluded",
name: "Exclude",
field: "isExcluded" /*, width: 120*/,
formatter: Slick.Formatters.Checkmark,
editor: Slick.Editors.Checkbox, sortable: true
},
{
id: "symbol",
name: "Symbol",
field: "symbol",
sortable: true /*, width: 120*/
},
{
id: "price",
name: "Price",
field: "price",
sortable: true
//, groupTotalsFormatter: sumTotalsFormatter
},
{
id: "parent_name",
name: "Parent Name",
field: "parent_name",
sortable: true /*, width: 120*/
},
{
id: "description",
name: "Description",
field: "description",
sortable: true,
width: 120,
editor: Slick.Editors.Text,
},
{ id: "cancel_ind",
name: "Canceled",
field: "cancel_ind",
sortable: true, width: 80 }
];
function requiredFieldValidator(value) {
if (value == null || value == undefined || !value.length) {
return { valid: false, msg: "This is a required field" };
} else {
return { valid: true, msg: null };
}
};
var options = {
editable: true,
enableAddRow: true,
enableCellNavigation: true,
asyncEditorLoading: false,
autoEdit: true,
enableExpandCollapse: true,
rowHeight: 25
};
var sortcol = "parent_name";
var sortdir = 1;
var grid;
var data = [];
var groupItemMetadataProviderTrades = new Slick.Data.GroupItemMetadataProvider();
var dataView = new Slick.Data.DataView({ groupItemMetadataProvider: groupItemMetadataProviderTrades });
dataView.onRowCountChanged.subscribe(function (e, args) {
grid.updateRowCount();
grid.render();
});
dataView.onRowsChanged.subscribe(function (e, args) {
grid.invalidateRows(args.rows);
grid.render();
});
function groupByParentAndSymbol() {
dataViewTrades.setGrouping([
{
getter: "parent_name",
formatter: function(g) {
return "Parent: " + g.value + " <span style='color:green'>(" + g.count + " items) Total: " + g.totals.sum.price + "</span>";
},
aggregators: [
new Slick.Data.Aggregators.Sum("price")
],
aggregateCollapsed: true
,lazyTotalsCalculation: true
},
{
getter: "symbol",
formatter: function(g) {
return "Symbol: " + g.value + " <span style='color:green'>(" + g.count + " items) Total: " + g.totals.sum.price + "</span>";
},
aggregators: [
new Slick.Data.Aggregators.Sum("price")
],
collapsed: true
,lazyTotalsCalculation: true
}]);
};
grid = new Slick.Grid("#gridList", dataView, columns, options);
grid.registerPlugin(groupItemMetadataProviderTrades);
grid.setSelectionModel(new Slick.RowSelectionModel());
..... /*sorting support etc*/
// use instead of the default formatter <--- removed not used.
function sumTotalsFormatter(totals, columnDef) {
var val = totals.sum && totals.sum[columnDef.field];
//if (val != null) {
// return "total: " + ((Math.round(parseFloat(val) * 100) / 100));
//}
return "";
}
// will be called on a button click (I didn't include the code as irrelevant)
var getDataList = function () {
$.getJSON('/Home/GetData/', function (json) {
data = json;
dataView.beginUpdate();
dataView.setItems(data);
groupByParentAndSymbol();
dataView.endUpdate();
dataView.syncGridSelection(grid, true);
});
};
getDataList();
});
Adding displayTOtalsRow: false to the dataview solved my problem - the total rows are not shown now.
var dataView = new Slick.Data.DataView({ groupItemMetadataProvider: groupItemMetadataProviderTrades, displayTotalsRow: false });
To answer simply to your question... Just remove the aggregators: [...], when I say remove you have 2 options, you can remove whatever is the array [...] or you could simply erase completely that object line (so removing completely the aggregators[...]).
Now if you want more explanation of how it works...Let's give you some definition so you'll understand better. An aggregate is a collection of items that are gathered together to form a total quantity. The total in question could be a sum of all the fields, an average or other type of aggregator you might define. So basically it's the action of regrouping by the column you chose and give the calculation result of the group. Alright now how does it work in the case of a SlickGrid? You need to define what kind of Aggregator you want to use (Avg, Sum, etc...) that goes inside your function groupByParentAndSymbol(), defining them will do the calculation BUT unless you attach it to the field, nothing will be displayed, so in the example you found it is very important to attach/bind this groupTotalsFormatter: sumTotalsFormatter to your columns definition, as for example:
...var columns = [{id: "cost", ...width: 90, groupTotalsFormatter: sumTotalsFormatter}, ...];
so let's recap... Once the Aggregator is define new Slick.Data.Aggregators.Sum("cost") it will do the calculation but unless it's bind to the field nothing will be displayed. As an extra option, the aggregateCollapsed (true/false) is to display the sub-total (avg, sum or whatever) inside or outside the group when you collapse.