I am using ZKOSS MVVM. So in View I am using a Listbox and it's bound (@load) to a list model object in ViewModel.
What I understand from documentation, if I change the model
1: Add an object to list model from View Model at index 0
I should see the latest object be appended at top of the Listbox.
2: Remove an item from model
I should see that particular row from Listbox be removed.
Note: It's an interface like social network e.g. Facebook wall when someone create a post and new post is appended to the posts list. If a post is deleted only that post is deleted from the list
Well, it does happen (new item gets appended/deleted item gets removed) but the whole Listbox reloads and not just that particular row which was added or removed.
Why is that? Why Listbox reloads fully on list model change.
Any idea?
Here are the code snippets (Use Case: Add new post is applicable. On creating new post whole Listbox reloads every time):
View
<z:div style="height: 100%; padding: 0px; margin: 0px;" apply="org.zkoss.bind.BindComposer"
viewModel="@id('want_vm') @init('want.WantDesktopVM')">
<z:div zclass="content">
<g:render template="../css/list/noEffectList"></g:render>
<z:div hflex="1" width="100%" visible="@load(want_vm.toggleInput)" style="margin-bottom: 5px; padding: 5px">
<z:vbox>
<z:textbox id="postInput" multiline="true" value="" width="690px" height="50px"/>
<z:div hflex="1" width="100%" style="text-align: right; padding-right: 5px">
<z:button label="Post" zclass="button rect theme" onClick="@command('post', text=postInput.value)"/>
</z:div>
</z:vbox>
</z:div>
<z:listbox model="@load(want_vm.posts)" emptyMessage="No new posts found." style="border:none;">
<z:template name="model" var="iwant">
<listitem style="margin-top: 10px">
<listcell>
<hbox hflex="true">
<div zclass="dpFrame small">
<image height="50px" width="50px" content="@load(iwant.from) @converter('converter.UserActorDisplayPicConverter')" />
</div>
<vbox hflex="true" zclass="post">
<hbox hflex="true">
<label value="@load(iwant.from) @converter('converter.ActorDisplayNameConverter')" zclass="displayName"/>
</hbox>
<hbox hflex="true">
<label value="@load(iwant.textData)" zclass="post_data" multiline="true" maxlength="25"/>
</hbox>
<hbox>
<label value="@load(iwant.dateCreated) @converter('converter.SinceDateConverter')" zclass="since"/>
</hbox>
</vbox>
</hbox>
</listcell>
</listitem>
</z:template>
</z:listbox>
</z:div>
ViewModel
class WantDesktopVM {
UserActorManagerService userActorManagerService
ActivityManagerService activityManagerService
UserActor me
UserActor profile
String error = null
String view = 'iwant'
@Wire
Textbox postInput
private List<Activity> posts = []
@Init
public void init(@ContextParam(ContextType.COMPONENT) Component component,
@ContextParam(ContextType.VIEW) Component view) {
profile = Executions.current.getAttribute("profile")
me = Executions.current.getAttribute("me")
loadPosts()
}
@AfterCompose
public void afterCompose(@ContextParam(ContextType.VIEW) Component view) {
Selectors.wireComponents(view, this, false);
}
public boolean isMyProfile() {
return me.id == profile.id
}
public UserActor getMe() {
return this.me
}
public boolean isToggleInput() {
return this.view == 'iwant' && isMyProfile()
}
public List<Activity> getPosts() {
println "Getting posts ...${posts.size()}"
return this.posts
}
private List<Activity> loadPosts() {
if(view == 'iwant') {
posts = Activity.createCriteria().list() {
eq 'from', profile
eq 'type', ACTIVITY_TYPE.WANT
order("lastUpdated", "desc")
}
} else {
posts = ActorActivitySpace.createCriteria().list() {
projections {property("activity")}
eq 'actor', profile
activity {
ne 'from', profile
eq 'type', ACTIVITY_TYPE.WANT
}
order("lastUpdated", "desc")
}
}
return posts
}
@NotifyChange(['posts', 'toggleInput'])
@Command
public void render(@BindingParam('view') String view) {
println "Changing view ..."
this.view = view
loadPosts()
}
@NotifyChange('posts')
@Command
public void post(@BindingParam('text') String text) {
println "Posting text: $text"
postInput.setValue("")
if(text) {
Activity want = activityManagerService.want(me.id, text)
println"Want ID : $want.id"
posts.addAll(0, [want])
}
}
}
You use @NotifyChange('posts')
to tell ZK that the whole list has changed. The grid doesn't try to examine the list, it simply replaces its current ListModel
with the new list -> full reload.
If you don't want that, you will have to use the methods of the ListModel
used by the grid to update the ui. That way, the grid will know exactly which rows have changed and only update those.
[EDIT] To achieve what you want, replace List<Activity> posts
with ListModelList<Activity> posts = new ListModelList<Activity>()
When the activities change, you must update this list (i.e. call add()
or addAll()
) to update individual rows. You can no longer load everything from the database, you must merge changes in the database with the existing list.