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(). |