Conditionally formatting kendo grid column issue with if statement in template - kendo-grid

I am running into an issue trying to add a percentage sign to my value in one of my columns in the kendo grid using a template, what I am using is..
template: "#if(Markup != null){ #=kendo.format('{0:p}', Markup / 100)# }#"
So if the Markup value is not null then I want it to show the percentage sign, but when I run the grid all I get returning is my column is
=kendo.format('{0:p}', Markup / 100)

In this case, I like to use template as a function:
template: function(item) {
if(item.markup) {
return kendo.format('{0:p0}', item.markup / 100);
}
return item.name;
}
Simple example: template as a function
Or you can use it your way:
template: "#= data.markup ? kendo.format('{0:p}', data.markup / 100): 'N/A' #"
Dojo: inline template
NOTE:
"#if(Markup != null){ #=kendo.format('{0:p}', Markup / 100)# }#"
\--- this hash closes script,
everithing after that is string.
Thats why you see
=kendo.format('{0:p}', Markup / 100) in grid

Related

Svelte virtual list component - function not working after filtering list

I am using the virtuallist component in a svelte project. I have added filtering to the list. My issue is that a function in my project stops working when I filter the list, I'm assuming because the list item is not yet in the dom when filtered?
The project converts medical units from metric units to international units using two inputs. Changing one input automatically converts the other.
Before filtering, everything works well with conversion but after entering a item name, (e.g. Type Zinc), the input conversion fails in the filtered items. No conversion occurs.
I've looked into afterUpdate as an option but not sure how to implement it.
---------Added Info -------------------
The issue is with list items not yet in view. Try typing "zinc" and then changing the input values of Zinc (fails) vs typing Acetone (item already in view) and changing those inputs (it works).
Here is a working REPL
The script:
<script>
import VirtualList from './VirtualList.svelte';
import unitsH from './data.js';
let searchTerm = "";
let start;
let end;
$: filteredList = unitsH.filter(item => item.name.toLowerCase().indexOf(searchTerm) !== -1);
function setBothFromSIH(value, i) {
const {factor, siValue} = unitsH[i];
unitsH[i].siValue = +value;
unitsH[i].usValue = +(value / factor).toFixed(2);
}
function setBothFromUSH(value, i) {
const {factor, usValue} = unitsH[i];
unitsH[i].usValue = +value;
unitsH[i].siValue = +(value * factor).toFixed(2);
}
</script>
With simplified html code:
<VirtualList items={filteredList} bind:start bind:end let:item >
<div class="border" style="overflow-x: scroll;"> <div><div>
<div class="name">{item.name}</div>
<span>Specimen: {item.specimen} </span>
<span> Conversion Factor: {item.factor} </span>
</div>
<div>
<label>US Range:{item.conventionalRange} {item.conventionalUnit}</label>
<input name="us{filteredList.indexOf(item)}" value={item.usValue} on:input="{e => setBothFromUSH(e.target.value, filteredList.indexOf(item))}" type=number placeholder=" US">
</div>
<div>
<label>SI Range: {item.siRange} {item.siUnit}</label>
<input name="si{filteredList.indexOf(item)}" value={item.siValue} on:input="{e => setBothFromSIH(e.target.value, filteredList.indexOf(item))}" type=number placeholder="SI">
</div></div> </div>
</VirtualList>
<p>showing items {start}-{end}</p>
Thanks for any help in getting this to work!
It's a small issue with your filter. You convert the product name to lower case but not the filter term ;) If you enter acetone instead of Acetone, then it works. The fix:
$: filteredList = unitsH.filter(item => item.name.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1);
Edit:
The issue with not calling the function for some filtered element is that you display the filteredList but still do the lookup on the unitsH list. Change it to this and it works:
function setBothFromSIH(value, i) {
const {factor, siValue} = filteredList[i];
filteredList[i].siValue = +value;
filteredList[i].usValue = +(value / factor).toFixed(2);
}
function setBothFromUSH(value, i) {
const {factor, usValue} = filteredList[i];
filteredList[i].usValue = +value;
filteredList[i].siValue = +(value * factor).toFixed(2);
}
Happy hacking!
Your problem is caused by using the wrong index, in the change handler you pass the index of the item in filteredIndex but then you use that one to change the item on that index in the array unitsH.
You can see that by:
- start anew
- note the value for Acetaminophen (index 0)
- search zinc
- change value of zinc (index 0 in filtered list)
- clear search
->> acetaminophen has changed because that is index 0 in unitsH
You can easily solve this by passing in the index of the original array instead:
<input name="si{filteredList.indexOf(item)}" value={item.siValue} on:input="{e => setBothFromSIH(e.target.value, unitsH.indexOf(item))}" type=number placeholder="SI">
However, if you move the markup for each item to a seperate component you can vastly simplify this by directly interacting with the properties instead of trying to change them in the array.

Pagination on Angular Material Design - Show page numbers or remove the row count

Angular 6/7, Material Design.
Since I don't have access to the total number of items the item count is irrelevant (the box in the screen shot).
How do I remove the item count completely? Or alternatively show the page I'm currently on instead of the item count?
<mat-paginator
itemsPerPageLabel="Items per page"
(page)="changePage()"
[length]="resultsLength"
[pageSizeOptions]="[10, 100]">
</mat-paginator>
Remove the range label by inserting in global CSS
.mat-paginator-range-label {
display: none;
}
Insert page number instead (of course based on your API - you might not have the page info!) by inserting in your component
ngAfterViewChecked() {
const list = document.getElementsByClassName('mat-paginator-range-label');
list[0].innerHTML = 'Page: ' + this.page.toString();
}
and of course delete the CSS rule above!
Paginator now looks like this
I just modified Johan Faerch's solution to fit more to your question.
Create method which has two parameters, one for matpaginator and another for list of HTMLCollectionOf
paginatorList: HTMLCollectionOf<Element>;
onPaginateChange(paginator: MatPaginator, list: HTMLCollectionOf<Element>) {
setTimeout((idx) => {
let from = (paginator.pageSize * paginator.pageIndex) + 1;
let to = (paginator.length < paginator.pageSize * (paginator.pageIndex + 1))
? paginator.length
: paginator.pageSize * (paginator.pageIndex + 1);
let toFrom = (paginator.length == 0) ? 0 : `${from} - ${to}`;
let pageNumber = (paginator.length == 0) ? `0 of 0` : `${paginator.pageIndex + 1} of ${paginator.getNumberOfPages()}`;
let rows = `Page ${pageNumber} (${toFrom} of ${paginator.length})`;
if (list.length >= 1)
list[0].innerHTML = rows;
}, 0, paginator.pageIndex);
}
How to call this method? you can initialize this on ngAfterViewInit()
ngAfterViewInit(): void {
this.paginatorList = document.getElementsByClassName('mat-paginator-range-label');
this.onPaginateChange(this.paginator, this.paginatorList);
this.paginator.page.subscribe(() => { // this is page change event
onPaginateChange(this.paginator, this.paginatorList);
});
}
Include this method in your css file(note: do not include in the main styles.css file)
.mat-paginator-range-label {
display: none;
}
You can call onPaginateChange(this.paginator, this.paginatorList) functions wherever you need to change the page number details other than clicking on the navigation buttons on the mat paginator.
Result looks like this

Customize html output for a field's droplist options

I have a field called icon, which is a droplist sourced from folder in the content tree. I would like the list to not just show the text value(shown in the screen shot) but also to utilize an icon font and display what the actual icon would look like. Basically customizing the content editor's droplist for this field from:
<option value="gears">gears</option>
to
<option value="gears">gears <span class="my-icon-font-gears"></span></option>
Is there any documentation on how to modify the outputted html for a droplist, and to modify the content editor page to load another link, in this case a font-file.
I created a module on the marketplace that does something similar. You can have a look here. There is some documentation on there explaining how to use it.
The code is also on Git if you want to have a look.
Suggest you use the Droplink field type instead of the Droplist, since the value is stored by GUID and this will lead to less longer term problems if the link item is renamed or moved. In any case you need a custom field, inherit from Sitecore.Shell.Applications.ContentEditor.LookupEx (which is the DropLink field type) and override the DoRender() method with the custom markup you require.
It's not possible to embed a span tag since the option tag cannot contain other tags as it is invalid HTML. Adding it will cause the browser to strip it out. You can however set the class on the option itself and style that.
`<option value="gears" style="my-icon-font-gears">gears</option>`
Here is some sample code to achieve the field.
using System;
using System.Web.UI;
using Sitecore;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
namespace MyProject.CMS.Custom.Controls
{
public class StyledLookupEx : Sitecore.Shell.Applications.ContentEditor.LookupEx
{
private string _styleClassField;
private string StyleClassField
{
get
{
if (String.IsNullOrEmpty(_styleClassField))
_styleClassField = StringUtil.ExtractParameter("StyleClassField", this.Source).Trim();
return _styleClassField;
}
}
// This method is copied pasted from the base class apart from thhe single lined marked below
protected override void DoRender(HtmlTextWriter output)
{
Assert.ArgumentNotNull(output, "output");
Item[] items = this.GetItems(Sitecore.Context.ContentDatabase.GetItem(this.ItemID, Language.Parse(this.ItemLanguage)));
output.Write("<select" + this.GetControlAttributes() + ">");
output.Write("<option value=\"\"></option>");
bool flag1 = false;
foreach (Item obj in items)
{
string itemHeader = this.GetItemHeader(obj);
bool flag2 = this.IsSelected(obj);
if (flag2)
flag1 = true;
/* Option markup modified with class added */
output.Write("<option value=\"" + this.GetItemValue(obj) + "\"" + (flag2 ? " selected=\"selected\"" : string.Empty) + " class=\"" + obj[StyleClassField] + "\" >" + itemHeader + "</option>");
}
bool flag3 = !string.IsNullOrEmpty(this.Value) && !flag1;
if (flag3)
{
output.Write("<optgroup label=\"" + Translate.Text("Value not in the selection list.") + "\">");
output.Write("<option value=\"" + this.Value + "\" selected=\"selected\">" + this.Value + "</option>");
output.Write("</optgroup>");
}
output.Write("</select>");
if (!flag3)
return;
output.Write("<div style=\"color:#999999;padding:2px 0px 0px 0px\">{0}</div>", Translate.Text("The field contains a value that is not in the selection list."));
}
}
}
This field adds a custom properties to allow you to specify the linked field to use for the style class. The assumption is that you have another single line text field on the linked item to specify the CSS class.
Usage: Set the source property of the field in the following format:
Datasource={path-or-guid-to-options}&StyleClassField={fieldname}
e.g. Datasource=/sitecore/content/lookup/iconfonts&StyleClassField=IconClassName
To use this new field compile the above code in to project, switch over to the core database and then create a new field type – you can duplicate the existing Droplink field located in /sitecore/system/Field types/Link Types/Droplink. Delete the existing Control field and instead set the ASSEMBLY and CLASS fields to point to your implementation.
You also need to load a custom CSS stylesheet with the style defintions into the Content Editor, which you can achieve that by following this blog post.

How to add Multiple CSS classes Using Bindngs

I have a data binding, In which I have to apply two css classes
data-bind ="css: isHiddenStage"
isHiddenStage ==> function returning a css class based on some Logic,
This is working fine, and I want to apply another css classes based on some condition
css:{ my-class:$index() + 1 === 10 }
Note: Here i can't use isHiddenStage function to check the condition
So finally I got this:
data-bind ="css: isHiddenStage, css:{ my-class:$index() + 1 === 10 }"
Which Is not working may be because, I can't use css twice in a binding.
Is there any alternative.
Regards
There cannot be multiple css bindings on the same element. Create a function that returns all css classes separated by spaces to be used in a single css binding.
HTML
<div data-bind="css: getCssClassesForIndex($index)"></div>
View Model
this.getCssClassesForIndex = function (index) {
var cssClasses = this.isHiddenStage() || '';
if ((index + 1) === 10) {
cssClasses += ' my-class';
}
return cssClasses;
}.bind(this);
What about something like:
data-bind ="css:{ isHiddenStage: true, 'my-class': $index() + 1 === 10 }"
This way the isHiddenStage() class will always be applied since its condition is always true.
Note that I put 'my-class' in quotes because it was not a valid identifier.
Knockout documentation link source

highlight words in html using regex & javascript - almost there

I am writing a jquery plugin that will do a browser-style find-on-page search. I need to improve the search, but don't want to get into parsing the html quite yet.
At the moment my approach is to take an entire DOM element and all nested elements and simply run a regex find/replace for a given term. In the replace I will simply wrap a span around the matched term and use that span as my anchor to do highlighting, scrolling, etc. It is vital that no characters inside any html tags are matched.
This is as close as I have gotten:
(?<=^|>)([^><].*?)(?=<|$)
It does a very good job of capturing all characters that are not in an html tag, but I'm having trouble figuring out how to insert my search term.
Input: Any html element (this could be quite large, eg <body>)
Search Term: 1 or more characters
Replace Txt: <span class='highlight'>$1</span>
UPDATE
The following regex does what I want when I'm testing with http://gskinner.com/RegExr/...
Regex: (?<=^|>)(.*?)(SEARCH_STRING)(?=.*?<|$)
Replacement: $1<span class='highlight'>$2</span>
However I am having some trouble using it in my javascript. With the following code chrome is giving me the error "Invalid regular expression: /(?<=^|>)(.?)(Mary)(?=.?<|$)/: Invalid group".
var origText = $('#'+opt.targetElements).data('origText');
var regx = new RegExp("(?<=^|>)(.*?)(" + $this.val() + ")(?=.*?<|$)", 'gi');
$('#'+opt.targetElements).each(function() {
var text = origText.replace(regx, '$1<span class="' + opt.resultClass + '">$2</span>');
$(this).html(text);
});
It's breaking on the group (?<=^|>) - is this something clumsy or a difference in the Regex engines?
UPDATE
The reason this regex is breaking on that group is because Javascript does not support regex lookbehinds. For reference & possible solutions: http://blog.stevenlevithan.com/archives/mimic-lookbehind-javascript.
Just use jQuerys built-in text() method. It will return all the characters in a selected DOM element.
For the DOM approach (docs for the Node interface): Run over all child nodes of an element. If the child is an element node, run recursively. If it's a text node, search in the text (node.data) and if you want to highlight/change something, shorten the text of the node until the found position, and insert a highligth-span with the matched text and another text node for the rest of the text.
Example code (adjusted, origin is here):
(function iterate_node(node) {
if (node.nodeType === 3) { // Node.TEXT_NODE
var text = node.data,
pos = text.search(/any regular expression/g), //indexOf also applicable
length = 5; // or whatever you found
if (pos > -1) {
node.data = text.substr(0, pos); // split into a part before...
var rest = document.createTextNode(text.substr(pos+length)); // a part after
var highlight = document.createElement("span"); // and a part between
highlight.className = "highlight";
highlight.appendChild(document.createTextNode(text.substr(pos, length)));
node.parentNode.insertBefore(rest, node.nextSibling); // insert after
node.parentNode.insertBefore(highlight, node.nextSibling);
iterate_node(rest); // maybe there are more matches
}
} else if (node.nodeType === 1) { // Node.ELEMENT_NODE
for (var i = 0; i < node.childNodes.length; i++) {
iterate_node(node.childNodes[i]); // run recursive on DOM
}
}
})(content); // any dom node
There's also highlight.js, which might be exactly what you want.