I'm doing a project in ASP.net, but I have a specific design that I need.
Unfortantley, this design works on <ul> and <li> only.
The CSS Code of it is the following :
.nav-tabs.nav-stacked.nav-coupon-category {
margin-bottom: 30px;
-webkit-box-shadow: 0 3px 1px rgba(0,0,0,0.15);
box-shadow: 0 3px 1px rgba(0,0,0,0.15);
}
.nav-tabs.nav-stacked.nav-coupon-category > li > a {
text-transform: uppercase;
font-size: 13px;
z-index: 1;
-webkit-border-radius: 0;
border-radius: 0;
background: #fff;
border-left: none;
border-right: none;
border: none;
-webkit-transition: 0.2s;
-moz-transition: 0.2s;
-o-transition: 0.2s;
-ms-transition: 0.2s;
transition: 0.2s;
height: 40px;
line-height: 40px;
padding: 0px 0px 0px 55px;
position: relative;
margin: 0;
color: #666;
}
.nav-tabs.nav-stacked.nav-coupon-category > li > a .fa {
-webkit-transform: translate3d(0, 0, 0);
-moz-transform: translate3d(0, 0, 0);
-o-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
font-size: 18px;
position: absolute;
display: block;
width: 40px;
height: 40px;
background: #fbfbfb;
top: 0;
left: 0;
text-align: center;
line-height: 40px;
border-right: 1px solid #ededed;
}
and the index.aspx part looks like this :
<asp:ListBox CssClass="nav nav-tabs nav-stacked nav-coupon-category" ID="Step1CatList" runat="server" onselectedindexchanged="ListCat_SelectedIndexChanged">
<asp:ListItem Selected="True" Text="Second Hand" Value="Items"></asp:ListItem>
<asp:ListItem Text="Cars" Value="Cars"></asp:ListItem>
<asp:ListItem Text="Rea Estate" Value="RealEstate"></asp:ListItem>
</asp:ListBox>
As you can see, I tries using the CssClass, but it didn't work.
I would be glad if someone knows any solution to this problem.
I currently have this need as well. Since the listbox is a select, it doesn't look right on ipad. Keep in mind, I don't usually create server controls, though I know they can be helpful, so this is quick, dirty and unfinished, mainly due to unfamiliarity. Also note that I am currently working with it from client-side(jquery), and have not really tested the postback functionality. However, this should give you an idea.
public class ItemEventArgs : EventArgs
{
#region Properties
public int Index { get; set; }
public string Text { get; set; }
public string Value { get; set; }
#endregion Properties
}
public class UnorderedItem
{
#region Constructors
public UnorderedItem(string text, string value)
{
Text = text;
Value = value;
}
#endregion Constructors
#region Properties
public string Text { get; set; }
public string Value { get; set; }
#endregion Properties
}
[DefaultProperty("Text")]
[ToolboxData("<{0}:UnorderedList runat=server></{0}:UnorderedList>")]
public class UnorderedList : WebControl, IPostBackEventHandler//, IScriptControl
{
#region Fields
private ScriptManager scriptManager;
#endregion Fields
#region Constructors
public UnorderedList()
{
Items = new List<UnorderedItem>();
}
#endregion Constructors
#region Events
public event EventHandler<ItemEventArgs> ItemClick;
#endregion Events
#region Properties
[Bindable(true)]
[Category("Appearance")]
[Localizable(true)]
[DefaultValue("")]
public string ItemAltCssClass { get; set; }
[Bindable(true)]
[Category("Appearance")]
[Localizable(true)]
[DefaultValue("")]
public string ItemCssClass { get; set; }
[Bindable(true)]
[Category("Appearance")]
public List<UnorderedItem> Items { get; set; }
[Bindable(true)]
[DefaultValue("")]
[Category("Client Events")]
[Localizable(true)]
public string OnClientItemClick { get; set; }
[Bindable(true)]
[Category("Appearance")]
[Localizable(true)]
[DefaultValue("")]
public int SelectedIndex { get; set; }
[Bindable(true)]
[Category("Appearance")]
[Localizable(true)]
[DefaultValue("")]
public string SelectedItemCssClass { get; set; }
[Bindable(true)]
[Category("Appearance")]
[Localizable(true)]
[DefaultValue("")]
public string SelectedValue { get; set; }
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public string Text { get; set; }
#endregion Properties
#region Methods
public void ClearSelection()
{
Text = null;
SelectedValue = null;
SelectedIndex = -1;
}
//public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
//{
// return new ScriptReference[] { };
//}
//public IEnumerable<ScriptReference> GetScriptReferences()
//{
// return new ScriptDescriptor[] { };
//}
public void RaisePostBackEvent(string eventArgument)
{
ItemEventArgs args = new ItemEventArgs();
args.Index = Convert.ToInt32(eventArgument);
UnorderedItem item = Items[args.Index];
args.Text = item.Text;
args.Value = item.Value;
SelectedValue = args.Value;
SelectedIndex = args.Index;
Text = args.Text;
if (ItemClick != null)
{
ItemClick(this, args);
}
}
protected virtual void OnItemClick(ItemEventArgs e)
{
EventHandler<ItemEventArgs> handler = ItemClick;
if (handler != null)
{
handler(this, e);
}
}
protected override void OnPreRender(EventArgs e)
{
//if (!DesignMode)
//{
// scriptManager = ScriptManager.GetCurrent(Page);
// if (scriptManager == null)
// {
// throw new HttpException("A scriptmanager control must exist on the current page.");
// }
// scriptManager.RegisterScriptControl(this);
//}
base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter writer)
{
//if (!this.DesignMode)
//{
// scriptManager.RegisterScriptDescriptors(this);
//}
var itemCssClass = false;
var itemAltCssClass = false;
if (!string.IsNullOrEmpty(ItemCssClass))
{
itemCssClass = true;
}
if (!string.IsNullOrEmpty(ItemAltCssClass))
{
itemAltCssClass = true;
}
writer.WriteBeginTag("ul");
if (!string.IsNullOrEmpty(CssClass))
{
writer.WriteAttribute("class", CssClass);
}
if (!string.IsNullOrEmpty(AccessKey))
{
writer.WriteAttribute("AccessKey", AccessKey);
}
if (Style != null && !string.IsNullOrEmpty(Style.Value))
{
writer.WriteAttribute("style", Style.Value);
}
writer.WriteAttribute("value", string.IsNullOrEmpty(SelectedValue) ? Items[0].Value : SelectedValue);
writer.WriteAttribute("index", SelectedIndex.ToString());
writer.WriteAttribute("id", this.ClientID);
writer.Write(HtmlTextWriter.TagRightChar);
var itemClass = string.Empty;
var prefix = string.Concat(ClientID, "_Item");
UnorderedItem item;
for (var i = 0; i < Items.Count; i++)
{
itemClass = string.Empty;
item = Items[i];
writer.WriteBeginTag("li");
writer.WriteAttribute("index", i.ToString());
writer.WriteAttribute("value", item.Value);
writer.WriteAttribute("onclick", String.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}", "SetSelectedValue(this, '", item.Text, "','", item.Value, "',", i, ");", OnClientItemClick ?? string.Empty, Page.ClientScript.GetPostBackEventReference(this, i.ToString())));
if (i % 2 == 0)
{
if (itemCssClass)
{
itemClass = ItemCssClass;
}
}
else
{
if (itemAltCssClass)
{
itemClass = ItemAltCssClass;
}
}
if (SelectedIndex == i)
{
itemClass = String.Format("{0}{1}{2}", itemClass, " ", SelectedItemCssClass).Trim();
}
if (itemClass != string.Empty)
{
writer.WriteAttribute("class", ItemCssClass);
}
writer.Write(HtmlTextWriter.TagRightChar);
writer.Write(item.Text);
writer.WriteEndTag("li");
}
writer.WriteEndTag("ul");
//base.Render(writer);
}
protected override void RenderContents(HtmlTextWriter writer)
{
base.RenderContents(writer);
}
#endregion Methods
}
Usage:
<ww:UnorderedList runat="server" ID="lstResourceIds" CssClass="lstResourceIds" style="Height:535px;width:250px;" ItemCssClass="lstResourceIdsItem" ItemAltCssClass="lstResourceIdsAltItem" AccessKey="L" meta:resourcekey="lstResourceIds" OnClientItemClick="return false;" />
Related
could you help me? I want to make a generic tree view component in blazor webassembly but I am a bit lost in how to do it, I want to be able to pass any type of object list to the component, for the moment I have done something very simple, with an object called directory load the component but I would like to replace it with Titem to be able to send any type of list
index.razor
#page "/index"
<h1>Treeview</h1>
<Treeview Directorios="directorios"></Treeview>
#code {
public Directorio[] directorios;
protected async override Task OnInitializedAsync()
{
var fall2014 = new Directorio("Fall 2014", new Directorio[] { }, new string[] { "image1.jpg", "image2.jpg", "image3.jpg" });
var summer2014 = new Directorio("Summer 2014", new Directorio[] { }, new string[] { "image10.jpg", "image20.jpg", "image30.jpg" });
var pictures = new Directorio("Pictures", new Directorio[] { fall2014, summer2014 }, new string[] { });
var music = new Directorio("Music", new Directorio[] { }, new string[] { "song1.mp3", "song2.mp3" });
directorios = new Directorio[] { pictures, music };
}
}
component.razor
<ul>
#foreach (var dir in Directorios)
{
<li>
<span #onclick="#dir.toggle">#dir.getIcon()</span>
<span>#dir.nombre</span>
#if (dir.expanded)
{
<div>
<ul>
#foreach (var archivo in dir.archivos)
{
<li>#archivo</li>
}
</ul>
<Treeview Directorios="#dir.directorios"></Treeview>
</div>
}
</li>
}
</ul>
#code {
[Parameter] public Directorio[] Directorios { get; set; }
}
directory.cs
public class Directorio
{
public bool expanded = false;
public string nombre;
public string[] archivos;
public Directorio[] directorios;
public Directorio(string nombre, Directorio[] directorios, string[] archivos)
{
this.nombre = nombre;
this.directorios = directorios;
this.archivos = archivos;
}
public void toggle()
{
expanded = !expanded;
}
public string getIcon()
{
if (expanded)
{
return "-";
}
return "+";
}
}
Try this one.
In future perhaps I will make a combotree and share with you here.
if you are able to do same, do not hesitate to post it here.
1.Treeview.razor
#typeparam Tvalue
#inherits TreeviewBase<Tvalue>
<ul class="parentUl">
#if (AllItems != null)
{
#foreach (var Pitem in AllItems)
{
if (GetPropertyValue(Pitem, ParentId) == ""|| Convert.ToInt32(GetPropertyValue(Pitem, ParentId)) == 0)
{
if (Convert.ToBoolean(GetPropertyValue(Pitem, HasChildren)))
{
<li>
<span #onclick="#(()=>SpanToggle(Pitem))" class="#_caretcss[Convert.ToInt32(#GetPropertyValue(Pitem, Id))]">#GetPropertyValue(Pitem, Text)</span>
<ul class="#_nestedcss[Convert.ToInt32(#GetPropertyValue(Pitem, Id))]">
#foreach (var Citem in AllItems)
{
if (GetPropertyValue(Pitem, Id) == GetPropertyValue(Citem, ParentId))
{
if (Convert.ToBoolean(GetPropertyValue(Citem, HasChildren)))
{
<li>
<span #onclick="#(()=>SpanToggle(Citem))" class="#_caretcss[Convert.ToInt32(#GetPropertyValue(Citem, Id))]">#GetPropertyValue(Citem, Text)</span>
<ul class="#_nestedcss[Convert.ToInt32(#GetPropertyValue(Citem, Id))]">
#foreach (var C1item in AllItems)
{
if (GetPropertyValue(Citem, Id) == GetPropertyValue(C1item, ParentId))
{
if (Convert.ToBoolean(GetPropertyValue(C1item, HasChildren)))
{
<li>
<span #onclick="#(()=>SpanToggle(C1item))" class="#_caretcss[Convert.ToInt32(#GetPropertyValue(C1item, Id))]">#GetPropertyValue(C1item, Text)</span>
<ul class="#_nestedcss[Convert.ToInt32(#GetPropertyValue(C1item, Id))]">
#foreach (var C2item in AllItems)
{
if (GetPropertyValue(C1item, Id) == GetPropertyValue(C2item, ParentId))
{
if (Convert.ToBoolean(GetPropertyValue(C2item, HasChildren)))
{
<li>
<span #onclick="#(()=>SpanToggle(C2item))" class="#_caretcss[Convert.ToInt32(#GetPropertyValue(C2item, Id))]">#GetPropertyValue(C1item, Text)</span>
</li>
}
else
{
<li>#GetPropertyValue(C2item, Text)</li>
}
}
}
</ul>
</li>
}
else
{
<li>#GetPropertyValue(C1item, Text)</li>
}
}
}
</ul>
</li>
}
else
{
<li>#GetPropertyValue(Citem, Text)</li>
}
}
}
</ul>
</li>
}
else
{
<li>#GetPropertyValue(Pitem, Text)</li>
}
}
}
}
</ul>
2.style.css
<style type="text/css">
/*css reference W3schools. "with small modification."*/
/* css begin*/
.parentUl li ul {
border-left: dashed 2px black;
height: fit-content;
border-start-end-radius: 2px;
}
ul, .parentUl {
list-style-type: none;
}
.parentUl ul li {
position: relative;
}
.parentUl ul li:before {
content: "";
position: absolute;
top: 13px;
left: -40px;
width: 40px;
height: 1px;
border-bottom: dashed 2px black;
}
.parentUl {
margin: 0;
padding: 0;
}
.caret {
cursor: pointer;
-webkit-user-select: none; /* Safari 3.1+ */
-moz-user-select: none; /* Firefox 2+ */
-ms-user-select: none; /* IE 10+ */
user-select: none;
}
.caret::before {
content: "\25B6";
color: black;
display: inline-block;
margin-right: 6px;
transition: all 0.45s;
}
.caret-down::before {
-ms-transform: rotate(60deg); /* IE 9 */
-webkit-transform: rotate(60deg); /* Safari */
transform: rotate(60deg);
transition: all 0.45s;
}
.nested {
display: none;
transition: all 0.45s;
}
.active {
display: block;
transition: all 0.45s;
}
/*css end*/
</style>
3.TreeviewBase.cs
public partial class TreeviewBase<Tvalue>:ComponentBase
{
[Parameter]
public List<Tvalue> DataSource { get; set; }
[Parameter]
public string Id { get; set; }
[Parameter]
public string ParentId { get; set; }
[Parameter]
public string HasChildren { get; set; }
[Parameter]
public string Text { get; set; }
[Parameter]
public string Expanded { get; set; }
protected List<Tvalue> AllItems;
protected Dictionary<int, bool> _caretDown= new Dictionary<int, bool>();
protected Dictionary<int, string> _caretcss=new Dictionary<int,string>();
protected Dictionary<int, string> _nestedcss=new Dictionary<int,string>();
protected override Task OnInitializedAsync()
{
//asigning to its new instance to avoid exceptions.
AllItems = new List<Tvalue>();
AllItems = DataSource.ToArray().ToList();
if (AllItems != null)
{
foreach (var item in AllItems)
{
var _id = Convert.ToInt32(GetPropertyValue(item, Id));
//initializing fields with default value.
_caretDown.Add(_id, true);
_caretcss.Add(_id, "caret");
_nestedcss.Add(_id, "nested");
}
}
return base.OnInitializedAsync();
}
protected override Task OnParametersSetAsync()
{
//This will check if the first item in the
// list/collection has a "parentId" then remove the "parentId" from it.
//Because we use the first item as a reference in the razor file, so it must not have "parentId".
var Parem = AllItems.First();
switch (GetPropertyType(Parem, ParentId))
{
case "Int32":
if (Convert.ToInt32(GetPropertyValue(Parem, ParentId)) > 0)
{
SetPropertyValue<int>(Parem, ParentId, 0);
}
break;
case "String":
if (GetPropertyValue(Parem, ParentId) != "")
{
SetPropertyValue<string>(Parem, ParentId, "");
}
break;
default:
break;
}
return base.OnParametersSetAsync();
}
protected void SpanToggle(Tvalue item)
{
var _clckdItemid = Convert.ToInt32(GetPropertyValue(item, Id));
_caretcss[_clckdItemid] = _caretDown[_clckdItemid] ? "caret caret-down" : "caret";
_nestedcss[_clckdItemid] = _caretDown[_clckdItemid] ? "active" : "nested";
_caretDown[_clckdItemid] = !_caretDown[_clckdItemid];
}
#region reflection methodes to get your property type, propert value and also set property value
protected string GetPropertyValue(Tvalue item, string Property)
{
if (item != null)
{
return item.GetType().GetProperty(Property).GetValue(item, null).ToString();
}
return "";
}
protected void SetPropertyValue<T>(Tvalue item, string Property, T value)
{
if (item != null)
{
item.GetType().GetProperty(Property).SetValue(item, value);
}
}
protected string GetPropertyType(Tvalue item, string Property)
{
if (item != null)
{
return item.GetType().GetProperty(Property).PropertyType.Name;
}
return null;
}
#endregion
}
Index.razor
<Treeview Tvalue="MailItem" DataSource="#MyFolder" Id="Id" Text="FolderName" ParentId="ParentId"
Expanded="Expanded" HasChildren="HasSubFolder"></Treeview>
#code{
protected class MailItem
{
public int Id { get; set; }
public string ParentId { get; set; }
public bool HasSubFolder { get; set; }
public string FolderName { get; set; }
public bool Expanded { get; set; }
}
List<MailItem> MyFolder = new List<MailItem>();
protected override Task OnInitializedAsync()
{
MyFolder.Add(new MailItem { Id = 1, FolderName = "Inbox", HasSubFolder = true, Expanded = true, ParentId = "" });
MyFolder.Add(new MailItem { Id = 2, FolderName = "Category", ParentId = "1", HasSubFolder = true, Expanded = true });
MyFolder.Add(new MailItem { Id = 3, FolderName = "Primary", ParentId = "2", HasSubFolder = false, Expanded = true });
MyFolder.Add(new MailItem { Id = 4, FolderName = "Social", ParentId = "6", HasSubFolder = false, Expanded = true });
MyFolder.Add(new MailItem { Id = 5, FolderName = "Promotion", ParentId = "6", HasSubFolder = false, Expanded = true });
MyFolder.Add(new MailItem { Id = 6, FolderName = "Demo", ParentId = "2", HasSubFolder = true, Expanded = true });
return base.OnInitializedAsync();
}
}
This is the logics behind a generic Treeview Component in Blazor.
Kindly click on this image to view the working example:
I am new to blazor and need to create a dynamic treeview where nodes can be created and deleted. All this data is saved in database. After pulling the data from database what kind of object I need to create to bind to Mudtreeview?
If only first level nodes are rendered initially, how can I load the children on node click?
is there any example somewhere? All the sample code on mudblazor site is with static data.
Adding and removeing items is easy if you bind the treeview to a collection in MudBlazor. I made a little demo for you to can play around online: https://try.mudblazor.com/snippet/cuwlvFQcfJTzHuki
Here is the full code of the example:
<MudButton OnClick="AddItems" Variant="Variant.Filled">Add items</MudButton>
<MudButton OnClick="DeleteItems" Variant="Variant.Filled">Delete items</MudButton>
<MudPaper Width="300px" Elevation="0">
<MudTreeView Items="TreeItems" MultiSelection="true" #bind-ActivatedValue="ActivatedValue" #bind-SelectedValues="SelectedValues">
<ItemTemplate>
<MudTreeViewItem #bind-Expanded="#context.IsExpanded" Items="#context.TreeItems" Value="#context"
Icon="#context.Icon" Text="#context.Title" EndText="#context.Number?.ToString()" EndTextTypo="#Typo.caption" />
</ItemTemplate>
</MudTreeView>
</MudPaper>
<div style="width: 100%">
<MudText Typo="#Typo.subtitle1">Activated item: #(ActivatedValue?.Title)</MudText>
<MudText Typo="#Typo.subtitle1">Sum of selected items: #GetSelectedSum()</MudText>
</div>
#code {
private TreeItemData ActivatedValue { get; set; }
private HashSet<TreeItemData> SelectedValues { get; set; }
private HashSet<TreeItemData> TreeItems { get; set; } = new HashSet<TreeItemData>();
public class TreeItemData
{
public string Title { get; set; }
public string Icon { get; set; }
public int? Number { get; set; }
public bool IsExpanded { get; set; }
public HashSet<TreeItemData> TreeItems { get; set; }
public TreeItemData(string title, string icon, int? number = null)
{
Title = title;
Icon = icon;
Number = number;
}
public override bool Equals(object x) {
var other = x as TreeItemData;
if (other==null)
return false;
return other.Title==Title;
}
public override int GetHashCode() {
return Title.GetHashCode();
}
}
protected override void OnInitialized()
{
TreeItems.Add(new TreeItemData("All Mail", Icons.Filled.Email));
TreeItems.Add(new TreeItemData("Trash", Icons.Filled.Delete));
TreeItems.Add(new TreeItemData("Categories", Icons.Filled.Label)
{
IsExpanded = true,
TreeItems = new HashSet<TreeItemData>()
{
new TreeItemData("Social", Icons.Filled.Group, 90),
new TreeItemData("Updates", Icons.Filled.Info, 2294),
new TreeItemData("Forums", Icons.Filled.QuestionAnswer, 3566),
new TreeItemData("Promotions", Icons.Filled.LocalOffer, 733)
}
});
TreeItems.Add(new TreeItemData("History", Icons.Filled.Label));
}
public int GetSelectedSum()
{
return SelectedValues?.Sum(i => i.Number ?? 0) ?? 0;
}
private int i=0;
public void AddItems() {
TreeItems.Add(new TreeItemData("Added Item " + (i++), Icons.Filled.Coronavirus));
}
public void DeleteItems() {
var item=TreeItems.FirstOrDefault(x=>x.Title.StartsWith("Added Item"));
TreeItems.Remove(item);
}
}
I've made a range input in a blazor component.
And now i'm trying to make a logarithme range input.
To do this, I wanted to use my first range input, and simply make a wrapper between a fake value in the range input and the real value.
but I think I havent well understood the component binding, the index page is not notified of modifications.
Here is the range input component:
<div id="rc-#ID">
#(new MarkupString($#"<style>
#rc-{ID} {{
position: relative;
width: 100%;
}}
#rc-{ID} > input[type='range'] {{
padding: 0;
margin: 0;
display: inline-block;
vertical-align: top;
width: 100%;
--range-color: hsl(211deg 100% 50%);
background: var(--track-background);
}}
#rc-{ID} > input[type='range']::-moz-range-track {{
border-color: transparent; /* needed to switch FF to 'styleable' control */
}}
#rc-{ID} > input[name='low-range'] {{
position: absolute;
}}
#rc-{ID} > input[name='low-range']::-webkit-slider-thumb {{
position: relative;
z-index: 2;
}}
#rc-{ID} > input[name='low-range']::-moz-range-thumb {{
transform: scale(1); /* FF doesn't apply position it seems */
z-index: 1;
}}
#rc-{ID} > input[name='high-range'] {{
position: relative;
--track-background: linear-gradient(to right, transparent {(int)(100 * (LowerValue - MinBound) / (MaxBound - MinBound) + 1 + 0.5f)}%, var(--range-color) 0, var(--range-color) {(int)(100 * (HigherValue - MinBound) / (MaxBound - MinBound) - 1 + 0.5f)}%, transparent 0 ) no-repeat 0 50% / 100% 100%;
background: linear-gradient(to right, gray {(int)(100 * (LowerValue - MinBound) / (MaxBound - MinBound) + 1+ 0.5f)}%, transparent 0, transparent {(int)(100 * (HigherValue - MinBound) / (MaxBound - MinBound) - 1 + 0.5f)}%, gray 0 ) no-repeat 0 50% / 100% 30%
}}
#rc-{ID} > input[type='range']::-webkit-slider-runnable-track {{
background: var(--track-background);
}}
#rc-{ID} > input[type='range']::-moz-range-track {{
background: var(--track-background);
}}
</style>"))
<input class="custom-range" name="low-range" type="range" min="#MinBound" max="#MaxBound" step="#Step" #bind="#LowerValue" #bind:event="oninput" />
<input class="custom-range" name="high-range" type="range" min="#MinBound" max="#MaxBound" step="#Step" #bind="#HigherValue" #bind:event="oninput" />
</div>
#code
{
[Parameter] public float MinBound { get; set; } = 0;
[Parameter] public float MaxBound { get; set; } = 1;
[Parameter] public float Step { get; set; } = 0.01f;
[Parameter]
public float? ValueLow
{
get
{
var res = Math.Min(_valueLow, _valueHigh);
if (res == MinBound)
return null;
return res;
}
set
{
if (!value.HasValue)
{
if (_valueLow.Equals(MinBound))
return;
_valueLow = MinBound;
}
else
{
if (_valueLow.Equals(value.Value))
return;
_valueLow = value.Value;
}
if (_valueLow > _valueHigh)
{
_valueLow = _valueHigh;
_valueHigh = value.Value;
ValueHighChanged.InvokeAsync(_valueHigh);
}
if (_valueLow == MinBound)
ValueLowChanged.InvokeAsync(null);
else
ValueLowChanged.InvokeAsync(_valueLow);
}
}
[Parameter]
public float? ValueHigh
{
get
{
var res = Math.Max(_valueLow, _valueHigh);
if (res == MaxBound)
return null;
return res;
}
set
{
if (!value.HasValue)
{
if (_valueHigh.Equals(MaxBound))
return;
_valueHigh = MaxBound;
}
else
{
if (_valueHigh.Equals(value.Value))
return;
_valueHigh = value.Value;
}
if (_valueLow > _valueHigh)
{
_valueHigh = _valueLow;
_valueLow = value.Value;
ValueLowChanged.InvokeAsync(_valueLow);
}
if (_valueHigh == MaxBound)
ValueHighChanged.InvokeAsync(null);
else
ValueHighChanged.InvokeAsync(_valueHigh);
}
}
[Parameter] public EventCallback<float?> ValueLowChanged { get; set; }
[Parameter] public EventCallback<float?> ValueHighChanged { get; set; }
float _valueLow = 0;
float _valueHigh = 1;
private float LowerValue
{
get => Math.Min(_valueLow, _valueHigh);
set => ValueLow = value;
}
private float HigherValue
{
get => Math.Max(_valueLow, _valueHigh);
set => ValueHigh = value;
}
string ID = Guid.NewGuid().ToString().Replace("-", "").Substring(15);
}
And here is my Range input log component:
<RangeControl #bind-ValueLow="Low"
#bind-ValueHigh="High"
MaxBound="max"
MinBound="min"
Step="1" />
<div class="d-flex">
<strong>Log values : </strong>
<span>#Low</span>
<span class="ml-2">#High</span>
</div>
#code
{
private float min = 1.0f;
private float max = 100.0f;
[Parameter] public float MinBound { get; set; } = 10;
[Parameter] public float MaxBound { get; set; } = 10000;
[Parameter] public float Step { get; set; } = 1;
private float r => MinBound == 0 ? MaxBound : (MaxBound / MinBound);
private float? _valueLow;
[Parameter]
public float? ValueLow
{
get => _valueLow;
set
{
if (value == _valueLow) return;
_valueLow = value;
ValueLowChanged.InvokeAsync(ValueLow);
}
}
private float? _valueHigh;
[Parameter]
public float? ValueHigh
{
get => _valueHigh;
set
{
if (value == _valueHigh) return;
_valueHigh = value;
ValueHighChanged.InvokeAsync(ValueHigh);
}
}
private float? Low
{
get
{
if (ValueLow.HasValue)
return (float)((min = max) * Math.Log(ValueLow.Value) / Math.Log(r));
return null;
}
set
{
if (value.HasValue)
ValueLow = (float)Math.Exp(value.Value * Math.Log(r) / (max - min));
else
ValueLow = null;
}
}
private float? High
{
get
{
if (ValueHigh.HasValue)
return (float)((min = max) * Math.Log(ValueHigh.Value) / Math.Log(r));
return null;
}
set
{
if (value.HasValue)
ValueHigh = (float)Math.Exp(value.Value * Math.Log(r) / (max - min));
else
ValueHigh = null;
}
}
[Parameter] public EventCallback<float?> ValueLowChanged { get; set; }
[Parameter] public EventCallback<float?> ValueHighChanged { get; set; }
}
And the index page :
#page "/"
<h1>Hello, world!</h1>
<RangeControl #bind-ValueHigh="ValueHigh" #bind-ValueLow="ValueLow" MinBound="10" MaxBound="10000" Step="1"></RangeControl>
<br />
<RangeControlLog #bind-ValueHigh="ValueHigh" #bind-ValueLow="ValueLow" MinBound="10" MaxBound="10000" Step="1"></RangeControlLog>
<div class="d-flex">
<strong>Real values : </strong>
<span>#ValueLow</span>
<span class="ml-2">#ValueHigh</span>
</div>
#code {
float? ValueHigh = null;
float? ValueLow = null;
}
You cannot have nested #bind- i.e. have a wrapper that uses #bind- of the wrapped component and also expose a property to be used with #bind-.
You need to pass Foo and FooChanged to the component being wrapped.
This means that in your RangeControlLog, you need to pass to RangeControl ValueLow and ValueLowChanged instead of using #bind-ValueLow
<RangeControl ValueLow="Low"
ValueHigh="High"
ValueLowChanged="ValueLowChanged"
ValueHighChanged="ValueHighChanged"
MaxBound="max"
MinBound="min"
Step="1" />
To learn more, you can take a look at the docs about chained binding and also take a look at this question I have made to understand better about ValueChanged and how it works.
But for short, when you use #bind-Foo="Bar" it transforms it into Foo="Bar", FooChanged="#(foo => Bar = foo;)" which are kind of a default value for updating the properties. But it doesn't work when you have multiple #bind-, so you need to pass that directly.
For me, #bind- looks like a syntax sugar for binding properties and when you have the parameters Foo and FooChanged, you can use #bind-Foo.
I'm creating a popup component and I want this to be movable. I can move it using the top / left style, but for now they are init to top:0;left:0; and so the popup appear on the top left corner of the page. I'm looking to make it appear on the center of the page and then get the Top Left coordinates of my div in ordor to manage properly my calcul after.
here is what I have now:
<div class="child-window" draggable="true" style="position:absolute; top: #(offsetY)px; left: #(offsetX)px; border-color: black;" #ondragend="OnDragEnd" #ondragstart="OnDragStart">
<div class="cw-content">
#Content
</div>
</div>
#code {
private double startX, startY, offsetX, offsetY;
protected override void OnInitialized() {
base.OnInitialized();
ResetStartPosition();
}
private void ResetStartPosition() {
//Set offsetX & offsetY to the top left div position
}
private void OnDragStart(DragEventArgs args) {
startX = args.ClientX;
startY = args.ClientY;
}
private void OnDragEnd(DragEventArgs args) {
offsetX += args.ClientX - startX;
offsetY += args.ClientY - startY;
}
}
At the moment it is possible with JS only
public class BoundingClientRect
{
public double X { get; set; }
public double Y { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public double Top { get; set; }
public double Right { get; set; }
public double Bottom { get; set; }
public double Left { get; set; }
}
private async Task OnElementClick(MouseEventArgs e)
{
var result = await JSRuntime.InvokeAsync<BoundingClientRect>("MyDOMGetBoundingClientRect", MyElementReference);
var x = (int) (e.ClientX - result.Left);
var y = (int) (e.ClientY - result.Top);
// now you can work with the position relative to the element.
}
and
<script> MyDOMGetBoundingClientRect = (element, parm) => { return element.getBoundingClientRect(); }; </script>
I'm working on a ServerControl (SuperFish Menu).
Below is my codes.
Menu.cs
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[ParseChildren(true, "MenuItems")]
[PersistChildren(true)]
[ToolboxData("<{0}:Menu runat=\"server\"></{0}:Menu>")]
public class Menu : WebControl, INamingContainer
{
#region Fields
List<MenuItem> _MenuItems;
#endregion
#region Properties
public VerOrHor VerticalOrHorizontal { get; set; }
public string Main_ul_CssClass { get; set; }
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public animation AnimationItems { get; set; }
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public List<MenuItem> MenuItems
{
get
{
if (_MenuItems == null)
{
_MenuItems = new List<MenuItem>();
}
return _MenuItems;
}
}
string _AnimationType
{
get
{
switch (this.AnimationItems.AnimationType)
{
case AnimationType.Opacity_Height:
return "animation:{opacity:'show',height:'show'}";
case AnimationType.Opacity:
return "animation:{opacity:'show'}";
case AnimationType.Height:
return "animation:{height:'show'}";
default:
return "animation:{opacity:'show',height:'show'}";
}
}
}
string _AnimationSpeed
{
get
{
switch (this.AnimationItems.AnimationSpeed)
{
case AnimationSpeed.Fast:
return "speed:Fast";
case AnimationSpeed.Normal:
return "speed:Normal";
case AnimationSpeed.Slow:
return "speed:Slow";
default:
return "speed:Fast";
}
}
}
#endregion
#region Methods
public override void RenderBeginTag(HtmlTextWriter writer)
{
}
public override void RenderEndTag(HtmlTextWriter writer)
{
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
#region Adding Script & link Tags
HtmlGenericControl jquery = new HtmlGenericControl("script");
jquery.Attributes.Add("type", "text/javascript");
jquery.Attributes.Add("src", Page.ClientScript.GetWebResourceUrl(typeof(Menu), "JQueryMenu.JavaScriptFiles.jquery_1_4_3.js"));
jquery.EnableViewState = false;
Page.Header.Controls.Add(jquery);
HtmlGenericControl hoverIntent = new HtmlGenericControl("script");
hoverIntent.Attributes.Add("type", "text/javascript");
hoverIntent.Attributes.Add("src", Page.ClientScript.GetWebResourceUrl(typeof(Menu), "JQueryMenu.JavaScriptFiles.hoverIntent.js"));
hoverIntent.EnableViewState = false;
Page.Header.Controls.Add(hoverIntent);
HtmlGenericControl jquery_bgiframe_min = new HtmlGenericControl("script");
jquery_bgiframe_min.Attributes.Add("type", "text/javascript");
jquery_bgiframe_min.Attributes.Add("src", Page.ClientScript.GetWebResourceUrl(typeof(Menu), "JQueryMenu.JavaScriptFiles.jquery_bgiframe_min.js"));
jquery_bgiframe_min.EnableViewState = false;
Page.Header.Controls.Add(jquery_bgiframe_min);
HtmlGenericControl superfish = new HtmlGenericControl("script");
superfish.Attributes.Add("type", "text/javascript");
superfish.Attributes.Add("src", Page.ClientScript.GetWebResourceUrl(typeof(Menu), "JQueryMenu.JavaScriptFiles.superfish.js"));
superfish.EnableViewState = false;
Page.Header.Controls.Add(superfish);
HtmlGenericControl supersubs = new HtmlGenericControl("script");
supersubs.Attributes.Add("type", "text/javascript");
supersubs.Attributes.Add("src", Page.ClientScript.GetWebResourceUrl(typeof(Menu), "JQueryMenu.JavaScriptFiles.supersubs.js"));
supersubs.EnableViewState = false;
Page.Header.Controls.Add(supersubs);
HtmlGenericControl csslink = new HtmlGenericControl("link");
csslink.Attributes.Add("href", Page.ClientScript.GetWebResourceUrl
(typeof(Menu), "JQueryMenu.CSS.superfish.css"));
csslink.ID = "NavigationMenu";
csslink.Attributes.Add("type", "text/css");
csslink.Attributes.Add("rel", "stylesheet");
csslink.EnableViewState = false;
Page.Header.Controls.Add(csslink);
if (this.VerticalOrHorizontal == VerOrHor.Vertical)
{
HtmlGenericControl csslink01 = new HtmlGenericControl("link");
csslink01.Attributes.Add("href", Page.ClientScript.GetWebResourceUrl
(typeof(Menu), "JQueryMenu.CSS.superfish-vertical.css"));
csslink01.Attributes.Add("type", "text/css");
csslink01.Attributes.Add("rel", "stylesheet");
csslink01.EnableViewState = false;
Page.Header.Controls.Add(csslink01);
}
#endregion
}
protected override void RenderContents(HtmlTextWriter output)
{
output.Write(CreateMenuHtmlTags().ToString());
}
StringBuilder CreateMenuHtmlTags()
{
if (_MenuItems == null || _MenuItems.Count <= 0)
throw new Exception("Please Fill the control with <MenuItem> tags");
StringBuilder Html = new StringBuilder("");
#region Add <Script>
if (String.IsNullOrEmpty(Main_ul_CssClass))
Html.Append("<script>$(document).ready(function() { $(\"ul.sf-menu\").superfish({pathLevels: 1,");
else
Html.Append("<script>$(document).ready(function() { $(\"ul." + Main_ul_CssClass + "\").superfish({ pathLevels: 1,");
if (AnimationItems == null)
Html.Append("delay:1000, animation:{opacity:'show',height:'show'}, speed:'normal', autoArrows: true, dropShadows: true}); });</script>");
else
{
Html.Append("delay:" + AnimationItems.Delay.Trim() + ",");
Html.Append(_AnimationType + ",");
Html.Append(_AnimationSpeed + ",");
Html.Append("dropShadows: " + AnimationItems.DropShadow.ToString() + ",");
Html.Append(#"autoArrows: false});});</script>");
}
#endregion
if (String.IsNullOrEmpty(Main_ul_CssClass))
Html.Append("<ul class=\"sf-menu sf-js-enabled sf-shadow\" id='sample-menu-1'>");
else
Html.Append("<ul class=\"" + Main_ul_CssClass + "\" id='sample-menu-1'>");
foreach (MenuItem item in _MenuItems)
{
if (item.SubMenuItems != null || item.SubMenuItems.Count > 0)
{
if (!string.IsNullOrEmpty(item.li_CssClass))
Html.Append("<li class=\"" + item.li_CssClass + "\">");
else
Html.Append("<li>");
Html.Append("" + item.Text.Trim() + "");
ParseSubMenuItems(ref Html, item);
Html.Append("</li>");
}
else
Html.Append("<li>" + item.Text.Trim() + "</li>");
}
Html.Append("</ul>");
return Html;
}
void ParseSubMenuItems(ref StringBuilder Html, MenuItem menuItems)
{
if (menuItems == null || menuItems.SubMenuItems.Count == 0) return;
Html.Append("<ul class=\"" + menuItems.ul_CssClass.Trim() + "\" style=\"display: none; visibility: hidden;\">");
foreach (MenuItem item in menuItems.SubMenuItems)
{
if (item.SubMenuItems != null || item.SubMenuItems.Count > 0)
{
if (!string.IsNullOrEmpty(item.li_CssClass))
Html.Append("<li class=\"" + item.li_CssClass + "\">");
else
Html.Append("<li>");
Html.Append("" + item.Text.Trim() + "");
ParseSubMenuItems(ref Html, item);
Html.Append("</li>");
}
else
Html.Append("<li>" + item.Text.Trim() + "</li>");
}
Html.Append("</ul>");
}
#endregion
}
public enum VerOrHor
{
Vertical,
Horizontal
}
public class MenuItemsCollectionEditor : CollectionEditor
{
public MenuItemsCollectionEditor(Type type)
: base(type)
{
}
protected override bool CanSelectMultipleInstances()
{
return false;
}
protected override Type CreateCollectionItemType()
{
return typeof(MenuItem);
}
}
MenuItem.cs
[PersistChildren(true)]
[ParseChildren(true, "SubMenuItems")]
public class MenuItem : Control, INamingContainer
{
#region Fields
List<MenuItem> _SubMenuItems;
#endregion
#region Properties
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
//[Editor(typeof(SubMenuItemsCollectionEditor), typeof(UITypeEditor))]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public List<MenuItem> SubMenuItems
{
get
{
if (_SubMenuItems == null)
{
_SubMenuItems = new List<MenuItem>();
}
return _SubMenuItems;
}
}
[DefaultValue("MenuItem")]
public string Text { get; set; }
[DefaultValue("")]
[Description("<ul /> css class")]
public string ul_CssClass { get; set; }
[DefaultValue("")]
[Description("<li /> css class")]
public string li_CssClass { get; set; }
[DefaultValue("#")]
[Description("<a /> href attribute")]
public string href { get; set; }
[TemplateContainer(typeof(MenuItem))]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public ITemplate Template { get; set; }
#endregion
}
public class SubMenuItemsCollectionEditor : CollectionEditor
{
public SubMenuItemsCollectionEditor(Type type)
: base(type)
{
}
protected override bool CanSelectMultipleInstances()
{
return false;
}
protected override Type CreateCollectionItemType()
{
return typeof(MenuItem);
}
}
animation.cs
public class animation
{
[DefaultValue("1000")]
[NotifyParentProperty(true)]
public string Delay { get; set; }
[DefaultValue("Opacity_Height")]
[NotifyParentProperty(true)]
public AnimationType AnimationType { get; set; }
[DefaultValue("fast")]
[NotifyParentProperty(true)]
public AnimationSpeed AnimationSpeed { get; set; }
[DefaultValue("false")]
[NotifyParentProperty(true)]
public bool DropShadow { get; set; }
}
public enum AnimationType
{
Opacity_Height,
Opacity,
Height
}
public enum AnimationSpeed
{
Fast,
Normal,
Slow
}
It works fine with below code (Without AnimationItems):
<MdsMenu:Menu ID="Menu1" runat="server">
<MdsMenu:MenuItem Text="MenuItem 01"></MdsMenu:MenuItem>
<MdsMenu:MenuItem Text="MenuItem 02"></MdsMenu:MenuItem>
<MdsMenu:MenuItem Text="MenuItem 03" />
</MdsMenu:Menu>
But when I add AnimationItems tag like the following code I receive the Exception :
Exception : Error Creating Control - Menu1Type 'JQueryMenu.Menu' does not have a public property named 'MenuItem'.
<MdsMenu:Menu ID="Menu1" runat="server">
<AnimationItems AnimationSpeed="Fast" AnimationType="Opacity_Height" DropShadow="true" Delay="1000" />
<MdsMenu:MenuItem Text="MenuItem 01"></MdsMenu:MenuItem>
<MdsMenu:MenuItem Text="MenuItem 02"></MdsMenu:MenuItem>
<MdsMenu:MenuItem Text="MenuItem 03" />
</MdsMenu:Menu>
You have two properties decorated with PersistenceMode.InnerDefaultProperty, and:
Only one property can be designated
the default property.
I'd suggest you decorate your AnimationItems property with PersistenceMode.InnerProperty instead:
[PersistenceMode(PersistenceMode.InnerProperty)]
public animation AnimationItems
{
get;
set;
}
EDIT: The above is not enough. I got the code to work with some tweaks:
First, disable child persistence and remove the defaultProperty argument from the [ParseChildren] attribute:
[AspNetHostingPermission(SecurityAction.Demand,
Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(SecurityAction.InheritanceDemand,
Level = AspNetHostingPermissionLevel.Minimal)]
[ParseChildren(true)]
[ToolboxData("<{0}:Menu runat=\"server\"></{0}:Menu>")]
public class Menu : WebControl, INamingContainer
{
}
Then, decorate the AnimationItems property with PersistenceMode.InnerProperty, as suggested above. Do the same to MenuItems and remove its [DesignerSerializationVisibility] attribute:
[PersistenceMode(PersistenceMode.InnerProperty)]
public animation AnimationItems { get; set; }
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<MenuItem> MenuItems
{
}
Finally, add a <MenuItems> element to your markup:
<MdsMenu:Menu ID="Menu1" runat="server">
<AnimationItems AnimationSpeed="Fast" AnimationType="Opacity_Height"
DropShadow="true" Delay="1000" />
<MenuItems>
<MdsMenu:MenuItem Text="MenuItem 01"></MdsMenu:MenuItem>
<MdsMenu:MenuItem Text="MenuItem 02"></MdsMenu:MenuItem>
<MdsMenu:MenuItem Text="MenuItem 03" />
</MenuItems>
</MdsMenu:Menu>