switch_access-rails 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: ->