I am trying to generate an export file out of some items, which should conform to some "criteria" in order to be able to be exported. The thing is: the user should select some items (by using checkboxes) and then click on the ICEFaces' OutputResource in order to export (hopefully all) the selected items.
The parts involved in this process are the following:
The OutputResource in the XHTML:
<ice:outputResource rendered="#{not myBackingBean.emptySelection}" resource="#{myBackingBean.excelResource}" label="export to Excel" shared="false" target="_self" />
The backing bean holding the resource:
#ManagedBean(name = "myBackingBean")
#ViewScoped
#WindowDisposed
public class MyBackingBean implements Serializable
{
...
private ExcelResource resource;
...
}
And, finally, the actual resource:
...
import com.icesoft.faces.context.Resource;
...
public class ExcelResource implements Resource
{
...
#Override
public InputStream open() throws IOException
{
//do some selection here. If there is no valid ticket to export then
//this method will return null, otherwise it will return an InputStream
//and everything will work properly
if (everythingOk)
{
return new ByteArrayInputStream(...);
}
//hopefully, it won't get to this point
return null;
}
As you can see, I'm implementing the com.icesoft.faces.context.Resource interface and overriding the open() method to create the Excel export "on the fly".
Now, once againg, what I want to do is to filter some of the originally selected items and, in case no item is left, navigate to some error page. If this was an h:commandButton or an ice:commandLink then I would use the action property to do it, but I cannot do this here because this is an ice:outputResource. Is there some workaround for this?. Please notice it is not enough to use the rendered property to do this because the user can select something (which will immediately render the ice:outputResource) but the selection should be filtered before exporting it.
Last but not least: I'm using Websphere 8 and ICEFaces 3 to do this.
Thanks in advance!
I've managed to do what I was looking for, this time using some buttons with real actions to redirect to pages.
What I did was the following:
MyBackingBean has now a method to determine whether or not the selection is empty;
It also has a method to determine if the selection is valid (this is: not empty and without any invalid item in it);
There is an <ice:commandLink> rendered when the selection is invalid. This commandLink has the actual redirection to the error page.
The <ice:outputResource> will be rendered when the <ice:commandLink> is not rendered (i.e.: when the selection is completely valid).
And now, the code:
First, the commandLink:
<ice:commandLink rendered="#{not myBackingBean.validSelection}"
disabled="#{myBackingBean.emptySelection}"
label="download excel report}"
action="redirect_to_error_page" />
Remember:
MyBackingBean.isEmptySelection() returns true if there is no item selected.
MyBackingBean.isValidSelection() returns true if there is at least one item selected and also each and every selected item is a valid item (this is: an item that is valid for export)
Now, the outputResource:
<ice:outputResource
rendered="#{myBackingBean.validSelection}"
resource="#{myBackingBean.excelResource}"
label="download excel report}" shared="false" target="_self" />
Last but not least, you may have figured out the fact that the <ice:outputResource> will now handle only valid selection (the actual redirection to the error page is being done by the <ice:commandLink>). This means there has to be a way to filter the items before passing them to the resource for the actual export. Well, in my case I decided to create a filter(...) method in the backing bean.
#ManagedBean(name = "myBackingBean")
#ViewScoped
#WindowDisposed
public class MyBackingBean implements Serializable
{
...
private ExcelResource resource;
...
public List<MyItems> getFilteredList(List<MyItems> allSelectedItems)
{
...
//do some selection here and return a list containing only valid items
return validItemsList;
}
public ExcelResource getExcelResource()
{
return new ExcelResource(getFilteredList(allSelectedItems));
}
public boolean isEmptySelection()
{
//return true if the selection is EMPTY, false otherwise.
}
public boolean isValidSelection()
{
//return true if the selection is NOT EMPTY and it has
//only VALID items in it, false otherwise.
}
}
This way you can generate the ExcelResource "on the fly" with nothing but valid items in it. By the time the <ice:outputResource> is being rendered in the XHTML, it will contain only valid and exportable items!.
I hope someone will find this useful :)
Related
I have a search page that contains a table that gets populated with search results after the user presses the search button. The page object wraps the rows of the search results table in a custom HtmlElement class.
I'm getting a stale element exception when accessing the results table - as you'd expect because it was just refreshed by an ajax request.
I've worked around it by returning a new instance of the page object after performing the search but I'd rather just recreate the search results field. Is there any way to do this?
Example:
#FindBy(css = "[data-viewid='Table1'] .dojoxGridRow")
List<ActivityPerformanceRow> results;
// ...
public void search() {
search.click()
waitForAjaxToComplete();
// If it was a standard WebElement list I'd do something like this:
results = driver.findElements(By.cssSelector(
"[data-viewid='Table1'] .dojoxGridRow"));
}
After a bit of playing around I came up with this solution - it works well but doesn't deal with HtmlElement name property. Given I don't use it that I'm aware of I'm ignoring it for now...
public <T extends HtmlElement> List<T> findElements(Class<T> elementClass, By by) {
List<T> elements = new LinkedList<T>();
for (WebElement element : driver.findElements(by)) {
elements.add(HtmlElementLoader.createHtmlElement(elementClass, element, null));
}
return elements;
}
I want to create input forms which validate user input and prevent the model from being saved with invalid data. I have been using databinding which works up to a point but my implementation is not as intuitive as I would like.
Imagine an input which contains '123' and the value must not be empty. The user deletes the characters one by one until empty. The databinding validator shows an error decoration.
However, if the user saves the form and reloads it, then a '1' is displayed in the field - i.e. the last valid input. The databinding does not transmit the invalid value into the model.
I have a ChangeListener but this is called before the databinding so at that point the invalid state has not been detected.
I would like the error to be displayed in the UI but the model remains valid (this is already so). Also, for as long as the UI contains errors, it should not be possible to save the model.
/**
* Bind a text control to a property in the view model
**/
protected Binding bindText(DataBindingContext ctx, Control control,
Object viewModel, String property, IValidator validator)
{
IObservableValue value = WidgetProperties.text(SWT.Modify).observe(
control);
IObservableValue modelValue = BeanProperties.value(
viewModel.getClass(), property).observe(viewModel);
Binding binding = ctx.bindValue(value, modelValue, getStrategy(validator), null);
binding.getTarget().addChangeListener(listener);
ControlDecorationSupport.create(binding, SWT.TOP | SWT.LEFT);
return binding;
}
private UpdateValueStrategy getStrategy(IValidator validator)
{
if (validator == null)
return null;
UpdateValueStrategy strategy = new UpdateValueStrategy();
strategy.setBeforeSetValidator(validator);
return strategy;
}
private IChangeListener listener = new IChangeListener()
{
#Override
public void handleChange(ChangeEvent event)
{
// notify all form listeners that something has changed
}
};
/**
* Called by form owner to check if the form contains valid data e.g. before saving
**/
public boolean isValid()
{
System.out.println("isValid");
for (Object o : getDataContext().getValidationStatusProviders())
{
ValidationStatusProvider vsp = (ValidationStatusProvider) o;
IStatus status = (IStatus)vsp.getValidationStatus()
.getValue();
if (status.matches(IStatus.ERROR))
return false;
}
return true;
}
Your best bet is to steer clear of ChangeListeners - as you've discovered, their order of execution is either undefined or just not helpful in this case.
Instead, you want to stick with the 'observable' as opposed to 'listener' model for as long as possible. As already mentioned, create an AggregateValidationStatus to listen to the overall state of the DataBindingContext, which has a similar effect to your existing code.
Then you can either listen directly to that (as below) to affect the save ability, or you could even bind it to another bean.
IObservableValue statusValue = new AggregateValidationStatus(dbc, AggregateValidationStatus. MAX_SEVERITY);
statusValue.addListener(new IValueChangeListener() {
handleValueChange(ValueChangeEvent event) {
// change ability to save here...
}
});
You can use AggregateValidationStatus to observe the aggregate validation status:
IObservableValue value = new AggregateValidationStatus(bindContext.getBindings(),
AggregateValidationStatus.MAX_SEVERITY);
You can bind this to something which accepts an IStatus parameter and it will be called each time the validation status changes.
How to restrict a CQ5/Custom component to add only once per page.? I want to restrict the drag and drop of component into the page when the author is going to add the same component for the second time into the same page.
One option is to include the component directly in the JSP of the template and exclude it from the list of available components in the sidekick. To do so, add the component directly to your JSP (foundation carousel in this example):
<cq:include path="carousel" resourceType="foundation/components/carousel" />
To hide the component from the sidekick, either set:
componentGroup: .hidden
or exclude it from the list of "Allowed Components" using design mode.
If you need to allow users to create a page without this component you can provide a second template with the cq:include omitted.
Thanks Rampant, I have followed your method and link stated.
Posting link again : please follow this blog
It was really helpful. I am posting the implementation whatever I have done.
It worked fine for me. One can definitely improve the code quality, this is raw code and is just for reference.
1.Servlet Filter
Keep this in mind that,if any resource gets refereshed, this filter will execute. So you need to filter the contents at your end for further processing.
P.S. chain.doFilter(request,response); is must. or cq will get hanged and nothing will be displayed.
#SlingFilter(generateComponent = false, generateService = true, order = -700,
scope = SlingFilterScope.REQUEST)
#Component(immediate = true, metatype = false)
public class ComponentRestrictorFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {}
#Reference
private ResourceResolverFactory resolverFactory;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
WCMMode mode = WCMMode.fromRequest(request);
if (mode == WCMMode.EDIT) {
SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;
PageManager pageManager = slingRequest.getResource().getResourceResolver().adaptTo(PageManager.class);
Page currentPage = pageManager.getContainingPage(slingRequest.getResource());
logger.error("***mode" + mode);
if (currentPage != null )) {
ComponentRestrictor restrictor = new ComponentRestrictor(currentPage.getPath(), RESTRICTED_COMPONENT);
restrictor.removeDuplicateEntry(resolverFactory,pageManager);
}
chain.doFilter(request, response);
}
}
public void destroy() {}
}
2.ComponentRestrictor class
public class ComponentRestrictor {
private String targetPage;
private String component;
private Pattern pattern;
private Set<Resource> duplicateResource = new HashSet<Resource>();
private Logger logger = LoggerFactory.getLogger(ComponentRestrictor.class);
private Resource resource = null;
private ResourceResolver resourceResolver = null;
private ComponentRestrictorHelper helper = new ComponentRestrictorHelper();
public ComponentRestrictor(String targetPage_, String component_){
targetPage = targetPage_ + "/jcr:content";
component = component_;
}
public void removeDuplicateEntry(ResourceResolverFactory resolverFactory, PageManager pageManager) {
pattern = Pattern.compile("([\"']|^)(" + component + ")(\\S|$)");
findReference(resolverFactory, pageManager);
}
private void findReference(ResourceResolverFactory resolverFactory, PageManager pageManager) {
try {
resourceResolver = resolverFactory.getAdministrativeResourceResolver(null);
resource = resourceResolver.getResource(this.targetPage);
if (resource == null)
return;
search(resource);
helper.removeDuplicateResource(pageManager,duplicateResource);
} catch (LoginException e) {
logger.error("Exception while getting the ResourceResolver " + e.getMessage());
}
resourceResolver.close();
}
private void search(Resource parentResource) {
searchReferencesInContent(parentResource);
for (Iterator<Resource> iter = parentResource.listChildren(); iter.hasNext();) {
Resource child = iter.next();
search(child);
}
}
private void searchReferencesInContent(Resource resource) {
ValueMap map = ResourceUtil.getValueMap(resource);
for (String key : map.keySet()) {
if (!helper.checkKey(key)) {
continue;
}
String[] values = map.get(key, new String[0]);
for (String value : values) {
if (pattern.matcher(value).find()) {
logger.error("resource**" + resource.getPath());
duplicateResource.add(resource);
}
}
}
}
}
3.To remove the node/ resource
Whichever resource you want to remove/delete just use PageManager api
pageManeger.delete(resource,false);
That's it !!! You are good to go.
None of the options looks easy to implement. The best approach I found is to use the ACS Commons Implementation which is very easy and can be adopted into any project.
Here is the link and how to configure it:
https://github.com/Adobe-Consulting-Services/acs-aem-commons/pull/639
Enjoy coding !!!
you can't prevent that without doing some massive hacking to the ui code, and even then, you've only prevented it from one aspect of the ui. there's still crxde, and then the ability to POST content.
if this is truly a requirement, the best approach might be the following:
have the component check for a special value in the pageContext object (use REQUEST_SCOPE)
if value is not found, render component and set value
otherwise, print out a message that component can only be used once
note that you can't prevent a dialog from showing, but at the very least the author has an indication that that particular component can only be used once.
It sounds like there needs to be clarification of requirements (and understanding why).
If the authors can be trained, let them manage limits of components through authoring and review workflows.
If there is just 1 fixed location the component can appear, then the page component should include the content component, and let the component have an "enable" toggle property to determine if it should render anything. The component's group should be .hidden to prevent dragging from the sidekick.
If there is a fixed set of locations for the component, the page component can have a dropdown of the list of locations (including "none"). The page render component would then conditionally include the component in the correct location. Again, prevent dragging the component from the sidekick.
In the "hard to imagine" case that the component can appear anywhere on the page, added by authors, but limited to only 1 instance - use a wrapper component to manage including the (undraggable) component. Let the authors drag the wrapper on the page as many times as they want, but the wrapper should query the page's resources and determine if it is the first instance, and if so, include the end component. Otherwise, the wrapper does nothing.
In our experience (>2years on CQ), implementing this type of business rules via code creates a brittle solution. Also, requirements have a habit of changing. If enforced via code, development work is required instead of letting authors make changes faster & elegantly.
None of these options are that great. If you truly want a robust solution to this problem (limit the number of items on the page without hardcoding location) then the best way is with a servlet filter chain OSGI service where you can administer the number of instances and then use a resource resolver to remove offending instances.
The basic gist is:
Refresh the page on edit using cq:editConfig
Create an OSGI service implementing javax.servlet.Filter that encapsulates your business rules.
Use the filter to remove excess components according to business rules
Continue page processing.
For more details see here:
Using a servlet filter to limit the number of instances of a component per page or parsys
This approach will let you administer the number of items per page or per parsys and apply other possibly complex business rules in a way that the other offered solutions simply cannot.
I have a rather complex form in the way that the number of form fields is flexibel. In short, the model object is a TLabel (TranslationLabel) that contains a Map of values (translations). Language here is an enum so the idea is that the number of fields (text areas) for which a translation is given depends on the values in this enum.
This is my form (simplified):
public class TranslationEditForm extends Form {
private final static List<Language> LANGUAGES = newArrayList(Language.values());
public TranslationEditForm(String id, final TranslationLabelView label) {
super(id, new CompoundPropertyModel<TranslationLabelView>(label));
ListView<Language> textAreas = new ListView<Language>("translationRepeater", LANGUAGES) {
#Override
protected void populateItem(final ListItem<Language> itemLang) {
//loop through the languages and create 1 textarea per language
itemLang.add(new Label("language", itemLang.getModelObject().toString()));
Model<String> textModel = new Model<String>() {
#Override
public String getObject() {
//return the value for current language
return label.getValue(itemLang.getModelObject());
}
#Override
public void setObject(String object) {
//set the value for current language
label.getTranslations().put(itemLang.getModelObject(), object);
}
};
itemLang.add(new TextArea<String>("value", textModel).setRequired(true));
}
};
//add the repeater containing a textarea per language to the form
this.add(textAreas);
}
}
Now, it works fine, 1 text area is created per language and its value is also set nicely; even more when changed the model gets updated as intended.
If you submit the form after emptying a text area (so originally there was a value) then of course there is a validation error (required). Normal (wicket) behaviour would be that the invalid field is still empty but for some reason the original value is reset and I don't understand why.
If I override onError like this:
#Override
protected void onError() {
this.updateFormComponentModels();
}
then it is fine, the value of the field is set to the submitted value (empty) instead of the original value.
Any idea what is causing this? What is wicket failing to do because the way I've set up the form (because with a simple form/model this is working fine as are the wicket examples)?
Posted as answer, so the question can be marked as solved:
ListView does recreate all its items at render time. This means that the validation will be broken. Have a look at API doc of the ListView
Calling setReuseItems() on the ListView solves this.
Regards,
Bert
I have a form where depending on the select state ([checkboxOn/checkboxOff]) a check box should appear or not. When the check box is displayed it should be checked by default.
How to handle that situation, taking into account that
when select is in 'checkboxOff'
state, I would have
MyFormObject.isCheckboxOn == false;
when select is in 'checkboxOn' state,
the value should be as in request?
All of this should work also on Validation errors postback, and when new form is shown and on valid form case.
Also, I would like to avoid using JavaScript on client side.
Here is some code, which needed to be extended:
class MyFormObject {
private String selectValue;
private boolean isCheckboxOn;
...
}
and two Spring controller's method:
#RequestMapping(method = RequestMethod.GET)
public ModelAndView showForm() {
return new ModelAndView('/form.jsp', 'command', new MyFormObject());
}
#RequestMapping(method = RequestMethod.POST)
public ModelAndView processSubmit(BindingResult result, MyFormObject command) {
if (result.hasErrors()) {
return new ModelAndView('/form', 'command', command);
}
...
return new ModelAndView('redirect:/success.jsp');
}
If you want to exclude the checkbox field from your view when isCheckboxOn is set to false, you can surround the checkbox field with a c:if in your JSP:
<c:if test="isCheckboxOn">
(your checkbox <input> tag goes here)
</c:if>
To check the box by default, you can use Spring's form tag library to bind directly to the checkbox field in the model, or you can use another c:if to add a checked to your <input> tag.