10159
Comment:
|
23548
|
Deletions are marked like this. | Additions are marked like this. |
Line 5: | Line 5: |
There are some bugs that must be fixed before layouts changes can start: * 403641 [[https://launchpad.net/bugs/403641 | base-layout must support a Set menu]] <<BR>> There may be three modification links that are global to an object. * 403651 [[https://launchpad.net/bugs/403651 | base-layout must support an Involvement menu]] <<BR>> There is a static menu to encourage cross application use We want to minimise the number of developers writing HTML and CSS until |
<<TableOfContents()>> We want to minimize the number of developers writing HTML and CSS until |
Line 16: | Line 13: |
{{{ locationless: A page without tabs, search, or side portlets |
* locationless: A page without tabs, search, or side portlets |
Line 20: | Line 16: |
that uses this layout have been converted. searchless: A page without global search or side portlets |
that use this layout have been converted. * searchless: A page without global search or side portlets |
Line 24: | Line 20: |
The Launchpad front page, uses this layout. Other pages may want to | The Launchpad front page uses this layout. Other pages may want to |
Line 27: | Line 23: |
main_only: A page without side portlets | * main_only: A page without side portlets |
Line 31: | Line 27: |
main_side: A page with everything This page is for artefacts that have actions that create notifications, have subscribers, or have notifications that can be placed in the side }}} |
* main_side: A page with everything This page is for artifacts that have actions that create notifications, have subscribers, or have notifications that can be placed in the side. '''Other portlets should not be placed in the sidebar.''' |
Line 50: | Line 44: |
See the [[http://people.canonical.com/~beuno/conversions.html | Conversion report]] for a summary of our progress. This page is updated five minutes past the hour based on landings to `launchpad/devel`. Some teams are also keeping track of conversions before landing: [[VersionThreeDotO/Bugs/TemplateConversion|Bugs]], [[VersionThreeDotO/Soyuz/TemplateConversion|Soyuz]], [[VersionThreeDotO/BlueprintsConversion|Blueprints]]. |
|
Line 52: | Line 49: |
{{{ * Choose the layout |
=== Choose the layout === {{{ |
Line 63: | Line 61: |
? All other cases (simple artefacts or modification pages) | ? All other cases (simple artifacts or modification pages) |
Line 65: | Line 63: |
* Mechanical changes |
}}} === Mechanical changes === {{{ |
Line 74: | Line 75: |
* Update the heading? * If this is the index of an object move <h1> to the heading slot. The page layout is probable main_side. The rule could also be stated as the context.title above the bread crumbs should not also be repeated in maincontent. |
* Update the heading (see below for rules) |
Line 91: | Line 88: |
* Remove floated classes and styles...those are hacks to make column in the onecolumn layout. | |
Line 96: | Line 94: |
* Design changes |
* (Barry: I do not know what that means) * Use <ul class="horizontal"> to present a list of links that follow content. See product-index.pt external-links and timeline * Use <dl> to create the details/info about objects. See product-index.pt Project info * The involvement links are for official use. Do not make a Report a bug Involvement link if the pillar does not use bugs. }}} === Heading rules === Here are the rules for headings and titles in a page. '''NOTE:''' our current code does not yet conform to these rules, so you may see some extra or incorrect data in them. This is currently being worked on as part of [[https://bugs.edge.launchpad.net/launchpad-foundations/+bug/417089|bug 417089]]. * For overview (a.k.a. index) pages, such as http://launchpad.net/bzr where the context is the root context * There is an H1 tag above the application tags, both of which are to the right of the logo and above the horizontal line. This H1 gets filled with the description of the object. This H1 should be in normal black text, not app-colored. * This H1 may have an edit button for changing the description if it makes sense (e.g. it does on a bug, but it doesn't on a person's translation index page since the H1 will be the person's name). * There should be '''no''' app-colored editable H1 below the tabs and horizontal line. * There should be no H2 or breadcrumbs below the horizontal line. * The page title is the H1 + " in Launchpad" (for now) * For subpages such as https://launchpad.net/~barry/+karma or https://code.edge.launchpad.net/~sinzui/launchpad/official-portlets/+merge/10946/+review?claim=launchpad&review_type * There should be an H2 above the app tabs which contains a description of the context of the page, e.g. "Launchpad itself" * There should be '''no''' H2 below the horizontal line * There is an H1 below the horizontal line which describes the page or form and it should be app-colored * There should be breadcrumbs below this H1 * The page title should be the reversed breadcrumbs for Google-fu. * The path components in the reversed breadcrumbs in the page title should be separated by colons * Other pages (e.g. top-level collections or their subpages) such as https://launchpad.dev/people/+newteam * Launchpad.net should occupy the H2 position above the app tabs * There should be no H2 below the horizontal line * There should be an H1 below the horizontal line which describes the form * There should be breadcrumbs below the H1 Here are the specific coding recommendations for your templates. If you follow these guidelines, your page will automatically adhere to the above rules. * You will almost never need to add an H1 to your heading slot. In fact, you should never need to fill your heading slot. If you do, let [[mailto:barry@canonical.com|me]] know! * If your page is the index page for your content object, have your view class implement the marker interface `lp.app.interfaces.headings.IMajorHeadingView`. This will put the root context title in an H1 above the app tabs. Do ''not'' implement a `label` attribute in your view since that will add an app-colored H1 tag below the tabs, which you don't want. * If your page is not the index page for your root context object, do nothing special. This will put the root context title in an H2 above the app tabs. * If your page is an index page and you want to allow for editing of the context title, have your view class implement `lp.app.interfaces.IEditContextTitle` instead of `IMajorHeadingView`. You will need to implement the `title_edit_widget` method, probably from the appropriate lazr-js widget, which will return the entire HTML (including the H1 wrapping) when called. * Implement a `label` property in your view class for the H1 below the app tabs in `LaunchpadView` subclasses. This same property will be used as the form header in `LaunchpadFormView` subclasses. * If for some reason you do not want to use the default reversed-breadcrumbs in your <title>, implement the `page_title` attribute in your view, and set the view class attribute `override_title_breadcrumbs` to `True`. Generally, you will ''not'' want to do this, so that the default reverse-breadcrumbs are used. You should take the time to convert any `pagetitle.py` entries for your view to using a `page_title` attribute. === Design changes === {{{ |
Line 110: | Line 149: |
* Some links cannot be moved into the page because the act on the | * Some links cannot be moved into the page because they act on the |
Line 122: | Line 161: |
The css requires that the div contain all three classes to describe a portlet, but 'first' is not reusable in this way. The portlet certainly knows it is class="yui-u portlet", but only the calling template knows if the portlet is 'first'. One solution is to ignore first and order the portlets last to first in the page. Another solution is to add a CSS rule that makes 'first' work as a parent tag. |
The YUI CSS requires that the div that contains 'first' also contain the 'yui-u' class. The portlet template can define its div and set the 'portlet' class. The calling template will set the yui classes. Something like this: <div class="first yui-u" tal:content="structure context/@@+portlet-top-contributors" /> |
Line 133: | Line 170: |
* Use the <div class="yui-u portlet"> structure to create the correct | * Note the 'first' problem above, if you separate the portlet to another file, keep the YUI classes in the calling template: <div class="yui-u first"> * Use the <div class="portlet"> structure to create the correct |
Line 142: | Line 181: |
to normalise spacing | to normalize spacing |
Line 148: | Line 187: |
* Ensure the CSS works because the classes are right, the css that works | * Ensure the CSS works because the classes are right, the CSS that works |
Line 150: | Line 189: |
* Implementation changes |
}}} === Implementation changes === {{{ |
Line 154: | Line 195: |
app (ApplicationMenu), context (ContextMenu), or view (NavigationMenu) | app (`ApplicationMenu`), context (`ContextMenu`), or view (`NavigationMenu`) |
Line 158: | Line 200: |
* pagetitles.py is deprecated. Do not add to it. Do no one use CONTEXTS/fmt:pagetitle |
|
Line 164: | Line 208: |
* Test changes. |
}}} === Test changes === {{{ |
Line 184: | Line 231: |
{{{ | |
Line 187: | Line 234: |
YUI is doing layout, Launchpad is decorating the layout, so many divs will have a yui and a launchpad class. YUI (layout) yui-g: A grid column yui-u: A unit of content Probably always paired with portlet Probably should have an id for testing and scripts first: The first item in the yui-g (fixes issues in css 2.x browsers) (See the note above about problems with this class) Launchpad (decoration) portlet: A chunk on content distinguished by margins, padding, and borders Probably always paired with yui-u top-portlet: The first portlet in the page, probably a description. |
YUI is doing layout, Launchpad is decorating the layout, The YUI layout rules are not portable between templates, do not use the in the same element that uses a launchpad class. Do not use YUI in the portlet templates because the instruction may break another page that also uses the portlet YUI (layout):: :: yui-g: A row :: yui-u: A column :: first: The first column You can image the yui-g as a table row, and the yui-u as a table column. This creates the holes as you said above, and this behaves just link designing pages with tables. This is called YUI-Grid after all. It assumes all content is essential. It's uses of the 'first' class makes portlet reusablity difficult without careful template creation. Launchpad (decoration):: :: portlet: A chunk on content distinguished by margins, padding, and borders Probably always paired with yui-u :: top-portlet: The first portlet in the page, probably a description. /!\ Mixing YUI classes on elements that use launchpad classes may lead to broken layouts when templates are refactored. |
Line 207: | Line 261: |
}}} == Example layout == {{{ A common layout will be an intro on top of two columns of content. You can see examples of the four layouts in lib/lp/app/browser/tests/testfiles/ |
=== Example layouts === A common layout will be an intro on top of two columns of content below it in the main slot . This layout is the easiest to do. The portlets will not align so '''it may look ugly'''. {{{ |
Line 217: | Line 272: |
metal:use-macro="view/macro:page/main_only"> | metal:use-macro="view/macro:page/main_side"> |
Line 229: | Line 284: |
<div class="yui-u first portlet"> content </div> <div class="yui-u portlet"> more content </div> <div class="yui-u portlet"> and more content |
<div class="first yui-u"> # Call the essential portlets. # Call the optional portlets. </div> <div class="yui-u"> # Call the essential portlets. # Call the optional portlets. |
Line 242: | Line 296: |
<div id="actions" class="yui-u first portlet"> | <div id="actions" class="first portlet"> |
Line 245: | Line 299: |
<div id="attachments" class="yui-u portlet"> | <div id="attachments" class="portlet"> |
Line 248: | Line 302: |
<div id=""subscribers class="yui-u portlet"> and more content </div> |
<div class="portlet" tal:content="structure context/@@+latest-announcements" /> |
Line 253: | Line 306: |
</html> }}} |
}}} The more complex approach is to create a row (yui-g) for each pair of essential content so that some of the page aligns and looks nice. In the last row (yui-g), call all the optional portlets. The pushed the only part of the layout below the fold. You can also interleave single column portlets between the yui-g rows to improve the layout. {{{ <tal:main metal:fill-slot="main"> <div class="top-portlet"> Description </div> <div class="yui-g"> <div class="first yui-u"> # Call the essential portlet. </div> <div class="yui-u"> # Call the essential portlet. </div> </div> <div class="yui-g"> <div class="first yui-u"> # Call the essential portlet. </div> <div class="yui-u"> # Call the essential portlet. </div> </div> <div> # an essential or optional portlet that is one column </div> <div class="yui-g"> <div class="first yui-u"> # Call the optional portlets. </div> <div class="yui-u"> # Call the optional portlets. </div> </div> </tal:main> }}} == Modification page/view example diff == Form pages are very simple at the minimum, you can change the page macro and be done. You should consider adding a cancel link if it is missing and updating a page title. This example shows the changes need to fix one page. They took about 15 minutes. Note that the final page template is identical to lp/app/templates/generic-edit.pt In cases like this consider updating the ZCML to use the generic template and delete the old one. {{{ === modified file 'lib/canonical/launchpad/pagetitles.py' --- lib/canonical/launchpad/pagetitles.py 2009-07-29 20:56:34 +0000 +++ lib/canonical/launchpad/pagetitles.py 2009-08-10 20:55:01 +0000 @@ -1225,13 +1217,6 @@ -def productseries_edit(context, view): - """Return the page title for changing a product series details.""" - return 'Change %s %s details' % ( - context.product.displayname, context.name) === modified file 'lib/lp/registry/browser/productseries.py' --- lib/lp/registry/browser/productseries.py 2009-07-21 22:27:22 +0000 +++ lib/lp/registry/browser/productseries.py 2009-08-10 20:54:37 +0000 class ProductSeriesEditView(LaunchpadEditFormView): """A View to edit the attributes of a series.""" schema = IProductSeries @@ -479,6 +489,17 @@ custom_widget('summary', TextAreaWidget, height=7, width=62) custom_widget('releasefileglob', StrippedTextWidget, displayWidth=40) + @property + def label(self): + """The form label.""" + return 'Edit %s series %s' % ( + self.context.product.displayname, self.context.name) + + @property + def page_title(self): + """The page title.""" + return self.label + def validate(self, data): """See `LaunchpadFormView`.""" branch = data.get('branch') @@ -497,6 +518,11 @@ """See `LaunchpadFormView`.""" return canonical_url(self.context) + @property + def cancel_url(self): + """See `LaunchpadFormView`.""" + return canonical_url(self.context) + === modified file 'lib/lp/registry/browser/tests/productseries-views.txt' --- lib/lp/registry/browser/tests/productseries-views.txt 2009-07-21 16:30:28 +0000 +++ lib/lp/registry/browser/tests/productseries-views.txt 2009-08-10 20:01:35 +0000 @@ -77,7 +77,85 @@ +Edit ProductSeries +------------------ + +The productseries +edit view provides a label and page_title for the page. + + >>> view = create_initialized_view(series, '+edit') + >>> print view.label + Edit App series simple + + >>> print view.page_title + Edit App series simple + +The view provides a cancel_url and a next_url. + + >>> print view.cancel_url + http://launchpad.dev/app/simple + + >>> print view.next_url + http://launchpad.dev/app/simple === modified file 'lib/lp/registry/templates/productseries-edit.pt' --- lib/lp/registry/templates/productseries-edit.pt 2009-07-17 17:59:07 +0000 +++ lib/lp/registry/templates/productseries-edit.pt 2009-08-10 16:16:33 +0000 @@ -1,23 +3,12 @@ + <html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:tal="http://xml.zope.org/namespaces/tal" + xmlns:metal="http://xml.zope.org/namespaces/metal" + xmlns:i18n="http://xml.zope.org/namespaces/i18n" + metal:use-macro="view/macro:page/main_only" + i18n:domain="launchpad"> + <body> + <div metal:fill-slot="main"> + <div metal:use-macro="context/@@launchpad_form/form" /> + </div> + </body> + </html> }}} == Adding a NavigationMenu == Launchpad uses `NavigationMenus` to associate outbound links to a view. The menu and view are bound together by a marker interface. Links that are used in more than one menu should be defined in a mixin class to ensure that there is a single canonical definition of them. The menu can be rendered as an action menu in the side portlets or as a list of related pages in the main content. === Working with more than one menu === There is a lot or duplicate definitions of links. Some even have different text, summaries, or icons. There should be only one definition on a link. If a link is used in more than one menu, move it to a mixin. Do not try to subclass an `ApplicationMenu` to a `NavigationMenu` to reuse a link. Bad things will happen. Use a mixin. === A browser module example === {{{#!python from zope.interface import implements, Interface from canonical.launchpad.webapp.menu import NavigationMenu from canonical.launchpad.webapp.publisher import LaunchpadView from lp.myapp.interfaces.myobject import IObject class ObjectLinksMixin: """A mixin class that provides links used by more than one menu.""" # Classes to create menus on more than one view class IObjectEditMenu(Interface): """A marker interface for the edit navigation menu.""" class ObjectEditMenu(NavigationMenu, ObjectLinksMixin): """A menu for different aspects of editing a object.""" # Bind the menu to the view. usedfor = IObjectEditMenu facet = 'overview' title = 'Change object' links = ['edit', 'images', 'reassign', 'review', 'administer'] class ObjectEditView(LaunchpadView): """A view to set branding.""" # Bind the menu to the view. implements(IObjectEditMenu) class ObjectImagesView(LaunchpadView): """A view to set images.""" # Bind the menu to the view. implements(IObjectEditMenu) # Class to create a menu for the object, irrespective of the view. class ObjectActionMenu(NavigationMenu, ObjectLinksMixin): """An action menu to modify the object.""" usedfor = IObject facet = 'overview' title = 'Action object' links = ['review', 'administer'] # Menus are normally registered using the menu ZCML directive. They can # be registered in Python. To register them: provideAdapter( ObjectEditMenu, [IObjectEditMenu], INavigationMenu, name="overview") provideAdapter( ObjectActionMenu, [IObject], INavigationMenu, name="overview") }}} === A ZCML example to register a menu === {{{ <browser:menus classes=" ObjectEditMenu ObjectActionMenu" module="lp.myapp.browser.myobject"/> }}} === Rendering a NavigationMenu === To render an action menu in the side portlets use this TALES view: <tal:menu replace="structure view/@@+global-actions" /> To render a related pages section at the bottom main content: <tal:menu replace="structure view/@@+related-pages" /> === Two menus on the same page === There maybe a few cases where a page may need two menus. One for actions that change an object that are not possible to move into the main content, the other for pages related to the content of the page. `NavigationMenus` can be created for content objects and views. Do not do this without first talking to Martin about the reason. The examples that have been given so far have not been legitimate. |
Line 259: | Line 581: |
{{{ The MenuAPI needs performance fixing. Once we start rendering, link states should never be re-calculated. There should be no cost to using a menu in three templates to render a page. }}} |
[X] The MenuAPI now caches menus per request. They are faster and there is no danger of a link changing during rendering. [ ] We need a unittest that can verify that every menu makes links to an adaptable view. |
UI 3.0 implementation
A primer about the 3.0 UI and how we go about doing it.
Contents
We want to minimize the number of developers writing HTML and CSS until there are more HTML examples and CSS rules in place.
Layouts
- locationless: A page without tabs, search, or side portlets
- This is used by non-app pages like errors. Most (maybe all) pages that use this layout have been converted.
- searchless: A page without global search or side portlets
- This is for pages that implement their own search in the main area. The Launchpad front page uses this layout. Other pages may want to switch to this layout.
- main_only: A page without side portlets
- This will be the most common layout because modification pages and some listing pages do not have items that qualify for side portlets.
- main_side: A page with everything
This page is for artifacts that have actions that create notifications, have subscribers, or have notifications that can be placed in the side. Other portlets should not be placed in the sidebar.
Estimate conversion table
Old layout New layout 5 default(2.0) main_side 30 applicationhome main_only 10 pillarindex main_side 1 search searchless 256 context/@@main_template/master main_?? 158 onecolumn main_??
See the Conversion report for a summary of our progress. This page is updated five minutes past the hour based on landings to launchpad/devel. Some teams are also keeping track of conversions before landing: Bugs, Soyuz, Blueprints.
Converting a template to a layout
Choose the layout
? The page does not belong to an application, can be shown anywhere ! use locationless. ? Does the page implement search in the main area, and does it contradict with global search ! use searchless ? Does the page have actions that create objects or notifications, does it have a subscriber's list, does it have notification (events) to show recent activity? ! use main_side ? All other cases (simple artifacts or modification pages) ! use main_only
Mechanical changes
* Switch the layout: * Update the macro - xml:lang="en" - lang="en" - dir="ltr" - metal:use-macro="context/@@main_template/master" + metal:use-macro="view/macro:page/main_only" * Update the heading (see below for rules) * Remove 0.0 and 1.0-isms * Delete all portlet slots * Delete the help * You may want to extract the help to a separate file and link to it * YUI-ify * If there is more than on block on the page: * First block is class="top-portlet" * All following blocks are class="portlet" * Launchpadify * Remove floated classes and styles...those are hacks to make column in the onecolumn layout. * Convert crafted links into fmt:link or fmt:link-icon * We may want to extend the formatter to handle alternate link text. * We probably need to add links the menus. * Create valid css-ids using fmt:css-id * It takes an optional prefix fmt:css-id/prefix- * (Barry: I do not know what that means) * Use <ul class="horizontal"> to present a list of links that follow content. See product-index.pt external-links and timeline * Use <dl> to create the details/info about objects. See product-index.pt Project info * The involvement links are for official use. Do not make a Report a bug Involvement link if the pillar does not use bugs.
Heading rules
Here are the rules for headings and titles in a page. NOTE: our current code does not yet conform to these rules, so you may see some extra or incorrect data in them. This is currently being worked on as part of bug 417089.
For overview (a.k.a. index) pages, such as http://launchpad.net/bzr where the context is the root context
- There is an H1 tag above the application tags, both of which are to the right of the logo and above the horizontal line. This H1 gets filled with the description of the object. This H1 should be in normal black text, not app-colored.
- This H1 may have an edit button for changing the description if it makes sense (e.g. it does on a bug, but it doesn't on a person's translation index page since the H1 will be the person's name).
There should be no app-colored editable H1 below the tabs and horizontal line.
- There should be no H2 or breadcrumbs below the horizontal line.
- The page title is the H1 + " in Launchpad" (for now)
For subpages such as https://launchpad.net/~barry/+karma or https://code.edge.launchpad.net/~sinzui/launchpad/official-portlets/+merge/10946/+review?claim=launchpad&review_type
- There should be an H2 above the app tabs which contains a description of the context of the page, e.g. "Launchpad itself"
There should be no H2 below the horizontal line
- There is an H1 below the horizontal line which describes the page or form and it should be app-colored
- There should be breadcrumbs below this H1
- The page title should be the reversed breadcrumbs for Google-fu.
- The path components in the reversed breadcrumbs in the page title should be separated by colons
Other pages (e.g. top-level collections or their subpages) such as https://launchpad.dev/people/+newteam
- Launchpad.net should occupy the H2 position above the app tabs
- There should be no H2 below the horizontal line
- There should be an H1 below the horizontal line which describes the form
- There should be breadcrumbs below the H1
Here are the specific coding recommendations for your templates. If you follow these guidelines, your page will automatically adhere to the above rules.
You will almost never need to add an H1 to your heading slot. In fact, you should never need to fill your heading slot. If you do, let me know!
If your page is the index page for your content object, have your view class implement the marker interface lp.app.interfaces.headings.IMajorHeadingView. This will put the root context title in an H1 above the app tabs. Do not implement a label attribute in your view since that will add an app-colored H1 tag below the tabs, which you don't want.
- If your page is not the index page for your root context object, do nothing special. This will put the root context title in an H2 above the app tabs.
If your page is an index page and you want to allow for editing of the context title, have your view class implement lp.app.interfaces.IEditContextTitle instead of IMajorHeadingView. You will need to implement the title_edit_widget method, probably from the appropriate lazr-js widget, which will return the entire HTML (including the H1 wrapping) when called.
Implement a label property in your view class for the H1 below the app tabs in LaunchpadView subclasses. This same property will be used as the form header in LaunchpadFormView subclasses.
If for some reason you do not want to use the default reversed-breadcrumbs in your <title>, implement the page_title attribute in your view, and set the view class attribute override_title_breadcrumbs to True. Generally, you will not want to do this, so that the default reverse-breadcrumbs are used. You should take the time to convert any pagetitle.py entries for your view to using a page_title attribute.
Design changes
* Menus and links * Dismantle the context, app, and navigation menus. * Move the links inline. Are the links really needed in the page? * Many pages will require a "related <nouns>" portlet at the bottom of the page that has a list of links to other pages. Edit pages might link to other edit pages for example. * Create a navigation menu if the links are shared between views. Use the related pages adapter to render the view's menu. <tal:menu replace="structure view/@@+related-pages" /> The disabled links and the current page are removed the from list. * Define the global Modification/Set links * Some links cannot be moved into the page because they act on the entire object or on some unseen object. eg. Change password and Change Details. The menu should be very small. * Side portlets * Build the side portlets that convey activity * Are there subscribers that need to be shown? * Are there items to download? * Are there events or notification to display? * Main portlets * The 'first' problem: The YUI CSS requires that the div that contains 'first' also contain the 'yui-u' class. The portlet template can define its div and set the 'portlet' class. The calling template will set the yui classes. Something like this: <div class="first yui-u" tal:content="structure context/@@+portlet-top-contributors" /> * If there is a lot of content, divide the main area into columns * Create a parent column <div class="yui-g"> * Create first portlet <div class="yui-u first portlet"> * Add all other portlets <div class="yui-u portlet"> * Note the 'first' problem above, if you separate the portlet to another file, keep the YUI classes in the calling template: <div class="yui-u first"> * Use the <div class="portlet"> structure to create the correct margins and borders around the chunk of content. * Note that the new CSS makes better use of content. The portlet presentation changes based on its location on the page. * Common structures * Replace 0.0-2.0 html structures with 3.0 so that the layout is consistent for all pages * Horizontal lines of links are now <ul class="horizontal"> to normalize spacing * Table and CSS that created content that was left-aligned to heading are removed. The subordinate information appears below the heading now. They are may be replaced with <dl> that may have many <dd> per <dt> * Ensure the CSS works because the classes are right, the CSS that works on ids is being removed.
Implementation changes
* Many pages are creating links that should be codified in a menu for the app (`ApplicationMenu`), context (`ContextMenu`), or view (`NavigationMenu`) * base-layout will use view/page_title before falling back pagetitles to insert the page title into the HTML. * pagetitles.py is deprecated. Do not add to it. Do no one use CONTEXTS/fmt:pagetitle * If the view is used by one template, the moving the title rule from pagetitles to view/page_title is simple. * If several templates use the view you may be able to write a more complex rule create the title * You can choose to not convert the page title, let the template fall back to page titles.
Test changes
* Lots of pagetests/stories will break. * Does the test verifying page titles, heading, and links work between pages? * Fix them. * Does the test check forms, or verify the primary content of the page? * Throw out the test, create a unittest or browser/doctest that verifies the view's contracts and purpose. * Does the page verify the that an object's state was changed. * Throw out the test, create unittest or browser/doctest that verifies the state. * Does the test verify an external script? * Throw out the test and create unittest or doctest that verifies the script ran and states changed.
3.0 CSS
Launchpad is using classes for styling, ids are used for testing and scripts. YUI is doing layout, Launchpad is decorating the layout, The YUI layout rules are not portable between templates, do not use the in the same element that uses a launchpad class. Do not use YUI in the portlet templates because the instruction may break another page that also uses the portlet
YUI (layout)::
- yui-g: A row
- yui-u: A column
- first: The first column
You can image the yui-g as a table row, and the yui-u as a table column. This creates the holes as you said above, and this behaves just link designing pages with tables. This is called YUI-Grid after all. It assumes all content is essential. It's uses of the 'first' class makes portlet reusablity difficult without careful template creation.
Launchpad (decoration)::
- portlet: A chunk on content distinguished by margins, padding, and borders Probably always paired with yui-u
- top-portlet: The first portlet in the page, probably a description.
Mixing YUI classes on elements that use launchpad classes may lead to broken layouts when templates are refactored.
There are may css classes we have not written yet because we have not discovered all the kinds of html structures we need to make the layout consistent.
Example layouts
A common layout will be an intro on top of two columns of content below it in the main slot . This layout is the easiest to do. The portlets will not align so it may look ugly.
<html ... metal:use-macro="view/macro:page/main_side"> <body> <tal:heading metal:fill-slot="heading"> <h1>Zaphod Beeblebrox</h1> </tal:heading> <tal:main metal:fill-slot="main"> <div class="top-portlet"> Description </div> <div class="yui-g"> <div class="first yui-u"> # Call the essential portlets. # Call the optional portlets. </div> <div class="yui-u"> # Call the essential portlets. # Call the optional portlets. </div> </div> </tal:main> <tal:side metal:fill-slot="side"> <div id="actions" class="first portlet"> content </div> <div id="attachments" class="portlet"> more content </div> <div class="portlet" tal:content="structure context/@@+latest-announcements" /> </tal:side> </body>
The more complex approach is to create a row (yui-g) for each pair of essential content so that some of the page aligns and looks nice. In the last row (yui-g), call all the optional portlets. The pushed the only part of the layout below the fold. You can also interleave single column portlets between the yui-g rows to improve the layout.
<tal:main metal:fill-slot="main"> <div class="top-portlet"> Description </div> <div class="yui-g"> <div class="first yui-u"> # Call the essential portlet. </div> <div class="yui-u"> # Call the essential portlet. </div> </div> <div class="yui-g"> <div class="first yui-u"> # Call the essential portlet. </div> <div class="yui-u"> # Call the essential portlet. </div> </div> <div> # an essential or optional portlet that is one column </div> <div class="yui-g"> <div class="first yui-u"> # Call the optional portlets. </div> <div class="yui-u"> # Call the optional portlets. </div> </div> </tal:main>
Modification page/view example diff
Form pages are very simple at the minimum, you can change the page macro and be done. You should consider adding a cancel link if it is missing and updating a page title. This example shows the changes need to fix one page. They took about 15 minutes.
Note that the final page template is identical to
- lp/app/templates/generic-edit.pt
In cases like this consider updating the ZCML to use the generic template and delete the old one.
=== modified file 'lib/canonical/launchpad/pagetitles.py' --- lib/canonical/launchpad/pagetitles.py 2009-07-29 20:56:34 +0000 +++ lib/canonical/launchpad/pagetitles.py 2009-08-10 20:55:01 +0000 @@ -1225,13 +1217,6 @@ -def productseries_edit(context, view): - """Return the page title for changing a product series details.""" - return 'Change %s %s details' % ( - context.product.displayname, context.name) === modified file 'lib/lp/registry/browser/productseries.py' --- lib/lp/registry/browser/productseries.py 2009-07-21 22:27:22 +0000 +++ lib/lp/registry/browser/productseries.py 2009-08-10 20:54:37 +0000 class ProductSeriesEditView(LaunchpadEditFormView): """A View to edit the attributes of a series.""" schema = IProductSeries @@ -479,6 +489,17 @@ custom_widget('summary', TextAreaWidget, height=7, width=62) custom_widget('releasefileglob', StrippedTextWidget, displayWidth=40) + @property + def label(self): + """The form label.""" + return 'Edit %s series %s' % ( + self.context.product.displayname, self.context.name) + + @property + def page_title(self): + """The page title.""" + return self.label + def validate(self, data): """See `LaunchpadFormView`.""" branch = data.get('branch') @@ -497,6 +518,11 @@ """See `LaunchpadFormView`.""" return canonical_url(self.context) + @property + def cancel_url(self): + """See `LaunchpadFormView`.""" + return canonical_url(self.context) + === modified file 'lib/lp/registry/browser/tests/productseries-views.txt' --- lib/lp/registry/browser/tests/productseries-views.txt 2009-07-21 16:30:28 +0000 +++ lib/lp/registry/browser/tests/productseries-views.txt 2009-08-10 20:01:35 +0000 @@ -77,7 +77,85 @@ +Edit ProductSeries +------------------ + +The productseries +edit view provides a label and page_title for the page. + + >>> view = create_initialized_view(series, '+edit') + >>> print view.label + Edit App series simple + + >>> print view.page_title + Edit App series simple + +The view provides a cancel_url and a next_url. + + >>> print view.cancel_url + http://launchpad.dev/app/simple + + >>> print view.next_url + http://launchpad.dev/app/simple === modified file 'lib/lp/registry/templates/productseries-edit.pt' --- lib/lp/registry/templates/productseries-edit.pt 2009-07-17 17:59:07 +0000 +++ lib/lp/registry/templates/productseries-edit.pt 2009-08-10 16:16:33 +0000 @@ -1,23 +3,12 @@ + <html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:tal="http://xml.zope.org/namespaces/tal" + xmlns:metal="http://xml.zope.org/namespaces/metal" + xmlns:i18n="http://xml.zope.org/namespaces/i18n" + metal:use-macro="view/macro:page/main_only" + i18n:domain="launchpad"> + <body> + <div metal:fill-slot="main"> + <div metal:use-macro="context/@@launchpad_form/form" /> + </div> + </body> + </html>
Adding a NavigationMenu
Launchpad uses NavigationMenus to associate outbound links to a view. The menu and view are bound together by a marker interface. Links that are used in more than one menu should be defined in a mixin class to ensure that there is a single canonical definition of them.
The menu can be rendered as an action menu in the side portlets or as a list of related pages in the main content.
Working with more than one menu
There is a lot or duplicate definitions of links. Some even have different text, summaries, or icons. There should be only one definition on a link. If a link is used in more than one menu, move it to a mixin.
Do not try to subclass an ApplicationMenu to a NavigationMenu to reuse a link. Bad things will happen. Use a mixin.
A browser module example
1 from zope.interface import implements, Interface
2
3 from canonical.launchpad.webapp.menu import NavigationMenu
4 from canonical.launchpad.webapp.publisher import LaunchpadView
5
6 from lp.myapp.interfaces.myobject import IObject
7
8
9 class ObjectLinksMixin:
10 """A mixin class that provides links used by more than one menu."""
11
12
13 # Classes to create menus on more than one view
14 class IObjectEditMenu(Interface):
15 """A marker interface for the edit navigation menu."""
16
17
18 class ObjectEditMenu(NavigationMenu, ObjectLinksMixin):
19 """A menu for different aspects of editing a object."""
20 # Bind the menu to the view.
21 usedfor = IObjectEditMenu
22 facet = 'overview'
23 title = 'Change object'
24 links = ['edit', 'images', 'reassign', 'review', 'administer']
25
26
27 class ObjectEditView(LaunchpadView):
28 """A view to set branding."""
29 # Bind the menu to the view.
30 implements(IObjectEditMenu)
31
32
33 class ObjectImagesView(LaunchpadView):
34 """A view to set images."""
35 # Bind the menu to the view.
36 implements(IObjectEditMenu)
37
38
39 # Class to create a menu for the object, irrespective of the view.
40 class ObjectActionMenu(NavigationMenu, ObjectLinksMixin):
41 """An action menu to modify the object."""
42
43 usedfor = IObject
44 facet = 'overview'
45 title = 'Action object'
46 links = ['review', 'administer']
47
48
49 # Menus are normally registered using the menu ZCML directive. They can
50 # be registered in Python. To register them:
51 provideAdapter(
52 ObjectEditMenu, [IObjectEditMenu], INavigationMenu, name="overview")
53 provideAdapter(
54 ObjectActionMenu, [IObject], INavigationMenu, name="overview")
A ZCML example to register a menu
<browser:menus classes=" ObjectEditMenu ObjectActionMenu" module="lp.myapp.browser.myobject"/>
Rendering a NavigationMenu
To render an action menu in the side portlets use this TALES view:
<tal:menu replace="structure view/@@+global-actions" />
To render a related pages section at the bottom main content:
<tal:menu replace="structure view/@@+related-pages" />
Two menus on the same page
There maybe a few cases where a page may need two menus. One for actions that change an object that are not possible to move into the main content, the other for pages related to the content of the page. NavigationMenus can be created for content objects and views.
Do not do this without first talking to Martin about the reason. The examples that have been given so far have not been legitimate.
Infrastructure improvements
[X] The MenuAPI now caches menus per request. They are faster and there is no danger of a link changing during rendering. [ ] We need a unittest that can verify that every menu makes links to an adaptable view.