Changes for page Direct To Web (D2W and ERD2W)
Last modified by Pascal Robert on 2012/06/30 13:50
From version 111.1
edited by David Holt
on 2012/06/30 13:50
on 2012/06/30 13:50
Change comment:
There is no comment for this version
To version 112.1
edited by David Holt
on 2012/06/30 13:50
on 2012/06/30 13:50
Change comment:
There is no comment for this version
Summary
-
Page properties (3 modified, 0 added, 0 removed)
-
Attachments (0 modified, 5 added, 0 removed)
Details
- Page properties
-
- Parent
-
... ... @@ -1,0 +1,1 @@ 1 +DirectToWeb Architecture - Tags
-
... ... @@ -1,0 +1,1 @@ 1 +erd2w|d2w|rules|rulemodeler|directtoweb|d2wlooks - Content
-
... ... @@ -1,53 +1,53 @@ 1 1 = Direct To Web (D2W and ERD2W) = 2 2 3 3 * Direct to Web 4 -** [[Overview - What is Direct to Web?>>What is Direct to Web]] 5 -** [[Changing the Rules with Direct to Web]] 4 +** [[Overview - What is Direct to Web?>>doc:What is Direct to Web?]] 5 +** [[doc:Changing the Rules with Direct to Web]] 6 6 ** See David LeBer's D2W presentation available as part of the WOWODC West 2009 conference materials 7 -*** [[D2W Overview Screencast WOWODC 2009 Part 1>>http://www.wocommunity.org/podcasts/WOWODC09W-D2W-part1.mov]] 8 -*** [[D2W Overview Screencast WOWODC 2009 Part 2>>http://www.wocommunity.org/podcasts/WOWODC09W-D2W-part2.mov]] 7 +*** [[D2W Overview Screencast WOWODC 2009 Part 1>>url:http://www.wocommunity.org/podcasts/WOWODC09W-D2W-part1.mov||shape="rect"]] 8 +*** [[D2W Overview Screencast WOWODC 2009 Part 2>>url:http://www.wocommunity.org/podcasts/WOWODC09W-D2W-part2.mov||shape="rect"]] 9 9 ** See other WOWODC D2W presentations: 10 -*** [[Hello D2W Building your first D2W application>>http://www.wocommunity.org/podcasts/wowodc/2010/HelloD2W.mov]] 11 -*** [[D2W Custom Assignments and Component Design>>http://www.wocommunity.org/podcasts/wowodc/2010/ComponentAssignment.mov]] 12 -*** [[Troubleshooting D2W rules>>http://www.wocommunity.org/podcasts/wowodc/2010/TroubleShootingD2WRules.mov]] 13 -** [[David LeBer's blog posts on Direct to Web>>http://davidleber.net/?cat=30]] 14 -** [[Create a new Wonder Direct to Web Application]] 15 -** [[D2W Rule System>>The D2W Rule System]] 16 -** [[Configuring Rule Modeler]] 17 -** [[How to Write D2W Rules]] 18 -** [[How to debug a D2W application]] 19 -** [[Direct to Web>>Direct-To-Web]] Getting started (older docs than the overview and rule system docs) 10 +*** [[Hello D2W! Building your first D2W application>>url:http://www.wocommunity.org/podcasts/wowodc/2010/HelloD2W.mov||shape="rect"]] 11 +*** [[D2W Custom Assignments and Component Design>>url:http://www.wocommunity.org/podcasts/wowodc/2010/ComponentAssignment.mov||shape="rect"]] 12 +*** [[Troubleshooting D2W rules>>url:http://www.wocommunity.org/podcasts/wowodc/2010/TroubleShootingD2WRules.mov||shape="rect"]] 13 +** [[David LeBer's blog posts on Direct to Web>>url:http://davidleber.net/?cat=30||shape="rect"]] 14 +** [[doc:Create a new Wonder Direct to Web Application]] 15 +** [[D2W Rule System>>doc:The D2W Rule System]] 16 +** [[doc:Configuring Rule Modeler]] 17 +** [[doc:How to Write D2W Rules]] 18 +** [[doc:How to debug a D2W application]] 19 +** [[Direct to Web>>doc:Direct-To-Web]] Getting started (older docs than the overview and rule system docs) 20 20 21 21 * Direct to Web in Practice 22 -** [[D2W Rules Reference - Cookbook - FAQ>>D2W Rules Reference - Cookbook - FAQ]] 23 -** [[Configuring ERDEditPassword and ERDEditPasswordConfirmation]] 24 -** [[WO:RuleModeler.app>> ^RuleModeler.app.zip]]22 +** [[D2W Rules Reference - Cookbook - FAQ>>doc:D2W Rules Reference - Cookbook - FAQ]] 23 +** [[doc:Configuring ERDEditPassword and ERDEditPasswordConfirmation]] 24 +** [[WO:RuleModeler.app>>attach:RuleModeler.app.zip]] 25 25 26 26 * Useful Documentation 27 -** [[The first of the infamous WROX Red WebObjects book's chapters on D2W>>http://javaboutique.internet.com/resources/books/ProfWebObj/pwo4_1.html]] 28 -** [[Apple Documentation for D2W>>http://developer.apple.com/legacy/mac/library/documentation/WebObjects/Developing_With_D2W/Introduction/Introduction.html]] 29 -** Max Muller's [[^The Best Kept Secret of the Best Kept Secret Part 1.pdf]] (available in early project Wonder downloads now with original images included in the article)30 -** Max Muller's [[^The Best Kept Secret of the Best Kept Secret Part 2.pdf]] (available in early project Wonder downloads now with original images included in the article)27 +** [[The first of the infamous WROX Red WebObjects book's chapters on D2W>>url:http://javaboutique.internet.com/resources/books/ProfWebObj/pwo4_1.html||shape="rect"]] 28 +** [[Apple Documentation for D2W>>url:http://developer.apple.com/legacy/mac/library/documentation/WebObjects/Developing_With_D2W/Introduction/Introduction.html||shape="rect"]] 29 +** Max Muller's [[attach:The Best Kept Secret of the Best Kept Secret Part 1.pdf]] (available in early project Wonder downloads - now with original images included in the article) 30 +** Max Muller's [[attach:The Best Kept Secret of the Best Kept Secret Part 2.pdf]] (available in early project Wonder downloads - now with original images included in the article) 31 31 32 32 * Look Frameworks 33 -** [[ERModernLook>>ERModernLook]] 34 -** [[ERDivaLook>>ERDivaLook]] 35 -** [[ERDivaLiteLook>>ERDivaliteLook]] 33 +** [[ERModernLook>>doc:ERModernLook]] 34 +** [[ERDivaLook>>doc:ERDivaLook]] 35 +** [[ERDivaLiteLook>>doc:ERDivaliteLook]] 36 36 ** ERNeutralLook 37 37 38 38 * Advanced 39 -** [[ERD2W - part of the WONDER frameworks>>WO :Programming__WebObjects-Direct-To-ERD2W]]40 -** See Anjo Krank's presentation [[available as part of the WOWODC 2008 conference materials>>http://wocommunity.org/apps/WebObjects/WOCommunity.woa/wa/recordings]] 41 -** [[D2W Flow Control>>D2W Flow Control]] 42 -** [[Using ERD2WWizardCreationPage]] 43 -** [[Adding a normal component in ERD2W>>Adding a Normal WOComponent Page to an ERModernLook 39 +** [[ERD2W - part of the WONDER frameworks>>doc:WO.Programming__WebObjects-Direct-To-ERD2W]] 40 +** See Anjo Krank's presentation [[available as part of the WOWODC 2008 conference materials>>url:http://wocommunity.org/apps/WebObjects/WOCommunity.woa/wa/recordings||shape="rect"]] 41 +** [[D2W Flow Control>>doc:D2W Flow Control]] 42 +** [[doc:Using ERD2WWizardCreationPage]] 43 +** [[Adding a normal component in ERD2W>>doc:Adding a Normal WOComponent Page to an ERModernLook-based application]] 44 44 * Examples 45 -** [[SimpleBlog]] (ERModernLook example application) 46 -** [[Project Wonder Bugtracker Application completely built with D2W>>Getting Started with BugTracker]] 47 -** [[David Leber's ERExcelLook screencast>>http://davidleber.net/?p=369]] 48 -** [[Additional tips for ERExcelLook]] 49 -** [[ERDivaLook framework and example application (included in Wonder)>>ERDivaLook]] 45 +** [[doc:SimpleBlog]] (ERModernLook example application) 46 +** [[Project Wonder Bugtracker Application completely built with D2W>>doc:WEB.Getting Started with BugTracker]] 47 +** [[David Leber's ERExcelLook screencast>>url:http://davidleber.net/?p=369||shape="rect"]] 48 +** [[doc:Additional tips for ERExcelLook]] 49 +** [[ERDivaLook framework and example application (included in Wonder)>>doc:ERDivaLook]] 50 50 51 51 * REALLY out of date but someone might find them useful 52 -** [[O'Reilly Article on D2W and the WebAssistant>>http://onjava.com/pub/a/mac/2003/11/04/web_objects.html]] 53 -** [[O'Reilly Article: Next installment>>http://onlamp.com/pub/a/mac/2004/01/16/web_objects.html]] 52 +** [[O'Reilly Article on D2W and the WebAssistant>>url:http://onjava.com/pub/a/mac/2003/11/04/web_objects.html||shape="rect"]] 53 +** [[O'Reilly Article: Next installment>>url:http://onlamp.com/pub/a/mac/2004/01/16/web_objects.html||shape="rect"]]
- BestKeptSecret1.html
-
- Author
-
... ... @@ -1,0 +1,1 @@ 1 +XWiki.XWikiGuest - Size
-
... ... @@ -1,0 +1,1 @@ 1 +17.0 KB - Content
-
... ... @@ -1,0 +1,47 @@ 1 + <WEBOBJECT NAME=PageWrapper1><BR><FONT SIZE=4><B>Direct To Web: Part 1</B></FONT><BR><BR> Direct To Web. What are some of the first words that come to your mind? Toy. Demo Ware. Not for real programers. Yeah I used to think that too. Then about five months ago I started working for Patrice Gautier (one of the original authors of DirectToWeb) at NetStruxr and everything changed. I have found DirectToWeb to be the most dynamic and flexible piece of software I have ever worked with, truly taking WebObjects design to another level. DirectToWeb is the best kept secret of WebObjects, which as we all can attest to is Apple's best kept secret. <BR> So what is DirectToWeb? DirectToWeb originally started out as a way for non-technical developers to quickly produce an application and then mdify its look and feel at runtime through the use of a Java Applet named the Assistant. This probably sounds familiar. Instead what the team ended up developing was another layer onto of the WebObjects framework. A layer that abstracts and leverages WebObjects and EOF even further than you can possibly imagine. The only problem is that where as the rest of WebObjects has at least some documentation, DirectToWeb has extremely little. It is my hope that after three or four articles you will begin to glimpse the power beneath. In these articles we will start with the basics of DirectToWeb and work our way up. I thought I would first outline a few misconceptions that I had of DirectToWeb before I started working with it.<BR><BR><FONT SIZE=4><B> Common Misconceptions of DirectToWeb:</B></FONT> <BR><BR>1) "Using DirectToWeb locks your whole app into being a 'DirectToWeb Application'." Not true at all. DirectToWeb does not change the way your app functions in the least. DirectToWeb is actually just a framework for creating dynamic pages on the fly based on a set of rules. To take advantage of DirectToWeb all that you have to do is add the DirectToWeb framework to your project and add a DirectToWeb rule file to your resources directory, just like an eomodel file (more on this in a bit). <BR><BR>2) "DirectToWeb will never look like my application. I've seen the three looks they have and that just won't work for me." Not true at all. By default DirectToWeb uses one of the built in looks or templates. But as you will soon see it is rather easy create your own template with your own site's look and then tell DirectToWeb to use that instead of one of the bundled templates. This is not the same as freezing a page (the act of turning a dynamic DirectToWeb page into a static regular WOComponent). <BR><BR><FONT SIZE=4><B>Simple DirectToWeb List Pages</B></FONT><BR><BR> Before you read any further you will probably want to download the sample project DirectToWebListExample which contains all of the examples discussed below. The eomodel used for this article is the standard movies eomodel, so if you have WebObjects installed on your machine you should be able to build the application and have it just work. Using list pages is a really simple way to get to know the DirectToWeb world. A list page does exactly what its name implies, it lists objects. In its simplest form, it takes an array of objects and an entity name and does the rest for you. This is shown in the WOComponent MyDirectToWebListPage1. You can see this page by clicking on the link "To List Page 1," from the main page of the example application Here all we have done is drop in a DirectToWebList component off of the DirectToWeb palette in WOBuilder and bound a list of Movie objects to the list binding and the string "Movie" to the entityName binding. This will spit out a plain vanilla DirectToWeb list page inside our standard WOComponent which will look like this:<BR><P ALIGN=CENTER><WEBOBJECT NAME=Image1></WEBOBJECT></P> Notice that by default we have the ability to delete objects by clicking the trashcan on the right and to edit these objects by clicking on the edit button on the left. Also note that all of the attributes that are marked as class properties on the entity Movie are shown and that none of the relationships that are marked as class properties are shown. Now let's modify this behaviour to make the objects read only, get rid of the banner at the top and limit the display keys to title, dateReleased, rated and revenue. To do this we need to create a pageConfiguration or a set of rules that tells DirectToWeb how to render the page. The application that is used to edit and create rules is called RuleEditor and is located after the EOModeler icon in the drop down list under the WebObjects tab. RuleEditor edits and creates d2wmodel files which store all of the rules for a given application or framework. DirectToWeb also offers another means of editing rules called The Live Assistant. The Live Assistant is an applet that can be configured to startup when teh application is launched. The Live Assistant is a useful tool when getting started, but it hids alot of the power and flexibility that DirectToWeb offers. For this reason all the examples shown are done with the RuleEditor. At the moment DirectToWeb will only pick up model files that are named user.d2wmodel and d2w.d2wmodel, where typically the former resides in the resources directory of application projects and the later in the resources directory of framework projects, although nothing is stopping you from adding both a d2w and a user d2wmodel to a project.<BR><BR> 2 + To change the behaviour, we bind "ListMovies2" to the D2WList's binding pageConfiguration. The final product which is displayed in the WOComponent MyD2WListPage2. It should look something like this:<BR> 3 + <P ALIGN=CENTER><WEBOBJECT NAME=Image2></WEBOBJECT></P><BR> 4 + Notice that 5 + now the banner and trashcan are gone and 6 + the editing icon has been replaced by an 7 + inspection icon (don't worry soon we will 8 + show how to use your own icons instead ;). 9 + To add the page configuration 10 + "ListMovies2" to the rules we 11 + first needed to add a user.d2wmodel to our 12 + project. To add a d2wmodel to our example 13 + project we open RuleEditor, selected new 14 + model and then chose the "Save 15 + As" option. We saved the file as 16 + user.d2wmodel in the project folder, then 17 + we chose "Add Files To Project" 18 + from inside Project Builder to add 19 + user.d2wmodel to the project's Resources.<BR><BR> 20 + Now for a quick'n dirty overview of the DirectToWeb rule system. DirectToWeb rules are very straightforward. A DirectToWeb rule is composed of four parts: the left hand side (lhs) or the condition in which the rule fires, the rigt hand side key (rhs), the right hand side value, and the priority. For example, the set of rules for the "ListMovies2" configuration is composed of five rules. If you open user.d2wmodel and select "Filter Rules" and type "ListMovies2" you should see six rules that look like this:<BR> 21 + <P ALIGN=CENTER><WEBOBJECT NAME=Image3></WEBOBJECT></P><BR> 22 + For now ignore the rule that has the Rhs key 'look'. These five rules tell 23 + DirectToWeb everything it needs to know about the pageConfiguration "ListMovies2". Look at the top 24 + most rule:<BR><BR><code>pageConfiguration = 'ListMovies2' => task = "list"</code><BR><BR> 25 + This says for this configuration the task is 'list'. By default, another rule will fire that will look like this:<BR><BR><code>task = 'list' => pageName = 'DefaultD2WListTemplateName'</code><BR><BR> 26 + In this way DirectToWeb will know which template to use for the list, in other words all the D2WList component really is is a WOSwitchComponent where the componentName is bound to the value pageName from the rules. Now click on the rule:<BR><BR><code>pageConfiguration = 'ListMovies2' => entity = "Movie"</code><BR><BR>It should look like this:<BR><P ALIGN=CENTER><WEBOBJECT NAME=Image4></WEBOBJECT></P><BR> 27 + Note that the type of class assignment isn't of the type Assignment, instead it says Custom with the string "com.apple.yellow.directtoweb.EntityAssignment" typed in the white box below. This will be covered in greater detail in another article, but for now just know that the only two special assignments are the EntityAssigment and the BooleanAssignment (click on the rule with the rhs readOnly to see the boolean assignment in action). In this way we can write rules that take advantage of the fact that the key entity represents an entity object. For instance, we could write the following rule if we wanted all objects whose entity is Movie to be readOnly by default:<BR><BR><code>entity.name = 'Movie' => readOnly = "true"</code><code>(com.apple.yellow.directtoweb.BooleanAssignment)</code><BR><BR> 28 + Now look at this rule:<BR><BR><code>*true* => look = "NeutralLook"</code><BR><BR> 29 + This rule does not have a lhs component, so we say that this is a default rule, or in this case the default 'look'. This is the only rule that you absolutely must have in your rule file. Without this rule, DirectToWeb will throw an exception when you attempt to render a dynamic page, even if you are not using any of the built-in DirectToWeb templates.<BR><BR> 30 + Now what if we don't want to have to have WOComponents that we have to maintain, but 31 + instead we would like them to be created on the fly. This is really simple. From the Main page are two 32 + links "To List Page 3" and "To List Page 4". These produce almost the exact same pages 33 + as pages 1 and 2 except they are dynamicly constructed from the rule system. By clicking on "Link To 34 + Page 4" you should see a page that looks like this:<BR> 35 + <P ALIGN=CENTER><WEBOBJECT NAME=Image5></WEBOBJECT></P><BR> 36 + The only difference between the above page and "To List Page 2" is the return button at the bottom (not shown above) and hardcoded text "This is an example of ..." that was hardcoded into the WOComponent MyD2WListPage2. Also note that we still have the dynamic page being wrapped in our PageWrapper navigation component. The reason for this is that when DirectToWeb renders a top level dynamic page it will look for a component named PageWrapper in your project. If it finds one then it uses that to wrap the dynamic pages in when they are rendered, thus giving you the ability to wrap top level dynamic pages in your own look and feel. Note that only top level dynamic pages have the ability to pick up pageWrappers, embedded components like the D2WList do not display the pageWrapper. Want to use a different name than PageWrapper, say for situations where you don't want navigation shown or if you already have a navigation component named something else? Easy - add a rule with the lhs specified (pageConfiguration = 'APageConfig' or *true* to set the default) and the rhs equal to pageWrapperName = "MyPageWrapperComponentName." The method to return the above page is located in Main.java and looks like:<BR><BR><code> public WOComponent myDirectToWebListPage4() {<BR> ListPageInterface lpi = (ListPageInterface)DirectToWeb.factory().pageForConfigurationNamed("ListMovies2", session());<BR> lpi.setNextPage(context().page());<BR> lpi.setDataSource(((Session)session()).movieArrayDataSource());<BR> return (WOComponent)lpi; <BR> }</code><BR><BR> 37 + The method movieArrayDataSource off of session simply returns an EOArrayDataSource that contains all of the Movie enterprise objects.<BR> 38 + <BR> 39 + <FONT SIZE=4><B>Custom List Templates</B></FONT><BR><BR> 40 + Now for the finale ... adding your own look and feel to a DirectToWeb List. 41 + Let's look at the result before explaining how to get there. By clicking on the link "To List 42 + Page 5" you should see a page that looks like this:<BR> 43 + <P ALIGN=CENTER><WEBOBJECT NAME=Image6></WEBOBJECT></P><BR> 44 + What you are probably thinking now is, "This looks just like a DirectToWebList page!?!?! Where is our custom look?" Well you are right, this is mainly because I completely lack graphic design ability. However, the really neat part is that the entire look of this page is generated from the template named DemoListTemplate. Here you have access to all of the images, all of the colors, fonts, everything. In fact it is fairly trival to integrate a stylesheet into this list component. You can add your own navigation bar, or buttons, whatever you like. Now the cool part. If you are using this style of approach and you have say ten pages displaying different lists of objects, and you need to change the color of text or backgound, you can make that change in one place and have the effect uniform across all places where that list template is used.<BR><BR> 45 + Let's take a quick look at how we integrated DemoListTemplate into DirectToWeb. First, we subclassed com.apple.yellow.directtoweb.DirectToWebListPage with our own component called DemoListTemplate. Then, we added a new pageConfiguration "ListMovies3" and duplicated all of the rules from before (Note: not the most efficient number of rules if you look at the operators DirectToWeb has to offer, but I wanted to keep the rules simple), as well as adding two rules to the pageConfiguration:<BR><BR><code>pageConfiguration = 'ListMovies3' => pageName = "DemoListTemplate"</code><BR><BR>and<BR><BR><code>pageConfiguration = 'ListMovies3' => isEntityInspectable = "false" (com.apple.yellow.directtoweb.BooleanAssignment)</code><BR><BR> 46 + We also added a default value to the rules:<BR><BR><code>*true* => isEntityInspectable = "true" (com.apple.yellow.directtoweb.BooleanAssignment)</code><BR><BR> We did this so that we will always get a Boolean value back from the rules when we ask for the key: "isEntityInspectable." Note, the rule system will be covered in far greater detail in another article. If you look at the code in DemoListTemplate.java we have only added two methods:<BR><BR><code> public boolean showReturn() { return nextPage()!=null; } <BR> <BR> public boolean isEntityInspectable() {<BR> Integer isEntityInspectable=(Integer)d2wContext().valueForKey("isEntityInspectable");<BR> return isEntityReadOnly() && (isEntityInspectable!=null && isEntityInspectable.intValue()!=0);<BR> }</code><BR><BR> 47 + The first method is bound to a conditional that displays the "Return" button. By doing this we now have the ability to not show the Return button if we do not set the nextPage on the ListPageInterface. The second is a hook into the DirectToWeb rules that asks for the value of "isEntityInspectable." If you delete the rule:<BR><BR><code>pageConfiguration = 'ListMovies3' => isEntityInspectable = "false" (com.apple.yellow.directtoweb.BooleanAssignment)</code><BR><BR>then the inspection button will once again show up in the list.<BR><BR> Understand that we are still dealing with the very, very basics here and that we have just begun to scratch the surface of the flexibility offered by DirectToWeb. If you are not impressed with this simple example, please be patient. I want to lay a solid foundation that more complex articles can be built on later. As this is the first in what I hope will be many articles, I would appreciate any feedback you might have about layout and/or content of this article or suggestions for future articles. If you have any general questions about DirectToWeb Omni's webobjects-dev mailing list is a great resource.<BR><font FACE="Arial, Helvetica, sans-serif"><BR></font><FONT SIZE=2>Many thanks to Patrice Gautier who is an incredible teacher and if it wasn't for him I would still be approaching WebObjects design the "old" way. Also thanks to Patrice, Angela Muller and Travis Cripps for their valuable feedback.</FONT><font FACE="Arial, Helvetica, sans-serif"><BR></font></WEBOBJECT>
- BestKeptSecret2.html
-
- Author
-
... ... @@ -1,0 +1,1 @@ 1 +XWiki.XWikiGuest - Size
-
... ... @@ -1,0 +1,1 @@ 1 +30.8 KB - Content
-
... ... @@ -1,0 +1,231 @@ 1 +<WEBOBJECT NAME=PageWrapper1><b>The Best Kept Secret of The Best Kept Secret: Part 2</b><br> 2 +<P>DirectToWeb. Any new words come to mind? Maybe, really-flexible -framework-for-creating-dynamic-WOComponents? No, probably not yet. So for the observant reader, what is the really cool part of DirectToWeb that we looked at in Part 1? Easy. DirectToWeb allows for the reuse of the most important components of a project .... the ones the graphic design artists spent hours and hours designing. In a normal WebObjects application every time you want to display the contents of an EOEnterpriseObject or a list of objects, you normally have to develop a new WOComponent , get the look and feel right, get all of the properties showing up correctly, set date and number formatters, etc. Time consuming. If now you want a limited view, no problem just hack in a WOConditional here and there, and presto limited view. Now what about the customer service agent who needs to also look at this EnterpriseObject? No problem, just add a few more WOConditonals ... you get the idea. Now you have this Frankenstein component that so heavily depends on user state and user type that the only way you can possibly reuse this component is by cutting and pasting bits and pieces into other components. Sound familiar? </P> 3 +<P>So, how does DirectToWeb help you solve this problem? Simple, for the style of pages that you find yourself using over and over again (inspect pages, tab inspect pages, wizard creation pages, list pages, edit pages, ect) DirectToWeb provides an extremely robust framework for reusing the part that requires the most work, the look of these templates. In the first article we looked at how the DirectToWeb List interface and embedded D2WList component could be used with a custom template. A custom template being a generic WOComponent that can be customized to a certain look and feel while at the same time retaining its extremely dynamic nature. In the list example we just displayed a list of property keys that just spit out what was in the database. I'll be the first to admit that really without being able to interact with the list of objects, a list for the sake of a list is not very exciting, nor very useful. In this article we will explore how to get our own components into the list and then into a DirectToWeb inspect page, as well as using a custom template for inspecting. If you haven't read the first article on DirectToWeb located here, I would highly suggest you take five minutes and at least skim over it as most of what will be presented in this article directly builds upon concepts introduced in the first article.</P> 4 +<b>Common DirectToWeb Questions</b><br> 5 +<p>After the 6 +first article went up on Stepwise I received a number of questions and 7 +comments. Thank you very much for the wonderful feedback and please keep 8 +it coming! Before moving into the heart of this article I thought I would 9 +spend a bit of time going over a few of the more common questions I received.</p><p><b>How does DirectToWeb render a page?</b> The way DirectToWeb renders a page is actually fairly straight forward. The heart of DirectToWeb is the rule engine. It is the rule engine's responsibility to answer questions. So, let's imagine that we have a page configuration named 'foo.' This page configuration has a task (list), an Entity (Bar) and an array of displayPropertyKeys((barName)). The object that embodies the rule engine in DirectToWeb is the D2WContext. The way the D2WContext answers questions is through key-value coding. So imagine we want to know what the displayPropertyKeys are in the page configuration 'foo.' This bit of code could find that out for you:</p><code>D2WContext myContext = new 10 +D2WContext();</code><br> 11 +<code>myContext.takeValueForKey("foo", 12 +"pageConfiguration");</code><br> 13 +<code>NSArray displayPropertyKeys = (NSArray)myContext.valueForKey("displayPropertyKeys");</code><br> 14 +<P>So how did the D2WContext know to return the correct array given just a pageConfiguration? Easy, the D2WContext resolved a few rules. Rule resovling is fairly straight forward, in the above example a key 'pageConfiguration' was set to a value 'foo' on the D2WContext, next a question was asked, essentially "What are your displayPropertyKeys?." To answer this question the D2WContext first asks itself "What are all the possible rules I have that will answer the above question?" Given that rule set the D2WContext then sorts the rules by priority (that's the number on the right hand side in Rule Editor), once the rules have been sorted by priority the D2WContext returns the rule with the highest priority (it also caches this result so that if asked again it won't have to perform the sort all over again). Given a tie, meaning two rules hit the criteria and both have the same priority the D2WContext will choose the most specific rule, ie the rule with the most cluases. These are the basics, don't worry if it doesn't make perfect sense right now, with a little playing around I the above should become more clear. If you would like a more detailed exaplanation of how DirectToWeb resolves rules I would refer you to the DirectToWeb article "Developing WebObjects Applications With DirectToWeb" in the WOInfoCenter. Returning to the above code snippet, if you look in the DemoListTemplate WOComponent from the first article (also used in this article), you will find that the WORepetion that displays the array of displayPropertyKeys for a given list page configuration has the following bindings:</P> 15 +<code>list = d2wContext.displayPropertyKeys</code><br> 16 +<code>item = d2wContext.propertyKey</code><br> 17 +<p>In this way the repetition will be enumerating through the displayKeys and 18 +setting the propertyKey on the context to the current displayPropertyKey. 19 + We will cover more of the D2WContext object in another article. 20 + Also note that you should never have to programmatically create a 21 +D2WContext, DirectToWeb handles all that for you.</P> 22 +<b>Performance. What about performance of DirectToWeb?</b> In WebObjects 4.5 DirectToWeb has been tuned and optimized so that dynamic pages only take about 25% longer to render than a regular WebObjects component of the same complexity. So the real question you should be thinking is gee, how did they make DirectToWeb so fast? Simple, the D2WContext caches rules as they fire. So the first time DirectToWeb renders a page, that page will render slower than any other time that page is rendered. The rule caching mechanism and performance tuning will be covered in a later article. For now just know that once the rule cache has "warmed up", to use the EOF term, page rendering is not noticeably different compared to a static WebObjects page.<p><b>How are DirectToWeb and 23 +DirectToJavaClient related?</b> Good question. DirectToJavaClient is built upon the same underlying rule engine as DirectToWeb, so if you are interested in learning more about how DirectToJavaClient's rule engine works, here is a pretty good place. Where DirectToJavaClient differs significantly from DirectToWeb is that instead of spitting out html it spits out xml, which is used to dynamically build the interface to an applet. DirectToJavaClient is an incredible piece of technology which really pushes the limits , but that's a story for another time ;)</P> 24 +<p><b>Custom Components. How can I get custom logic into a dynamic page?</b> Good question. First we need to differentiate bewteen custom property level components and custom page level components, becuase in DirectToWeb we hvae both. I custom page level component is referred to as a template and is used to add a custom look to an entire page. This was demonstrated in the first article and is extended in this article. The second type of custom component is a property level component. These are the components that are used to display a particular propertyKey or attribute. Property level custom components are the topic of this article, so keep reading to learn about all these wacky and wild components. If you have any other questions on DirectToWeb, please send them on, and I will try to answer them in a future article. Before reading further, it would be a really good idea to download the example project. It contains all of the code, rules and links to pages referenced below.</p> 25 +<b>Custom Property Level Components in DirectToWeb</b><br> 26 +<p>In the context of DirectToWeb there are two 27 +different kinds of property level custom components. A custom property level component being a 28 +component that is used instead of the normal property level display component, i.e. instead of 29 +a D2WDisplayString or a D2WDisplayDate. Recall that in the list example 30 +from the first article all of the attributes being displayed were either a 31 +string or a date. In this case DirectToWeb was using the default 32 +components to display this information, i.e. D2WDisplayString and 33 +D2WDisplayDate. Now imagine that we don't want the name attribute of the 34 +Movie to show up as just a String, but instead we want the name to be a 35 +hyperlink that takes us to a page that displays all of the information about 36 +that movie. How would we do that? With a custom component of 37 +course.</p> 38 +<p>The first kind of custom component is a direct subclass of D2WComponent. These components are only available within a DirectToWeb page, for example the D2WDisplayString, a subclass of D2WComponent, cannot be used outside the context of a DirectToWeb page. These types of custom components are generally used to create new ways of editing attributes. These will be covered in a future article about EnterpriseObject editing and creation. The second kind of custom component is a plain vanilla subclass of WOComponent. When used with DirectToWeb this component will be handed an object and a key through its bindings. With these two bits of information the component can do whatever it likes. I usually like to make these components non-synchronizing and stateless, seeing as they can be used many times on a given page. If you don't make your custom component non-synchronizing then you will need to have a variable of the type EOEnterpriseObject named 'object' and a String variable named 'key' in your component. Let's actually look at the code from a custom component named LinkToViewMovie1.java taken from the D2WInspectExample project:</p> 39 +<code>public class LinkToViewMovie1 extends 40 +WOComponent {</code><br> 41 +<BR><code> public boolean isStateless() { return true; }</code><br> 42 +<br> 43 +<code> public Object object() { return 44 +valueForBinding("object"); }</code><br> 45 +<code> public String key() { return (String)valueForBinding("key"); 46 +}</code><br> 47 +<code> </code><br> 48 +<code> public Movie movie() {</code><br> 49 +<code> return (Movie)(object() 50 +instanceof Movie ? object() : object().valueForKeyPath(key()));</code><br> 51 +<code> }</code><br> 52 +<br> 53 +<code> public WOComponent view() {</code><br> 54 +<code> MyD2WInspect1 view = 55 +(MyD2WInspect1)pageWithName("MyD2WInspect1");</code><br> 56 +<code> view.setMovie(movie());</code><br> 57 +<code> view.setBackPage(context().page());</code><br> 58 +<code> return view;</code><br> 59 +<code> }</code><br> 60 +<code>}</code><br> 61 +<p>Looking at the .wod of this component we can see that the only function of this component is to display the movie's name 62 +in a hyperlink with the action method bound to the view() method . So how do we get this component into the 63 +DirectToWeb list? With a rule of course. Well actually with two rules, they are:</p> 64 +<code>(pageConfiguration = 'ListMovies1') and 65 +(propertyKey = 'title') => componentName = "D2WCustomComponent"</code><br> 66 +<br> 67 +<code>(pageConfiguration = 'ListMovies1') and (propertyKey = 'title') => 68 +customComponentName = "LinkToViewMovie1"</code><br> 69 +<p>Tiny bit of review on the DirectToWeb rule system: All of the rules for a project are found in the DirectToWeb model file, named user.d2wmodel in the example project. To edit/create and find rules we use the RuleEditor application. The 'Find' command is your friend. Want to see all the rules associated with the propertyKey title? Type 'title' in the find box and presto, every rule that has 'title' in it appears in the list. Note that this will find all rules that have title in either the lhs (left hand side, the part of the rule before the => above) or the rhs (right hand side, the part after the =>).</p> 70 +<p>The two rules above tell DirectToWeb two things, first when displaying the propertyKey 'title' I want to use a custom component as oppossed to the built-in D2WDisplayString component and second, the custom component's name is 'LinkToViewMovie1.' The generated list page will now look like this:</p> 71 +<WEBOBJECT NAME=Image1></WEBOBJECT><p>As you can see in the above list the movie's title is encapsulated with a hyperlink. Also note that the name at the top of the column displays 'Movie Title,' instead of Title which is the name of the attribute in the EOModel By default DirectToWeb beautifies the attribute's name, so 'title' would become 'Title' and 'movieRating' would become 'Movie Rating.' Often however (as anyone who has ever had to work with a content team can attest to) the name in the EOModel will not be the name that you would like the users of the application to see. In this case the display name can be specified with a rule, like so:</p> 72 +<code>(entity.name = 'Movies') and 73 +(propertyKey = 'title') => displayNameForProperty = "Movie Title"</code><br> 74 +<p>Notice that this rule does not include a 75 +pageConfiguration in the lhs of the rule. The scope of this rule is not 76 +tied to a single pageConfiguration, i.e. a single dynamic page. Instead 77 +this rule will apply anytime you are inspecting, listing, editing or searching 78 +on an object whose entity name is 'Movie.' This is a very, very powerful concept and the main reason why you want to have a rule system instead of a plain lookup mechanism or code. For example imagine you wanted to build two build a suite of applications and have the Movie entity's title appear as 'Movie Title' everywhere it is used, except when listing Movie entities or when you are in the customer service application. Building up this logic with a few rules is pretty simple, but trying to implement the above case in code would be non-trival and probabely require constant fiddling everytime you wanted a different behaviour. The DirectToWeb way is the exact opposite, provide a default general rule and then override that rule in specific pageConfigurations or applications. Gernally when writing rules for a large project you will have two rule files, the rule file at the business logic layer and a rule file at the application layer. A good example of a rule that belongs in the business logic layer or framework is the above rule. Once that rule is in place everytime the title attribute of a Movie object is displayed it will be displayed as 'Movie Title' instead of 'Title'. A good example of a rule that belongs in the application layer is any rule that deals with a particular pageConfiguration. Remember a pageConfiguration is the equivalent to a static WOComponent's name, so in most cases you wouldn't place specific WOComponents in the business logic layer, likewise rules that correspond to a particular pageConfiguration mostly belong in the application rule file. To see the above rule in action click on one of the movie title's hyperlinks from the above list. The component 79 +returned from the view method above should look like this:</p> 80 +<WEBOBJECT NAME=Image2></WEBOBJECT><p>Notice that the attribute 'title' is still 81 +getting displayed as 'Movie Title' from the display rule above. This can 82 +be extremely handy, especially if you have a fickle content team ("We 83 +realize you just changed the entire site to be Movie this and Movie that, but 84 +well we decided that we really like the word Film better ..."). From 85 +the above screen shot you can get a feel for the plain vanilla D2WInspect 86 +component which is embedded in the component '<code>MyD2WInspect1</code>'. The D2WInspect component is very similar 87 +to the D2WList component, i.e. you give it an object, an entity and optionally 88 +a pageConfiguration and it produces a D2WInspect page inside your WOComponent. 89 + Notice how easy it is to move between DirectToWeb dynamic components (the 90 +page with the list in it) and regular WOComponents ('MyD2WInspect1' is a 91 +regular WOComponent) by using custom components.</p> 92 +<p>Let's jazz things up a bit, actually let's jazz things up a lot. In the list page let's add another column that lists the number of reviews of that movie with a hyperlink to view the list of reviews. To show that the display keys of the list don't actually have to correspond to attribute keys, the key added to the display keys will be 'dummy,' likewise let's add a corresponding displayNameForProperty so that 'Dummy' won't show up at the top of the column. Let's also format the movie revenue number as '$ #,##0.00.' In the movie inspect page let's display the relationship to the plot summary just like a normal attribute, and let's display a table of the directors (a flattened relationship) showing each director's first and last name. Plus let's add a link to write a movie review in the inspect page. To make things even more interesting let's use a custom inspect template instead of the built-in template (just like we are currently using for the list pages). Plus let's do all this with dynamic pages (no D2WInspect or D2WList components embedded in normal WOComponents), three custom components and 31 rules in less than nine minutes. Ya think it's possible? I do. The final product will look like this:</p> 93 +<p>Movie List Page:</p> 94 +<WEBOBJECT NAME=Image12></WEBOBJECT> 95 +<p>Review List Page:</p> 96 +<WEBOBJECT NAME=Image3></WEBOBJECT> 97 +<p>Movie Inspect Page:</p> 98 +<WEBOBJECT NAME=Image11></WEBOBJECT> 99 +<p>Write A Review Page:</p> 100 +<WEBOBJECT NAME=Image10></WEBOBJECT><p>Note: when you try out the example project for 101 +the first time on your machine you will notice that the number of reviews shows 102 +up as "No Reviews" for all the movies. Little did I realize 103 +when designing the example that the OpenBase Lite Movies database did not 104 +include any reviews, thus the addition of the "Write A Review 105 +Page".</p> 106 +<p>So where to begin? Let's start with the list page. It looks very similar to the first list page, except it has the added column 'Number of Reviews.' This is another custom component called 'LinkToViewReviews.' All it does is test if the number of reviews is greater than 0, if so it displays a hyperlink to another dynamic list page displaying the movie's reviews, if not it displays 'No Reviews.' As indicated above, the key in the displayPropertyKeys is 'dummy' and the corresponding componentName, customComponentName, and displayNameForProperty have all been set for the entity. They look like this:</p> 107 +<code>(entity.name = 'Movie') and 108 +(propertyKey = 'dummy') => componentName = "D2WCustomComponent"</code><br> 109 +<code>(entity.name = 'Movie') and (propertyKey = 'dummy') => 110 +customComponentName = "LinkToViewReviews"</code><br> 111 +<code>(entity.name = 'Movie') and (propertyKey = 'dummy') => 112 +displayNameForProperty = "Number of Reviews"</code><br> 113 +<p>Note that these three rules correspond to the following screen shot in Rule 114 +Editor:</p> 115 +<WEBOBJECT NAME=Image4></WEBOBJECT><p>Notice also that in the inspect page we have 116 +included the key dummy in the displayPropertyKeys of the inspect page and sure 117 +enough all three of the above rules fire. Adding the formatter to the 118 +revenue attribute is really straight forward. Simply add the rule:</p> 119 +<code>(propertyKey = 'revenue') and 120 +(pageConfiguration = 'ListMovies2') => formatter = "$ #,##0.00"</code><br> 121 +<p>This formatter rule functions just like the 122 +WebObjects binding numberFormat or dateFormat on a WOString. So if you 123 +wanted all numbers being displayed to by default be formatted with the format 124 +"$ #,##0.00" you could add the rule:</p> 125 +<code>(attribute.valueClassName = 'NSNumber') 126 +or (attribute.valueClassName = 'NSDecimalNumber') => formatter = "$ 127 +#,##0.00"</code><br> 128 +<p>Note: This and many other high level hooks can be very handy for localizing 129 +applications, but that's a story for another time ;)</p> 130 +<p>Notice here that we are using the key 'attribute' in the above rules. In the rule system, just as entity refers to the EOEntity of the object, attribute refers to the EOAttribute of the propertyKey if it is an attribute of the entity*. As you could probably infer, the rule system also has a key 'relationship', which corresponds to the EORelationship if the current propertyKey is a relationship of the entity. Notice also that the valueClassName in the above rules refers to the Obj-C class names, not the corresponding Java equivalent. Even though DirectToWeb is written entirely in Java, the rule system still uses the Obj-C class names and most importantly nil instead of null.</p> 131 +<p>In the inspect page, everything is pretty 132 +straight forward. If you open the rules and perform a find for 133 +'InspectMovie1'. The following set of rules should come up:</p> 134 +<WEBOBJECT NAME=Image5></WEBOBJECT><p>Note that we have specified the pageName in the 135 +rules as 'DemoInspectTemplate.' This is the hook we use to specify our 136 +own custom look and feel. The 'DemoInspectTemplate' is pretty much a 137 +plain vanilla inspect page, but note that you have complete control over the 138 +look and the feel. Want style sheet elements, easy as pie. Want 139 +graphic images instead of displayNames, sure no problem you have the template. Note 140 +that we have added one ability to the DemoInspectTemplate which is to disable 141 +the 'edit' button. To do this we add the default rule:</p> 142 +<code>*true* => isEntityEditable = "true" (BooleanAssignment)</code><br> 143 +<p>To turn it off in the inspect configuration used, 'InspectMovie1' we simply add 144 +the rule:</p> 145 +<code>(pageConfiguration = 'InspectMovie1') => isEntityEditable = 146 +"false" (BooleanAssignment)</code><br> 147 +<p>Notice that the above two rules use 148 +BooleanAssignment instead of the regular rule assignment. A screenshot of 149 +what this type of assignment looks like in Rule Editor:</p> 150 +<WEBOBJECT NAME=Image6></WEBOBJECT><p>So what is the difference between the default assignment and using a custom assignment class? By default the value part of the key-value pair of the rule is just a plist that gets turned into a String, NSArray or NSDictionary. For example, the displayPropertyKeys rule in Rule Editor looks like this:</p> 151 +<WEBOBJECT NAME=Image7></WEBOBJECT><p>Whereas a rule for a displayNameForProperty will 152 +look like this:</p> 153 +<WEBOBJECT NAME=Image8></WEBOBJECT><p>Finally, if you wanted to have a rule that returns a dictionary it would look something like this:</p> 154 +<WEBOBJECT NAME=Image9></WEBOBJECT><p>So if the rule engine is asked for any of the rhs keys from above (displayPropertyKeys, displayNameForProperty and dictionaryDemo), either a String , NSArray or NSDictionary object will be returned. So how do the BooleanAssignment and EntityAssignment differ? Simple, by providing a custom Assignment class DirectToWeb is able to return other objects, like for example Boolean and EOEntity objects. Note however that due to the Java Bridge Boolean objects get turned into Java Integer objects. In the next article we will look at adding our own custom assignment classes which can be a very powerful extension of DirectToWeb. (This is actually really simple, if you can't wait until the next article check the WOInfoCenter Assignment class for further information).</p> 155 +<p>Back to the custom inspect page and its table of directors. How was this done? With four rules of course! First note that in the displayPropertyKeys "directors" appears. This is the name of the flattened relationship to the Talent entity. The component that DirectToWeb uses by default to display to-many relationships is called D2WDisplayToManyFault. This component appears the same way it did in the plain vanilla inspect page (a collapsible component with the directors first name displayed wrapped in a hyperlink that takes the user to another inspect page about the selected director). To create the look used on the above inspect page we simply tell DirectToWeb to use another component. We could have written our own custom component to display the list of directors first and last name in a table, however this would not be a very reusable approach. Instead what we want is a generic way to display a to-many relationship in a table. Well it turns out that DirectToWeb has a component named D2WDisplayToManyTable that does exactly what we need. To use this component to display the directors we need to specify four rules. They are:</p> 156 +<code>(pageConfiguration = 'InspectMovie1') and (propertyKey = 'directors') => 157 +componentName = "D2WDisplayToManyTable"<br> 158 +<br> 159 +(pageConfiguration = 'InspectMovie1') and (propertyKey = 'directors') => 160 +disabled = "true" (BooleanAssignment)<br> 161 +<br> 162 +(pageConfiguration = 'InspectMovie1') and (propertyKey = 'directors') => 163 +allowCollapsing = "false" (BooleanAssignment)<br> 164 +<br> 165 +(pageConfiguration = 'InspectMovie1') and (propertyKey = 'directors') => 166 +keyWhenRelationship = "fullName"</code><br> 167 +<p>The first rule tells DirectToWeb to use the component D2WDisplayToManyTable to display the relationship directors. The second rule 'disabled' tells DirectToWeb to not place a hyperlink to the director inspect page around each director. By setting the 'allowCollapsing' to false this tells DirectToWeb to not use a collapsible component around the D2WDisplayToManyTable component. The last rule, keyWhenRelationship, tells DirectToWeb what to display for each director. Note that fullName is not in the model, instead it is a method off of Talent that simply returns firstName() + " " + lastName().</p> 168 +<p>As a preview of things to come in the movie 169 +inspect page the key writeAReview was added to the displayPropertyKeys. 170 + This key is used to get the custom component 'LinkToWriteAReview' into 171 +the inspect page. This component displays the text "Write A 172 +Review" in a hyperlink that when clicked creates a Review object and then 173 +takes the user to a regular DirectToWeb edit page. Let's take a quick 174 +peak at the code that creates the review object and returns the DirectToWeb 175 +edit page. Here it is:</p> 176 +<code> 177 + public WOComponent writeReview() {<br> 178 + // This will be a peer of the 179 +session's default editingContext.<br> 180 + EOEditingContext peer = new 181 +EOEditingContext();<br> 182 + // The below methods would be better suited in the business logic. But for simplicity it it here.<br> 183 + EOClassDescription cd = 184 +EOClassDescription.classDescriptionForEntityName("Review");<br> 185 + EOEnterpriseObject review = 186 +cd.createInstanceWithEditingContext(peer,null);<br> 187 + peer.insertObject(review);<br> 188 + Movie localMovie = 189 +(Movie)EOUtilities.localInstanceOfObject(peer, movie());<br> 190 + if (localMovie == null)<br> 191 + throw 192 +new RuntimeException("Attempting to create a review for a null 193 +movie.");<br> 194 + localMovie.addObjectToBothSidesOfRelationshipWithKey(review, 195 +"reviews");<br> 196 + EditPageInterface epi = 197 +(EditPageInterface)D2W.factory().pageForConfigurationNamed("CreateReview", 198 +session());<br> 199 + epi.setObject(review);<br> 200 + epi.setNextPage(context().page());<br> 201 + // Need to make sure that the 202 +ec survives, ie after the EOUtilities.localInstanceOfObject method the ec could 203 +be released.<br> 204 + peer.hasChanges();<br> 205 + return (WOComponent)epi;<br> 206 + }</code><br> 207 +<p>As you can see, it's all pretty straight forward. A peer context is created so that if the user hits the back button then s/he is not left with a dirty editing context. The page configuration "CreateReview" has the following four rules:</p> 208 +<code> 209 +(pageConfiguration = 'CreateReview') => task = "edit"<br> 210 +<br> 211 +(pageConfiguration = 'CreateReview') => entity = "Review" 212 +(EntityAssignment)<br> 213 +<br> 214 +(pageConfiguration = 'CreateReview') => displayPropertyKeys = 215 +"(movie.title, reviewer, review)<br> 216 +<br> 217 +(pageConfiguration = 'CreateReview') and (propertyKey = 'movie.title') => 218 +componentName = "D2WDisplayString"<br></code> 219 +<p>Again, all pretty straight forward stuff. Note that by using a D2WDisplayString for the propertyKey 'movie.title' we have effectively made this attribute read-only.</p> 220 +<b>Recap</b><br> 221 +<P>So what the heck did we just cover? In a 222 +nutshell we looked at how to use custom components in either a DirectToWeb list 223 +or inspect page. We also explored more of how the rule system works, in particular 224 +we looked at formatters, display names, EOAttributes and Boolean assignments. 225 + We also looked at how to use a custom inspect template with DirectToWeb. 226 + In a preview of things to come we showed how to create objects and edit 227 +them with the standard DirectToWeb edit page. In the next article we will 228 +be looking more at object creation, editing and validation as well as custom 229 +assignment classes. Slightly more impressed with DirectToWeb yet? 230 + If not at least give me the benefit of the doubt and read the next few 231 +articles because believe me the best is yet to come!</P></WEBOBJECT>
- RuleModeler.app.zip
-
- Author
-
... ... @@ -1,0 +1,1 @@ 1 +XWiki.XWikiGuest - Size
-
... ... @@ -1,0 +1,1 @@ 1 +485.9 KB - Content
- The Best Kept Secret of the Best Kept Secret Part 1.pdf
-
- Author
-
... ... @@ -1,0 +1,1 @@ 1 +XWiki.XWikiGuest - Size
-
... ... @@ -1,0 +1,1 @@ 1 +166.8 KB - Content
- The Best Kept Secret of the Best Kept Secret Part 2.pdf
-
- Author
-
... ... @@ -1,0 +1,1 @@ 1 +XWiki.XWikiGuest - Size
-
... ... @@ -1,0 +1,1 @@ 1 +315.2 KB - Content