-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathview.js
147 lines (130 loc) · 5.24 KB
/
view.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*!
* view.js
* Swap visible elements in a single call.
*
* Copyright (c) 2013-2014 Tony Ross
* Released under the MIT license
* http://github.com/antross/view
*/
(function(win, doc){
'use strict';
var _hidden = 'hidden';
// Approximate `setImmediate` support until a simple, cross-browser option exists
var setImmediate = win.setImmediate || function(fn) { setTimeout(fn, 0) };
// Create a `view` instance with the provided `root` as the default context
function _scope(root) {
// Show a set of elements and hide their siblings.
function view(targets, options) {
_iterate(targets, _view, root, options);
}
// Hide elements without showing others.
view.hide = function(targets, options) {
_iterate(targets, _hide, root, options);
};
// Show elements without hiding others.
view.show = function(targets, options) {
_iterate(targets, _show, root, options);
};
// Toggle the visibility of elements without affecting siblings.
view.toggle = function (targets, options) {
_iterate(targets, _toggle, root, options);
};
// Obtain a `view` scoped to a given root.
view.scope = function(root) {
// Verify root can contain and query elements
if(!root || !root.querySelectorAll) {
throw new Error('[view.js] The `root` passed to `view.scope` must support `querySelectorAll`.');
}
return _scope(root);
};
return view;
}
// Convert a selector into a collection of targets
function _find(selector, root) {
var targets = [].slice.call(root.querySelectorAll(selector));
// Facilitate debugging mis-typed selectors
if(!targets.length) {
throw new Error('[view.js] No targets found for "' + selector + '".');
}
// Return the converted target list
return targets;
}
// Ensure the target element is hidden
function _hide(target) {
if(!target.hasAttribute(_hidden)) {
target.setAttribute(_hidden, '');
_notify(target, 'view:hide');
// Notify visible descendants
[].forEach.call(target.querySelectorAll('[hidden] ~ :not([hidden])'), function(item) {
_notify(item, 'view:hide');
});
}
}
// Loop over and process the arguments passed to `view`, `view.show`, or `view.hide`
function _iterate(targets, fn, root, options) {
if(!options) options = {};
// Extract data if provided
var data = options.data;
// Disambiguate target formats to produce a list of target references
if(typeof targets == 'string') {
targets = _find(targets, root);
} else if(!targets.length || targets instanceof Element) {
targets = [targets];
}
// Iterate and call our provided function on each matched target
targets.forEach(function(target) {
fn(target, targets, data, options);
});
}
// Create and dispatch a DOM event when elements are shown/hidden
function _notify(element, name, detail) {
var event = doc.createEvent('CustomEvent');
event.initCustomEvent(name, false, false, detail || {});
setImmediate(function(){ element.dispatchEvent(event) });
}
// Ensure the target element is visible
function _show(target, targets, data) {
if(target.hasAttribute(_hidden)) {
target.removeAttribute(_hidden);
_notify(target, 'view:show', {data: data});
// Notify visible descendants
[].forEach.call(target.querySelectorAll('[hidden] ~ :not([hidden])'), function(item) {
_notify(item, 'view:show');
});
}
}
// Toggle the visibility of the target element
function _toggle(target, targets, data) {
if (target.hasAttribute(_hidden)) {
_show(target, targets, data);
} else {
_hide(target, targets, data);
}
}
// Ensure the target element is visible and hide siblings.
// Optionally ensure ancestors are also visible.
function _view(target, targets, data, options) {
// Show the target element
_show(target, targets, data);
// Walk all siblings to ensure only the target element is visible
var parent = target.parentNode;
var next = parent && parent.firstElementChild;
while(next) {
// Hide siblings that are not also in the targets list
if(targets.indexOf(next) == -1) {
_hide(next);
}
next = next.nextElementSibling;
}
// Check if we should force full visibility of the target
if(options.deep && parent && parent.hasAttribute) {
// Walk the parent chain to ensure each ancestor is visible
_view(parent, targets, null, options);
}
}
// Expose the `view` function globally and via AMD if available
var view = win.view = _scope(doc);
if(typeof define === "function") {
define(function(){ return view });
}
})(this, this.document);