parade 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -318,14 +318,40 @@ If you want to trigger some JavaScript as soon as a certain page is shown or
318
318
  when you switch to the next or previous slide, you can bind a callback to a
319
319
  custom event:
320
320
 
321
- > ### parade:show
322
- > will be triggered as soon as you enter a page
323
- > ### parade:next
324
- > will be triggered when you switch to the next page
325
- > ### parade:incr
326
- > will be triggered when you advance to the next increment on the page
327
- > ### parade:prev
328
- > will be triggered when you switch to the previous page
321
+ ### Appearance
322
+
323
+ * parade:willAppear
324
+
325
+ > triggered before the slide is presented
326
+
327
+ * parade:didAppear
328
+
329
+ > triggered after the slide is presented
330
+
331
+ * parade:show
332
+
333
+ ### Disappearance
334
+
335
+ > triggered after the slide is presented
336
+
337
+ * parade:willDisappear
338
+
339
+ > triggered before the slide disappears
340
+
341
+ * parade:didDisappear
342
+
343
+ > triggered after the slide disppeared
344
+
345
+ ### Navigation
346
+
347
+ * parade:next
348
+
349
+ > triggered when an attempt to move to the next slide or incremental bullet point
350
+
351
+ * parade:prev
352
+
353
+ > triggered when an attempt to move back a slide or incremental bullet point
354
+
329
355
 
330
356
  These events are triggered on the "div.content" child of the slide, so you must
331
357
  add a custom and unique class to your SLIDE to identify it:
@@ -335,12 +361,14 @@ add a custom and unique class to your SLIDE to identify it:
335
361
  # 1st Example h1
336
362
  <script>
337
363
  // bind to custom event
338
- $(".custom_and_unique_class").bind("parade:show", function (event) {
364
+ $(".custom_and_unique_class").live("parade:show", function (event) {
339
365
  // animate the h1
340
366
  var h1 = $(event.target).find("h1");
341
367
  h1.delay(500)
342
368
  .slideUp(300, function () { $(this).css({textDecoration: "line-through"}); })
343
369
  .slideDown(300);
370
+
371
+ return false;
344
372
  });
345
373
  </script>
346
374
  ```
@@ -350,17 +378,17 @@ h1-element will be animated, as soon as this event is triggered on that slide.
350
378
 
351
379
  If you bind an event handler to the custom events *parade:next* or
352
380
  *parade:prev*, you can prevent the default action (that is switching to the
353
- appropriate slide) by calling *event.preventDefault()*:
381
+ appropriate slide) by returning *false*:
354
382
 
355
383
  ```markdown
356
384
  !SLIDE prevent_default
357
385
  # 2nd Example h1
358
386
  <script>
359
- $(".prevent_default").bind("parade:next", function (event) {
387
+ $(".prevent_default").live("parade:next", function (event) {
360
388
  var h1 = $(event.target).find("h1");
361
389
  if (h1.css("text-decoration") === "none") {
362
- event.preventDefault();
363
390
  h1.css({textDecoration: "line-through"})
391
+ return false;
364
392
  }
365
393
  });
366
394
  </script>
@@ -368,7 +396,7 @@ $(".prevent_default").bind("parade:next", function (event) {
368
396
 
369
397
  This will bind an event handler for *parade:next* to your slide. When you press
370
398
  the right arrow key the first time, the h1-element will be decorated. When you
371
- press the right array key another time, you will switch to the next slide.
399
+ press the right arrow key another time, you will switch to the next slide.
372
400
 
373
401
  The same applies to the *parade:prev* event, of course.
374
402
 
@@ -382,7 +410,7 @@ be applied as soon as it is loaded.
382
410
  The content generated by the slide is wrapped with a *div* with the class .+content+ like this.
383
411
 
384
412
  ```html
385
- <div ref="intro/01_slide/1" class="content" style="margin-top: 210px;">
413
+ <div class="content">
386
414
  <h1>jQuery &amp; Sinatra</h1>
387
415
  <h2>A Classy Combination</h2>
388
416
  </div>
data/bin/parade CHANGED
@@ -106,24 +106,6 @@ command [:static] do |c|
106
106
 
107
107
  end
108
108
 
109
- pre do |global,command,options,args|
110
- # Pre logic here
111
- # Return true to proceed; false to abourt and not call the
112
- # chosen command
113
- true
114
- end
115
-
116
- post do |global,command,options,args|
117
- # Post logic here
118
- end
119
-
120
- on_error do |exception|
121
- # Error logic here
122
- # return false to skip default error handling
123
- true
124
- end
125
-
126
-
127
109
  # To allow an easier command-line to launch parade, the following format
128
110
  #
129
111
  # `parade` is converted to `parade server .`
@@ -133,6 +115,6 @@ end
133
115
  parameters = ARGV
134
116
 
135
117
  parameters = [ "server", "." ] if parameters.empty?
136
- parameters.unshift "server" if parameters.count == 1
118
+ parameters.unshift "server" if parameters.count == 1 and not parameters.include?("help")
137
119
 
138
120
  exit GLI.run(parameters)
@@ -39,5 +39,5 @@ rescue LoadError
39
39
  }
40
40
  end
41
41
 
42
- require_relative 'parade/helpers/encode_image'
43
- require_relative 'parade/server'
42
+ require 'parade/helpers/encode_image'
43
+ require 'parade/server'
@@ -1,4 +1,3 @@
1
- require_relative '../section'
2
1
  require_relative 'presentation_filepath_parser'
3
2
 
4
3
  module Parade
@@ -28,9 +28,7 @@ module Parade
28
28
  def render(content)
29
29
 
30
30
  html = Nokogiri::XML.fragment(content)
31
- parser = CommandlineParser.new
32
-
33
- html.css(".#{css_class}").each do |slide|
31
+ html.css(".content.#{css_class}").each do |slide|
34
32
 
35
33
  columns = []
36
34
  slop = []
@@ -35,7 +35,7 @@ module Parade
35
35
  #
36
36
  def self.render(html_content)
37
37
 
38
- html = Nokogiri::XML.fragment(html_content)
38
+ html = Nokogiri::HTML.fragment(html_content)
39
39
  parser = CommandlineParser.new
40
40
 
41
41
  html.css('.commandline pre').each do |code|
@@ -46,7 +46,23 @@ module Parade
46
46
  rule(:prompt => simple(:prompt), :input => simple(:input), :output => simple(:output)) do
47
47
  command = Nokogiri::XML::Node.new('pre', html)
48
48
  command.set_attribute('class', 'command')
49
- command.content = "#{prompt} #{input}"
49
+
50
+ node_prompt = Nokogiri::XML::Node.new('span', html)
51
+ # The 'nv' class specifically gives it the same code syntax highlighting
52
+ node_prompt.set_attribute('class','prompt nv')
53
+ node_prompt.content = prompt
54
+
55
+ separator = Nokogiri::XML::Text.new(' ',html)
56
+
57
+ node_input = Nokogiri::XML::Node.new('span',html)
58
+ node_input.content = input
59
+ # The 'nb' class specifically gives it the same syntax highlighting
60
+ node_input.set_attribute('class','input nb')
61
+
62
+ command << node_prompt
63
+ command << separator
64
+ command << node_input
65
+
50
66
  code << command
51
67
 
52
68
  # Add newline after the input so that users can
@@ -25,6 +25,14 @@ module Parade
25
25
  @title ? @title : (section ? section.title : "Section")
26
26
  end
27
27
 
28
+ # @return [Array<String>] the name of all the parent sections. In this
29
+ # instance we are not interested in sections without names. These are
30
+ # the lowest level sections and are usually within a parent section that
31
+ # they are acurrately named.
32
+ def hierarchy
33
+ Array(@title) + (section ? section.hierarchy : [])
34
+ end
35
+
28
36
  # @return [String] the description of the section
29
37
  attr_accessor :description
30
38
 
@@ -1,3 +1,4 @@
1
+ require_relative 'section'
1
2
  require_relative "parsers/dsl"
2
3
  require_relative 'renderers/update_image_paths'
3
4
 
@@ -5,6 +6,9 @@ require_relative 'features/live_ruby'
5
6
  require_relative 'features/pdf_presentation'
6
7
  require_relative 'features/preshow'
7
8
 
9
+ require_relative 'slide_post_renderers'
10
+ require_relative 'slide_pre_renderers'
11
+
8
12
  module Parade
9
13
 
10
14
  class Server < Sinatra::Application
@@ -73,7 +77,7 @@ module Parade
73
77
  # presentation directory.
74
78
  #
75
79
  def custom_css_files
76
- custom_resource "css" do |path|
80
+ custom_resource "css" do |path|
77
81
  css path
78
82
  end
79
83
  end
@@ -83,7 +87,7 @@ module Parade
83
87
  # presentation directory.
84
88
  #
85
89
  def custom_js_files
86
- custom_resource "js" do |path|
90
+ custom_resource "js" do |path|
87
91
  js path
88
92
  end
89
93
  end
@@ -1,8 +1,4 @@
1
1
  require_relative 'metadata'
2
- require_relative 'renderers/html_with_pygments'
3
- require_relative 'renderers/command_line_renderer'
4
- require_relative 'renderers/special_paragraph_renderer'
5
- require_relative 'renderers/columns_renderer'
6
2
 
7
3
  module Parade
8
4
 
@@ -24,8 +20,8 @@ module Parade
24
20
  section ? section.title : "Slide"
25
21
  end
26
22
 
27
- def reference
28
- "#{section ? section.title : 'slide'}/#{sequence}"
23
+ def hierarchy
24
+ section.hierarchy
29
25
  end
30
26
 
31
27
  #
@@ -72,15 +68,15 @@ module Parade
72
68
  # information for the slide
73
69
  #
74
70
  attr_accessor :metadata
75
-
71
+
76
72
  # @return [String] the CSS classes for the slide
77
73
  def slide_classes
78
- title.downcase.gsub(' ','-')
74
+ [ title.downcase.gsub(' ','-') ] + content_classes
79
75
  end
80
76
 
81
77
  # @return [String] the CSS classes for the content section of the slide
82
78
  def content_classes
83
- metadata.classes.join(" ")
79
+ metadata.classes
84
80
  end
85
81
 
86
82
  # @return [String] the transition style for the slide
@@ -94,13 +90,11 @@ module Parade
94
90
  end
95
91
 
96
92
  def pre_renderers
97
- [ Renderers::HTMLwithPygments ]
93
+ SlidePreRenderers.renderers
98
94
  end
99
95
 
100
96
  def post_renderers
101
- [ Renderers::SpecialParagraphRenderer,
102
- Renderers::CommandLineRenderer,
103
- Renderers::ColumnsRenderer.new(:css_class => 'columns',:html_element => "h2",:segments => 12) ]
97
+ SlidePostRenderers.renderers
104
98
  end
105
99
 
106
100
  # @return [String] HTML rendering of the slide's raw contents.
@@ -0,0 +1,24 @@
1
+ require_relative 'renderers/command_line_renderer'
2
+ require_relative 'renderers/special_paragraph_renderer'
3
+ require_relative 'renderers/columns_renderer'
4
+
5
+ module Parade
6
+
7
+ module SlidePostRenderers
8
+ extend self
9
+
10
+ def register(renderer)
11
+ renderers.push renderer
12
+ end
13
+
14
+ def renderers
15
+ @renderers ||= []
16
+ end
17
+ end
18
+
19
+ SlidePostRenderers.register Renderers::SpecialParagraphRenderer
20
+ SlidePostRenderers.register Renderers::CommandLineRenderer
21
+ SlidePostRenderers.register Renderers::ColumnsRenderer.new(css_class: 'columns',
22
+ html_element: "h2", segments: 12)
23
+
24
+ end
@@ -0,0 +1,19 @@
1
+ require_relative 'renderers/html_with_pygments'
2
+
3
+ module Parade
4
+
5
+ module SlidePreRenderers
6
+ extend self
7
+
8
+ def register(renderer)
9
+ renderers.push renderer
10
+ end
11
+
12
+ def renderers
13
+ @renderers ||= []
14
+ end
15
+ end
16
+
17
+ SlidePreRenderers.register Renderers::HTMLwithPygments
18
+
19
+ end
@@ -1,3 +1,3 @@
1
1
  module Parade
2
- VERSION = '0.8.0'
2
+ VERSION = '0.8.1'
3
3
  end
@@ -0,0 +1,3 @@
1
+ @media screen {
2
+
3
+ }
@@ -1,6 +1,6 @@
1
1
  @media screen {
2
2
  body {
3
- font-size: 100%;
3
+ font-size: 100%;
4
4
  font-family: "Gill Sans", Helvetica, Arial, sans-serif;
5
5
  background:#333;
6
6
  overflow:hidden;
@@ -39,7 +39,7 @@
39
39
  border-top-right-radius: 3px;
40
40
  z-index: 2147483647; /* max, see http://www.puidokas.com/max-z-index/ */
41
41
  }
42
-
42
+
43
43
  #pauseScreen {
44
44
  background: rgba(0, 0, 0, 0.85);
45
45
  width: 100%;
@@ -104,9 +104,9 @@
104
104
 
105
105
  /* plain (non-bullet) text */
106
106
  .content > p {
107
- font-size: 2em;
107
+ font-size: 2em;
108
108
  margin: 1em;
109
- text-align: center;
109
+ text-align: center;
110
110
  }
111
111
 
112
112
  .content > pre {
@@ -139,6 +139,9 @@
139
139
  display: block;
140
140
  }
141
141
 
142
+ .content.columns .grid_12 p {
143
+ font-size: 200%;
144
+ }
142
145
 
143
146
  .content.columns .grid_6 p {
144
147
  font-size: 150%;
@@ -165,9 +168,9 @@
165
168
  /* numbered lists are numbered */
166
169
  .content ol {
167
170
  margin-left: 40px;
168
- font-size: 3em;
169
- text-align: left;
170
- padding-left: 40px;
171
+ font-size: 3em;
172
+ text-align: left;
173
+ padding-left: 40px;
171
174
  }
172
175
  .content ol > li {
173
176
  list-style: decimal;
@@ -180,9 +183,9 @@
180
183
  list-style: disc;
181
184
  }
182
185
  .content > ul {
183
- font-size: 3em;
184
- text-align: left;
185
- padding-left: 40px;
186
+ font-size: 3em;
187
+ text-align: left;
188
+ padding-left: 40px;
186
189
  }
187
190
  .content > ul > li {
188
191
  padding: .5em;
@@ -192,8 +195,8 @@
192
195
  /* ironically, normal lists have bullets and 'bullets' lists don't */
193
196
  .bullets > ul {
194
197
  list-style: none;
195
- font-size: 3em;
196
- padding-left: 0px;
198
+ font-size: 3em;
199
+ padding-left: 0px;
197
200
  }
198
201
  .bullets > ul > li {
199
202
  text-align: center;
@@ -243,7 +246,7 @@
243
246
  .subsection h1 {
244
247
  background: #008;
245
248
  color: #fff;
246
- padding: .25em;
249
+ padding: .25em;
247
250
  }
248
251
 
249
252
  .small {
@@ -1,11 +1,11 @@
1
- /*--------------------------------------------------------------------
1
+ /*--------------------------------------------------------------------
2
2
  Scripts for creating and manipulating custom menus based on standard <ul> markup
3
3
  Version: 3.0, 03.31.2009
4
4
 
5
5
  By: Maggie Costello Wachs (maggie@filamentgroup.com) and Scott Jehl (scott@filamentgroup.com)
6
- http://www.filamentgroup.com
7
- * reference articles: http://www.filamentgroup.com/lab/jquery_ipod_style_drilldown_menu/
8
-
6
+ http://www.filamentgroup.com
7
+ * reference articles: http://www.filamentgroup.com/lab/jquery_ipod_style_drilldown_menu/
8
+
9
9
  Copyright (c) 2009 Filament Group
10
10
  Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses.
11
11
  --------------------------------------------------------------------*/
@@ -14,531 +14,567 @@ Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL
14
14
  var allUIMenus = [];
15
15
 
16
16
  $.fn.menu = function(options){
17
- var caller = this;
18
- var options = options;
19
- var m = new Menu(caller, options);
20
- allUIMenus.push(m);
21
-
22
- $(this)
23
- .mousedown(function(){
24
- if (!m.menuOpen) { m.showLoading(); };
25
- })
26
- .click(function(){
27
- if (m.menuOpen == false) { m.showMenu(); }
28
- else { m.kill(); };
29
- return false;
30
- });
17
+ var caller = this;
18
+ var options = options;
19
+ var m = new Menu(caller, options);
20
+ allUIMenus.push(m);
21
+
22
+ $(this)
23
+ .mousedown(function(){
24
+ if (!m.menuOpen) { m.showLoading(); };
25
+ })
26
+ .click(function(){
27
+ debugger
28
+ if (m.menuOpen == false) { m.showMenu(); }
29
+ else { m.kill(); };
30
+ return false;
31
+ });
31
32
  };
32
33
 
33
34
  function Menu(caller, options){
34
- var menu = this;
35
- var caller = $(caller);
36
- var container = $('<div class="fg-menu-container ui-widget ui-widget-content ui-corner-all">'+options.content+'</div>');
37
-
38
- this.menuOpen = false;
39
- this.menuExists = false;
40
-
41
- var options = jQuery.extend({
42
- content: null,
43
- width: 180, // width of menu container, must be set or passed in to calculate widths of child menus
44
- maxHeight: 180, // max height of menu (if a drilldown: height does not include breadcrumb)
45
- positionOpts: {
46
- posX: 'left',
47
- posY: 'bottom',
48
- offsetX: 0,
49
- offsetY: 0,
50
- directionH: 'right',
51
- directionV: 'down',
52
- detectH: true, // do horizontal collision detection
53
- detectV: true, // do vertical collision detection
54
- linkToFront: false
55
- },
56
- showSpeed: 200, // show/hide speed in milliseconds
57
- callerOnState: 'ui-state-active', // class to change the appearance of the link/button when the menu is showing
58
- loadingState: 'ui-state-loading', // class added to the link/button while the menu is created
59
- linkHover: 'ui-state-hover', // class for menu option hover state
60
- linkHoverSecondary: 'li-hover', // alternate class, may be used for multi-level menus
61
- // ----- multi-level menu defaults -----
62
- crossSpeed: 200, // cross-fade speed for multi-level menus
63
- crumbDefaultText: 'Choose an option:',
64
- backLink: true, // in the ipod-style menu: instead of breadcrumbs, show only a 'back' link
65
- backLinkText: 'Back',
66
- flyOut: false, // multi-level menus are ipod-style by default; this parameter overrides to make a flyout instead
67
- flyOutOnState: 'ui-state-default',
68
- nextMenuLink: 'ui-icon-triangle-1-e', // class to style the link (specifically, a span within the link) used in the multi-level menu to show the next level
69
- topLinkText: 'All',
70
- nextCrumbLink: 'ui-icon-carat-1-e'
71
- }, options);
72
-
73
- var killAllMenus = function(){
74
- $.each(allUIMenus, function(i){
75
- if (allUIMenus[i].menuOpen) { allUIMenus[i].kill(); };
76
- });
77
- };
78
-
79
- this.kill = function(){
80
- caller
81
- .removeClass(options.loadingState)
82
- .removeClass('fg-menu-open')
83
- .removeClass(options.callerOnState);
84
- container.find('li').removeClass(options.linkHoverSecondary).find('a').removeClass(options.linkHover);
85
- if (options.flyOutOnState) { container.find('li a').removeClass(options.flyOutOnState); };
86
- if (options.callerOnState) { caller.removeClass(options.callerOnState); };
87
- if (container.is('.fg-menu-ipod')) { menu.resetDrilldownMenu(); };
88
- if (container.is('.fg-menu-flyout')) { menu.resetFlyoutMenu(); };
89
- container.parent().hide();
90
- menu.menuOpen = false;
91
- $(document).unbind('click', killAllMenus);
92
- $(document).unbind('keydown');
93
- };
94
-
95
- this.showLoading = function(){
96
- caller.addClass(options.loadingState);
97
- };
98
-
99
- this.showMenu = function(){
100
- killAllMenus();
101
- if (!menu.menuExists) { menu.create() };
102
- caller
103
- .addClass('fg-menu-open')
104
- .addClass(options.callerOnState);
105
- container.parent().show().click(function(){ menu.kill(); return false; });
106
- container.hide().slideDown(options.showSpeed).find('.fg-menu:eq(0)');
107
- menu.menuOpen = true;
108
- caller.removeClass(options.loadingState);
109
- $(document).click(killAllMenus);
110
-
111
- // assign key events
112
- $(document).keydown(function(event){
113
- var e;
114
- if (event.which !="") { e = event.which; }
115
- else if (event.charCode != "") { e = event.charCode; }
116
- else if (event.keyCode != "") { e = event.keyCode; }
117
-
118
- var menuType = ($(event.target).parents('div').is('.fg-menu-flyout')) ? 'flyout' : 'ipod' ;
119
-
120
- // vi bindings
121
- switch(e) {
122
- case 72: // left arrow
123
- if (menuType == 'flyout') {
124
- $(event.target).trigger('mouseout');
125
- if ($('.'+options.flyOutOnState).size() > 0) { $('.'+options.flyOutOnState).trigger('mouseover'); };
126
- };
127
-
128
- if (menuType == 'ipod') {
129
- $(event.target).trigger('mouseout');
130
- if ($('.fg-menu-footer').find('a').size() > 0) { $('.fg-menu-footer').find('a').trigger('click'); };
131
- if ($('.fg-menu-header').find('a').size() > 0) { $('.fg-menu-current-crumb').prev().find('a').trigger('click'); };
132
- if ($('.fg-menu-current').prev().is('.fg-menu-indicator')) {
133
- $('.fg-menu-current').prev().trigger('mouseover');
134
- };
135
- };
136
- return false;
137
- break;
138
-
139
- case 75: // up arrow
140
- if ($(event.target).is('.' + options.linkHover)) {
141
- var prevLink = $(event.target).parent().prev().find('a:eq(0)');
142
- if (prevLink.size() > 0) {
143
- $(event.target).trigger('mouseout');
144
- prevLink.trigger('mouseover');
145
- };
146
- }
147
- else { container.find('a:eq(0)').trigger('mouseover'); }
148
- return false;
149
- break;
150
-
151
- case 76: // right arrow
152
- if ($(event.target).is('.fg-menu-indicator')) {
153
- if (menuType == 'flyout') {
154
- $(event.target).next().find('a:eq(0)').trigger('mouseover');
155
- }
156
- else if (menuType == 'ipod') {
157
- $(event.target).trigger('click');
158
- setTimeout(function(){
159
- $(event.target).next().find('a:eq(0)').trigger('mouseover');
160
- }, options.crossSpeed);
161
- };
162
- };
163
- return false;
164
- break;
165
-
166
- case 74: // down arrow
167
- if ($(event.target).is('.' + options.linkHover)) {
168
- var nextLink = $(event.target).parent().next().find('a:eq(0)');
169
- if (nextLink.size() > 0) {
170
- $(event.target).trigger('mouseout');
171
- nextLink.trigger('mouseover');
172
- };
173
- }
174
- else { container.find('a:eq(0)').trigger('mouseover'); }
175
- return false;
176
- break;
177
-
178
- case 27: // escape
179
- killAllMenus();
180
- break;
181
-
182
- case 13: // enter
183
- if ($(event.target).is('.fg-menu-indicator') && menuType == 'ipod') {
184
- $(event.target).trigger('click');
185
- setTimeout(function(){
186
- $(event.target).next().find('a:eq(0)').trigger('mouseover');
187
- }, options.crossSpeed);
188
- };
189
- break;
190
- };
191
- });
192
- };
193
-
194
- this.create = function(){
195
- container.css({ width: options.width }).appendTo('body').find('ul:first').not('.fg-menu-breadcrumb').addClass('fg-menu');
196
- container.find('ul, li a').addClass('ui-corner-all');
197
-
198
- // aria roles & attributes
199
- container.find('ul').attr('role', 'menu').eq(0).attr('aria-activedescendant','active-menuitem').attr('aria-labelledby', caller.attr('id'));
200
- container.find('li').attr('role', 'menuitem');
201
- container.find('li:has(ul)').attr('aria-haspopup', 'true').find('ul').attr('aria-expanded', 'false');
202
- container.find('a').attr('tabindex', '-1');
203
-
204
- // when there are multiple levels of hierarchy, create flyout or drilldown menu
205
- if (container.find('ul').size() > 1) {
206
- if (options.flyOut) { menu.flyout(container, options); }
207
- else { menu.drilldown(container, options); }
208
- }
209
- else {
210
- container.find('a').click(function(){
211
- menu.chooseItem(this);
212
- return false;
213
- });
214
- };
215
-
216
- if (options.linkHover) {
217
- var allLinks = container.find('.fg-menu li a');
218
- allLinks.hover(
219
- function(){
220
- var menuitem = $(this);
221
- $('.'+options.linkHover).removeClass(options.linkHover).blur().parent().removeAttr('id');
222
- $(this).addClass(options.linkHover).focus().parent().attr('id','active-menuitem');
223
- },
224
- function(){
225
- $(this).removeClass(options.linkHover).blur().parent().removeAttr('id');
226
- }
227
- );
228
- };
229
-
230
- if (options.linkHoverSecondary) {
231
- container.find('.fg-menu li').hover(
232
- function(){
233
- $(this).siblings('li').removeClass(options.linkHoverSecondary);
234
- if (options.flyOutOnState) { $(this).siblings('li').find('a').removeClass(options.flyOutOnState); }
235
- $(this).addClass(options.linkHoverSecondary);
236
- },
237
- function(){ $(this).removeClass(options.linkHoverSecondary); }
238
- );
239
- };
240
-
241
- menu.setPosition(container, caller, options);
242
- menu.menuExists = true;
243
- };
244
-
245
- this.chooseItem = function(item){
246
- menu.kill();
247
- gotoSlide($(item).attr('rel'));
248
- $('#navmenu').hide();
249
- };
35
+ var menu = this;
36
+ var caller = $(caller);
37
+ var container = $('<div class="fg-menu-container ui-widget ui-widget-content ui-corner-all">'+options.content+'</div>');
38
+
39
+ this.menuOpen = false;
40
+ this.menuExists = false;
41
+
42
+ $.subscribe('navigation:show',$.proxy(function() {
43
+ this.showMenu();
44
+ },this));
45
+ $.subscribe('navigation:hidden',$.proxy(function() {
46
+ this.currentSelection = this.htmlContent.find('a:first');
47
+ this.killAllMenus();
48
+ },this));
49
+ $.subscribe('navigation:left',$.proxy(function() {
50
+ this.goLeft();
51
+ },this));
52
+ $.subscribe('navigation:right',$.proxy(function() {
53
+ this.goRight();
54
+ },this));
55
+ $.subscribe('navigation:down',$.proxy(function() {
56
+ this.goDown();
57
+ },this));
58
+ $.subscribe('navigation:up',$.proxy(function() {
59
+ this.goUp();
60
+ },this));
61
+ $.subscribe('navigation:selection',$.proxy(function() {
62
+ this.makeSelection();
63
+ },this));
64
+
65
+ var options = jQuery.extend({
66
+ content: null,
67
+ width: 180, // width of menu container, must be set or passed in to calculate widths of child menus
68
+ maxHeight: 180, // max height of menu (if a drilldown: height does not include breadcrumb)
69
+ positionOpts: {
70
+ posX: 'left',
71
+ posY: 'bottom',
72
+ offsetX: 0,
73
+ offsetY: 0,
74
+ directionH: 'right',
75
+ directionV: 'down',
76
+ detectH: true, // do horizontal collision detection
77
+ detectV: true, // do vertical collision detection
78
+ linkToFront: false
79
+ },
80
+ showSpeed: 200, // show/hide speed in milliseconds
81
+ callerOnState: 'ui-state-active', // class to change the appearance of the link/button when the menu is showing
82
+ loadingState: 'ui-state-loading', // class added to the link/button while the menu is created
83
+ linkHover: 'ui-state-hover', // class for menu option hover state
84
+ linkHoverSecondary: 'li-hover', // alternate class, may be used for multi-level menus
85
+ // ----- multi-level menu defaults -----
86
+ crossSpeed: 200, // cross-fade speed for multi-level menus
87
+ crumbDefaultText: 'Choose an option:',
88
+ backLink: true, // in the ipod-style menu: instead of breadcrumbs, show only a 'back' link
89
+ backLinkText: 'Back',
90
+ flyOut: false, // multi-level menus are ipod-style by default; this parameter overrides to make a flyout instead
91
+ flyOutOnState: 'ui-state-default',
92
+ nextMenuLink: 'ui-icon-triangle-1-e', // class to style the link (specifically, a span within the link) used in the multi-level menu to show the next level
93
+ topLinkText: 'All',
94
+ nextCrumbLink: 'ui-icon-carat-1-e'
95
+ }, options);
96
+
97
+ this.options = options;
98
+
99
+ this.killAllMenus = function() {
100
+ $.each(allUIMenus, function(i){
101
+ if (allUIMenus[i].menuOpen) { allUIMenus[i].kill(); };
102
+ });
103
+ }
104
+
105
+ this.kill = function(){
106
+ caller
107
+ .removeClass(options.loadingState)
108
+ .removeClass('fg-menu-open')
109
+ .removeClass(options.callerOnState);
110
+ container.find('li').removeClass(options.linkHoverSecondary).find('a').removeClass(options.linkHover);
111
+ if (options.flyOutOnState) { container.find('li a').removeClass(options.flyOutOnState); };
112
+ if (options.callerOnState) { caller.removeClass(options.callerOnState); };
113
+ if (container.is('.fg-menu-ipod')) { menu.resetDrilldownMenu(); };
114
+ if (container.is('.fg-menu-flyout')) { menu.resetFlyoutMenu(); };
115
+ container.parent().hide();
116
+ menu.menuOpen = false;
117
+ };
118
+
119
+ this.showLoading = function(){
120
+ caller.addClass(options.loadingState);
121
+ };
122
+
123
+ this.menuIsFlyout = function() { return true; }
124
+ this.menuIsIpod = function() { return false; }
125
+
126
+ this.currentSelectionIsMenu = function() {
127
+ return this.currentSelection.hasClass('fg-menu-indicator');
128
+ }
129
+
130
+ this.currentSelectionIsMenuItem = function() {
131
+ return !this.currentSelectionIsMenu();
132
+ }
133
+
134
+ this.goLeft = function() {
135
+
136
+ if (this.menuIsFlyout()) {
137
+ this.htmlContent.trigger('mouseout');
138
+ var parentItem = $('.'+this.options.flyOutOnState);
139
+
140
+ if (parentItem.length > 1) {
141
+ parentItem.trigger('mouseover');
142
+ this.currentSelection = $(parentItem[parentItem.length - 1]);
143
+ };
144
+ }
145
+
146
+ if (this.menuIsIpod()) {
147
+ this.htmlContent.trigger('mouseout');
148
+ if (this.htmlContent.find('.fg-menu-footer').find('a').size() > 0) { this.htmlContent.find('.fg-menu-footer').find('a').trigger('click'); };
149
+ if (this.htmlContent.find('.fg-menu-header').find('a').size() > 0) { this.htmlContent.find('.fg-menu-current-crumb').prev().find('a').trigger('click'); };
150
+ if (this.htmlContent.find('.fg-menu-current').prev().is('.fg-menu-indicator')) {
151
+ this.htmlContent.find('.fg-menu-current').prev().trigger('mouseover');
152
+ }
153
+ };
154
+ }
155
+
156
+ this.goRight = function() {
157
+ if (this.currentSelectionIsMenuItem()) { return; }
158
+
159
+ if (this.menuIsFlyout()) {
160
+ var newSelection = this.currentSelection.next().find('a:eq(0)');
161
+ newSelection.trigger('mouseover');
162
+ this.currentSelection = newSelection;
163
+ }
164
+
165
+ if (this.menuIsIpod()) {
166
+ this.currentSelection.trigger('click');
167
+ setTimeout(function(){
168
+ var newSelection = $(this.currentSelection).next().find('a:eq(0)');
169
+ newSelection.trigger('mouseover');
170
+ this.currentSelection = newSelection;
171
+ }, options.crossSpeed);
172
+ }
173
+ }
174
+
175
+ this.goDown = function() {
176
+
177
+ if (this.currentSelection) {
178
+ var nextLink = $(this.currentSelection).parent().next().find('a:eq(0)');
179
+ if (nextLink.size() > 0) {
180
+ $(this.currentSelection).trigger('mouseout');
181
+ nextLink.trigger('mouseover');
182
+ this.currentSelection = nextLink
183
+ };
184
+ }
185
+ else { container.find('a:eq(0)').trigger('mouseover'); }
186
+ }
187
+
188
+ this.goUp = function() {
189
+ if (this.currentSelection) {
190
+ var prevLink = $(this.currentSelection).parent().prev().find('a:eq(0)');
191
+ if (prevLink.size() > 0) {
192
+ $(this.currentSelection).trigger('mouseout');
193
+ prevLink.trigger('mouseover');
194
+ this.currentSelection = prevLink;
195
+ };
196
+ }
197
+ else { container.find('a:eq(0)').trigger('mouseover'); }
198
+
199
+ }
200
+
201
+ this.makeSelection = function() {
202
+ if ($(event.target).is('.fg-menu-indicator') && this.menuIsIpod()) {
203
+ $(event.target).trigger('click');
204
+ setTimeout(function(){
205
+ $(event.target).next().find('a:eq(0)').trigger('mouseover');
206
+ }, options.crossSpeed);
207
+ };
208
+ }
209
+
210
+ this.showMenu = function(){
211
+ this.killAllMenus();
212
+ if (!menu.menuExists) { menu.create() };
213
+ caller.addClass('fg-menu-open').addClass(options.callerOnState);
214
+ container.parent().show().click(function(){ menu.kill(); return false; });
215
+ container.hide().slideDown(options.showSpeed).find('.fg-menu:eq(0)');
216
+ menu.menuOpen = true;
217
+ caller.removeClass(options.loadingState);
218
+
219
+ this.htmlContent = $('.fg-menu');
220
+ this.currentSelection = this.htmlContent.find('a:first');
221
+ this.currentSelection.addClass(options.linkHover);
222
+ this.currentSelection.trigger('mouseover');
223
+ };
224
+
225
+ this.create = function(){
226
+ container.css({ width: options.width }).appendTo('body').find('ul:first').not('.fg-menu-breadcrumb').addClass('fg-menu');
227
+ container.find('ul, li a').addClass('ui-corner-all');
228
+
229
+ // aria roles & attributes
230
+ container.find('ul').attr('role', 'menu').eq(0).attr('aria-activedescendant','active-menuitem').attr('aria-labelledby', caller.attr('id'));
231
+ container.find('li').attr('role', 'menuitem');
232
+ container.find('li:has(ul)').attr('aria-haspopup', 'true').find('ul').attr('aria-expanded', 'false');
233
+ container.find('a').attr('tabindex', '-1');
234
+
235
+ // when there are multiple levels of hierarchy, create flyout or drilldown menu
236
+ if (container.find('ul').size() > 1) {
237
+ if (options.flyOut) { menu.flyout(container, options); }
238
+ else { menu.drilldown(container, options); }
239
+ }
240
+ else {
241
+ container.find('a').click(function(){
242
+ menu.chooseItem(this);
243
+ return false;
244
+ });
245
+ };
246
+
247
+ if (options.linkHover) {
248
+ var allLinks = container.find('.fg-menu li a');
249
+ allLinks.hover(
250
+ function(){
251
+ var menuitem = $(this);
252
+ $('.'+options.linkHover).removeClass(options.linkHover).blur().parent().removeAttr('id');
253
+ $(this).addClass(options.linkHover).focus().parent().attr('id','active-menuitem');
254
+ },
255
+ function(){
256
+ $(this).removeClass(options.linkHover).blur().parent().removeAttr('id');
257
+ }
258
+ );
259
+ };
260
+
261
+ if (options.linkHoverSecondary) {
262
+ container.find('.fg-menu li').hover(
263
+ function(){
264
+ $(this).siblings('li').removeClass(options.linkHoverSecondary);
265
+ if (options.flyOutOnState) { $(this).siblings('li').find('a').removeClass(options.flyOutOnState); }
266
+ $(this).addClass(options.linkHoverSecondary);
267
+ },
268
+ function(){ $(this).removeClass(options.linkHoverSecondary); }
269
+ );
270
+ };
271
+
272
+ menu.setPosition(container, caller, options);
273
+ menu.menuExists = true;
274
+ };
275
+
276
+ this.chooseItem = function(item){
277
+ $.publish('navigation:hidden');
278
+ var slideNumber = $(item).attr('rel');
279
+ $.publish('presentation:slide:location:change',slideNumber);
280
+ };
250
281
  };
251
282
 
252
283
  Menu.prototype.flyout = function(container, options) {
253
- var menu = this;
254
-
255
- this.resetFlyoutMenu = function(){
256
- var allLists = container.find('ul ul');
257
- allLists.removeClass('ui-widget-content').hide();
258
- };
259
-
260
- container.addClass('fg-menu-flyout').find('li:has(ul)').each(function(){
261
- var linkWidth = container.width();
262
- var showTimer, hideTimer;
263
- var allSubLists = $(this).find('ul');
264
-
265
- allSubLists.css({ left: linkWidth, width: linkWidth }).hide();
266
-
267
- $(this).find('a:eq(0)').addClass('fg-menu-indicator').html('<span>' + $(this).find('a:eq(0)').text() + '</span><span class="ui-icon '+options.nextMenuLink+'"></span>').hover(
268
- function(){
269
- clearTimeout(hideTimer);
270
- var subList = $(this).next();
271
- if (!fitVertical(subList, $(this).offset().top)) { subList.css({ top: 'auto', bottom: 0 }); };
272
- if (!fitHorizontal(subList, $(this).offset().left + 100)) { subList.css({ left: 'auto', right: linkWidth, 'z-index': 999 }); };
273
- showTimer = setTimeout(function(){
274
- subList.addClass('ui-widget-content').show(options.showSpeed).attr('aria-expanded', 'true');
275
- }, 300);
276
- },
277
- function(){
278
- clearTimeout(showTimer);
279
- var subList = $(this).next();
280
- hideTimer = setTimeout(function(){
281
- subList.removeClass('ui-widget-content').hide(options.showSpeed).attr('aria-expanded', 'false');
282
- }, 400);
283
- }
284
- );
285
-
286
- $(this).find('ul a').hover(
287
- function(){
288
- clearTimeout(hideTimer);
289
- if ($(this).parents('ul').prev().is('a.fg-menu-indicator')) {
290
- $(this).parents('ul').prev().addClass(options.flyOutOnState);
291
- }
292
- },
293
- function(){
294
- hideTimer = setTimeout(function(){
295
- allSubLists.hide(options.showSpeed);
296
- container.find(options.flyOutOnState).removeClass(options.flyOutOnState);
297
- }, 500);
298
- }
299
- );
300
- });
301
-
302
- container.find('a').click(function(){
303
- menu.chooseItem(this);
304
- return false;
305
- });
284
+ var menu = this;
285
+
286
+ this.resetFlyoutMenu = function(){
287
+ var allLists = container.find('ul ul');
288
+ allLists.removeClass('ui-widget-content').hide();
289
+ };
290
+
291
+ container.addClass('fg-menu-flyout').find('li:has(ul)').each(function(){
292
+ var linkWidth = container.width();
293
+ var showTimer, hideTimer;
294
+ var allSubLists = $(this).find('ul');
295
+
296
+ allSubLists.css({ left: linkWidth, width: linkWidth }).hide();
297
+
298
+ $(this).find('a:eq(0)').addClass('fg-menu-indicator').html('<span>' + $(this).find('a:eq(0)').text() + '</span><span class="ui-icon '+options.nextMenuLink+'"></span>').hover(
299
+ function(){
300
+ clearTimeout(hideTimer);
301
+ var subList = $(this).next();
302
+ // Previously this would make sure that the items rocked up past the top
303
+ // when the list was too long. I would rather the list run down the length
304
+ // of the page instead of not be accessible.
305
+ // TODO: make it scrollable or provide a better interface
306
+
307
+ // if (!fitVertical(subList, $(this).offset().top)) { subList.css({ top: 'auto', bottom: 0 }); };
308
+ if (!fitHorizontal(subList, $(this).offset().left + 100)) { subList.css({ left: 'auto', right: linkWidth, 'z-index': 999 }); };
309
+ showTimer = setTimeout(function(){
310
+ subList.addClass('ui-widget-content').show(options.showSpeed).attr('aria-expanded', 'true');
311
+ }, 300);
312
+ },
313
+ function(){
314
+ clearTimeout(showTimer);
315
+ var subList = $(this).next();
316
+ hideTimer = setTimeout(function(){
317
+ subList.removeClass('ui-widget-content').hide(options.showSpeed).attr('aria-expanded', 'false');
318
+ }, 400);
319
+ }
320
+ );
321
+
322
+ $(this).find('ul a').hover(
323
+ function(){
324
+ clearTimeout(hideTimer);
325
+ if ($(this).parents('ul').prev().is('a.fg-menu-indicator')) {
326
+ $(this).parents('ul').prev().addClass(options.flyOutOnState);
327
+ }
328
+ },
329
+ function(){
330
+ hideTimer = setTimeout(function(){
331
+ allSubLists.hide(options.showSpeed);
332
+ container.find(options.flyOutOnState).removeClass(options.flyOutOnState);
333
+ }, 500);
334
+ }
335
+ );
336
+ });
337
+
338
+ container.find('a').click(function(){
339
+ menu.chooseItem(this);
340
+ return false;
341
+ });
306
342
  };
307
343
 
308
344
 
309
345
  Menu.prototype.drilldown = function(container, options) {
310
- var menu = this;
311
- var topList = container.find('.fg-menu');
312
- var breadcrumb = $('<ul class="fg-menu-breadcrumb ui-widget-header ui-corner-all ui-helper-clearfix"></ul>');
313
- var crumbDefaultHeader = $('<li class="fg-menu-breadcrumb-text">'+options.crumbDefaultText+'</li>');
314
- var firstCrumbText = (options.backLink) ? options.backLinkText : options.topLinkText;
315
- var firstCrumbClass = (options.backLink) ? 'fg-menu-prev-list' : 'fg-menu-all-lists';
316
- var firstCrumbLinkClass = (options.backLink) ? 'ui-state-default ui-corner-all' : '';
317
- var firstCrumbIcon = (options.backLink) ? '<span class="ui-icon ui-icon-triangle-1-w"></span>' : '';
318
- var firstCrumb = $('<li class="'+firstCrumbClass+'"><a href="#" class="'+firstCrumbLinkClass+'">'+firstCrumbIcon+firstCrumbText+'</a></li>');
319
-
320
- container.addClass('fg-menu-ipod');
321
-
322
- if (options.backLink) { breadcrumb.addClass('fg-menu-footer').appendTo(container).hide(); }
323
- else { breadcrumb.addClass('fg-menu-header').prependTo(container); };
324
- breadcrumb.append(crumbDefaultHeader);
325
-
326
- var checkMenuHeight = function(el){
327
- if (el.height() > options.maxHeight) { el.addClass('fg-menu-scroll') };
328
- el.css({ height: options.maxHeight });
329
- };
330
-
331
- var resetChildMenu = function(el){ el.removeClass('fg-menu-scroll').removeClass('fg-menu-current').height('auto'); };
332
-
333
- this.resetDrilldownMenu = function(){
334
- $('.fg-menu-current').removeClass('fg-menu-current');
335
- topList.animate({ left: 0 }, options.crossSpeed, function(){
336
- $(this).find('ul').each(function(){
337
- $(this).hide();
338
- resetChildMenu($(this));
339
- });
340
- topList.addClass('fg-menu-current');
341
- });
342
- $('.fg-menu-all-lists').find('span').remove();
343
- breadcrumb.empty().append(crumbDefaultHeader);
344
- $('.fg-menu-footer').empty().hide();
345
- checkMenuHeight(topList);
346
- };
347
-
348
- topList
349
- .addClass('fg-menu-content fg-menu-current ui-widget-content ui-helper-clearfix')
350
- .css({ width: container.width() })
351
- .find('ul')
352
- .css({ width: container.width(), left: container.width() })
353
- .addClass('ui-widget-content')
354
- .hide();
355
- checkMenuHeight(topList);
356
-
357
- topList.find('a').each(function(){
358
- // if the link opens a child menu:
359
- if ($(this).next().is('ul')) {
360
- $(this)
361
- .addClass('fg-menu-indicator')
362
- .each(function(){ $(this).html('<span>' + $(this).text() + '</span><span class="ui-icon '+options.nextMenuLink+'"></span>'); })
363
- .click(function(){ // ----- show the next menu
364
- var nextList = $(this).next();
365
- var parentUl = $(this).parents('ul:eq(0)');
366
- var parentLeft = (parentUl.is('.fg-menu-content')) ? 0 : parseFloat(topList.css('left'));
367
- var nextLeftVal = Math.round(parentLeft - parseFloat(container.width()));
368
- var footer = $('.fg-menu-footer');
369
-
370
- // show next menu
371
- resetChildMenu(parentUl);
372
- checkMenuHeight(nextList);
373
- topList.animate({ left: nextLeftVal }, options.crossSpeed);
374
- nextList.show().addClass('fg-menu-current').attr('aria-expanded', 'true');
375
-
376
- var setPrevMenu = function(backlink){
377
- var b = backlink;
378
- var c = $('.fg-menu-current');
379
- var prevList = c.parents('ul:eq(0)');
380
- c.hide().attr('aria-expanded', 'false');
381
- resetChildMenu(c);
382
- checkMenuHeight(prevList);
383
- prevList.addClass('fg-menu-current').attr('aria-expanded', 'true');
384
- if (prevList.hasClass('fg-menu-content')) { b.remove(); footer.hide(); };
385
- };
386
-
387
- // initialize "back" link
388
- if (options.backLink) {
389
- if (footer.find('a').size() == 0) {
390
- footer.show();
391
- $('<a href="#"><span class="ui-icon ui-icon-triangle-1-w"></span> <span>Back</span></a>')
392
- .appendTo(footer)
393
- .click(function(){ // ----- show the previous menu
394
- var b = $(this);
395
- var prevLeftVal = parseFloat(topList.css('left')) + container.width();
396
- topList.animate({ left: prevLeftVal }, options.crossSpeed, function(){
397
- setPrevMenu(b);
398
- });
399
- return false;
400
- });
401
- }
402
- }
403
- // or initialize top breadcrumb
404
- else {
405
- if (breadcrumb.find('li').size() == 1){
406
- breadcrumb.empty().append(firstCrumb);
407
- firstCrumb.find('a').click(function(){
408
- menu.resetDrilldownMenu();
409
- return false;
410
- });
411
- }
412
- $('.fg-menu-current-crumb').removeClass('fg-menu-current-crumb');
413
- var crumbText = $(this).find('span:eq(0)').text();
414
- var newCrumb = $('<li class="fg-menu-current-crumb"><a href="javascript://" class="fg-menu-crumb">'+crumbText+'</a></li>');
415
- newCrumb
416
- .appendTo(breadcrumb)
417
- .find('a').click(function(){
418
- if ($(this).parent().is('.fg-menu-current-crumb')){
419
- menu.chooseItem(this);
420
- }
421
- else {
422
- var newLeftVal = - ($('.fg-menu-current').parents('ul').size() - 1) * 180;
423
- topList.animate({ left: newLeftVal }, options.crossSpeed, function(){
424
- setPrevMenu();
425
- });
426
-
427
- // make this the current crumb, delete all breadcrumbs after this one, and navigate to the relevant menu
428
- $(this).parent().addClass('fg-menu-current-crumb').find('span').remove();
429
- $(this).parent().nextAll().remove();
430
- };
431
- return false;
432
- });
433
- newCrumb.prev().append(' <span class="ui-icon '+options.nextCrumbLink+'"></span>');
434
- };
435
- return false;
436
- });
437
- }
438
- // if the link is a leaf node (doesn't open a child menu)
439
- else {
440
- $(this).click(function(){
441
- menu.chooseItem(this);
442
- return false;
443
- });
444
- };
445
- });
346
+ var menu = this;
347
+ var topList = container.find('.fg-menu');
348
+ var breadcrumb = $('<ul class="fg-menu-breadcrumb ui-widget-header ui-corner-all ui-helper-clearfix"></ul>');
349
+ var crumbDefaultHeader = $('<li class="fg-menu-breadcrumb-text">'+options.crumbDefaultText+'</li>');
350
+ var firstCrumbText = (options.backLink) ? options.backLinkText : options.topLinkText;
351
+ var firstCrumbClass = (options.backLink) ? 'fg-menu-prev-list' : 'fg-menu-all-lists';
352
+ var firstCrumbLinkClass = (options.backLink) ? 'ui-state-default ui-corner-all' : '';
353
+ var firstCrumbIcon = (options.backLink) ? '<span class="ui-icon ui-icon-triangle-1-w"></span>' : '';
354
+ var firstCrumb = $('<li class="'+firstCrumbClass+'"><a href="#" class="'+firstCrumbLinkClass+'">'+firstCrumbIcon+firstCrumbText+'</a></li>');
355
+
356
+ container.addClass('fg-menu-ipod');
357
+
358
+ if (options.backLink) { breadcrumb.addClass('fg-menu-footer').appendTo(container).hide(); }
359
+ else { breadcrumb.addClass('fg-menu-header').prependTo(container); };
360
+ breadcrumb.append(crumbDefaultHeader);
361
+
362
+ var checkMenuHeight = function(el){
363
+ if (el.height() > options.maxHeight) { el.addClass('fg-menu-scroll') };
364
+ el.css({ height: options.maxHeight });
365
+ };
366
+
367
+ var resetChildMenu = function(el){ el.removeClass('fg-menu-scroll').removeClass('fg-menu-current').height('auto'); };
368
+
369
+ this.resetDrilldownMenu = function(){
370
+ $('.fg-menu-current').removeClass('fg-menu-current');
371
+ topList.animate({ left: 0 }, options.crossSpeed, function(){
372
+ $(this).find('ul').each(function(){
373
+ $(this).hide();
374
+ resetChildMenu($(this));
375
+ });
376
+ topList.addClass('fg-menu-current');
377
+ });
378
+ $('.fg-menu-all-lists').find('span').remove();
379
+ breadcrumb.empty().append(crumbDefaultHeader);
380
+ $('.fg-menu-footer').empty().hide();
381
+ checkMenuHeight(topList);
382
+ };
383
+
384
+ topList
385
+ .addClass('fg-menu-content fg-menu-current ui-widget-content ui-helper-clearfix')
386
+ .css({ width: container.width() })
387
+ .find('ul')
388
+ .css({ width: container.width(), left: container.width() })
389
+ .addClass('ui-widget-content')
390
+ .hide();
391
+ checkMenuHeight(topList);
392
+
393
+ topList.find('a').each(function(){
394
+ // if the link opens a child menu:
395
+ if ($(this).next().is('ul')) {
396
+ $(this)
397
+ .addClass('fg-menu-indicator')
398
+ .each(function(){ $(this).html('<span>' + $(this).text() + '</span><span class="ui-icon '+options.nextMenuLink+'"></span>'); })
399
+ .click(function(){ // ----- show the next menu
400
+ var nextList = $(this).next();
401
+ var parentUl = $(this).parents('ul:eq(0)');
402
+ var parentLeft = (parentUl.is('.fg-menu-content')) ? 0 : parseFloat(topList.css('left'));
403
+ var nextLeftVal = Math.round(parentLeft - parseFloat(container.width()));
404
+ var footer = $('.fg-menu-footer');
405
+
406
+ // show next menu
407
+ resetChildMenu(parentUl);
408
+ checkMenuHeight(nextList);
409
+ topList.animate({ left: nextLeftVal }, options.crossSpeed);
410
+ nextList.show().addClass('fg-menu-current').attr('aria-expanded', 'true');
411
+
412
+ var setPrevMenu = function(backlink){
413
+ var b = backlink;
414
+ var c = $('.fg-menu-current');
415
+ var prevList = c.parents('ul:eq(0)');
416
+ c.hide().attr('aria-expanded', 'false');
417
+ resetChildMenu(c);
418
+ checkMenuHeight(prevList);
419
+ prevList.addClass('fg-menu-current').attr('aria-expanded', 'true');
420
+ if (prevList.hasClass('fg-menu-content')) { b.remove(); footer.hide(); };
421
+ };
422
+
423
+ // initialize "back" link
424
+ if (options.backLink) {
425
+ if (footer.find('a').size() == 0) {
426
+ footer.show();
427
+ $('<a href="#"><span class="ui-icon ui-icon-triangle-1-w"></span> <span>Back</span></a>')
428
+ .appendTo(footer)
429
+ .click(function(){ // ----- show the previous menu
430
+ var b = $(this);
431
+ var prevLeftVal = parseFloat(topList.css('left')) + container.width();
432
+ topList.animate({ left: prevLeftVal }, options.crossSpeed, function(){
433
+ setPrevMenu(b);
434
+ });
435
+ return false;
436
+ });
437
+ }
438
+ }
439
+ // or initialize top breadcrumb
440
+ else {
441
+ if (breadcrumb.find('li').size() == 1){
442
+ breadcrumb.empty().append(firstCrumb);
443
+ firstCrumb.find('a').click(function(){
444
+ menu.resetDrilldownMenu();
445
+ return false;
446
+ });
447
+ }
448
+ $('.fg-menu-current-crumb').removeClass('fg-menu-current-crumb');
449
+ var crumbText = $(this).find('span:eq(0)').text();
450
+ var newCrumb = $('<li class="fg-menu-current-crumb"><a href="javascript://" class="fg-menu-crumb">'+crumbText+'</a></li>');
451
+ newCrumb
452
+ .appendTo(breadcrumb)
453
+ .find('a').click(function(){
454
+ if ($(this).parent().is('.fg-menu-current-crumb')){
455
+ menu.chooseItem(this);
456
+ }
457
+ else {
458
+ var newLeftVal = - ($('.fg-menu-current').parents('ul').size() - 1) * 180;
459
+ topList.animate({ left: newLeftVal }, options.crossSpeed, function(){
460
+ setPrevMenu();
461
+ });
462
+
463
+ // make this the current crumb, delete all breadcrumbs after this one, and navigate to the relevant menu
464
+ $(this).parent().addClass('fg-menu-current-crumb').find('span').remove();
465
+ $(this).parent().nextAll().remove();
466
+ };
467
+ return false;
468
+ });
469
+ newCrumb.prev().append(' <span class="ui-icon '+options.nextCrumbLink+'"></span>');
470
+ };
471
+ return false;
472
+ });
473
+ }
474
+ // if the link is a leaf node (doesn't open a child menu)
475
+ else {
476
+ $(this).click(function(){
477
+ menu.chooseItem(this);
478
+ return false;
479
+ });
480
+ };
481
+ });
446
482
  };
447
483
 
448
484
 
449
485
  /* Menu.prototype.setPosition parameters (defaults noted with *):
450
- referrer = the link (or other element) used to show the overlaid object
451
- settings = can override the defaults:
452
- - posX/Y: where the top left corner of the object should be positioned in relation to its referrer.
453
- X: left*, center, right
454
- Y: top, center, bottom*
455
- - offsetX/Y: the number of pixels to be offset from the x or y position. Can be a positive or negative number.
456
- - directionH/V: where the entire menu should appear in relation to its referrer.
457
- Horizontal: left*, right
458
- Vertical: up, down*
459
- - detectH/V: detect the viewport horizontally / vertically
460
- - linkToFront: copy the menu link and place it on top of the menu (visual effect to make it look like it overlaps the object) */
461
-
462
- Menu.prototype.setPosition = function(widget, caller, options) {
463
- var el = widget;
464
- var referrer = caller;
465
- var dims = {
466
- refX: referrer.offset().left,
467
- refY: referrer.offset().top,
468
- refW: referrer.getTotalWidth(),
469
- refH: referrer.getTotalHeight()
470
- };
471
- var options = options;
472
- var xVal, yVal;
473
-
474
- var helper = $('<div class="positionHelper"></div>');
475
- helper.css({ position: 'absolute', left: dims.refX, top: dims.refY, width: dims.refW, height: dims.refH });
476
- el.wrap(helper);
477
-
478
- // get X pos
479
- switch(options.positionOpts.posX) {
480
- case 'left': xVal = 0;
481
- break;
482
- case 'center': xVal = dims.refW / 2;
483
- break;
484
- case 'right': xVal = dims.refW;
485
- break;
486
- };
487
-
488
- // get Y pos
489
- switch(options.positionOpts.posY) {
490
- case 'top': yVal = 0;
491
- break;
492
- case 'center': yVal = dims.refH / 2;
493
- break;
494
- case 'bottom': yVal = dims.refH;
495
- break;
496
- };
497
-
498
- // add the offsets (zero by default)
499
- xVal += options.positionOpts.offsetX;
500
- yVal += options.positionOpts.offsetY;
501
-
502
- // position the object vertically
503
- if (options.positionOpts.directionV == 'up') {
504
- el.css({ top: 'auto', bottom: yVal });
505
- if (options.positionOpts.detectV && !fitVertical(el)) {
506
- el.css({ bottom: 'auto', top: yVal });
507
- }
508
- }
509
- else {
510
- el.css({ bottom: 'auto', top: yVal });
511
- if (options.positionOpts.detectV && !fitVertical(el)) {
512
- el.css({ top: 'auto', bottom: yVal });
513
- }
514
- };
515
-
516
- // and horizontally
517
- if (options.positionOpts.directionH == 'left') {
518
- el.css({ left: 'auto', right: xVal });
519
- if (options.positionOpts.detectH && !fitHorizontal(el)) {
520
- el.css({ right: 'auto', left: xVal });
521
- }
522
- }
523
- else {
524
- el.css({ right: 'auto', left: xVal });
525
- if (options.positionOpts.detectH && !fitHorizontal(el)) {
526
- el.css({ left: 'auto', right: xVal });
527
- }
528
- };
529
-
530
- // if specified, clone the referring element and position it so that it appears on top of the menu
531
- if (options.positionOpts.linkToFront) {
532
- referrer.clone().addClass('linkClone').css({
533
- position: 'absolute',
534
- top: 0,
535
- right: 'auto',
536
- bottom: 'auto',
537
- left: 0,
538
- width: referrer.width(),
539
- height: referrer.height()
540
- }).insertAfter(el);
541
- };
486
+ referrer = the link (or other element) used to show the overlaid object
487
+ settings = can override the defaults:
488
+ - posX/Y: where the top left corner of the object should be positioned in relation to its referrer.
489
+ X: left*, center, right
490
+ Y: top, center, bottom*
491
+ - offsetX/Y: the number of pixels to be offset from the x or y position. Can be a positive or negative number.
492
+ - directionH/V: where the entire menu should appear in relation to its referrer.
493
+ Horizontal: left*, right
494
+ Vertical: up, down*
495
+ - detectH/V: detect the viewport horizontally / vertically
496
+ - linkToFront: copy the menu link and place it on top of the menu (visual effect to make it look like it overlaps the object) */
497
+
498
+ Menu.prototype.setPosition = function(widget, caller, options) {
499
+ var el = widget;
500
+ var referrer = caller;
501
+ var dims = {
502
+ refX: referrer.offset().left,
503
+ refY: referrer.offset().top,
504
+ refW: referrer.getTotalWidth(),
505
+ refH: referrer.getTotalHeight()
506
+ };
507
+ var options = options;
508
+ var xVal, yVal;
509
+
510
+ var helper = $('<div class="positionHelper"></div>');
511
+ helper.css({ position: 'absolute', left: dims.refX, top: dims.refY, width: dims.refW, height: dims.refH });
512
+ el.wrap(helper);
513
+
514
+ // get X pos
515
+ switch(options.positionOpts.posX) {
516
+ case 'left': xVal = 0;
517
+ break;
518
+ case 'center': xVal = dims.refW / 2;
519
+ break;
520
+ case 'right': xVal = dims.refW;
521
+ break;
522
+ };
523
+
524
+ // get Y pos
525
+ switch(options.positionOpts.posY) {
526
+ case 'top': yVal = 0;
527
+ break;
528
+ case 'center': yVal = dims.refH / 2;
529
+ break;
530
+ case 'bottom': yVal = dims.refH;
531
+ break;
532
+ };
533
+
534
+ // add the offsets (zero by default)
535
+ xVal += options.positionOpts.offsetX;
536
+ yVal += options.positionOpts.offsetY;
537
+
538
+ // position the object vertically
539
+ if (options.positionOpts.directionV == 'up') {
540
+ el.css({ top: 'auto', bottom: yVal });
541
+ if (options.positionOpts.detectV && !fitVertical(el)) {
542
+ el.css({ bottom: 'auto', top: yVal });
543
+ }
544
+ }
545
+ else {
546
+ el.css({ bottom: 'auto', top: yVal });
547
+ if (options.positionOpts.detectV && !fitVertical(el)) {
548
+ el.css({ top: 'auto', bottom: yVal });
549
+ }
550
+ };
551
+
552
+ // and horizontally
553
+ if (options.positionOpts.directionH == 'left') {
554
+ el.css({ left: 'auto', right: xVal });
555
+ if (options.positionOpts.detectH && !fitHorizontal(el)) {
556
+ el.css({ right: 'auto', left: xVal });
557
+ }
558
+ }
559
+ else {
560
+ el.css({ right: 'auto', left: xVal });
561
+ if (options.positionOpts.detectH && !fitHorizontal(el)) {
562
+ el.css({ left: 'auto', right: xVal });
563
+ }
564
+ };
565
+
566
+ // if specified, clone the referring element and position it so that it appears on top of the menu
567
+ if (options.positionOpts.linkToFront) {
568
+ referrer.clone().addClass('linkClone').css({
569
+ position: 'absolute',
570
+ top: 0,
571
+ right: 'auto',
572
+ bottom: 'auto',
573
+ left: 0,
574
+ width: referrer.width(),
575
+ height: referrer.height()
576
+ }).insertAfter(el);
577
+ };
542
578
  };
543
579
 
544
580
 
@@ -547,99 +583,99 @@ Menu.prototype.setPosition = function(widget, caller, options) {
547
583
  function sortBigToSmall(a, b) { return b - a; };
548
584
 
549
585
  jQuery.fn.getTotalWidth = function(){
550
- return $(this).width() + parseInt($(this).css('paddingRight')) + parseInt($(this).css('paddingLeft')) + parseInt($(this).css('borderRightWidth')) + parseInt($(this).css('borderLeftWidth'));
586
+ return $(this).width() + parseInt($(this).css('paddingRight')) + parseInt($(this).css('paddingLeft')) + parseInt($(this).css('borderRightWidth')) + parseInt($(this).css('borderLeftWidth'));
551
587
  };
552
588
 
553
589
  jQuery.fn.getTotalHeight = function(){
554
- return $(this).height() + parseInt($(this).css('paddingTop')) + parseInt($(this).css('paddingBottom')) + parseInt($(this).css('borderTopWidth')) + parseInt($(this).css('borderBottomWidth'));
590
+ return $(this).height() + parseInt($(this).css('paddingTop')) + parseInt($(this).css('paddingBottom')) + parseInt($(this).css('borderTopWidth')) + parseInt($(this).css('borderBottomWidth'));
555
591
  };
556
592
 
557
593
  function getScrollTop(){
558
- return self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
594
+ return self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
559
595
  };
560
596
 
561
597
  function getScrollLeft(){
562
- return self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
598
+ return self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
563
599
  };
564
600
 
565
601
  function getWindowHeight(){
566
- var de = document.documentElement;
567
- return self.innerHeight || (de && de.clientHeight) || document.body.clientHeight;
602
+ var de = document.documentElement;
603
+ return self.innerHeight || (de && de.clientHeight) || document.body.clientHeight;
568
604
  };
569
605
 
570
606
  function getWindowWidth(){
571
- var de = document.documentElement;
572
- return self.innerWidth || (de && de.clientWidth) || document.body.clientWidth;
607
+ var de = document.documentElement;
608
+ return self.innerWidth || (de && de.clientWidth) || document.body.clientWidth;
573
609
  };
574
610
 
575
611
  /* Utilities to test whether an element will fit in the viewport
576
- Parameters:
577
- el = element to position, required
578
- leftOffset / topOffset = optional parameter if the offset cannot be calculated (i.e., if the object is in the DOM but is set to display: 'none') */
579
-
612
+ Parameters:
613
+ el = element to position, required
614
+ leftOffset / topOffset = optional parameter if the offset cannot be calculated (i.e., if the object is in the DOM but is set to display: 'none') */
615
+
580
616
  function fitHorizontal(el, leftOffset){
581
- var leftVal = parseInt(leftOffset) || $(el).offset().left;
582
- return (leftVal + $(el).width() <= getWindowWidth() + getScrollLeft() && leftVal - getScrollLeft() >= 0);
617
+ var leftVal = parseInt(leftOffset) || $(el).offset().left;
618
+ return (leftVal + $(el).width() <= getWindowWidth() + getScrollLeft() && leftVal - getScrollLeft() >= 0);
583
619
  };
584
620
 
585
621
  function fitVertical(el, topOffset){
586
- var topVal = parseInt(topOffset) || $(el).offset().top;
587
- return (topVal + $(el).height() <= getWindowHeight() + getScrollTop() && topVal - getScrollTop() >= 0);
622
+ var topVal = parseInt(topOffset) || $(el).offset().top;
623
+ return (topVal + $(el).height() <= getWindowHeight() + getScrollTop() && topVal - getScrollTop() >= 0);
588
624
  };
589
625
 
590
- /*--------------------------------------------------------------------
626
+ /*--------------------------------------------------------------------
591
627
  * javascript method: "pxToEm"
592
628
  * by:
593
- Scott Jehl (scott@filamentgroup.com)
629
+ Scott Jehl (scott@filamentgroup.com)
594
630
  Maggie Wachs (maggie@filamentgroup.com)
595
631
  http://www.filamentgroup.com
596
632
  *
597
633
  * Copyright (c) 2008 Filament Group
598
634
  * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses.
599
635
  *
600
- * Description: Extends the native Number and String objects with pxToEm method. pxToEm converts a pixel value to ems depending on inherited font size.
636
+ * Description: Extends the native Number and String objects with pxToEm method. pxToEm converts a pixel value to ems depending on inherited font size.
601
637
  * Article: http://www.filamentgroup.com/lab/retaining_scalable_interfaces_with_pixel_to_em_conversion/
602
- * Demo: http://www.filamentgroup.com/examples/pxToEm/
603
- *
604
- * Options:
605
- scope: string or jQuery selector for font-size scoping
606
- reverse: Boolean, true reverses the conversion to em-px
607
- * Dependencies: jQuery library
638
+ * Demo: http://www.filamentgroup.com/examples/pxToEm/
639
+ *
640
+ * Options:
641
+ scope: string or jQuery selector for font-size scoping
642
+ reverse: Boolean, true reverses the conversion to em-px
643
+ * Dependencies: jQuery library
608
644
  * Usage Example: myPixelValue.pxToEm(); or myPixelValue.pxToEm({'scope':'#navigation', reverse: true});
609
645
  *
610
- * Version: 2.0, 08.01.2008
646
+ * Version: 2.0, 08.01.2008
611
647
  * Changelog:
612
- * 08.02.2007 initial Version 1.0
613
- * 08.01.2008 - fixed font-size calculation for IE
648
+ * 08.02.2007 initial Version 1.0
649
+ * 08.01.2008 - fixed font-size calculation for IE
614
650
  --------------------------------------------------------------------*/
615
651
 
616
652
  Number.prototype.pxToEm = String.prototype.pxToEm = function(settings){
617
- //set defaults
618
- settings = jQuery.extend({
619
- scope: 'body',
620
- reverse: false
621
- }, settings);
622
-
623
- var pxVal = (this == '') ? 0 : parseFloat(this);
624
- var scopeVal;
625
- var getWindowWidth = function(){
626
- var de = document.documentElement;
627
- return self.innerWidth || (de && de.clientWidth) || document.body.clientWidth;
628
- };
629
-
630
- /* When a percentage-based font-size is set on the body, IE returns that percent of the window width as the font-size.
631
- For example, if the body font-size is 62.5% and the window width is 1000px, IE will return 625px as the font-size.
632
- When this happens, we calculate the correct body font-size (%) and multiply it by 16 (the standard browser font size)
633
- to get an accurate em value. */
634
-
635
- if (settings.scope == 'body' && $.browser.msie && (parseFloat($('body').css('font-size')) / getWindowWidth()).toFixed(1) > 0.0) {
636
- var calcFontSize = function(){
637
- return (parseFloat($('body').css('font-size'))/getWindowWidth()).toFixed(3) * 16;
638
- };
639
- scopeVal = calcFontSize();
640
- }
641
- else { scopeVal = parseFloat(jQuery(settings.scope).css("font-size")); };
642
-
643
- var result = (settings.reverse == true) ? (pxVal * scopeVal).toFixed(2) + 'px' : (pxVal / scopeVal).toFixed(2) + 'em';
644
- return result;
653
+ //set defaults
654
+ settings = jQuery.extend({
655
+ scope: 'body',
656
+ reverse: false
657
+ }, settings);
658
+
659
+ var pxVal = (this == '') ? 0 : parseFloat(this);
660
+ var scopeVal;
661
+ var getWindowWidth = function(){
662
+ var de = document.documentElement;
663
+ return self.innerWidth || (de && de.clientWidth) || document.body.clientWidth;
664
+ };
665
+
666
+ /* When a percentage-based font-size is set on the body, IE returns that percent of the window width as the font-size.
667
+ For example, if the body font-size is 62.5% and the window width is 1000px, IE will return 625px as the font-size.
668
+ When this happens, we calculate the correct body font-size (%) and multiply it by 16 (the standard browser font size)
669
+ to get an accurate em value. */
670
+
671
+ if (settings.scope == 'body' && $.browser.msie && (parseFloat($('body').css('font-size')) / getWindowWidth()).toFixed(1) > 0.0) {
672
+ var calcFontSize = function(){
673
+ return (parseFloat($('body').css('font-size'))/getWindowWidth()).toFixed(3) * 16;
674
+ };
675
+ scopeVal = calcFontSize();
676
+ }
677
+ else { scopeVal = parseFloat(jQuery(settings.scope).css("font-size")); };
678
+
679
+ var result = (settings.reverse == true) ? (pxVal * scopeVal).toFixed(2) + 'px' : (pxVal / scopeVal).toFixed(2) + 'em';
680
+ return result;
645
681
  };