source: t29-www/shared/js-v6/modules/menu.js @ 567

Last change on this file since 567 was 567, checked in by sven, 10 years ago

Den Footer implementiert, der seit langem unter das Design
gehört.

Ausserdem: Beam-Navigation angetestet.

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