How can I tell from a Wicket Page object that it has changed? - wicket

Here's my scenario. I'm testing a Wicket app, and I'm parsing the page text wicktetTester.getServletResponse.getDocument as XML in order to find components with XPath. This is quite expensive, so I'd like to keep the dom4j.Document until the page changes, then rebuild it.
I know the current page - wicketTester.getLastRenderedPage but if I for example submit a form and stay on the same page, the Page object is the same object. What property of the page can I query to know that it has been re-rendered and that I need to rebuild my DOM?
public Document getDocument() {
if (tester.getLastRenderedPage() != lastPageParsed && SOME_OTHER_TEST) {
cachedDocument = parse(tester.getServletResponse().getDocument();
lastPageParsed = tester.getLastRenderedPage();
}
return cachedDocument;
}

Better see TagTester. I guess it will serve you better than XPath.

Related

How to keep track of history in Wicket?

When using setResponsePage(Somepage.class) or setResponsePage(new Somepage()) I want to know the page where the setResponsePage(...) is called from in the Page I'm going to.
setResponsePage in Component is final so I can't override it.
I don't want to set the current page in the next one manually like so:
Page page = new Somepage();
page.setReturnPage(new Returnable() {
#Override
public BasePage onReturn() {
// use some local final variable to create a new instance of this previous page
}
});
setResponsePage(page);
I just want it to be available. I do want to be able to manually change the "returnpage" if I want.
I tried to keep track of this using the Session but that didn't work.
I'm currently trying to figure out if this is possible using an IRequestCycleListener but I can't determine the Page I'm coming from in the RequestCycle.
Any help is appreciated!
You can design your page to take a PageReference argument in its constructor. Every Page descendant has getPageReference() method to return this for you.
public SomePage(PageReference pageRef) {
this.pageRef = pageRef;
}
Then when you need to return to the previous page you can simply call
setResponsePage(this.pageRef.getPage());

Zend creating forms based on requests within one controller/action

I don't really know how to word the title well, but here's my issue. I decided instead of having 25 controllers to handle pages, I have one PageController with a viewAction that takes in a :page parameter - for example, http://localhost/website/page/about-us would direct to PageController::viewAction() with a parameter of page = about-us. All of the pages are stored in a templates folder, so the viewrenderer is set to render application\templates\default\about-us.phtml.
I did this so I can consolidate and it seemed like a better approach. My question is the following: lets say when the page request is contact-us, I would need a Zend_Form to be used within the contact page. So, I would need a way within PageController::viewAction() to recognize that the page needs to have a form built, build the form, and also upon submission the need to process it (maybe this should be handled in an abstract process method - not sure).
I have no idea how to implement this. I thought maybe I can store a column with the name of a form and a connecting page identifier. Even better, create a one-to-many page to forms, and then in the submission loop through the forms and check if submitted and if so then process it (maybe there is a isSubmitted() method within zend_form. I really don't know how to handle this, and am looking for any help i can get.
Thanks!
Here is something that came to mind that may work or help point you in a direction that works for you.
This may only work well assuming you were to have no more than one form per page, if you need more than one form on a page, you would have to do something beyond this automatic form handling.
Create a standard location for forms that are attached to pages (e.g. application/forms/page). This is where the automatic forms associated with pages will be kept.
In your viewAction, you could take advantage of the autoloader to see if a form for that page exists. For example:
$page = $this->getParam('page');
$page = ucfirst(preg_replace('/-(\w)/ie', "strtoupper('$1')", $page)); // contact-us -> ContactUs
$class = 'Application_Form_Page_' . $page;
// class_exists will invoke the autoloader to map a class to a file
if (class_exists($class)) {
// a form is defined for this page
$form = new $class();
// check if form was posted
if ($this->getRequest()->isPost()) {
if ($form->isValid($this->getRequest()->getPost()) {
// form is valid - determine how to process it
}
}
// assign the form to the view
$this->view->pageForm = $form;
}
All this really leaves out is the action you take to process a specific form. Since the contact form will likely generate an email, and another form may insert data into a database, you will need some sort of callback system or perhaps another class that can be mapped automatically which contains the form processor code.
Anyway something along those lines is what came to mind first, I hope that helps give you some more ideas.

How do I avoid repetition when passing variables from the Controller/Action to the Layout

I am currently working on a project developed using Zend Framework, based on the structure of my web page design I have reached a point where I have to pass a small number of variables to my layout from each Controller/Action. These variables are:
<?php Zend_Layout::getMvcInstance()->assign('pageId', 'page1'); ?>
<?php Zend_Layout::getMvcInstance()->assign('headerType', '<header id="index">'); ?>
The reason for passing this information is firstly, I pass the page id as the multi column layout may change depending on the content being displayed, thus the page id within the body tag links the appropriate CSS to how the page should be displayed. Secondly I display a promotional jQuery slider only on the index page, but I need the flexibility to have it displayed on potentially multiple pages in case the wind changes and the client changes their mind.
My actual question: Is there a more appropriate method of passing this information to the Layout that I am overlooking?
I am not really questioning whether the information has to be sent, rather is there some Zend Framework feature that I have, in my haste, overlooked which would reduce the amount of repetitive redundant code which may very well be repeated in multiple Actions within the same controller?
You could turn that logic into an action helper than you can call from your controllers in a more direct way. You could also make a view helper to accomplish the same thing but view helpers usually generate data for the view rather than set properties.
// library/PageId.php
class Lib_PageId extends Zend_Controller_Action_Helper_Abstract
{
public function direct($title, $pageId, $headerType)
{
$view = $this->getActionController()->view;
$view->headTitle()->append($title);
$view->pageId = $pageId;
$view->headerType = $headerType;
}
}
In your controller actions you can now do this:
$this->_helper->PageId('Homepage', 'page1', 'index');
// now pageId and headerType are available in the view and
// Homepage has been appended to the title
You will also need to register the helper path in your Bootstrap like this:
protected function _initActionHelpers()
{
Zend_Controller_Action_HelperBroker::addPrefix('Lib');
}
Doing it like that can reduce the amount of repetitive code and remove needing to assign the values from the view. You can do it in the controller very quickly. You can also have default values in the case that the helper hasn't been called.
You shoudn't really be passing anything from the view to the layout, for a start the view should be included IN the layout, not the other way around.
So, setting your page title should be done using similar code to what you have, but inside the controller action being called:
$this->view->headTitle()->append('Homepage');
And the other two issues - you need to rethink as I stated to begin with. Maybe you're misunderstanding the layout/view principle? If you include the different views per action, then you simply change the div id when needed, and include the header for your banner only in the index.phtml file.

Showing an initially selected object in an ObjectAutoCompleteField on page load in Wicket

I've followed the Wicket by Example guide to get the ObjectAutoCompleteField working, and it does so quite nicely.
I have a huge problem, though, and that is to show an initially set object in the field when the page loads. The object is retrieved from a model I use for the form where the ObjectAutoCompleteField is used. Changing the ObjectAutoCompleteField changes the model attribute it is "connected" to, and any subsequent changes in the field shows the appropriate label in its place, just not the initial one when the page loads—the only thing that shows is the edit link (to get to the autocomplete functionality).
I've looked around in the documentation for the ObjectAutoCompleteBuilder but haven't found any corresponding method to even set the initial value explicitly on page load.
I finally managed to find a solution by looking through the classes relating to ObjectAutoCompleteField.
The ObjectAutoCompleteField is constructed by the build method in ObjectAutoCompleteBuilder. So, by calling the readOnlyRenderer method on the builder, creating a new ObjectReadOnlyRenderer creating a label inside its getObjectRenderer, I got the ObjectAutoCompleteField to render a preselected object on page load.
ObjectAutoCompleteBuilder<Author, Long> builder = new ObjectAutoCompleteBuilder<Author, Long>(provider);
builder.readOnlyRenderer(new ObjectReadOnlyRenderer<Long>() {
public Component getObjectRenderer(String id, IModel<Long> pModel, IModel<String> pSearchTextModel) {
return new Label(id, new PropertyModel<Author>(model, "author"));
}
});
One would think that this was the standard behaviour, but now I know for future reference.

CollapsiblePanelExtender: Can I initiate collapse/expand from client-side javascript? (AJAX Control Toolkit)

The CollapsiblePanelExtender seems primarily designed to collapse/expand things in response to user mouse events. Is there also a good way to get the extender to collapse/expand things in response to client-side javascript?
In my particular case, I have a number of CollapsiblePanelExtenders (and their corresponding Panels) on a page, and I'm wondering if I could implement an "expand all panels" button by doing something like this strictly on the client side:
for each CollapsiblePanelExtender on this page, call somethingOrOther(extender)
I can implement this logic server-side instead if I did a full postback, but my page takes a long time to load, and so this doesn't seem like it would provide a very slick user experience. Thus I am interested in doing expand/collapse client-side.
It seems like this isn't a use case the AJAX Control Toolkit people had in mind, but I thought I'd check.
Write the following code in the OnClick event of Image/button
<asp:Image ID="img1" runat="server" OnClick="ExpandCollapse()"/>
function ExpandCollapse() {
$find("collapsibleBehavior1").set_Collapsed(true);
$find("collapsibleBehavior2").set_Collapsed(true);
}
Hope this helps!
I have a partly working solution now.
I followed Ian's suggestion and looked through the toolkit source. In CollapsiblePanelBehavior.debug.js, you can that expandPanel() is apparently intended as part of the public interface for the behavior. There's also a get_Collapsed(). The key to accessing these behaviors in javascript seems to be setting the BehaviorID property on your CollapsiblePanelExtender tags in ASP.NET.
I modified the repeater on my page so that the BehaviorIDs are predictible, along these lines:
<ajaxToolkit:CollapsiblePanelExtender
BehaviorID="<%#'collapsebehavior'+DataBinder.Eval(Container.DataItem,'id')%>"
ID="CollapsiblePanelExtender" runat="server" />
This results with behaviors named collapsebehavior1, collapsebehavior2, collapsebehavior3, etc..
With this done, I'm able to expand all the collapsible panels on the client as follows:
function expandAll() {
var i = 0;
while (true) {
i++;
var name = 'collapsebehavior' + i;
var theBehavior = $find(name);
if (theBehavior) {
var isCollapsed = theBehavior.get_Collapsed();
if (isCollapsed) {
theBehavior.expandPanel();
}
} else {
// No more more panels to examine
break;
}
}
}
I'm sure using $find in a loop like that is really inefficient, but that's what I have so far.
Also, it doesn't work on Firefox for some reason. (On FF only the first element expands, and then there's a Javascript error inside the Control Toolkit code.)
This will all seem extremely ugly to all you javascript pros. Maybe I'll clean things up later, or you can help me out.
You can also just toggle the panels to switch between collapsed/expanded states:
function toggle() {
var MenuCollapser = $find("name");
MenuCollapser.togglePanel();
}