1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This file contains functions that control several animations in the print
6// preview tab (scrollbars, showing hiding options, resizing).
7
8// Scrollbar will never get smaller than this
9const TRANSIENT_MIN_SCROLLBAR_SIZE = 40;
10//  Timeout duration in milliseconds used for showing the scrollbars.
11const HIDE_SCROLLBARS_TIMEOUT = 250;
12// Timeout id used to reset the timer when needed.
13var transientHideScrollbarsTimeoutId = [];
14//  Holds all elements that have scrollbars attached to them.
15var transientScrollbarEls = [];
16// Counter used to give webkit animations unique names.
17var animationCounter = 0;
18
19/**
20 * Makes the scrollbars visible. If |el| has already a scrollbar timer, it
21 * resets its value.
22 *
23 * @param {HTMLDivElement} el The element associated with the scrollbars
24 */
25function showTransientScrollbars(el) {
26  el.scrollHorEl.classList.remove('invisible');
27  el.scrollVertEl.classList.remove('invisible');
28
29  if (el.transientHideScrollbarsTimeoutId)
30    window.clearTimeout(el.transientHideScrollbarsTimeoutId);
31
32  el.transientHideScrollbarsTimeoutId =
33      window.setTimeout(function() { hideTransientScrollbars(el) },
34                        HIDE_SCROLLBARS_TIMEOUT);
35}
36
37/**
38 * Hides the scrollbars.
39 *
40 * @param {HTMLElement} el The element associated with the scrollbars
41*/
42function hideTransientScrollbars(el) {
43  el.scrollHorEl.classList.add('invisible');
44  el.scrollVertEl.classList.add('invisible');
45}
46
47/**
48 * Handles a mouse move event, takes care of updating the scrollbars.
49 *
50 * @param {event} event The event that triggered this handler
51*/
52function handleTransientMouseMove(event) {
53  var el = event.target;
54
55  while (!el.classList.contains('scrollbar-inside') && el != document.body)
56    el = el.parentNode;
57
58  showTransientScrollbars(el);
59}
60
61/**
62 * Updates the scrollbars associated with the the input element.
63 *
64 * @param {HTMLElement} el
65 */
66function updateTransientScrollbars(el) {
67  var scrollLeft = el.scrollLeft;
68  var scrollTop = el.scrollTop;
69
70  var scrollWidth = el.scrollWidth;
71  var scrollHeight = el.scrollHeight;
72
73  var offsetWidth = el.offsetWidth - el.scrollbarWidth;
74  var offsetHeight = el.offsetHeight - el.scrollbarHeight;
75
76  var elevatorWidth = offsetWidth / scrollWidth * offsetWidth;
77  var elevatorHeight = offsetHeight / scrollHeight * offsetHeight;
78
79  // Make sure the scrollbars are big enough.
80  if (elevatorWidth < TRANSIENT_MIN_SCROLLBAR_SIZE)
81    elevatorWidth = TRANSIENT_MIN_SCROLLBAR_SIZE;
82  if (elevatorHeight < TRANSIENT_MIN_SCROLLBAR_SIZE)
83    elevatorHeight = TRANSIENT_MIN_SCROLLBAR_SIZE;
84
85  if (offsetWidth >= scrollWidth) {
86    if (!el.scrollHorEl.classList.contains('hidden'))
87      el.scrollHorEl.classList.add('hidden');
88  } else {
89    if (el.scrollHorEl.classList.contains('hidden'))
90      el.scrollHorEl.classList.remove('hidden');
91    var x = scrollLeft / (scrollWidth - offsetWidth);
92
93    // TODO(mwichary): 6 shouldnât be hardcoded
94    el.scrollHorEl.style.left = (x * (offsetWidth - elevatorWidth - 6)) + 'px';
95    el.scrollHorEl.style.width = elevatorWidth + 'px';
96  }
97
98  if (offsetHeight >= scrollHeight) {
99    if (!el.scrollVertEl.classList.contains('hidden'))
100      el.scrollVertEl.classList.add('hidden');
101  } else {
102    if (el.scrollVertEl.classList.contains('hidden'))
103      el.scrollVertEl.classList.remove('hidden');
104
105    var y = scrollTop / (scrollHeight - offsetHeight);
106
107    // TODO(mwichary): 6 shouldnât be hardcoded
108    el.scrollVertEl.style.top =
109        (y * (offsetHeight - elevatorHeight - 6)) + 'px';
110    el.scrollVertEl.style.height = elevatorHeight + 'px';
111  }
112}
113
114/**
115 * Updates all exising scrollbars.
116 */
117function updateAllTransientScrollbars() {
118  for (var i = 0; i < transientScrollbarEls.length; i++) {
119    var el = transientScrollbarEls[i];
120    updateTransientScrollbars(el);
121  }
122}
123
124/**
125 * Handles a scroll event.
126 */
127function handleTransientScroll(event) {
128  var el = event.target;
129
130  if (el == document)
131    el = document.body;
132
133  updateTransientScrollbars(el);
134
135  // Make sure to show the scrollbars if they are hidden.
136  showTransientScrollbars(el);
137}
138
139/**
140 * Adds scrollbars to the input element.
141 *
142 * @param {HTMLElement} scrollableEl The scrollable element
143 */
144function addTransientScrollbars(scrollableEl) {
145  var insideEl = scrollableEl.querySelector('.scrollbar-inside');
146
147  if (!insideEl)
148    insideEl = scrollableEl;
149
150  // Determine the width/height of native scrollbar elements.
151  insideEl.scrollbarWidth = insideEl.offsetWidth - insideEl.clientWidth;
152  insideEl.scrollbarHeight = insideEl.offsetHeight - insideEl.clientHeight;
153
154  // Create scrollbar elements
155  insideEl.scrollHorEl = document.createElement('div');
156  insideEl.scrollHorEl.className = 'scrollbar hor';
157  scrollableEl.appendChild(insideEl.scrollHorEl);
158
159  insideEl.scrollVertEl = document.createElement('div');
160  insideEl.scrollVertEl.className = 'scrollbar vert';
161  scrollableEl.appendChild(insideEl.scrollVertEl);
162
163  // Need to make sure the scrollbars are absolutely positioned vis-a-vis
164  // their parent element. If not, make its position relative.
165  if (insideEl.scrollVertEl.offsetParent != scrollableEl)
166    scrollableEl.style.position = 'relative';
167
168  if (insideEl == document.body)
169    window.addEventListener('scroll', handleTransientScroll, false);
170  else
171    insideEl.addEventListener('scroll', handleTransientScroll, false);
172  insideEl.addEventListener('mousemove', handleTransientMouseMove, false);
173
174  updateTransientScrollbars(insideEl);
175  showTransientScrollbars(insideEl);
176
177  transientScrollbarEls.push(insideEl);
178}
179
180/**
181 * Creates webkit animation as specified by |code.
182 *
183 * @param {string} code The code specifying the animation.
184 */
185function addAnimation(code) {
186  var name = 'anim' + animationCounter;
187  animationCounter++;
188
189  var rules = document.createTextNode(
190      '@-webkit-keyframes ' + name + ' {' + code + '}');
191
192  var el = document.createElement('style');
193  el.type = 'text/css';
194  el.appendChild(rules);
195  document.body.appendChild(el);
196
197  return name;
198}
199
200/**
201 * Shows/hides elements of class "hidden-section" depending on their
202 * current state.
203 *
204 * @param {HTLMElement} el The element to be shown/hidden
205 */
206function handleZippyClickEl(el) {
207  while (el.tagName != 'LI' && !el.classList.contains('hidden-section'))
208    el = el.parentNode;
209
210  var extraEl = el.querySelector('.extra');
211
212  if (!el.classList.contains('opened')) {
213    extraEl.style.height = 'auto';
214    var height = extraEl.offsetHeight;
215
216    extraEl.style.height = height + 'px';
217
218    el.classList.add('opened');
219
220    var animName = addAnimation(
221      '0% { opacity: 0; height: 0; padding-top: 0; } ' +
222      '80% { height: ' + (height + 2) + 'px; padding-top: 12px; }' +
223      '100% { opacity: 1; height: ' + height + 'px; padding-top: 10px; }');
224
225    extraEl.style.webkitAnimationName = animName;
226
227  } else {
228    extraEl.style.webkitAnimationName = '';
229    extraEl.style.height = 'auto';
230    el.classList.remove('opened');
231    el.classList.add('closing');
232
233    // DEBUG
234    window.setTimeout(
235      function() { handleZippyClickCleanup(el, extraEl); }, 120);
236  }
237
238  window.setTimeout(updateAllTransientScrollbars, 100);
239}
240
241/**
242 * Cleans up the show/hide animation of function handleZippyClickEl().
243 */
244function handleZippyClickCleanup(el, extraEl) {
245  extraEl.style.height = '';
246  el.classList.remove('closing');
247}
248
249/**
250 * Handler for the load event.
251 */
252function handleBodyLoad() {
253  document.body.classList.add('loaded');
254}
255
256/**
257 * Initializes the scrollbars animation.
258 */
259function initializeAnimation() {
260  if (document.querySelector('body > .sidebar'))
261    addTransientScrollbars(document.querySelector('body > .sidebar'));
262  else
263    addTransientScrollbars(document.body);
264
265  window.addEventListener('resize', updateAllTransientScrollbars, false);
266  window.addEventListener('load', handleBodyLoad, false);
267}
268