app/soc/views/helpers/html_menu.py
changeset 198 e4cbd0909520
parent 196 089a86d84067
equal deleted inserted replaced
197:4cf7c0040f2e 198:e4cbd0909520
    23 
    23 
    24 
    24 
    25 from soc.logic import menu
    25 from soc.logic import menu
    26 
    26 
    27 
    27 
    28 class UlMenu(menu.Menu):
    28 class HtmlMenu:
    29   """Ordered collection of MenuItem objects as a <ul> list.
    29   """Ordered collection of MenuItem objects as <p>...</p> paragraphs.
    30   """
    30   """
    31 
    31   ITEM_PREFIX_FMT = '%(indent)s<p>'
    32   def __init__(self, items=None):
    32   ITEM_SUFFIX_FMT = '%(indent)s</p>'
    33     """Passes the menu items to the base class __init__().
    33 
    34     """
    34   def __init__(self, menu, item_class=None):
    35     menu.Menu.__init__(self, items=items)
    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
    36 
    49 
    37   def getHtmlTags(self, indent):
    50   def getHtmlTags(self, indent):
    38     """Returns list of HTML tags for arbitrarily nested items in the menu.
    51     """Returns list of HTML tags for arbitrarily nested items in the menu.
    39     
    52     
    40     Args:
    53     Args:
    45       a list of strings that can be joined with '\n' into a single string
    58       a list of strings that can be joined with '\n' into a single string
    46       to produce an entire <ul>...</ul> list in HTML
    59       to produce an entire <ul>...</ul> list in HTML
    47     """
    60     """
    48     tags = []
    61     tags = []
    49 
    62 
    50     if self.items:
    63     if self._menu.items:
    51       tags.append('%s<ul>' % indent)
    64       tags.append(self.ITEM_PREFIX_FMT % {'indent': indent})
    52 
    65 
    53       for item in self.items:
    66       for item in self._menu.items:
    54         tags.extend(item.getHtmlTags(indent + ' '))
    67         tags.extend(self._item_class(
    55     
    68             item, menu_class=self.__class__).getHtmlTags(indent + ' '))
    56       tags.append('%s</ul>' % indent)
    69     
       
    70       tags.append(self.ITEM_SUFFIX_FMT % {'indent': indent})
    57 
    71 
    58     return tags
    72     return tags
    59 
    73 
    60   def __str__(self):
    74   def __str__(self):
    61     return '\n'.join(self.getHtmlTags(''))
    75     return '\n'.join(self.getHtmlTags(''))
    62 
    76 
    63 
    77 
    64 class AHrefMenuItem(menu.MenuItem):
    78 class UlMenu(HtmlMenu):
    65   """Provides HTML menu item properties as attributes as an <a href> link. 
    79   """Ordered collection of MenuItem objects as a <ul> list.
    66   """
    80   """
    67   
    81   ITEM_PREFIX_FMT = '%(indent)s<ul>'
    68   def __init__(self, text, value=None, selected=False, annotation=None,
    82   ITEM_SUFFIX_FMT = '%(indent)s</ul>'
    69                sub_menu=None):
    83 
    70     """Initializes the menu item attributes from supplied arguments.
    84   def __init__(self, menu, item_class=None):
    71     
    85     """Wraps an soc.logic.menu.Menu in order to render it as HTML.
    72     Args:
    86     
    73       text: text displayed for the menu item link anchor
    87     Args:
    74       value: optional URL to be placed in the menu item link href;
    88       menu: an soc.logic.menu.Menu object
    75         default is None
    89       item_class: style used to render the MenuItems contained in menu;
    76       selected: Boolean indicating if this menu item is selected;
    90         default is None, which causes LiMenuItem to be used  
    77         default is False
    91     """
    78       annotation: optional help text associated with the menu item
    92     # workaround for circular dependency between LiMenuItem and this class
    79       sub_menu: see menu.MenuItem.__init__() 
    93     if not item_class:
    80     """
    94       item_class = LiMenuItem
    81     menu.MenuItem.__init__(self, text, value=value, selected=selected,
    95 
    82                            annotation=annotation, sub_menu=sub_menu)
    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)
    83 
   160 
    84   def getHtmlTags(self, indent):
   161   def getHtmlTags(self, indent):
    85     """Returns list of HTML tags for a menu item (and possibly its sub-menus).
   162     """Returns list of HTML tags for a menu item (and possibly its sub-menus).
    86     
   163     
    87     Args:
   164     Args:
    88       indent: string prepended to the beginning of each line of output
   165       indent: string prepended to the beginning of each line of output
    89         (usually consists entirely of spaces)
   166         (usually consists entirely of spaces)
    90         
   167         
    91     Returns:
   168     Returns:
    92       a list of strings that can be joined with '\n' into a single string
   169       a list of strings that can be joined with '\n' into a single string
    93       to produce an <a href="...">...</a> link, or just the MenuItem.name
   170       to produce an HTML representation of the wrapped MenuItem, with
    94       as plain text if there was no AHrefMenuItem.value URL; may also append
   171       arbitrarily nested sub-menus possibly appended
    95       arbitrarily nested sub-menus
   172     """
    96     """
   173     return self.getItemHtmlTags(indent) + self.getSubMenuHtmlTags(indent)
    97     tags = []
   174 
    98 
   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     """
    99     # TODO(tlarsen): user-supplied content *must* be escaped to prevent XSS
   195     # TODO(tlarsen): user-supplied content *must* be escaped to prevent XSS
   100 
   196     return text
   101     # TODO(tlarsen): implement "selected" style
       
   102     if self.value:
       
   103       # URL supplied, so make an <a href="self.value">self.name</a> link
       
   104       tags.append('%s<a href=' % indent)
       
   105       tags.append('"%s">%s</a>' % (self.value, self.name))
       
   106     else:
       
   107       # if no URL, then not a link, so just display self.name as text
       
   108       tags.append(self.name)
       
   109 
       
   110     # TODO(tlarsen): implement the mouse-over support for item.annotation
       
   111 
       
   112     if self.sub_menu:
       
   113       tags.extend(self.sub_menu.getHtmlTags(indent + ' '))
       
   114           
       
   115     return tags
       
   116 
   197 
   117   def __str__(self):
   198   def __str__(self):
   118     return '\n'.join(self.getHtmlTags(''))
   199     return '\n'.join(self.getHtmlTags(''))
   119 
   200 
   120 
   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 
   121 class LiMenuItem(AHrefMenuItem):
   230 class LiMenuItem(AHrefMenuItem):
   122   """Provides HTML menu item properties as attributes as an <li> list item.
   231   """Provides HTML menu item properties as attributes as an <li> list item.
   123   """
   232   """
   124   
   233 
   125   def __init__(self, text, value=None, selected=False, annotation=None,
   234   def __init__(self, item, menu_class=UlMenu):
   126                sub_menu=None):
   235     """Wraps an soc.logic.menu.MenuItem in order to render it as HTML.
   127     """Initializes the menu item attributes from supplied arguments.
   236     
   128     
   237     Args:
   129     Args:
   238       item: an soc.logic.menu.MenuItem object to wrap, in order to produce
   130       text, value, selected, annotation, sub_menu:
   239         a representation of it as HTML tags later
   131         see AHrefMenuItem.__init__() 
   240       menu_class: a class derived from HtmlMenu, used to style any sub-menu
   132     """
   241         of the MenuItem; default is UlMenu
   133     AHrefMenuItem.__init__(self, text, value=value, selected=selected,
   242     """
   134                            annotation=annotation, sub_menu=sub_menu)
   243     AHrefMenuItem.__init__(self, item, menu_class=menu_class)
   135 
   244 
   136   def getHtmlTags(self, indent):
   245   def getHtmlTags(self, indent):
   137     """Returns <a href> link wrapped as an <li> list item.
   246     """Returns <a href> link wrapped as an <li> list item.
   138     
   247     
   139     See also AHrefMenuItem.getHtmlTags().
   248     See also AHrefMenuItem.getHtmlTags().