app/soc/views/helper/html_menu.py
changeset 517 661ab830e921
parent 516 ec1dcd70b97e
child 518 d9d31d316a74
equal deleted inserted replaced
516:ec1dcd70b97e 517:661ab830e921
     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 class HtmlMenu:
       
    26   """Ordered collection of MenuItem objects as <p>...</p> paragraphs.
       
    27   """
       
    28   ITEM_PREFIX_FMT = '%(indent)s<p>'
       
    29   ITEM_SUFFIX_FMT = '%(indent)s</p>'
       
    30 
       
    31   def __init__(self, menu, item_class=None):
       
    32     """Wraps an soc.logic.menu.Menu in order to render it as HTML.
       
    33     
       
    34     Args:
       
    35       menu: an soc.logic.menu.Menu object
       
    36       item_class: style used to render the MenuItems contained in menu;
       
    37         default is None, which causes AHrefMenuItem to be used  
       
    38     """
       
    39     self._menu = menu
       
    40 
       
    41     # workaround for circular dependency between AHrefMenuItem and this class
       
    42     if not item_class:
       
    43       item_class = AHrefMenuItem
       
    44 
       
    45     self._item_class = item_class
       
    46 
       
    47   def getHtmlTags(self, indent):
       
    48     """Returns list of HTML tags for arbitrarily nested items in the menu.
       
    49     
       
    50     Args:
       
    51       indent: string prepended to the beginning of each line of output
       
    52         (usually consists entirely of spaces)
       
    53         
       
    54     Returns:
       
    55       a list of strings that can be joined with '\n' into a single string
       
    56       to produce an entire <ul>...</ul> list in HTML
       
    57     """
       
    58     tags = []
       
    59 
       
    60     if self._menu.items:
       
    61       tags.append(self.ITEM_PREFIX_FMT % {'indent': indent})
       
    62 
       
    63       for item in self._menu.items:
       
    64         tags.extend(self._item_class(
       
    65             item, menu_class=self.__class__).getHtmlTags(indent + ' '))
       
    66     
       
    67       tags.append(self.ITEM_SUFFIX_FMT % {'indent': indent})
       
    68 
       
    69     return tags
       
    70 
       
    71   def __str__(self):
       
    72     return '\n'.join(self.getHtmlTags(''))
       
    73 
       
    74 
       
    75 class UlMenu(HtmlMenu):
       
    76   """Ordered collection of MenuItem objects as a <ul> list.
       
    77   """
       
    78   ITEM_PREFIX_FMT = '%(indent)s<ul>'
       
    79   ITEM_SUFFIX_FMT = '%(indent)s</ul>'
       
    80 
       
    81   def __init__(self, menu, item_class=None):
       
    82     """Wraps an soc.logic.menu.Menu in order to render it as HTML.
       
    83     
       
    84     Args:
       
    85       menu: an soc.logic.menu.Menu object
       
    86       item_class: style used to render the MenuItems contained in menu;
       
    87         default is None, which causes LiMenuItem to be used  
       
    88     """
       
    89     # workaround for circular dependency between LiMenuItem and this class
       
    90     if not item_class:
       
    91       item_class = LiMenuItem
       
    92 
       
    93     HtmlMenu.__init__(self, menu, item_class=item_class)
       
    94 
       
    95 
       
    96 class HtmlMenuItem:
       
    97   """Base class for specific MenuItem wrappers used by HtmlMenu sub-classes.
       
    98   """
       
    99   
       
   100   def __init__(self, item, menu_class=HtmlMenu):
       
   101     """Wraps an soc.logic.menu.MenuItem in order to render it as HTML.
       
   102     
       
   103     Args:
       
   104       item: an soc.logic.menu.MenuItem object to wrap, in order to produce
       
   105         a representation of it as HTML tags later
       
   106       menu_class: a class derived from HtmlMenu, used to style any sub-menu
       
   107         of the MenuItem; default is HtmlMenu
       
   108     """
       
   109     self._item = self.escapeItem(item)
       
   110     self._menu_class = menu_class
       
   111 
       
   112   def getItemHtmlTags(self, indent):
       
   113     """Returns list of HTML tags for the menu item itself.
       
   114     
       
   115     This method is intended to be overridden by sub-classes.
       
   116     
       
   117     Args:
       
   118       indent: string prepended to the beginning of each line of output
       
   119         (usually consists entirely of spaces)
       
   120         
       
   121     Returns:
       
   122       a list of strings that can be joined with '\n' into a single string
       
   123       to produce:
       
   124         <b>name</b> value <i>(annotation)</i>
       
   125       with value and/or <i>(annotation)</i> omitted if either is missing
       
   126     """
       
   127     # TODO(tlarsen): implement "selected" style
       
   128 
       
   129     tags = ['%s<b>%s</b>' % (indent, self._item.name)]
       
   130     
       
   131     if self._item.value:
       
   132       tags.append('%s%s' % (indent, self._item.value))
       
   133 
       
   134     if self._item.annotation:
       
   135       tags.append('%s<i>(%s)</i>' % (indent, self._item.annotation))
       
   136       
       
   137     return tags
       
   138 
       
   139   def getSubMenuHtmlTags(self, indent):
       
   140     """Returns list of HTML tags for any sub-menu, if one exists.
       
   141     
       
   142     Args:
       
   143       indent: string prepended to the beginning of each line of output
       
   144         (usually consists entirely of spaces)
       
   145         
       
   146     Returns:
       
   147       an empty list if there is no sub-menu
       
   148         -OR-
       
   149       the list of HTML tags that render the entire sub-menu (depends on the
       
   150       menu_class that was provided to __init__()
       
   151     """
       
   152     if not self._item.sub_menu:
       
   153       return []
       
   154   
       
   155     return self._menu_class(self._item.sub_menu,
       
   156         item_class=self.__class__).getHtmlTags(indent)
       
   157 
       
   158   def getHtmlTags(self, indent):
       
   159     """Returns list of HTML tags for a menu item (and possibly its sub-menus).
       
   160     
       
   161     Args:
       
   162       indent: string prepended to the beginning of each line of output
       
   163         (usually consists entirely of spaces)
       
   164         
       
   165     Returns:
       
   166       a list of strings that can be joined with '\n' into a single string
       
   167       to produce an HTML representation of the wrapped MenuItem, with
       
   168       arbitrarily nested sub-menus possibly appended
       
   169     """
       
   170     return self.getItemHtmlTags(indent) + self.getSubMenuHtmlTags(indent)
       
   171 
       
   172   def escapeItem(self, item):
       
   173     """HTML-escapes possibly user-supplied fields to prevent XSS.
       
   174     
       
   175     Args:
       
   176       item: an soc.logic.menu.MenuItem that is altered in-place; the
       
   177         fields that are potentially user-provided (name, value, annotation)
       
   178         are escaped using self.escapeText()
       
   179         
       
   180     Returns:
       
   181       the originally supplied item, for convenience, so that this method can
       
   182       be combined with an assignment
       
   183     """
       
   184     item.name = self.escapeText(item.name)
       
   185     item.value = self.escapeText(item.value)
       
   186     item.annotation = self.escapeText(item.annotation)
       
   187     return item
       
   188 
       
   189   def escapeText(self, text):
       
   190     """
       
   191     """
       
   192     # TODO(tlarsen): user-supplied content *must* be escaped to prevent XSS
       
   193     return text
       
   194 
       
   195   def __str__(self):
       
   196     return '\n'.join(self.getHtmlTags(''))
       
   197 
       
   198 
       
   199 class AHrefMenuItem(HtmlMenuItem):
       
   200   """Provides HTML menu item properties as attributes as an <a href> link. 
       
   201   """
       
   202   
       
   203   def getItemHtmlTags(self, indent):
       
   204     """Returns list of HTML tags for the menu item itself.
       
   205     
       
   206     Args:
       
   207       indent: string prepended to the beginning of each line of output
       
   208         (usually consists entirely of spaces)
       
   209         
       
   210     Returns:
       
   211       a list of strings that can be joined with '\n' into a single string
       
   212       to produce an <a href="...">...</a> link, or just the MenuItem.name
       
   213       as plain text if there was no AHrefMenuItem.value URL
       
   214     """
       
   215     # TODO(tlarsen): implement "selected" style
       
   216 
       
   217     if not self._item.value:
       
   218       # if no URL, then not a link, so just display item.name as text
       
   219       return ['%s<span>%s</span>' % (indent, self._item.name)]
       
   220   
       
   221     # URL supplied, so make an <a href="item.value">item.name</a> link
       
   222     return ['%s<span><a href="%s">%s</a></span>' % (indent, self._item.value, self._item.name)]
       
   223 
       
   224 class LiMenuItem(AHrefMenuItem):
       
   225   """Provides HTML menu item properties as attributes as an <li> list item.
       
   226   """
       
   227 
       
   228   def __init__(self, item, menu_class=UlMenu):
       
   229     """Wraps an soc.logic.menu.MenuItem in order to render it as HTML.
       
   230     
       
   231     Args:
       
   232       item: an soc.logic.menu.MenuItem object to wrap, in order to produce
       
   233         a representation of it as HTML tags later
       
   234       menu_class: a class derived from HtmlMenu, used to style any sub-menu
       
   235         of the MenuItem; default is UlMenu
       
   236     """
       
   237     AHrefMenuItem.__init__(self, item, menu_class=menu_class)
       
   238 
       
   239   def getHtmlTags(self, indent):
       
   240     """Returns <a href> link wrapped as an <li> list item.
       
   241     
       
   242     See also AHrefMenuItem.getHtmlTags().
       
   243     """
       
   244     if self._item.sub_menu:
       
   245       css_class = 'expandable'
       
   246     else:
       
   247       css_class = 'leaf'
       
   248     
       
   249     return (['%s<li class="%s">' % (indent, css_class)]
       
   250             + AHrefMenuItem.getHtmlTags(self, indent + ' ')
       
   251             + ['%s</li>' % indent])