1 | /** |
---|
2 | * t29v6 Menu |
---|
3 | * |
---|
4 | * Features: |
---|
5 | * - Collapsable (can be expanded by button) |
---|
6 | * - Scrollable (can be maked position:fixed by button) |
---|
7 | * - Guide beam (footer clone of navigation in a fancy beam like way) |
---|
8 | * |
---|
9 | * Language-specific strings are taken from t29.msg. |
---|
10 | * |
---|
11 | * (c) 2012 Sven Koeppel <sven@tech..29.de> |
---|
12 | * Licensed under GPL |
---|
13 | **/ |
---|
14 | |
---|
15 | if(!t29) window.t29 = {}; // the t29 namespace |
---|
16 | |
---|
17 | t29.menu = { collapsed:{}, scroll:{}, guide:{} }; // mit Unterklassen |
---|
18 | t29.menu.setup = function() { |
---|
19 | t29.menu.side = $("nav.side"); // Hauptseitennavigation |
---|
20 | t29.menu.beam = $("nav.guide"); // Strahlnavigation/Guide (Kopie von side) |
---|
21 | t29.menu.rel = $("nav.rel"); // relative navigation im footer (vor/zurück) |
---|
22 | t29.menu.collapsed.setup(); |
---|
23 | t29.menu.scroll.setup(); |
---|
24 | t29.menu.guide.setup(); |
---|
25 | }; |
---|
26 | |
---|
27 | /*************************************************************************************** |
---|
28 | 1. Collapsable Menu system t29.menu.collapsed |
---|
29 | |
---|
30 | The collapsable menu system is capable of collapse parts of the menu by user |
---|
31 | interaction or program request. The current state is stored in t29.prefs. |
---|
32 | |
---|
33 | ***************************************************************************************/ |
---|
34 | |
---|
35 | /** |
---|
36 | * Construct a new collapsible. It needs three named arguments: |
---|
37 | * c = new t29.menu.Collapsible({ |
---|
38 | * id: 'foobar', // a senseful id for the t29.prefs cookie system |
---|
39 | * lists: $(".your ul"), // some element which should be collapsed |
---|
40 | * button: $("<button/>").appendTo("body"); // some button which should do the work |
---|
41 | * }); |
---|
42 | * |
---|
43 | **/ |
---|
44 | t29.menu.Collapsible = function(arglist) { |
---|
45 | for (var n in arguments[0]) { this[n] = arguments[0][n]; } |
---|
46 | this.store_key = 'menu-collapsible-'+this.id; // key for t29.prefs.get/set |
---|
47 | |
---|
48 | // default values |
---|
49 | if(!this.button) // button widget |
---|
50 | this.button = $('<span class="button collapse-menu"></span>') |
---|
51 | .addClass('for-'+this.id).appendTo("nav.side"); |
---|
52 | if(!this.label) { // button label |
---|
53 | this.label = {}; |
---|
54 | this.label[t29c.FOLD] = t29._("js-menu-collapse-out"); |
---|
55 | this.label[t29c.EXPAND] = t29._("js-menu-collapse-in"); |
---|
56 | } |
---|
57 | if(!this.initial) // initial state |
---|
58 | this.initial = t29.prefs.get(this.store_key, t29c.FOLD); |
---|
59 | |
---|
60 | // set initial state |
---|
61 | this.set(this.initial, t29c.QUICK); |
---|
62 | |
---|
63 | // set button callback |
---|
64 | this.button.click($.proxy(function(){ this.set(); }, this)); |
---|
65 | } |
---|
66 | |
---|
67 | // Constants: |
---|
68 | if(!window.t29c) window.t29c = {}; // namespace for t29 contstants |
---|
69 | t29c.FOLD = true; // state: folded menu (small) |
---|
70 | t29c.EXPAND = false; // state: expanded menu (big) |
---|
71 | t29c.QUICK = true; // action: quick crossover (no animation, instantanous) |
---|
72 | t29c.ANIMATE = false; // action: animated crossover (visible to user) |
---|
73 | |
---|
74 | /** |
---|
75 | * Menu ein- oder ausklappen. |
---|
76 | * |
---|
77 | * @param target_state true: Eingeklappt, false: ausgeklappt |
---|
78 | * @param quick true=keine Animation, false/undefined=Animation |
---|
79 | **/ |
---|
80 | t29.menu.Collapsible.prototype.set = function(collapse, quick) { |
---|
81 | if(collapse == undefined) |
---|
82 | collapse = ! this.is_collapsed(); |
---|
83 | log("Collapse: "+this.id+" FOLD " +(collapse==t29c.FOLD ? "<=" : "=>")+" EXPAND " + (quick==t29c.QUICK ? "[quick!]" : "")); |
---|
84 | if(this.set_pre) |
---|
85 | this.set_pre(collapse, quick); // execute some callback |
---|
86 | if(quick) this.lists[collapse ? 'hide' : 'show'](); |
---|
87 | else this.lists[collapse ? 'slideUp' : 'slideDown'](); |
---|
88 | this.button.text(this.label[collapse]); |
---|
89 | // body CSS class shall only be used for CSS interaction, not for JS readout. Use is_collapsed() instead. |
---|
90 | $("body")[collapse ? 'addClass' : 'removeClass']("collapsed-menu-"+this.id); |
---|
91 | t29.prefs.set(this.store_key, collapse); |
---|
92 | } |
---|
93 | |
---|
94 | // returns whether menu is collapsed (boolean). |
---|
95 | t29.menu.Collapsible.prototype.is_collapsed = function() { return t29.prefs.get(this.store_key) == t29c.FOLD; } |
---|
96 | |
---|
97 | t29.menu.collapsed.setup = function() { |
---|
98 | // set up some collapsible lists |
---|
99 | t29.menu.collapsed.u3 = new t29.menu.Collapsible({ |
---|
100 | id: 'u3', |
---|
101 | lists: $("nav.side .u3").not("nav.side li.active > .u3, .geraete"), |
---|
102 | }); |
---|
103 | |
---|
104 | // check if we want mini menu for the beginning |
---|
105 | if( $("body").hasClass("in-geraete") ) { |
---|
106 | t29.menu.collapsed.u3.button.hide(); |
---|
107 | // mini doesn't care about cookie settings. |
---|
108 | t29.menu.collapsed.mini = new t29.menu.Collapsible({ |
---|
109 | id: 'mini', |
---|
110 | lists: $("nav.side li").not('.guide-only').not("li.active, li.active > ul.u3 > li, li.active > ul.u4 > li"), |
---|
111 | initial: t29c.FOLD, |
---|
112 | set_pre: function(collapse) { |
---|
113 | if(collapse == t29c.EXPAND) { |
---|
114 | // after first expanding, disable system and enable rest of systems |
---|
115 | this.button.hide(); |
---|
116 | t29.menu.collapsed.u3.button.show(); |
---|
117 | } |
---|
118 | } |
---|
119 | }); |
---|
120 | } |
---|
121 | |
---|
122 | /* |
---|
123 | t29.menu.collapsed.geraete = new t29.menu.Collapsible({ |
---|
124 | id: 'geraete', |
---|
125 | lists: $("nav.side ul.geraete"), |
---|
126 | label: (function(){ l = {}; l[t29c.FOLD] = '(+ extra)'; l[t29c.EXPAND] = '(- extra)'; return l; })(), |
---|
127 | }); |
---|
128 | */ |
---|
129 | |
---|
130 | // special situation on gerate pages (body.in-geraete): only active li's are shown there |
---|
131 | // by default. This is a third state next to FOLDed and EXPANDed menu: super-FOLDED. |
---|
132 | // Clicking the 'details' button yields ordinary FOLDed state. |
---|
133 | |
---|
134 | // hide geraete |
---|
135 | //t29.menu.collapsed.geraete.button.hide(); |
---|
136 | //$("ul.geraete").hide(); |
---|
137 | }; |
---|
138 | |
---|
139 | /*************************************************************************************** |
---|
140 | 2. Menu scroll system t29.menu.scroll |
---|
141 | |
---|
142 | The scrollable menu system can handle a position:fixed navigation area with dynamic |
---|
143 | switching to static or absolute positioning. It is narrowly toothed to the |
---|
144 | collapse system. Current state is stored in t29.prefs. |
---|
145 | |
---|
146 | ***************************************************************************************/ |
---|
147 | |
---|
148 | // enums, die CSS-Klassen im <html> entsprechen: |
---|
149 | t29.menu.scroll.States = Object.freeze({STATIC:"static-menu",FIX:"fixed-menu",STICK_TOP:"stick-top-menu",STICK_BOTTOM:"stick-bottom-menu"}); |
---|
150 | /** |
---|
151 | * Menuezustand beim Scrollen umschalten. |
---|
152 | * @param target_state Zustand aus scroll.States-Enum |
---|
153 | * @param |
---|
154 | * |
---|
155 | **/ |
---|
156 | t29.menu.scroll.set = function(target_state) { |
---|
157 | old_state = t29.menu.scroll.state; |
---|
158 | t29.menu.scroll.state = target_state; |
---|
159 | $("html").removeClass("static-menu fixed-menu stick-top-menu stick-bottom-menu").addClass(t29.menu.scroll.state); |
---|
160 | |
---|
161 | // Aufraeumen nach altem Status: |
---|
162 | switch(old_state) { |
---|
163 | case t29.menu.scroll.States.STICK_BOTTOM: |
---|
164 | t29.menu.side.attr("style",""); // reset css "top" value for positioning |
---|
165 | break; |
---|
166 | } |
---|
167 | |
---|
168 | // Einrichten des neuen Status: |
---|
169 | log("Gehe in Scroll-Zustand "+target_state); |
---|
170 | switch(target_state) { |
---|
171 | case t29.menu.scroll.States.STICK_TOP: |
---|
172 | // Menue schlaegt obene an. Prinzipiell Gleicher Zustand wie STATIC. Weiter. |
---|
173 | case t29.menu.scroll.States.STATIC: |
---|
174 | // die CSS-Klassen regeln eigentlich alles. |
---|
175 | //CSS// t29.menu.collapsed.u3.button.show(); |
---|
176 | t29.menu.scroll.but.text(t29._("js-menu-scroll-show")); |
---|
177 | t29.menu.side.show(); |
---|
178 | break; |
---|
179 | case t29.menu.scroll.States.FIX: |
---|
180 | // checken ob fixing ueberhaupt geht |
---|
181 | /* |
---|
182 | if( !t29.menu.collapsed.is() && t29.menu.side.height() > $(window).height()) { |
---|
183 | // Navi ist gerade ausgeklappt und zu groß fuer Bildschirm. Probiere Einklappen: |
---|
184 | t29.menu.collapsed.set(true, true); |
---|
185 | if(t29.menu.side.height() > $(window).height()) { |
---|
186 | // Navi ist auch eingeklappt noch zu groß! |
---|
187 | log("Navi ist auch eingeklappt zu groß zum fixen!"); |
---|
188 | // eingeklappt lassen. Weitermachen. |
---|
189 | // hier noch was ueberlegen: Bei zu kleinem Bildschirm |
---|
190 | // sollte der Button gar nicht erst angezeigt werden. |
---|
191 | // dazu braucht man einen resize-Listener, der aber im |
---|
192 | // ausgeklappten zustand jedesmal checken müsste, ob das |
---|
193 | // eingeklappte menue reinpasst. (werte muss man cachen) |
---|
194 | // Ziemlich doofe Aufgabe. |
---|
195 | } |
---|
196 | }*/ |
---|
197 | |
---|
198 | t29.menu.collapsed.u3.set(true, true); // Sicherstellen, dass Navi eingeklappt. |
---|
199 | //CSS// t29.menu.collapsed.u3.button.hide(); // Ausgeklappte Navi passt auf keinen Bildschirm. |
---|
200 | t29.menu.scroll.but.text(t29._("js-menu-scroll-hide")); |
---|
201 | break; |
---|
202 | case t29.menu.scroll.States.STICK_BOTTOM: |
---|
203 | // Position absolute; Top-Position manuell setzen. |
---|
204 | t29.menu.side.css({top: t29.menu.scroll.stick_bottom }); |
---|
205 | break; |
---|
206 | default: |
---|
207 | log("Schlechter Zustand: "+target_state); |
---|
208 | } |
---|
209 | } |
---|
210 | |
---|
211 | t29.menu.scroll.setup = function() { |
---|
212 | t29.menu.scroll.but = $('<span class="button get-menu"></span>').appendTo("section.sidebar"); |
---|
213 | |
---|
214 | /** |
---|
215 | * t29.prefs and t29.menu.scroll: Valid values are only FIX and STATIC. |
---|
216 | * Crossover states like STICK_BOTTOM, STICK_TOP shall not be stored. |
---|
217 | **/ |
---|
218 | t29.menu.scroll.store_key = 'menu-scroll'; // key for accessing t29.prefs value |
---|
219 | |
---|
220 | // set initial state: |
---|
221 | initial = t29.prefs.get(t29.menu.scroll.store_key, t29.menu.scroll.States.STATIC); |
---|
222 | switch(initial) { |
---|
223 | case t29.menu.scroll.States.STATIC: |
---|
224 | t29.menu.scroll.set(initial); |
---|
225 | break; |
---|
226 | case t29.menu.scroll.States.FIX: |
---|
227 | // davon ausgehend, dass wir uns ganz oben befinden beim Seiten-Laden. |
---|
228 | // TODO / PENDING: Wenn man mit Anker #foobar auf die Seite reinkommt, |
---|
229 | // ist das nicht der Fall! Das kann kombiniert werden |
---|
230 | // mit t29.smoothscroll! |
---|
231 | t29.menu.scroll.set(t29.menu.scroll.States.STICK_TOP); |
---|
232 | break; |
---|
233 | default: |
---|
234 | log("t29.menu.scroll.setup: Invalid value "+initial+" for initial prefs"); |
---|
235 | } |
---|
236 | |
---|
237 | t29.menu.scroll.but.click(function(){ |
---|
238 | switch(t29.menu.scroll.state) { |
---|
239 | case t29.menu.scroll.States.STATIC: |
---|
240 | // zu Fix uebergehen, mit Animation. |
---|
241 | t29.menu.side.hide(); |
---|
242 | t29.menu.scroll.set(t29.menu.scroll.States.FIX); |
---|
243 | t29.menu.side.fadeIn(); |
---|
244 | t29.prefs.set(t29.menu.scroll.store_key, t29.menu.scroll.States.FIX); |
---|
245 | break; |
---|
246 | case t29.menu.scroll.States.FIX: |
---|
247 | case t29.menu.scroll.States.STICK_BOTTOM: |
---|
248 | // zu Static uebergehen, mit Animation. |
---|
249 | t29.menu.side.fadeOut(function(){ |
---|
250 | t29.menu.scroll.set(t29.menu.scroll.States.STATIC); |
---|
251 | }); |
---|
252 | t29.prefs.set(t29.menu.scroll.store_key, t29.menu.scroll.States.STATIC); |
---|
253 | break; |
---|
254 | case t29.menu.scroll.States.STICK_TOP: |
---|
255 | default: |
---|
256 | // diese Faelle sollten nicht vorkommen. |
---|
257 | log("Get-Menu Scroll-Button gedrückt obwohl unmöglich; state="+t29.menu.scroll.state); |
---|
258 | } |
---|
259 | }); // end event t29.menu.scroll.but.click. |
---|
260 | |
---|
261 | // nun ein paar Y-Koordinaten. berechnet mit dem Ausgangs-menu.side (STATIC). |
---|
262 | t29.menu.scroll.origin_top = t29.menu.side.offset().top; |
---|
263 | t29.menu.scroll.max_bottom = $("footer").offset().top - t29.menu.side.height(); |
---|
264 | t29.menu.scroll.stick_bottom = $("footer").offset().top - t29.menu.side.height() - $("#background-color-container").offset().top; |
---|
265 | t29.menu.scroll.button_max_bottom = $("footer").offset().top; |
---|
266 | //t29.menu.scroll.max_bottom - $("#background-color-container").offset().top; |
---|
267 | |
---|
268 | $(window).scroll(function(){ |
---|
269 | var y = $(this).scrollTop(); |
---|
270 | |
---|
271 | switch(t29.menu.scroll.state) { |
---|
272 | case t29.menu.scroll.States.STATIC: break; // System inaktiv. |
---|
273 | case t29.menu.scroll.States.FIX: |
---|
274 | if(y < t29.menu.scroll.origin_top) |
---|
275 | t29.menu.scroll.set(t29.menu.scroll.States.STICK_TOP); |
---|
276 | else if(y > t29.menu.scroll.max_bottom) |
---|
277 | t29.menu.scroll.set(t29.menu.scroll.States.STICK_BOTTOM); |
---|
278 | break; |
---|
279 | case t29.menu.scroll.States.STICK_TOP: |
---|
280 | if(y > t29.menu.scroll.origin_top) { |
---|
281 | // wir sind wieder weiter runter gescrollt. |
---|
282 | if(t29.menu.collapsed.u3.is_collapsed()) |
---|
283 | // und der Benutzer hat zwischenzeitlich nicht das Menue ausgeklappt |
---|
284 | t29.menu.scroll.set(t29.menu.scroll.States.FIX); |
---|
285 | else |
---|
286 | // der Benutzer hat zwischenzeitlich ausgeklappt. Schalte fixing aus. |
---|
287 | t29.menu.scroll.set(t29.menu.scroll.States.STATIC); |
---|
288 | } |
---|
289 | break; |
---|
290 | case t29.menu.scroll.States.STICK_BOTTOM: |
---|
291 | if(y < t29.menu.scroll.max_bottom) { |
---|
292 | // wir sind wieder weiter hoch gescrollt. Entcollapsen bieten wir ganz |
---|
293 | // unten nicht an. Ergo: Fixing wieder einschalten. |
---|
294 | t29.menu.scroll.set(t29.menu.scroll.States.FIX); |
---|
295 | } |
---|
296 | break; |
---|
297 | } |
---|
298 | |
---|
299 | // Sichtbarkeit des Fixed-Buttons am unteren Seitenrand |
---|
300 | // festlegen: |
---|
301 | if(y + $(window).height() > t29.menu.scroll.button_max_bottom) { |
---|
302 | $("html").addClass('button-stick-bottom'); |
---|
303 | } else if($("html").hasClass('button-stick-bottom')) { |
---|
304 | $("html").removeClass('button-stick-bottom'); |
---|
305 | } |
---|
306 | }); // end event window.scroll. |
---|
307 | }; // end t29.menu.scroll.setup. |
---|
308 | |
---|
309 | |
---|
310 | /*************************************************************************************** |
---|
311 | 2. Footer Guided Tour System t29.menu.guide |
---|
312 | |
---|
313 | The "beam" is a fancy jquery application where the menu is cloned and displayed |
---|
314 | in the footer in a very other way. This is quite static compared to the |
---|
315 | applications above. |
---|
316 | |
---|
317 | ***************************************************************************************/ |
---|
318 | t29.menu.guide.setup = function() { |
---|
319 | // Beam nur anzeigen wenn auf Seite, die auch in der Beamnavigation drin ist, |
---|
320 | // sprich Seitenleiste. |
---|
321 | if(t29.conf['seite_in_nav'] != t29.menu.side.attr('class')) |
---|
322 | return; |
---|
323 | |
---|
324 | // Zentraler Hauptschritt: Das Menue ab oberster Ebene klonen und im Footer dranhaengen, |
---|
325 | // ausserdem ein paar Ummodellierungen. |
---|
326 | g = t29.menu.beam; |
---|
327 | t29.menu.side.find(".u1").clone().appendTo(g); |
---|
328 | g.find("ul").show(); // durch t29.menu.collapse.setup wurden die .u3er auf hide gesetzt. Revert! |
---|
329 | g.find(".geraete").remove(); // geraete-Links nicht anzeigen |
---|
330 | |
---|
331 | |
---|
332 | // Texte ersetzen durch laengere verstaendlichere Beschreibungen im title |
---|
333 | g.find("a[title]").each(function(){ |
---|
334 | $(this).text( $(this).attr('title') ).attr('title',''); // title attribut entfernen |
---|
335 | }); |
---|
336 | |
---|
337 | // Abkuerzungen und Wrappings |
---|
338 | a = g.find("a"); li = g.find("li"); |
---|
339 | a.wrapInner("<span class='text'/>").append("<span class='bullet'/>"); |
---|
340 | |
---|
341 | // Punkte aequidistant verteilen |
---|
342 | count = a.length; |
---|
343 | bwidth = $(".bullet",g).outerWidth(); |
---|
344 | each_width = (g.width() + bwidth*2) / count; |
---|
345 | a.width(Math.floor(each_width)); |
---|
346 | // text-Label zentriert darstellen um den Punkt |
---|
347 | $(".text", a).css("left", function(){return(bwidth - $(this).width())/2; }); |
---|
348 | |
---|
349 | default_visibles = g.find(".start, .end, .current"); |
---|
350 | default_visibles.addClass("visible"); //.find("a").css("z-index",0); |
---|
351 | default_visibles = default_visibles.find("a:first-child"); // von li auf a |
---|
352 | |
---|
353 | // Overlappings finden |
---|
354 | // left,right: Funktionen geben links/rechts-Offset des Objekts wieder |
---|
355 | left = function($o) { return $o.offset().left; } |
---|
356 | right = function($o) { return $o.offset().left + $o.width(); } |
---|
357 | edges = default_visibles.map(function(){ |
---|
358 | t = $(".text", this); |
---|
359 | return {'left': left(t), 'right': right(t) }; |
---|
360 | }); |
---|
361 | min_left = Math.min.apply(null, edges.map(function(){ return this.left })); |
---|
362 | max_right = Math.max.apply(null, edges.map(function(){ return this.right; })); |
---|
363 | a.not(default_visibles).each(function(){ |
---|
364 | t = $(".text", this); this_a = $(this); |
---|
365 | l = left(t); r = right(t); |
---|
366 | edges.each(function(i){ |
---|
367 | if((l < this.right && l > this.left) || // rechte kante drin |
---|
368 | (r > this.left && r < this.right) || // linke kante drin |
---|
369 | (l < this.right && l < min_left) || |
---|
370 | (r > this.left && r > max_right)) { |
---|
371 | this_a.addClass("higher-text"); |
---|
372 | } |
---|
373 | }); |
---|
374 | }); |
---|
375 | |
---|
376 | // Split position for relative navigation |
---|
377 | // 20px von nav.side margin left; 40px = 4*10px padding left von nav.rel a |
---|
378 | ///// 22.04.2012: Deaktiviert, weil anderes Design vor Augen. |
---|
379 | /* |
---|
380 | split = $(".current a",g).offset().left - g.offset().left + bwidth/2; |
---|
381 | rest = $("#content").outerWidth() - split - 40; |
---|
382 | t29.menu.rel.find(".prev a").width(split); |
---|
383 | t29.menu.rel.find(".next a").width(rest); |
---|
384 | */ |
---|
385 | }; |
---|