UNPKG

4.9 kBJavaScriptView Raw
1import $ from './jquery';
2import keyCode from './key-code';
3import amdify from './internal/amdify';
4import skate from './internal/skate';
5import state from './internal/state';
6import { warn } from './internal/log'
7
8export function getTrigger (element) {
9 return state(element).get('last-trigger') || findControllers(element)[0];
10}
11
12export function setTrigger (element, trigger) {
13 var validTrigger = trigger && trigger.nodeType && trigger.nodeType === 1;
14 return state(element).set('last-trigger', validTrigger ? trigger : false);
15}
16
17export function hasTrigger (element) {
18 return !!getTrigger(element);
19}
20
21export function doIfTrigger (element, callback) {
22 var trigger = getTrigger(element);
23
24 if (trigger) {
25 callback(trigger);
26 }
27}
28
29export function forEachTrigger (element, callback) {
30 return Array.prototype.forEach.call(findControllers(element), callback);
31}
32
33function isNestedAnchor(trigger, target) {
34 var $closestAnchor = $(target).closest('a[href]', trigger);
35 return !!$closestAnchor.length && $closestAnchor[0] !== trigger;
36}
37
38function findControllers(element) {
39 const frames = window.frames;
40 const selector = `[aria-controls="${element.id}"]`;
41
42 let controllers = [];
43 let someFramesAreCrossOrigin = false;
44 for (let i = 0 ; i < frames.length; i++) {
45 try {
46 let nodeList = frames[i].document.querySelectorAll(selector);
47 controllers = controllers.concat(Array.prototype.slice.apply(nodeList));
48 } catch (e) {
49 // Silently catch DOM exceptions related to accessing cross-origin frames
50 someFramesAreCrossOrigin = true;
51 }
52 }
53 const currentDocumentControllers = document.querySelectorAll(selector);
54 const allControllers = Array.prototype.slice.apply(currentDocumentControllers).concat(controllers);
55 if (allControllers.length === 0 && someFramesAreCrossOrigin === true) {
56 warn(
57 [
58 `No triggers found for element (${element.id}) in iframes from the same origin.`,
59 'However some iframes in this document are cross-origin.',
60 'The trigger-element relations crossing the origin boundary are not supported.'
61 ].join(' ')
62 )
63 }
64 return allControllers;
65}
66
67function findControlled(trigger) {
68 return document.getElementById(trigger.getAttribute('aria-controls'));
69}
70
71function isEnabled (element) {
72 return element.getAttribute('aria-disabled') !== 'true';
73}
74
75function triggerMessage(trigger, e) {
76 if (isEnabled(trigger)) {
77 var component = findControlled(trigger);
78 if (component && component.message) {
79 component.message(e);
80 }
81 }
82}
83
84/**
85 * Converts native or jQuery events in to a "message" object.
86 * Basically helps us keep message types consistent.
87 */
88function msg(e, type) {
89 const { target, currentTarget, relatedTarget } = e;
90 const { keyCode, which } = e;
91 return {
92 type,
93 data: type === 'keydown' ? which || keyCode : undefined,
94 target,
95 currentTarget,
96 relatedTarget,
97 preventDefault: () => e.preventDefault()
98 };
99}
100
101function focusingToControlledElement(trigger, e) {
102 let relatedTarget = e.relatedTarget;
103 // relatedTarget is always null in IE11 but activeElement is set to correct value
104 if (!relatedTarget) {
105 relatedTarget = document.activeElement;
106 }
107 const $component = $(findControlled(trigger));
108 return $component.find(relatedTarget).length > 0;
109}
110
111const events = {
112 click(trigger, e) {
113 if (!isNestedAnchor(trigger, e.target)) {
114 triggerMessage(trigger, e);
115 e.preventDefault();
116 }
117 },
118 keydown(trigger, e) {
119 const key = e.data;
120 if (key === keyCode.ENTER || key === keyCode.SPACE) {
121 e.preventDefault();
122 e.type = 'click';
123 events.click(trigger, e);
124 }
125 },
126 mouseenter(trigger, e) {
127 triggerMessage(trigger, e);
128 },
129 mouseleave(trigger, e) {
130 triggerMessage(trigger, e);
131 },
132 focus(trigger, e) {
133 triggerMessage(trigger, e);
134 },
135 blur(trigger, e) {
136 if (focusingToControlledElement(trigger, e)){
137 return;
138 }
139 triggerMessage(trigger, e);
140 }
141};
142
143Object.keys(events).forEach(function(name) {
144 const handler = events[name];
145 $(document).on(`${name}.aui-trigger`, '[data-aui-trigger]', function(e) {
146 handler(e.currentTarget, msg(e, name));
147 });
148});
149
150skate('data-aui-trigger', {
151 type: skate.type.ATTRIBUTE,
152 prototype: {
153 disable: function () {
154 this.setAttribute('aria-disabled', 'true');
155 },
156 enable: function () {
157 this.setAttribute('aria-disabled', 'false');
158 },
159 isEnabled: function () {
160 return isEnabled(this);
161 }
162 }
163});
164
165amdify('aui/trigger');