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.
6 * @fileoverview Legacy supervised user creation flow screen.
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');
22 ManagerPod.userImageSalt_ = {};
25 * UI element for displaying single account in list of possible managers for
26 * new supervised user.
29 ManagerPod.prototype = {
30 __proto__: HTMLDivElement.prototype,
33 decorate: function() {
34 // Mousedown has to be used instead of click to be able to prevent 'focus'
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');
46 screen.configureTextInput(
48 screen.updateNextButtonForManager_.bind(screen),
49 screen.validIfNotEmpty_.bind(screen),
51 screen.getScreenButton('next').focus();
53 hideManagerPasswordError);
55 this.passwordElement.addEventListener('keydown', function(e) {
56 switch (e.keyIdentifier) {
58 managerPodList.selectNextPod(-1);
62 managerPodList.selectNextPod(+1);
70 * Updates UI elements from user data.
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(
84 loadTimeData.getString(
85 'createSupervisedUserWrongManagerPasswordText'),
86 cr.ui.Bubble.Attachment.BOTTOM,
91 * Brings focus to password field.
93 focusInput: function() {
94 this.passwordElement.focus();
99 * @type {!HTMLImageElement}
102 return this.querySelector('.supervised-user-creation-manager-image');
107 * @type {!HTMLDivElement}
110 return this.querySelector('.supervised-user-creation-manager-name');
114 * Gets e-mail element.
115 * @type {!HTMLDivElement}
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');
138 handleMouseDown_: function(e) {
139 this.parentNode.selectPod(this);
140 // Prevent default so that we don't trigger 'focus' event.
145 * The user that this pod represents.
153 this.user_ = userDict;
158 var ManagerPodList = cr.ui.define('div');
161 * UI element for selecting manager account for new supervised user.
164 ManagerPodList.prototype = {
165 __proto__: HTMLDivElement.prototype,
170 decorate: function() {
174 * Returns all the pods in this pod list.
178 return this.children;
181 addPod: function(manager) {
182 var managerPod = new ManagerPod({user: manager});
183 this.appendChild(managerPod);
187 clearPods: function() {
189 this.selectedPod_ = null;
192 selectPod: function(podToSelect) {
193 if ((this.selectedPod_ == podToSelect) && !!podToSelect) {
194 podToSelect.focusInput();
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;
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_)
225 for (var i = 0, pod; pod = this.pods[i]; ++i) {
226 if (pod == this.selectedPod_) {
233 index = index + direction;
234 if (index < 0 || index >= this.pods.length)
236 this.selectPod(this.pods[index]);
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');
249 * UI element for displaying single supervised user in list of possible users
250 * for importing existing users.
253 ImportPod.prototype = {
254 __proto__: HTMLDivElement.prototype,
257 decorate: function() {
258 // Mousedown has to be used instead of click to be able to prevent 'focus'
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.
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);
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}
288 return this.querySelector('.import-pod-image');
293 * @type {!HTMLDivElement}
296 return this.querySelector('.import-pod-name');
300 handleMouseDown_: function(e) {
301 this.parentNode.selectPod(this);
302 // Prevent default so that we don't trigger 'focus' event.
307 * The user that this pod represents.
317 this.user_ = userDict;
322 var ImportPodList = cr.ui.define('div');
325 * UI element for selecting existing supervised user for import.
328 ImportPodList.prototype = {
329 __proto__: HTMLDivElement.prototype,
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) {
350 importList.selectNextPod(-1);
354 if (importList.selectedPod_ != null)
355 screen.importSupervisedUser_();
359 importList.selectNextPod(+1);
367 * Returns all the pods in this pod list.
371 return this.children;
375 * Returns selected pod.
379 return this.selectedPod_;
382 addPod: function(user) {
383 var importPod = new ImportPod({user: user});
384 this.appendChild(importPod);
388 clearPods: function() {
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;
402 // Function to adjust the tops of viewport and row.
403 function scrollToAdjustTop() {
404 self.scrollTop = top;
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;
420 // Check if the entire of given indexed row can be shown in the viewport.
421 if (itemHeight <= clientHeight) {
423 return scrollToAdjustTop();
424 if (scrollTop + clientHeight < top + itemHeight)
425 return scrollToAdjustBottom();
428 return scrollToAdjustTop();
429 if (top + itemHeight < scrollTop + clientHeight)
430 return scrollToAdjustBottom();
436 * @param {Element} podToSelect - pod to select, can be null.
438 selectPod: function(podToSelect) {
439 if ((this.selectedPod_ == podToSelect) && !!podToSelect) {
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');
450 podToSelect.classList.add('focused');
452 var screen = $('supervised-user-creation');
453 if (!this.selectedPod_) {
454 screen.getScreenButton('import').disabled = true;
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_)
469 for (var i = 0, pod; pod = this.pods[i]; ++i) {
470 if (pod == this.selectedPod_) {
477 index = index + direction;
478 if (index < 0 || index >= this.pods.length)
480 this.selectPod(this.pods[index]);
484 selectUser: function(user_id) {
485 for (var i = 0, pod; pod = this.pods[i]; ++i) {
486 if (pod.user.id == user_id) {
488 this.scrollIntoView(pod);
500 'setExistingSupervisedUsers',
504 'showManagerPasswordError',
511 'supervisedUserNameError',
512 'supervisedUserNameOk',
513 'supervisedUserSuggestImport',
516 lastVerifiedName_: null,
517 lastIncorrectUserName_: null,
522 imagesRequested_: false,
524 // Contains data that can be auto-shared with handler.
528 decorate: function() {
529 this.managerList_ = new ManagerPodList();
530 $('supervised-user-creation-managers-pane').appendChild(
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) {
544 $('supervised-user-creation-password').classList.remove(
548 this.configureTextInput(userNameField,
549 this.checkUserName_.bind(this),
550 this.validIfNotEmpty_.bind(this),
552 passwordField.focus();
554 this.clearUserNameError_.bind(this));
555 this.configureTextInput(passwordField,
556 this.updateNextButtonForUser_.bind(this),
557 this.validIfNotEmpty_.bind(this),
559 password2Field.focus();
561 hideUserPasswordError);
562 this.configureTextInput(password2Field,
563 this.updateNextButtonForUser_.bind(this),
564 this.validIfNotEmpty_.bind(this),
566 creationScreen.getScreenButton('next').focus();
568 hideUserPasswordError);
570 this.getScreenButton('error').addEventListener('click', function(e) {
571 creationScreen.handleErrorButtonPressed_();
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');
624 * Creates button for adding to controls.
625 * @param {string} buttonId -- id for button, have to be unique within
626 * screen. Actual id will be prefixed with screen name and appended with
627 * '-button'. Use getScreenButton(buttonId) to find it later.
628 * @param {string} i18nPrefix -- screen prefix for i18n values.
629 * @param {function} callback -- will be called on button press with
630 * buttonId parameter.
631 * @param {array} pages -- list of pages where this button should be
633 * @param {array} classes -- list of additional CSS classes for button.
635 makeButton: function(buttonId, i18nPrefix, callback, pages, classes) {
636 var capitalizedId = buttonId.charAt(0).toUpperCase() + buttonId.slice(1);
637 this.buttonIds.push(buttonId);
638 var result = this.ownerDocument.createElement('button');
639 result.id = this.name() + '-' + buttonId + '-button';
640 result.classList.add('screen-control-button');
641 for (var i = 0; i < classes.length; i++) {
642 result.classList.add(classes[i]);
644 result.textContent = loadTimeData.
645 getString(i18nPrefix + capitalizedId + 'ButtonTitle');
646 result.addEventListener('click', function(e) {
650 result.pages = pages;
655 * Simple validator for |configureTextInput|.
656 * Element is considered valid if it has any text.
657 * @param {Element} element - element to be validated.
658 * @return {boolean} - true, if element has any text.
660 validIfNotEmpty_: function(element) {
661 return (element.value.length > 0);
665 * Configure text-input |element|.
666 * @param {Element} element - element to be configured.
667 * @param {function(element)} inputChangeListener - function that will be
668 * called upon any button press/release.
669 * @param {function(element)} validator - function that will be called when
670 * Enter is pressed. If it returns |true| then advance to next element.
671 * @param {function(element)} moveFocus - function that will determine next
672 * element and move focus to it.
673 * @param {function(element)} errorHider - function that is called upon
674 * every button press, so that any associated error can be hidden.
676 configureTextInput: function(element,
681 element.addEventListener('keydown', function(e) {
682 if (e.keyIdentifier == 'Enter') {
683 var dataValid = true;
685 dataValid = validator(element);
697 if (inputChangeListener)
698 inputChangeListener(element);
700 element.addEventListener('keyup', function(e) {
701 if (inputChangeListener)
702 inputChangeListener(element);
707 * Makes element from template.
708 * @param {string} templateId -- template will be looked up within screen
709 * by class with name "template-<templateId>".
710 * @param {string} elementId -- id for result, uinque within screen. Actual
711 * id will be prefixed with screen name. Use getScreenElement(id) to find
714 makeFromTemplate: function(templateId, elementId) {
715 var templateClassName = 'template-' + templateId;
716 var templateNode = this.querySelector('.' + templateClassName);
717 var screenPrefix = this.name() + '-';
718 var result = templateNode.cloneNode(true);
719 result.classList.remove(templateClassName);
720 result.id = screenPrefix + elementId;
725 * @param {string} buttonId -- id of button to be found,
726 * @return {Element} button created by makeButton with given buttonId.
728 getScreenButton: function(buttonId) {
729 var fullId = this.name() + '-' + buttonId + '-button';
730 return this.getScreenElement(buttonId + '-button');
734 * @param {string} elementId -- id of element to be found,
735 * @return {Element} button created by makeFromTemplate with elementId.
737 getScreenElement: function(elementId) {
738 var fullId = this.name() + '-' + elementId;
744 * @type {!Array} Array of Buttons.
747 var links = this.ownerDocument.createElement('div');
748 var buttons = this.ownerDocument.createElement('div');
749 links.classList.add('controls-links');
750 buttons.classList.add('controls-buttons');
752 var importLink = this.makeFromTemplate('import-supervised-user-link',
754 importLink.hidden = true;
755 links.appendChild(importLink);
757 var linkElement = importLink.querySelector('.signin-link');
758 linkElement.addEventListener('click',
759 this.importLinkPressed_.bind(this));
761 var createLink = this.makeFromTemplate('create-supervised-user-link',
763 createLink.hidden = true;
764 links.appendChild(createLink);
766 var status = this.makeFromTemplate('status-container', 'status');
767 buttons.appendChild(status);
769 linkElement = createLink.querySelector('.signin-link');
770 linkElement.addEventListener('click',
771 this.createLinkPressed_.bind(this));
773 buttons.appendChild(this.makeButton(
775 'supervisedUserCreationFlow',
776 this.startButtonPressed_.bind(this),
778 ['custom-appearance', 'button-fancy', 'button-blue']));
780 buttons.appendChild(this.makeButton(
782 'supervisedUserCreationFlow',
783 this.prevButtonPressed_.bind(this),
787 buttons.appendChild(this.makeButton(
789 'supervisedUserCreationFlow',
790 this.nextButtonPressed_.bind(this),
791 ['manager', 'username'],
794 buttons.appendChild(this.makeButton(
796 'supervisedUserCreationFlow',
797 this.importButtonPressed_.bind(this),
798 ['import', 'import-password'],
801 buttons.appendChild(this.makeButton(
803 'supervisedUserCreationFlow',
804 this.gotItButtonPressed_.bind(this),
806 ['custom-appearance', 'button-fancy', 'button-blue']));
807 return [links, buttons];
811 * Does sanity check and calls backend with current user name/password pair
812 * to authenticate manager. May result in showManagerPasswordError.
815 validateAndLogInAsManager_: function() {
816 var selectedPod = this.managerList_.selectedPod_;
817 if (null == selectedPod)
820 var managerId = selectedPod.user.username;
821 var managerDisplayId = selectedPod.user.emailAddress;
822 var managerPassword = selectedPod.passwordElement.value;
823 if (managerPassword.length == 0)
827 this.disabled = true;
828 this.context_.managerId = managerId;
829 this.context_.managerDisplayId = managerDisplayId;
830 this.context_.managerName = selectedPod.user.displayName;
831 chrome.send('authenticateManagerInSupervisedUserCreationFlow',
832 [managerId, managerPassword]);
836 * Does sanity check and calls backend with user display name/password pair
840 validateAndCreateSupervisedUser_: function() {
841 var firstPassword = $('supervised-user-creation-password').value;
843 $('supervised-user-creation-password-confirm').value;
844 var userName = $('supervised-user-creation-name').value;
845 if (firstPassword != secondPassword) {
846 this.showPasswordError(loadTimeData.getString(
847 'createSupervisedUserPasswordMismatchError'));
852 this.disabled = true;
854 this.context_.supervisedName = userName;
855 chrome.send('specifySupervisedUserCreationFlowUserData',
856 [userName, firstPassword]);
860 * Does sanity check and calls backend with selected existing supervised
861 * user id to import user.
864 importSupervisedUser_: function() {
867 if (this.currentPage_ == 'import-password') {
868 var firstPassword = this.getScreenElement('password').value;
869 var secondPassword = this.getScreenElement('password-confirm').value;
870 if (firstPassword != secondPassword) {
871 this.showPasswordError(loadTimeData.getString(
872 'createSupervisedUserPasswordMismatchError'));
875 var userId = this.context_.importUserId;
876 this.disabled = true;
877 chrome.send('importSupervisedUserWithPassword',
878 [userId, firstPassword]);
881 var selectedPod = this.importList_.selectedPod_;
884 var user = selectedPod.user;
885 var userId = user.id;
887 this.context_.importUserId = userId;
888 this.context_.supervisedName = user.name;
889 this.context_.selectedImageUrl = user.avatarurl;
890 if (!user.needPassword) {
891 this.disabled = true;
892 chrome.send('importSupervisedUser', [userId]);
894 this.setVisiblePage_('import-password');
900 * Calls backend part to check if current user name is valid/not taken.
901 * Results in a call to either supervisedUserNameOk or
902 * supervisedUserNameError.
905 checkUserName_: function() {
906 var userName = this.getScreenElement('name').value;
909 if (userName == this.lastIncorrectUserName_ ||
910 userName == this.lastVerifiedName_) {
913 if (userName.length > 0) {
914 chrome.send('checkSupervisedUserName', [userName]);
916 this.nameErrorVisible = false;
917 this.lastVerifiedName_ = null;
918 this.lastIncorrectUserName_ = null;
919 this.updateNextButtonForUser_();
924 * Called by backend part in case of successful name validation.
925 * @param {string} name - name that was validated.
927 supervisedUserNameOk: function(name) {
928 this.lastVerifiedName_ = name;
929 this.lastIncorrectUserName_ = null;
930 if ($('supervised-user-creation-name').value == name)
931 this.clearUserNameError_();
932 this.updateNextButtonForUser_();
936 * Called by backend part in case of name validation failure.
937 * @param {string} name - name that was validated.
938 * @param {string} errorText - reason why this name is invalid.
940 supervisedUserNameError: function(name, errorText) {
941 this.disabled = false;
942 this.lastIncorrectUserName_ = name;
943 this.lastVerifiedName_ = null;
945 var userNameField = $('supervised-user-creation-name');
946 if (userNameField.value == this.lastIncorrectUserName_) {
947 this.nameErrorVisible = true;
948 $('bubble').showTextForElement(
949 $('supervised-user-creation-name'),
951 cr.ui.Bubble.Attachment.RIGHT,
953 this.setButtonDisabledStatus('next', true);
957 supervisedUserSuggestImport: function(name, user_id) {
958 this.disabled = false;
959 this.lastIncorrectUserName_ = name;
960 this.lastVerifiedName_ = null;
962 var userNameField = $('supervised-user-creation-name');
963 var creationScreen = this;
965 if (userNameField.value == this.lastIncorrectUserName_) {
966 this.nameErrorVisible = true;
967 var link = this.ownerDocument.createElement('div');
968 link.innerHTML = loadTimeData.getStringF(
970 '<a class="signin-link" href="#">',
973 link.querySelector('.signin-link').addEventListener('click',
975 creationScreen.handleSuggestImport_(user_id);
978 $('bubble').showContentForElement(
979 $('supervised-user-creation-name'),
980 cr.ui.Bubble.Attachment.RIGHT,
983 this.setButtonDisabledStatus('next', true);
988 * Clears user name error, if name is no more guaranteed to be invalid.
991 clearUserNameError_: function() {
993 if ($('supervised-user-creation-name').value ==
994 this.lastIncorrectUserName_) {
997 this.nameErrorVisible = false;
1001 * Called by backend part in case of password validation failure.
1002 * @param {string} errorText - reason why this password is invalid.
1004 showPasswordError: function(errorText) {
1005 $('bubble').showTextForElement(
1006 $('supervised-user-creation-password'),
1008 cr.ui.Bubble.Attachment.RIGHT,
1010 $('supervised-user-creation-password').classList.add('password-error');
1011 $('supervised-user-creation-password').focus();
1012 this.disabled = false;
1013 this.setButtonDisabledStatus('next', true);
1017 * True if user name error should be displayed.
1020 set nameErrorVisible(value) {
1021 $('supervised-user-creation-name').
1022 classList.toggle('duplicate-name', value);
1028 * Updates state of Continue button after minimal checks.
1029 * @return {boolean} true, if form seems to be valid.
1032 updateNextButtonForManager_: function() {
1033 var selectedPod = this.managerList_.selectedPod_;
1034 canProceed = null != selectedPod &&
1035 selectedPod.passwordElement.value.length > 0;
1037 this.setButtonDisabledStatus('next', !canProceed);
1042 * Updates state of Continue button after minimal checks.
1043 * @return {boolean} true, if form seems to be valid.
1046 updateNextButtonForUser_: function() {
1047 var firstPassword = this.getScreenElement('password').value;
1048 var secondPassword = this.getScreenElement('password-confirm').value;
1049 var userName = this.getScreenElement('name').value;
1051 var passwordOk = (firstPassword.length > 0) &&
1052 (firstPassword.length == secondPassword.length);
1054 if (this.currentPage_ == 'import-password') {
1055 this.setButtonDisabledStatus('import', !passwordOk);
1058 var imageGrid = this.getScreenElement('image-grid');
1059 var imageChosen = !(imageGrid.selectionType == 'camera' &&
1060 imageGrid.cameraLive);
1063 (userName.length > 0) &&
1064 this.lastVerifiedName_ &&
1065 (userName == this.lastVerifiedName_) &&
1068 this.setButtonDisabledStatus('next', !canProceed);
1072 showSelectedManagerPasswordError_: function() {
1073 var selectedPod = this.managerList_.selectedPod_;
1074 selectedPod.showPasswordError();
1075 selectedPod.passwordElement.value = '';
1076 selectedPod.focusInput();
1077 this.updateNextButtonForManager_();
1081 * Enables one particular subpage and hides the rest.
1082 * @param {string} visiblePage - name of subpage.
1085 setVisiblePage_: function(visiblePage) {
1086 this.disabled = false;
1089 if (!this.imagesRequested_) {
1090 chrome.send('supervisedUserGetImages');
1091 this.imagesRequested_ = true;
1093 var pageNames = ['intro',
1099 var pageButtons = {'intro' : 'start',
1101 'import' : 'import',
1102 'import-password' : 'import',
1103 'created' : 'gotit'};
1105 var pageToDisplay = visiblePage;
1106 if (visiblePage == 'import-password')
1107 pageToDisplay = 'username';
1109 for (i in pageNames) {
1110 var pageName = pageNames[i];
1111 var page = $('supervised-user-creation-' + pageName);
1112 page.hidden = (pageName != pageToDisplay);
1113 if (pageName == pageToDisplay)
1114 $('step-logo').hidden = page.classList.contains('step-no-logo');
1117 for (i in this.buttonIds) {
1118 var button = this.getScreenButton(this.buttonIds[i]);
1119 button.hidden = button.pages.indexOf(visiblePage) < 0;
1120 button.disabled = false;
1123 var pagesWithCancel = ['intro', 'manager', 'username', 'import-password',
1125 $('login-header-bar').allowCancel =
1126 pagesWithCancel.indexOf(visiblePage) > 0;
1127 $('cancel-add-user-button').disabled = false;
1129 this.getScreenElement('import-link').hidden = true;
1130 this.getScreenElement('create-link').hidden = true;
1132 if (pageButtons[visiblePage])
1133 this.getScreenButton(pageButtons[visiblePage]).focus();
1135 this.currentPage_ = visiblePage;
1137 if (visiblePage == 'manager' || visiblePage == 'intro') {
1138 $('supervised-user-creation-password').classList.remove(
1140 if (this.managerList_.pods.length > 0)
1141 this.managerList_.selectPod(this.managerList_.pods[0]);
1144 if (visiblePage == 'username' || visiblePage == 'import-password') {
1145 var elements = this.getScreenElement(pageToDisplay).
1146 querySelectorAll('.hide-on-import');
1147 for (var i = 0; i < elements.length; i++) {
1148 elements[i].classList.toggle('hidden-on-import',
1149 visiblePage == 'import-password');
1152 if (visiblePage == 'username') {
1153 var imageGrid = this.getScreenElement('image-grid');
1154 // select some image.
1155 var selected = this.imagesData_[
1156 Math.floor(Math.random() * this.imagesData_.length)];
1157 this.context_.selectedImageUrl = selected.url;
1158 imageGrid.selectedItemUrl = selected.url;
1159 chrome.send('supervisedUserSelectImage',
1160 [selected.url, 'default']);
1161 this.getScreenElement('image-grid').redraw();
1162 this.checkUserName_();
1163 this.updateNextButtonForUser_();
1164 this.getScreenElement('name').focus();
1165 this.getScreenElement('import-link').hidden =
1166 this.importList_.pods.length == 0;
1167 } else if (visiblePage == 'import-password') {
1168 var imageGrid = this.getScreenElement('image-grid');
1170 if ('selectedImageUrl' in this.context_) {
1171 selected = this.context_.selectedImageUrl;
1173 // select some image.
1174 selected = this.imagesData_[
1175 Math.floor(Math.random() * this.imagesData_.length)].url;
1176 chrome.send('supervisedUserSelectImage',
1177 [selected, 'default']);
1179 imageGrid.selectedItemUrl = selected;
1180 this.getScreenElement('image-grid').redraw();
1182 this.updateNextButtonForUser_();
1184 this.getScreenElement('password').focus();
1185 this.getScreenElement('import-link').hidden = true;
1187 this.getScreenElement('image-grid').stopCamera();
1189 if (visiblePage == 'import') {
1190 this.getScreenElement('create-link').hidden = false;
1191 this.getScreenButton('import').disabled =
1192 !this.importList_.selectedPod_ ||
1193 this.importList_.selectedPod_.user.exists;
1195 chrome.send('currentSupervisedUserPage', [this.currentPage_]);
1198 setButtonDisabledStatus: function(buttonName, status) {
1199 var button = $('supervised-user-creation-' + buttonName + '-button');
1200 button.disabled = status;
1203 gotItButtonPressed_: function() {
1204 chrome.send('finishLocalSupervisedUserCreation');
1207 handleErrorButtonPressed_: function() {
1208 chrome.send('abortLocalSupervisedUserCreation');
1211 startButtonPressed_: function() {
1212 this.setVisiblePage_('manager');
1213 this.setButtonDisabledStatus('next', true);
1216 nextButtonPressed_: function() {
1217 if (this.currentPage_ == 'manager') {
1218 this.validateAndLogInAsManager_();
1221 if (this.currentPage_ == 'username') {
1222 this.validateAndCreateSupervisedUser_();
1226 importButtonPressed_: function() {
1227 this.importSupervisedUser_();
1230 importLinkPressed_: function() {
1231 this.setVisiblePage_('import');
1234 handleSuggestImport_: function(user_id) {
1235 this.setVisiblePage_('import');
1236 this.importList_.selectUser(user_id);
1239 createLinkPressed_: function() {
1240 this.setVisiblePage_('username');
1241 this.lastIncorrectUserName_ = null;
1242 this.lastVerifiedName_ = null;
1243 this.checkUserName_();
1246 prevButtonPressed_: function() {
1247 this.setVisiblePage_('intro');
1250 showProgress: function(text) {
1251 var status = this.getScreenElement('status');
1252 var statusText = status.querySelector('.id-text');
1253 statusText.textContent = text;
1254 statusText.classList.remove('error');
1255 status.querySelector('.id-spinner').hidden = false;
1256 status.hidden = false;
1257 this.getScreenElement('import-link').hidden = true;
1258 this.getScreenElement('create-link').hidden = true;
1261 showStatusError: function(text) {
1262 var status = this.getScreenElement('status');
1263 var statusText = status.querySelector('.id-text');
1264 statusText.textContent = text;
1265 statusText.classList.add('error');
1266 status.querySelector('.id-spinner').hidden = true;
1267 status.hidden = false;
1268 this.getScreenElement('import-link').hidden = true;
1269 this.getScreenElement('create-link').hidden = true;
1272 hideStatus_: function() {
1273 var status = this.getScreenElement('status');
1274 status.hidden = true;
1278 * Updates state of login header so that necessary buttons are displayed.
1280 onBeforeShow: function(data) {
1281 $('login-header-bar').signinUIState =
1282 SIGNIN_UI_STATE.SUPERVISED_USER_CREATION_FLOW;
1283 if (data['managers']) {
1284 this.loadManagers(data['managers']);
1286 var imageGrid = this.getScreenElement('image-grid');
1287 imageGrid.updateAndFocus();
1291 * Update state of login header so that necessary buttons are displayed.
1293 onBeforeHide: function() {
1294 $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
1295 this.getScreenElement('image-grid').stopCamera();
1299 * Returns a control which should receive an initial focus.
1301 get defaultControl() {
1302 return $('supervised-user-creation-name');
1306 * True if the the screen is disabled (handles no user interaction).
1312 return this.disabled_;
1315 set disabled(value) {
1316 this.disabled_ = value;
1317 var controls = this.querySelectorAll('button,input');
1318 for (var i = 0, control; control = controls[i]; ++i) {
1319 control.disabled = value;
1321 $('login-header-bar').disabled = value;
1322 $('cancel-add-user-button').disabled = false;
1326 * Called by backend part to propagate list of possible managers.
1327 * @param {Array} userList - list of users that can be managers.
1329 loadManagers: function(userList) {
1330 $('supervised-user-creation-managers-block').hidden = false;
1331 this.managerList_.clearPods();
1332 for (var i = 0; i < userList.length; ++i)
1333 this.managerList_.addPod(userList[i]);
1334 if (userList.length > 0)
1335 this.managerList_.selectPod(this.managerList_.pods[0]);
1339 * Cancels user creation and drops to user screen (either sign).
1341 cancel: function() {
1342 var notSignedInPages = ['intro', 'manager'];
1343 var postCreationPages = ['created'];
1344 if (notSignedInPages.indexOf(this.currentPage_) >= 0) {
1345 // Make sure no manager password is kept:
1346 this.managerList_.clearPods();
1348 $('pod-row').loadLastWallpaper();
1350 Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
1351 Oobe.resetSigninUI(true);
1354 if (postCreationPages.indexOf(this.currentPage_) >= 0) {
1355 chrome.send('finishLocalSupervisedUserCreation');
1358 chrome.send('abortLocalSupervisedUserCreation');
1361 updateText_: function() {
1362 var managerDisplayId = this.context_.managerDisplayId;
1363 this.updateElementText_('intro-alternate-text',
1364 'createSupervisedUserIntroAlternateText');
1365 this.updateElementText_('created-text-1',
1366 'createSupervisedUserCreatedText1',
1367 this.context_.supervisedName);
1368 // TODO(antrim): Move wrapping with strong in grd file, and eliminate this
1370 this.updateElementText_('created-text-2',
1371 'createSupervisedUserCreatedText2',
1373 loadTimeData.getString('managementURL')),
1374 this.context_.supervisedName);
1375 this.updateElementText_('created-text-3',
1376 'createSupervisedUserCreatedText3',
1378 this.updateElementText_('name-explanation',
1379 'createSupervisedUserNameExplanation',
1383 wrapStrong: function(original) {
1384 if (original == undefined)
1386 return '<strong>' + original + '</strong>';
1389 updateElementText_: function(localId, templateName) {
1390 var args = Array.prototype.slice.call(arguments);
1392 this.getScreenElement(localId).innerHTML =
1393 loadTimeData.getStringF.apply(loadTimeData, args);
1396 showIntroPage: function() {
1397 $('supervised-user-creation-password').value = '';
1398 $('supervised-user-creation-password-confirm').value = '';
1399 $('supervised-user-creation-name').value = '';
1401 this.lastVerifiedName_ = null;
1402 this.lastIncorrectUserName_ = null;
1403 this.passwordErrorVisible = false;
1404 $('supervised-user-creation-password').classList.remove('password-error');
1405 this.nameErrorVisible = false;
1407 this.setVisiblePage_('intro');
1410 showManagerPage: function() {
1411 this.setVisiblePage_('manager');
1414 showUsernamePage: function() {
1415 this.setVisiblePage_('username');
1418 showTutorialPage: function() {
1419 this.setVisiblePage_('created');
1422 showPage: function(page) {
1423 this.setVisiblePage_(page);
1426 showErrorPage: function(errorTitle, errorText, errorButtonText) {
1427 this.disabled = false;
1428 $('supervised-user-creation-error-title').innerHTML = errorTitle;
1429 $('supervised-user-creation-error-text').innerHTML = errorText;
1430 $('supervised-user-creation-error-button').textContent = errorButtonText;
1431 this.setVisiblePage_('error');
1434 showManagerPasswordError: function() {
1435 this.disabled = false;
1436 this.showSelectedManagerPasswordError_();
1440 TODO(antrim) : this is an explicit code duplications with UserImageScreen.
1441 It should be removed by issue 251179.
1444 * Currently selected user image index (take photo button is with zero
1448 selectedUserImage_: -1,
1451 setDefaultImages: function(imagesData) {
1452 var imageGrid = this.getScreenElement('image-grid');
1453 imageGrid.setDefaultImages(imagesData);
1454 this.imagesData_ = imagesData;
1458 handleActivate_: function() {
1459 var imageGrid = this.getScreenElement('image-grid');
1460 if (imageGrid.selectedItemUrl == ButtonImages.TAKE_PHOTO) {
1461 this.handleTakePhoto_();
1464 this.nextButtonPressed_();
1468 * Handles selection change.
1469 * @param {Event} e Selection change event.
1472 handleSelect_: function(e) {
1473 var imageGrid = this.getScreenElement('image-grid');
1474 this.updateNextButtonForUser_();
1476 $('supervised-user-creation-flip-photo').tabIndex =
1477 (imageGrid.selectionType == 'camera') ? 0 : -1;
1478 if (imageGrid.cameraLive || imageGrid.selectionType != 'camera')
1479 imageGrid.previewElement.classList.remove('phototaken');
1481 imageGrid.previewElement.classList.add('phototaken');
1483 if (!imageGrid.cameraLive || imageGrid.selectionType != 'camera') {
1484 this.context_.selectedImageUrl = imageGrid.selectedItemUrl;
1485 chrome.send('supervisedUserSelectImage',
1486 [imageGrid.selectedItemUrl, imageGrid.selectionType]);
1488 // Start/stop camera on (de)selection.
1489 if (!imageGrid.inProgramSelection &&
1490 imageGrid.selectionType != e.oldSelectionType) {
1491 if (imageGrid.selectionType == 'camera') {
1492 // Programmatic selection of camera item is done in
1493 // startCamera callback where streaming is started by itself.
1494 imageGrid.startCamera(
1496 // Start capture if camera is still the selected item.
1497 $('supervised-user-creation-image-preview-img').classList.
1498 toggle('animated-transform', true);
1499 return imageGrid.selectedItem == imageGrid.cameraImage;
1502 $('supervised-user-creation-image-preview-img').classList.toggle(
1503 'animated-transform', false);
1504 imageGrid.stopCamera();
1510 * Handle camera-photo flip.
1512 handleFlipPhoto_: function() {
1513 var imageGrid = this.getScreenElement('image-grid');
1514 imageGrid.previewElement.classList.add('animation');
1515 imageGrid.flipPhoto = !imageGrid.flipPhoto;
1516 var flipMessageId = imageGrid.flipPhoto ?
1517 'photoFlippedAccessibleText' : 'photoFlippedBackAccessibleText';
1518 announceAccessibleMessage(loadTimeData.getString(flipMessageId));
1522 * Handle photo capture from the live camera stream.
1524 handleTakePhoto_: function(e) {
1525 this.getScreenElement('image-grid').takePhoto();
1526 chrome.send('supervisedUserTakePhoto');
1529 handlePhotoTaken_: function(e) {
1530 chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1531 announceAccessibleMessage(
1532 loadTimeData.getString('photoCaptureAccessibleText'));
1536 * Handle photo updated event.
1537 * @param {Event} e Event with 'dataURL' property containing a data URL.
1539 handlePhotoUpdated_: function(e) {
1540 chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1544 * Handle discarding the captured photo.
1546 handleDiscardPhoto_: function(e) {
1547 var imageGrid = this.getScreenElement('image-grid');
1548 imageGrid.discardPhoto();
1549 chrome.send('supervisedUserDiscardPhoto');
1550 announceAccessibleMessage(
1551 loadTimeData.getString('photoDiscardAccessibleText'));
1554 setCameraPresent: function(present) {
1555 this.getScreenElement('image-grid').cameraPresent = present;
1558 setExistingSupervisedUsers: function(users) {
1559 var selectedUser = null;
1560 // Store selected user
1561 if (this.importList_.selectedPod)
1562 selectedUser = this.importList_.selectedPod.user.id;
1564 var userList = users;
1565 userList.sort(function(a, b) {
1566 // Put existing users last.
1567 if (a.exists != b.exists)
1568 return a.exists ? 1 : -1;
1569 // Sort rest by name.
1570 return a.name.localeCompare(b.name, [], {sensitivity: 'base'});
1573 this.importList_.clearPods();
1574 var selectedIndex = -1;
1575 for (var i = 0; i < userList.length; ++i) {
1576 this.importList_.addPod(userList[i]);
1577 if (selectedUser == userList[i].id)
1581 if (userList.length == 1)
1582 this.importList_.selectPod(this.importList_.pods[0]);
1584 if (selectedIndex >= 0)
1585 this.importList_.selectPod(this.importList_.pods[selectedIndex]);
1587 if (this.currentPage_ == 'username')
1588 this.getScreenElement('import-link').hidden = (userList.length == 0);