I've got this webpage using Wicket, which loads in some data with some headlines, and then tons of rows beneath with the corresponding data.
Now, I want my data to be able to get sorted when i click on one of the headlines. From what I've found on google, SortableDataProvider is the way to go. But every example I find requires 1 parameter, whereas when I implement it, it requires two. What to do?
My class to sort is this:
public class SimpleDataView extends WebPage {
public SimpleDataView() {
addNumberOfRecords();
addHeadlines();
addRecords();
}
private void addNumberOfRecords() {
Data dataModel = getDataModel().getObject();
add(new Label("size", "Number of records: " + dataModel.numberOfRecords()));
}
private void addRecords() {
Data dataModel = getDataModel().getObject();
ListView records = new ListView("records", dataModel.getRecords()) {
#Override
protected void populateItem(ListItem item) {
item.add(new ListView("recordColumn", item.getModel()) {
#Override
protected void populateItem(ListItem item) {
item.add(new Label("value", item.getModel()));
}
});
}
};
add(records);
}
private void addHeadlines() {
Data dataModel = getDataModel().getObject();
ListView headlines = new ListView("headlines", dataModel.getHeaders()) {
#Override
protected void populateItem(ListItem item) {
item.add(new Label("headlineColumn", item.getModel()));
}
};
add(headlines);
}
private IModel<Data> getDataModel() {
IModel<Data> model = new LoadableDetachableModel<Data>() {
#Override
protected Data load() {
DataHandler dataHandler = new DataHandlerImpl();
return dataHandler.getDataFromSource();
}
};
return model;
}
}
This is a basic example in the Wicket wiki.
https://cwiki.apache.org/confluence/display/WICKET/Simple+Sortable+DataTable+Example
You can adjust the columns if you need more or less columns.
If you use SortableDataProvider, you should (when creating columns) define a sortProperty in the column's creation. Every one of the sortable-filterable columns has a constructor, with the possibility to define by hand the sortProperty.
It should by the way be identical to the generated SQL query's part that it points to.
Related
I have a students_table and there are stored students of different levels. I want to display students by one level and hide other levels.
I select student to show like this:
if (id == R.id.beginners) {
stLvl = 0;
}else if (id == R.id.intermediate) {
stLvl = 1;
}else if (id == R.id.advanced) {
stLvl = 2;
}else if (id == R.id.high_level) {
stLvl = 3;
}
showStud();
And here it is showStud ();
public void showStud() {
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
final StudentAdapter adapter = new StudentAdapter();
recyclerView.setAdapter(adapter);
setStLvl(stLvl);
if (stLvl == 0) {
studentViewModel = ViewModelProviders.of(this).get(StudentViewModel.class);
studentViewModel.getAllStudents().observe(this, new Observer<List<Student>>() {
#Override
public void onChanged(#Nullable List<Student> students) {
// update RecyclerView
adapter.submitList(students);
}
});
}else {
studentViewModel = ViewModelProviders.of(this).get(StudentViewModel.class);
studentViewModel.getStudentsByLevel().observe(this, new Observer<List<Student>>() {
#Override
public void onChanged(#Nullable List<Student> students) {
// update RecyclerView
adapter.submitList(students);
}
});
}
}
First time when the code run it works perfect, no matter the value of stLvl, but when I change it's value is not displaying what I want, or nothing at all.
I think the problem is at this line:
studentViewModel = ViewModelProviders.of(this).get(StudentViewModel.class);
First time it runs, it is working ok, going to StudentViewModel.class doing what is supposed to do, but second time just jumps to next line of code, without going to StudentViewModel.class.
What am I doing wrong? Thank you in advance!
First of all, reading this Guide to app architecture will help you get the general idea of how these architectural components should work together. The rule of thumb is,
each component depends only on the component one level below it.
This also means that each component should not depend on the components above it. For example, the repository should not depend on neither ViewModels nor Activities. Your code can be refactored in this way:
StudentRepository:
private StudentDao studentDao;
// public int stLevel;
// public void setStLvl() { // Do not read view components. Do not store their states.
// MainActivity mainActivity = new MainActivity();
// stLevel = mainActivity.getStLvl();
// }
public StudentRepository(Application application) {
AppDatabase database = AppDatabase.getInstance(application);
studentDao = database.studentDao();
// setStLvl();
}
.
.
.
public LiveData<List<Student>> getAllStudents() {
return studentDao.getAllStudents();
}
public LiveData<List<Student>> getStudentsByLevel(int stLevel) {
return studentDao.getStudentsByLevel(stLevel);
}
In the above example, the repository looks like it doesn't do much, and that is normal because there is only one layer below it, Room. In real practice you can have other data sources including network clients and cache. The repository's job is to abstract all data source logics.
ViewModel:
private MutableLiveData<Integer> studentLevel; // This will store the student level
private LiveData<List<Student>> studentsByLevel; // This will store the list of students
public StudentViewModel(#NonNull Application application) {
super(application);
repository = new StudentRepository(application);
studentLevel = new MutableLiveData<>();
// Place your logic inside the ViewModel
// Change in studentLevel will be reflected to studentsByLevel
studentsByLevel = Transformations.switchMap(studentLevel, lvl -> {
if (studentLevel == 0) {
return repository.getAllStudents();
} else {
repository.getStudentsByLevel(stLevel);
}
});
studentLevel.setValue(0) // Set initial student level.
}
.
.
.
public void setStudentLevel(int level) { // Change studentLevel anytime.
return studentLevel.setValue(level);
}
public LiveData<List<Student>> getStudentList() {
return studentsByLevel;
}
I am not a fan of LiveData, but here's what I would do. Keep all of your logic in ViewModel and make the view layer as simple as possible.
Lastly, Activity:
private StudentViewModel studentViewModel
protected void onCreate(Bundle savedInstanceState) {
...
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
final StudentAdapter adapter = new StudentAdapter();
recyclerView.setAdapter(adapter);
studentViewModel = ViewModelProviders.of(this).get(StudentViewModel.class);
studentViewModel.observe(this, students -> {
adapter.submitList(students);
});
// studentViewModel.setValue(1) // call this function anywhere you like.
}
Above code will show all students because we set the default value to 0 in the viewmodel. Call studentViewModel.setValue(/*any integer*/) to switch the list to any level.
Yes, you are right, in fact I am a beginner in android programming. Here is StudentViewModel:
private StudentRepository repository;
private LiveData<List<Student>> allStudents;
private LiveData<List<Student>> studentsByLevel;
public StudentViewModel(#NonNull Application application) {
super(application);
repository = new StudentRepository(application);
int stLevel = 0;
studentsByLevel = repository.getStudentsByLevel(stLevel);
allStudents = repository.getAllStudents();
}
.
.
.
public LiveData<List<Student>> getAllStudents() {
return allStudents;
}
public LiveData<List<Student>> getStudentsByLevel() {
return studentsByLevel;
}
StudentRepository:
private StudentDao studentDao;
private LiveData<List<Student>> allStudents;
private LiveData<List<Student>> studentsByLevel;
public int stLevel;
public void setStLvl() {
MainActivity mainActivity = new MainActivity();
stLevel = mainActivity.getStLvl();
}
public StudentRepository(Application application) {
AppDatabase database = AppDatabase.getInstance(application);
studentDao = database.studentDao();
setStLvl();
studentsByLevel = studentDao.getStudentsByLevel(stLevel);
allStudents = studentDao.getAllStudents();
}
.
.
.
public LiveData<List<Student>> getAllStudents() {
return allStudents;
}
public LiveData<List<Student>> getStudentsByLevel(int stLevel) {
return studentsByLevel;
}
In StudentDao I have:
#Query("SELECT * FROM student_table WHERE level = :level")
LiveData<List<Student>> getStudentsByLevel(int level);
I hope that I provided enough data.
In vaadin7, I used GeneratedPropertyContainer to do this, eg adding row number:
wrappedContainer = new GeneratedPropertyContainer(_container);
wrappedContainer.addGeneratedProperty("#",
new PropertyValueGenerator<Integer>() {
#Override
public Integer getValue(Item item, Object itemId, Object propertyId) {
return (int) _container.indexOfId(itemId) + 1;
}
#Override
public Class<Integer> getType() {
return java.lang.Integer.class;
}
}
);
setContainerDataSource(wrappedContainer);
In vaadin8, since GeneratedPropertyContainer is deprecated, I tried to do like this:
grid.addColumn((v)->((List)_container.getData().getItems()).indexOf(v)+1);
But the index is static, when I sort the rows ascending and descending, the row number is moving too.
What I need is the first row is row number 1 and the last row is row number N, no matter how I sort the rows.
Thanks.
Unfortunately, as far as I can tell there is no simple solution out of the box. However, you can do like the following code:
Grid<MyBean> grid = new Grid<>();
grid.setDataProvider(new RowIndexDataProviderWrapper<>(DataProvider.ofItems(new MyBean("Item 1"), new MyBean("Item 2"), new MyBean("Item 3"))));
grid.addColumn(MyBean::getRowIndex).setCaption("#");
grid.addColumn(MyBean::getName).setCaption("Name");
public interface RowIndexAware {
void setRowIndex(int rowIndex);
int getRowIndex();
}
public class MyBean implements RowIndexAware {
// implement the interface (e.g. store row index in field)
// and add your bean properties
}
public class RowIndexDataProviderWrapper<T extends RowIndexAware, F> implements DataProvider<T, F> {
private DataProvider<T, F> wrapped;
public RowIndexDataProviderWrapper(DataProvider<T, F> wrapped) {
this.wrapped = wrapped;
}
// delegate all methods to be implemented for DataProvider interface
// to wrapped DataProvider with the exception of "fetch":
#Override
public Stream<T> fetch(Query<T, F> query) {
List<T> result = wrapped.fetch(query).collect(Collectors.toCollection(ArrayList::new));
for (int i = 0; i < result.size(); i++) {
result.get(i).setRowIndex(query.getOffset() + i);
}
return result.stream();
}
}
The idea is to get the row index when rows are fetched within the DataProvider and to store them in your bean.
I'm trying to see how it would be possible to chain together x number of ObservableCollections.CollectionChanged event, exposed as a N level depth object tree to a single parent level CollectionChanged event that consumers can listen to? Essentially I want to funnel or bubble all child CollectionChanged events up to the top most parent. A number of solution I've noticed that tackle similar issues make an assumption of a fixed number of levels, say 2 deep. I idea is to support any level of depth.
Originally I had hoped I could just pass the instance of the FieldInfos to the child constructors and attach directly to the handler. However i get an error stating the "Event 'CollectionChanged' can only appear on the left hand side of+= or -=.
Thanks,
public class FieldInfos
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
private ObservableCollection<Field> _fields;
public ObservableCollection<Field> Fields => _fields ?? (_fields = new ObservableCollection<Field>());
}
public class Field
{
public string Name;
private ObservableCollection<FieldInstance> _instances;
public ObservableCollection<FieldInstance> Instances => _instances ?? (_instances = new ObservableCollection<FieldInstance>());
}
public class FieldInstance
{
public string Id { get; set; }
}
The simplest approach is subclass the original ObservableCollection<T>.
You'd need at least one interface to avoid covariance problems. You can also have your own classes to implement the INotifyDescendantsChanged interface.
public interface INotifyDescendantsChanged
{
event NotifyCollectionChangedEventHandler DescendantsChanged;
}
public class ObservableBubbleCollection<T> : ObservableCollection<T>, INotifyDescendantsChanged
{
public event NotifyCollectionChangedEventHandler DescendantsChanged;
protected virtual void OnDescendantsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler handler = DescendantsChanged;
if (handler != null)
handler(sender, e);
}
private readonly Func<T, INotifyDescendantsChanged> childSelector;
public ObservableBubbleCollection() { }
public ObservableBubbleCollection(Func<T, INotifyDescendantsChanged> childSelector)
{
this.childSelector = childSelector;
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
OnDescendantsChanged(this, e);
if (childSelector == null)
return;
if (e.NewItems != null)
foreach (var item in e.NewItems.Cast<T>())
childSelector(item).DescendantsChanged += OnDescendantsChanged;
if (e.OldItems != null)
foreach (var item in e.OldItems.Cast<T>())
childSelector(item).DescendantsChanged -= OnDescendantsChanged;
}
}
To use it, replace instances of ObservableCollection and pass a selector to the collection.
public class FieldInfos
{
private ObservableBubbleCollection<Field> _fields;
public ObservableBubbleCollection<Field> Fields => _fields ?? (_fields = new ObservableBubbleCollection<Field>(fi => fi.Instances));
}
public class Field
{
public string Name;
private ObservableBubbleCollection<FieldInstance> _instances;
public ObservableBubbleCollection<FieldInstance> Instances => _instances ?? (_instances = new ObservableBubbleCollection<FieldInstance>());
}
public class FieldInstance
{
public string Id { get; set; }
}
static class Program
{
static void Main(string[] args)
{
var fi = new FieldInfos();
fi.Fields.DescendantsChanged += (sender, e) =>
{
Console.WriteLine("Change from {0}", sender.GetType());
};
var field = new Field();
fi.Fields.Add(field);
field.Instances.Add(new FieldInstance());
Console.ReadLine();
}
}
I'm trying to handle a DropDownChoice onchange event in a listView that can display a modal window. It seems working fine for first element but not for subsequent added elements.
final ModalWindow modal = new ModalWindow("modal");
modal.setOutputMarkupId(true);
form.add(modal);
final ListView<CommandeFournisseurDetails> myView = new ListView<CommandeFournisseurDetails>(
"rowsList",
new PropertyModel<List<CommandeFournisseurDetails>>(this,
"rows")) {
#Override
protected void populateItem(
final ListItem<CommandeFournisseurDetails> item) {
final CommandeCollectionJDBC myCollection = new CommandeCollectionJDBC();
CommandeFournisseurDetails row = item.getModelObject();
item.add(new Label("index",
new AbstractReadOnlyModel<Integer>() {
#Override
public Integer getObject() {
return item.getIndex() + 1;
}
}));
final DropDownChoice<String> ID_PRODUIT = new DropDownChoice(
"ID_PRODUIT", new PropertyModel<String>(row,
"ID_PRODUIT"), myCollection.getProduit());
ID_PRODUIT.setOutputMarkupId(true);
ID_PRODUIT.setMarkupId("ID_PRODUIT");
ID_PRODUIT.setLabel(Model.of("Produit"));
ID_PRODUIT.setRequired(true);
AjaxFormComponentUpdatingBehavior behavior = new AjaxFormComponentUpdatingBehavior(
"onChange") {
protected void onUpdate(AjaxRequestTarget target) {
if (!ID_PRODUIT.getDefaultModelObjectAsString()
.isEmpty()) {
final PageParameters params = new PageParameters();
params.set("message",
ID_PRODUIT.getDefaultModelObjectAsString());
params.set("type", "Produit");
modal.setPageCreator(new ModalWindow.PageCreator() {
public Page createPage() {
// Use this constructor to pass a reference
// of this page.
return new ModalContentPage(modal, params);
}
});
modal.show(target);
target.add(modal);
target.add(ID_PRODUIT);
}
}
protected void onError(AjaxRequestTarget target,
RuntimeException e) {
System.out.println(e.toString());
}
};
ID_PRODUIT.add(behavior);
AbstractSubmitLink remove = new SubmitLink("removeRowLink") {
#Override
public void onSubmit() {
getList().remove(item.getModelObject());
getParent().getParent().removeAll();
};
}.setDefaultFormProcessing(false);
item.add(remove);
}
}.setReuseItems(true);
form.add(new SubmitLink("addRowLink") {
#Override
public void onSubmit() {
rows.add(new CommandeFournisseurDetails());
}
}.setDefaultFormProcessing(false));
myView.setOutputMarkupId(true);
form.add(myView);
Any idea why the other elements do not inherit the same event?
Thanks for your help.
All ID-PRODUIT dropdownchoices (the first, but also the rest) have the same markupId, thanks to:
ID_PRODUIT.setMarkupId("ID_PRODUIT");
Try giving them a unique MarkupId. Perhaps by adding the index of the listitem:
ID_PRODUIT.setMarkupId("ID_PRODUIT" + item.getIndex());
or remove that line of code altogether.
I have created a widget to display the slideshow.In firefox,everything is fine but in chrome nothing happens. After I refresh with many times, the slideshow is displayed. I don't know Why. Can you give me some ideas? Tks
This is my GWT client:
public SlideClient() {
super();
setStyleName("flexslider");
setHeight("100%");
setWidth("100%");
}
#Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
this.client = client;
this.paintableId = uidl.getId();
listImage = Arrays.asList(uidl.getStringArrayAttribute("listImage"));
listUrl = Arrays.asList(uidl.getStringArrayAttribute("listUrl"));
loadImage();
checkImagesLoadedTimer.run();
}
public void display() {
m.setStyleName("slides");
m.setHeight("100%");
m.setWidth("100%");
add(m);
}
public native void slideshow() /*-{
$wnd.$('.flexslider').flexslider({slideshowSpeed: 2000});
}-*/;
public native String getURL(String url)/*-{
return $wnd.open(url,
'target=_blank')
}-*/;
private Timer checkImagesLoadedTimer = new Timer() {
#Override
public void run() {
if (loadedImageElements.size() == toLoad) {
display();
} else {
add(new Label("đang load "+loadedImageElements.size()));
checkImagesLoadedTimer.schedule(2000);
}
}
};
private void loadImage() {
for (String tmp : listImage) {
AbsolutePanel panel = new AbsolutePanel();
final Image ima = new Image(tmp);
add(new Label("before put"));
ima.addLoadHandler(new LoadHandler() {
#Override
public void onLoad(LoadEvent event) {
loadedImageElements.put(toLoad+"", ima);
slideshow();
add(new Label("đang put "+loadedImageElements.size()));
}
});
add(new Label("after put"));
panel.add(ima);
m.add(panel);
if (toLoad != 0) {
panel.setVisible(false);
}
toLoad++;
}
}
}
Did you implement an Image Loader to prepare your images before they are displayed? A clean solution would be to add the image elements to your page root as an invisible istance, wait for them to load and then use them elsewhere.
You should check out the tutorials about ImageBundling as well: ImageResource
Here's a little extract from one of my image loader classes as you requested, altough there are different ways to realize that:
private HashMap<String,ImageElement> loadedImageElements = new HashMap<String,ImageElement>();
private int toLoad = 0;
private void loadImage(final String name, String url){
final Image tempImage = new Image(url);
RootPanel.get().add(tempImage);
++toLoad;
tempImage.addLoadHandler(new LoadHandler(){
public void onLoad(LoadEvent event) {
loadedImageElements.put(name,ImageElement.as(tempImage.getElement()));
tempImage.setVisible(false);
}
});
}
The image url is retrieved via a ClientBundle-Interface pointing towards the real positions of the images.
I also implemented a timer running in the background to check if all the images have been loaded:
private Timer checkImagesLoadedTimer = new Timer(){
public void run() {
System.out.println("Loaded " + loadedImageElements.size() + "/" + toLoad + " Images.");
if(loadedImageElements.size() == toLoad){
buildWidget();
}else{
checkImagesLoadedTimer.schedule(50);
}
}
};
After everythign is ready, the original widget/page is created.
But as I said there are many ways to implement image loaders. Try out different implementations and select one that suits your needs best.