I am working on creating a new Custom Component and unable to bind a controller's property to the SelectedItem annotation property of my custom component. My idea is that any one who passes my Custom component SelectedItem annotation, I should be able to retrieve it in my component and assign it myself to the ListBox's SelectedItem property. This will give flexibility to the users of my component to not worry about the internals and the component will be re-usable.
Problem is that I am not able to get/set the Controller value in my custom component. I get NULL. Can someone please help me resolve this issue or point me to the right direction? Here is the code:
<bandbox id="filterDropdownBandBox" instant="true" readonly="false">
<bandpopup id="filterDropdownBandPopup" style="max-height:250px;overflow-x:hidden">
<listbox id="listBox" hflex="1" rows="0" >
<template name="model">
<listitem>
<listcell label="${each.label}" />
</listitem>
</template>
</listbox>
</bandpopup>
public class FilterDropdown extends Div implements IdSpace {
#Wire
private Listbox listBox;
#Wire
private Bandpopup filterDropdownBandPopup;
#Wire
private Bandbox filterDropdownBandBox;
private ListModelList<GenericNameValuePair> lbModel;
public FilterDropdown() {
Executions.createComponents("/filterDropdown.zul", this, null);
Selectors.wireComponents(this, this, false);
Selectors.wireEventListeners(this, this);
}
public void setSelectedItem(Listitem l) // getting NULL here
{
l.setParent(listBox);
listBox.setSelectedItem(l);
}
public void saveSelection() {
listBox.getSelectedItem();
}
public Listitem getSelectedItem() {
return listBox.getSelectedItem();
}
}
This is how I added this component to lang-addon.xml file
<component>
<component-name>filter-dropdown</component-name>
<extends>div</extends>
<component-class>com.components.FilterDropdown</component-class>
<annotation>
<annotation-name>DDBIND</annotation-name>
<property-name>selectedItem</property-name>
<attribute>
<attribute-name>ACCESS</attribute-name>
<attribute-value>both</attribute-value>
</attribute>
</annotation>
</component>
And this is how I am using my custom component in other ZUL files
<filter-dropdown id="filterProjDropdown" selectedItem="#DDBIND{XYZCtrl.bean.propbean.actualProp}"/>
First of all, keep to the normal annotation like #load(), #save() or #bind()`.
Now, mine first suggestion is to throw your zul away.
Implement the AfterCompose interface in your component and add all the items there with a renderer.
It makes it easier for anyone to change that component and it will be more performent.
Secondly, use the correct annotation in your class :
#ComponentAnnotation({"selectedItem:#ZKBIND(ACCESS=both,SAVE_EVENT=onSelect)"})
Like this your lang-addon.xml should look like :
<component>
<component-name>filter-dropdown</component-name>
<extends>div</extends>
<component-class>com.components.FilterDropdown</component-class>
</component>
And as last :
You need to inform the binder that there was a change in the selectedItems :
Events.postEvent("onSelect", FilterDropdown.this, selectedItems);
You should handle this in an eventlistener attached to the bandbox.
If you want an advanced working component code including in how to export it to a separate jar, please check out mine github project.
Related
Below is the Vaadin Designer code for simple tab functionality
import {html, PolymerElement} from '#polymer/polymer/polymer-element.js';
import '#vaadin/vaadin-tabs/src/vaadin-tabs.js';
import '#vaadin/vaadin-tabs/src/vaadin-tab.js';
class TestUi extends PolymerElement {
static get template() {
return html`
<style include="shared-styles">
:host {
display: block;
height: 100%;
}
</style>
<vaadin-tabs theme="equal-width-tabs" id="vaadinTabs">
<vaadin-tab id="vaadinTab">
Product Overview
</vaadin-tab>
<vaadin-tab id="vaadinTab1">
Product DetailView
</vaadin-tab>
<vaadin-tab id="vaadinTab2">
Reports
</vaadin-tab>
</vaadin-tabs>
`;
}
static get is() {
return 'test-ui';
}
static get properties() {
return {
// Declare your properties here.
};
}
}
customElements.define(TestUi.is, TestUi);
It's corresponding Java companion file looks as below
import com.vaadin.flow.component.polymertemplate.Id;
import com.vaadin.flow.component.tabs.Tab;
import com.vaadin.flow.component.tabs.Tabs;
import com.vaadin.flow.templatemodel.TemplateModel;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.polymertemplate.PolymerTemplate;
/**
* A Designer generated component for the test-ui template.
*
* Designer will add and remove fields with #Id mappings but
* does not overwrite or otherwise change this file.
*/
#Tag("test-ui")
#JsModule("./src/productdetailview/test-ui.js")
public class TestUi extends PolymerTemplate<TestUi.TestUiModel> {
#Id("vaadinTabs")
private Tabs vaadinTabs;
#Id("vaadinTab")
private Tab vaadinTab;
#Id("vaadinTab1")
private Tab vaadinTab1;
#Id("vaadinTab2")
private Tab vaadinTab2;
/**
* Creates a new TestUi.
*/
public TestUi() {
// You can initialise any data required for the connected UI components here.
vaadinTabs.addSelectedChangeListener(selectedChangeEvent -> {
selectedChangeEvent.getSelectedTab().getElement().getStyle().set("background-color":"blue");
});
}
/**
* This model binds properties between TestUi and test-ui
*/
public interface TestUiModel extends TemplateModel {
// Add setters and getters for template properties here.
}
}
In the above code, My thinking was to start writing the selectedChangeListener Handler directly without doing much but instead this doesn't work and below initialization code needs to be added.
//I have added for one tab but it requires all the tabs to be added
vaadinTabs = new Tabs();
vaadinTab = new Tab();
vaadinTabs.add(vaadinTab);
My question here is why would I need to initialize when the Polymer js code generated using Vaadin Designer clearly defines the tab and it's group?
This is the same issue with Vaadin Grid. Even after defining the columns in the Polymer js, I have to redefine it from the Java component end instead of directly start providing the data via data provider
TLDR; Unfortunately, you have encountered this issue IllegalArgumentException when switching tabs
which is closed as won't fix.
My question here is why would I need to initialize when the Polymer js code generated using Vaadin Designer clearly defines the tab and it's group?
Generally, you don't need to. But Tabs doesn't work as intended in this case. Thus, for this particular component, it's suggested to not mix template/Java logic.
For example, you can verify it with a <vaadin-text-field>, where event is fired correctly.
Java counterpart
#Id("vaadinTextField")
private TextField vaadinTextField;
/**
* Creates a new TestUi.
*/
public TestUi() {
// You can initialise any data required for the connected UI components here.
vaadinTextField.addValueChangeListener(event->{
System.out.println("Event has happened");
});
vaadinTextField.setValueChangeMode(ValueChangeMode.EAGER);
and snippet for the template right after the tabs:
<vaadin-vertical-layout id="vaadinVerticalLayout" style="width: 100%; height: 100%;">
<vaadin-text-field id="vaadinTextField"></vaadin-text-field>
</vaadin-vertical-layout>
Taken from the issue:
So all Tab related API methods in Tabs are completely broken in regard to injected Tabs.
and
Unfortunately we've concluded that there is no sensible way we can support this for now, thus this issue will be a known limitation with Tabs. It will not work as #Id mapped component when the child vaadin-tabs are created in the template file, so you should not try to mix client & server logic and content for the Tabs component.
As a workaround, you could try to use your own component for #Id mapping tabs like:
#Tag("vaadin-tabs")
public IdMappedTabs extends Component {
public IdMappedTabs() {
}
public Registration addSelectionListener(PropertyChangeListener listener) {
return getElement().addPropertyChangeListener("selected", listener);
}
public void setSelectedTabIndex(int index) {
getElement().setProperty("selected", index);
}
}
Edit:
What is the issue with Grid you are having? (There is a good tutorial about Designer, where Grid is used. It might be useful : Vaadin Designer tutorial)
Using Vaadin 14.0.13 without compatibility mode.
I use a view to create a Dialog with dynamic content:
#Route("")
public class MainView extends VerticalLayout {
public MainView(DialogContentProvider contentProvider) {
this.add(new Button("Click me!", event -> new Dialog(contentProvider.create()).open()));
}
}
The contentProvider is an interface
public interface DialogContentProvider {
Component create();
}
with this implementation:
public class CheckBoxContentProvider implements DialogContentProvider {
#Override
public Component create() {
return new Checkbox("My checkbox", true);
}
}
instantiated by Spring Boot (version 2.2.1.RELEASE) with a bean:
#Bean
public DialogContentProvider contentProvier() {
return new CheckBoxContentProvider();
}
When I click on the button, the dialog is opened but the checkbox haven't the box:
The source code is on github: https://github.com/gronono/bug-vaadin-checkbox
I don't understand why and how I can fix it. If I include the checkbox creation inside the main view, it works fine:
#Route("")
public class MainView extends VerticalLayout {
public MainView(DialogContentProvider contentProvider) {
// this.add(new Button("Click me!", event -> new Dialog(contentProvider.create()).open()));
this.add(new Button("Click me!", event -> new Dialog(new Checkbox("My checkbox", true)).open()));
}
}
This sound an awful lot like this (related github issue)
Basically, this happens when you don't have any View that uses a Checkbox directly, but through other means like reflection or in your case the contentProvider, because in no view of your app there is any import statement of Checkbox (--> therefore, vaadins scan during the installation will not detect usages of Checkbox, so it will not download npm stuff for checkbox).
in the github it says this will be fixed in 14.1
If you need a fix now, for me it worked when I declared a field of that type in any view with a #Route. That field doesn't have to be used.
#Route("")
public class MainView extends VerticalLayout {
private Checkbox unusedCheckbox; // this line fixes it.
public MainView(DialogContentProvider contentProvider) {
this.add(new Button("Click me!", event -> new Dialog(contentProvider.create()).open()));
}
}
Addendum: This is not related to the Checkbox component specifically, it happens with any vaadin component that isn't initially scanned in a route, but used anyway through reflective-, provider-, or generic means.
Edit: You can also work around this currently by adding a #Route(registerAtStartup = false) to your provider that uses the Checkbox directly. This will make vaadins scan see the checkbox usage (therefore importing its npm package), but will not actually register the provider as a real route..
Another way which I prefer if you need this for multiple components is to create a new View with a #Route(registerAtStartup = false) which only defines private variables for each component that you'll need in the application (and arent already used directly in some view of yours). This has the advantage of all these component usage definitions in one place, and once the official fix is released, you need only to delete one class and the deprecated workaround is gone.
#Route(registerAtStartup = false)
public class ComponentImportView extends VerticalLayout {
private Checkbox checkBox;
private Upload upload;
private ProgressBar progressBar;
}
I‘m a beginer who uses MvvmCross for the Xamarin.Android.I try to realize the function like 'click the button and show a dialog to say hello'.
When I use the way which sets ViewModel object to Activity object's DataContext to bind,I can pass UI object directly (or using Interface indirectly).In this way,I can access UI object to show a dialog.
In another way likes the offical demo, bing the UI object and ViewModel automatically,how can I show the dialog?The auto bing code like this
public class App:MvxApplication { public App() { Mvx.RegisterSingleton(new MvxAppStart()); } }
[Activity(Label = "MvvmC_TutorialActivity")] public class MvvmC_TutorialActivity : MvxActivity
{
.............
}
Thanks!
I slove the problem!I can pass the UI object in 'MvxActivity's OnViewModelSet'.
protected override void OnViewModelSet()
{
SetContentView(Resource.Layout.View_Tip);//pass UI object here
}
I have a list of the following class:
public class Set {
public string IconUrl { get; set; }
}
This list is bound to a ListView:
<ListView ItemsSource="{Binding Sets}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Image Source="{Binding IconUrl}" />
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
When the view loads and the user starts scrolling, the cells are reused and the Image briefly shows the previous image before the new image is downloaded and rendered.
Is there a way to prevent this kind of behavior without disabling RecycleElement?
I haven't tried this but on ViewCell you have Disappearing and Appearing events that you can hook into.
You may want to look at releasing the image source on the Disappearing event handler, but sometimes this can occur sometime later I think from recollection, so you may also want to try releasing the image on the Appearing event handler that hopefully will be executed prior to the display on the screen?
We have solved this by manually setting the Image source to null to force the render of the new images. we achieve this by using OnBindingContextChanged Event of the ListView. Hope this helps.
Edited (Added code below):
We have something like this:
public class PeopleCell : ViewCell
{...
Image profileImage;
public PeopleCell()
{
profileImage = new Image
{
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.CenterAndExpand,
BackgroundColor = Color.FromHex("f5f5f5"),
Source = ImageSource.FromFile("profile_blankimage"),
};...
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
people = BindingContext as CustomerViewModel;
if(people.Customer.Avatar != null)
profileImage.Source = ImageSource.FromUri(new Uri(people.Customer.Avatar.Url));
I like to make a general module in ActionScript to create an interactive tooltip. The tooltip has to resize on mouseover() event and then should contain hyperlinks once resized. Thanks
Yes, its possible. Are you using Flex? or just pure Actionscript? In the case of actionscript:
Add an event listener to rollOver event, and display the tooltip, heres some code:
[in some function, after the comp is added to the stage ]
public function myComp(){
myComponent.addEventListener(MouseEvent.ROLL_OVER,createToolTip);
stage.addEventListener(MouseEvent.CLICK,destroyToolTip);
}
private var toolTip:CustomToolTip;
private function createToolTip(e:MouseEvent):void{
toolTip = new CustomToolTip();
stage.addChild(myToolTip);
myToolTip.x = e.localX;
myToolTip.y = e.localY;
}
private function destroyToolTip(e:Event):void{
stage.removeChild(toolTip);
toolTip = null;
}
(you might need to refine the tooltip destruction logic, now it gets destroyed, if you click anywhere. For example you could call Event.stopPropagation, if the user click inside the tooltip. )
The custom tooltip class:
package{
class CustomToolTip extends Sprite{
public function CustomToolTip():void{
super();
// put drawing logic, children, text,... here.
}
}
}