switch_access-rails 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1007 @@
1
+ ###
2
+ Switch Access for webpages
3
+ (c) 2012 Leif Ringstad
4
+ Dual-licensed under GPL or commercial license (LICENSE and LICENSE.GPL)
5
+ Source: http://github.com/leifcr/switch_access
6
+ v 1.1.4
7
+ ###
8
+
9
+ SwitchAccessCommon =
10
+ generateRandomUUID: ->
11
+ "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace /[xy]/g, (c) ->
12
+ r = Math.random() * 16 | 0
13
+ v = (if c is "x" then r else (r & 0x3 | 0x8))
14
+ v.toString 16
15
+ options:
16
+ ###
17
+ Element highlighting using the built in Highlighter object feature
18
+ ###
19
+ highlighter:
20
+ ###
21
+ Use highlighter div element for each element. A div is positioned absolute
22
+ around the element and shown/hidden accordingly
23
+ Default: true
24
+ ###
25
+ use: true
26
+
27
+ ###
28
+ Additional content for the highlighter
29
+ Note: The content is placed within every highlighter and multiple
30
+ highlighters can be visible at the same time. It is best to not
31
+ use IDs on elements placed inside the highlighter, to avoid duplicate
32
+ IDs on a page
33
+ Default: ""
34
+ ###
35
+ content: ""
36
+
37
+ ###
38
+ Class for the highlighter
39
+ Default: "highlighter"
40
+ ###
41
+ class: "highlighter"
42
+
43
+ ###
44
+ The class when a highlighter is active/currently selected
45
+ Default: "current"
46
+ ###
47
+ current_class: "current"
48
+
49
+ ###
50
+ The class when set on a highlighter when activated action is triggered
51
+ Note: only usable if options.visual.delay_before_activating_element is > 0
52
+ Default: "activate"
53
+ ###
54
+ activate_class: "activate"
55
+
56
+ ###
57
+ Margin between the highlighter and the element
58
+ Default: 5
59
+ ###
60
+ margin_to_element: 5
61
+
62
+ ###
63
+ Selector to set size on. (Change in case you have content inside the highlighter you wish to highlight)
64
+ ###
65
+ selector_for_set_to_size: ".highlighter"
66
+
67
+ ###
68
+ Use CSS watch to watch the element for changes in position and dimensions
69
+ This is only needed if you have javascript or other DOM elements
70
+ that might change the position or size of a switch-enabled element
71
+ Default: false
72
+ ###
73
+ watch_for_resize: false
74
+ # use_size_position_check: true
75
+
76
+ ###
77
+ The ID for the holder for all highlighters. Unlikely to need changing
78
+ Default: "sw-highlighter-holder"
79
+ ###
80
+ holder_id: "sw-highlighter-holder"
81
+
82
+ ###
83
+ Options specific to highlighting
84
+ ###
85
+ highlight:
86
+ ###
87
+ Options specifict to highlighting a switch-element
88
+ ###
89
+ element:
90
+ ###
91
+ The class when a element is active/currently selected
92
+ Default: "current"
93
+ ###
94
+ current_class: "current"
95
+
96
+ ###
97
+ The class when set on a switch-element when activated
98
+ action is triggered
99
+ Note: options.visual.delay_before_activating_element must
100
+ be greater than 0
101
+ Default: "activate"
102
+ ###
103
+ activate_class: "activate"
104
+
105
+ debug: false
106
+
107
+ ###
108
+ Internal options, but can be changed if needed
109
+ ###
110
+ internal:
111
+ ###
112
+ The data attribute for the unique ID on each element and switch highlighter
113
+ Default: "sw-elem"
114
+ ###
115
+ unique_element_data_attribute: "sw-elem"
116
+
117
+ ###
118
+ Set a unique class on each element
119
+ Default: false
120
+ ###
121
+ set_unique_element_class: false
122
+
123
+ ###
124
+ Actions (Enumish)
125
+ These should not be overridden, as they are used internally
126
+ ###
127
+ actions:
128
+ none: 0
129
+ moved_to_next_element: 1
130
+ moved_to_next_level: 2
131
+ moved_to_previous_level: 3
132
+ triggered_action: 10
133
+ triggered_delayed_action: 11
134
+ stayed_at_element: 20
135
+
136
+
137
+
138
+ class SwitchAccess
139
+ constructor: (options) ->
140
+ # return existing instance if already initialized, as there should only be one instance of Switch Access on a page
141
+ if typeof (window['__switch_access_sci']) != "undefined" && window['__switch_access_sci'] != null
142
+ window.__switch_access_sci.setoptions(options)
143
+ return window.__switch_access_sci
144
+
145
+ window.__switch_access_sci = this
146
+ ###
147
+ Options
148
+ ###
149
+ @options =
150
+ ###
151
+ Switch/Key settings
152
+ ###
153
+ switches:
154
+
155
+ ###
156
+ The number of switches 0 = disable, 1 = single switch, 2 = two switches
157
+ Default: 2
158
+ ###
159
+ number_of_switches: 0
160
+
161
+ ###
162
+ Array for the keycodes to use as single switch (Multiple keycodes possible)
163
+ Default: [32, 13] (32 = 'Space', 13 = 'Enter')
164
+ ###
165
+ keys_1: [32, 13] # Space / Enter
166
+
167
+ ###
168
+ Array of two arrays for the keys to use as two switches
169
+ Default: [[32, 9], [13]] (9 = 'Tab, 32 = 'Space', 13 = 'Enter')
170
+ ###
171
+ keys_2: [[9, 32], [13]] # Tab + Space / Enter
172
+
173
+ #keys_3: # forward/backward and select
174
+
175
+ ###
176
+ Time for single switch scanning to move from element to element
177
+ Default: 1500 milliseconds
178
+ ###
179
+ single_switch_move_time: 1500
180
+
181
+ ###
182
+ If the single switch movement should restart/go to index 0 when restarted
183
+ Default: true
184
+ ###
185
+ single_switch_restart_on_activate: true
186
+
187
+ ###
188
+ Time after "triggering" a element to it's activated
189
+ Default: 0
190
+ ###
191
+ delay_before_activating_element: 0
192
+
193
+ ###
194
+ Delay before an keypress is "allowed" after last keypress.
195
+ Default: 250 ms
196
+ ###
197
+ delay_for_allowed_keypress: 250
198
+
199
+ ###
200
+ Groups enabled/disabled (If elements should be grouped or run as single elements)
201
+ Default: true
202
+ ###
203
+ groups: true
204
+
205
+ ###
206
+ DOM options
207
+ ###
208
+ dom:
209
+ ###
210
+ The class which all elements must have to be a switch controlled element
211
+ The class should be appended with numbers 1,2,3 etc to set order of elements.
212
+ order is unpredicaable if several elements have the same number within a group.
213
+ Use classnames switch-element-1 switch-element-2 or change this value
214
+ Default: "switch-element-"
215
+ ###
216
+ element_class: "switch-element-"
217
+ ###
218
+ The jQuery selector from where the first switch element should be searched for.
219
+ Usually this should be body or the first container on the webpage
220
+ Note: Use a selector which selects a single object. Else behaviour is unpredictable
221
+ ###
222
+ start_element_selector: "body"
223
+
224
+ ###
225
+ Other settings
226
+ ###
227
+ # Use .search where you have class="search" or #search for id="search" (jQuery selectors)
228
+ key_filter_skip: [".search"]
229
+
230
+ ###
231
+ If set to true, the first link within the element is "clicked".
232
+ Else the actual element is clicked.
233
+ FUTURE feature: (on the todo list)
234
+ A data attribute can be set on the element in order to override this on a per-element basis
235
+ ###
236
+ activate_first_link: true # activate element or first link within
237
+ ###
238
+ Enable/Disable debug
239
+ Note: log4javascript must be available if used
240
+ Default: false
241
+ ###
242
+ debug: false
243
+
244
+ ###
245
+ Visual settings
246
+ ###
247
+ visual:
248
+ ###
249
+ Scroll to ensure the entire element in focus is visible (if possible)
250
+ Default: true
251
+ ###
252
+ ensure_visible_element: true # ensure element is visible on the page
253
+
254
+ ###
255
+ The number of pixels for margin to the viewport/window when
256
+ the element is positioned in the viewport/window
257
+ Default: 15
258
+ ###
259
+ scroll_offset: 15
260
+
261
+ ###
262
+ Time in milliseconds the scroll will animate
263
+ (set to 0 if instant scroll is preferred)
264
+ Default: 200
265
+ ###
266
+ animate_scroll_time: 200
267
+ ###
268
+ The easing to use for animation
269
+ Default: "linear"
270
+ ###
271
+ easing: "linear" # easing to use for scrolling
272
+
273
+ # hide_show_delay: 500
274
+ # move_fade_delay: 200
275
+ # pulsate: false
276
+ # play_sound: false
277
+ # highlight_sound: "./switch_move.mp3"
278
+ # activate_sound: "./switch_activate.mp3"
279
+
280
+ ###
281
+ Runtime properties
282
+ ###
283
+ @runtime =
284
+ active: false # switchaccess is active or not
285
+ element_list: null # an array with root elements
286
+ current_list: null # the current list
287
+ # (Root or an elements list of children)
288
+
289
+ parent_list: null # the list the parent is in after going into
290
+ # the group and highlighting the first child
291
+
292
+ element:
293
+ current: null # the current element within the active group
294
+ idx: 0 # the current element idx within the active group
295
+ level: 0 # the current level within a group or nested group
296
+ next_level: 0 # next level when moving to elements.
297
+ next_idx: 0 # next elements idx
298
+ parent_idx: 0 # the last idx on the parent before moving into the group
299
+ action_triggered: false # set to true if an action is triggered
300
+ keypress_allowed: true # keypress allowed or not
301
+ timers:
302
+ single_switch_id: null # The id of the single switch timer
303
+
304
+ highlighter_holder: null # The highlighter holder as a jquery object
305
+
306
+ @setoptions(options)
307
+ @init()
308
+
309
+ init: ->
310
+ if (@options.debug)
311
+ appender = null
312
+ @logger = log4javascript.getLogger()
313
+ if $('iframe[id*=log4javascript]').length <= 0
314
+ if $('#logger').length > 0
315
+ appender = new log4javascript.InPageAppender("logger")
316
+ appender.setWidth("100%")
317
+ appender.setHeight("100%")
318
+ else
319
+ appender = new log4javascript.InPageAppender()
320
+ appender.setHeight("500px")
321
+
322
+ appender.setThreshold(log4javascript.Level.ALL)
323
+ @logger.setLevel(log4javascript.Level.ALL)
324
+ @logger.addAppender(appender)
325
+
326
+ @log("init") if (@options.debug)
327
+ @createHighlighterHolder() if SwitchAccessCommon.options.highlighter.use
328
+ @registerCallbacks()
329
+ @start()
330
+
331
+ setoptions: (options) ->
332
+ @log "setoptions" if (@options.debug)
333
+ # @log options, "trace", true
334
+ @stop() if @runtime.active == true
335
+ jQuery.extend SwitchAccessCommon.options, {
336
+ highlighter: options.highlighter,
337
+ highlight: options.highlight,
338
+ internal: options.internal,
339
+ debug: options.debug
340
+ }
341
+ delete options.highlighter
342
+ delete options.highlight
343
+ delete options.internal
344
+ jQuery.extend true, @options, options
345
+ return
346
+
347
+ log: (msg, type = "debug", raw = false) ->
348
+ #console.log msg if @options.nested_debug
349
+ if (@options.debug)
350
+ if (raw)
351
+ @logger[type] msg
352
+ else
353
+ @logger[type] "SwitchAccess: " + msg
354
+
355
+ checkForNonNumberedElements: ->
356
+ if $(".#{@options.dom.element_class}").length > 0
357
+ msg = "Warning! #{$(".#{@options.dom.element_class}").length} element(s) without numbers found. Class selector is: #{@options.element_class}."
358
+ @log msg, "warning" if (@options.debug)
359
+ console.log "SwitchAccess: " + msg # Alert about no-numbered elements
360
+ return
361
+
362
+ buildListFromjqElement: (jq_element, parent, depth = 0) ->
363
+ not_str = "[class*=#{@options.dom.element_class}] [class*=#{@options.dom.element_class}]"
364
+ i = 0
365
+ while i < depth
366
+ not_str += " [class*=#{@options.dom.element_class}]"
367
+ i++
368
+
369
+ temp_list = jq_element.find("[class*=#{@options.dom.element_class}]").not(not_str)
370
+ @log "buildListFromjqElement - element count #{temp_list.length} depth: #{depth} element-classes:#{jq_element.attr("class")}", "trace" if (@options.debug)
371
+ return [] if temp_list.length <= 0
372
+ # warn if there are any elements that don't have any numbers
373
+ @hashAlizeAndRecurseList(@sortList(temp_list, @options.dom.element_class), parent, depth)
374
+
375
+ hashAlizeAndRecurseList: (list, parent, depth) ->
376
+ ret = []
377
+ i = 0
378
+ while i < list.length
379
+ new_element = new SwitchAccessElement($(list[i]), @runtime.highlighter_holder, @, parent)
380
+ new_element.children(@buildListFromjqElement($(list[i]), new_element, depth + 1 ))
381
+ ret.push( new_element )
382
+ i++
383
+ ret
384
+
385
+ sortList: (list, list_class) ->
386
+ @log "sortList Sorting list for #{list_class} Elements: #{list.length}" if (@options.debug)
387
+ search_regexp_class = ///#{list_class}\d+///
388
+ search_regexp_num = ///\d+///
389
+ list.sort (a,b) =>
390
+ item_class_name_a = search_regexp_class.exec($(a).attr("class"))
391
+ item_class_name_b = search_regexp_class.exec($(b).attr("class"))
392
+ num_a = 0
393
+ num_b = 0
394
+ if (item_class_name_a != null && item_class_name_b != null)
395
+ num_a = search_regexp_num.exec(item_class_name_a)
396
+ num_b = search_regexp_num.exec(item_class_name_b)
397
+ num_a - num_b
398
+ list
399
+
400
+ elementWithoutChildren: (element) ->
401
+ @log "elementWithoutChildren #{element.uniqueDataAttr()}" if (@options.debug)
402
+ ret = []
403
+ if element.children().length == 0
404
+ ret.push(element)
405
+ else
406
+ ret = ret.concat(@elementWithoutChildren(child)) for child in element.children()
407
+ element.children([]) # remove children from the element
408
+ element.destroy() # destroy the element, as the children have been returned
409
+ ret
410
+
411
+
412
+ flattenElementList: ->
413
+ @log "flattenElementList" if (@options.debug)
414
+ new_list = []
415
+ new_list = new_list.concat(@elementWithoutChildren(element)) for element in @runtime.element_list
416
+ new_list
417
+
418
+ buildElementList: ->
419
+ @runtime.element_list = @buildListFromjqElement($(@options.dom.start_element_selector), null, 0)
420
+
421
+ # if groups are disabled, flatten the list by moving children upwards
422
+ if @options.switches.groups == false
423
+ @runtime.element_list = @flattenElementList()
424
+
425
+ @log "buildElementList: count:#{@runtime.element_list.length}, class-name: #{@options.dom.element_class}" if (@options.debug)
426
+ return
427
+
428
+ deinit: ->
429
+ @log "deinit" if (@options.debug)
430
+ @stop()
431
+ @removeHighlightdiv()
432
+
433
+ start: ->
434
+ return if (@options.switches.number_of_switches == 0) || (@runtime.active == true)
435
+ @log "start" if (@options.debug)
436
+ @buildElementList()
437
+ # return
438
+ @runtime.active = true
439
+ @moveToFirstRootElement()
440
+ @startSingleSwitchTimer()
441
+ @runtime.action_triggered = false
442
+
443
+ stop: ->
444
+ return if (@runtime.active == false)
445
+ @log "stop" if (@options.debug)
446
+ @runtime.active = false
447
+ @removeHighlight()
448
+ @removeActivateClass()
449
+ @stopSingleSwitchTimer()
450
+
451
+ destroy: ->
452
+ @log "destroy" if (@options.debug)
453
+ @stop()
454
+ @removeCallbacks()
455
+ @destroy_elements(@runtime.element_list)
456
+ @removeHighlighterHolder()
457
+ @runtime.element_list = null
458
+ @runtime.current_list = null
459
+ @runtime.parent_list = null
460
+ @runtime.element.current = null
461
+ @runtime.highlighter_holder = null
462
+ window.__switch_access_sci = null
463
+
464
+ ###
465
+ Destroy elements in a list
466
+ Children of the element will be destroyed by the element itself
467
+ ###
468
+ destroy_elements: (list) ->
469
+ @log "destroy_elements", "trace" if (@options.debug)
470
+ element.destroy() for element in list
471
+ return
472
+
473
+ moveToFirstRootElement: ->
474
+ @runtime.element.idx = -1
475
+ @moveToNextElementAtLevel()
476
+
477
+ moveToNextElementAtLevel: ->
478
+ @log "moveToNextElementAtLevel", "trace" if (@options.debug)
479
+ @runtime.element.next_idx = @runtime.element.idx + 1
480
+ # verify that next idx is possible for "root"
481
+ if @runtime.element.next_level == @runtime.element.level and @runtime.element.level == 0
482
+ if (@runtime.element.next_idx >= @runtime.element_list.length)
483
+ @runtime.element.next_idx = 0
484
+
485
+ # see if we should move "out" of the current group
486
+ if @runtime.element.next_level isnt 0
487
+ if @runtime.element.next_idx >= @runtime.current_list.length
488
+ return @moveToPreviousLevel()
489
+
490
+ if @moveToNext()
491
+ @runtime.element.current.jq_element().triggerHandler("switch-access-move", [@runtime.element.idx, @runtime.element.level, @runtime.element.current])
492
+ return SwitchAccessCommon.actions.moved_to_next_element
493
+ else
494
+ return SwitchAccessCommon.actions.stayed_at_element
495
+
496
+ moveToNextLevel: ->
497
+ @log "moveToNextLevel", "trace" if (@options.debug)
498
+ # "catch" if the current element doesn't have children
499
+ if @runtime.element.current.children().length > 1
500
+ @runtime.element.next_level = @runtime.element.level + 1
501
+ @runtime.element.next_idx = 0
502
+ # return SwitchAccessCommon.actions.stayed_at_element
503
+
504
+ if @moveToNext()
505
+ @runtime.element.current.parent().jq_element().triggerHandler("switch-access-enter-group", [@runtime.element.idx, @runtime.element.level, @runtime.element.current])
506
+ return SwitchAccessCommon.actions.moved_to_next_level
507
+ else
508
+ return SwitchAccessCommon.actions.stayed_at_element
509
+
510
+ moveToPreviousLevel: ->
511
+ @log "moveToPreviousLevel", "trace" if (@options.debug)
512
+ @runtime.element.next_level = @runtime.element.level - 1
513
+ @runtime.element.next_idx = @runtime.element.parent_idx
514
+ # safety catch impossible levelss
515
+ if @runtime.element.next_level < 0
516
+ @runtime.element.next_level = 0
517
+ @runtime.element.next_idx = 0
518
+
519
+ if @moveToNext()
520
+ @runtime.element.current.jq_element().triggerHandler("switch-access-leave-group", [@runtime.element.idx, @runtime.element.level, @runtime.element.current])
521
+ return SwitchAccessCommon.actions.moved_to_previous_level
522
+ else
523
+ return SwitchAccessCommon.actions.stayed_at_element
524
+
525
+ moveToNext: ->
526
+ @log "moveToNext Current: I: #{@runtime.element.next_idx} L: #{@runtime.element.level} Next: I: #{@runtime.element.next_idx} L: #{@runtime.element.next_level}" if (@options.debug)
527
+ @runtime.action_triggered = true
528
+
529
+ # return if current level and idxs are equal
530
+ return false if (@runtime.element.next_level == @runtime.element.level) and (@runtime.element.idx == @runtime.element.next_idx)
531
+
532
+ @removeHighlight()
533
+
534
+ # find list to work on element
535
+ if @runtime.element.next_level > @runtime.element.level
536
+ list_n = @runtime.element.current.children()
537
+ @runtime.element.parent_idx = @runtime.element.idx
538
+ @runtime.parent_list = @runtime.current_list
539
+ @runtime.element.next_idx = 0
540
+ else if @runtime.element.next_level < @runtime.element.level
541
+ @runtime.element.next_idx = @runtime.element.parent_idx
542
+ if @runtime.element.current.parent() isnt null
543
+ list_n = @runtime.parent_list
544
+ else
545
+ list_n = @runtime.element_list
546
+ else if @runtime.element.next_level == 0
547
+ list_n = @runtime.element_list
548
+ else
549
+ list_n = @runtime.current_list
550
+
551
+ list_n[@runtime.element.next_idx].highlight()
552
+
553
+ @runtime.element.idx = @runtime.element.next_idx
554
+ @runtime.element.level = @runtime.element.next_level
555
+ @runtime.current_list = list_n
556
+ @runtime.element.current = list_n[@runtime.element.idx]
557
+
558
+ @makeElementVisible()
559
+ return true
560
+
561
+ removeHighlight: ->
562
+ @log "removeHighlight" if (@options.debug)
563
+ return if @runtime.element.current is null
564
+ @runtime.element.current.removeHighlight()
565
+ return
566
+
567
+ removeActivateClass: ->
568
+ @log "removeHighlight" if (@options.debug)
569
+ return if @runtime.current_list is null
570
+ if @runtime.element.current.children().length == 0
571
+ @runtime.element.current.removeActivateClass()
572
+ else
573
+ # else it's most likely that the children should be highlighted
574
+ child.removeActivateClass() for child in @runtime.element.current.children()
575
+ return
576
+
577
+ ###
578
+ Make the element(s) visible. If the current selected element is a group, they are all moved inside the visible area of the screen
579
+ ###
580
+ makeElementVisible: ->
581
+ return unless @options.visual.ensure_visible_element == true
582
+ @log "makeElementVisible", "trace" if (@options.debug)
583
+ scrollval = null
584
+ scroll_top = $(document).scrollTop()
585
+ element = @runtime.element.current.jq_element()
586
+ # positive scroll:
587
+ if ($(window).height() + scroll_top) < (element.offset().top + element.outerHeight() + @options.visual.scroll_offset)
588
+ diff_to_make_visible = (element.offset().top + element.outerHeight() + @options.visual.scroll_offset) - ($(document).scrollTop() + $(window).height())
589
+ if (diff_to_make_visible > 0)
590
+ scrollval = diff_to_make_visible + scroll_top
591
+ # negative scroll:
592
+ else if (scroll_top > (element.offset().top - @options.visual.scroll_offset))
593
+ if (element.offset().top - @options.visual.scroll_offset < 0)
594
+ scrollval = 0
595
+ else
596
+ scrollval = element.offset().top - @options.visual.scroll_offset
597
+
598
+ if (scroll_top != scrollval) && scrollval != null
599
+ if (@options.visual.animate_scroll_time == 0)
600
+ # for FF
601
+ $("html").scrollTop(scrollval)
602
+ # for Chrome
603
+ $("html body").scrollTop(scrollval)
604
+ else
605
+ # for FF
606
+ $("html").animate({scrollTop: scrollval}, @options.visual.animate_scroll_time, @options.visual.easing);
607
+ # for Chrome
608
+ $("html body").animate({scrollTop: scrollval}, @options.visual.animate_scroll_time, @options.visual.easing);
609
+
610
+
611
+ activateElement: ->
612
+ @log "activateElement" if (@options.debug)
613
+ @runtime.action_triggered = true
614
+ @stopSingleSwitchTimer()
615
+
616
+ @log "Activate Element: idx: #{@runtime.element.idx} level: IDX: #{@runtime.element.level} uuid: #{@runtime.element.current.uniqueDataAttr()}" if (@options.debug)
617
+ if (@options.switches.delay_before_activating_element == 0)
618
+ return @activateElementCallBack()
619
+ else
620
+ @runtime.element.current.jq_element().addClass(@options.element_activate_class)
621
+ if @runtime.element.current.children().length > 0
622
+ child.addActivateClass() for child in @runtime.element.current.children()
623
+ else
624
+ @runtime.element.current.addActivateClass()
625
+
626
+ window.setTimeout(( => @removeActivateClass(); @activateElementCallBack(); return), @options.switches.delay_before_activating_element)
627
+ return SwitchAccessCommon.actions.triggered_delayed_action
628
+
629
+ activateElementCallBack: ->
630
+ @log "activateElementCallBack" if (@options.debug)
631
+
632
+ # see if the element has children, if so go into level
633
+ if @runtime.element.current.children().length > 0
634
+ @startSingleSwitchTimer()
635
+ return @moveToNextLevel()
636
+ # else the element should "activate"
637
+
638
+ if ((@runtime.element.current.jq_element().is("a")) || (@activate_first_link == false))
639
+ element_to_click = @runtime.element.current.jq_element()
640
+ else
641
+ # if element isn't a link, find first link within element and go to the url/trigger it
642
+ element_to_click = @runtime.element.current.jq_element().find("a")
643
+
644
+ # safety catch
645
+ if (element_to_click.length <= 0)
646
+ element_to_click = @runtime.element.current.jq_element()
647
+
648
+ @log "Triggering Element: IDX: #{@runtime.element.current_idx} Element Tag: #{$(element_to_click).get(0).tagName.toLowerCase()} Text: #{$(element_to_click).text()}" if (@options.debug)
649
+
650
+ @runtime.element.current.jq_element().triggerHandler("switch-access-activate", [@runtime.element.idx, @runtime.element.level, element_to_click, @runtime.element.current])
651
+ if element_to_click.length > 0
652
+ element_to_click[0].click() #trigger("click")
653
+ # if (ret == true) && element_to_click.is("a")
654
+ # document.location = element_to_click.attr("href")
655
+ if (@options.switches.number_of_switches == 1)
656
+ @runtime.next_element_idx = -1 if (@options.single_switch_restart_on_activate)
657
+ @runtime.next_level = 0
658
+ @startSingleSwitchTimer()
659
+ else
660
+ msg = "Nothing to do. Verify options passed to SwitchAccess"
661
+ @log msg, "warn" if (@options.debug)
662
+ console.log "SwitchAccess: Warning: " + msg
663
+ return SwitchAccessCommon.actions.none
664
+
665
+ return SwitchAccessCommon.actions.triggered_action
666
+
667
+
668
+ singleSwitchTimerCallback: ->
669
+ @log "singleSwitchTimerCallback", "trace" if (@options.debug)
670
+ @moveToNextElementAtLevel()
671
+
672
+ allowKeyPressCallback: ->
673
+ @log "allowKeyPressCallback", "trace" if (@options.debug)
674
+ @runtime.keypress_allowed = true
675
+
676
+ createHighlighterHolder: ->
677
+ if $("div##{SwitchAccessCommon.options.holder_id}").length == 0
678
+ @runtime.highlighter_holder = $("<div id=\"#{SwitchAccessCommon.options.highlighter.holder_id}\"></div>")
679
+ $('body').append(@runtime.highlighter_holder)
680
+ return
681
+
682
+ removeHighlighterHolder: ->
683
+ $("div##{SwitchAccessCommon.options.highlighter.holder_id}").remove()
684
+
685
+ callbackForKeyPress: (event) ->
686
+ @log "callbackForKeyPress keycode: #{event.which} Allowed: #{@runtime.keypress_allowed}" if (@options.debug)
687
+ return if (@options.switches.number_of_switches == 0)
688
+ action = 0
689
+
690
+ if @options.switches.number_of_switches == 1
691
+ if event.which in @options.switches.keys_1
692
+ if !@runtime.keypress_allowed
693
+ event.stopPropagation()
694
+ return false
695
+ action = @activateElement()
696
+
697
+ else if @options.switches.number_of_switches == 2
698
+ if (event.which in @options.switches.keys_2[0]) || (event.which in @options.switches.keys_2[1])
699
+ if !@runtime.keypress_allowed
700
+ event.stopPropagation()
701
+ return false
702
+ action = @moveToNextElementAtLevel() if event.which in @options.switches.keys_2[0]
703
+ action = @activateElement() if event.which in @options.switches.keys_2[1]
704
+
705
+ if (@runtime.action_triggered)
706
+ @runtime.action_triggered = false
707
+ @runtime.keypress_allowed = false
708
+ timeout = @options.switches.delay_for_allowed_keypress
709
+ if (action == SwitchAccessCommon.actions.triggered_action) || (action == SwitchAccessCommon.actions.triggered_delayed_action)
710
+ if (@options.switches.number_of_switches == 1)
711
+ if (@options.switches.single_switch_move_time > @options.switches.delay_before_activating_element)
712
+ timeout = @options.switches.single_switch_move_time
713
+ else
714
+ timeout = @options.switches.delay_before_activating_element
715
+ else
716
+ if (@options.switches.delay_before_activating_element > timeout)
717
+ timeout = @options.switches.delay_before_activating_element
718
+
719
+ if timeout == 0
720
+ @allowKeyPressCallback()
721
+ else
722
+ window.setTimeout((=>@allowKeyPressCallback();return), timeout)
723
+
724
+ event.stopPropagation()
725
+ return false
726
+ else
727
+ return true
728
+
729
+ startSingleSwitchTimer: ->
730
+ return unless @options.switches.number_of_switches == 1
731
+ @log "startSingleSwitchTimer", "trace" if (@options.debug)
732
+ @runtime.single_switch_timer_id = window.setInterval(( => @singleSwitchTimerCallback();return), @options.switches.single_switch_move_time)
733
+
734
+ stopSingleSwitchTimer: ->
735
+ return unless @options.switches.number_of_switches == 1
736
+ @log "stopSingleSwitchTimer", "trace" if (@options.debug)
737
+ window.clearInterval(@runtime.single_switch_timer_id)
738
+
739
+ removeCallbacks: ->
740
+ @log "removeCallbacks", "trace" if (@options.debug)
741
+ $(document).off("keydown.switch_access")
742
+
743
+ registerCallbacks: ->
744
+ @log "registerCallbacks", "trace" if (@options.debug)
745
+ $(document).on("keydown.switch_access", (event) =>
746
+ @callbackForKeyPress(event)
747
+ )
748
+ return
749
+
750
+ window.SwitchAccess = SwitchAccess
751
+
752
+ class SwitchAccessElement
753
+ constructor: (jq_element, highlight_holder = null, logger = null, parent = null, children = []) ->
754
+ @options =
755
+ debug: false
756
+
757
+ @runtime =
758
+ jq_highlighter: null # jquery object of the highlighter belonging to this element
759
+ jq_element: jq_element # jquery object of this element
760
+ uuid: null # The UUID for this element
761
+ watching: false # if csswatch is enabled for this element
762
+ csswatch_init: false # if csswatch has been initialized on/for this element
763
+ parent: parent # the parent switch-element of this element
764
+ children: children # the child switch-elements of this element
765
+ highlight_holder: null # the highlight holder from SwitchAccess
766
+
767
+ @options.debug = SwitchAccessCommon.options.debug
768
+ @logger = logger if (@options.debug)
769
+
770
+ @runtime.highlight_holder = if highlight_holder == null then $('body') else highlight_holder
771
+
772
+ @init(@runtime.highlight_holder)
773
+
774
+ init: (highlight_holder)->
775
+ @uniqueDataAttr(true)
776
+ @createHighlighter(highlight_holder)
777
+ @log "init", "trace" if (@options.debug)
778
+
779
+ destroy: ->
780
+ @log "destroy", "trace" if (@options.debug)
781
+ @destroyHighlighter()
782
+ # destroy any children
783
+ child.destroy() for child in @children()
784
+
785
+ parent = null
786
+ children = null
787
+ jq_element = null
788
+
789
+ parent: (parent = null) ->
790
+ if parent == null
791
+ @runtime.parent
792
+ else
793
+ @runtime.parent = parent
794
+
795
+ children: (children = null) ->
796
+ if children == null
797
+ @runtime.children
798
+ else
799
+ @runtime.children = children
800
+
801
+ jq_element: (jq_element = null) ->
802
+ if (jq_element == null)
803
+ @runtime.jq_element
804
+ else
805
+ @runtime.jq_element = jq_element
806
+
807
+ log: (msg, type = "debug", raw = false) ->
808
+ if @options.debug && @logger != null
809
+ if (raw)
810
+ @logger.log "Element: #{@runtime.uuid} :", type
811
+ @logger.log msg, type, true
812
+ else
813
+ @logger.log "Element: #{@runtime.uuid} : #{msg}", type
814
+
815
+ ###
816
+ Trigger the active element, link or event depending on options
817
+ ###
818
+ trigger: ->
819
+ @log "trigger" if (@options.debug)
820
+
821
+ ###
822
+ Show the highlighter and add highlight class to current object
823
+ ###
824
+ highlight: (check_children = true) ->
825
+ @log "highlight" if (@options.debug)
826
+ # if the element has children, it's most likely that the children should be highlighted
827
+ if @children().length > 0 and check_children == true
828
+ child.highlight(false) for child in @children()
829
+ return
830
+
831
+ # if the next element doesn't have any children, highlight it
832
+ @runtime.jq_element.addClass(SwitchAccessCommon.options.highlight.element.current_class)
833
+ return unless SwitchAccessCommon.options.highlighter.use
834
+ @runtime.jq_highlighter.addClass(SwitchAccessCommon.options.highlighter.current_class)
835
+ @runtime.jq_highlighter.show()
836
+ # Must show the element before moving, else the offset will not be correct
837
+ @setHighlighterSizeAndPosition()
838
+ if SwitchAccessCommon.options.highlighter.watch_for_resize
839
+ if @runtime.watching == false
840
+ @enableCSSWatch()
841
+
842
+ ###
843
+ Hide highlighter and remove highlight class on the current object(s)
844
+ ###
845
+ removeHighlight: (check_children = true) ->
846
+ @log "removeHighlight" if (@options.debug)
847
+
848
+ # if set to check for children:
849
+ if @children().length > 0 and check_children == true
850
+ child.removeHighlight(false) for child in @children()
851
+ return
852
+
853
+ @runtime.jq_element.removeClass(SwitchAccessCommon.options.highlight.element.current_class)
854
+ @runtime.jq_element.removeClass(SwitchAccessCommon.options.highlight.element.activate_class)
855
+ return unless SwitchAccessCommon.options.highlighter.use
856
+ @runtime.jq_highlighter.removeClass(SwitchAccessCommon.options.highlighter.current_class)
857
+ @runtime.jq_highlighter.removeClass(SwitchAccessCommon.options.highlighter.activate_class)
858
+ @runtime.jq_highlighter.hide()
859
+ if SwitchAccessCommon.options.highlighter.watch_for_resize
860
+ @disableCSSWatch()
861
+
862
+ addActivateClass: ->
863
+ @runtime.jq_element.addClass(SwitchAccessCommon.options.highlight.element.activate_class)
864
+ return unless SwitchAccessCommon.options.highlighter.use
865
+ @runtime.jq_highlighter.addClass(SwitchAccessCommon.options.highlighter.activate_class)
866
+
867
+ removeActivateClass: ->
868
+ @log "removeActivateClass" if (@options.debug)
869
+ @runtime.jq_element.removeClass(SwitchAccessCommon.options.highlight.element.activate_class)
870
+ return unless SwitchAccessCommon.options.highlighter.use
871
+ @runtime.jq_highlighter.removeClass(SwitchAccessCommon.options.highlighter.activate_class)
872
+
873
+ ###
874
+ Set Highlighter size and position
875
+ ###
876
+ setHighlighterSizeAndPosition: ->
877
+ @setHighlighterSize(@runtime.jq_element, @runtime.jq_highlighter)
878
+ @setHighlighterPosition(@runtime.jq_element, @runtime.jq_highlighter)
879
+
880
+ ###
881
+ Set highlighter position
882
+ ###
883
+ setHighlighterPosition: (element, highlighter) ->
884
+ # posshift = SwitchAccessCommon.options.highlighter.margin_to_element #+ ((highlighter.outerWidth() - highlighter.innerWidth())/2)
885
+ @log "m_to_el: #{SwitchAccessCommon.options.highlighter.margin_to_element}, outerW-innerW: #{highlighter.outerWidth() - highlighter.width()} outerH-innerH: #{highlighter.outerHeight() - highlighter.innerHeight()}", "trace" if (@options.debug)
886
+ position = {
887
+ top: element.offset().top - SwitchAccessCommon.options.highlighter.margin_to_element - (highlighter.outerHeight() - highlighter.innerHeight())/2
888
+ left: element.offset().left - SwitchAccessCommon.options.highlighter.margin_to_element - (highlighter.outerWidth() - highlighter.innerWidth())/2
889
+ }
890
+ return if (highlighter.offset().top == position.top) and (highlighter.offset().left == position.left)
891
+ highlighter.offset(position)
892
+ @log "setHighlighterPosition left: #{position.left} top: #{position.top}", "trace" if (@options.debug)
893
+ return
894
+
895
+ ###
896
+ Set size on the Highlighter object to the match the given element
897
+ ###
898
+ setHighlighterSize: (element, highlighter) ->
899
+ w = element.outerWidth(false) + (SwitchAccessCommon.options.highlighter.margin_to_element * 2)
900
+ h = element.outerHeight(false) + (SwitchAccessCommon.options.highlighter.margin_to_element * 2)
901
+
902
+ highlighter.width(w)
903
+ highlighter.height(h)
904
+ @log "setHighlighterSize w: #{w}, h: #{h}", "trace" if (@options.debug)
905
+ return
906
+
907
+ ###
908
+ Create the highlighter DOM object
909
+ ###
910
+ createHighlighter: (jq_holder)->
911
+ return if (SwitchAccessCommon.options.highlighter.use is false) or
912
+ (@runtime.jq_highlighter isnt null)
913
+ @log "createHighlight" if (@options.debug)
914
+
915
+ @runtime.jq_highlighter = $("<div id=\"sw-el-#{@runtime.uuid}\" class=\"#{SwitchAccessCommon.options.highlighter.class}\"></div>")
916
+ jq_holder.append(@runtime.jq_highlighter)
917
+ @runtime.jq_highlighter.css('position','absolute')
918
+ @runtime.jq_highlighter.hide()
919
+ @runtime.jq_highlighter.append(SwitchAccessCommon.options.highlighter.content)
920
+
921
+ if SwitchAccessCommon.options.watch_for_resize
922
+ # check if content contains selector to use for resizeing
923
+ if @runtime.jq_highlighter.find(SwitchAccessCommon.options.highlighter.selector_for_set_to_size).length == 0
924
+ @runtime.jq_highlighter.addClass(SwitchAccessCommon.options.highlighter.selector_for_set_to_size)
925
+
926
+ ###
927
+ Destroy the highlighter DOM object
928
+ ###
929
+ destroyHighlighter: ->
930
+ return if @runtime.jq_highlighter == null
931
+ @log "destroyHighlight", "trace" if (@options.debug)
932
+ @disableCSSWatch()
933
+
934
+ @runtime.jq_highlighter.remove()
935
+ @runtime.jq_highlighter = null
936
+
937
+ ###
938
+ Enable wathcing CSS changes on the element belonging to this object
939
+ ###
940
+ enableCSSWatch: ->
941
+ return unless SwitchAccessCommon.options.highlighter.use == true and
942
+ SwitchAccessCommon.options.highlighter.watch_for_resize == true
943
+ @log "enableCSSWatch", "trace" if (@options.debug)
944
+ @runtime.watching = true
945
+ if @runtime.csswatch_init
946
+ @runtime.jq_element.csswatch('start')
947
+ else
948
+ @runtime.csswatch_init = true
949
+ @runtime.jq_element.csswatch({
950
+ props: "top,left,bottom,right,width,height"
951
+ props_functions: {
952
+ top: "offset().top"
953
+ left: "offset().left"
954
+ bottom: "offset().bottom"
955
+ right: "offset().right"
956
+ outerwidth: "outerWidth(false)"
957
+ outerheight: "outerHeight(false)"
958
+ }
959
+ callback: (->@callbackForResize????)
960
+ })
961
+
962
+ ###
963
+ Disable watching CSS changes on the element belonging to this object
964
+ ###
965
+ disableCSSWatch: ->
966
+ return unless SwitchAccessCommon.options.highlighter.use == true and
967
+ SwitchAccessCommon.options.highlighter.watch_for_resize == true
968
+ @log "disableCSSWatch", "trace" if (@options.debug)
969
+ @runtime.watching = false
970
+ @runtime.jq_element.csswatch('stop')
971
+
972
+ ###
973
+ Add a data attribute to the element that has a unique ID.
974
+ Will also add the same attribute as a class if option
975
+ set_unique_element_class is enabled.
976
+ ###
977
+ uniqueDataAttr: (create = false) ->
978
+ @log "uniqueDataAttr: Create: #{create}", "trace" if (@options.debug)
979
+ if create
980
+ @runtime.uuid = SwitchAccessCommon.generateRandomUUID()
981
+ @runtime.jq_element.data(SwitchAccessCommon.options.internal.unique_element_data_attribute, @runtime.uuid)
982
+ if SwitchAccessCommon.options.set_unique_element_class == true
983
+ @runtime.jq_element.addClass("#{SwitchAccessCommon.options.internal.unique_element_data_attribute}+uuid")
984
+ @runtime.uuid
985
+ else
986
+ @runtime.uuid
987
+
988
+ ###
989
+ Callback for resize event on this particular element
990
+ ###
991
+ callbackForResize: (event, changes) ->
992
+ @log "callbackForResize", "trace" if (@options.debug)
993
+ return unless SwitchAccessCommon.options.highlighter.use
994
+ @setHighlighterSize(@runtime.jq_element, @runtime.jq_highlighter)
995
+ @setHighlighterPosition(@runtime.jq_element, @runtime.jq_highlighter)
996
+ # if (changes.keys)
997
+
998
+ ###
999
+ Play the sound for the current element upon highlight
1000
+ ###
1001
+ # playHighlightSound: ->
1002
+
1003
+
1004
+ ###
1005
+ Play the sound for the current element upon activation
1006
+ ###
1007
+ # playActivateSound: ->