Reputation: 5709
I'm trying to make the following work in Tapestry.
I have a Dictionary<String, Dictionary<String, Object>>
which contains my data.
I'm trying to accomplish that I have 1 drop-down menu (Select component) which contains the keys from outer Dictionary.
When that selection changes, grid should be updated with the keys and values from the selected sub-dictionary.
For example:
Dictionary<String, Dictionary<String, Object>> dictionaries = new Hashtable<String, Dictionary<String, Object>>();
Dictionary<String, Object> dict1 = new Hashtable<String, Object>();
Dictionary<String, Object> dict2 = new Hashtable<String, Object>();
dict1.put("k1", "d1v1");
dict1.put("k2", "d1v2");
dict2.put("k1", "d2v1");
dict2.put("k2", "d2v2");
dictionaries.put("d1", dict1);
dictionaries.put("d2", dict2);
Would anyone would be so kind to give me an example or push me in the right direction?
EDIT:
I managed to get the first part working, with grid contents changing depending on which keys from outer dictionary are selected.
However, I I'm having problems with updating the grid and saving the changes:
I am using my own GridDataSource:
public class EntityDataSource<T> implements GridDataSource {
private Class<T> persistentClass;
protected List<T> data;
protected int count = -1;
protected int startIndex = 0;
public EntityDataSource(Class<T> persistentClass, List<T> data) {
this.persistentClass = persistentClass;
this.data = data;
}
public static final <T> EntityDataSource<T> create(
Class<T> persistentClass, List<T> data) {
return new EntityDataSource<T>(persistentClass, data);
}
public int getAvailableRows() {
return this.data.size();
}
public void prepare(int startIndex, int endIndex,
List<SortConstraint> sortConstraints) {
this.startIndex = startIndex;
this.data = this.data.subList(startIndex, endIndex + 1);
}
public Object getRowValue(int index) {
return this.data.get(index - this.startIndex);
}
@SuppressWarnings("rawtypes")
public Class getRowType() {
return this.persistentClass;
}
}
My grid looks like this:
<t:form t:id="configSelectForm">
<t:select t:id="storageKeySelecter"
t:model="storageKeyModel"
t:value="storageKey"
zone="configZone" />
</t:form>
<t:zone t:id="configZone" id="configZone">
<t:form t:id="configReviewForm">
<table width="100%">
<t:grid t:source="configurationEntrySource"
t:add="update, delete"
t:row="configurationEntry"
t:mixins="DisableGridSorting"
t:include="configKey, configValue"
t:encoder="configurationEntryEncoder">
<p:configValueCell>
<input t:id="value" t:type="TextField" t:value="configurationEntry.configValue"
t:validate="required" t:context="configurationEntry.configValue" />
</p:configValueCell>
<p:updateCell>
<t:actionlink t:id="update" zone="configZone" context="[configurationEntry.configKey, configurationEntry.configValue]">Update</t:actionlink>
</p:updateCell>
<p:deleteCell>
<t:actionlink t:id="delete" zone="configZone" context="configurationEntry.configKey">Delete</t:actionlink>
</p:deleteCell>
</t:grid>
</table>
<br></br>
<!-- <input type="submit" value="Update" class="button" /> -->
</t:form>
</t:zone>
I have managed to make a delete link work, however, I cannot seem to update the value for the arbitrary key.
When I click the link to update, the value from the text field is not the one that is being passed:
public Object onActionFromUpdate(String configKey, String configValue) {
// my stuff here
return request.isXHR() ? configZone.getBody() : null;
}
The configValue is the value of the entry that currently exist and not the one I'm trying to change it to.
Is there a way to get that value? My grid has an an arbitrary number of rows.
EDIT2: Even more info provided
This is my TML:
<t:form t:id="configSelectForm">
<t:select t:id="storageKeySelecter"
t:model="storageKeyModel"
t:value="storageKey"
zone="configZone" />
</t:form>
<br/>
<t:zone t:id="configZone" id="configZone" elementName="div">
<form t:type="form" t:id="configReviewForm" id="configReviewForm" t:zone="configZone" zone="configZone">
<table width="100%">
<t:grid t:source="configurationEntries"
t:add="update, delete"
t:row="configurationEntry"
t:mixins="DisableGridSorting"
t:include="configKey, configValue"
t:encoder="configurationEntryEncoder" >
<p:configValueCell>
<input t:id="configValueTextField" t:type="TextField" t:value="configurationEntry.configValue"
t:validate="required" />
</p:configValueCell>
<p:updateCell>
<t:actionlink id="update" t:id="update" zone="configZone" t:context="[configurationEntry.configKey, configurationEntry.configValue]">Update</t:actionlink>
</p:updateCell>
<p:deleteCell>
<t:actionlink id="delete" t:id="delete" zone="configZone" t:context="configurationEntry.configKey">Delete</t:actionlink>
</p:deleteCell>
</t:grid>
</table>
<br/>
<input type="submit" value="Update" class="button" zone="configZone"/>
</form>
</t:zone>
<br/>
<br/>
<t:form t:id="addNewEntryForm">
<table width="100%">
<tr>
<td>
<input t:id="newEntryKey" t:type="textfield" t:value="newEntry.configKey"
t:validate="required" t:context="newEntry.configKey" />
</td>
<td>
<input t:id="newEntryValue" t:type="textfield" t:value="newEntry.configValue"
t:validate="required" t:context="newEntry.configValue" />
</td>
<td>
<input type="submit" value="Add New Entry" zone="configZone"/>
</td>
</tr>
</table>
</t:form>
This is my JAVA:
public class PropertyConfiguration {
@Inject
private BeanModelSource beanModelSource;
@Component
private Form configReviewForm;
@Property
private List<ConfigurationEntry> configurationEntries;
private List<ConfigurationEntry> persistentEntries;
@Property
private ConfigurationEntry configurationEntry = new ConfigurationEntry("", "");
@Property
private ConfigurationEntryEncoder configurationEntryEncoder;
@InjectComponent
private Zone configZone;
@InjectComponent
private TextField configValueTextField;
@Inject
private ConfigurationPersitanceDAO dao;
private GridDataSource dataSource;
@Inject
private Messages messages;
@Property
private ConfigurationEntry newEntry;
@Inject
private Request request;
@Property
@Validate("required")
@Persist(PersistenceConstants.SESSION)
private String storageKey;
private StringSelectModel storageKeysSelectModel;
public ValueEncoder<ConfigurationEntry> getConfigurationEntryEncoder() {
initConfigurationEntityEncoder();
return this.configurationEntryEncoder;
}
public BeanModel<ConfigurationEntry> getModel() {
return beanModelSource.createDisplayModel(ConfigurationEntry.class, messages);
}
public SelectModel getStorageKeyModel() {
if (storageKeysSelectModel == null) {
storageKeysSelectModel = new StringSelectModel(this.dao.getStorageKeys());
}
return storageKeysSelectModel;
}
private void initConfigurationEntityEncoder() {
if (this.configurationEntryEncoder == null) {
this.configurationEntryEncoder = new ConfigurationEntryEncoder(dao, storageKey);
}
}
private void initZoneData() {
if (this.storageKey == null) {
this.storageKey = this.dao.getStorageKeys().get(0);
}
initConfigurationEntityEncoder();
}
public Object onActionFromDelete(String configKey) {
System.out.println("Deleting from: " + storageKey + " entry: " + configKey);
this.dao.deleteConfigurationEntry(storageKey, configKey);
return request.isXHR() ? configZone.getBody() : null;
}
public Object onActionFromUpdate(String configKey, String configValue) {
this.dao.updateConfigurationEntry(storageKey, configKey, configValue);
return request.isXHR() ? configZone.getBody() : null;
}
void onActivate(String storageKey) {
initZoneData();
this.newEntry = new ConfigurationEntry("", "");
}
String onPassivate() {
this.newEntry = new ConfigurationEntry("", "");
return this.storageKey;
}
Object onRefresh() {
return request.isXHR() ? configZone.getBody() : null;
}
Object onSuccessFromAddNewEntryForm() {
String configKey = this.newEntry.getConfigKey();
String configValue = this.newEntry.getConfigValue();
this.dao.addConfigurationEntry(storageKey, configKey, configValue);
return request.isXHR() ? configZone.getBody() : null;
}
void onValidateFromAddNewEntryForm() {
return;
}
Object onValueChangedFromStorageKeySelecter(String storageKey) {
this.storageKey = storageKey;
initConfigurationEntityEncoder();
this.configurationEntries = wrap(this.dao.getConfiguration(storageKey));
return configZone.getBody();
}
private void updateChangedConfigurations(List<ConfigurationEntry> changedEntries) {
for (ConfigurationEntry changedEntry : changedEntries) {
String configKey = changedEntry.getConfigKey();
String configValue = changedEntry.getConfigValue();
System.out.println("Updated: [" + storageKey + ":" + configKey + ":" + configValue + "]");
this.dao.updateConfigurationEntry(storageKey, configKey, configValue);
}
}
void onValidateFromConfigReviewForm() {
this.persistentEntries = wrap(this.dao.getConfiguration(storageKey));
List<ConfigurationEntry> tmpList = new ArrayList<ConfigurationEntry>();
for (ConfigurationEntry newEntry : this.configurationEntries) {
for (ConfigurationEntry oldEntry : this.persistentEntries) {
System.out.println("NewEntry: " + newEntry.toString() + " vs. OldEntry: " + oldEntry.toString());
if (oldEntry.getConfigKey().equals(newEntry.getConfigKey())) {
if (!oldEntry.getConfigValue().equals(newEntry.getConfigValue())) {
newEntry.setConfigValue(newEntry.getConfigValue().trim());
tmpList.add(newEntry);
}
}
}
}
this.persistentEntries = tmpList;
}
Object onSuccessFromConfigReviewForm() {
updateChangedConfigurations(this.persistentEntries);
return request.isXHR() ? configZone.getBody() : null;
}
/**
* Wraps dictionary entries in instances of ConfigurationEntry
*/
private List<ConfigurationEntry> wrap(
Dictionary<String, Object> rawConfiguration) {
Set<String> keySet = new TreeSet<String>();
List<ConfigurationEntry> wrappedEntries = new ArrayList<ConfigurationEntry>();
Enumeration<String> keys = rawConfiguration.keys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
keySet.add(key);
}
for (String key : keySet) {
String value = (String) rawConfiguration.get(key);
ConfigurationEntry entry = new ConfigurationEntry(key, value);
wrappedEntries.add(entry);
}
return wrappedEntries;
}
}
For some reason, when the "Update" action link is clicked the values passed to the public Object onActionFromUpdate(String configKey, String configValue)
aren't the ones I just wrote into the designated textfield, but the ones that were fetched from my database when page was rendered.
For example, if I had these pairs initially:
key1 => value1
key2 => value2
key3 => value3
and I wanted to change value for key2
to "newValue2" the parameters passed to the method are "key2" and "value2" instead of "key2" and "newValue2".
The same problem is the reason the mass-update doesn't work. All the values in configurationEntries
are shapshot values from the database instead of the values currently written in my grid.
So, my question would be how I can update my DB from editable grid using AJAX for my concrete example. I've tried many things suggested on SO and rest of internet, but they don't seem to work and I don't know why.
Upvotes: 1
Views: 1918
Reputation: 3791
To have actual values from inputs you need to submit form. As you use ActionLink the values from its context are used (this values was saved in context during link rendering and can't be changed on client side without hacking).
To have your example working you can use separate form for each row. You will also need some submit component that can work outside the form:
<t:zone t:id="configZone" id="configZone">
<table width="100%">
<t:grid t:source="configurationEntrySource"
t:add="update, delete"
t:row="configurationEntry"
t:mixins="DisableGridSorting"
t:include="configKey, configValue"
t:encoder="configurationEntryEncoder">
<p:configValueCell>
<t:form t:id="configReviewForm" zone="configZone">
<input t:id="value" t:type="TextField" t:value="configurationEntry.configValue"
t:validate="required" t:context="configurationEntry.configValue" />
</t:form>
</p:configValueCell>
<p:updateCell>
<t:customlinksubmit form="configReviewForm">Update</t:customlinksubmit>
</p:updateCell>
<p:deleteCell>
<t:actionlink t:id="delete" zone="configZone" context="configurationEntry.configKey">Delete</t:actionlink>
</p:deleteCell>
</t:grid>
</table>
<br/>
</t:zone>
CustomLinkSubmit:
@SupportsInformalParameters
public class CustomLinkSubmit implements ClientElement {
@Parameter(required = true, allowNull = false, defaultPrefix = BindingConstants.COMPONENT)
private Form form;
@Parameter(allowNull = false, defaultPrefix = BindingConstants.LITERAL)
private SubmitMode mode = SubmitMode.NORMAL;
@Inject
private ComponentResources resources;
@Inject
private JavaScriptSupport javascriptSupport;
private String clientId;
@BeginRender
void beginRender(MarkupWriter writer) {
clientId = javascriptSupport.allocateClientId(resources);
writer.element("span", "id", clientId);
resources.renderInformalParameters(writer);
}
@AfterRender
void afterRender(MarkupWriter writer) {
writer.end();
JSONObject spec = new JSONObject()
.put("form", form.getClientId())
.put("clientId", clientId)
.put("validate", mode == SubmitMode.NORMAL);
javascriptSupport.addInitializerCall(InitializationPriority.EARLY, "linkSubmit", spec);
}
public String getClientId() {
return clientId;
}
}
Upvotes: 2
Reputation: 28016
You need to use the zone
parameter.
Take a look at the SelectZoneDemo.tml and SelectZoneDemo.java from the select component's javadoc. It gives an example of updating a zone when a select changes.
For more complex interactions, you might be interested in this
Upvotes: 3