1 // Copyright (c) 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 * @fileoverview Locally managed user creation flow screen.
9 login.createScreen('LocallyManagedUserCreationScreen',
10 'managed-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 = $('managed-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 locally managed 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 = $('managed-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('createManagedUserWrongManagerPasswordText'),
85 cr.ui.Bubble.Attachment.BOTTOM,
90 * Brings focus to password field.
92 focusInput: function() {
93 this.passwordElement.focus();
98 * @type {!HTMLImageElement}
101 return this.querySelector('.managed-user-creation-manager-image');
106 * @type {!HTMLDivElement}
109 return this.querySelector('.managed-user-creation-manager-name');
113 * Gets e-mail element.
114 * @type {!HTMLDivElement}
117 return this.querySelector('.managed-user-creation-manager-email');
121 * Gets password element.
122 * @type {!HTMLDivElement}
124 get passwordElement() {
125 return this.querySelector('.managed-user-creation-manager-password');
129 * Gets password enclosing block.
130 * @type {!HTMLDivElement}
132 get passwordBlock() {
133 return this.querySelector('.password-block');
137 handleMouseDown_: function(e) {
138 this.parentNode.selectPod(this);
139 // Prevent default so that we don't trigger 'focus' event.
144 * The user that this pod represents.
152 this.user_ = userDict;
157 var ManagerPodList = cr.ui.define('div');
160 * UI element for selecting manager account for new managed user.
163 ManagerPodList.prototype = {
164 __proto__: HTMLDivElement.prototype,
169 decorate: function() {
173 * Returns all the pods in this pod list.
177 return this.children;
180 addPod: function(manager) {
181 var managerPod = new ManagerPod({user: manager});
182 this.appendChild(managerPod);
186 clearPods: function() {
188 this.selectedPod_ = null;
191 selectPod: function(podToSelect) {
192 if ((this.selectedPod_ == podToSelect) && !!podToSelect) {
193 podToSelect.focusInput();
196 this.selectedPod_ = podToSelect;
197 for (var i = 0, pod; pod = this.pods[i]; ++i) {
198 if (pod != podToSelect) {
199 pod.classList.remove('focused');
200 pod.passwordElement.value = '';
201 pod.passwordBlock.hidden = true;
206 podToSelect.classList.add('focused');
207 podToSelect.passwordBlock.hidden = false;
208 podToSelect.passwordElement.value = '';
209 podToSelect.focusInput();
210 chrome.send('managerSelectedOnLocallyManagedUserCreationFlow',
211 [podToSelect.user.username]);
215 * Select pod next to currently selected one in given |direction|.
216 * @param {integer} direction - +1 for selecting pod below current, -1 for
217 * selecting pod above current.
218 * @type {boolean} returns if selected pod has changed.
220 selectNextPod: function(direction) {
221 if (!this.selectedPod_)
224 for (var i = 0, pod; pod = this.pods[i]; ++i) {
225 if (pod == this.selectedPod_) {
232 index = index + direction;
233 if (index < 0 || index >= this.pods.length)
235 this.selectPod(this.pods[index]);
240 var ImportPod = cr.ui.define(function() {
241 var node = $('managed-user-creation-import-template').cloneNode(true);
242 node.removeAttribute('id');
243 node.removeAttribute('hidden');
248 * UI element for displaying single supervised user in list of possible users
249 * for importing existing users.
252 ImportPod.prototype = {
253 __proto__: HTMLDivElement.prototype,
256 decorate: function() {
257 // Mousedown has to be used instead of click to be able to prevent 'focus'
259 this.addEventListener('mousedown',
260 this.handleMouseDown_.bind(this));
264 * Updates UI elements from user data.
267 this.imageElement.src = this.user.avatarurl;
268 this.nameElement.textContent = this.user.name;
269 if (this.user.exists) {
270 if (this.user.conflict == 'imported') {
271 this.nameElement.textContent =
272 loadTimeData.getStringF('importUserExists', this.user.name);
274 this.nameElement.textContent =
275 loadTimeData.getStringF('importUsernameExists', this.user.name);
278 this.classList.toggle('imported', this.user.exists);
282 * Gets image element.
283 * @type {!HTMLImageElement}
286 return this.querySelector('.import-pod-image');
291 * @type {!HTMLDivElement}
294 return this.querySelector('.import-pod-name');
298 handleMouseDown_: function(e) {
299 this.parentNode.selectPod(this);
300 // Prevent default so that we don't trigger 'focus' event.
305 * The user that this pod represents.
315 this.user_ = userDict;
320 var ImportPodList = cr.ui.define('div');
323 * UI element for selecting existing supervised user for import.
326 ImportPodList.prototype = {
327 __proto__: HTMLDivElement.prototype,
332 decorate: function() {
336 * Returns all the pods in this pod list.
340 return this.children;
343 addPod: function(user) {
344 var importPod = new ImportPod({user: user});
345 this.appendChild(importPod);
349 clearPods: function() {
351 this.selectedPod_ = null;
354 scrollIntoView: function(pod) {
355 scroller = this.parentNode;
356 var itemHeight = pod.getBoundingClientRect().height;
357 var scrollTop = scroller.scrollTop;
358 var top = pod.offsetTop - scroller.offsetTop;
359 var clientHeight = scroller.clientHeight;
363 // Function to adjust the tops of viewport and row.
364 function scrollToAdjustTop() {
365 self.scrollTop = top;
368 // Function to adjust the bottoms of viewport and row.
369 function scrollToAdjustBottom() {
370 var cs = getComputedStyle(self);
371 var paddingY = parseInt(cs.paddingTop, 10) +
372 parseInt(cs.paddingBottom, 10);
374 if (top + itemHeight > scrollTop + clientHeight - paddingY) {
375 self.scrollTop = top + itemHeight - clientHeight + paddingY;
381 // Check if the entire of given indexed row can be shown in the viewport.
382 if (itemHeight <= clientHeight) {
384 return scrollToAdjustTop();
385 if (scrollTop + clientHeight < top + itemHeight)
386 return scrollToAdjustBottom();
389 return scrollToAdjustTop();
390 if (top + itemHeight < scrollTop + clientHeight)
391 return scrollToAdjustBottom();
397 * @param {Element} podToSelect - pod to select, can be null.
399 selectPod: function(podToSelect) {
400 if ((this.selectedPod_ == podToSelect) && !!podToSelect) {
403 this.selectedPod_ = podToSelect;
404 for (var i = 0; i < this.pods.length; i++) {
405 var pod = this.pods[i];
406 if (pod != podToSelect)
407 pod.classList.remove('focused');
411 podToSelect.classList.add('focused');
412 var screen = $('managed-user-creation');
413 if (!this.selectedPod_) {
414 screen.getScreenButton('import').disabled = true;
416 screen.getScreenButton('import').disabled =
417 this.selectedPod_.user.exists;
418 if (!this.selectedPod_.user.exists) {
419 chrome.send('userSelectedForImportInManagedUserCreationFlow',
420 [podToSelect.user.id]);
425 selectUser: function(user_id) {
426 for (var i = 0, pod; pod = this.pods[i]; ++i) {
427 if (pod.user.id == user_id) {
429 this.scrollIntoView(pod);
439 'managedUserSuggestImport',
440 'managedUserNameError',
445 'showManagerPasswordError',
454 'setExistingManagedUsers',
457 lastVerifiedName_: null,
458 lastIncorrectUserName_: null,
463 imagesRequested_: false,
465 // Contains data that can be auto-shared with handler.
469 decorate: function() {
470 this.managerList_ = new ManagerPodList();
471 $('managed-user-creation-managers-pane').appendChild(this.managerList_);
473 this.importList_ = new ImportPodList();
474 $('managed-user-creation-import-pane').appendChild(this.importList_);
476 var userNameField = $('managed-user-creation-name');
477 var passwordField = $('managed-user-creation-password');
478 var password2Field = $('managed-user-creation-password-confirm');
480 var creationScreen = this;
482 var hideUserPasswordError = function(element) {
484 $('managed-user-creation-password').classList.remove('password-error');
487 this.configureTextInput(userNameField,
488 this.checkUserName_.bind(this),
489 this.validIfNotEmpty_.bind(this),
491 passwordField.focus();
493 this.clearUserNameError_.bind(this));
494 this.configureTextInput(passwordField,
495 this.updateNextButtonForUser_.bind(this),
496 this.validIfNotEmpty_.bind(this),
498 password2Field.focus();
500 hideUserPasswordError);
501 this.configureTextInput(password2Field,
502 this.updateNextButtonForUser_.bind(this),
503 this.validIfNotEmpty_.bind(this),
505 creationScreen.getScreenButton('next').focus();
507 hideUserPasswordError);
509 this.getScreenButton('error').addEventListener('click', function(e) {
510 creationScreen.handleErrorButtonPressed_();
515 TODO(antrim) : this is an explicit code duplications with UserImageScreen.
516 It should be removed by issue 251179.
518 var imageGrid = this.getScreenElement('image-grid');
519 UserImagesGrid.decorate(imageGrid);
521 // Preview image will track the selected item's URL.
522 var previewElement = this.getScreenElement('image-preview');
523 previewElement.oncontextmenu = function(e) { e.preventDefault(); };
525 imageGrid.previewElement = previewElement;
526 imageGrid.selectionType = 'default';
527 imageGrid.flipPhotoElement = this.getScreenElement('flip-photo');
529 imageGrid.addEventListener('activate',
530 this.handleActivate_.bind(this));
531 imageGrid.addEventListener('select',
532 this.handleSelect_.bind(this));
533 imageGrid.addEventListener('phototaken',
534 this.handlePhotoTaken_.bind(this));
535 imageGrid.addEventListener('photoupdated',
536 this.handlePhotoUpdated_.bind(this));
537 // Set the title for camera item in the grid.
538 imageGrid.setCameraTitles(
539 loadTimeData.getString('takePhoto'),
540 loadTimeData.getString('photoFromCamera'));
542 this.getScreenElement('take-photo').addEventListener(
543 'click', this.handleTakePhoto_.bind(this));
544 this.getScreenElement('discard-photo').addEventListener(
545 'click', this.handleDiscardPhoto_.bind(this));
547 // Toggle 'animation' class for the duration of WebKit transition.
548 this.getScreenElement('flip-photo').addEventListener(
549 'click', function(e) {
550 previewElement.classList.add('animation');
551 imageGrid.flipPhoto = !imageGrid.flipPhoto;
553 this.getScreenElement('image-stream-crop').addEventListener(
554 'webkitTransitionEnd', function(e) {
555 previewElement.classList.remove('animation');
557 this.getScreenElement('image-preview-img').addEventListener(
558 'webkitTransitionEnd', function(e) {
559 previewElement.classList.remove('animation');
566 * Creates button for adding to controls.
567 * @param {string} buttonId -- id for button, have to be unique within
568 * screen. Actual id will be prefixed with screen name and appended with
569 * '-button'. Use getScreenButton(buttonId) to find it later.
570 * @param {string} i18nPrefix -- screen prefix for i18n values.
571 * @param {function} callback -- will be called on button press with
572 * buttonId parameter.
573 * @param {array} pages -- list of pages where this button should be
575 * @param {array} classes -- list of additional CSS classes for button.
577 makeButton: function(buttonId, i18nPrefix, callback, pages, classes) {
578 var capitalizedId = buttonId.charAt(0).toUpperCase() + buttonId.slice(1);
579 this.buttonIds.push(buttonId);
580 var result = this.ownerDocument.createElement('button');
581 result.id = this.name() + '-' + buttonId + '-button';
582 result.classList.add('screen-control-button');
583 for (var i = 0; i < classes.length; i++) {
584 result.classList.add(classes[i]);
586 result.textContent = loadTimeData.
587 getString(i18nPrefix + capitalizedId + 'ButtonTitle');
588 result.addEventListener('click', function(e) {
592 result.pages = pages;
597 * Simple validator for |configureTextInput|.
598 * Element is considered valid if it has any text.
599 * @param {Element} element - element to be validated.
600 * @return {boolean} - true, if element has any text.
602 validIfNotEmpty_: function(element) {
603 return (element.value.length > 0);
607 * Configure text-input |element|.
608 * @param {Element} element - element to be configured.
609 * @param {function(element)} inputChangeListener - function that will be
610 * called upon any button press/release.
611 * @param {function(element)} validator - function that will be called when
612 * Enter is pressed. If it returns |true| then advance to next element.
613 * @param {function(element)} moveFocus - function that will determine next
614 * element and move focus to it.
615 * @param {function(element)} errorHider - function that is called upon
616 * every button press, so that any associated error can be hidden.
618 configureTextInput: function(element,
623 element.addEventListener('keydown', function(e) {
624 if (e.keyIdentifier == 'Enter') {
625 var dataValid = true;
627 dataValid = validator(element);
639 if (inputChangeListener)
640 inputChangeListener(element);
642 element.addEventListener('keyup', function(e) {
643 if (inputChangeListener)
644 inputChangeListener(element);
649 * Makes element from template.
650 * @param {string} templateId -- template will be looked up within screen
651 * by class with name "template-<templateId>".
652 * @param {string} elementId -- id for result, uinque within screen. Actual
653 * id will be prefixed with screen name. Use getScreenElement(id) to find
656 makeFromTemplate: function(templateId, elementId) {
657 var templateClassName = 'template-' + templateId;
658 var templateNode = this.querySelector('.' + templateClassName);
659 var screenPrefix = this.name() + '-';
660 var result = templateNode.cloneNode(true);
661 result.classList.remove(templateClassName);
662 result.id = screenPrefix + elementId;
667 * @param {string} buttonId -- id of button to be found,
668 * @return {Element} button created by makeButton with given buttonId.
670 getScreenButton: function(buttonId) {
671 var fullId = this.name() + '-' + buttonId + '-button';
672 return this.getScreenElement(buttonId + '-button');
676 * @param {string} elementId -- id of element to be found,
677 * @return {Element} button created by makeFromTemplate with elementId.
679 getScreenElement: function(elementId) {
680 var fullId = this.name() + '-' + elementId;
686 * @type {!Array} Array of Buttons.
691 var status = this.makeFromTemplate('status-container', 'status');
692 buttons.push(status);
694 var importLink = this.makeFromTemplate('import-supervised-user-link',
696 importLink.hidden = true;
697 buttons.push(importLink);
698 var linkElement = importLink.querySelector('.signin-link');
699 linkElement.addEventListener('click',
700 this.importLinkPressed_.bind(this));
702 var createLink = this.makeFromTemplate('create-supervised-user-link',
704 createLink.hidden = true;
705 buttons.push(createLink);
707 linkElement = createLink.querySelector('.signin-link');
708 linkElement.addEventListener('click',
709 this.createLinkPressed_.bind(this));
711 buttons.push(this.makeButton(
713 'managedUserCreationFlow',
714 this.startButtonPressed_.bind(this),
716 ['custom-appearance', 'button-fancy', 'button-blue']));
718 buttons.push(this.makeButton(
720 'managedUserCreationFlow',
721 this.prevButtonPressed_.bind(this),
725 buttons.push(this.makeButton(
727 'managedUserCreationFlow',
728 this.nextButtonPressed_.bind(this),
729 ['manager', 'username'],
732 buttons.push(this.makeButton(
734 'managedUserCreationFlow',
735 this.importButtonPressed_.bind(this),
736 ['import', 'import-password'],
739 buttons.push(this.makeButton(
741 'managedUserCreationFlow',
742 this.gotItButtonPressed_.bind(this),
744 ['custom-appearance', 'button-fancy', 'button-blue']));
749 * Does sanity check and calls backend with current user name/password pair
750 * to authenticate manager. May result in showManagerPasswordError.
753 validateAndLogInAsManager_: function() {
754 var selectedPod = this.managerList_.selectedPod_;
755 if (null == selectedPod)
758 var managerId = selectedPod.user.username;
759 var managerDisplayId = selectedPod.user.emailAddress;
760 var managerPassword = selectedPod.passwordElement.value;
761 if (managerPassword.length == 0)
765 this.disabled = true;
766 this.context_.managerId = managerId;
767 this.context_.managerDisplayId = managerDisplayId;
768 this.context_.managerName = selectedPod.user.displayName;
769 chrome.send('authenticateManagerInLocallyManagedUserCreationFlow',
770 [managerId, managerPassword]);
774 * Does sanity check and calls backend with user display name/password pair
778 validateAndCreateLocallyManagedUser_: function() {
779 var firstPassword = $('managed-user-creation-password').value;
781 $('managed-user-creation-password-confirm').value;
782 var userName = $('managed-user-creation-name').value;
783 if (firstPassword != secondPassword) {
784 this.showPasswordError(
785 loadTimeData.getString('createManagedUserPasswordMismatchError'));
790 this.disabled = true;
791 this.context_.managedName = userName;
792 chrome.send('specifyLocallyManagedUserCreationFlowUserData',
793 [userName, firstPassword]);
797 * Does sanity check and calls backend with selected existing supervised
798 * user id to import user.
801 importSupervisedUser_: function() {
804 if (this.currentPage_ == 'import-password') {
805 var firstPassword = this.getScreenElement('password').value;
806 var secondPassword = this.getScreenElement('password-confirm').value;
807 if (firstPassword != secondPassword) {
808 this.showPasswordError(
809 loadTimeData.getString('createManagedUserPasswordMismatchError'));
812 var userId = this.context_.importUserId;
813 this.disabled = true;
814 chrome.send('importSupervisedUserWithPassword',
815 [userId, firstPassword]);
818 var selectedPod = this.importList_.selectedPod_;
821 var user = selectedPod.user;
822 var userId = user.id;
824 this.context_.importUserId = userId;
825 this.context_.managedName = user.name;
826 this.context_.selectedImageUrl = user.avatarurl;
827 if (!user.needPassword) {
828 this.disabled = true;
829 chrome.send('importSupervisedUser', [userId]);
831 this.setVisiblePage_('import-password');
837 * Calls backend part to check if current user name is valid/not taken.
838 * Results in call to either managedUserNameOk or managedUserNameError.
841 checkUserName_: function() {
842 var userName = this.getScreenElement('name').value;
845 if (userName == this.lastIncorrectUserName_ ||
846 userName == this.lastVerifiedName_) {
849 if (userName.length > 0) {
850 chrome.send('checkLocallyManagedUserName', [userName]);
852 this.nameErrorVisible = false;
853 this.lastVerifiedName_ = null;
854 this.lastIncorrectUserName_ = null;
855 this.updateNextButtonForUser_();
860 * Called by backend part in case of successful name validation.
861 * @param {string} name - name that was validated.
863 managedUserNameOk: function(name) {
864 this.lastVerifiedName_ = name;
865 this.lastIncorrectUserName_ = null;
866 if ($('managed-user-creation-name').value == name)
867 this.clearUserNameError_();
868 this.updateNextButtonForUser_();
872 * Called by backend part in case of name validation failure.
873 * @param {string} name - name that was validated.
874 * @param {string} errorText - reason why this name is invalid.
876 managedUserNameError: function(name, errorText) {
877 this.disabled = false;
878 this.lastIncorrectUserName_ = name;
879 this.lastVerifiedName_ = null;
881 var userNameField = $('managed-user-creation-name');
882 if (userNameField.value == this.lastIncorrectUserName_) {
883 this.nameErrorVisible = true;
884 $('bubble').showTextForElement(
885 $('managed-user-creation-name'),
887 cr.ui.Bubble.Attachment.RIGHT,
889 this.setButtonDisabledStatus('next', true);
893 managedUserSuggestImport: function(name, user_id) {
894 this.disabled = false;
895 this.lastIncorrectUserName_ = name;
896 this.lastVerifiedName_ = null;
898 var userNameField = $('managed-user-creation-name');
899 var creationScreen = this;
901 if (userNameField.value == this.lastIncorrectUserName_) {
902 this.nameErrorVisible = true;
903 var link = this.ownerDocument.createElement('div');
904 link.innerHTML = loadTimeData.getStringF(
906 '<a class="signin-link" href="#">',
909 link.querySelector('.signin-link').addEventListener('click',
911 creationScreen.handleSuggestImport_(user_id);
914 $('bubble').showContentForElement(
915 $('managed-user-creation-name'),
916 cr.ui.Bubble.Attachment.RIGHT,
919 this.setButtonDisabledStatus('next', true);
924 * Clears user name error, if name is no more guaranteed to be invalid.
927 clearUserNameError_: function() {
929 if ($('managed-user-creation-name').value ==
930 this.lastIncorrectUserName_) {
933 this.nameErrorVisible = false;
937 * Called by backend part in case of password validation failure.
938 * @param {string} errorText - reason why this password is invalid.
940 showPasswordError: function(errorText) {
941 $('bubble').showTextForElement(
942 $('managed-user-creation-password'),
944 cr.ui.Bubble.Attachment.RIGHT,
946 $('managed-user-creation-password').classList.add('password-error');
947 $('managed-user-creation-password').focus();
948 this.disabled = false;
949 this.setButtonDisabledStatus('next', true);
953 * True if user name error should be displayed.
956 set nameErrorVisible(value) {
957 $('managed-user-creation-name').
958 classList.toggle('duplicate-name', value);
964 * Updates state of Continue button after minimal checks.
965 * @return {boolean} true, if form seems to be valid.
968 updateNextButtonForManager_: function() {
969 var selectedPod = this.managerList_.selectedPod_;
970 canProceed = null != selectedPod &&
971 selectedPod.passwordElement.value.length > 0;
973 this.setButtonDisabledStatus('next', !canProceed);
978 * Updates state of Continue button after minimal checks.
979 * @return {boolean} true, if form seems to be valid.
982 updateNextButtonForUser_: function() {
983 var firstPassword = this.getScreenElement('password').value;
984 var secondPassword = this.getScreenElement('password-confirm').value;
985 var userName = this.getScreenElement('name').value;
987 var passwordOk = (firstPassword.length > 0) &&
988 (firstPassword.length == secondPassword.length);
990 if (this.currentPage_ == 'import-password') {
991 this.setButtonDisabledStatus('import', !passwordOk);
994 var imageGrid = this.getScreenElement('image-grid');
995 var imageChosen = !(imageGrid.selectionType == 'camera' &&
996 imageGrid.cameraLive);
999 (userName.length > 0) &&
1000 this.lastVerifiedName_ &&
1001 (userName == this.lastVerifiedName_) &&
1004 this.setButtonDisabledStatus('next', !canProceed);
1008 showSelectedManagerPasswordError_: function() {
1009 var selectedPod = this.managerList_.selectedPod_;
1010 selectedPod.showPasswordError();
1011 selectedPod.passwordElement.value = '';
1012 selectedPod.focusInput();
1013 this.updateNextButtonForManager_();
1017 * Enables one particular subpage and hides the rest.
1018 * @param {string} visiblePage - name of subpage.
1021 setVisiblePage_: function(visiblePage) {
1022 this.disabled = false;
1025 if (!this.imagesRequested_) {
1026 chrome.send('supervisedUserGetImages');
1027 this.imagesRequested_ = true;
1029 var pageNames = ['intro',
1035 var pageButtons = {'intro' : 'start',
1037 'import' : 'import',
1038 'import-password' : 'import',
1039 'created' : 'gotit'};
1041 var pageToDisplay = visiblePage;
1042 if (visiblePage == 'import-password')
1043 pageToDisplay = 'username';
1045 for (i in pageNames) {
1046 var pageName = pageNames[i];
1047 var page = $('managed-user-creation-' + pageName);
1048 page.hidden = (pageName != pageToDisplay);
1049 if (pageName == pageToDisplay)
1050 $('step-logo').hidden = page.classList.contains('step-no-logo');
1053 for (i in this.buttonIds) {
1054 var button = this.getScreenButton(this.buttonIds[i]);
1055 button.hidden = button.pages.indexOf(visiblePage) < 0;
1056 button.disabled = false;
1059 var pagesWithCancel = ['intro', 'manager', 'username', 'import-password',
1061 var cancelButton = $('cancel-add-user-button');
1062 cancelButton.hidden = pagesWithCancel.indexOf(visiblePage) < 0;
1063 cancelButton.disabled = false;
1065 this.getScreenElement('import-link').hidden = true;
1066 this.getScreenElement('create-link').hidden = true;
1068 if (pageButtons[visiblePage])
1069 this.getScreenButton(pageButtons[visiblePage]).focus();
1071 this.currentPage_ = visiblePage;
1073 if (visiblePage == 'manager' || visiblePage == 'intro') {
1074 $('managed-user-creation-password').classList.remove('password-error');
1075 if (this.managerList_.pods.length > 0)
1076 this.managerList_.selectPod(this.managerList_.pods[0]);
1079 if (visiblePage == 'username' || visiblePage == 'import-password') {
1080 var elements = this.getScreenElement(pageToDisplay).
1081 querySelectorAll('.hide-on-import');
1082 for (var i = 0; i < elements.length; i++) {
1083 elements[i].classList.toggle('hidden-on-import',
1084 visiblePage == 'import-password');
1087 if (visiblePage == 'username') {
1088 var imageGrid = this.getScreenElement('image-grid');
1089 // select some image.
1090 var selected = this.imagesData_[
1091 Math.floor(Math.random() * this.imagesData_.length)];
1092 this.context_.selectedImageUrl = selected.url;
1093 imageGrid.selectedItemUrl = selected.url;
1094 chrome.send('supervisedUserSelectImage',
1095 [selected.url, 'default']);
1096 this.getScreenElement('image-grid').redraw();
1097 this.checkUserName_();
1098 this.updateNextButtonForUser_();
1099 this.getScreenElement('name').focus();
1100 this.getScreenElement('import-link').hidden =
1101 this.importList_.pods.length == 0;
1102 } else if (visiblePage == 'import-password') {
1103 var imageGrid = this.getScreenElement('image-grid');
1105 if ('selectedImageUrl' in this.context_) {
1106 selected = this.context_.selectedImageUrl;
1108 // select some image.
1109 selected = this.imagesData_[
1110 Math.floor(Math.random() * this.imagesData_.length)].url;
1111 chrome.send('supervisedUserSelectImage',
1112 [selected, 'default']);
1114 imageGrid.selectedItemUrl = selected;
1115 this.getScreenElement('image-grid').redraw();
1117 this.updateNextButtonForUser_();
1119 this.getScreenElement('password').focus();
1120 this.getScreenElement('import-link').hidden = true;
1122 this.getScreenElement('image-grid').stopCamera();
1124 if (visiblePage == 'import') {
1125 this.getScreenElement('create-link').hidden = false;
1126 this.getScreenButton('import').disabled =
1127 !this.importList_.selectedPod_ ||
1128 this.importList_.selectedPod_.user.exists;
1130 chrome.send('currentSupervisedUserPage', [this.currentPage_]);
1133 setButtonDisabledStatus: function(buttonName, status) {
1134 var button = $('managed-user-creation-' + buttonName + '-button');
1135 button.disabled = status;
1138 gotItButtonPressed_: function() {
1139 chrome.send('finishLocalManagedUserCreation');
1142 handleErrorButtonPressed_: function() {
1143 chrome.send('abortLocalManagedUserCreation');
1146 startButtonPressed_: function() {
1147 this.setVisiblePage_('manager');
1148 this.setButtonDisabledStatus('next', true);
1151 nextButtonPressed_: function() {
1152 if (this.currentPage_ == 'manager') {
1153 this.validateAndLogInAsManager_();
1156 if (this.currentPage_ == 'username') {
1157 this.validateAndCreateLocallyManagedUser_();
1161 importButtonPressed_: function() {
1162 this.importSupervisedUser_();
1165 importLinkPressed_: function() {
1166 this.setVisiblePage_('import');
1169 handleSuggestImport_: function(user_id) {
1170 this.setVisiblePage_('import');
1171 this.importList_.selectUser(user_id);
1174 createLinkPressed_: function() {
1175 this.setVisiblePage_('username');
1176 this.lastIncorrectUserName_ = null;
1177 this.lastVerifiedName_ = null;
1178 this.checkUserName_();
1181 prevButtonPressed_: function() {
1182 this.setVisiblePage_('intro');
1185 showProgress: function(text) {
1186 var status = this.getScreenElement('status');
1187 var statusText = status.querySelector('.id-text');
1188 statusText.textContent = text;
1189 statusText.classList.remove('error');
1190 status.querySelector('.id-spinner').hidden = false;
1191 status.hidden = false;
1192 this.getScreenElement('import-link').hidden = true;
1193 this.getScreenElement('create-link').hidden = true;
1196 showStatusError: function(text) {
1197 var status = this.getScreenElement('status');
1198 var statusText = status.querySelector('.id-text');
1199 statusText.textContent = text;
1200 statusText.classList.add('error');
1201 status.querySelector('.id-spinner').hidden = true;
1202 status.hidden = false;
1203 this.getScreenElement('import-link').hidden = true;
1204 this.getScreenElement('create-link').hidden = true;
1207 hideStatus_: function() {
1208 var status = this.getScreenElement('status');
1209 status.hidden = true;
1213 * Updates state of login header so that necessary buttons are displayed.
1215 onBeforeShow: function(data) {
1216 $('login-header-bar').signinUIState =
1217 SIGNIN_UI_STATE.MANAGED_USER_CREATION_FLOW;
1218 if (data['managers']) {
1219 this.loadManagers(data['managers']);
1221 var imageGrid = this.getScreenElement('image-grid');
1222 imageGrid.updateAndFocus();
1226 * Update state of login header so that necessary buttons are displayed.
1228 onBeforeHide: function() {
1229 $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
1230 this.getScreenElement('image-grid').stopCamera();
1234 * Returns a control which should receive an initial focus.
1236 get defaultControl() {
1237 return $('managed-user-creation-name');
1241 * True if the the screen is disabled (handles no user interaction).
1247 return this.disabled_;
1250 set disabled(value) {
1251 this.disabled_ = value;
1252 var controls = this.querySelectorAll('button,input');
1253 for (var i = 0, control; control = controls[i]; ++i) {
1254 control.disabled = value;
1256 $('login-header-bar').disabled = value;
1260 * Called by backend part to propagate list of possible managers.
1261 * @param {Array} userList - list of users that can be managers.
1263 loadManagers: function(userList) {
1264 $('managed-user-creation-managers-block').hidden = false;
1265 this.managerList_.clearPods();
1266 for (var i = 0; i < userList.length; ++i)
1267 this.managerList_.addPod(userList[i]);
1268 if (userList.length > 0)
1269 this.managerList_.selectPod(this.managerList_.pods[0]);
1273 * Cancels user creation and drops to user screen (either sign).
1275 cancel: function() {
1276 var notSignedInPages = ['intro', 'manager'];
1277 var postCreationPages = ['created'];
1278 if (notSignedInPages.indexOf(this.currentPage_) >= 0) {
1279 // Make sure no manager password is kept:
1280 this.managerList_.clearPods();
1282 $('pod-row').loadLastWallpaper();
1284 Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
1285 Oobe.resetSigninUI(true);
1288 if (postCreationPages.indexOf(this.currentPage_) >= 0) {
1289 chrome.send('finishLocalManagedUserCreation');
1292 chrome.send('abortLocalManagedUserCreation');
1295 updateText_: function() {
1296 var managerDisplayId = this.context_.managerDisplayId;
1297 this.updateElementText_('intro-alternate-text',
1298 'createManagedUserIntroAlternateText');
1299 this.updateElementText_('created-text-1',
1300 'createManagedUserCreatedText1',
1301 this.context_.managedName);
1302 // TODO(antrim): Move wrapping with strong in grd file, and eliminate this
1304 this.updateElementText_('created-text-2',
1305 'createManagedUserCreatedText2',
1307 loadTimeData.getString('managementURL')),
1308 this.context_.managedName);
1309 this.updateElementText_('created-text-3',
1310 'createManagedUserCreatedText3',
1312 this.updateElementText_('name-explanation',
1313 'createManagedUserNameExplanation',
1317 wrapStrong: function(original) {
1318 if (original == undefined)
1320 return '<strong>' + original + '</strong>';
1323 updateElementText_: function(localId, templateName) {
1324 var args = Array.prototype.slice.call(arguments);
1326 this.getScreenElement(localId).innerHTML =
1327 loadTimeData.getStringF.apply(loadTimeData, args);
1330 showIntroPage: function() {
1331 $('managed-user-creation-password').value = '';
1332 $('managed-user-creation-password-confirm').value = '';
1333 $('managed-user-creation-name').value = '';
1335 this.lastVerifiedName_ = null;
1336 this.lastIncorrectUserName_ = null;
1337 this.passwordErrorVisible = false;
1338 $('managed-user-creation-password').classList.remove('password-error');
1339 this.nameErrorVisible = false;
1341 this.setVisiblePage_('intro');
1344 showManagerPage: function() {
1345 this.setVisiblePage_('manager');
1348 showUsernamePage: function() {
1349 this.setVisiblePage_('username');
1352 showTutorialPage: function() {
1353 this.setVisiblePage_('created');
1356 showPage: function(page) {
1357 this.setVisiblePage_(page);
1360 showErrorPage: function(errorTitle, errorText, errorButtonText) {
1361 this.disabled = false;
1362 $('managed-user-creation-error-title').innerHTML = errorTitle;
1363 $('managed-user-creation-error-text').innerHTML = errorText;
1364 $('managed-user-creation-error-button').textContent = errorButtonText;
1365 this.setVisiblePage_('error');
1368 showManagerPasswordError: function() {
1369 this.disabled = false;
1370 this.showSelectedManagerPasswordError_();
1374 TODO(antrim) : this is an explicit code duplications with UserImageScreen.
1375 It should be removed by issue 251179.
1378 * Currently selected user image index (take photo button is with zero
1382 selectedUserImage_: -1,
1385 setDefaultImages: function(imagesData) {
1386 var imageGrid = this.getScreenElement('image-grid');
1387 for (var i = 0, data; data = imagesData[i]; i++) {
1388 var item = imageGrid.addItem(data.url, data.title);
1389 item.type = 'default';
1390 item.author = data.author || '';
1391 item.website = data.website || '';
1393 this.imagesData_ = imagesData;
1397 handleActivate_: function() {
1398 var imageGrid = this.getScreenElement('image-grid');
1399 if (imageGrid.selectedItemUrl == ButtonImages.TAKE_PHOTO) {
1400 this.handleTakePhoto_();
1403 this.nextButtonPressed_();
1407 * Handles selection change.
1408 * @param {Event} e Selection change event.
1411 handleSelect_: function(e) {
1412 var imageGrid = this.getScreenElement('image-grid');
1413 this.updateNextButtonForUser_();
1415 $('managed-user-creation-flip-photo').tabIndex =
1416 (imageGrid.selectionType == 'camera') ? 0 : -1;
1417 if (imageGrid.cameraLive || imageGrid.selectionType != 'camera')
1418 imageGrid.previewElement.classList.remove('phototaken');
1420 imageGrid.previewElement.classList.add('phototaken');
1422 if (!imageGrid.cameraLive || imageGrid.selectionType != 'camera') {
1423 this.context_.selectedImageUrl = imageGrid.selectedItemUrl;
1424 chrome.send('supervisedUserSelectImage',
1425 [imageGrid.selectedItemUrl, imageGrid.selectionType]);
1427 // Start/stop camera on (de)selection.
1428 if (!imageGrid.inProgramSelection &&
1429 imageGrid.selectionType != e.oldSelectionType) {
1430 if (imageGrid.selectionType == 'camera') {
1431 // Programmatic selection of camera item is done in
1432 // startCamera callback where streaming is started by itself.
1433 imageGrid.startCamera(
1435 // Start capture if camera is still the selected item.
1436 $('managed-user-creation-image-preview-img').classList.toggle(
1437 'animated-transform', true);
1438 return imageGrid.selectedItem == imageGrid.cameraImage;
1441 $('managed-user-creation-image-preview-img').classList.toggle(
1442 'animated-transform', false);
1443 imageGrid.stopCamera();
1449 * Handle photo capture from the live camera stream.
1451 handleTakePhoto_: function(e) {
1452 this.getScreenElement('image-grid').takePhoto();
1455 handlePhotoTaken_: function(e) {
1456 chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1460 * Handle photo updated event.
1461 * @param {Event} e Event with 'dataURL' property containing a data URL.
1463 handlePhotoUpdated_: function(e) {
1464 chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1468 * Handle discarding the captured photo.
1470 handleDiscardPhoto_: function(e) {
1471 var imageGrid = this.getScreenElement('image-grid');
1472 imageGrid.discardPhoto();
1475 setCameraPresent: function(present) {
1476 this.getScreenElement('image-grid').cameraPresent = present;
1479 setExistingManagedUsers: function(users) {
1480 var userList = users;
1482 userList.sort(function(a, b) {
1483 // Put existing users last.
1484 if (a.exists != b.exists)
1485 return a.exists ? 1 : -1;
1486 // Sort rest by name.
1487 return a.name.localeCompare(b.name, [], {sensitivity: 'base'});
1490 this.importList_.clearPods();
1491 for (var i = 0; i < userList.length; ++i)
1492 this.importList_.addPod(userList[i]);
1494 if (userList.length == 1)
1495 this.importList_.selectPod(this.managerList_.pods[0]);
1497 if (userList.length > 0 && this.currentPage_ == 'username')
1498 this.getScreenElement('import-link').hidden = false;