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
Related
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
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.
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.
For example, in the following example I want to add a class called "noDisplay" based on a certain condition. (1==1, for simplicity's sake.)
I tried:
<tr class='#if (1== 1) { Html.Raw("noDisplay"); }'>
And:
#if (1 == 1) {
#: class="noDisplay"
}>
What you posted is working but the Html.Raw section is just being treated as a function call, not as something to be rendered. The simple solution is to prefix the call with an # symbol (which will make Razor render the output of the function) or remove the call to Html.Raw and use <text> blocks:
<tr class='#if (1== 1) { #Html.Raw("noDisplay"); }'>
Or:
<tr class='#if (1== 1) { <text>noDisplay</text> }'>
This is what you need to do:
#{
var myClass = (1 == 1 ? "noDisplay" : "");
}
<tr class='#myClass'>
Or, just:
<tr class='#(1 == 1 ? "noDisplay" : "")'>
#{
if(1 == 1)
{
var theClass = "noDisplay";
}
}
<tr class='#theClass'>
The #: is an operator that outputs a single line of content containing plain text or unmatched HTML tags. The #{ ... } defines a block of code. Using # lets you access a variable that you may have defined in your code or other C# constructs.
Plenty more info and examples here: http://www.asp.net/web-pages/overview/getting-started/introducing-razor-syntax-c
I need help of Javascript / jQuery experts to solve the next problem:
---- 1. this Javascript alerts the id of a selected option in a select html tag:
$(function(){
$("#id_productos_list").change(
function(){
var op = $(this).selectedValues()
alert(op);
}
);
});
----2. this Javascript clone html code:
function cloneMore(selector, type) {
var newElement = $(selector).clone();
var total = $('#id_' + type + '-TOTAL_FORMS').val();
newElement.find(':input').each(function() {
var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
var id = 'id_' + name;
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
});
newElement.find('label').each(function() {
var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
$(this).attr('for', newFor);
});
total++;
$('#id_' + type + '-TOTAL_FORMS').val(total);
$(selector).after(newElement);
}
---- this is a part of the HTML code that JS is cloning, and it works with no problems
<select id="id_productos_list" name="productos_list" >
<option value="1">One</option>
<option value="2">Two</option>
</select>
BUT just the Javascript #1 works with the initial html code (original for clone). the cloned others doesn't alert selected options. I've tested with different id's attrs for each cloned select tags, without results.
What am I missing? Firebug display the cloned html DOM nice :S Do I have to update DOM after cloning to make $("#id productos list").change(...) works?
Have you tried .clone(true) which clones all the handlers attached? It's described at the bottom of the Clone documentation.
The jQuery $("#...") syntax will return the first matched element by exact id. If you are cloning elements but not differentiating them by their id, this code will not work as you expect it to.
You can compare the differences between the following two expressions:
alert($("#id_productos_list").size());
...and
alert($("[id='#id_productos_list']").size());
The first expression will return either zero or one depending on the presence of an element with id "id_productos_list" in your page. The first element in declared order wins.
The second expression will return zero or one or more depending on the the set of all elements with id "id_productos_list" in your page.
Also important to note is that it doesn't appear that the event handlers are copied over as part of the clone() operation. You may need to reassign these handlers to the new elements.
var newElement = $(selector).clone(true);