Merge lp:~bac/launchpad/hidden_links into lp:launchpad

Proposed by Brad Crittenden
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 12626
Proposed branch: lp:~bac/launchpad/hidden_links
Merge into: lp:launchpad
Diff against target: 322 lines (+81/-55)
6 files modified
lib/canonical/launchpad/browser/launchpad.py (+27/-0)
lib/canonical/launchpad/doc/menus.txt (+25/-15)
lib/canonical/launchpad/templates/launchpad-inline-link.pt (+18/-34)
lib/canonical/launchpad/webapp/interfaces.py (+6/-0)
lib/canonical/launchpad/webapp/menu.py (+2/-1)
lib/lp/app/browser/tests/menu.txt (+3/-5)
To merge this branch: bzr merge lp:~bac/launchpad/hidden_links
Reviewer Review Type Date Requested Status
Curtis Hovey (community) ui Approve
Tim Penhey (community) Approve
Review via email: mp+53911@code.launchpad.net

Commit message

[r=thumper][ui=sinzui][no-qa] Allow menu links to be enabled but hidden.

Description of the change

= Summary =

We have a need to have a link enabled but hidden which will subsequently
be made visible via JavaScript. If it were not hidden then browsers
which don't support JavaScript would see the link which would be a dead-end.

That said, this branch modifies the Link mechanism to add a 'hidden'
attribute. Links that are hidden but enabled will be rendered with the
class 'invisible-link'.

== Proposed fix ==

As above.

== Pre-implementation notes ==

Talk with Curtis.

== Implementation details ==

As above.

== Tests ==

bin/test -vvt doc/menus.txt

== Demo and Q/A ==

None

= Launchpad lint =

Checking for conflicts and issues in changed files.

I may fix these pre-existing problems but I may not.

Linting changed files:
  lib/canonical/launchpad/webapp/interfaces.py
  lib/canonical/launchpad/webapp/menu.py
  lib/canonical/launchpad/templates/launchpad-inline-link.pt
  lib/canonical/launchpad/doc/menus.txt

./lib/canonical/launchpad/webapp/interfaces.py
     879: redefinition of unused 'StartRequestEvent' from line 871
      42: 'UnexpectedFormData' imported but unused
     279: E301 expected 1 blank line, found 0
     281: E202 whitespace before ')'
     289: E302 expected 2 blank lines, found 1
     309: E202 whitespace before ')'
     314: E301 expected 1 blank line, found 0
     317: E301 expected 1 blank line, found 0
     319: E301 expected 1 blank line, found 0
     321: E301 expected 1 blank line, found 0
     428: E301 expected 1 blank line, found 0
     435: E301 expected 1 blank line, found 0
     443: E301 expected 1 blank line, found 0
     458: E301 expected 1 blank line, found 0
     502: E302 expected 2 blank lines, found 1
     549: E302 expected 2 blank lines, found 1
     611: E202 whitespace before ')'
     644: E202 whitespace before ')'
     756: E301 expected 1 blank line, found 0
     826: E301 expected 1 blank line, found 0
     873: E301 expected 1 blank line, found 0
     879: E301 expected 1 blank line, found 2
     200: Line exceeds 78 characters.
./lib/canonical/launchpad/doc/menus.txt
       1: narrative uses a moin header.
       6: narrative uses a moin header.
      55: narrative uses a moin header.
     102: source exceeds 78 characters.
     164: narrative uses a moin header.
     253: narrative uses a moin header.
     333: source exceeds 78 characters.
     370: narrative uses a moin header.
     429: narrative uses a moin header.
     501: narrative uses a moin header.
     568: narrative uses a moin header.
     576: source exceeds 78 characters.
     688: narrative uses a moin header.
     878: narrative uses a moin header.
     897: narrative uses a moin header.
     909: narrative uses a moin header.

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

Hi Brad,

Nothing wrong with the concept, but I have a few comments
on the implementation.

Can we not have a single tal expression to render the link?

Move the full class definition into the view object, and should
specify the invisible-link or not. Also avoiding the inline
style of "line-height: 20px". That should be moved to a class
style in our stylesheet for invisible-link.

Also the anchor tag specifying empty href, class and title
attributes had me confused until I looked at the tal:attributes.
I find they detract more than they help. Can you remove them?

review: Needs Fixing
Revision history for this message
Brad Crittenden (bac) wrote :

Hi Tim,

I didn't move the class definition into the view originally as I was under the impression the view wanted to remain generic and the customization of the rendering was done via the templates. But I see your point and will do it.

Curtis and I discussed the "line-height" issue and he wanted to have a full investigation of it to see if it is the culprit in some alignment issues. He asked that I not remove it in this pass. Also in the past there has been vigorous discouragement of moving one-off stylings into the CSS.

Finally, I too was confused by the empty anchor tag attributes. I meant to remove them but forgot. Thanks for pointing these out.

Revision history for this message
Tim Penhey (thumper) wrote :

Brad, a little aggressive in the removal of code. A link with no link should be a <span>

If you fix this, you're good to go.

review: Approve
Revision history for this message
Curtis Hovey (sinzui) wrote :

Hi brad. Sorry about not completing the review. I suck. As I said in the implementation talk, He cannot mess with line-height. It is wrong and needs to be removed. I will remove the source you cargo-culted.

review: Approve (ui)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/browser/launchpad.py'
2--- lib/canonical/launchpad/browser/launchpad.py 2011-03-15 09:26:33 +0000
3+++ lib/canonical/launchpad/browser/launchpad.py 2011-03-18 19:17:47 +0000
4@@ -211,6 +211,33 @@
5 else:
6 return ''
7
8+ @property
9+ def css_class(self):
10+ """Return the CSS class."""
11+ value = ["menu-link-%s" % self.context.name]
12+ if not self.context.linked:
13+ value.append('nolink')
14+ if self.context.icon:
15+ value.append(self.sprite_class)
16+ value.append(self.context.icon)
17+ if self.context.hidden:
18+ value.append('invisible-link')
19+ return " ".join(value)
20+
21+ @property
22+ def url(self):
23+ """Return the url if linked."""
24+ if self.context.linked:
25+ return self.context.url
26+ return ''
27+
28+ @property
29+ def summary(self):
30+ """Return the summary if linked."""
31+ if self.context.linked:
32+ return self.context.summary
33+ return ''
34+
35
36 class Hierarchy(LaunchpadView):
37 """The hierarchy part of the location bar on each page."""
38
39=== modified file 'lib/canonical/launchpad/doc/menus.txt'
40--- lib/canonical/launchpad/doc/menus.txt 2010-11-07 21:15:07 +0000
41+++ lib/canonical/launchpad/doc/menus.txt 2011-03-18 19:17:47 +0000
42@@ -192,7 +192,7 @@
43 ... target = '+foo'
44 ... text = 'Foo'
45 ... return Link(target, text)
46- ...
47+ ...
48 ... def bar(self):
49 ... target = '+bar'
50 ... text = 'Bar'
51@@ -215,6 +215,7 @@
52 --- link foo ---
53 enabled: True
54 escapedtext: Foo
55+ hidden: False
56 icon: None
57 icon_url: None
58 linked: True
59@@ -232,6 +233,7 @@
60 --- link bar ---
61 enabled: True
62 escapedtext: Bar
63+ hidden: False
64 icon: None
65 icon_url: None
66 linked: True
67@@ -357,6 +359,13 @@
68 # Clean up our special login.
69 >>> login(ANONYMOUS)
70
71+A menu item can be marked as hidden even though it is enabled.
72+
73+ >>> link = Link('z', 'text', 'summary', icon='icon', hidden=True)
74+ >>> print ILink(link).render() #doctest: +NORMALIZE_WHITESPACE
75+ <a class="menu-link-None sprite icon invisible-link"
76+ title="summary">text</a>
77+
78
79 == How do we tell which link from a facetmenu is the selected one? ==
80
81@@ -448,10 +457,10 @@
82 adapter. For this part of the test, we won't worry about that.
83
84 >>> class FooApplicationMenu(ApplicationMenu):
85- ...
86+ ...
87 ... links = ['first']
88 ... facet = 'foo'
89- ...
90+ ...
91 ... def first(self):
92 ... target = '+first'
93 ... text = 'First menu'
94@@ -473,6 +482,7 @@
95 --- link first ---
96 enabled: True
97 escapedtext: First menu
98+ hidden: False
99 icon: None
100 icon_url: None
101 linked: True
102@@ -515,9 +525,9 @@
103 For this part of the test, we won't worry about that.
104
105 >>> class MyContextMenu(ContextMenu):
106- ...
107+ ...
108 ... links = ['first']
109- ...
110+ ...
111 ... def first(self):
112 ... target = '+firstcontext'
113 ... text = 'First context menu item'
114@@ -539,6 +549,7 @@
115 --- link first ---
116 enabled: True
117 escapedtext: First context menu item
118+ hidden: False
119 icon: None
120 icon_url: None
121 linked: True
122@@ -583,9 +594,9 @@
123
124 >>> class FacetsForThing(Facets):
125 ... usedfor = IThingHavingFacets
126- ...
127+ ...
128 ... links = ['foo', 'bar', 'baz']
129- ...
130+ ...
131 ... def baz(self):
132 ... target = ''
133 ... text = 'baz'
134@@ -694,16 +705,16 @@
135 >>> from canonical.launchpad.webapp.vhosts import allvhosts
136 >>> class FakeRequest:
137 ... implements(IHTTPApplicationRequest, IBrowserRequest)
138- ...
139+ ...
140 ... interaction = None
141- ...
142+ ...
143 ... def __init__(self, url, query=None, url1=None):
144 ... self.url = url
145 ... self.query = query
146 ... self.url1 = url1 # returned from getURL(1)
147 ... self.method = 'GET'
148 ... self.annotations = {}
149- ...
150+ ...
151 ... def getURL(self, level=0):
152 ... assert 0 <= level <=1, 'level must be 0 or 1'
153 ... if level == 0:
154@@ -712,7 +723,7 @@
155 ... assert self.url1 is not None, (
156 ... 'Must set url1 in FakeRequest')
157 ... return self.url1
158- ...
159+ ...
160 ... def getRootURL(self, rootsite):
161 ... if rootsite is not None:
162 ... return allvhosts.configs[rootsite].rooturl
163@@ -722,14 +733,14 @@
164 ... def getApplicationURL(self):
165 ... # Just the http://place:port part, so stop at the 3rd slash.
166 ... return '/'.join(self.url.split('/', 3)[:3])
167- ...
168+ ...
169 ... def get(self, key, default=None):
170 ... assert key == 'QUERY_STRING', 'we handle only QUERY_STRING'
171 ... if self.query is None:
172 ... return default
173 ... else:
174 ... return self.query
175- ...
176+ ...
177 ... def setPrincipal(self, principal):
178 ... self.principal = principal
179 >>> request = FakeRequest('http://launchpad.dev/sesamestreet/+bar')
180@@ -906,7 +917,7 @@
181 >>> from canonical.launchpad.webapp import enabled_with_permission
182
183 >>> class SomeMenu(ContextMenu):
184- ...
185+ ...
186 ... @enabled_with_permission('launchpad.Admin')
187 ... def foo(self):
188 ... return Link('+admin', 'Admin the foo')
189@@ -930,4 +941,3 @@
190 >>> foolink = somemenu.foo()
191 >>> foolink.enabled
192 True
193-
194
195=== modified file 'lib/canonical/launchpad/templates/launchpad-inline-link.pt'
196--- lib/canonical/launchpad/templates/launchpad-inline-link.pt 2010-08-02 16:36:09 +0000
197+++ lib/canonical/launchpad/templates/launchpad-inline-link.pt 2011-03-18 19:17:47 +0000
198@@ -1,40 +1,24 @@
199 <tal:link-enabled
200 xmlns:tal="http://xml.zope.org/namespaces/tal"
201 condition="context/enabled">
202+
203 <tal:link-linked
204- condition="context/linked"><a
205- href="" class="" title=""
206- tal:condition="context/icon"
207- tal:attributes="
208- class string:menu-link-${context/name} ${view/sprite_class} ${context/icon};
209- href context/url;
210- title context/summary;
211- "
212- tal:content="structure context/escapedtext"
213- /><a
214- tal:condition="not: context/icon"
215- style="line-height: 20px;"
216- href="" class="" title=""
217- tal:attributes="
218- class string:menu-link-${context/name};
219- href context/url;
220- title context/summary;
221- "
222- tal:content="structure context/escapedtext"
223- /></tal:link-linked>
224+ condition="context/linked">
225+ <a tal:attributes="
226+ class view/css_class;
227+ href view/url;
228+ title view/summary;
229+ "
230+ tal:content="structure context/escapedtext"
231+ />
232+ </tal:link-linked>
233+
234 <tal:link-not-linked
235- condition="not: context/linked"><span
236- tal:condition="context/icon"
237- tal:attributes="
238- class string:menu-link-${context/name} nolink
239- ${view/sprite_class} ${context/icon};
240- "
241- tal:content="structure context/escapedtext"
242- >link text</span><span
243- tal:condition="not: context/icon"
244- tal:attributes="
245- class string:menu-link-${context/name} nolink
246- "
247- tal:content="structure context/escapedtext"
248- >link text</span></tal:link-not-linked>
249+ condition="not: context/linked">
250+ <span
251+ tal:attributes="class view/css_class"
252+ tal:content="structure context/escapedtext"
253+ >link text</span>
254+ </tal:link-not-linked>
255+
256 </tal:link-enabled>
257
258=== modified file 'lib/canonical/launchpad/webapp/interfaces.py'
259--- lib/canonical/launchpad/webapp/interfaces.py 2011-01-20 23:55:37 +0000
260+++ lib/canonical/launchpad/webapp/interfaces.py 2011-03-18 19:17:47 +0000
261@@ -187,6 +187,12 @@
262 sort_key = Attribute(
263 "The sort key to use when rendering it with a group of links.")
264
265+ hidden = Attribute(
266+ "Boolean to say whether this link is hidden. This is separate from "
267+ "being enabled and is used to support links which need to be be "
268+ "enabled but not viewable in the rendered HTML. The link may be "
269+ "changed to visible by JavaScript or some other means.")
270+
271
272 class ILink(ILinkData):
273 """An object that represents a link in a menu.
274
275=== modified file 'lib/canonical/launchpad/webapp/menu.py'
276--- lib/canonical/launchpad/webapp/menu.py 2011-03-01 05:00:01 +0000
277+++ lib/canonical/launchpad/webapp/menu.py 2011-03-18 19:17:47 +0000
278@@ -128,7 +128,7 @@
279 implements(ILinkData)
280
281 def __init__(self, target, text, summary=None, icon=None, enabled=True,
282- site=None, menu=None):
283+ site=None, menu=None, hidden=False):
284 """Create a new link to 'target' with 'text' as the link text.
285
286 'target' is a relative path, an absolute path, or an absolute url.
287@@ -158,6 +158,7 @@
288 self.enabled = enabled
289 self.site = site
290 self.menu = menu
291+ self.hidden = hidden
292
293 Link = LinkData
294
295
296=== modified file 'lib/lp/app/browser/tests/menu.txt'
297--- lib/lp/app/browser/tests/menu.txt 2009-10-30 17:54:58 +0000
298+++ lib/lp/app/browser/tests/menu.txt 2011-03-18 19:17:47 +0000
299@@ -87,8 +87,7 @@
300 <BLANKLINE>
301 <BLANKLINE>
302 <li>
303- <a style="..."
304- href="http://launchpad.dev/~beaker/+edit-people"
305+ <a href="http://launchpad.dev/~beaker/+edit-people"
306 class="menu-link-edit_people">Edit people related to thing</a>
307 </li>
308 ...
309@@ -145,11 +144,10 @@
310 <ul>
311 <li>
312 <span class="menu-link-edit_thing nolink
313- sprite modify edit">Edit thing</span>
314+ sprite modify edit">Edit thing</span>
315 </li>
316 <li>
317- <a style="..."
318- href="http://launchpad.dev/~beaker/+edit-people"
319+ <a href="http://launchpad.dev/~beaker/+edit-people"
320 class="menu-link-edit_people">Edit people related to thing</a>
321 </li>
322 </ul>