1 // Copyright 2013 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.
6 * Prototype for first-run tutorial steps.
9 cr
.define('cr.FirstRun', function() {
10 var Step
= cr
.ui
.define('div');
13 __proto__
: HTMLDivElement
.prototype,
18 // Button leading to next tutorial step.
21 // Default control for this step.
22 defaultControl_
: null,
24 decorate: function() {
25 this.name_
= this.getAttribute('id');
26 var controlsContainer
= this.getElementsByClassName('controls')[0];
27 if (!controlsContainer
)
28 throw Error('Controls not found.');
30 controlsContainer
.getElementsByClassName('next-button')[0];
31 if (!this.nextButton_
)
32 throw Error('Next button not found.');
33 this.nextButton_
.addEventListener('click', (function(e
) {
34 chrome
.send('nextButtonClicked', [this.getName()]);
37 this.defaultControl_
= controlsContainer
.children
[0];
41 * Returns name of the string.
49 * @param {boolean} animated Whether transition should be animated.
50 * @param {function()=} opt_onHidden Called after step has been hidden.
52 hide: function(animated
, opt_onHidden
) {
53 var transitionDuration
=
54 animated
? cr
.FirstRun
.getDefaultTransitionDuration() : 0;
55 changeVisibility(this,
59 this.classList
.add('hidden');
67 * @param {boolean} animated Whether transition should be animated.
68 * @param {function(Step)=} opt_onShown Called after step has been shown.
70 show: function(animated
, opt_onShown
) {
71 var transitionDuration
=
72 animated
? cr
.FirstRun
.getDefaultTransitionDuration() : 0;
73 this.classList
.remove('hidden');
74 changeVisibility(this,
84 * Sets position of the step.
85 * @param {object} position Parameter with optional fields |top|,
86 * |right|, |bottom|, |left| holding corresponding offsets.
88 setPosition: function(position
) {
89 var style
= this.style
;
90 ['top', 'right', 'bottom', 'left'].forEach(function(property
) {
91 if (position
.hasOwnProperty(property
))
92 style
.setProperty(property
, position
[property
] + 'px');
97 * Makes default control focused. Default control is a first control in
98 * current implementation.
100 focusDefaultControl: function() {
101 this.defaultControl_
.focus();
105 var Bubble
= cr
.ui
.define('div');
107 // List of rules declaring bubble's arrow position depending on text direction
108 // and shelf alignment. Every rule has required field |position| with list
109 // of classes that should be applied to arrow element if this rule choosen.
110 // The rule is suitable if its |shelf| and |dir| fields are correspond
111 // to current shelf alignment and text direction. Missing fields behaves like
112 // '*' wildcard. The last suitable rule in list is choosen for arrow style.
113 var ARROW_POSITION
= {
116 position
: ['points-down', 'left']
120 position
: ['points-down', 'right']
124 position
: ['points-left', 'top']
128 position
: ['points-right', 'top']
133 position
: ['points-right', 'top']
138 position
: ['points-left', 'top']
142 position
: ['points-left', 'top']
147 position
: ['points-right', 'bottom']
152 position
: ['points-left', 'bottom']
156 position
: ['points-left', 'bottom']
161 var DISTANCE_TO_POINTEE
= 10;
162 var MINIMAL_SCREEN_OFFSET
= 10;
163 var ARROW_LENGTH
= 6; // Keep synced with .arrow border-width.
166 __proto__
: Step
.prototype,
168 // Element displaying arrow.
171 // Unit vector directed along the bubble arrow.
175 * In addition to base class 'decorate' this method creates arrow and
176 * sets some properties related to arrow.
178 decorate: function() {
179 Step
.prototype.decorate
.call(this);
180 this.arrow_
= document
.createElement('div');
181 this.arrow_
.classList
.add('arrow');
182 this.appendChild(this.arrow_
);
183 var inputDirection
= document
.documentElement
.getAttribute('dir');
184 var shelfAlignment
= document
.documentElement
.getAttribute('shelf');
185 var isSuitable = function(rule
) {
186 var inputDirectionMatch
= !rule
.hasOwnProperty('dir') ||
187 rule
.dir
=== inputDirection
;
188 var shelfAlignmentMatch
= !rule
.hasOwnProperty('shelf') ||
189 rule
.shelf
=== shelfAlignment
;
190 return inputDirectionMatch
&& shelfAlignmentMatch
;
192 var lastSuitableRule
= null;
193 var rules
= ARROW_POSITION
[this.getName()];
194 rules
.forEach(function(rule
) {
195 if (isSuitable(rule
))
196 lastSuitableRule
= rule
;
198 assert(lastSuitableRule
);
199 lastSuitableRule
.position
.forEach(function(cls
) {
200 this.arrow_
.classList
.add(cls
);
202 var list
= this.arrow_
.classList
;
203 if (list
.contains('points-up'))
204 this.direction_
= [0, -1];
205 else if (list
.contains('points-right'))
206 this.direction_
= [1, 0];
207 else if (list
.contains('points-down'))
208 this.direction_
= [0, 1];
209 else // list.contains('points-left')
210 this.direction_
= [-1, 0];
214 * Sets position of bubble in such a maner that bubble's arrow points to
216 * @param {Array} point Bubble arrow should point to this point after
217 * positioning. |point| has format [x, y].
218 * @param {offset} number Additional offset from |point|.
220 setPointsTo: function(point
, offset
) {
221 var shouldShowBefore
= this.hidden
;
222 // "Showing" bubble in order to make offset* methods work.
223 if (shouldShowBefore
) {
224 this.style
.setProperty('opacity', '0');
227 var arrow
= [this.arrow_
.offsetLeft
+ this.arrow_
.offsetWidth
/ 2,
228 this.arrow_
.offsetTop
+ this.arrow_
.offsetHeight
/ 2];
229 var totalOffset
= DISTANCE_TO_POINTEE
+ offset
;
230 var left
= point
[0] - totalOffset
* this.direction_
[0] - arrow
[0];
231 var top
= point
[1] - totalOffset
* this.direction_
[1] - arrow
[1];
232 // Force bubble to be inside screen.
233 if (this.arrow_
.classList
.contains('points-up') ||
234 this.arrow_
.classList
.contains('points-down')) {
235 left
= Math
.max(left
, MINIMAL_SCREEN_OFFSET
);
236 left
= Math
.min(left
, document
.body
.offsetWidth
- this.offsetWidth
-
237 MINIMAL_SCREEN_OFFSET
);
239 if (this.arrow_
.classList
.contains('points-left') ||
240 this.arrow_
.classList
.contains('points-right')) {
241 top
= Math
.max(top
, MINIMAL_SCREEN_OFFSET
);
242 top
= Math
.min(top
, document
.body
.offsetHeight
- this.offsetHeight
-
243 MINIMAL_SCREEN_OFFSET
);
245 this.style
.setProperty('left', left
+ 'px');
246 this.style
.setProperty('top', top
+ 'px');
247 if (shouldShowBefore
) {
249 this.style
.removeProperty('opacity');
254 * Sets position of bubble. Overrides Step.setPosition to adjust offsets
255 * in case if its direction is the same as arrow's direction.
256 * @param {object} position Parameter with optional fields |top|,
257 * |right|, |bottom|, |left| holding corresponding offsets.
259 setPosition: function(position
) {
260 var arrow
= this.arrow_
;
261 // Increasing offset if it's from side where bubble points to.
262 [['top', 'points-up'],
263 ['right', 'points-right'],
264 ['bottom', 'points-down'],
265 ['left', 'points-left']].forEach(function(mapping
) {
266 if (position
.hasOwnProperty(mapping
[0]) &&
267 arrow
.classList
.contains(mapping
[1])) {
268 position
[mapping
[0]] += ARROW_LENGTH
+ DISTANCE_TO_POINTEE
;
271 Step
.prototype.setPosition
.call(this, position
);
275 var HelpStep
= cr
.ui
.define('div');
277 HelpStep
.prototype = {
278 __proto__
: Bubble
.prototype,
280 decorate: function() {
281 Bubble
.prototype.decorate
.call(this);
282 var helpButton
= this.getElementsByClassName('help-button')[0];
283 helpButton
.addEventListener('click', function(e
) {
284 chrome
.send('helpButtonClicked');
290 var DecorateStep = function(el
) {
292 HelpStep
.decorate(el
);
293 else if (el
.classList
.contains('bubble'))
299 return {DecorateStep
: DecorateStep
};