Kendo TreeView Search with Highlight - javascript

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>

Related

How to make a column value copyable and not editable in Ag-grid - JavaScript

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

Custom number of buttons tinymce

How can I create custom buttons tinymce buttons using JQuery? I would need n "Menu item" buttons. "n" would be defined depending on the selected data before opening the tinymce editor.
My buttons:
editor.addButton('addButtons', {
type: 'menubutton',
text: 'My button',
icon: false,
menu: [
{
text: 'Menu item 1',
onclick: function() {
editor.insertContent(' <strong>item1</strong> ');
}
}, {
text: 'Menu item 2',
onclick: function() {
editor.insertContent(' <strong>item2</strong> ');
}
}, {
text: 'Menu item 3',
onclick: function() {
editor.insertContent(' <strong>item3</strong> ');
}
}
]
});
I can get "n" value from a input type hidden using JQuery $("#totalButtons").val(). If totalButtons is 4, I would need 4 item buttons. Does it make sense? Is it possible to do?
Thanks
Updated Code:
var n = $('#total').val();
var menuItems = [];
tinymce.init({
selector: '#mytextareaTenant',
content_css: 'https://fonts.googleapis.com/css?family=Aguafina+Script|Alex+Brush|Bilbo|Condiment|Great+Vibes|Herr+Von+Muellerhoff|Kristi|Meddon|Monsieur+La+Doulaise|Norican|Nothing+You+Could+Do|Parisienne|Permanent+Marker|Sacramento|Yellowtail',
theme: 'modern',
menubar: false,
plugins: [
"print"
],
setup: function (editor) {
editor.on('init', function (e) {
renderEditorTenant();
for (var i=1; i<=n; i++){
var msg = ' <strong>#item' + i + '#</strong> ';
var obj = {
text: 'Menu item ' + i,
onclick: function() {
editor.insertContent(msg);
}
}
menuItems.push(obj);
}
});
Supposing you have a hidden input like this:
<input type="hidden" id="foo" name="zyx" value="3" />
You can get the value of the input and generate an array with n elements:
var n = $('#foo').val();
var menuItems = [];
for (var i=0; i<n; i++){
var msg = ' <strong>item' + i + '</strong> ';
var obj = {
text: 'Menu item ' + i,
onclick: function() {
editor.insertContent(msg);
}
}
menuItems.push(obj);
}
Now, just pass this array to the function you're using to generate the editor:
editor.addButton('addButtons', {
type: 'menubutton',
text: 'My button',
icon: false,
menu: menuItems
});

prevent nested for loops in javascript

this is my JSON file and i want to create HTML multi level menu without nested loops
[{"text":"Home","href":"http://home.com","icon":"fas fa-home","target":"_top","title":"My Home"},{"text":"Posts","href":"","icon":"fas fa-bell","target":"_self","title":"","children":[{"text":"Sports","href":"","icon":"empty","target":"_self","title":""},{"text":"IT1","href":"","icon":"empty","target":"_self","title":""},{"text":"Web","href":"","icon":"","target":"_self","title":""},{"text":"About","href":"","icon":"fas fa-chart-bar","target":"_self","title":""}]}]
i wrote this but i think its not the best way to do that
thanks for helping
function MenuToHTML(JSON) {
let html = "<ul>";
for (items in JSON) {
html += "<li>";
//console.log(JSON[items].text);
html += JSON[items].text;
if (JSON[items].hasOwnProperty("children")) {
var child = JSON[items].children;
html += "<ul>";
for (subItems in child) {
html += "<li>";
html += child[subItems].text;
if (child[subItems].hasOwnProperty("children")) {
html += "<ul>";
var child = child[subItems].children;
for (SubsubItems in child) {
html += "<li>";
html += child[SubsubItems].text;
html += "</li>";
}
html += "</ul>";
}
html += "</li>";
}
html += "</ul>";
}
html += "</li>";
}
html += "</ul>";
return html;
}
You could build real elements and use a recursion for nested children.
function getMenu(array) {
return array.reduce((ul, { text, href, icon, target, title, children }) => {
var li = document.createElement('li'),
a;
if (href) {
a = document.createElement('a');
a.href = href;
a.target = target;
a.appendChild(document.createTextNode(text));
a.title = title;
li.appendChild(a);
} else {
li.appendChild(document.createTextNode(text));
}
if (children) {
li.appendChild(getMenu(children));
}
ul.appendChild(li);
return ul;
}, document.createElement('ul'));
}
var data = [{ text: "Home", href: "http://home.com", icon: "fas fa-home", target: "_top", title: "My Home" }, { text: "Posts", href: "", icon: "fas fa-bell", target: "_self", title: "", children: [{ text: "Sports", href: "", icon: "empty", target: "_self", title: "" }, { text: "IT1", href: "", icon: "empty", target: "_self", title: "" }, { text: "Web", href: "", icon: "", target: "_self", title: "" }, { text: "About", href: "", icon: "fas fa-chart-bar", target: "_self", title: "" }] }];
document.body.appendChild(getMenu(data));
You donot need to create nested loops like that use recursion and for arrays use for for..of or forEach instead of for..in
let arr = [
{"text":"Home","href":"http://home.com","icon":"fas fa-home","target":"_top","title":"My Home"},
{"text":"Posts","href":"","icon":"fas fa-bell","target":"_self","title":"","children":
[{"text":"Sports","href":"","icon":"empty","target":"_self","title":""},
{"text":"IT1","href":"","icon":"empty","target":"_self","title":""},
{"text":"Web","href":"","icon":"","target":"_self","title":""},
{"text":"About","href":"","icon":"fas fa-chart-bar","target":"_self","title":""}]}
]
/*this function will convert an array to html list. So you can pass the children
array again to the same function and it will to convert children to html list*/
function MenuToHTML(JSON) {
let html = "<ul>";
JSON.forEach(item => {
html += `<li>${item.text}</li>`;
if(item.children){
html += MenuToHTML(item.children);
}
})
return html + '</ul>';
}
document.body.innerHTML = MenuToHTML(arr);

jQuery datatable - unexpected vertical scrollbar

I am trying display data in jQuery datatable but, I am seeing unexpected vertical scrollbar.
Fiddler: https://jsfiddle.net/8f63kmeo/15/
HTML:
<table id="CustomFilterOnTop" class="table table-bordered table-condensed" width="100%"></table>
JS
var Report4Component = (function () {
function Report4Component() {
//contorls
this.customFilterOnTopControl = "CustomFilterOnTop"; //table id
//data table object
this.customFilterOnTopGrid = null;
//variables
this.result = null;
}
Report4Component.prototype.ShowGrid = function () {
var instance = this;
//create the datatable object
instance.customFilterOnTopGrid = $('#' + instance.customFilterOnTopControl).DataTable({
columns: [
{ title: "<input name='SelectOrDeselect' value='1' id='ChkBoxSelectAllOrDeselect' type='checkbox'/>" },
{ data: "Description", title: "Desc" },
{ data: "Status", title: "Status" },
{ data: "Count", title: "Count" }
],
"paging": true,
scrollCollapse: true,
"scrollX": true,
scrollY: "50vh",
deferRender: true,
scroller: true,
dom: '<"top"Bf<"clear">>rt <"bottom"<"Notes">i<"clear">>',
buttons: [
{
text: 'Load All',
action: function (e, dt, node, config) {
instance.ShowData(10000);
}
}
],
columnDefs: [{
orderable: false,
className: 'select-checkbox text-center',
targets: 0,
render: function (data, type, row) {
return '';
}
}],
select: {
style: 'multi',
selector: 'td:first-child',
className: 'selected-row selected'
}
});
};
Report4Component.prototype.ShowData = function (limit) {
if (limit === void 0) { limit = 2; }
var instance = this;
instance.customFilterOnTopGrid.clear(); //latest api function
instance.result = instance.GetData(limit);
instance.customFilterOnTopGrid.rows.add(instance.result.RecordList);
instance.customFilterOnTopGrid.draw();
};
Report4Component.prototype.GetData = function (limit) {
//structure of the response from controller method
var resultObj = {};
resultObj.Total = 0;
resultObj.RecordList = [];
for (var i = 1; i <= limit; i++) {
resultObj.Total += i;
var record = {};
record.Description = "Some test data will be displayed here.This is a test description of record " + i;
record.Status = ["A", "B", "C", "D"][Math.floor(Math.random() * 4)] + 'name text ' + i;
record.Count = i;
resultObj.RecordList.push(record);
}
return resultObj;
};
return Report4Component;
}());
$(function () {
var report4Component = new Report4Component();
report4Component.ShowGrid();
report4Component.ShowData();
});
function StopPropagation(evt) {
if (evt.stopPropagation !== undefined) {
evt.stopPropagation();
}
else {
evt.cancelBubble = true;
}
}
ISSUE:
I am wondering why the vertical scrollbar is appearing and why I am seeing an incorrect count...? Is it because my datatable has rows with multiple lines? As I have already set the scrolly to 50vh, I am expecting all the rows to be displayed.
Note:
The table should support large data too. I have enabled scroller for that purpose as it is required as per the application design. To verify that click on "Load all" button.
Any suggestion / help will be greatly appreciated?
You just need to remove property " scroller: true" it will solve your problem.
For demo please check https://jsfiddle.net/dipakthoke07/8f63kmeo/20/

Group Summary Disappears When Grid is Refreshed/Re-Drawn

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/

Categories