PDA

View Full Version : WEB 2.0 App? Design Considerations with Borderlayout



Wolfgang
6 Feb 2007, 2:46 AM
Hello,

yui-ext is an excellent framework. The great examples show the power of the widgets available.
But, what if putting all of this in a "web 2.0 style application"?

Given that the application should be embedded in a "Border Layout", it needs a sort of menu (can be a menubar, tree etc.) and view ports (can be panels within the regions of BL).

So how to populate the regions depending on menu selections?

Using classic php programming, a menuitem would call a php script that in turn would create a new page.

However using BL (Border Layout), it would not make sense to recreate the layout everytime.
So one would go for panels within the region(s).

There are some options i see:
- 1.) Have one page with all the required panels from markup/pre loaded javascript
(Panels are either visible or hidden - added or removed - depending on the application).
- 2.) Have one page with all required panels loaded via XHR upon request
(Panels are either visible or hidden - added or removed - depending on the application).
- 3.) Have the content of the panels loaded as iframes.
(Panels are either visible or hidden - added or removed - depending on the application).

Thoughts on 1.)
+ simple to do
- one big javscript / set of javascripts
- hard to do it modular
- all components need to be loaded on startup
- all components use memory even if not needed
- potential memory leaks and programming issues can cause trouble (user won't have to reload the page)

Thoughts on 2.)
+ more flexible than 1.)
+ allows a modular approach
+ components can be loaded on request
- requires some "manager component" to keep track of menuitems and their requirements for
- potential memory leaks and programming issues can cause trouble (user won't have to reload the page)
regions/panels.
- javascript that is loaded via XHR requires "additional attention" (scope, race conditions, eval etc.)


Thoughts on 3.)
+ simple to do
+ allows a very modular approach (similar to a generic php created page)
+ components can be loaded on request
+ less options for potential memory leaks and programming issues, because the iframe contents can be reloaded.
- iframe issues in Borderlayout. (Resizing fails)
- interaction between components require additional work.

Any comments are welcome

Wolfgang

hunkybill
6 Feb 2007, 6:29 AM
Hi,

As you have probably gathered, yui-ext is a a well written client-side library. Whether you choose to use C#, PHP or Ruby makes no difference to using BorderLayout in an application. Take classic PHP for example. Grab a framework like Prado or Symfony and make yourself a nice MVC or Event driven application in the framework. Interface your UI modules/component to yui, yui-ext, prototype, whatever. Use XHR to control the application. Bob's your uncle.

I use a modified Prado3 framework for the .NET like approach in PHP. Hook that up to Propel for PHP database ORM, and you have a neat way to build serious web applications. Problem is, Prado was POSTBACK driven, which is a kind of caveman web programming right? So, the Callback XHR code was built out and voila! The most recent Prado comes with 'Active' controls that are XHR based, making things a little easier now.

At this point, we've got custom PHP wrappers now around a lot of yui-ext widgets, encapsulating all the mappings between client/server components. Works well!

Animal
6 Feb 2007, 7:17 AM
I'm using option 2.

It's easy with JSP.

My <aspicio:Page> tag that wraps all pages we write looks at the HTTP header X-Requested-With (I've added this to my copy of YAHOO.util.Connect because they are adding it in the next release), and only wraps the whole HTML document, header, scripts css, and layout initialization code round the content if that header is not there.

If it IS there, it's an XHR to retrieve a fragment, so all that's needed is the content.

So clicking on a link in the document is intercepted by a document listener which loads the href (say "http://localhost:8080/aspicio/Lister.jsp?entity=Country") into the content area in the center Region.

But you can also open that URL directly (Open in new Tab) and it will still do the right thing.

Wolfgang
6 Feb 2007, 1:47 PM
Actually i also go for 2.).

However, sometimes i think that 3.) would be easier given that iframes would work flawless in BL.

Once you have loaded your page and need to load something else, do you keep the previous panel or do you remove them and create new one(s)?
Example:
Action1 requires 1 Panels (Center)
Action2 requires 2 Panels (Center, East)
Action3 requires 3 Panels (Center, East, West)
Action4 requires 1 Panel (Center)
...

So far i am thinking of a manager (collection) that holds the information which action requires which set of panels.

Then i see the option of either removing existing panels or keep/hide them until they are needed again.

I believe that keeping them (and hide/show them upon request) will give a better user experience, but i can imaging that, depending on the application size, the browser will still require an amount of memory. Also the browser cannot clean up anything, so it might be more stable when removing and recreating panels.

lstroud
6 Feb 2007, 7:05 PM
Animal,
So, you are actually doing a sitemesh style thing, but in javascript? Am I understanding that right?

LES

tony.summerville
6 Feb 2007, 8:17 PM
I am also interested in this discussion and would like to hear more ideas for creating a "single page" application using BorderLayout.

At my job, I've created a JavaScript framework for handling and serving up different "views" of the application that also manages history based on Really Simple History. Other than a simple JSP that creates the initial, basic layout, all of the views are built using JavaScript. We have a Java backend that uses Spring/Hibernate, with an "API layer" to build simple DTOs, which we then use to ship objects back and forth via DWR.

The problem is that even with things such as DomHelper, it can be quite tedious to build different views of the application while sprinkling in data from the server. Lately I've been thinking that it might be easier to build different views in some sort of JSP/Servlet layer and retrieve those views via XHR.

I'd like to implement something similar to what Animal is doing in his app, but also integrate history/state management that supports bookmarking and the back button. I've read that the YUI team is planning on releasing their version of a History manager in their upcoming release. Hopefully, we (or Jack) will be able to incorporate it into the BorderLayout. I believe that would make a truly great starting point for building a solid, single page application.

Does anyone else have any thoughts on this type of architecture? Animal, if you don't mind, can you provide any more information about your architecture?

Animal
7 Feb 2007, 12:44 AM
Well it's still evolving, but it's based around JSP tags that we write with Ajax and RIA capabilities (ie, YUI + Ext) in mind.

All JSPs have the <aspicio:Page> tag wrapped round them. This is the repository for all information about the page, and other tags can use it. It knows whether a full page is being requested, or a page fragment.

So <aspicio:Script>myFunc("foo")</aspicio:Script> just informs the Page - sends the source fragment to it. <aspicio:Script src="/js/foo.js"/> resolves the URL, and sends the URL to the Page.

Similar with <aspicio:Style>.

The Page tag handler buffers all output, and it's all written by the DoEndTag method when everything about the page is known.

If the Page tag knows that it's being asked for a full page, it writes the layout markup, and the initialization script around the content.

If it's being asked for a fragment, it just writes the content.

All pages within the application are loaded into the "center" Region which is cleared every time.

JSP tags are so powerful, I really think there needs to be a push to fully integrate them with Ajax+RIA and the back end beans that receive form data.

I mean Struts and all that XML splits out the metadata about a form all over the place in fragments of XML. And when you think about it, decent tags handlers for all inputs should either be given all the metadata in attributes, or infer it from attributes.

eg,

We use Hibernate, so our business objects are Hibernate Entities with a lot of metadata attached (using annotations), so our tag to allow a key field lookup looks like:




<!-- This property is a ManyToOne relation. The resulting server-side widget
allows popup of an Ext.grid.Grid in an Ext.BasicDialog for selection -->
<aspicio:keyfield entity="<%= myCountry %>" property="currency"/>


<aspicio:numericfield entity="<%= myCountry %>" property="areaLengthMin"/>


<aspicio:select entity="<%=entity%>" property="unitsDistance"/>


All types of input field are handled by various tags.

So everything about the form data is known at page generation time. What's needed is a scheme to unify all this metadata, and get it to the server in some form, so that submission can send back the form data and metadata encoded ni some way so that some receiving code can update the beans automagically.

We still haven't figurd this out yet, but it has to be possible. All form metadata must be known at page generation time, so all those XML files are ugly, prone to be mismatched and redundant.

tony.summerville
7 Feb 2007, 6:49 AM
Thanks, that's really interesting Animal.

Couple of other questions: Are you using any of the JSP frameworks like Struts? Are you sending data from the client to the backing beans or do you write other servlets to capture that information? Also, do you have a way to handle authorization on the pages?

Animal
7 Feb 2007, 8:47 AM
We're using JBoss's J2EE authorization. We might roll our own based on servlet Filters though.

We kicked Struts out. It's totally bound up with the old "web page" paradigm of submit->new page. And it's UGLY. All that XML just to write a servlet.

To capture the information, currently, the submissions contain form fields for just one entity. As you see from the above snippet, there's no name specified on input fields. The input field name is the property name, so we match them up automagically name->property. Conversions are easy because of all the metadata bound up with the entities (And we have a neat API which pulls all this together and provides easy access).

Also, data is formatted correctly by the client-side widgets, ie date fields are always submitted in ISO format YYYY-MM-DD.

In the future when the data in the form will encompass several entities - we'll have to go ahead with this idea of binding up the metadata with the form that's sent to the server, and then having the form submission send that back, and have a server-side component which understands that, and updates the necessary entities.

SteveEisner
7 Feb 2007, 6:43 PM
I actually think it's a mistake to make a "one-page" web app. Sticking your entire app into a single URL breaks a lot of things that users expect to work, including the back button and bookmarking. There are fixes to both but the hacks (typically, using #<data> to store state in the URL) aren't very clean. It's okay for an app like GMail - who needs a bookmark to a mail message? - but not so good for the typical web app.

That said, our ASP.NET app's architecture is basically the same as the one Animal described, with the ability to render a whole frame or just call back for some subset of the HTML. So we do make use of the callback mechanisms but don't bypass normal page navigation for it. ASP.NET takes care of the form scheme for us, though we've subverted the typical ASP.NET stuff (esp. Viewstate) to go for something a bit cleaner.

Since we want the URL to update, and therefore we have to reload the entire page, we focus on making the initial page response as fast as possible. This usually means that very little of the page is actually rendered at that time, most of it being retrieved with subsequent callbacks. You might be surprised, but combined with some page transition hacks (meta Page-Enter: opacity(100) ) the page-to-page reload of a BorderLayout is invisible for simple pages.

Unfortunately, some more complex layouts do visibly flash. I do wish there were some way to pass "hints" into the border layout code so that it could quickly snap to the same dimensions as the previous page layout. (I think there's cookie-based layout persistence, but haven't tried that yet.)

sean
7 Feb 2007, 7:15 PM
animal,

i like your approach. may i ask how/where you bind the fragment views to yui-ext widgets (your custom extensions or standard)? i'm assuming that a view is generated on the server with a root container element. during processing of the onupdate event of the updatemgr, this root element is provided to a panel during construction and added to the layout. this is the basic idea for handling fragments?

sean

Animal
7 Feb 2007, 11:22 PM
The fragments are basically the same as a page.

They set up their widgets in embedded <script> tags which are processed by the Element.update method.

The page BorderLayout, the page Regions, and the predefined ContentPanels in those Regions are available from the application namespace in case the loaded fragments have to interact with existing layout elements.

So I have aspicio.layout, aspicio.westRegion, aspicio.navPanel, aspicio.centerRegion, aspicio.contentPanel, aspicio.contentUpdateManager etc...

corey.gilmore
13 Feb 2007, 2:33 PM
I'm actually working on something like this, the menu calls javascript functions which use a jsonview to retrieve my information. I've got several actions/objects users will work with, and they can view lists of a type of object, and add/edit them. The first time you retrieve an object it returns a DomHelper Template along with data (~15kb), and the Template is cached; subsequent calls for objects just return the data (~1kb). Each object is in a form, and the form and all items have a unique appended (part of the Template). Upon post the unique id is stripped from the field names to make my PHP a bit easier. I wrote simple helper functions for select boxes to set the defaults.

Essentially it's a switch/case for DomHelper Templates, I overrode the prepareData function to check the type of information returned and choose the appropriate Template.

On the backend my output is buffered and HTML and JS are returned separately. I'm also using DomHelper Templates for the returned JS b/c of the quick and easy variable substitution.

The biggest issues are all of the usual single-page problems, and that's something I've got to tackle eventually.

I wrote this up really quickly, shout if you want more details or something doesn't make sense.

nproto
15 Feb 2007, 3:27 PM
Corey.gilmore your approach sound interesting. Can you please provide more details and/or sample code for your approach? Do you have a sample site where we could examine your sample code?

concept
12 Mar 2007, 12:56 AM
Corey.gilmore, we are investigating ways to totally restructuring an old web application hopefully using the ext framework. Your ideas look very interesting. Please provide some more details (code).

corey.gilmore
12 Mar 2007, 6:39 AM
Sorry, I've got a meeting in 45 so this will be a bit rushed.

It's nothing special, and will definitely change when I upgrade to Ext since I think I read that JSONView is being superceded by Ext.View.

First I have an HTML template, nothing simple just labels and spans inside of a form. The default value for any text boxes and text areas is {DBColName}. I have PHP methods to generate code for everything, and if necessary I'll return javascript separately. It's also important to make sure you have unique HTML IDs for everything. Here are some PHP snippets from the code generation and retrieval.


$unique = '{UniquePrefix}';
// ...
$DateInvolvement = GetInput('DateInvolvement' . $unique, 12, '{DateInvolvement}');
list($DateInvolvementCal, $DateInvolvementCalJS) = GetCalPopup('DateInvolvement' . $unique);


Callback functions are needed to set the default values for SELECT boxes; everything else can be set by leveraging the jsonview.



// Every tab we open has a number of associated objects.
// To keep them organized and prevent NS pollution they're stored
// in a prototyped object which we can destroy when a tab is closed
$tabAction = "YAHOO.myAppNamespace.myAppObj.actions.{TabName}";

// ...

// $obj is a generic name; it is an instance of a class containing whatever object
// that has been fetched
$Values = array(
'id' => $id,
'DBColName' => $obj->DBColName,
//...
);

$SelectObjs = array(
'DBCol1' => false,
'Involvement' => false,
'AssocIssues[]' => false,
'AllInits' => "$tabAction.optXferFunction('left', 'AllInits', 'copyLeftToRight');",
// ...
};


HTML is generated using more helper functions, it is just a bunch of collapsible sections wrapped around rows. A glorified table. Finally it's all tied together:



$html = <<<END
<div class="form-wrapper">
<h2>$header</h2>
<div class="btnBar"><span id="expandbtn$unique"></span><span id="collapsebtn$unique"></span></div>
<form name="letterForm$unique" id="letterForm$unique" method="get" action="?">
$a $UniqueID

$sctGeneral
$sctStaff
$sctNotes
$sctInitIssue
$sctAuth

<div class="btnBarBottom">$idobj<span id="savebtn$unique"></span><span id="cancelbtn$unique"></span></div>
</form>
</div>
END;


JavaScript is kept separately


$script = <<<END
$tabAction = new ActionObj('$unique');
$tabAction.addButton([
new YAHOO.ext.Button('savebtn$unique', {text: 'Save', handler:genericItemSave.createCallback('$unique', YAHOO.myAppNamespace.myAppObj.appItems.{$jsp})}),
new YAHOO.ext.Button('cancelbtn$unique', {text: 'Cancel', handler:YAHOO.myAppNamespace.myAppObj.removeTab}),
new YAHOO.ext.Button('collapsebtn$unique', { text: 'Collapse All', handler:$tabAction.collapseAll.createDelegate($tabAction)}),
new YAHOO.ext.Button('expandbtn$unique', {text: 'Expand All', handler:$tabAction.expandAll.createDelegate($tabAction)})
]);
$tabAction.addCollapser([$Sections], '$jsp', '$unique');
$tabAction.addNotes($NotesJS);

$tabAction.addOptXfer([
$InitBtnsJS,
$StaffBtnsJS,
$AuthorBtnsJS,
$CoSponsorsBtnsJS
]);

$tabAction.addCal([
$DateInvolvementCalJS
]);

END;


At the end we return an array of our HTML, JavaScript, the values of everything, our select objects (and their callbacks), and code specific to notes (which differs if we're adding an item or editing one).



return array($html, $script, $Values, $SelectObjs, $ManageNotes);



The front end to the code above follows. Realistically we shouldn't even waste cpu cycles generating the HTML if we don't need it, I just haven't moved the logic inside. HTML 'templates' are cached in a JS object inside the browser so they're only returned once per page load, and since this is a single screen (intranet) application, there aren't many page loads.



$resp = array();
$display = new ObjectInstance();
list($resp['html'], $resp['script'], $resp['data'], $resp['selects'], $resp['notes']) = $display->getEditForm($_GET['id']);
if( !isset($_GET['returnTemplate']) || $_GET['returnTemplate'] != 1) {
$resp['html'] = '';
}
echo safe_json_encode( $resp );


I have a generic function that opens a new tab and uses XHR to retrieve an item. If the item is successfully retrieved it's processed to set the appropriate values, and then I loop through the SELECT objects and set their values. Procesing order is important here since some items depend on others, so in our generation code some SELECT objects must always be inserted into our (PHP) array before others that depend on them. I wrote this function a long time ago and it is a real mess and in need of a complete rewrite. For instance just looking at it now I realized that I'm not compiling my templates.



function processItem(resp, config, dest) {
var evalErr = null;
var hasSelections = false;

var prefix = randomStr();
var r = YAHOO.ext.util.JSON.decode(resp.responseText);
r.data['UniquePrefix'] = prefix;
r.data['TabName'] = config.tabName;

if( typeof YAHOO.myAppNamespace.myAppObj.templateCache[config.method] == 'undefined' ) {
YAHOO.myAppNamespace.myAppObj.templateCache[config.method] = new YAHOO.ext.DomHelper.Template(r.html);
}
var scp = new YAHOO.ext.DomHelper.Template(r.script);
var script = scp.applyTemplate(r.data);

if( typeof r.notes != 'undefined' ) {
var notes = new YAHOO.ext.DomHelper.Template(r.notes);
r.data['ManageNotes'] = notes.applyTemplate(r.data);
}

YAHOO.myAppNamespace.myAppObj.templateCache[config.method].overwrite(dest, r.data);
try {
eval(script);
} catch(evalErr) {
alert('Error evaluating script: ' + evalErr);
console.log('evalErr: %s', script);
}
for( var s in r.selects ) {
var sel = $(s+prefix);
if( sel.multiple === false ) {
for (var i = 0; i < sel.options.length; i++) {
if( sel.options[i].value == r.data[s] ) {
sel.options[i].selected = true;
break;
}
}
} else {
// only try and set our select if we have options
for( hasSelections in r.data[s] ) {
hasSelections = true;
break;
}
if( hasSelections !== false ) {
for (var i = 0; i < sel.options.length; i++) {
if( typeof r.data[s][sel.options[i].value] != 'undefined' ) {
sel.options[i].selected = true;
}
}
if( r.selects[s] !== false ) {
try {
var scp = new YAHOO.ext.DomHelper.Template(r.selects[s]);
var script = scp.applyTemplate(r.data);
eval(script);
} catch(evalErr) {
alert('Error with select callback for ' + s + ': ' + evalErr);
console.error('r.selects[s]: %s', script);
}
}
}
}
}
if( config.successCallback !== null ) {
config.successCallback(config.successCallbackArgs);
}
}


So basically the HTML I generate has placeholder values that the DomHelper efficiently substitutes real values for. Every form field has some unique prefix that is stripped off by PHP after the form is submitted.

papasi
18 Mar 2007, 2:16 PM
Hi corey,

One drawback of your approach is that one cannot develop / debug single "page" or component separately.

Using animal's method, you can develop the page separately and it will load into the center content panel when used within the border layout.


Hi Animal,

I don’t know if you have come across this problem with your approach.
Let's say we have a grid view in the center region with a list of addresses.

When a user double click on an address, another edit page will be added to the center region so that the user can edit it using a form.

Using your method, we can develop this edit page with a form and several text fields, radio buttons and checkboxes,etc.

However, since we're using Ext, we'll probably apply some goodies to this page as well. For instance, replace the textfield with Combobox, etc.

Now, most of these Ext api expect you to pass the element id so it can search the component and replace its html and attach events.


<input id='username' name='username' type='text'/>

new Ext.form.ComboBox('username',...)


All is good when the user edit one address at a time and click ok or cancel to dismiss the edit page before going over another record.

But if the user edit 'address1', click the tab to go back to the address grid and edit 'address2', we'll open another tab which load the same html where the form elements have the same id as the previous tab.

Now all bets are off if you have elements with duplicated ID in the same page. getElementById() might only return the first object it finds and you’ll not be able to properly initialize the new tab.

The approach that I use to solve this problem is similar to how Jack implements the widgets, ie building the html using domHelper so one can assign unique id to each form element and later refer to them using the autogen id.

But then one cannot develop and debug using the old school html/js way (so that you can use tools like topstyle or dreamweaver) but to do everything in js.

So I wrote a script to extract all the html from a page and write it out using domhelper's config syntax along with the in page JavaScript.

This works ok for now but I'm still not satisfied as I still need to do things in multiple steps (test standalone page, generate the js, test embedded version, etc).

If anyone has more insight into this please share.

Cheers

harley.333
18 Mar 2007, 4:19 PM
I'm struggling with the same dilemma as Papasi. I work with ASP.Net. They solved this problem using server-generated id's, where they simply prepend the parent control's id to each childs'. (You end up with things like "parent_child" and "greatGrandParent_grandParent_parent_child".) Unfortunately, in the AJAX world, the server knows nothing about the parent (the parent is on the browser). For some reason, I feel like the server should be generating the majority of the HTML. The main reason I don't want to use DomHelper is that I'll be locking myself into a library. Another reason is the difficulties with automated testing (I'd like to use Selenium).

Web technologies move so fast, I've never had good luck by locking myself in. First I used ASP.Net "out-of-box," then I used Infragistics, then I wrote my own ASP.Net control library, and then I wrote a second ASP.Net control library. I had the most luck with my second library, but AJAX has killed it. You could say that my code is not extensible :), but honestly there have been fundamental differences in all my different avenues. Jack's library is the latest that I'm giving a go, and I'm hoping that it's the least painful.

How are other people dealing with these types of issues?

Animal
19 Mar 2007, 12:11 AM
The way I get round it is by using JSP tags. I don't write seperate markup and then javascript to "activate" the page. It's all sent by the JSP infrastracture.

So I might have a JSP tag:

<aspicio:combobox name="consignee" enityClass="Customer"/>

With the metadata evailable from Reflection, and JDK 5.0 annotations, the tag handler has all the information it needs to send down both HTML markup, and at attach a fragment of script to the page header.

Pages are wrapped with an <aspicio:page> tag which all tag handlers register with so that they can put their own javascript fragments into a single <script> element. All script handling is done by the Page tag handler.

The important thing that's been lacking so far is metadata. Data about the data items you are inputting in the page.

If that can be gathered either automagically from reflection and annotations, or from tag attributes, it can be encapsulated in the page so that the form has knowledge of what's required to input correct data.

It can also be used to send metadata back to the server so that a recipient servlet knows what persistent objects to update and how to update them.

The point is that the browser and server sides have to be designed together.

JollyRoger
15 May 2007, 12:20 PM
At this point, we've got custom PHP wrappers now around a lot of yui-ext widgets, encapsulating all the mappings between client/server components. Works well!

I'm trying to do the exact same thing and christocracy http://extjs.com/forum/showthread.php?t=1320 has given me a lead on how to proceed.

Is it possible for you to share your Prado wrappers around yui-ext?

JasonMichael
15 May 2007, 4:15 PM
Personally, I would try to stay away from the border layout stuff from my current applications, though I think they're cool. My client doesn't understand and appreciate how things are boxed and and slide in and out. he was very impressed with two grids, side by side with controls in the middle to send data from one grid to the other (until I get drag and drop functionality between them). I loves the themes that come with extjs, also, which don't translate over to the borderlayout designs. If you can use the grids well, and create your own buttons and handler functions to do various things (like take selected rows and update another grid/database), you've won half the battle.

for some of my personal stuff, and a more liberal, progressive client, I'd definitely use the border layouts, because it helps get so much information on the screen....

Without MVC, I have to admit, it would be a real pain doing my projects - I would probably have some cobbled together version of MVC thrown together, like I used to do in the old days before I really knew what MVC was... with MVC and PHP, you can just load what you need, as you need it - don't have to have every custom javascript routine loaded in the header for all your pages.. without MVC, I'd try to avoid this also... keeps your javascript organized.

jbowman
15 May 2007, 5:54 PM
The biggest problem I have with the borderlayout stuff is that it's too easy to get caught up with how easy it is to do stuff with it, that degradability breaks. It's great for places where you don't have to support non-javascript environments. But if that's a concern, I've learned to just stay away from it.

marceloverdijk
18 May 2007, 6:51 AM
@Animal,

You approach with using the JSP tags sounds very interesting.

Would you be able to share more of your code?

If I read correctly, you mentioned your Page takes care of updating all elements. Do you mean doing the layouting etc of all elements of the page.


Cheers,
Marcel

JasonMichael
21 May 2007, 2:44 PM
JBowman, that's exactly the lesson I've learned, as well, but I had fun learning it! :)