Introduction
Hello,
Before I start, I just want to say I already tried to solve the issue by visiting other posts here, and I didn't find anything right for me.
I saw this post stating "composite components unfortunately do not support recursion.". But the post is from 2011, so I am assuming it's possible now.
I read and tried to use this BalusC explaination, but it didn't work.
The problem and code snippets
So here's my problem. I want to have a recursive table of DTOs objects (using datatables of Primefaces, but I doesn't matter I think). My component looks like this :
<cc:interface componentType="recursiveDomainsTable">
<cc:attribute name="domains_list" type="java.util.List" required="true" />
</cc:interface>
<cc:implementation>
<p:dataTable var="domain" value="#{cc.getDomainsList()}">
<p:column>
<!-- allow a row to be expanded -->
<p:rowToggler rendered="#{domain.hasSubDomains()}"/>
<!-- some text here for the demo -->
<h:outputText value="#{domain.someText()}" />
</p:column>
<p:rowExpansion>
<!-- some that shows that #{domain.getSubdomains()} is NOT null when the recursion is removed -->
<p>#{domain} -> #{domain.getSubdomains()}</p>
<!-- The recursion -->
<efr:recursiveDomainsTable
domains_list="#{domain.getSubdomains()}"
/>
</p:rowExpansion>
</p:dataTable>
</cc:implementation>
And I simply get a StackOverflowError when I try to render this :
<efr:recursiveDomainsTable domains_list="#{domains_bean.domains}" />
Note that I tried to create a composite with a bean to see what's happening here :
#FacesComponent("recursiveDomainsTable")
public class RecursiveDomainsTableComposite extends UINamingContainer {
#lombok.Getter #lombok.Setter private List<?> domainsList;
#Override
public void setValueExpression(String name, ValueExpression expression) {
Log.debug("name="+name);
Log.debug("expression="+expression.getExpressionString() + " -> "+ expression.getValue(getFacesContext().getELContext()));
if ("domains_list".equals(name)) {
List<?> l = (List<?>) expression.getValue(getFacesContext().getELContext());
setDomainsList(l);
} else {
super.setValueExpression(name, expression);
}
}
}
And, when the StackOverflow is occuring, I have messages (in a loop) stating :
name=domains_list
expression=#{domain.getSubdomains()} -> null
So when I don't use the recursion, #{domain.getSubdomains()} is properly displayed, but when I use it the stackoverflows happens BEFORE the value is event interpreted.
Does someone knows what I did wrong ? Why can't JSF use the cc.domainsList value ?
Related
I am trying to set up a component with data binding. This is basically a seperate content view that would have a property Item of type Item and supports binding. The following is the definition for the binding:
public static readonly BindableProperty ItemProperty
= BindableProperty.Create(
nameof(Item), typeof(Item), typeof(ItemComponent), null,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: ItemPropertyChanged);
private readonly ItemComponentViewModel vm;
static void ItemPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = (ItemComponent)bindable;
view.Item = (Item)newValue;
}
public Item Item
{
get => (Item)GetValue(ItemProperty);
set
{
SetValue(ItemProperty, value);
if (vm != null) vm.Data = value; // break point here
}
}
The item doesn't seem to get bound. The commented line had a breakpoint and doesn't break. The complete source code is here: https://github.com/neville-nazerane/xamarin-component-sample
The above code can be found in the ItemComponent class. This component is called in the MainPage class.
Update
Just to explain what I am trying to simulate and why:
Why do we use MVVM in pages? While we'll have better type safety and performance by using the behind code directly, when the page's logic gets bigger, it becomes cleaner to handle it with a view model and to have a view that is simply bound to it.
Why do we have components? So that we can reuse a UI we intend to use with some functionality. If this functionality becomes complex it might need a view model for the same reason explained above. Hence, if pages need view models, I don't see why components won't need them at some point too.
This being considered this does feel like a particle requirement without easy to find examples.
So after looking at your example it turns out it's a bit of a complicated problem. So if my explanation is not clear, please let me know.
Basically the problem lies in these 2 code pieces:
MainPage.xaml(line 14):
<local:ItemComponent Item="{Binding Demo}" />
ItemComponent.xaml.cs (line 43):
public ItemComponent()
{
InitializeComponent();
vm = new ItemComponentViewModel();
BindingContext = vm; //this breaks the functionality
}
The first part you tell it to bind to the Demo property, and as normal it looks for this property in it's BindingContext. However in the second part you override it's BindigContext and set it to a ItemComponentViewModel this ViewModel however does not have a property Demo so the {Binding Demo} does not work on this new BindingContext you've set.
Now a possible solution for your demo application would be to change MainPage.xaml to the following code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SampleApp"
x:Class="SampleApp.MainPage"
x:DataType="local:MainViewModel"
x:Name="MyDemoPage">
<StackLayout>
<Label Text="Manual:" />
<Label Text="{Binding Demo.Title}" />
<Label Text="Component: " />
<local:ItemComponent Item="{Binding Path=BindingContext.Demo, Source={x:Reference MyDemoPage}}" />
</StackLayout>
</ContentPage>
Basically we now place the Demo binding outside of the BindingContext of our ItemComponent control. However if you want to use it in a ListView (if I remember correctly from your original question, this solution might not work and it's possible you'll have to drop the ItemComponentViewModel and bind directly to the properties (ListView will already make sure that the BindingContext of your ItemComponent is set to the current Item, no need to pass it around through a bindable property.
Hope this helps!
I have been using ReactiveUI for a while with Xamarin Forms, but I've hit a brick wall when trying to use a ReactiveTabbedPage. I can't figure out how the ViewModel will get bound to the ReactiveContentPage's that are the children of the ReactiveTabbedPage.
So, as an example, I might have the following XAML:
<ReactiveTabbedPage x:Name="TabbedPage">
<local:Page1View x:Name="Page1" />
<local:Page2View x:Name="Page2" />
</ReactiveTabbedPage>
Where Page1View and Page2View are both of type ReactiveContentPage and T is the associated ViewModel.
What I expected to happen was that when the ReactiveTabbedPage was navigated to, Page1View would be displayed, and the ViewModel would be loaded (in the same way it would if I navigated to the Page1View directly). However, the ViewModel never gets called (the constructor is never fired and no data binding occurs).
However, both Page1View and Page2View do render and I can see the initial data that is created in those views (e.g. default text for labels etc.).
I know that the ViewModel stuff is working correctly, because if I navigate to Page1View directly (e.g. not in the ReactiveTabbedPage) everything displays as I expect.
Have I missed something, or am I going about this the wrong way? Or is this just not supported in the current version of RxUI?
Any advice is greatly appreciated!
The responsibility for tying the VM to the child pages lies with the host page (i.e. the ReactiveTabbedPage). It alone knows which VM corresponds to which view.
Let's take this one step at a time. First of all, the MainViewModel:
public class MainViewModel : ReactiveObject
{
public ChildViewModel1 Child1 => new ChildViewModel1();
public ChildViewModel2 Child2 => new ChildViewModel2();
}
This code obviously isn't realistic because you wouldn't want to recreate the child VMs upon every property access. It's more the API that's pertinent here.
ChildViewModel1 looks like this:
public class ChildViewModel1 : ReactiveObject
{
public string Test => "Hello";
}
And ChildViewModel2 looks much the same.
Now we can go about setting the views up. Our MainView.xaml looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<rxui:ReactiveTabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:TypeArguments="vms:MainViewModel"
xmlns:local="clr-namespace:ReactiveTabbedPageTest"
xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
xmlns:vms="clr-namespace:ReactiveTabbedPageTest.VMs"
x:Class="ReactiveTabbedPageTest.MainView">
<local:Child1View x:Name="child1View" Title="Child 1"/>
<local:Child2View x:Name="child2View" Title="Child 2"/>
</rxui:ReactiveTabbedPage>
Notice it declares each of the child views. We need to hook up the VMs to those views, which we do in the code-behind for MainView:
public partial class MainView : ReactiveTabbedPage<VMs.MainViewModel>
{
public MainView()
{
InitializeComponent();
this.ViewModel = new VMs.MainViewModel();
this.WhenActivated(
disposables =>
{
this
.OneWayBind(this.ViewModel, x => x.Child1, x => x.child1View.ViewModel)
.DisposeWith(disposables);
this
.OneWayBind(this.ViewModel, x => x.Child2, x => x.child2View.ViewModel)
.DisposeWith(disposables);
});
}
}
I've done this the safest way by using WhenActivated and OneWayBind calls. In reality, it's unlikely your child VMs will change, so directly assigning them rather than binding is totally fine.
Now our child views can be thrown together. Here's ChildView1.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<rxui:ReactiveContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ReactiveTabbedPageTest.Child1View"
x:TypeArguments="vms:ChildViewModel1"
xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
xmlns:vms="clr-namespace:ReactiveTabbedPageTest.VMs">
<Label x:Name="label" VerticalTextAlignment="Center" HorizontalTextAlignment="Center"/>
</rxui:ReactiveContentPage>
And the code-behind:
public partial class Child1View : ReactiveContentPage<ChildViewModel1>
{
public Child1View()
{
InitializeComponent();
this.WhenActivated(
disposables =>
{
this
.OneWayBind(this.ViewModel, x => x.Test, x => x.label.Text)
.DisposeWith(disposables);
});
}
}
Once again we're doing the usual RxUI binding goodness to associate properties in the VM with controls in the UI. And once again you could optimize this for properties that don't mutate.
For the purposes of this example, ChildView2 is much the same as ChildView1, but obviously it could be totally different.
The end result is as you'd expect:
What's not evident from the screenshot but is very important is that each tab is deactivating when you switch away from it (as would its associated view model if it implemented ISupportsActivation). This means you can clean up any bindings and subscriptions for that tab when it's not in use, reducing memory pressure and improving performance.
I am trying to use Thymeleaf to create custom tags, just like in JSP.
The tag I have now is:
<select th:include="fragments/combobox :: combobox_beans (beans=${#accountService.getAccounts()}, innerHTML='id,description,currency', separator=' - ', dumbHtmlName='List of accounts', name='sender' )" th:remove="tag"></select>
The purpose is just defining the beans list, the properties of the bean to show on screen, the separator between them, the default value when shown as a native template, and the property name of the original bean we are processing here.
combobox.html:
<div th:fragment="combobox_beans (beans, innerHTML, separator, dumbHtmlName, name)">
<select th:field="*{__${name}__}" class="combobox form-control" required="required">
<option th:each="obj : ${beans}" th:with="valueAsString=${#strings.replace( 'obj.' + innerHTML, ',', '+'' __${separator}__ ''+ obj.')}"
th:value="${obj}" th:text="${valueAsString}" >
<p th:text="${dumbHtmlName}" th:remove="tag"></p>
</option>
</select>
I need the text of the option tag to be based on the properties set in innerHTML property (innerHTML='id,description,devise') of the fragment.
I end up having an option with this text:
<option value="...">obj.id+' - '+ obj.description+' - '+ obj.currency</option>
instead of the desired result
<option value="...">2 - primary - USD</option>
I know this is due to the usage of Strings library which results in a string.
Is there a way Thymeleaf can re-evaluate this string to be understood as an object again?
Maybe using strings library is just so wrong in this situation... Maybe I need to use a th:each to process the each bean as an object and read its properties, but yet again, how to only get the properties specified in innerHtml ?
Anyone has a solution or work-around for this ?
thanks.
If there is a way to do what you want in Thymeleaf/Spring expression alone, it most certainly very complicated and long winded, plus it would probably be a pain to read.
The easier way to do it would be add a custom utility object to the expression context. Very little code is needed. This answer shows it.
Then you need to add you new dialect as additional dialect to the template engine in your Spring xml config. Assuming you have a fairly standard Spring config, it should be similar to this.
<bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
<property name="additionalDialects">
<set>
<bean class="mypackage.MyUtilityDialect" />
</set>
</property>
</bean>
Now for the utility object
What you want is to get the properties from objects by name, and combine their values with a separator. It seems that the list of property names can be of any size. For accessing properties by name, the most convenient thing is to use a library like the Apache beanutils.
Your custom utility object could the look something like this using the Java 8 streams library, lambdas and Beanutils:
public class MyUtil {
public String joinProperties(Object obj, List<String> props, String separator){
return props.stream()
.map(p -> PropertyUtils.getProperty(obj,p).toString())
.collect(Collectors.joining(separator))
}
}
Then when you add you dialect to SpringTemplateEngine you can call your utility:
th:with="valueAsString=${#myutils.joinProperties(obj,properties,separator)}"
I have replaced you innerHTML parameter with properties which is a List<String>, because it makes more sense. It is essentially a list of property names, and Spring EL supports inline lists.
Your calling tag should then look like this.
<select th:include="fragments/combobox :: combobox_beans (beans=${#accountService.getAccounts()}, properties=${ {'id','description','currency'} }, separator=' - ', dumbHtmlName='List of accounts', name='sender' )" th:remove="tag"></select>
In Flex, lets say I have a super-class... something like:
class SuperComponent extends DragStack {
private var _childReference:UIComponent;
public function SuperComponent() {
// ???
addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
}
private function onCreationComplete(e:FlexEvent):void {
//The 'this[]' technique doesn't seem to work and causes run-time errors:
//trace("Component found: " + this["myButton"]);
}
}
And then I make use of the following derived-class in my application (just a mockup MXML as an example):
<!-- Component ChildComponent.mxml -->
<mx:SuperComponent>
<mx:Button id="myButton" label="Press Me!" />
</mx:SuperComponent>
How do I go about verifying the presence of "myButton" from the SuperComponent class, and referencing it? Do I need to use getChildByName( ... ) ?
I'm not sure what type of component DragStack is. Does it extend Container (Flex 3) or Group (Flex4)? If so, then the component will go through it's lifecycle process, and myButton should be accessible after createChildren method is executed.
I believe that MXML does some magic under the hood to create the button as a child of your component.
If DragStack is not a container, then you have to tell us what the default property of DragStack is. The DefaultProperty would be specified in class metadata.
I believe what the MXML does is, basically, assign the XML Children to the default property of the SuperComponent class if no other property is specified. If you want to assign it to a different property, you'll have to specify it, like this:
<mx:SuperComponent>
<mx:myProperty>
<mx:Button id="myButton" label="Press Me!" />
</mx:myProperty>
</mx:SuperComponent>
This syntax is usually only used in situations where the property doesn't have a simple value, such as the array of columns for a DataGrid.
You can't use this["myButton"] from within containers even if myButton is a child of that container added in MXML. myButton is still not a class property but element of container's children.
You'd better use getChildByName() passing "myButton" as a name.
I want to create a component that has a couple of "holes" that are to be filled in differently on each use. Currently, I'm doing it like this (using the Flex 4 framework in this example --- it would look almost the same for Flex 3):
public var fooComponent : IVisualElement;
public var barComponent : IVisualElement;
override protected function createChildren() : void
{
super.createChildren();
fooContainer.addElement(fooComponent);
barContainer.addElement(barComponent);
}
<Group id="fooContainer"/>
<!-- ... other components ... -->
<Group id="barContainer"/>
This works well, but it's kind of a lot of code to write for something so simple.
What I'd like is something like this:
[Bindable] public var fooComponent : IVisualElement;
[Bindable] public var barComponent : IVisualElement;
<Placeholder content="{fooComponent}"/>
<!-- ... other components ... -->
<Placeholder content="{barComponent}"/>
Now, I could implement the Placeholder component myself, but I can't help wondering if there isn't a better way to do this using the existing tools in the Flex framework.
Theoretically, with the proper compiler support, it could even be boiled down to something like this:
<Placeholder id="fooComponent"/>
<!-- ... other components ... -->
<Placeholder id="barComponent"/>
Here is template component practice for Flex3.
http://livedocs.adobe.com/flex/3/html/help.html?content=templating_3.html
We just need to use any of standard containers as the placeholder. And we could use the addChild() and removeChild() methods to add, remove controls in that container.
Have you tried to create a SkinnableComponent with a couple of SkinParts, and a proper Skin for it? I guess it could help.
<UIComponent id="fooComponent"/>
<!-- ... other components ... -->
<UIComponent id="barComponent"/>