I ask this squestion on behalf of one of my developers. Haven't looked into details myself.
Assume you have a simple managed bean (=contact) This bean has a method to get the contacts firstName.
I can open an xpage and bind the bean to a computedText Field #{contact.firstName}
In our application we use a tabContainer to have multiple documents of the same type ( contact ) open.
How do I have to use my bean in the container?
faces-config.xml:
<managed-bean>
<managed-bean-name>person</managed-bean-name>
<managed-bean-class>com.package.Person</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
Java Bean Class:
public class Person implements Serializable {
private String strDocumentID;
private Document docData;
private String strFirstName;
private String strLastName;
private static final long serialVersionUID = 2934723410254681213L;
public Person() {
//setting the DocumentUniqueID of the current in a tab opened document
//attention: there could be more than one open tab, all with different documents
//and even different document types; and it is possible to switch back and forth between tabs
//DocumentId = ???;
//Setting the values from the stored document to the object
//setValues();
}
private void setValues() {
try {
Session session=NotesContext.getCurrent().getCurrentSession();
Database currdb=session.getCurrentDatabase();
docData=currdb.getDocumentByUNID(DocumentId);
setStrFirstName(docData.getItemValueString("FirstName"));
setStrLastName(docData.getItemValueString("LastName"));
} catch (NotesException e) {
throw new FacesException("Could not open document for documentId "+ DocumentId, e);
}
}
public Document getDataDocument() {
return docData;
}
public void setDataDocument(Document docData) {
this.docData = docData;
}
public String getDocumentId() {
return DocumentId;
}
public void setDocumentId(String documentId) {
DocumentId = documentId;
}
public String getStrFirstName() {
return strFirstName;
}
public void setStrFirstName(String strFirstName) {
this.strFirstName = strFirstName;
}
public String getStrLastName() {
return strLastName;
}
public void setStrLastName(String strLastName) {
this.strLastName = strLastName;
}
}
Custom Control with computed field:
person.strFirstName
So, the problem is the constructor of the Person Class. It needs to get the "link" to the opened document when the document is opened in a tab and everytime when switched back to this tab. And this without the use of any Data source, because this is what should be done by the managed bean itself.
So, hope that helped to get a little bit more understanding of the problem.
If not, please ask again.
My advice:
make another meta bean implementing map interface. Alter its getter to instantiate and return your data bean. Binding may be then:
meta[someparamwithunid].field
And save would be:
meta[someparamwithunid].setValues()
Like this:
public class People implments java.util.Map {
Map<String,Person> people = new HashMap<String,Person>();
public Person get(String unid) {
if people.keySet().contains(unid) {
return people.get(unid)
} else {
// make instance and store it in people map, return it
}
// implement other methods
}
With view scope I think there is no problem with concurrency.
Frantisek points into the right direction. Your request bean would not be a person bean, but a people bean. You then can use an expression like
#{people[index].name}
to refer to a specific person. People would be the managed bean and the index could either be the UNID or the tab number. I find the later one easier to implement. you need to have a loadPerson(index) = UNID function to load an existing person. More information on the use of Expression language can be found here:
Sun Oracle JSF documentation or in some Course materials.
Hope that helps.
I'm not sure if this bean will work in the requestScope because you have probably a lot of partial refreshes with the tabcontainer (maybe try change it to a higher level scope).
Related
I have a button in View "A" which already has a bindingSet attached to it (it binds to ViewModel "A"). I have button though which needs to be bound to ViewModel "B".
What is the best way to do this?
Your ViewModel is your Model for your View.
If that ViewModel is made up of parts, then that can be done by aggregation - by having your ViewModel made up of lots of sub-models - e.g:
// simplified pseudo-code (add INPC to taste)
public class MyViewModel
{
public MainPartViewModel A {get;set;}
public SubPartViewModel B {get;set;}
public string Direct {get;set;}
}
With this done, then a view component can be bound to direct sub properties as well as sub properties of sub view models:
set.Bind(button).For("Title").To(vm => vm.Direct);
set.Bind(button).For("TouchUpInside").To(vm => vm.A.GoCommand);
set.Bind(button).For("Hidden").To(vm => vm.B.ShouldHideThings);
As long as each part supports INotifyPropertyChanged then data-binding should "just work" in this situation.
If that approach doesn't work for you... In mvvmcross, you could set up a nested class within the View that implemented IMvxBindingContextOwner and which provided a secondary binding context for your View... something like:
public sealed class Nested : IMvxBindingContextOwner, IDisposable {
public Nested() { _bindingContext = new MvxBindingContext(); }
public void Dispose() {
_bindingContext.Dispose();
}
private MvxBindingContext _bindingContext;
public IMvxBindingContext BindingContext { get { return _bindingContext; } }
public Thing ViewModel {
get { return (Thing)_bindingContext.DataContext; }
set { _bindingContext.DataContext = value; }
}
}
This could then be used as something like:
_myNested = new Nested();
_myNested.ViewModel = /* set the "B" ViewModel here */
var set2 = _myNested.CreateBindingSet<Nested, Thing>();
// make calls to set2.Bind() here
set2.Apply();
Notes:
I've not run this pseudo-code, but it feels like it should work...
to get this fully working, you will also want to call Dispose on the Nested when Dispose is fired on your View
given that Views and ViewModels are normally written 1:1 I think this approach is probably going to be harder to code and to understand later.
In my XPages application, I use a managed Java bean (scope = application) for translating strings:
public class Translator extends HashMap<String,String> implements Serializable {
private static final long serialVersionUID = 1L;
public String language = "en";
public Translator() { super(); this.init(null); }
public Translator(String language) { super(); this.init(language); }
public boolean init(String language) {
try {
FacesContext context = FacesContext.getCurrentInstance();
if (language!=null) this.language=language;
Properties data = new Properties();
// load translation strings from properties file in WEB-INF
data.load(new InputStreamReader(context.getExternalContext().getResourceAsStream("WEB-INF/translations_"+this.language+".properties"),"UTF-8"));
super.putAll(new HashMap<String,String>((Map) data));
// serializing the bean to a file on disk > this part of the code is just here to easily test how often the bean is initialized
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("C:\\dump\\Translator_"+this.language+"_"+new Date().getTime()+".ser"));
out.writeObject(this);
out.close();
return true;
}
catch (Exception e) { return false; }
}
public String getLanguage() { return this.language; }
public boolean setLanguage(String language) { return this.init(language); }
// special get function which is more tolerant than HashMap.get
public String get(Object key) {
String s = (String) key;
if (super.containsKey(s)) return super.get(s);
if (super.containsKey(s.toLowerCase())) return super.get(s.toLowerCase());
String s1 = s.substring(0,1);
if (s1.toLowerCase().equals(s1)) {
s1=super.get(s1.toUpperCase()+s.substring(1));
if (s1!=null) return s1.substring(0,1).toLowerCase()+s1.substring(1);
} else {
s1=super.get(s1.toLowerCase()+s.substring(1));
if (s1!=null) return s1.substring(0,1).toUpperCase()+s1.substring(1);
}
return s;
}
}
I use "extends HashMap" because in this way i only have to write "${myTranslatorBean['someText']}" (expression language) to get the translations into my XPage. The problem is that the bean is re-initialized at EVERY complete refresh or page reload. I tested this by serializing the bean to a unique file on the disk at the end of every initialisiation. In my other managed Java beans (which do not use "extends HashMap") this problem does not occur. Can anybody tell me what's wrong with my code? Thanks in advance.
EDIT: The entry for the managed Java bean in the faces-config.xml looks like this:
<managed-bean>
<managed-bean-name>myTranslatorBean</managed-bean-name>
<managed-bean-class>com.ic.Translator</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
I concur with David about the faces-config entry - if you could post it, that could shine some light on it.
In its absence, I'll take a stab at it: are you using a managed property to set the "language" value for the app. If you are, I suspect that there's a high chance that the runtime calls the setLanguage(...) method excessively. Since you call this.init(...) in that method, that would re-run that method repeatedly as well.
As a point of code style you are free to ignore, over time I (in part due to reading others' opinions) have moved away from extending collection classes directly for this kind of use. What I do instead in this situation is create an object that implements the DataObject interface and then uses a HashMap internally to store cached values. That's part of a larger industry preference called "Composition over inheritance": http://en.wikipedia.org/wiki/Composition_over_inheritance
Just to make sure nothings weird - I suggest you post your faces-config. I use beans all the time but haven't extended HashMap in any of them. You can add a map and still use EL.
Assuming you have a map getter like "getMyMap()" then EL might be:
AppBean.myMap["myKey"]
Truth be told I don't typically use that syntax but I BELIEVE that works. I gave it a quick test and it didn't work as I expected so I'm missing something. I tried something like:
imageData.size["Large"].url
I THINK it didn't work for me because my bean doesn't IMPLEMENT Map. I notice you're EXTENDING HashMap. You might want to try implementing it. I found an interesting post here: http://blog.defrog.nl/2012/04/settings-bean-parameterized-method-call.html
Usually I do still use SSJS to pass Parameters in. It's really not the end of the would using SSJS for that. And I use EL for everything else.
This is an example of passing an object to a custom control and return a TreeSet with EL.
value="#{compositeData.imageSet.allImages}">
Regarding the bigger issue of the bean re-initializing.. That is odd.. I don't do a ton with ApplicationScope. But I suggest you play with the constructor. I'm not sure what you get by calling super() there. I would suggest use a boolean to only run any init code of the boolean wasn't already set. Obviously you then set it in the init code. See what that does.
I need to set an event handler on objects that get instantiated by OrmLite, and can't figure out a good way to do it short of visiting every Get method in a repo (which obviously is not a good way).
To give some background - say I have a class User, which is pulled from database; it also implements INotifyPropertyChanged. I want to assign a handler to that event. Having it auto-populated from Funq would be ideal, but of course OrmLite doesn't ask Funq to hydrate the new object.
So I'm stuck.
Any hints in a right direction would be appreciated.
It sounds to me like you're mixing in presentation logic with your data access logic. If I was in your position I would not attempt to implement INotifyPropertyChanged on a model (such as your User class). Instead I would create a ViewModel and place the databinding logic there (MVVM Style).
Having INotifyPropertyChanged on the data model is not quite logical when you get down to it. If I were to update the database record it would not fire this event for example (but the property has changed). It makes a lot more sense on a ViewModel.
Beyond solving your original issue it also makes building complex screens a lot easier by letting you aggregate, compose, and filter data for display purposes. If you need to pull in information from your database, a RSS feed, a stock ticker web API, and twitter you can do so in your ViewModel.
public class User
{
[AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
}
public class UserViewModel : INotifyPropertyChanged
{
private string _name;
public UserViewModel(User user)
{
_name = user.Name;
}
public string Name
{
get { return _name; }
set {
if (value == _name) return;
_name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Small Note: This answer was written in the context of display data on a screen with a ViewModel, however, the same concept applies to observing model changes for any purpose.
This has baffled me for a while now and I cannot seem to get the grasp of it. I'm using Cell Value Factory to populate a simple one column table and it does not populate in the table.
It does and I click the rows that are populated but I do not see any values in them- in this case String values. [I just edited this to make it clearer]
I have a different project under which it works under the same kind of data model. What am I doing wrong?
Here's the code. The commented code at the end seems to work though. I've checked to see if the usual mistakes- creating a new column instance or a new tableview instance, are there. Nothing. Please help!
//Simple Data Model
Stock.java
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getstockTicker() {
return stockTicker.get();
}
public void setstockTicker(String stockticker) {
stockTicker.set(stockticker);
}
}
//Controller class
MainGuiController.java
private ObservableList<Stock> data;
#FXML
private TableView<Stock> stockTableView;// = new TableView<>(data);
#FXML
private TableColumn<Stock, String> tickerCol;
private void setTickersToCol() {
try {
Statement stmt = conn.createStatement();//conn is defined and works
ResultSet rsltset = stmt.executeQuery("SELECT ticker FROM tickerlist order by ticker");
data = FXCollections.observableArrayList();
Stock stockInstance;
while (rsltset.next()) {
stockInstance = new Stock(rsltset.getString(1).toUpperCase());
data.add(stockInstance);
}
} catch (SQLException ex) {
Logger.getLogger(WriteToFile.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Connection Failed! Check output console");
}
tickerCol.setCellValueFactory(new PropertyValueFactory<Stock,String>("stockTicker"));
stockTableView.setItems(data);
}
/*THIS, ON THE OTHER HAND, WORKS*/
/*Callback<CellDataFeatures<Stock, String>, ObservableValue<String>> cellDataFeat =
new Callback<CellDataFeatures<Stock, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<Stock, String> p) {
return new SimpleStringProperty(p.getValue().getstockTicker());
}
};*/
Suggested solution (use a Lambda, not a PropertyValueFactory)
Instead of:
aColumn.setCellValueFactory(new PropertyValueFactory<Appointment,LocalDate>("date"));
Write:
aColumn.setCellValueFactory(cellData -> cellData.getValue().dateProperty());
For more information, see this answer:
Java: setCellValuefactory; Lambda vs. PropertyValueFactory; advantages/disadvantages
Solution using PropertyValueFactory
The lambda solution outlined above is preferred, but if you wish to use PropertyValueFactory, this alternate solution provides information on that.
How to Fix It
The case of your getter and setter methods are wrong.
getstockTicker should be getStockTicker
setstockTicker should be setStockTicker
Some Background Information
Your PropertyValueFactory remains the same with:
new PropertyValueFactory<Stock,String>("stockTicker")
The naming convention will seem more obvious when you also add a property accessor to your Stock class:
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getStockTicker() {
return stockTicker.get();
}
public void setStockTicker(String stockticker) {
stockTicker.set(stockticker);
}
public StringProperty stockTickerProperty() {
return stockTicker;
}
}
The PropertyValueFactory uses reflection to find the relevant accessors (these should be public). First, it will try to use the stockTickerProperty accessor and, if that is not present fall back to getters and setters. Providing a property accessor is recommended as then you will automatically enable your table to observe the property in the underlying model, dynamically updating its data as the underlying model changes.
put the Getter and Setter method in you data class for all the elements.
I have an input (JSF) that should be bound to a property in my bean. This property represents another bean and has an auxiliar method that checks if it's null (I use this method a lot).
The problem is that the binding is failing to get the proper getter and setter. Instead of reading the method that returns the bean, it reads the one that return a boolean value.
The property name is guest. The methods are:
getGuest;
setGuest;
isGuest (checks if guest is null).
JSF is trying to bind the object to isGuest and setGuest, instead of getGuest and setGuest.
I cannot rename isGuest to guestIsNull or something, because that would'nt make to much sense (see the class below).
Finally, my question is: how can I bind this property to the object without renaming my methods? Is it possible?
I also accept suggestions of a better method name (but the meaning must be the same).
Entity
#Entity
public class Passenger {
private Employee employee;
private Guest guest;
public Passenger() {
}
#Transient
public boolean isEmployee() {
return null != this.employee;
}
#Transient
public boolean isGuest() {
return null != this.guest;
}
#OneToOne
public Employee getEmployee() {
return this.employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
#OneToOne
public Guest getGuest() {
return this.guest;
}
public void setGuest(Guest guest) {
this.guest = guest;
}
}
JSF
<h:inputText value="#{passenger.employee}" />
<h:inputText value="#{passenger.guest}" />
Change the method name to isGuestNull.
The problem you're seeing is due to the fact that the EL lets you use getFoo or isFoo as the naming style for getter methods that return booleans.
No, that's not possible. You've to rename them.
Another way is to add a single getter returning an enum which covers all cases.
public enum Type {
GUEST, EMPLOYEE;
}
public Type getType() {
return guest != null ? Type.GUEST
: employee != null ? Type.EMPLOYEE
: null;
}
with
<h:something rendered="#{passenger.type == 'GUEST'}">
Binding to any property using any method is possible and quite easy if you create your custom ELResolver (apidocs). elresolvers are registered in faces config, and they are responsible, given an Object and a String defining a property, for determining the value and type of the given properties (and, as the need arises, to change it).
You could easily write your own ELResolver that would only work for your chosen, single type, and use (for example in a switch statement) the specific methods you need to write and read properties. And for other types it would delegate resolving up the resolver chain. It's really easy to do, much easier than it sounds.
But don't do it. The standard naming pattern of properties predates EL by many years. It is part of the JavaBeans™ standard - one of the very few undisputed standards in Javaland, working everywhere - from ant scripts, through spring configuration files to JSF. Seeing methods isPerson and getPerson in one class actually makes me fill uneasy, as it breaks something I always take for granted and can always count on.
If you like DDD and want to have your method's names pure, use an adapter. It's easy, fun, and gives a couple of additional lines, which is not something to sneer at if you get paid for the ammount of code produced:
public class MyNotReallyBean {
public String checkName() { ... }
public String lookUpLastName() { ... }
public String carefullyAskAboutAge() { ... }
public class BeanAdapter {
public String getName() { return checkName(); }
public String getLastName() { return lookUpLastName(); }
public String getAge() { return carefullyAskAboutAge(); }
}
private static BeanAdapter beanAdapter = new BeanAdapter();
private BeanAdapter getBeanAdapter(){ return beanAdapter; }
}