p:autoComplete insert a new value to database - jsf

I use in my project a p:autoComplete with multiSelection = true and forceSelection = false. I use it to add "tags" to my object.
This works indeed very good and is very useful.
Now I got a question and I hope it is possible to solve it.
Example
Tags stored in database: test, example
If I type test, or example, autocomplete makes a suggestion.
Now i would like to type:
primefaces
which is not found in db. Now I want to push this value from my autocomplete into the database.
How can I do that?
Regards
LStrike

OK, I made up a solution, which works fine for me.
Primefaces autocomplete:
<p:autoComplete id="mulitAutoSchlagWorte"
value="#{entityHandler.entity.tags}"
completeMethod="#{entityListHandler.completeAreaWithCreation}"
var="_tag"
itemLabel="#{_tag.value}"
itemValue="#{_tag}"
multiple="true"
size="300"
converter="#{entityConverter}">
</p:autoComplete>
List Handler, which also creates a new Tag:
public List<Tag> completeAreaWithCreation(String query) {
List<Tag> returnList = this.entityManager.createNamedQuery(Tag.NAMED_QUERY_GET_TAGS_BY_LIKE, Tag.class).setParameter("tag", query.toLowerCase().trim() + "%").getResultList();
if(returnList.size() == 0){
if(query.endsWith(" ")){
entityHandler.create();
entityHandler.getEntity().value(query.trim());
entityHandler.save();
}
returnList = completeAreaWithCreation(query.trim());
}
return returnList;
}

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! :)

Managed Property is not getting updated in Primefaces

I am using an ajax box which will fetch the list of object based the string provided in the box as follows:
<p:inputText id="zid" placeholder="Search" value="#{resourceListView.wanted}">
<p:ajax event="keyup" update=":form:abc"
listener="#{resourceListView.SearchResources}" />
</p:inputText>
SearchResources will fetch the objects based on the value of input box as follows:
public void SearchResources(String wanted) {
this.resources=resourceServiceImpl.listResources(wanted);
}
I was running the query in the DAO which was meant to return the object based on the search. But it didn't return anything. So I sysout the query and the query comes out as follows:
SELECT * FROM test.resourcemaster where Resource_ZID like '%javax.faces.event.AjaxBehaviorEvent[source=org.primefaces.component.inputtext.InputText#79d635]%' OR Employee_ID like '%javax.faces.event.AjaxBehaviorEvent[source=org.primefaces.component.inputtext.InputText#79d635]%' OR First_Name like '%javax.faces.event.AjaxBehaviorEvent[source=org.primefaces.component.inputtext.InputText#79d635]%' OR Last_Name like '%javax.faces.event.AjaxBehaviorEvent[source=org.primefaces.component.inputtext.InputText#79d635]%'
the query was supposed to be searching on the 'wanted'
Could anyone explain what is the problem.
Your SearchResources() method accepts one parameter, which you don't supply from page. That's why AjaxBehaviorEvent is passed to it (event.toString(), to be more precise), which is the default if you don't specify parameters in method call.
Try changing the ajax listener to listener="#{resourceListView.SearchResources(resourceListView.wanted)}"
Or, simply remove the parameter from the method, and use bean wariable wanted
public void SearchResources() {
this.resources = resourceServiceImpl.listResources(this.getWanted());
}

Iterate over ArrayList of Entities obtained from a query within a JSF page

I have a managed bean as my View in which I have a method called List<ArrayList> getImages() where I query the database and get a List of entities which is returned by the method. All well and good.
My problem is that when I try to iterate over this List from with JSF using either <c:forEach or ui:repeat e.g. <c:forEach var="image" items="#{viewBean.images}"> the server, Tomee throws and exception java.lang.UnsupportedOperationException: Result lists are read-only. and I'm not even doing anything with the values at this point.
If I just return the ArrayList with simple objects, no problem. I understand it must be something to do with the fact the object is an entity therefore tied to the database but I'm not sure the correct way, or best practice, to return the what I need to the JSP page.
Thanks.
Jason.
Edit. Below is method used for retrieving objects from db for iteration in JSF.
public List<ProfileImage> getProfileImageList() {
profileImageList = facade.findAllByProfileId(1L);
while (profileImageList.size() < 4) {
// Add placeholders to bring the list size to 4
ProfileImage placeHolder = new ProfileImage();
placeHolder.setProfileId(1L);
profileImageList.add(placeHolder);
}
return Collections.unmodifiableList(profileImageList);
}
JSF snippet below : Note, I am not doing anything with the value of var for now
<ui:repeat value="${imageUploadView.profileImageList}" var="profileImage">
<p:commandButton id="imageBtn_1" value="Picture 1" type="button" />
<p:overlayPanel id="imagePanel_1" for="imageBtn_1" hideEffect="fade" >
<ui:include src="/WEB-INF/xhtml/includes/profile_imageupload.xhtml" />
</p:overlayPanel>
</ui:repeat>
The following error is generated
javax.el.ELException: Error reading 'profileImageList' on type com.goobang.view.ImageUploadView
viewId=/profile/create_profile.xhtml
location=/Users/xxxxxxxxx/Documents/NetBeansProjects/testmaven/target/testmaven-1.0-SNAPSHOT/profile/create_profile.xhtml
phaseId=RENDER_RESPONSE(6)
Caused by:
java.lang.UnsupportedOperationException - Result lists are read-only.
at org.apache.openjpa.lib.rop.AbstractResultList.readOnly(AbstractResultList.java:44)
/profile/create_profile.xhtml at line 16 and column 87 value="${imageUploadView.profileImageList}"
I have solved it. The exception is thrown because I am modifying the list after assigning it to the result set. If I simply return the result set all is fine. So to achieve what I intended in getProfileImageList() I created a new ArrayList from the original, as suggested by tt_emrah, and then modify that before returning it.
public List<ProfileImage> getProfileImageList() {
profileImageList = new ArrayList(facade.findAllByProfileId(1L));
while (profileImageList.size() < 4) { // Add placeholders to bring the list size to 4
ProfileImage placeHolder = new ProfileImage();
placeHolder.setProfileId(1L);
profileImageList.add(placeHolder);
}
return profileImageList;
}

Same method of the managed bean getting called twice

I am developing pagination logic using JSF1.2 and in the process I have 4 links for first, previous, next and last pages corresponding to the results. I enter the search criteria in the Search page and click submit to get some records. I have a view link corresponding to each of the records to see the complete details. So I have two Managed beans one for Search/pagination functionality and other for View complete record details.
Whats the problem then?
When I search for the records the pagination works completely fine. However when I view the details of a record and come back to search page, I find that every time I click the next button the next() method is getting called twice.
Is there any solution for this?
The code is as follows:-
Inside search:-
<h:commandLink value="#{msg['heading.nextLink']}"
binding="#{searchRoutesForView.nextLink}"
actionListener="#{searchRoutesForView.next}">
</h:commandLink>
Inside SearchManagedbean:-
public void next(ActionEvent actionEvent) {
if ((pointer + noOfRecordsToBeDisplayed) >= readConfig.length) {
readRoutingResponse.setReadConfig(Arrays.copyOfRange(readConfig,
pointer, readConfig.length));
pointer = readConfig.length;
System.out.println("pointer inside next =" + pointer);
setOrDisableLinks(false, false, true, true);
} else {
readRoutingResponse.setReadConfig(Arrays.copyOfRange(readConfig,
pointer, pointer + noOfRecordsToBeDisplayed));
pointer += noOfRecordsToBeDisplayed;
System.out.println("pointer inside next -- else =" + pointer);
setOrDisableLinks(false, false, false, false);
}
}
Please have a look at the lifecyle of JSF
Below approach may not be the best one..
public void next(ActionEvent actionEvent)
{
if((pointer + noOfRecordsToBeDisplayed) >= readConfig.length) && (isVisitedOnce == false))
{
visitedOnce = true //set one boolean indicating this method is already visited
}
}
This will not work if bean is in Request scope.

Transfer data from one JSF page to ProcessAction() method of FacesPortlet

Here is what I am trying to do.
I want the data from jsf page1 to be available in jsf page2 which is a popup window opened by page1.
Both have separate managed beans.
I tried using sessions but it resulted in null pointers.
I somehow managed to get the data in page2 using window.opener() in javascript.
Now I want this data to be available in the processAction() method of FacesPortlet.
Tried using request.getParameter, request.getAttributes, all in vain.
After a lot of research I somehow managed to send some hard coded data in processAction() method. But I am unable to send the value from page1.
Here is how I am sending the hardcoded value.
<form name="uploadbaseline" method="post"
enctype="multipart/form-data" action="<portlet:actionURL><portlet:param name = "page" value = "someval"/></portlet:actionURL>">
This is followed by the other fields inside the form.
I get the value in processAction() method like this
System.out.println("valuefrompage1"+request.getParameter("page"));
This returns "someval".
Now I try to assign the value from page1 using javascript using the following code.
var val = window.opener.document.getElementById("BaseLine:EngineModel").value;
var actionval = "<portlet:actionURL><portlet:param name='page' value=" + val.value + "/></portlet:actionURL>";
document.uploadbaseline.action = actionval.value;
document.uploadbaseline.submit();
This returns the value "+ val.value +" as it is and not the actual value in the "val" variable.
Please guide me in the right direction.
Keenly looking forward to your replies.
Thanks.
Found the solution finally.
The problem lies in the enctype attribute of my form.
This prevents me from accessing the page fields using the regular request.getParameter.
This needs to be handled in a different way.
Here is how.
for (Iterator requestItems = upload.parseRequest(request).iterator(); requestItems.hasNext();) {
item = (FileItem) requestItems.next(); // Get each item in the request
if (!item.isFormField()) {
//handle the file data
} else {
System.out.println((String)item.getString());
}
}

Resources