app/soc/views/helper/html_menu.py
changeset 276 56357a92c110
parent 198 e4cbd0909520
child 390 d12c95ade374
equal deleted inserted replaced
275:78fd8c2ed80a 276:56357a92c110
       
     1 #!/usr/bin/python2.5
       
     2 #
       
     3 # Copyright 2008 the Melange authors.
       
     4 #
       
     5 # Licensed under the Apache License, Version 2.0 (the "License");
       
     6 # you may not use this file except in compliance with the License.
       
     7 # You may obtain a copy of the License at
       
     8 #
       
     9 #   http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    11 # Unless required by applicable law or agreed to in writing, software
       
    12 # distributed under the License is distributed on an "AS IS" BASIS,
       
    13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
    14 # See the License for the specific language governing permissions and
       
    15 # limitations under the License.
       
    16 
       
    17 """Helpers for displaying arbitrarily nested menus as HTML lists.
       
    18 """
       
    19 
       
    20 __authors__ = [
       
    21   '"Todd Larsen" <tlarsen@google.com>',
       
    22   ]
       
    23 
       
    24 
       
    25 from soc.logic import menu
       
    26 
       
    27 
       
    28 class HtmlMenu:
       
    29   """Ordered collection of MenuItem objects as <p>...</p> paragraphs.
       
    30   """
       
    31   ITEM_PREFIX_FMT = '%(indent)s<p>'
       
    32   ITEM_SUFFIX_FMT = '%(indent)s</p>'
       
    33 
       
    34   def __init__(self, menu, item_class=None):
       
    35     """Wraps an soc.logic.menu.Menu in order to render it as HTML.
       
    36     
       
    37     Args:
       
    38       menu: an soc.logic.menu.Menu object
       
    39       item_class: style used to render the MenuItems contained in menu;
       
    40         default is None, which causes AHrefMenuItem to be used  
       
    41     """
       
    42     self._menu = menu
       
    43 
       
    44     # workaround for circular dependency between AHrefMenuItem and this class
       
    45     if not item_class:
       
    46       item_class = AHrefMenuItem
       
    47 
       
    48     self._item_class = item_class
       
    49 
       
    50   def getHtmlTags(self, indent):
       
    51     """Returns list of HTML tags for arbitrarily nested items in the menu.
       
    52     
       
    53     Args:
       
    54       indent: string prepended to the beginning of each line of output
       
    55         (usually consists entirely of spaces)
       
    56         
       
    57     Returns:
       
    58       a list of strings that can be joined with '\n' into a single string
       
    59       to produce an entire <ul>...</ul> list in HTML
       
    60     """
       
    61     tags = []
       
    62 
       
    63     if self._menu.items:
       
    64       tags.append(self.ITEM_PREFIX_FMT % {'indent': indent})
       
    65 
       
    66       for item in self._menu.items:
       
    67         tags.extend(self._item_class(
       
    68             item, menu_class=self.__class__).getHtmlTags(indent + ' '))
       
    69     
       
    70       tags.append(self.ITEM_SUFFIX_FMT % {'indent': indent})
       
    71 
       
    72     return tags
       
    73 
       
    74   def __str__(self):
       
    75     return '\n'.join(self.getHtmlTags(''))
       
    76 
       
    77 
       
    78 class UlMenu(HtmlMenu):
       
    79   """Ordered collection of MenuItem objects as a <ul> list.
       
    80   """
       
    81   ITEM_PREFIX_FMT = '%(indent)s<ul>'
       
    82   ITEM_SUFFIX_FMT = '%(indent)s</ul>'
       
    83 
       
    84   def __init__(self, menu, item_class=None):
       
    85     """Wraps an soc.logic.menu.Menu in order to render it as HTML.
       
    86     
       
    87     Args:
       
    88       menu: an soc.logic.menu.Menu object
       
    89       item_class: style used to render the MenuItems contained in menu;
       
    90         default is None, which causes LiMenuItem to be used  
       
    91     """
       
    92     # workaround for circular dependency between LiMenuItem and this class
       
    93     if not item_class:
       
    94       item_class = LiMenuItem
       
    95 
       
    96     HtmlMenu.__init__(self, menu, item_class=item_class)
       
    97 
       
    98 
       
    99 class HtmlMenuItem:
       
   100   """Base class for specific MenuItem wrappers used by HtmlMenu sub-classes.
       
   101   """
       
   102   
       
   103   def __init__(self, item, menu_class=HtmlMenu):
       
   104     """Wraps an soc.logic.menu.MenuItem in order to render it as HTML.
       
   105     
       
   106     Args:
       
   107       item: an soc.logic.menu.MenuItem object to wrap, in order to produce
       
   108         a representation of it as HTML tags later
       
   109       menu_class: a class derived from HtmlMenu, used to style any sub-menu
       
   110         of the MenuItem; default is HtmlMenu
       
   111     """
       
   112     self._item = self.escapeItem(item)
       
   113     self._menu_class = menu_class
       
   114 
       
   115   def getItemHtmlTags(self, indent):
       
   116     """Returns list of HTML tags for the menu item itself.
       
   117     
       
   118     This method is intended to be overridden by sub-classes.
       
   119     
       
   120     Args:
       
   121       indent: string prepended to the beginning of each line of output
       
   122         (usually consists entirely of spaces)
       
   123         
       
   124     Returns:
       
   125       a list of strings that can be joined with '\n' into a single string
       
   126       to produce:
       
   127         <b>name</b> value <i>(annotation)</i>
       
   128       with value and/or <i>(annotation)</i> omitted if either is missing
       
   129     """
       
   130     # TODO(tlarsen): implement "selected" style
       
   131 
       
   132     tags = ['%s<b>%s</b>' % (indent, self._item.name)]
       
   133     
       
   134     if self._item.value:
       
   135       tags.append('%s%s' % (indent, self._item.value))
       
   136 
       
   137     if self._item.annotation:
       
   138       tags.append('%s<i>(%s)</i>' % (indent, self._item.annotation))
       
   139       
       
   140     return tags
       
   141 
       
   142   def getSubMenuHtmlTags(self, indent):
       
   143     """Returns list of HTML tags for any sub-menu, if one exists.
       
   144     
       
   145     Args:
       
   146       indent: string prepended to the beginning of each line of output
       
   147         (usually consists entirely of spaces)
       
   148         
       
   149     Returns:
       
   150       an empty list if there is no sub-menu
       
   151         -OR-
       
   152       the list of HTML tags that render the entire sub-menu (depends on the
       
   153       menu_class that was provided to __init__()
       
   154     """
       
   155     if not self._item.sub_menu:
       
   156       return []
       
   157   
       
   158     return self._menu_class(self._item.sub_menu,
       
   159         item_class=self.__class__).getHtmlTags(indent)
       
   160 
       
   161   def getHtmlTags(self, indent):
       
   162     """Returns list of HTML tags for a menu item (and possibly its sub-menus).
       
   163     
       
   164     Args:
       
   165       indent: string prepended to the beginning of each line of output
       
   166         (usually consists entirely of spaces)
       
   167         
       
   168     Returns:
       
   169       a list of strings that can be joined with '\n' into a single string
       
   170       to produce an HTML representation of the wrapped MenuItem, with
       
   171       arbitrarily nested sub-menus possibly appended
       
   172     """
       
   173     return self.getItemHtmlTags(indent) + self.getSubMenuHtmlTags(indent)
       
   174 
       
   175   def escapeItem(self, item):
       
   176     """HTML-escapes possibly user-supplied fields to prevent XSS.
       
   177     
       
   178     Args:
       
   179       item: an soc.logic.menu.MenuItem that is altered in-place; the
       
   180         fields that are potentially user-provided (name, value, annotation)
       
   181         are escaped using self.escapeText()
       
   182         
       
   183     Returns:
       
   184       the originally supplied item, for convenience, so that this method can
       
   185       be combined with an assignment
       
   186     """
       
   187     item.name = self.escapeText(item.name)
       
   188     item.value = self.escapeText(item.value)
       
   189     item.annotation = self.escapeText(item.annotation)
       
   190     return item
       
   191 
       
   192   def escapeText(self, text):
       
   193     """
       
   194     """
       
   195     # TODO(tlarsen): user-supplied content *must* be escaped to prevent XSS
       
   196     return text
       
   197 
       
   198   def __str__(self):
       
   199     return '\n'.join(self.getHtmlTags(''))
       
   200 
       
   201 
       
   202 class AHrefMenuItem(HtmlMenuItem):
       
   203   """Provides HTML menu item properties as attributes as an <a href> link. 
       
   204   """
       
   205   
       
   206   def getItemHtmlTags(self, indent):
       
   207     """Returns list of HTML tags for the menu item itself.
       
   208     
       
   209     Args:
       
   210       indent: string prepended to the beginning of each line of output
       
   211         (usually consists entirely of spaces)
       
   212         
       
   213     Returns:
       
   214       a list of strings that can be joined with '\n' into a single string
       
   215       to produce an <a href="...">...</a> link, or just the MenuItem.name
       
   216       as plain text if there was no AHrefMenuItem.value URL
       
   217     """
       
   218     # TODO(tlarsen): implement "selected" style
       
   219 
       
   220     if not self._item.value:
       
   221       # if no URL, then not a link, so just display item.name as text
       
   222       return [self._item.name]
       
   223   
       
   224     # URL supplied, so make an <a href="item.value">item.name</a> link
       
   225     return ['%s<a href=' % indent,
       
   226             '%s "%s"' % (indent, self._item.value),
       
   227             '%s>%s</a>' % (indent, self._item.name)]
       
   228 
       
   229 
       
   230 class LiMenuItem(AHrefMenuItem):
       
   231   """Provides HTML menu item properties as attributes as an <li> list item.
       
   232   """
       
   233 
       
   234   def __init__(self, item, menu_class=UlMenu):
       
   235     """Wraps an soc.logic.menu.MenuItem in order to render it as HTML.
       
   236     
       
   237     Args:
       
   238       item: an soc.logic.menu.MenuItem object to wrap, in order to produce
       
   239         a representation of it as HTML tags later
       
   240       menu_class: a class derived from HtmlMenu, used to style any sub-menu
       
   241         of the MenuItem; default is UlMenu
       
   242     """
       
   243     AHrefMenuItem.__init__(self, item, menu_class=menu_class)
       
   244 
       
   245   def getHtmlTags(self, indent):
       
   246     """Returns <a href> link wrapped as an <li> list item.
       
   247     
       
   248     See also AHrefMenuItem.getHtmlTags().
       
   249     """
       
   250     return (['%s<li>' % indent]
       
   251             + AHrefMenuItem.getHtmlTags(self, indent + ' ')
       
   252             + ['%s</li>' % indent])