Update broken references to image assets
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / login / screen_supervised_user_creation.js
blobcc0bc952e8e39b79efe49bd3dacedbe3fb8a5ddb
1 // Copyright 2014 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.
5 /**
6 * @fileoverview Legacy supervised user creation flow screen.
7 */
9 login.createScreen('SupervisedUserCreationScreen',
10 'supervised-user-creation', function() {
11 var MAX_NAME_LENGTH = 50;
12 var UserImagesGrid = options.UserImagesGrid;
13 var ButtonImages = UserImagesGrid.ButtonImages;
15 var ManagerPod = cr.ui.define(function() {
16 var node = $('supervised-user-creation-manager-template').cloneNode(true);
17 node.removeAttribute('id');
18 node.removeAttribute('hidden');
19 return node;
20 });
22 ManagerPod.userImageSalt_ = {};
24 /**
25 * UI element for displaying single account in list of possible managers for
26 * new supervised user.
27 * @type {Object}
29 ManagerPod.prototype = {
30 __proto__: HTMLDivElement.prototype,
32 /** @override */
33 decorate: function() {
34 // Mousedown has to be used instead of click to be able to prevent 'focus'
35 // event later.
36 this.addEventListener('mousedown',
37 this.handleMouseDown_.bind(this));
38 var screen = $('supervised-user-creation');
39 var managerPod = this;
40 var managerPodList = screen.managerList_;
41 var hideManagerPasswordError = function(element) {
42 managerPod.passwordElement.classList.remove('password-error');
43 $('bubble').hide();
46 screen.configureTextInput(
47 this.passwordElement,
48 screen.updateNextButtonForManager_.bind(screen),
49 screen.validIfNotEmpty_.bind(screen),
50 function(element) {
51 screen.getScreenButton('next').focus();
53 hideManagerPasswordError);
55 this.passwordElement.addEventListener('keydown', function(e) {
56 switch (e.keyIdentifier) {
57 case 'Up':
58 managerPodList.selectNextPod(-1);
59 e.stopPropagation();
60 break;
61 case 'Down':
62 managerPodList.selectNextPod(+1);
63 e.stopPropagation();
64 break;
66 });
69 /**
70 * Updates UI elements from user data.
72 update: function() {
73 this.imageElement.src = 'chrome://userimage/' + this.user.username +
74 '?id=' + ManagerPod.userImageSalt_[this.user.username];
76 this.nameElement.textContent = this.user.displayName;
77 this.emailElement.textContent = this.user.emailAddress;
80 showPasswordError: function() {
81 this.passwordElement.classList.add('password-error');
82 $('bubble').showTextForElement(
83 this.passwordElement,
84 loadTimeData.getString(
85 'createSupervisedUserWrongManagerPasswordText'),
86 cr.ui.Bubble.Attachment.BOTTOM,
87 24, 4);
90 /**
91 * Brings focus to password field.
93 focusInput: function() {
94 this.passwordElement.focus();
97 /**
98 * Gets image element.
99 * @type {!HTMLImageElement}
101 get imageElement() {
102 return this.querySelector('.supervised-user-creation-manager-image');
106 * Gets name element.
107 * @type {!HTMLDivElement}
109 get nameElement() {
110 return this.querySelector('.supervised-user-creation-manager-name');
114 * Gets e-mail element.
115 * @type {!HTMLDivElement}
117 get emailElement() {
118 return this.querySelector('.supervised-user-creation-manager-email');
122 * Gets password element.
123 * @type {!HTMLDivElement}
125 get passwordElement() {
126 return this.querySelector('.supervised-user-creation-manager-password');
130 * Gets password enclosing block.
131 * @type {!HTMLDivElement}
133 get passwordBlock() {
134 return this.querySelector('.password-block');
137 /** @override */
138 handleMouseDown_: function(e) {
139 this.parentNode.selectPod(this);
140 // Prevent default so that we don't trigger 'focus' event.
141 e.preventDefault();
145 * The user that this pod represents.
146 * @type {!Object}
148 user_: undefined,
149 get user() {
150 return this.user_;
152 set user(userDict) {
153 this.user_ = userDict;
154 this.update();
158 var ManagerPodList = cr.ui.define('div');
161 * UI element for selecting manager account for new supervised user.
162 * @type {Object}
164 ManagerPodList.prototype = {
165 __proto__: HTMLDivElement.prototype,
167 selectedPod_: null,
169 /** @override */
170 decorate: function() {
174 * Returns all the pods in this pod list.
175 * @type {NodeList}
177 get pods() {
178 return this.children;
181 addPod: function(manager) {
182 var managerPod = new ManagerPod({user: manager});
183 this.appendChild(managerPod);
184 managerPod.update();
187 clearPods: function() {
188 this.innerHTML = '';
189 this.selectedPod_ = null;
192 selectPod: function(podToSelect) {
193 if ((this.selectedPod_ == podToSelect) && !!podToSelect) {
194 podToSelect.focusInput();
195 return;
197 this.selectedPod_ = podToSelect;
198 for (var i = 0, pod; pod = this.pods[i]; ++i) {
199 if (pod != podToSelect) {
200 pod.classList.remove('focused');
201 pod.passwordElement.value = '';
202 pod.passwordBlock.hidden = true;
205 if (!podToSelect)
206 return;
207 podToSelect.classList.add('focused');
208 podToSelect.passwordBlock.hidden = false;
209 podToSelect.passwordElement.value = '';
210 podToSelect.focusInput();
211 chrome.send('managerSelectedOnSupervisedUserCreationFlow',
212 [podToSelect.user.username]);
216 * Select pod next to currently selected one in given |direction|.
217 * @param {integer} direction - +1 for selecting pod below current, -1 for
218 * selecting pod above current.
219 * @type {boolean} returns if selected pod has changed.
221 selectNextPod: function(direction) {
222 if (!this.selectedPod_)
223 return false;
224 var index = -1;
225 for (var i = 0, pod; pod = this.pods[i]; ++i) {
226 if (pod == this.selectedPod_) {
227 index = i;
228 break;
231 if (-1 == index)
232 return false;
233 index = index + direction;
234 if (index < 0 || index >= this.pods.length)
235 return false;
236 this.selectPod(this.pods[index]);
237 return true;
241 var ImportPod = cr.ui.define(function() {
242 var node = $('supervised-user-creation-import-template').cloneNode(true);
243 node.removeAttribute('id');
244 node.removeAttribute('hidden');
245 return node;
249 * UI element for displaying single supervised user in list of possible users
250 * for importing existing users.
251 * @type {Object}
253 ImportPod.prototype = {
254 __proto__: HTMLDivElement.prototype,
256 /** @override */
257 decorate: function() {
258 // Mousedown has to be used instead of click to be able to prevent 'focus'
259 // event later.
260 this.addEventListener('mousedown', this.handleMouseDown_.bind(this));
261 var screen = $('supervised-user-creation');
262 var importList = screen.importList_;
266 * Updates UI elements from user data.
268 update: function() {
269 this.imageElement.src = this.user.avatarurl;
270 this.nameElement.textContent = this.user.name;
271 if (this.user.exists) {
272 if (this.user.conflict == 'imported') {
273 this.nameElement.textContent =
274 loadTimeData.getStringF('importUserExists', this.user.name);
275 } else {
276 this.nameElement.textContent =
277 loadTimeData.getStringF('importUsernameExists', this.user.name);
280 this.classList.toggle('imported', this.user.exists);
284 * Gets image element.
285 * @type {!HTMLImageElement}
287 get imageElement() {
288 return this.querySelector('.import-pod-image');
292 * Gets name element.
293 * @type {!HTMLDivElement}
295 get nameElement() {
296 return this.querySelector('.import-pod-name');
299 /** @override */
300 handleMouseDown_: function(e) {
301 this.parentNode.selectPod(this);
302 // Prevent default so that we don't trigger 'focus' event.
303 e.preventDefault();
307 * The user that this pod represents.
308 * @type {Object}
310 user_: undefined,
312 get user() {
313 return this.user_;
316 set user(userDict) {
317 this.user_ = userDict;
318 this.update();
322 var ImportPodList = cr.ui.define('div');
325 * UI element for selecting existing supervised user for import.
326 * @type {Object}
328 ImportPodList.prototype = {
329 __proto__: HTMLDivElement.prototype,
331 selectedPod_: null,
333 /** @override */
334 decorate: function() {
335 this.setAttribute('tabIndex', 0);
336 this.classList.add('nofocus');
337 var importList = this;
338 var screen = $('supervised-user-creation');
340 this.addEventListener('focus', function(e) {
341 if (importList.selectedPod_ == null) {
342 if (importList.pods.length > 0)
343 importList.selectPod(importList.pods[0]);
347 this.addEventListener('keydown', function(e) {
348 switch (e.keyIdentifier) {
349 case 'Up':
350 importList.selectNextPod(-1);
351 e.stopPropagation();
352 break;
353 case 'Enter':
354 if (importList.selectedPod_ != null)
355 screen.importSupervisedUser_();
356 e.stopPropagation();
357 break;
358 case 'Down':
359 importList.selectNextPod(+1);
360 e.stopPropagation();
361 break;
367 * Returns all the pods in this pod list.
368 * @type {NodeList}
370 get pods() {
371 return this.children;
375 * Returns selected pod.
376 * @type {Node}
378 get selectedPod() {
379 return this.selectedPod_;
382 addPod: function(user) {
383 var importPod = new ImportPod({user: user});
384 this.appendChild(importPod);
385 importPod.update();
388 clearPods: function() {
389 this.innerHTML = '';
390 this.selectedPod_ = null;
393 scrollIntoView: function(pod) {
394 scroller = this.parentNode;
395 var itemHeight = pod.getBoundingClientRect().height;
396 var scrollTop = scroller.scrollTop;
397 var top = pod.offsetTop - scroller.offsetTop;
398 var clientHeight = scroller.clientHeight;
400 var self = scroller;
402 // Function to adjust the tops of viewport and row.
403 function scrollToAdjustTop() {
404 self.scrollTop = top;
405 return true;
407 // Function to adjust the bottoms of viewport and row.
408 function scrollToAdjustBottom() {
409 var cs = getComputedStyle(self);
410 var paddingY = parseInt(cs.paddingTop, 10) +
411 parseInt(cs.paddingBottom, 10);
413 if (top + itemHeight > scrollTop + clientHeight - paddingY) {
414 self.scrollTop = top + itemHeight - clientHeight + paddingY;
415 return true;
417 return false;
420 // Check if the entire of given indexed row can be shown in the viewport.
421 if (itemHeight <= clientHeight) {
422 if (top < scrollTop)
423 return scrollToAdjustTop();
424 if (scrollTop + clientHeight < top + itemHeight)
425 return scrollToAdjustBottom();
426 } else {
427 if (scrollTop < top)
428 return scrollToAdjustTop();
429 if (top + itemHeight < scrollTop + clientHeight)
430 return scrollToAdjustBottom();
432 return false;
436 * @param {Element} podToSelect - pod to select, can be null.
438 selectPod: function(podToSelect) {
439 if ((this.selectedPod_ == podToSelect) && !!podToSelect) {
440 return;
442 this.selectedPod_ = podToSelect;
443 for (var i = 0; i < this.pods.length; i++) {
444 var pod = this.pods[i];
445 if (pod != podToSelect)
446 pod.classList.remove('focused');
448 if (!podToSelect)
449 return;
450 podToSelect.classList.add('focused');
451 podToSelect.focus();
452 var screen = $('supervised-user-creation');
453 if (!this.selectedPod_) {
454 screen.getScreenButton('import').disabled = true;
455 } else {
456 screen.getScreenButton('import').disabled =
457 this.selectedPod_.user.exists;
458 if (!this.selectedPod_.user.exists) {
459 chrome.send('userSelectedForImportInSupervisedUserCreationFlow',
460 [podToSelect.user.id]);
465 selectNextPod: function(direction) {
466 if (!this.selectedPod_)
467 return false;
468 var index = -1;
469 for (var i = 0, pod; pod = this.pods[i]; ++i) {
470 if (pod == this.selectedPod_) {
471 index = i;
472 break;
475 if (-1 == index)
476 return false;
477 index = index + direction;
478 if (index < 0 || index >= this.pods.length)
479 return false;
480 this.selectPod(this.pods[index]);
481 return true;
484 selectUser: function(user_id) {
485 for (var i = 0, pod; pod = this.pods[i]; ++i) {
486 if (pod.user.id == user_id) {
487 this.selectPod(pod);
488 this.scrollIntoView(pod);
489 break;
495 return {
496 EXTERNAL_API: [
497 'loadManagers',
498 'setCameraPresent',
499 'setDefaultImages',
500 'setExistingSupervisedUsers',
501 'showErrorPage',
502 'showIntroPage',
503 'showManagerPage',
504 'showManagerPasswordError',
505 'showPage',
506 'showPasswordError',
507 'showProgress',
508 'showStatusError',
509 'showTutorialPage',
510 'showUsernamePage',
511 'supervisedUserNameError',
512 'supervisedUserNameOk',
513 'supervisedUserSuggestImport',
516 lastVerifiedName_: null,
517 lastIncorrectUserName_: null,
518 managerList_: null,
519 importList_: null,
521 currentPage_: null,
522 imagesRequested_: false,
524 // Contains data that can be auto-shared with handler.
525 context_: {},
527 /** @override */
528 decorate: function() {
529 this.managerList_ = new ManagerPodList();
530 $('supervised-user-creation-managers-pane').appendChild(
531 this.managerList_);
533 this.importList_ = new ImportPodList();
534 $('supervised-user-creation-import-pane').appendChild(this.importList_);
536 var userNameField = $('supervised-user-creation-name');
537 var passwordField = $('supervised-user-creation-password');
538 var password2Field = $('supervised-user-creation-password-confirm');
540 var creationScreen = this;
542 var hideUserPasswordError = function(element) {
543 $('bubble').hide();
544 $('supervised-user-creation-password').classList.remove(
545 'password-error');
548 this.configureTextInput(userNameField,
549 this.checkUserName_.bind(this),
550 this.validIfNotEmpty_.bind(this),
551 function(element) {
552 passwordField.focus();
554 this.clearUserNameError_.bind(this));
555 this.configureTextInput(passwordField,
556 this.updateNextButtonForUser_.bind(this),
557 this.validIfNotEmpty_.bind(this),
558 function(element) {
559 password2Field.focus();
561 hideUserPasswordError);
562 this.configureTextInput(password2Field,
563 this.updateNextButtonForUser_.bind(this),
564 this.validIfNotEmpty_.bind(this),
565 function(element) {
566 creationScreen.getScreenButton('next').focus();
568 hideUserPasswordError);
570 this.getScreenButton('error').addEventListener('click', function(e) {
571 creationScreen.handleErrorButtonPressed_();
572 e.stopPropagation();
576 TODO(antrim) : this is an explicit code duplications with UserImageScreen.
577 It should be removed by issue 251179.
579 var imageGrid = this.getScreenElement('image-grid');
580 UserImagesGrid.decorate(imageGrid);
582 // Preview image will track the selected item's URL.
583 var previewElement = this.getScreenElement('image-preview');
584 previewElement.oncontextmenu = function(e) { e.preventDefault(); };
586 imageGrid.previewElement = previewElement;
587 imageGrid.selectionType = 'default';
588 imageGrid.flipPhotoElement = this.getScreenElement('flip-photo');
590 imageGrid.addEventListener('activate',
591 this.handleActivate_.bind(this));
592 imageGrid.addEventListener('select',
593 this.handleSelect_.bind(this));
594 imageGrid.addEventListener('phototaken',
595 this.handlePhotoTaken_.bind(this));
596 imageGrid.addEventListener('photoupdated',
597 this.handlePhotoUpdated_.bind(this));
598 // Set the title for camera item in the grid.
599 imageGrid.setCameraTitles(
600 loadTimeData.getString('takePhoto'),
601 loadTimeData.getString('photoFromCamera'));
603 this.getScreenElement('take-photo').addEventListener(
604 'click', this.handleTakePhoto_.bind(this));
605 this.getScreenElement('discard-photo').addEventListener(
606 'click', this.handleDiscardPhoto_.bind(this));
608 // Toggle 'animation' class for the duration of WebKit transition.
609 this.getScreenElement('flip-photo').addEventListener(
610 'click', this.handleFlipPhoto_.bind(this));
611 this.getScreenElement('image-stream-crop').addEventListener(
612 'webkitTransitionEnd', function(e) {
613 previewElement.classList.remove('animation');
615 this.getScreenElement('image-preview-img').addEventListener(
616 'webkitTransitionEnd', function(e) {
617 previewElement.classList.remove('animation');
620 $('supervised-user-creation-close-button-item').addEventListener(
621 'click', function(e) {
622 this.cancel();
623 e.preventDefault();
624 }.bind(this));
627 buttonIds: [],
630 * Creates button for adding to controls.
631 * @param {string} buttonId -- id for button, have to be unique within
632 * screen. Actual id will be prefixed with screen name and appended with
633 * '-button'. Use getScreenButton(buttonId) to find it later.
634 * @param {string} i18nPrefix -- screen prefix for i18n values.
635 * @param {function} callback -- will be called on button press with
636 * buttonId parameter.
637 * @param {array} pages -- list of pages where this button should be
638 * displayed.
639 * @param {array} classes -- list of additional CSS classes for button.
641 makeButton: function(buttonId, i18nPrefix, callback, pages, classes) {
642 var capitalizedId = buttonId.charAt(0).toUpperCase() + buttonId.slice(1);
643 this.buttonIds.push(buttonId);
644 var result = this.ownerDocument.createElement('button');
645 result.id = this.name() + '-' + buttonId + '-button';
646 result.classList.add('screen-control-button');
647 for (var i = 0; i < classes.length; i++) {
648 result.classList.add(classes[i]);
650 result.textContent = loadTimeData.
651 getString(i18nPrefix + capitalizedId + 'ButtonTitle');
652 result.addEventListener('click', function(e) {
653 callback(buttonId);
654 e.stopPropagation();
656 result.pages = pages;
657 return result;
661 * Simple validator for |configureTextInput|.
662 * Element is considered valid if it has any text.
663 * @param {Element} element - element to be validated.
664 * @return {boolean} - true, if element has any text.
666 validIfNotEmpty_: function(element) {
667 return (element.value.length > 0);
671 * Configure text-input |element|.
672 * @param {Element} element - element to be configured.
673 * @param {function(element)} inputChangeListener - function that will be
674 * called upon any button press/release.
675 * @param {function(element)} validator - function that will be called when
676 * Enter is pressed. If it returns |true| then advance to next element.
677 * @param {function(element)} moveFocus - function that will determine next
678 * element and move focus to it.
679 * @param {function(element)} errorHider - function that is called upon
680 * every button press, so that any associated error can be hidden.
682 configureTextInput: function(element,
683 inputChangeListener,
684 validator,
685 moveFocus,
686 errorHider) {
687 element.addEventListener('keydown', function(e) {
688 if (e.keyIdentifier == 'Enter') {
689 var dataValid = true;
690 if (validator)
691 dataValid = validator(element);
692 if (!dataValid) {
693 element.focus();
694 } else {
695 if (moveFocus)
696 moveFocus(element);
698 e.stopPropagation();
699 return;
701 if (errorHider)
702 errorHider(element);
703 if (inputChangeListener)
704 inputChangeListener(element);
706 element.addEventListener('keyup', function(e) {
707 if (inputChangeListener)
708 inputChangeListener(element);
713 * Makes element from template.
714 * @param {string} templateId -- template will be looked up within screen
715 * by class with name "template-<templateId>".
716 * @param {string} elementId -- id for result, uinque within screen. Actual
717 * id will be prefixed with screen name. Use getScreenElement(id) to find
718 * it later.
720 makeFromTemplate: function(templateId, elementId) {
721 var templateClassName = 'template-' + templateId;
722 var templateNode = this.querySelector('.' + templateClassName);
723 var screenPrefix = this.name() + '-';
724 var result = templateNode.cloneNode(true);
725 result.classList.remove(templateClassName);
726 result.id = screenPrefix + elementId;
727 return result;
731 * @param {string} buttonId -- id of button to be found,
732 * @return {Element} button created by makeButton with given buttonId.
734 getScreenButton: function(buttonId) {
735 var fullId = this.name() + '-' + buttonId + '-button';
736 return this.getScreenElement(buttonId + '-button');
740 * @param {string} elementId -- id of element to be found,
741 * @return {Element} button created by makeFromTemplate with elementId.
743 getScreenElement: function(elementId) {
744 var fullId = this.name() + '-' + elementId;
745 return $(fullId);
749 * Screen controls.
750 * @type {!Array} Array of Buttons.
752 get buttons() {
753 var links = this.ownerDocument.createElement('div');
754 var buttons = this.ownerDocument.createElement('div');
755 links.classList.add('controls-links');
756 buttons.classList.add('controls-buttons');
758 var importLink = this.makeFromTemplate('import-supervised-user-link',
759 'import-link');
760 importLink.hidden = true;
761 links.appendChild(importLink);
763 var linkElement = importLink.querySelector('.signin-link');
764 linkElement.addEventListener('click',
765 this.importLinkPressed_.bind(this));
767 var createLink = this.makeFromTemplate('create-supervised-user-link',
768 'create-link');
769 createLink.hidden = true;
770 links.appendChild(createLink);
772 var status = this.makeFromTemplate('status-container', 'status');
773 buttons.appendChild(status);
775 linkElement = createLink.querySelector('.signin-link');
776 linkElement.addEventListener('click',
777 this.createLinkPressed_.bind(this));
779 buttons.appendChild(this.makeButton(
780 'start',
781 'supervisedUserCreationFlow',
782 this.startButtonPressed_.bind(this),
783 ['intro'],
784 ['custom-appearance', 'button-fancy', 'button-blue']));
786 buttons.appendChild(this.makeButton(
787 'prev',
788 'supervisedUserCreationFlow',
789 this.prevButtonPressed_.bind(this),
790 ['manager'],
791 []));
793 buttons.appendChild(this.makeButton(
794 'next',
795 'supervisedUserCreationFlow',
796 this.nextButtonPressed_.bind(this),
797 ['manager', 'username'],
798 []));
800 buttons.appendChild(this.makeButton(
801 'import',
802 'supervisedUserCreationFlow',
803 this.importButtonPressed_.bind(this),
804 ['import', 'import-password'],
805 []));
807 buttons.appendChild(this.makeButton(
808 'gotit',
809 'supervisedUserCreationFlow',
810 this.gotItButtonPressed_.bind(this),
811 ['created'],
812 ['custom-appearance', 'button-fancy', 'button-blue']));
813 return [links, buttons];
817 * Does sanity check and calls backend with current user name/password pair
818 * to authenticate manager. May result in showManagerPasswordError.
819 * @private
821 validateAndLogInAsManager_: function() {
822 var selectedPod = this.managerList_.selectedPod_;
823 if (null == selectedPod)
824 return;
826 var managerId = selectedPod.user.username;
827 var managerDisplayId = selectedPod.user.emailAddress;
828 var managerPassword = selectedPod.passwordElement.value;
829 if (managerPassword.length == 0)
830 return;
831 if (this.disabled)
832 return;
833 this.disabled = true;
834 this.context_.managerId = managerId;
835 this.context_.managerDisplayId = managerDisplayId;
836 this.context_.managerName = selectedPod.user.displayName;
837 chrome.send('authenticateManagerInSupervisedUserCreationFlow',
838 [managerId, managerPassword]);
842 * Does sanity check and calls backend with user display name/password pair
843 * to create a user.
844 * @private
846 validateAndCreateSupervisedUser_: function() {
847 var firstPassword = $('supervised-user-creation-password').value;
848 var secondPassword =
849 $('supervised-user-creation-password-confirm').value;
850 var userName = $('supervised-user-creation-name').value;
851 if (firstPassword != secondPassword) {
852 this.showPasswordError(loadTimeData.getString(
853 'createSupervisedUserPasswordMismatchError'));
854 return;
856 if (this.disabled)
857 return;
858 this.disabled = true;
860 this.context_.supervisedName = userName;
861 chrome.send('specifySupervisedUserCreationFlowUserData',
862 [userName, firstPassword]);
866 * Does sanity check and calls backend with selected existing supervised
867 * user id to import user.
868 * @private
870 importSupervisedUser_: function() {
871 if (this.disabled)
872 return;
873 if (this.currentPage_ == 'import-password') {
874 var firstPassword = this.getScreenElement('password').value;
875 var secondPassword = this.getScreenElement('password-confirm').value;
876 if (firstPassword != secondPassword) {
877 this.showPasswordError(loadTimeData.getString(
878 'createSupervisedUserPasswordMismatchError'));
879 return;
881 var userId = this.context_.importUserId;
882 this.disabled = true;
883 chrome.send('importSupervisedUserWithPassword',
884 [userId, firstPassword]);
885 return;
886 } else {
887 var selectedPod = this.importList_.selectedPod_;
888 if (!selectedPod)
889 return;
890 var user = selectedPod.user;
891 var userId = user.id;
893 this.context_.importUserId = userId;
894 this.context_.supervisedName = user.name;
895 this.context_.selectedImageUrl = user.avatarurl;
896 if (!user.needPassword) {
897 this.disabled = true;
898 chrome.send('importSupervisedUser', [userId]);
899 } else {
900 this.setVisiblePage_('import-password');
906 * Calls backend part to check if current user name is valid/not taken.
907 * Results in a call to either supervisedUserNameOk or
908 * supervisedUserNameError.
909 * @private
911 checkUserName_: function() {
912 var userName = this.getScreenElement('name').value;
914 // Avoid flickering
915 if (userName == this.lastIncorrectUserName_ ||
916 userName == this.lastVerifiedName_) {
917 return;
919 if (userName.length > 0) {
920 chrome.send('checkSupervisedUserName', [userName]);
921 } else {
922 this.nameErrorVisible = false;
923 this.lastVerifiedName_ = null;
924 this.lastIncorrectUserName_ = null;
925 this.updateNextButtonForUser_();
930 * Called by backend part in case of successful name validation.
931 * @param {string} name - name that was validated.
933 supervisedUserNameOk: function(name) {
934 this.lastVerifiedName_ = name;
935 this.lastIncorrectUserName_ = null;
936 if ($('supervised-user-creation-name').value == name)
937 this.clearUserNameError_();
938 this.updateNextButtonForUser_();
942 * Called by backend part in case of name validation failure.
943 * @param {string} name - name that was validated.
944 * @param {string} errorText - reason why this name is invalid.
946 supervisedUserNameError: function(name, errorText) {
947 this.disabled = false;
948 this.lastIncorrectUserName_ = name;
949 this.lastVerifiedName_ = null;
951 var userNameField = $('supervised-user-creation-name');
952 if (userNameField.value == this.lastIncorrectUserName_) {
953 this.nameErrorVisible = true;
954 $('bubble').showTextForElement(
955 $('supervised-user-creation-name'),
956 errorText,
957 cr.ui.Bubble.Attachment.RIGHT,
958 12, 4);
959 this.setButtonDisabledStatus('next', true);
963 supervisedUserSuggestImport: function(name, user_id) {
964 this.disabled = false;
965 this.lastIncorrectUserName_ = name;
966 this.lastVerifiedName_ = null;
968 var userNameField = $('supervised-user-creation-name');
969 var creationScreen = this;
971 if (userNameField.value == this.lastIncorrectUserName_) {
972 this.nameErrorVisible = true;
973 var link = this.ownerDocument.createElement('div');
974 link.innerHTML = loadTimeData.getStringF(
975 'importBubbleText',
976 '<a class="signin-link" href="#">',
977 name,
978 '</a>');
979 link.querySelector('.signin-link').addEventListener('click',
980 function(e) {
981 creationScreen.handleSuggestImport_(user_id);
982 e.stopPropagation();
984 $('bubble').showContentForElement(
985 $('supervised-user-creation-name'),
986 cr.ui.Bubble.Attachment.RIGHT,
987 link,
988 12, 4);
989 this.setButtonDisabledStatus('next', true);
994 * Clears user name error, if name is no more guaranteed to be invalid.
995 * @private
997 clearUserNameError_: function() {
998 // Avoid flickering
999 if ($('supervised-user-creation-name').value ==
1000 this.lastIncorrectUserName_) {
1001 return;
1003 this.nameErrorVisible = false;
1007 * Called by backend part in case of password validation failure.
1008 * @param {string} errorText - reason why this password is invalid.
1010 showPasswordError: function(errorText) {
1011 $('bubble').showTextForElement(
1012 $('supervised-user-creation-password'),
1013 errorText,
1014 cr.ui.Bubble.Attachment.RIGHT,
1015 12, 4);
1016 $('supervised-user-creation-password').classList.add('password-error');
1017 $('supervised-user-creation-password').focus();
1018 this.disabled = false;
1019 this.setButtonDisabledStatus('next', true);
1023 * True if user name error should be displayed.
1024 * @type {boolean}
1026 set nameErrorVisible(value) {
1027 $('supervised-user-creation-name').
1028 classList.toggle('duplicate-name', value);
1029 if (!value)
1030 $('bubble').hide();
1034 * Updates state of Continue button after minimal checks.
1035 * @return {boolean} true, if form seems to be valid.
1036 * @private
1038 updateNextButtonForManager_: function() {
1039 var selectedPod = this.managerList_.selectedPod_;
1040 canProceed = null != selectedPod &&
1041 selectedPod.passwordElement.value.length > 0;
1043 this.setButtonDisabledStatus('next', !canProceed);
1044 return canProceed;
1048 * Updates state of Continue button after minimal checks.
1049 * @return {boolean} true, if form seems to be valid.
1050 * @private
1052 updateNextButtonForUser_: function() {
1053 var firstPassword = this.getScreenElement('password').value;
1054 var secondPassword = this.getScreenElement('password-confirm').value;
1055 var userName = this.getScreenElement('name').value;
1057 var passwordOk = (firstPassword.length > 0) &&
1058 (firstPassword.length == secondPassword.length);
1060 if (this.currentPage_ == 'import-password') {
1061 this.setButtonDisabledStatus('import', !passwordOk);
1062 return passwordOk;
1064 var imageGrid = this.getScreenElement('image-grid');
1065 var imageChosen = !(imageGrid.selectionType == 'camera' &&
1066 imageGrid.cameraLive);
1067 var canProceed =
1068 passwordOk &&
1069 (userName.length > 0) &&
1070 this.lastVerifiedName_ &&
1071 (userName == this.lastVerifiedName_) &&
1072 imageChosen;
1074 this.setButtonDisabledStatus('next', !canProceed);
1075 return canProceed;
1078 showSelectedManagerPasswordError_: function() {
1079 var selectedPod = this.managerList_.selectedPod_;
1080 selectedPod.showPasswordError();
1081 selectedPod.passwordElement.value = '';
1082 selectedPod.focusInput();
1083 this.updateNextButtonForManager_();
1087 * Enables one particular subpage and hides the rest.
1088 * @param {string} visiblePage - name of subpage.
1089 * @private
1091 setVisiblePage_: function(visiblePage) {
1092 this.disabled = false;
1093 this.updateText_();
1094 $('bubble').hide();
1095 if (!this.imagesRequested_) {
1096 chrome.send('supervisedUserGetImages');
1097 this.imagesRequested_ = true;
1099 var pageNames = ['intro',
1100 'manager',
1101 'username',
1102 'import',
1103 'error',
1104 'created'];
1105 var pageButtons = {'intro' : 'start',
1106 'error' : 'error',
1107 'import' : 'import',
1108 'import-password' : 'import',
1109 'created' : 'gotit'};
1110 this.hideStatus_();
1111 var pageToDisplay = visiblePage;
1112 if (visiblePage == 'import-password')
1113 pageToDisplay = 'username';
1115 for (i in pageNames) {
1116 var pageName = pageNames[i];
1117 var page = $('supervised-user-creation-' + pageName);
1118 page.hidden = (pageName != pageToDisplay);
1119 if (pageName == pageToDisplay)
1120 $('step-logo').hidden = page.classList.contains('step-no-logo');
1123 for (i in this.buttonIds) {
1124 var button = this.getScreenButton(this.buttonIds[i]);
1125 button.hidden = button.pages.indexOf(visiblePage) < 0;
1126 button.disabled = false;
1129 var pagesWithCancel = ['intro', 'manager', 'username', 'import-password',
1130 'error', 'import'];
1131 $('login-header-bar').allowCancel =
1132 pagesWithCancel.indexOf(visiblePage) > -1;
1133 $('cancel-add-user-button').disabled = false;
1135 this.getScreenElement('import-link').hidden = true;
1136 this.getScreenElement('create-link').hidden = true;
1138 if (pageButtons[visiblePage])
1139 this.getScreenButton(pageButtons[visiblePage]).focus();
1141 this.currentPage_ = visiblePage;
1143 if (visiblePage == 'manager' || visiblePage == 'intro') {
1144 $('supervised-user-creation-password').classList.remove(
1145 'password-error');
1146 if (this.managerList_.pods.length > 0)
1147 this.managerList_.selectPod(this.managerList_.pods[0]);
1148 $('login-header-bar').updateUI_();
1151 if (visiblePage == 'username' || visiblePage == 'import-password') {
1152 var elements = this.getScreenElement(pageToDisplay).
1153 querySelectorAll('.hide-on-import');
1154 for (var i = 0; i < elements.length; i++) {
1155 elements[i].classList.toggle('hidden-on-import',
1156 visiblePage == 'import-password');
1159 if (visiblePage == 'username') {
1160 var imageGrid = this.getScreenElement('image-grid');
1161 // select some image.
1162 var selected = this.imagesData_[
1163 Math.floor(Math.random() * this.imagesData_.length)];
1164 this.context_.selectedImageUrl = selected.url;
1165 imageGrid.selectedItemUrl = selected.url;
1166 chrome.send('supervisedUserSelectImage',
1167 [selected.url, 'default']);
1168 this.getScreenElement('image-grid').redraw();
1169 this.checkUserName_();
1170 this.updateNextButtonForUser_();
1171 this.getScreenElement('name').focus();
1172 this.getScreenElement('import-link').hidden =
1173 this.importList_.pods.length == 0;
1174 } else if (visiblePage == 'import-password') {
1175 var imageGrid = this.getScreenElement('image-grid');
1176 var selected;
1177 if ('selectedImageUrl' in this.context_) {
1178 selected = this.context_.selectedImageUrl;
1179 } else {
1180 // select some image.
1181 selected = this.imagesData_[
1182 Math.floor(Math.random() * this.imagesData_.length)].url;
1183 chrome.send('supervisedUserSelectImage',
1184 [selected, 'default']);
1186 imageGrid.selectedItemUrl = selected;
1187 this.getScreenElement('image-grid').redraw();
1189 this.updateNextButtonForUser_();
1191 this.getScreenElement('password').focus();
1192 this.getScreenElement('import-link').hidden = true;
1193 } else {
1194 this.getScreenElement('image-grid').stopCamera();
1196 if (visiblePage == 'import') {
1197 this.getScreenElement('create-link').hidden = false;
1198 this.getScreenButton('import').disabled =
1199 !this.importList_.selectedPod_ ||
1200 this.importList_.selectedPod_.user.exists;
1202 $('supervised-user-creation-close-button-item').hidden =
1203 (visiblePage == 'created');
1205 chrome.send('currentSupervisedUserPage', [this.currentPage_]);
1208 setButtonDisabledStatus: function(buttonName, status) {
1209 var button = $('supervised-user-creation-' + buttonName + '-button');
1210 button.disabled = status;
1213 gotItButtonPressed_: function() {
1214 chrome.send('finishLocalSupervisedUserCreation');
1217 handleErrorButtonPressed_: function() {
1218 chrome.send('abortLocalSupervisedUserCreation');
1221 startButtonPressed_: function() {
1222 this.setVisiblePage_('manager');
1223 this.setButtonDisabledStatus('next', true);
1226 nextButtonPressed_: function() {
1227 if (this.currentPage_ == 'manager') {
1228 this.validateAndLogInAsManager_();
1229 return;
1231 if (this.currentPage_ == 'username') {
1232 this.validateAndCreateSupervisedUser_();
1236 importButtonPressed_: function() {
1237 this.importSupervisedUser_();
1240 importLinkPressed_: function() {
1241 this.setVisiblePage_('import');
1244 handleSuggestImport_: function(user_id) {
1245 this.setVisiblePage_('import');
1246 this.importList_.selectUser(user_id);
1249 createLinkPressed_: function() {
1250 this.setVisiblePage_('username');
1251 this.lastIncorrectUserName_ = null;
1252 this.lastVerifiedName_ = null;
1253 this.checkUserName_();
1256 prevButtonPressed_: function() {
1257 this.setVisiblePage_('intro');
1260 showProgress: function(text) {
1261 var status = this.getScreenElement('status');
1262 var statusText = status.querySelector('.id-text');
1263 statusText.textContent = text;
1264 statusText.classList.remove('error');
1265 status.querySelector('.id-spinner').hidden = false;
1266 status.hidden = false;
1267 this.getScreenElement('import-link').hidden = true;
1268 this.getScreenElement('create-link').hidden = true;
1271 showStatusError: function(text) {
1272 var status = this.getScreenElement('status');
1273 var statusText = status.querySelector('.id-text');
1274 statusText.textContent = text;
1275 statusText.classList.add('error');
1276 status.querySelector('.id-spinner').hidden = true;
1277 status.hidden = false;
1278 this.getScreenElement('import-link').hidden = true;
1279 this.getScreenElement('create-link').hidden = true;
1282 hideStatus_: function() {
1283 var status = this.getScreenElement('status');
1284 status.hidden = true;
1288 * Updates state of login header so that necessary buttons are displayed.
1290 onBeforeShow: function(data) {
1291 $('login-header-bar').signinUIState =
1292 SIGNIN_UI_STATE.SUPERVISED_USER_CREATION_FLOW;
1293 if (data['managers']) {
1294 this.loadManagers(data['managers']);
1296 var imageGrid = this.getScreenElement('image-grid');
1297 imageGrid.updateAndFocus();
1301 * Update state of login header so that necessary buttons are displayed.
1303 onBeforeHide: function() {
1304 $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
1305 this.getScreenElement('image-grid').stopCamera();
1309 * Returns a control which should receive an initial focus.
1311 get defaultControl() {
1312 return $('supervised-user-creation-name');
1316 * True if the the screen is disabled (handles no user interaction).
1317 * @type {boolean}
1319 disabled_: false,
1321 get disabled() {
1322 return this.disabled_;
1325 set disabled(value) {
1326 this.disabled_ = value;
1327 var controls = this.querySelectorAll('button,input');
1328 for (var i = 0, control; control = controls[i]; ++i) {
1329 control.disabled = value;
1331 $('login-header-bar').disabled = value;
1332 $('cancel-add-user-button').disabled = false;
1336 * Called by backend part to propagate list of possible managers.
1337 * @param {Array} userList - list of users that can be managers.
1339 loadManagers: function(userList) {
1340 $('supervised-user-creation-managers-block').hidden = false;
1341 this.managerList_.clearPods();
1342 for (var i = 0; i < userList.length; ++i)
1343 this.managerList_.addPod(userList[i]);
1344 if (userList.length > 0)
1345 this.managerList_.selectPod(this.managerList_.pods[0]);
1349 * Cancels user creation and drops to user screen (either sign).
1351 cancel: function() {
1352 var notSignedInPages = ['intro', 'manager'];
1353 var postCreationPages = ['created'];
1354 if (notSignedInPages.indexOf(this.currentPage_) >= 0) {
1355 chrome.send('hideLocalSupervisedUserCreation');
1357 // Make sure no manager password is kept:
1358 this.managerList_.clearPods();
1360 Oobe.showUserPods();
1361 return;
1363 if (postCreationPages.indexOf(this.currentPage_) >= 0) {
1364 chrome.send('finishLocalSupervisedUserCreation');
1365 return;
1367 chrome.send('abortLocalSupervisedUserCreation');
1370 updateText_: function() {
1371 var managerDisplayId = this.context_.managerDisplayId;
1372 this.updateElementText_('intro-alternate-text',
1373 'createSupervisedUserIntroAlternateText');
1374 this.updateElementText_('created-text-1',
1375 'createSupervisedUserCreatedText1',
1376 this.context_.supervisedName);
1377 // TODO(antrim): Move wrapping with strong in grd file, and eliminate this
1378 //call.
1379 this.updateElementText_('created-text-2',
1380 'createSupervisedUserCreatedText2',
1381 this.wrapStrong(
1382 loadTimeData.getString('managementURL')),
1383 this.context_.supervisedName);
1384 this.updateElementText_('created-text-3',
1385 'createSupervisedUserCreatedText3',
1386 managerDisplayId);
1387 this.updateElementText_('name-explanation',
1388 'createSupervisedUserNameExplanation',
1389 managerDisplayId);
1392 wrapStrong: function(original) {
1393 if (original == undefined)
1394 return original;
1395 return '<strong>' + original + '</strong>';
1398 updateElementText_: function(localId, templateName) {
1399 var args = Array.prototype.slice.call(arguments);
1400 args.shift();
1401 this.getScreenElement(localId).innerHTML =
1402 loadTimeData.getStringF.apply(loadTimeData, args);
1405 showIntroPage: function() {
1406 $('supervised-user-creation-password').value = '';
1407 $('supervised-user-creation-password-confirm').value = '';
1408 $('supervised-user-creation-name').value = '';
1410 this.lastVerifiedName_ = null;
1411 this.lastIncorrectUserName_ = null;
1412 this.passwordErrorVisible = false;
1413 $('supervised-user-creation-password').classList.remove('password-error');
1414 this.nameErrorVisible = false;
1416 this.setVisiblePage_('intro');
1419 showManagerPage: function() {
1420 this.setVisiblePage_('manager');
1423 showUsernamePage: function() {
1424 this.setVisiblePage_('username');
1427 showTutorialPage: function() {
1428 this.setVisiblePage_('created');
1431 showPage: function(page) {
1432 this.setVisiblePage_(page);
1435 showErrorPage: function(errorTitle, errorText, errorButtonText) {
1436 this.disabled = false;
1437 $('supervised-user-creation-error-title').innerHTML = errorTitle;
1438 $('supervised-user-creation-error-text').innerHTML = errorText;
1439 $('supervised-user-creation-error-button').textContent = errorButtonText;
1440 this.setVisiblePage_('error');
1443 showManagerPasswordError: function() {
1444 this.disabled = false;
1445 this.showSelectedManagerPasswordError_();
1449 TODO(antrim) : this is an explicit code duplications with UserImageScreen.
1450 It should be removed by issue 251179.
1453 * Currently selected user image index (take photo button is with zero
1454 * index).
1455 * @type {number}
1457 selectedUserImage_: -1,
1458 imagesData: [],
1460 setDefaultImages: function(imagesData) {
1461 var imageGrid = this.getScreenElement('image-grid');
1462 imageGrid.setDefaultImages(imagesData);
1463 this.imagesData_ = imagesData;
1467 handleActivate_: function() {
1468 var imageGrid = this.getScreenElement('image-grid');
1469 if (imageGrid.selectedItemUrl == ButtonImages.TAKE_PHOTO) {
1470 this.handleTakePhoto_();
1471 return;
1473 this.nextButtonPressed_();
1477 * Handles selection change.
1478 * @param {Event} e Selection change event.
1479 * @private
1481 handleSelect_: function(e) {
1482 var imageGrid = this.getScreenElement('image-grid');
1483 this.updateNextButtonForUser_();
1485 $('supervised-user-creation-flip-photo').tabIndex =
1486 (imageGrid.selectionType == 'camera') ? 0 : -1;
1487 if (imageGrid.cameraLive || imageGrid.selectionType != 'camera')
1488 imageGrid.previewElement.classList.remove('phototaken');
1489 else
1490 imageGrid.previewElement.classList.add('phototaken');
1492 if (!imageGrid.cameraLive || imageGrid.selectionType != 'camera') {
1493 this.context_.selectedImageUrl = imageGrid.selectedItemUrl;
1494 chrome.send('supervisedUserSelectImage',
1495 [imageGrid.selectedItemUrl, imageGrid.selectionType]);
1497 // Start/stop camera on (de)selection.
1498 if (!imageGrid.inProgramSelection &&
1499 imageGrid.selectionType != e.oldSelectionType) {
1500 if (imageGrid.selectionType == 'camera') {
1501 // Programmatic selection of camera item is done in
1502 // startCamera callback where streaming is started by itself.
1503 imageGrid.startCamera(
1504 function() {
1505 // Start capture if camera is still the selected item.
1506 $('supervised-user-creation-image-preview-img').classList.
1507 toggle('animated-transform', true);
1508 return imageGrid.selectedItem == imageGrid.cameraImage;
1510 } else {
1511 $('supervised-user-creation-image-preview-img').classList.toggle(
1512 'animated-transform', false);
1513 imageGrid.stopCamera();
1519 * Handle camera-photo flip.
1521 handleFlipPhoto_: function() {
1522 var imageGrid = this.getScreenElement('image-grid');
1523 imageGrid.previewElement.classList.add('animation');
1524 imageGrid.flipPhoto = !imageGrid.flipPhoto;
1525 var flipMessageId = imageGrid.flipPhoto ?
1526 'photoFlippedAccessibleText' : 'photoFlippedBackAccessibleText';
1527 announceAccessibleMessage(loadTimeData.getString(flipMessageId));
1531 * Handle photo capture from the live camera stream.
1533 handleTakePhoto_: function(e) {
1534 this.getScreenElement('image-grid').takePhoto();
1535 chrome.send('supervisedUserTakePhoto');
1538 handlePhotoTaken_: function(e) {
1539 chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1540 announceAccessibleMessage(
1541 loadTimeData.getString('photoCaptureAccessibleText'));
1545 * Handle photo updated event.
1546 * @param {Event} e Event with 'dataURL' property containing a data URL.
1548 handlePhotoUpdated_: function(e) {
1549 chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1553 * Handle discarding the captured photo.
1555 handleDiscardPhoto_: function(e) {
1556 var imageGrid = this.getScreenElement('image-grid');
1557 imageGrid.discardPhoto();
1558 chrome.send('supervisedUserDiscardPhoto');
1559 announceAccessibleMessage(
1560 loadTimeData.getString('photoDiscardAccessibleText'));
1563 setCameraPresent: function(present) {
1564 this.getScreenElement('image-grid').cameraPresent = present;
1567 setExistingSupervisedUsers: function(users) {
1568 var selectedUser = null;
1569 // Store selected user
1570 if (this.importList_.selectedPod)
1571 selectedUser = this.importList_.selectedPod.user.id;
1573 var userList = users;
1574 userList.sort(function(a, b) {
1575 // Put existing users last.
1576 if (a.exists != b.exists)
1577 return a.exists ? 1 : -1;
1578 // Sort rest by name.
1579 return a.name.localeCompare(b.name, [], {sensitivity: 'base'});
1582 this.importList_.clearPods();
1583 var selectedIndex = -1;
1584 for (var i = 0; i < userList.length; ++i) {
1585 this.importList_.addPod(userList[i]);
1586 if (selectedUser == userList[i].id)
1587 selectedIndex = i;
1590 if (userList.length == 1)
1591 this.importList_.selectPod(this.importList_.pods[0]);
1593 if (selectedIndex >= 0)
1594 this.importList_.selectPod(this.importList_.pods[selectedIndex]);
1596 if (this.currentPage_ == 'username')
1597 this.getScreenElement('import-link').hidden = (userList.length == 0);