DynamicVIewPanel ignores oncolumnclick if customized - xpages

Using a DynamicViewPanel and a customizer bean to change the column that is a link. The column that is the first column in the view is being hidden via the bean using the setRendered() method and another column is made the link using the setDisplayAs("link") method. That works fine but the oncolumnclick event never fires. It appears that the event is tied to the original column and not the "new" link column.
Anyway to tie an event to the "new" link column? I need to set a scoped variable before navigating to the new XPage.
Howard
Got this to work using something like Maire suggested.
In the method, public void afterCreateColumn(FacesContext context, int index,
ColumnDef colDef, IControl column) {, I added this code to get the event from the column I was hiding:
//Hide the first column in this view
if(dynamicColumn.getColumnName().equalsIgnoreCase("$2")){
//dynamicColumn.setRendered(false);
dynamicColumn.setDisplayAs("hidden");
String type = dynamicColumn.getChildren().get(0).getClass().toString();
DebugToolbarBean.get().info("type is " + type);
event = (XspEventHandler) dynamicColumn.getChildren().get(0);
}
I also created an event variable to hold this:
com.ibm.xsp.component.xp.XspEventHandler event;
Then, where I made the column I wanted to be a link I added:
if (dynamicColumn.getColumnName().equalsIgnoreCase("OrderDate")){
//make it a link
dynamicColumn.setDisplayAs("link");
DebugToolbarBean.get().info("make OrderDate a link");
if (event != null){
dynamicColumn.getChildren().add(event);
DebugToolbarBean.get().info("adding event");
} else {
DebugToolbarBean.get().info("event is null");
}
}

I haven't tried this, but you could try moving the location of the xp:eventHandler in the control tree.
As in, the initial dynamically generated control tree is like:
xp:viewColumn id="column1" displayAs="link"
xp:eventHandler event="onclick"
xp:viewColumn id="column2" displayAs="text"
And your code is changing it to switch the displayAs values:
xp:viewColumn id="column1" displayAs="text"
xp:eventHandler event="onclick"
xp:viewColumn id="column2" displayAs="link"
but the xp:eventHandler would still listen for clicks on its ancestor column1.
You could move the eventHandler in the customizerBean, like so:
public void afterCreateColumns(FacesContext context, UIDynamicViewPanel panel) {
UIViewColumn col1 = (UIViewColumn) panel.getChildren().get(0);
UIViewColumn col2 = (UIViewColumn) panel.getChildren().get(1);
UIEventHandler eventHandler = (UIEventHandler) col1.getChildren().get(0);
// move the eventHandler to col2.
col2.getChildren().add(eventHandler);
}
[The code in ExtLib that creates the control tree structure is:
com.ibm.xsp.extlib.component.dynamicview.DominoDynamicColumnBuilder.createColumn(...) ]

Related

.net maui MVVM Binding a SelectedItemCommand and SelectedItemParameter from a CollectionView

So I am working with SQLite, CommunityToolkit.Mvvm.ComponentModel;
I have database containing a table of friends. I can bind this to a CollectionView.
I am following https://www.youtube.com/watch?v=8_cqUvriwM8 but trying to use MVVM approach.
I can get it to work happily with SelectionChanged and an event, but not with SelectionChangedCommand and I can't get access to the Friend item in the list.
Here is the relevant xaml
<CollectionView Grid.Row="2"
x:Name="FriendsList"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectionChangedCommand}"
SelectionChangedCommandParameter="{Binding .}"
SelectionChanged="OnSelectionChanged" >
Here is the relevant part of the code (I'm using the code behind for the xaml just for testing)
public MainPage()
{
InitializeComponent();
this.BindingContext = this; //cool for binding the xaml to the code behind.
}
...
//This works fine (so why do I bother with mvvm?)
public void OnSelectionChanged(Object sender, SelectionChangedEventArgs e)
{
Console.WriteLine("Selection changed click");
Friend f = e.CurrentSelection[0] as Friend;
Console.WriteLine(f.LName);
}
//Can't get this to work, though it will register the click
public ICommand SelectionChangedCommand => new Command(SelectionChangedControl);
public void SelectionChangedControl()
{
Console.WriteLine("selection made");
}
My thinking was that if I could do this to get at the Friend item since the CommandParameter is, as I understand, to provide an object?
public ICommand SelectionChangedCommand => new Command<Friend>(SelectionChangedControl);
public void SelectionChangedControl(Friend f)
{
Console.WriteLine("selection made");
}
But the command doesn't even fire now. Clearly I am way off beam.
Any ideas please. (Oh by the way I have tried commenting out one or the other just in case).
BTW is there a reference (not MS docs) which explains this stuff in beginners terms?
Is there an API reference to dot net Maui?
EDIT: From the documentation https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/collectionview/selection
Single selection
When the SelectionMode property is set to Single, a single item in the CollectionView can be selected. When an item is selected, the SelectedItem property will be set to the value of the selected item. When this property changes, the SelectionChangedCommand is executed (with the value of the SelectionChangedCommandParameter being passed to the ICommand, and the SelectionChanged event fires.
How do I get at value of the SelectionChangedCommandParameter, i.e. the row object, i.e. my Friend object?
EDIT2: Somehow I think I need to get at the CurrentSelection[0] but I don't know how.
I've learnt that I can do something like this (from the docs)
SelectionChangedCommand="{Binding SelectionChangedCommand}"
SelectionChangedCommandParameter="Hello G"
and
public ICommand SelectionChangedCommand => new Command<string>( (String s) =>
{
Console.WriteLine($"selection made {s}");
});
and the command is picked up and displayed, so my thinking is that using {Binding .} is not what I want, but what do I bind to?
SelectionChangedCommandParameter ={Binding ???}
Thanks, G.
first, bind SelectedItem
SelectedItem="{Binding SelectedFriend}"
then in your VM create a property for that bound item
public Friend SelectedFriend { get; set; }
then in your Command you can use that property
public void SelectionChangedControl()
{
Console.WriteLine(SelectedFriend.Name);
}
When you use . at CollectionView.SelectionChangedCommandParameter, it points at the BidingContext of its parent view.
e.g. If your CollectionView is in a ContentPage, . points at the BindingContext of the ContentPage.
If you want a reference of each item in FriendsList, one of solutions is use SelectedItem.
Try something like this:
<CollectionView
Grid.Row="2"
x:Name="FriendsList"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectionChangedCommand}"
SelectionChangedCommandParameter="{Binding Path=SelectedItem, Source={x:Reference FriendsList}}">
or
<CollectionView
Grid.Row="2"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectionChangedCommand}"
SelectionChangedCommandParameter="{Binding Path=SelectedItem, Source={RelativeSource Self}}">
References:
Bind to self (Source={RelativeSource Self}}):
https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/data-binding/relative-bindings#bind-to-self
Note for Multiple Selections
I got hung up trying to bind multiple selections to the view model without linking it in the code behind. This page was the only relevant search result and helped a lot, but was missing a piece for multiple selections.
View.xaml
<CollectionView ItemsSource="{Binding DataItems}"
SelectedItems="{Binding SelectedData}"
SelectionMode="Multiple"
SelectionChangedCommand="{Binding SelectionChangedCommand}">
....
Couple of things to mention for the view model. I'm using CommunityToolkit.Mvvm, so the [ObservableProperty] annotation creates the property for you in proper camel case, and the [RelayCommand] for OnMethodName will drop the 'On' and just be MethodNameCommand.
ViewModel.cs
[ObservableProperty]
ObservableCollection<CustomDataItem> dataItems;
[ObservableProperty]
ObservableCollection<object> selectedData;
[RelayCommand]
void OnSelectionChanged()
{
foreach(var o in SelectedData)
{
if(o is CustomDataItem i)
...
}
}
The major takeaway though is that the SelectedItems must be a List<object> , they cannot be the <CustomDataItem>. I spent a couple hours searching and trying different things until I gave up and just linked the event handler in the code behind. But then I couldn't pre-select the items as described here until I changed them to the object list. So that list will populate both ways and you just have to cast it to the data type you're using.
Anyway, might've been obvious for some but maybe this will help anyone like me who just assumed the SelectedItems would be the same as the SelectedItem but in a list.
#Jason I'm laughing so much, I just figured it out and then came to post and saw your answer. Thankyou so much for your help.
For the record I found this post https://www.mfractor.com/blogs/news/migrating-listview-to-collectionview-in-xamarin-forms-interactivity
and eventually I figured out that I needed the SelectedItem as you pointed out. I think that because this wasn't needed (or is implicit) in the SelectionChanged click event.
Anyhow in my xaml
<CollectionView Grid.Row="2"
x:Name="FriendsList"
SelectionMode="Single"
SelectedItem="{Binding SelectedItem}"
SelectionChangedCommand="{Binding SelectionChangedCommand}"
SelectionChangedCommandParameter="{Binding .}" >
In my code
public Friend SelectedItem { get; set; }
//respond to item select
public ICommand SelectionChangedCommand => new Command<Object>((Object e) =>
{
Console.WriteLine($"selection made {SelectedItem.FName}");
});
Your code is much simpler of course.
You pointed out that SelectionChangedCommandParameter="{Binding .}" was (probably) not needed, so what is it's purpose?
What is the object e that is being returned in my code? I assume it is related to the SelectionChangedCommandParameter?
In my immediate window I get
e
{Census.MainPage}
base: {Microsoft.Maui.Controls.ContentPage}
AddFriendCommand: {Microsoft.Maui.Controls.Command}
SelectedItem: {Census.Classes.Friend}
SelectionChangedCommand: {Microsoft.Maui.Controls.Command<object>}
And is it possible to trace through from the xaml to the code. For instance when I was trying to figure things out I would have liked to have trapped the item click event in the xaml and see what is was doing? (Especially since it didn't at times touch a breakpoint in my code.
Just idle questions and not expecting or needing an answer unless someone is so inclined.
Thank you much again #Jason, you are a star! :)

#StateObject for a NSManagedObject without context not publishing changes

Like in a lot of apps, I have a list of items (populated by a Core Data fetch request), a sheet to create new items, and a sheet to edit an item when tapping on a row in my list. I'm trying to unify both forms to create and edit an update, and put the cancel / save logic in a superview of the form.
So I've something like this:
ListView: a list with row populated by a Core Data fetch request
AddView: a NavigationView with the FormView embed + cancel and save button
EditView: a NavigationView with the FormView embed + cancel and save button
FormView: a TextField to update the name of the item
In the init() for the AddView, I create a new NSManagedObject without any context (I do that because I don't want my ListView to be updated when I create a new item in the AddView, but only when I save this item -> alternative could be to use a child context, or filter the fetch request results based on the isInserted or objectID.isTemporaryID of the return objects). AddView contains a NavigationView with the FormView embed, a cancel button, and a save button. This save button is disabled based on a computed property on the managed object (name for the object can't be nil).
In the EditView, I pass the item that was tapped from the ListView. This item is an existing NSManagedObject attached to the main viewContext of the app (coming from the fetch request of the ListView). EditView contains a NavigationView with the FormView embed, a cancel button and a save button (exactly like the AddView). This save button is also disabled based on the same computed property.
My issue is that when I update the name of the item from the TextField in my FormView, the condition to enable / disable the save button is not working for the AddView (this AddView is actually not refreshed when I change the item name from the FormView) but working for the EditView (this EditView is refreshed when I change the item name from the FormView). If I attach a context to the new NSManagedObject in the init() of the AddView, the condition is working like in the EditView.
So it appears that a NSManagedObject without any context is not observed by SwiftUI? Am I missing anything or is that a bug?
I wouldn't be surprised (but haven't verified) if the change-notifying ability of a managed object depends on the presence of the context. I can't think of a situation where you'd want to create a managed object without a context.
You should use a child context. The context does a lot of work for you in Core Data (managing relationships, probably change notifying, validation etc), and offers a simple way to cancel / save changes - just save the child context and the data flows back up into the main context, or discard the context to abandon.
A workaround to get the change notifications is to add this override to the NSManagedObject subclass:
override public func willChangeValue(forKey key: String) {
super.willChangeValue(forKey: key)
self.objectWillChange.send()
}
NSManagedObject could be subclassed to add this override (more info here and here)
We can be more specific on the update value if we don't want to trigger the change for every key. This will also work for relationships (not the case for the above solution).
func setName(_ name: String) {
objectWillChange.send()
self.name = name
}
In this case, my AddView updates even if the observed object does not have a context (change notifications are probably trigger only when a context exists for the object). The save button is disabled / enabled based on the following computed property in my NSManagedObject subclass.
var canBeSaved: Bool {
if self.name.isEmpty {
return false
} else {
return true
}
}

kendo ui angular grid - how to leave row when user hits enter key instead of clicking off of row

Ive got a working grid, using in-line editing thanks to this example
https://www.telerik.com/kendo-angular-ui/components/grid/editing/editing-row-click/
What I need to do now, is force the saving upon a user hitting the enter key, instead of clicking away onto another row or away from the current row. I suppose I could add a "save" button in the header?
You could probably use cellClose event in your html (cellClose)="cellCloseHandler($event)" - API Documentation
You could then write your own code (in typescript) in cellCloseHandler() to modify and save the updated items accordingly.
From Kendo UI for Angular Documentation:
In-Cell Editing
You could capture the Enter key hits and force executing cellCloseHandler() like that:
#HostListener('keydown', ['$event'])
public keydown(event: any): void {
console.log("keydown event", event);
if (event.keyCode === 13) {
this.cellCloseHandler();
}
}
Similar to Giannis answer but with small modifications:
Add keydown event to kendo-grid tag.
Call grid.closeCell() instead of calling the closeHander directly.
Template
<kendo-grid #grid
[data]="data$ | async"
(cellClose)="cellCloseHandler($event)"
(keydown)="onKeydown(grid, $event)"
>
Class
onKeydown(grid: GridComponent, e: KeyboardEvent) {
if (e.key === 'Enter') {
grid.closeCell();
}
}
cellCloseHandler(args: CellCloseEvent) {
const { formGroup, dataItem } = args;
// save to backend etc
}
Calling grid.closeCell(); will make the grid to call cellCloseHandler
You dont have to implement a HostListener for Keydown by yourself. If you set the input navigable to true, the cellClose event will get triggered when pressing Enter while editing a cell or row. This way you get the data of the row in your cellCloseHandler aswell for saving it.
<kendo-grid [navigable]="true" (cellClose)="cellCloseHandler($event)"></kendo-grid>

Populating a dropdown based on selection in another dropdown in ASP.NET MVC 5

hoping someone can point out where I am going wrong. I am working on an MVC 5 ASP.NET application. In my View I have a form which, amongst other controls, has 2 dropdown boxes.The first dropdown is populated with values in the Viewbag, but I want to populate the second dropdown based on the value selected in the first dropdown. I've read lots of other posts but still can't work it out.
Here is the code for the dropdowns.
#Html.DropDownList("EquipmentPortList", new SelectList(ViewBag.EquipmentPortList, "hvid", "hvnamn"), "--Select Equipment--")
#Html.DropDownList("PortConnectedList", Enumerable.Empty<SelectListItem>(), "--Select Port--")
Here is the change event code for the first dropdown :-
$(document).on('change', '#EquipmentPortList', function () {
var url = '#Url.Action("GetPortInt", "bearers")';
var ports = $('#PortConnectedList');
var id = $(this).val();
$.getJSON(url, { portid: id }, function (response) {
ports.empty();
$.each(response, function (index, item) {
ports.append($('</option>').text(item.portnamn).val(item.portid));
});
});
});
Here is the method in the controller :-
public ActionResult GetPortInt(int portid)
{
var PortConnectedList = from h in nadb.hvportar
where h.porthvid == portid && (h.portnamn.Contains("NT") || (h.portnamn.Contains("Port")) || (h.portnamn.Contains("/")))
orderby h.portid
select new { h.portid, h.portnamn };
return Json(PortConnectedList, JsonRequestBehavior.AllowGet);
}
OK, so the first dropdown populates correctly, and when I select a value I can see (using alerts) that the change event fires and the value is correctly selected.
I can see with the use of a breakpoint that the method is triggered and returns the correct data from the database, but after that I don't know what is wrong as the dropdown does not populate.
I picked up the code change event code from another post and amended it to fit my own project but I can't see what I'm missing. Thanks.
Stephen, your DotnetFiddle link actually showed me the answer. This line :-
ports.append($('</option>').text(item.portnamn).val(item.por‌​‌​tid));
Should read
ports.append($('<option></option>').text(item.portnamn).val(‌​‌​item.portid));
Once I changed that it worked. Many thanks.

How to get updated model values in a valueChangeListener method?

I am stuck with an issue on valueChangeListener. I am using JSF 1.2 on Weblogic 6.0.
I have a page, where on top of the page there are 2 radio buttons with name as Name1 and Name2. When Name1 is clicked, the Name1 details(there are about 30 fields) is displayed below the radio buttons and when Name2 is clicked, Name2 details is displayed. Now the user can updated the clicked named details. e.g. the user clicks on Name1 and changes the address field and then clicks on Name2 and changes the age of Name2. When the user clicks back Name1, the address should be shown updated with the new value and again when the user clicks on Name2, the age should be updated.
I have used valueChangeListener to tackle it because I need the old and new value of the changed event. The problem is, as the valueChangeListener is invoked at the end of the VALIDATION phase, I am not getting the updated address of the Name1 field in the valueChangeListener method. Can someone help me out to get any workaround ?
as the valueChangeListener is invoked at the end of the VALIDATION phase, I am not getting the updated address of the Name1 field in the valueChangeListener method
Queue the event to the INVOKE_ACTION phase so that it acts like as an action(listener) method.
public void valueChangeListenerMethod(ValueChangeEvent event) {
if (event.getPhaseId() != PhaseId.INVOKE_APPLICATION) {
event.setPhaseId(PhaseId.INVOKE_APPLICATION);
event.queue();
return;
}
// Do your original job here.
// It will only be entered when current phase ID is INVOKE_APPLICATION.
}
As INVOKE_ACTION phase runs after UPDATE_MODEL_VALUES, the updated model values will be just available "the usual way".
(Extension to BalusC answer)
As noted in my comment to BalusC answer, here the support method with usage example:
public static boolean moveToEndOfUpdateModel(FacesEvent event) {
if (event.getPhaseId() == null || event.getPhaseId().getOrdinal() < PhaseId.UPDATE_MODEL_VALUES.getOrdinal()) {
// see UIViewRoot.processUpdates(): first components.processUpdates(), then broadcastEvents()
event.setPhaseId(PhaseId.UPDATE_MODEL_VALUES);
event.queue();
return true;
}
return false;
}
(And to repeat the reason for the relevant change by me:
The event is put at the end of the queue, so if you have a valueChangeEvent together with an action-method the event will be executed after the action-method - typically not what you want.)
In the listener method I use it like this:
public void myValueChangeMethod(ValueChangeEvent vce) {
if (JSFUtils.moveToEndOfUpdateModel(vce)) return;
// ... perform my actions here, with access to already updated bean values ...
}
And for JSF 2+ you can better use <f:ajax listener="..."> - if you are not forced to use a component lib not supporting this (like the old Trinidad).

Resources