I have something like this where I am binding a value in the child component from the parent component.
<div class="col">
<Child #bind-InputValue="#parentInputValue"/>
</div>
I need to know when the child has changed that InputValue so I can invoke a method in the parent because there are other components that need to be updated based on the values in parentInputValue.
I've tried binding InputValueChanged, but that throws me an error because it says it is used in two or more places. I'm guessing that the #bind-InputValue sets that up already.
The question I have is - can I somehow catch that event in my parent component? There are around 12 different inputs in that child component and I want to somehow catch an event when the child changes anything in the parent InputValue object.
I've also tried setting up some parameter in the child called OnChange that I would invoke if anything changed, but I'm using these weird dropdown things that make it hard to make that work.
Well, when I need to do that I used a INotifyPropertyChanged implementation:
public class Model: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _value;
public string Value
{
get { return _value; }
set
{
_value = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
}
<div class="col">
<Child #bind-InputValue="#model.Value"/>
</div>
#code {
private Model model = new Model();
protected override void OnInitialized()
{
model.PropertyChanged += (s, e) =>
{
Console.WriteLine($"Model {e.PropertyName} has changed.");
};
}
}
Related
I know that there is a post closely related to my question (How to animate state transitions in Blazor?).
However, my problem is the following : Consider a list of toasts for instance. I want to display 5 toasts, they can be removed by clicking on them, or they remove themselves when their timers is out.
Code simplified as example.
Component Toasts
#inject ToastService ToastService
<div class="toasts">
#foreach (ToastData data in ToastService.List)
{
<Toast Data="data" />
}
</div>
#code {
protected override async Task OnInitializedAsync()
{
ToastService.OnListChange += RefreshList;
}
public void RefreshList()
{
StateHasChanged();
}
}
ToastService
public class ToastService
{
private List<ToastData > _list;
public List<ToastData > List
{
get
{
var list = _list.Take(5).ToList();
foreach (ToastData data in list)
{
data.StartCountDown();
}
return list;
}
}
public event Action OnListChange;
public ToastService()
{
_List = new List<ToastData >();
}
public async Task CreateToast(ToastData data)
{
_list.Add(data);
OnListChange?.Invoke();
}
public async Task RemoveToast(ToastData data)
{
_list.Remove(data);
OnListChange?.Invoke();
}
}
ToastData
public class ToastData
{
private ToastService _toastService;
private bool _isCountdownStarted;
private System.Timers.Timer _countdown;
public ToastData(ToastService toastService)
{
_toastService= toastService;
}
public void StartCountDown()
{
if (_isCountdownStarted)
return;
_countdown = new System.Timers.Timer(5000);
_countdown.AutoReset = false;
_countdown.Start();
_countdown.Elapsed += RemoveNotification;
_isCountdownStarted = true;
}
public void RemoveNotification()
{
_countdown.Close();
_toastService.RemoveNotification(this);
}
private void RemoveNotification(object source, ElapsedEventArgs args)
{
RemoveNotification();
}
}
Component Toast
<div #onclick="Clicked" class="toast">
Some message on a toast
</div>
#code {
[Parameter] public ToastData Data { get; set; }
public void Clicked()
{
Data.RemoveNotification();
}
}
The above example work fine, cause there's no animation yet.
But now, I want to add an animation of the Toast component. So I modify the ToastData to first call a Hide method, this method will notify the Toast component who will add a CSS class that will animate the removal.
This works fine, until this happen :
Toast component 1 start to animate the removal
Toast component 2 start to animate the removal
Toast 1 is removed, the list is refreshed
Toast 2 is now Toast 1, the animation is gone, and suddenly it disappear
Worst even, a Toast 3 would become Toast 2 and will animate even if not intended to be removed yet.
I understand that Blazor choose to reuse HTML, that's why in the Toasts component, all Toast component will always be the same. That's why I put the logic in the ToastData.
I'm guessing I'm missing something...
Any help or insight appreciated!
When rendering components in a loop, the #key directive attribute is your friend.
It will help Blazor keep the relationship between data and a component instance.
https://learn.microsoft.com/en-us/aspnet/core/blazor/components/?view=aspnetcore-6.0#use-key-to-control-the-preservation-of-elements-and-components
I have to retrieve some data from my database to dynamically create a TreeView and select some CheckBoxTreeItems from this TreeView. This TreeView represents permissions to a menu structure.
My doubt is when I create the TreeView and select specific items from the Tree according to the user's permissions programmatically, the parents items don't have any status change (selected or indeterminate). But when I select any item directly from the interface, the parents get updated.
For example, here I have my screen when I select the items programmatically:
You can see that I have two menu items selected, but the parents aren't.
On this image, I have selected the same menu items using the screen, and the parents were updated with indeterminate status or selected if I select all children inside the submenu.
I have gone through the documentation, google and here on Stack Overflow, but only found examples to update the children.
Is there a way to update the parents programmatically or to call the event executed from the screen when an item is selected?
EDIT:
All items from the Tree have the independent property set to false.
I came with a workaround for this problem.
I had to first create all the TreeView structure, and change the selected property after using this code snippet:
Platform.runLater(new Runnable() {
#Override
public void run() {
selectItems();
}
});
Here is the code to verify the TreeItems:
private void selectItems(){
TreeItem root = tree.getRoot();
if (root != null) {
selectChildren(root);
}
}
private void selectChildren(TreeItem<TesteVO> root){
for(TreeItem<TesteVO> child: root.getChildren()){
// HERE I CHECK IF THE USER HAS PERMISSION FOR THE MENU ITEM
// IF SO, I CHANGE THE SELECTED PROPERTY TO TRUE
if (child.getValue().id == 4) {
((CheckBoxTreeItem) child).setSelected(true);
}
// IF THERE ARE CHILD NODES, KEEP DIGGING RECURSIVELY
if(!child.getChildren().isEmpty()) {
selectChildren(child);
}
}
}
If there is a simpler way, please let me know!
This is not the case. Parent items do get automatically get set to the indeterminate state when you select a child item. I'm not sure if this is something that got corrected from the time that this question was posted, probably not.
My guess is that there's a programming bug in how the node was selected or how the TableView was constructed and initialized.
Here's some code that shows what I'm doing, and it works! In my case, I'm using a CheckBoxTreeItem<File> for the TreeItem.
How the treeview was created
treeView = new TreeView(root);
treeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observableValue, Object o, Object t1) {
CheckBoxTreeItem<File> node = (CheckBoxTreeItem<File>)t1;
if (node.getValue() != currentFile) {
setFileDetail(node);
showChildren(node);
}
}
});
treeView.setCellFactory(new CallBackWrapper());
treeView.setShowRoot(false);
Below show the CallBackWrapper class.
private class CallBackWrapper implements Callback<TreeView<File>, TreeCell<File>> {
Callback<TreeView<File>, TreeCell<File>> theCallback;
private CallBackWrapper() {
theCallback = CheckBoxTreeCell.<File>forTreeView(getSelectedProperty, converter);
}
#Override
public TreeCell<File> call(TreeView<File> fileTreeView) {
return theCallback.call(fileTreeView);
}
final Callback<TreeItem<File>, ObservableValue<Boolean>> getSelectedProperty = (TreeItem<File> item) -> {
if (item instanceof CheckBoxTreeItem<?>) {
return ((CheckBoxTreeItem<?>) item).selectedProperty();
}
return null;
};
final StringConverter<TreeItem<File>> converter = new StringConverter<TreeItem<File>>() {
#Override
public String toString(TreeItem<File> object) {
File item = object.getValue();
return fileSystemView.getSystemDisplayName(item);
}
#Override
public TreeItem<File> fromString(String string) {
return new TreeItem<File>(new File(string));
}
};
}
And lastly here some code that the selection was made in:
boolean selectNode(CheckBoxTreeItem<File> parentNode, String name) {
Object[] children = parentNode.getChildren().toArray();
for (Object child : children) {
CheckBoxTreeItem<File> childItem = (CheckBoxTreeItem<File>) child;
if (name.equals(childItem.getValue().getName())) {
childItem.setSelected(true);
//treeView.getSelectionModel().select(child); <-- this does not work!
return true;
}
}
return false;
}
I have two fields binded with a fieldgroup. I need to make the second field change when the first field loses focus.
What I have so far:
class MyBean {
private String first;
private String second;
//getters, setters
}
class MasterData extends CustomComponent{
private TextField first;
private TextField second;
//add to layout, other tasks
}
//the calling code
FieldGroup fieldgroup = new FieldGroup(new MyBean());
fieldgroup.bindMemberFields(new MasterData());
((AbstractComponent)fg.getField("first")).setImmediate(true);
fg.getField("first").addValueChangeListener(new ValueChangeListener() {
#Override
public void valueChange(ValueChangeEvent event) {
MyBean bean = fg.getItemDataSource().getBean();
bean.setSecond((String) event.getProperty().getValue());
try {
fg.commit();
} catch (CommitException e) { }
}
});
The value change event is called but the second field never gets updated on the screen. How canI force the fieldgroup to repaint its field?
You might want to take a look at BlurListener.
Also, I think you need to update the value of the TextField "manually". Changing it in the bean might no update the TextField. When you call commit() on the FieldGroup it commits the values in the field to the bean, not the other way around. So in the listener's implementation, it might look something like this:
second.setValue(event.getProperty().getValue());
try {
fg.commit();
} catch (CommitException e) { }
Does anyone know, is there any way to catch ItemClick Event in a Flex ComboBox (or anything similar). Maybe there's any trick .. :) I do realize, that I can customize it, but this not suits my case.
Thanks for your time :)
As you can see in mx:ComboBox sources, the function, creating the dropdown list, is private, the listener to ITEM_CLICK is private and the list itself is also private:
private var _dropdown:ListBase;
private function getDropdown():ListBase
{
// ...
_dropdown = dropdownFactory.newInstance();
// ...
_dropdown.addEventListener(ListEvent.ITEM_CLICK, dropdown_itemClickHandler);
// ....
}
private function dropdown_itemClickHandler(event:ListEvent):void
{
if (_showingDropdown)
{
close();
}
}
So you can not even extend ComboBox.
The only public thing is dropdownFactory, which theoretically can be overriden to somehow register the created dropdown list or create extended list. But the problem I see is that ComboBox is not the parent of dropdown list - PopupManager is. This can make dispatching (bubble) events quite difficult.
I think the following document will be helpful
ItemClick event in flex List
I found this solution. I just want a spark dropdownlist with itemClick event and without itemselect option (don't show selected item label on button)
[Event(name="itemClick", type="mx.events.ItemClickEvent")]
public class ItemClickDropDownList extends DropDownList
{
public function ItemClickDropDownList()
{
super();
}
override public function closeDropDown(commit:Boolean):void
{
super.closeDropDown(commit);
var e:ItemClickEvent = new ItemClickEvent(ItemClickEvent.ITEM_CLICK, true);
e.item = this.selectedItem;
e.index = this.selectedIndex;
dispatchEvent(e);
//Deselect item
this.selectedIndex = -1;
}
Let's say you have a large number (N) of spark buttons in your app. Let's also say that your buttons all have very similar skins (size, various effects, etc) - the only difference being the specific png that they use as their BitmapImage.
Do you end up with N skin files, all differing by 1 line? Or is there a smarter way to do this while not adding a lot of code when you create the buttons in MXML (in fact, ideally, none).
Creating a custom Button with a icon SkinPart typed as a BitmapImage will allow you to use the same Skin for all buttons :
<YourCustomButton icon="#Embed('yourIconFile.png') />
CustomButton.as
public class CustomButton extends Button
{
[SkinPart(required="false")]
public var iconContainer:BitmapImage;
private var _icon:Object;
public function CustomButton()
{
super();
}
override protected function partAdded(partName:String, instance:Object):void
{
super.partAdded(partName, instance);
if (instance == iconContainer && _icon)
iconContainer.source = _icon;
}
public function get icon():Object
{
return _icon;
}
public function set icon(value:Object):void
{
if (iconContainer)
iconContainer.source = value;
_icon = value;
}
}