Cannot Programatically Select MudTreeView - razor

This example is a slight variation of the MudBlazor examples: It contains of a tree, where I programmatically try to select the "content" node:
<MudPaper Width="300px" Elevation="0">
<MudTreeView #bind-SelectedValue="SelectedValue" Hover="true">
<MudTreeViewItem #bind-Expanded="#folderOneExpanded" Value="#(".vscode")" Icon="#(folderOneExpanded ? Icons.Custom.Uncategorized.FolderOpen : Icons.Custom.Uncategorized.Folder)">
<MudTreeViewItem Value="#("launch.json")" Icon="#Icons.Custom.FileFormats.FileCode" />
<MudTreeViewItem Value="#("tasks.json")" Icon="#Icons.Custom.FileFormats.FileCode" />
</MudTreeViewItem>
<MudTreeViewItem #bind-Expanded="#folderTwoExpanded" Value="#("content")" Icon="#(folderTwoExpanded ? Icons.Custom.Uncategorized.FolderOpen : Icons.Custom.Uncategorized.Folder)">
<MudTreeViewItem Value="#("logo.png")" Icon="#Icons.Custom.FileFormats.FileImage" />
</MudTreeViewItem>
</MudTreeView>
</MudPaper>
<MudText Style="width: 100%" Typo="#Typo.subtitle1">Selected item: #SelectedValue</MudText>
string SelectedValue { get; set; }
bool folderOneExpanded;
bool folderTwoExpanded;
protected override void OnInitialized()
{
SelectedValue = "content";
}
(Try interactive example)
However when I run the program, the content node is not selected, even though everything else looks like it should be.
Why is that? How do I programmatically select nodes in a MudTreeView?
Other variants that do not work:
<MudTreeView SelectedValue="content" T="string">
<MudTreeViewItem Value="#("launch.json")" Icon="#Icons.Custom.FileFormats.FileCode" Selected="true"/>

Assign Activated to true on MudTreeViewItem you want to get selected. Then on the first render, call the StateHasChanged method if you want to do it after the component has been initialized. This should do the trick.
Example
Here's a modified version of your code:
<MudPaper Width="300px" Elevation="0">
<MudTreeView #bind-SelectedValue="SelectedValue" Hover="true">
<MudTreeViewItem #bind-Expanded="#folderOneExpanded" Value="#(".vscode")" Icon="#(folderOneExpanded ? Icons.Custom.Uncategorized.FolderOpen : Icons.Custom.Uncategorized.Folder)">
<MudTreeViewItem Value="#("launch.json")" Icon="#Icons.Custom.FileFormats.FileCode" />
<MudTreeViewItem Value="#("tasks.json")" Icon="#Icons.Custom.FileFormats.FileCode" />
</MudTreeViewItem>
<MudTreeViewItem Activated=#ContentSelected #bind-Expanded="#folderTwoExpanded" Value="#("content")" Icon="#(folderTwoExpanded ? Icons.Custom.Uncategorized.FolderOpen : Icons.Custom.Uncategorized.Folder)">
<MudTreeViewItem Value="#("logo.png")" Icon="#Icons.Custom.FileFormats.FileImage" />
</MudTreeViewItem>
</MudTreeView>
</MudPaper>
<MudText Style="width: 100%" Typo="#Typo.subtitle1">Selected item: #SelectedValue</MudText>
#code {
string SelectedValue { get; set; }
bool ContentSelected { get; set; }
bool folderOneExpanded;
bool folderTwoExpanded;
protected override void OnInitialized()
{
ContentSelected = true;
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
StateHasChanged();
}
}
https://try.mudblazor.com/snippet/GuQmOyvzQshYmJie

Related

How do you render a list of components in a loop (Blazor)?

I must be missing something very obvious with blazor... I want to simply render a list containing a component, yet there's no (obvious?) way to reference the iterator (which is a component) for rendering?
TodoList.razor
<input #bind="_newTodo" />
<button #onclick="#AddTodoItem">+</button>
#foreach (TodoItem todoItem in _todoItems)
{
// todoItem is a razor component, yet I can't simply render it here?
// <todoItem />
}
#code {
private IList<TodoItem> _todoItems = new List<TodoItem>();
private string _newTodo;
private void AddTodoItem()
{
if (!string.IsNullOrWhiteSpace(_newTodo))
{
_todoItems.Add(new TodoItem { Title = _newTodo });
_newTodo = string.Empty;
}
}
}
TodoItem.razor
<span>#Title</span>
#code {
public string Title { get; set; }
}
One solution to do that is have a class that holds the component properties and pass the properties to it
<input #bind="_newTodo" />
<button #onclick="#AddTodoItem">+</button>
#foreach (TodoItem todoItem in _todoItemsDto)
{
// Pass the Dto properties to the component
<TodoItem Title="#todoItem.Title" />
}
#code {
private IList<TodoItemDto> _todoItemsDto = new List<TodoItemDto>();
private string _newTodo;
class TodoItemDto {
public string Title { get; set; }
}
private void AddTodoItem()
{
if (!string.IsNullOrWhiteSpace(_newTodo))
{
_todoItems.Add(new TodoItemDto { Title = _newTodo });
_newTodo = string.Empty;
}
}
}
I just built a Help system that has a LinkButton component, and I render it like this:
foreach (HelpCategory category in Categories)
{
<LinkButton Category=category Parent=this></LinkButton>
<br />
}
Each HelpCategory has one or more Help Articles that can be expanded.
Here is the code for my LinkButton, it does more of the same:
#using DataJuggler.UltimateHelper.Core
#using ObjectLibrary.BusinessObjects
#if (HasCategory)
{
<button class="linkbutton"
#onclick="SelectCategory">#Category.Name</button>
#if (Selected)
{
<div class="categorydetail">
#Category.Description
</div>
<br />
<div class="margintop">
#if (ListHelper.HasOneOrMoreItems(Category.HelpArticles))
{
foreach (HelpArticle article in Category.HelpArticles)
{
<ArticleViewer HelpArticle=article Parent=this>
</ArticleViewer>
<br />
<div class="smallline"></div>
}
}
</div>
}
}
Sometimes the obvious solutiton is simpler and better.
TodoItem:
<span>#Title</span>
#code {
[Parameter] // add this parameter to accept title
public string Title { get; set; }
}
Page:
<input #bind="_newTodo"/>
<button #onclick="#AddTodoItem">+</button>
<ol>
#foreach (var todoItem in _todoItems)
{
<li>
<TodoItem Title="#todoItem.Title"/>
</li>
}
</ol>
#code {
private readonly IList<TodoItem> _todoItems = new List<TodoItem>();
private string _newTodo;
private void AddTodoItem()
{
if (!string.IsNullOrWhiteSpace(_newTodo))
{
_todoItems.Add(new TodoItem { Title = _newTodo });
_newTodo = string.Empty;
}
}
}
Output:
This may not be the best way to do it but it will avoid having 50+ attributes to set in the tag.
Component :
<h1>#Title</h1>
<h2>#Description</h2>
#code {
public string? Title { get; set; }
public string? Description { get; set; }
[Parameter]
public KanbanTask? Origin //KanbanTask is how I named this same component
{
get { return null; }
set
{
Title = value?.Title;
Description = value?.Description;
}
}
}
Then how to call it :
#foreach (var todoTask in TodoList)
{
<KanbanTask Origin="#todoTask" />
}
This is using the set of a property has a constructor. It works, but I think it's not excellent since set was not made for it in the first instance. If anyone else has an idea to make it better I'm interested
Yes, of course you can render a list with foreach. This article covers it well.
Here is an example. Note the use of the item in the click event so you know which item was clicked on. Note that this must be done using a lambda.
<section data-info="List of images">
#foreach (var item in this.Parent.CurrentCard.Images.OrderByDescending(a => a.InsertedDate))
{
<div class="border border-secondary m-2">
<img class="img-fluid" src="/api/image/fetch/#item.StorageName" alt="#item. Filename">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
Remove
</div>
</div>
}

How to write a function that can be available in all Razor views?

I'm trying to write a function that can bring a language resource from database in MVC 5 and Razor,
I want it to be very simple to use, for example, the following function should just get some text:
#T("ResourceType", "ResourceName")
I don't want to use #this. - just the the function name...
I saw some posts about it mentioning the line below, but still trying to understand how to do it
public abstract class WebViewPage<TModel> : System.Web.Mvc.WebViewPage<TModel>
Any help will be greatly appreciated.
Thanks in advance.
I finally found a way to do it, inspired by the NopCommerce project, see the code below.
The code can be used in any Razor (cshtml) view like this:
<h1>#T("StringNameToGet")</h1>
Also, note that pageBaseType needs to be updated with the correct new namespace,
this is the web.config in the Views folder - not the main one, should look like this:
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="MyNameSpace.Web.Extensions.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization"/>
<add namespace="System.Web.Routing" />
<add namespace="APE.Web" />
</namespaces>
</pages>
The code:
namespace MyNameSpace.Web.Extensions
{
public delegate LocalizedString Localizer(string text, params object[] args);
public abstract class WebViewPage : WebViewPage<dynamic>
{
}
/// <summary>
/// Update the pages element /views/web.config to reflect the
/// pageBaseType="MyNameSpace.Web.Extensions.WebViewPage"
/// </summary>
/// <typeparam name="TModel"></typeparam>
public abstract class WebViewPage<TModel> : System.Web.Mvc.WebViewPage<TModel>
{
private Localizer _localizer;
/// <summary>
/// Get a localized resources
/// </summary>
public Localizer T
{
get
{
if (_localizer == null)
{
//null localizer
//_localizer = (format, args) => new LocalizedString((args == null || args.Length == 0) ? format : string.Format(format, args));
//default localizer
_localizer = (format, args) =>
{
var resFormat = SampleGetResource(format);
if (string.IsNullOrEmpty(resFormat))
{
return new LocalizedString(format);
}
return
new LocalizedString((args == null || args.Length == 0)
? resFormat
: string.Format(resFormat, args));
};
}
return _localizer;
}
}
public string SampleGetResource(string resourceKey)
{
const string resourceValue = "Get resource value based on resourceKey";
return resourceValue;
}
}
public class LocalizedString : System.MarshalByRefObject, System.Web.IHtmlString
{
private readonly string _localized;
private readonly string _scope;
private readonly string _textHint;
private readonly object[] _args;
public LocalizedString(string localized)
{
_localized = localized;
}
public LocalizedString(string localized, string scope, string textHint, object[] args)
{
_localized = localized;
_scope = scope;
_textHint = textHint;
_args = args;
}
public static LocalizedString TextOrDefault(string text, LocalizedString defaultValue)
{
if (string.IsNullOrEmpty(text))
return defaultValue;
return new LocalizedString(text);
}
public string Scope
{
get { return _scope; }
}
public string TextHint
{
get { return _textHint; }
}
public object[] Args
{
get { return _args; }
}
public string Text
{
get { return _localized; }
}
public override string ToString()
{
return _localized;
}
public string ToHtmlString()
{
return _localized;
}
public override int GetHashCode()
{
var hashCode = 0;
if (_localized != null)
hashCode ^= _localized.GetHashCode();
return hashCode;
}
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != GetType())
return false;
var that = (LocalizedString)obj;
return string.Equals(_localized, that._localized);
}
}
}

Passing binded item to MvxCommand

Considering the following code:
<Mvx.MvxListView
android:id="#+id/items_list"
style="#style/ListNoDividers"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_above="#+id/footer_panel"
android:layout_below="#+id/intro_text"
local:MvxBind="ItemsSource Items;ItemClick DoItCommand"
local:MvxItemTemplate="#layout/item_template" />
I know that when I tap in item in the list, the DoItCommand will be invoked and the binded item will be past as a command parameter.
How can I use the same in a non MvxListView, like on this code snippet:
<LinearLayout
android:id="#+id/item1"
style="#style/ItemStyle"
local:MvxBind="Click DoItCommand, CommandParameter=PropertyInViewModel"
android:layout_marginBottom="#dimen/HalfDefaultInnerMargin" />
<LinearLayout
android:id="#+id/item1"
style="#style/ItemStyle"
local:MvxBind="Click DoItCommand, CommandParameter=OtherPropertyInViewModel"
android:layout_marginBottom="#dimen/HalfDefaultInnerMargin" />
Bottom line is that I need to pass a property value to DoItCommand using the command parameter.
As pointed out in the comments, using a similar approach to this, solves the issue!
public class MyLinearLayout : LinearLayout
{
public HhLinearLayout(Context context, IAttributeSet attrs)
: base(context, attrs)
{
Click += LinearLayoutClick;
}
public ICommand Command { get; set; }
public object CommandParameter { get; set; }
private void LinearLayoutClick(object sender, EventArgs e)
{
var command = Command;
var commandParameter = CommandParameter;
if (command == null || !command.CanExecute(commandParameter))
{
return;
}
command.Execute(commandParameter);
}
}

ConfigurationSection with nested ConfigurationElementCollections

Hopefully, I can present this problem to the brain trust of this site and someone will see my mistake.
I am working on a project where email text needs to be "mail merged" with information found in the properties of various internal classes. A typical symbol found in the email text might look like "{member name}, {mobile phone}, etc."
I would like to define the symbols and the classes they are found in using a ConfigurationSection in web.config. Here is my proposed configuration section:
<EmailSymbols>
<SymbolClasses>
<SymbolClass name="OHMember">
<Symbol name="Member Name" template="{0} {1}">
<add index="0" value="SMFirstName" />
<add index="1" value="SMLastName" />
</Symbol>
<Symbol name="Phone" template="{0}">
<add index="0" value="SMPhone" />
</Symbol>
</SymbolClass>
<SymbolClass name="Form">
<Symbol name="Contact Name" dataname="ContactName" />
</SymbolClass>
</SymbolClasses>
</EmailSymbols>
...and the code that I am trying to parse it with:
public class EmailSymbols : ConfigurationSection {
[ConfigurationProperty("SymbolClasses", IsRequired = true)]
public SymbolClassCollection SymbolClasses {
get {
return this["SymbolClasses"] as SymbolClassCollection;
}
}
}
[ConfigurationCollection(typeof(SymbolClass), AddItemName = "SymbolClass")]
public class SymbolClassCollection : ConfigurationElementCollection {
protected override ConfigurationElement CreateNewElement() {
return new SymbolClass();
}
protected override object GetElementKey(ConfigurationElement element) {
return ((SymbolClass)element).Name;
}
}
[ConfigurationCollection(typeof(Symbol), AddItemName = "Symbol")]
public class SymbolClass : ConfigurationElementCollection {
[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
public String Name {
get {
return this["name"] as String;
}
}
protected override ConfigurationElement CreateNewElement() {
return new Symbol();
}
protected override object GetElementKey(ConfigurationElement element) {
return ((Symbol)element).Name;
}
}
[ConfigurationCollection(typeof(TemplateValue), AddItemName = "add")]
public class Symbol : ConfigurationElementCollection {
[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
public String Name {
get {
return this["name"] as String;
}
}
[ConfigurationProperty("template", IsRequired = false)]
public String Template {
get {
return this["template"] as String;
}
}
[ConfigurationProperty("dataname", IsRequired = false)]
public String DataName {
get {
return this["dataname"] as String;
}
}
protected override ConfigurationElement CreateNewElement() {
return new TemplateValue();
}
protected override object GetElementKey(ConfigurationElement element) {
return ((TemplateValue)element).Index;
}
}
public class TemplateValue : ConfigurationElement {
[ConfigurationProperty("index", IsRequired = false, IsKey = true)]
public Int32 Index {
get {
return this["index"] == null ? -1 : Convert.ToInt32(this["index"]);
}
}
[ConfigurationProperty("value", IsRequired = false)]
public String Value {
get {
return this["value"] as String;
}
}
}
When I parse the section with this statement:
symbols = ConfigurationManager.GetSection("EmailSymbols") as EmailSymbols;
I receive this error message: "Unrecognized element 'Symbol'."
This is simply an area of .NET that I don't know my way around. Any help that anyone could give would be most appreciated.
Does my XML definition make sense and is it in the correct form? I want a collection of SymbolClass, each containing a collection of Symbol, each containing a collection of TemplateValue.
Again, thanks for your help.
Best Regards,
Jimmy
You could try to override the Init() method of the SymbolClass class:
protected override void Init()
{
base.Init();
this.AddElementName = "Symbol";
}
You an also remove [ConfigurationCollection(typeof(SymbolClass), AddItemName = "SymbolClass")] and the others like it from above the class declarations as their not doing anything.

log4net filtering on exception message?

How can I filter logging based on a logged exception's message?
Code looks like this:
try {
someService.DoSomeWorkflow();
} catch(Exception e) {
log.Error("Hey I have an error", e);
}
Config looks like this:
<appender name="EventLogger" type="log4net.Appender.EventLogAppender">
<applicationName value="foo" />
<layout type="log4net.Layout.PatternLayout" value="PID:%P{pid}: %message" />
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="TextInsideTheException" />
</filter>
</appender>
I'm finding that I can filter only on the logged message ("Hey I have an error") but it seemingly ignores the exception's message. Since this is in our production environment I can't make any code changes so I can't change the logged message. Is there some configuration that would specify to also check the exception's message?
By subclassing FilterSkeleton, you can implement a filter that evaluates the exception text. Or exception type for that matter.
Here are basic implementations based on Peter's accepted answer
using System;
using log4net.Core;
namespace log4net.Filter
{
public abstract class ExceptionFilterBase : FilterSkeleton
{
public override FilterDecision Decide(LoggingEvent loggingEvent)
{
if (loggingEvent == null)
throw new ArgumentNullException("loggingEvent");
var str = GetString(loggingEvent);
if (StringToMatch == null || string.IsNullOrEmpty(str) || !str.Contains(StringToMatch))
return FilterDecision.Neutral;
return AcceptOnMatch ? FilterDecision.Accept : FilterDecision.Deny;
}
protected abstract string GetString(LoggingEvent loggingEvent);
public string StringToMatch { get; set; }
public bool AcceptOnMatch { get; set; }
}
public class ExceptionMessageFilter : ExceptionFilterBase
{
protected override string GetString(LoggingEvent loggingEvent)
{
return loggingEvent.ExceptionObject == null
? null : loggingEvent.ExceptionObject.Message;
}
}
public class ExceptionTypeFilter : ExceptionFilterBase
{
protected override string GetString(LoggingEvent loggingEvent)
{
return loggingEvent.ExceptionObject == null
? null : loggingEvent.ExceptionObject.GetType().FullName;
}
}
public class ExceptionStackFilter : ExceptionFilterBase
{
protected override string GetString(LoggingEvent loggingEvent)
{
return loggingEvent.ExceptionObject == null
? null : loggingEvent.ExceptionObject.StackTrace;
}
}
}
Configuration file
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="Client.log" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{yyyy/MM/dd HH:mm:ss,fff} [%-5level] %logger - %message%newline" />
</layout>
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="Token is not valid." />
<acceptOnMatch value="false" />
</filter>
<filter type="log4net.Filter.ExceptionMessageFilter, YourAssembly">
<stringToMatch value="Application is not installed." />
<acceptOnMatch value="false" />
</filter>
<filter type="log4net.Filter.ExceptionTypeFilter, YourAssembly">
<stringToMatch value="System.Deployment.Application.DeploymentException" />
<acceptOnMatch value="false" />
</filter>
<filter type="log4net.Filter.ExceptionStackFilter, YourAssembly">
<stringToMatch value="at System.Deployment.Application.ComponentStore.GetPropertyString(DefinitionAppId appId, String propName)" />
<acceptOnMatch value="false" />
</filter>
</appender>
Try this:
log.Error("Hey I have an error: " + e.Message);
Edit: Sorry, didn't see that you cannot change that line...