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', this.handleMouseDown_.bind(this));
260 var screen = $('managed-user-creation');
261 var importList = screen.importList_;
265 * Updates UI elements from user data.
268 this.imageElement.src = this.user.avatarurl;
269 this.nameElement.textContent = this.user.name;
270 if (this.user.exists) {
271 if (this.user.conflict == 'imported') {
272 this.nameElement.textContent =
273 loadTimeData.getStringF('importUserExists', this.user.name);
275 this.nameElement.textContent =
276 loadTimeData.getStringF('importUsernameExists', this.user.name);
279 this.classList.toggle('imported', this.user.exists);
283 * Gets image element.
284 * @type {!HTMLImageElement}
287 return this.querySelector('.import-pod-image');
292 * @type {!HTMLDivElement}
295 return this.querySelector('.import-pod-name');
299 handleMouseDown_: function(e) {
300 this.parentNode.selectPod(this);
301 // Prevent default so that we don't trigger 'focus' event.
306 * The user that this pod represents.
316 this.user_ = userDict;
321 var ImportPodList = cr.ui.define('div');
324 * UI element for selecting existing supervised user for import.
327 ImportPodList.prototype = {
328 __proto__: HTMLDivElement.prototype,
333 decorate: function() {
334 this.setAttribute('tabIndex', 0);
335 this.classList.add('nofocus');
336 var importList = this;
337 var screen = $('managed-user-creation');
339 this.addEventListener('focus', function(e) {
340 if (importList.selectedPod_ == null) {
341 if (importList.pods.length > 0)
342 importList.selectPod(importList.pods[0]);
346 this.addEventListener('keydown', function(e) {
347 switch (e.keyIdentifier) {
349 importList.selectNextPod(-1);
353 if (importList.selectedPod_ != null)
354 screen.importSupervisedUser_();
358 importList.selectNextPod(+1);
366 * Returns all the pods in this pod list.
370 return this.children;
374 * Returns selected pod.
378 return this.selectedPod_;
381 addPod: function(user) {
382 var importPod = new ImportPod({user: user});
383 this.appendChild(importPod);
387 clearPods: function() {
389 this.selectedPod_ = null;
392 scrollIntoView: function(pod) {
393 scroller = this.parentNode;
394 var itemHeight = pod.getBoundingClientRect().height;
395 var scrollTop = scroller.scrollTop;
396 var top = pod.offsetTop - scroller.offsetTop;
397 var clientHeight = scroller.clientHeight;
401 // Function to adjust the tops of viewport and row.
402 function scrollToAdjustTop() {
403 self.scrollTop = top;
406 // Function to adjust the bottoms of viewport and row.
407 function scrollToAdjustBottom() {
408 var cs = getComputedStyle(self);
409 var paddingY = parseInt(cs.paddingTop, 10) +
410 parseInt(cs.paddingBottom, 10);
412 if (top + itemHeight > scrollTop + clientHeight - paddingY) {
413 self.scrollTop = top + itemHeight - clientHeight + paddingY;
419 // Check if the entire of given indexed row can be shown in the viewport.
420 if (itemHeight <= clientHeight) {
422 return scrollToAdjustTop();
423 if (scrollTop + clientHeight < top + itemHeight)
424 return scrollToAdjustBottom();
427 return scrollToAdjustTop();
428 if (top + itemHeight < scrollTop + clientHeight)
429 return scrollToAdjustBottom();
435 * @param {Element} podToSelect - pod to select, can be null.
437 selectPod: function(podToSelect) {
438 if ((this.selectedPod_ == podToSelect) && !!podToSelect) {
441 this.selectedPod_ = podToSelect;
442 for (var i = 0; i < this.pods.length; i++) {
443 var pod = this.pods[i];
444 if (pod != podToSelect)
445 pod.classList.remove('focused');
449 podToSelect.classList.add('focused');
451 var screen = $('managed-user-creation');
452 if (!this.selectedPod_) {
453 screen.getScreenButton('import').disabled = true;
455 screen.getScreenButton('import').disabled =
456 this.selectedPod_.user.exists;
457 if (!this.selectedPod_.user.exists) {
458 chrome.send('userSelectedForImportInManagedUserCreationFlow',
459 [podToSelect.user.id]);
464 selectNextPod: function(direction) {
465 if (!this.selectedPod_)
468 for (var i = 0, pod; pod = this.pods[i]; ++i) {
469 if (pod == this.selectedPod_) {
476 index = index + direction;
477 if (index < 0 || index >= this.pods.length)
479 this.selectPod(this.pods[index]);
483 selectUser: function(user_id) {
484 for (var i = 0, pod; pod = this.pods[i]; ++i) {
485 if (pod.user.id == user_id) {
487 this.scrollIntoView(pod);
497 'managedUserSuggestImport',
498 'managedUserNameError',
503 'showManagerPasswordError',
512 'setExistingManagedUsers',
515 lastVerifiedName_: null,
516 lastIncorrectUserName_: null,
521 imagesRequested_: false,
523 // Contains data that can be auto-shared with handler.
527 decorate: function() {
528 this.managerList_ = new ManagerPodList();
529 $('managed-user-creation-managers-pane').appendChild(this.managerList_);
531 this.importList_ = new ImportPodList();
532 $('managed-user-creation-import-pane').appendChild(this.importList_);
534 var userNameField = $('managed-user-creation-name');
535 var passwordField = $('managed-user-creation-password');
536 var password2Field = $('managed-user-creation-password-confirm');
538 var creationScreen = this;
540 var hideUserPasswordError = function(element) {
542 $('managed-user-creation-password').classList.remove('password-error');
545 this.configureTextInput(userNameField,
546 this.checkUserName_.bind(this),
547 this.validIfNotEmpty_.bind(this),
549 passwordField.focus();
551 this.clearUserNameError_.bind(this));
552 this.configureTextInput(passwordField,
553 this.updateNextButtonForUser_.bind(this),
554 this.validIfNotEmpty_.bind(this),
556 password2Field.focus();
558 hideUserPasswordError);
559 this.configureTextInput(password2Field,
560 this.updateNextButtonForUser_.bind(this),
561 this.validIfNotEmpty_.bind(this),
563 creationScreen.getScreenButton('next').focus();
565 hideUserPasswordError);
567 this.getScreenButton('error').addEventListener('click', function(e) {
568 creationScreen.handleErrorButtonPressed_();
573 TODO(antrim) : this is an explicit code duplications with UserImageScreen.
574 It should be removed by issue 251179.
576 var imageGrid = this.getScreenElement('image-grid');
577 UserImagesGrid.decorate(imageGrid);
579 // Preview image will track the selected item's URL.
580 var previewElement = this.getScreenElement('image-preview');
581 previewElement.oncontextmenu = function(e) { e.preventDefault(); };
583 imageGrid.previewElement = previewElement;
584 imageGrid.selectionType = 'default';
585 imageGrid.flipPhotoElement = this.getScreenElement('flip-photo');
587 imageGrid.addEventListener('activate',
588 this.handleActivate_.bind(this));
589 imageGrid.addEventListener('select',
590 this.handleSelect_.bind(this));
591 imageGrid.addEventListener('phototaken',
592 this.handlePhotoTaken_.bind(this));
593 imageGrid.addEventListener('photoupdated',
594 this.handlePhotoUpdated_.bind(this));
595 // Set the title for camera item in the grid.
596 imageGrid.setCameraTitles(
597 loadTimeData.getString('takePhoto'),
598 loadTimeData.getString('photoFromCamera'));
600 this.getScreenElement('take-photo').addEventListener(
601 'click', this.handleTakePhoto_.bind(this));
602 this.getScreenElement('discard-photo').addEventListener(
603 'click', this.handleDiscardPhoto_.bind(this));
605 // Toggle 'animation' class for the duration of WebKit transition.
606 this.getScreenElement('flip-photo').addEventListener(
607 'click', this.handleFlipPhoto_.bind(this));
608 this.getScreenElement('image-stream-crop').addEventListener(
609 'webkitTransitionEnd', function(e) {
610 previewElement.classList.remove('animation');
612 this.getScreenElement('image-preview-img').addEventListener(
613 'webkitTransitionEnd', function(e) {
614 previewElement.classList.remove('animation');
621 * Creates button for adding to controls.
622 * @param {string} buttonId -- id for button, have to be unique within
623 * screen. Actual id will be prefixed with screen name and appended with
624 * '-button'. Use getScreenButton(buttonId) to find it later.
625 * @param {string} i18nPrefix -- screen prefix for i18n values.
626 * @param {function} callback -- will be called on button press with
627 * buttonId parameter.
628 * @param {array} pages -- list of pages where this button should be
630 * @param {array} classes -- list of additional CSS classes for button.
632 makeButton: function(buttonId, i18nPrefix, callback, pages, classes) {
633 var capitalizedId = buttonId.charAt(0).toUpperCase() + buttonId.slice(1);
634 this.buttonIds.push(buttonId);
635 var result = this.ownerDocument.createElement('button');
636 result.id = this.name() + '-' + buttonId + '-button';
637 result.classList.add('screen-control-button');
638 for (var i = 0; i < classes.length; i++) {
639 result.classList.add(classes[i]);
641 result.textContent = loadTimeData.
642 getString(i18nPrefix + capitalizedId + 'ButtonTitle');
643 result.addEventListener('click', function(e) {
647 result.pages = pages;
652 * Simple validator for |configureTextInput|.
653 * Element is considered valid if it has any text.
654 * @param {Element} element - element to be validated.
655 * @return {boolean} - true, if element has any text.
657 validIfNotEmpty_: function(element) {
658 return (element.value.length > 0);
662 * Configure text-input |element|.
663 * @param {Element} element - element to be configured.
664 * @param {function(element)} inputChangeListener - function that will be
665 * called upon any button press/release.
666 * @param {function(element)} validator - function that will be called when
667 * Enter is pressed. If it returns |true| then advance to next element.
668 * @param {function(element)} moveFocus - function that will determine next
669 * element and move focus to it.
670 * @param {function(element)} errorHider - function that is called upon
671 * every button press, so that any associated error can be hidden.
673 configureTextInput: function(element,
678 element.addEventListener('keydown', function(e) {
679 if (e.keyIdentifier == 'Enter') {
680 var dataValid = true;
682 dataValid = validator(element);
694 if (inputChangeListener)
695 inputChangeListener(element);
697 element.addEventListener('keyup', function(e) {
698 if (inputChangeListener)
699 inputChangeListener(element);
704 * Makes element from template.
705 * @param {string} templateId -- template will be looked up within screen
706 * by class with name "template-<templateId>".
707 * @param {string} elementId -- id for result, uinque within screen. Actual
708 * id will be prefixed with screen name. Use getScreenElement(id) to find
711 makeFromTemplate: function(templateId, elementId) {
712 var templateClassName = 'template-' + templateId;
713 var templateNode = this.querySelector('.' + templateClassName);
714 var screenPrefix = this.name() + '-';
715 var result = templateNode.cloneNode(true);
716 result.classList.remove(templateClassName);
717 result.id = screenPrefix + elementId;
722 * @param {string} buttonId -- id of button to be found,
723 * @return {Element} button created by makeButton with given buttonId.
725 getScreenButton: function(buttonId) {
726 var fullId = this.name() + '-' + buttonId + '-button';
727 return this.getScreenElement(buttonId + '-button');
731 * @param {string} elementId -- id of element to be found,
732 * @return {Element} button created by makeFromTemplate with elementId.
734 getScreenElement: function(elementId) {
735 var fullId = this.name() + '-' + elementId;
741 * @type {!Array} Array of Buttons.
744 var links = this.ownerDocument.createElement('div');
745 var buttons = this.ownerDocument.createElement('div');
746 links.classList.add('controls-links');
747 buttons.classList.add('controls-buttons');
749 var importLink = this.makeFromTemplate('import-supervised-user-link',
751 importLink.hidden = true;
752 links.appendChild(importLink);
754 var linkElement = importLink.querySelector('.signin-link');
755 linkElement.addEventListener('click',
756 this.importLinkPressed_.bind(this));
758 var createLink = this.makeFromTemplate('create-supervised-user-link',
760 createLink.hidden = true;
761 links.appendChild(createLink);
763 var status = this.makeFromTemplate('status-container', 'status');
764 buttons.appendChild(status);
766 linkElement = createLink.querySelector('.signin-link');
767 linkElement.addEventListener('click',
768 this.createLinkPressed_.bind(this));
770 buttons.appendChild(this.makeButton(
772 'managedUserCreationFlow',
773 this.startButtonPressed_.bind(this),
775 ['custom-appearance', 'button-fancy', 'button-blue']));
777 buttons.appendChild(this.makeButton(
779 'managedUserCreationFlow',
780 this.prevButtonPressed_.bind(this),
784 buttons.appendChild(this.makeButton(
786 'managedUserCreationFlow',
787 this.nextButtonPressed_.bind(this),
788 ['manager', 'username'],
791 buttons.appendChild(this.makeButton(
793 'managedUserCreationFlow',
794 this.importButtonPressed_.bind(this),
795 ['import', 'import-password'],
798 buttons.appendChild(this.makeButton(
800 'managedUserCreationFlow',
801 this.gotItButtonPressed_.bind(this),
803 ['custom-appearance', 'button-fancy', 'button-blue']));
804 return [links, buttons];
808 * Does sanity check and calls backend with current user name/password pair
809 * to authenticate manager. May result in showManagerPasswordError.
812 validateAndLogInAsManager_: function() {
813 var selectedPod = this.managerList_.selectedPod_;
814 if (null == selectedPod)
817 var managerId = selectedPod.user.username;
818 var managerDisplayId = selectedPod.user.emailAddress;
819 var managerPassword = selectedPod.passwordElement.value;
820 if (managerPassword.length == 0)
824 this.disabled = true;
825 this.context_.managerId = managerId;
826 this.context_.managerDisplayId = managerDisplayId;
827 this.context_.managerName = selectedPod.user.displayName;
828 chrome.send('authenticateManagerInLocallyManagedUserCreationFlow',
829 [managerId, managerPassword]);
833 * Does sanity check and calls backend with user display name/password pair
837 validateAndCreateLocallyManagedUser_: function() {
838 var firstPassword = $('managed-user-creation-password').value;
840 $('managed-user-creation-password-confirm').value;
841 var userName = $('managed-user-creation-name').value;
842 if (firstPassword != secondPassword) {
843 this.showPasswordError(
844 loadTimeData.getString('createManagedUserPasswordMismatchError'));
849 this.disabled = true;
851 this.context_.managedName = userName;
852 chrome.send('specifyLocallyManagedUserCreationFlowUserData',
853 [userName, firstPassword]);
857 * Does sanity check and calls backend with selected existing supervised
858 * user id to import user.
861 importSupervisedUser_: function() {
864 if (this.currentPage_ == 'import-password') {
865 var firstPassword = this.getScreenElement('password').value;
866 var secondPassword = this.getScreenElement('password-confirm').value;
867 if (firstPassword != secondPassword) {
868 this.showPasswordError(
869 loadTimeData.getString('createManagedUserPasswordMismatchError'));
872 var userId = this.context_.importUserId;
873 this.disabled = true;
874 chrome.send('importSupervisedUserWithPassword',
875 [userId, firstPassword]);
878 var selectedPod = this.importList_.selectedPod_;
881 var user = selectedPod.user;
882 var userId = user.id;
884 this.context_.importUserId = userId;
885 this.context_.managedName = user.name;
886 this.context_.selectedImageUrl = user.avatarurl;
887 if (!user.needPassword) {
888 this.disabled = true;
889 chrome.send('importSupervisedUser', [userId]);
891 this.setVisiblePage_('import-password');
897 * Calls backend part to check if current user name is valid/not taken.
898 * Results in call to either managedUserNameOk or managedUserNameError.
901 checkUserName_: function() {
902 var userName = this.getScreenElement('name').value;
905 if (userName == this.lastIncorrectUserName_ ||
906 userName == this.lastVerifiedName_) {
909 if (userName.length > 0) {
910 chrome.send('checkLocallyManagedUserName', [userName]);
912 this.nameErrorVisible = false;
913 this.lastVerifiedName_ = null;
914 this.lastIncorrectUserName_ = null;
915 this.updateNextButtonForUser_();
920 * Called by backend part in case of successful name validation.
921 * @param {string} name - name that was validated.
923 managedUserNameOk: function(name) {
924 this.lastVerifiedName_ = name;
925 this.lastIncorrectUserName_ = null;
926 if ($('managed-user-creation-name').value == name)
927 this.clearUserNameError_();
928 this.updateNextButtonForUser_();
932 * Called by backend part in case of name validation failure.
933 * @param {string} name - name that was validated.
934 * @param {string} errorText - reason why this name is invalid.
936 managedUserNameError: function(name, errorText) {
937 this.disabled = false;
938 this.lastIncorrectUserName_ = name;
939 this.lastVerifiedName_ = null;
941 var userNameField = $('managed-user-creation-name');
942 if (userNameField.value == this.lastIncorrectUserName_) {
943 this.nameErrorVisible = true;
944 $('bubble').showTextForElement(
945 $('managed-user-creation-name'),
947 cr.ui.Bubble.Attachment.RIGHT,
949 this.setButtonDisabledStatus('next', true);
953 managedUserSuggestImport: function(name, user_id) {
954 this.disabled = false;
955 this.lastIncorrectUserName_ = name;
956 this.lastVerifiedName_ = null;
958 var userNameField = $('managed-user-creation-name');
959 var creationScreen = this;
961 if (userNameField.value == this.lastIncorrectUserName_) {
962 this.nameErrorVisible = true;
963 var link = this.ownerDocument.createElement('div');
964 link.innerHTML = loadTimeData.getStringF(
966 '<a class="signin-link" href="#">',
969 link.querySelector('.signin-link').addEventListener('click',
971 creationScreen.handleSuggestImport_(user_id);
974 $('bubble').showContentForElement(
975 $('managed-user-creation-name'),
976 cr.ui.Bubble.Attachment.RIGHT,
979 this.setButtonDisabledStatus('next', true);
984 * Clears user name error, if name is no more guaranteed to be invalid.
987 clearUserNameError_: function() {
989 if ($('managed-user-creation-name').value ==
990 this.lastIncorrectUserName_) {
993 this.nameErrorVisible = false;
997 * Called by backend part in case of password validation failure.
998 * @param {string} errorText - reason why this password is invalid.
1000 showPasswordError: function(errorText) {
1001 $('bubble').showTextForElement(
1002 $('managed-user-creation-password'),
1004 cr.ui.Bubble.Attachment.RIGHT,
1006 $('managed-user-creation-password').classList.add('password-error');
1007 $('managed-user-creation-password').focus();
1008 this.disabled = false;
1009 this.setButtonDisabledStatus('next', true);
1013 * True if user name error should be displayed.
1016 set nameErrorVisible(value) {
1017 $('managed-user-creation-name').
1018 classList.toggle('duplicate-name', value);
1024 * Updates state of Continue button after minimal checks.
1025 * @return {boolean} true, if form seems to be valid.
1028 updateNextButtonForManager_: function() {
1029 var selectedPod = this.managerList_.selectedPod_;
1030 canProceed = null != selectedPod &&
1031 selectedPod.passwordElement.value.length > 0;
1033 this.setButtonDisabledStatus('next', !canProceed);
1038 * Updates state of Continue button after minimal checks.
1039 * @return {boolean} true, if form seems to be valid.
1042 updateNextButtonForUser_: function() {
1043 var firstPassword = this.getScreenElement('password').value;
1044 var secondPassword = this.getScreenElement('password-confirm').value;
1045 var userName = this.getScreenElement('name').value;
1047 var passwordOk = (firstPassword.length > 0) &&
1048 (firstPassword.length == secondPassword.length);
1050 if (this.currentPage_ == 'import-password') {
1051 this.setButtonDisabledStatus('import', !passwordOk);
1054 var imageGrid = this.getScreenElement('image-grid');
1055 var imageChosen = !(imageGrid.selectionType == 'camera' &&
1056 imageGrid.cameraLive);
1059 (userName.length > 0) &&
1060 this.lastVerifiedName_ &&
1061 (userName == this.lastVerifiedName_) &&
1064 this.setButtonDisabledStatus('next', !canProceed);
1068 showSelectedManagerPasswordError_: function() {
1069 var selectedPod = this.managerList_.selectedPod_;
1070 selectedPod.showPasswordError();
1071 selectedPod.passwordElement.value = '';
1072 selectedPod.focusInput();
1073 this.updateNextButtonForManager_();
1077 * Enables one particular subpage and hides the rest.
1078 * @param {string} visiblePage - name of subpage.
1081 setVisiblePage_: function(visiblePage) {
1082 this.disabled = false;
1085 if (!this.imagesRequested_) {
1086 chrome.send('supervisedUserGetImages');
1087 this.imagesRequested_ = true;
1089 var pageNames = ['intro',
1095 var pageButtons = {'intro' : 'start',
1097 'import' : 'import',
1098 'import-password' : 'import',
1099 'created' : 'gotit'};
1101 var pageToDisplay = visiblePage;
1102 if (visiblePage == 'import-password')
1103 pageToDisplay = 'username';
1105 for (i in pageNames) {
1106 var pageName = pageNames[i];
1107 var page = $('managed-user-creation-' + pageName);
1108 page.hidden = (pageName != pageToDisplay);
1109 if (pageName == pageToDisplay)
1110 $('step-logo').hidden = page.classList.contains('step-no-logo');
1113 for (i in this.buttonIds) {
1114 var button = this.getScreenButton(this.buttonIds[i]);
1115 button.hidden = button.pages.indexOf(visiblePage) < 0;
1116 button.disabled = false;
1119 var pagesWithCancel = ['intro', 'manager', 'username', 'import-password',
1121 $('login-header-bar').allowCancel =
1122 pagesWithCancel.indexOf(visiblePage) > 0;
1123 $('cancel-add-user-button').disabled = false;
1125 this.getScreenElement('import-link').hidden = true;
1126 this.getScreenElement('create-link').hidden = true;
1128 if (pageButtons[visiblePage])
1129 this.getScreenButton(pageButtons[visiblePage]).focus();
1131 this.currentPage_ = visiblePage;
1133 if (visiblePage == 'manager' || visiblePage == 'intro') {
1134 $('managed-user-creation-password').classList.remove('password-error');
1135 if (this.managerList_.pods.length > 0)
1136 this.managerList_.selectPod(this.managerList_.pods[0]);
1139 if (visiblePage == 'username' || visiblePage == 'import-password') {
1140 var elements = this.getScreenElement(pageToDisplay).
1141 querySelectorAll('.hide-on-import');
1142 for (var i = 0; i < elements.length; i++) {
1143 elements[i].classList.toggle('hidden-on-import',
1144 visiblePage == 'import-password');
1147 if (visiblePage == 'username') {
1148 var imageGrid = this.getScreenElement('image-grid');
1149 // select some image.
1150 var selected = this.imagesData_[
1151 Math.floor(Math.random() * this.imagesData_.length)];
1152 this.context_.selectedImageUrl = selected.url;
1153 imageGrid.selectedItemUrl = selected.url;
1154 chrome.send('supervisedUserSelectImage',
1155 [selected.url, 'default']);
1156 this.getScreenElement('image-grid').redraw();
1157 this.checkUserName_();
1158 this.updateNextButtonForUser_();
1159 this.getScreenElement('name').focus();
1160 this.getScreenElement('import-link').hidden =
1161 this.importList_.pods.length == 0;
1162 } else if (visiblePage == 'import-password') {
1163 var imageGrid = this.getScreenElement('image-grid');
1165 if ('selectedImageUrl' in this.context_) {
1166 selected = this.context_.selectedImageUrl;
1168 // select some image.
1169 selected = this.imagesData_[
1170 Math.floor(Math.random() * this.imagesData_.length)].url;
1171 chrome.send('supervisedUserSelectImage',
1172 [selected, 'default']);
1174 imageGrid.selectedItemUrl = selected;
1175 this.getScreenElement('image-grid').redraw();
1177 this.updateNextButtonForUser_();
1179 this.getScreenElement('password').focus();
1180 this.getScreenElement('import-link').hidden = true;
1182 this.getScreenElement('image-grid').stopCamera();
1184 if (visiblePage == 'import') {
1185 this.getScreenElement('create-link').hidden = false;
1186 this.getScreenButton('import').disabled =
1187 !this.importList_.selectedPod_ ||
1188 this.importList_.selectedPod_.user.exists;
1190 chrome.send('currentSupervisedUserPage', [this.currentPage_]);
1193 setButtonDisabledStatus: function(buttonName, status) {
1194 var button = $('managed-user-creation-' + buttonName + '-button');
1195 button.disabled = status;
1198 gotItButtonPressed_: function() {
1199 chrome.send('finishLocalManagedUserCreation');
1202 handleErrorButtonPressed_: function() {
1203 chrome.send('abortLocalManagedUserCreation');
1206 startButtonPressed_: function() {
1207 this.setVisiblePage_('manager');
1208 this.setButtonDisabledStatus('next', true);
1211 nextButtonPressed_: function() {
1212 if (this.currentPage_ == 'manager') {
1213 this.validateAndLogInAsManager_();
1216 if (this.currentPage_ == 'username') {
1217 this.validateAndCreateLocallyManagedUser_();
1221 importButtonPressed_: function() {
1222 this.importSupervisedUser_();
1225 importLinkPressed_: function() {
1226 this.setVisiblePage_('import');
1229 handleSuggestImport_: function(user_id) {
1230 this.setVisiblePage_('import');
1231 this.importList_.selectUser(user_id);
1234 createLinkPressed_: function() {
1235 this.setVisiblePage_('username');
1236 this.lastIncorrectUserName_ = null;
1237 this.lastVerifiedName_ = null;
1238 this.checkUserName_();
1241 prevButtonPressed_: function() {
1242 this.setVisiblePage_('intro');
1245 showProgress: function(text) {
1246 var status = this.getScreenElement('status');
1247 var statusText = status.querySelector('.id-text');
1248 statusText.textContent = text;
1249 statusText.classList.remove('error');
1250 status.querySelector('.id-spinner').hidden = false;
1251 status.hidden = false;
1252 this.getScreenElement('import-link').hidden = true;
1253 this.getScreenElement('create-link').hidden = true;
1256 showStatusError: function(text) {
1257 var status = this.getScreenElement('status');
1258 var statusText = status.querySelector('.id-text');
1259 statusText.textContent = text;
1260 statusText.classList.add('error');
1261 status.querySelector('.id-spinner').hidden = true;
1262 status.hidden = false;
1263 this.getScreenElement('import-link').hidden = true;
1264 this.getScreenElement('create-link').hidden = true;
1267 hideStatus_: function() {
1268 var status = this.getScreenElement('status');
1269 status.hidden = true;
1273 * Updates state of login header so that necessary buttons are displayed.
1275 onBeforeShow: function(data) {
1276 $('login-header-bar').signinUIState =
1277 SIGNIN_UI_STATE.MANAGED_USER_CREATION_FLOW;
1278 if (data['managers']) {
1279 this.loadManagers(data['managers']);
1281 var imageGrid = this.getScreenElement('image-grid');
1282 imageGrid.updateAndFocus();
1286 * Update state of login header so that necessary buttons are displayed.
1288 onBeforeHide: function() {
1289 $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
1290 this.getScreenElement('image-grid').stopCamera();
1294 * Returns a control which should receive an initial focus.
1296 get defaultControl() {
1297 return $('managed-user-creation-name');
1301 * True if the the screen is disabled (handles no user interaction).
1307 return this.disabled_;
1310 set disabled(value) {
1311 this.disabled_ = value;
1312 var controls = this.querySelectorAll('button,input');
1313 for (var i = 0, control; control = controls[i]; ++i) {
1314 control.disabled = value;
1316 $('login-header-bar').disabled = value;
1317 $('cancel-add-user-button').disabled = false;
1321 * Called by backend part to propagate list of possible managers.
1322 * @param {Array} userList - list of users that can be managers.
1324 loadManagers: function(userList) {
1325 $('managed-user-creation-managers-block').hidden = false;
1326 this.managerList_.clearPods();
1327 for (var i = 0; i < userList.length; ++i)
1328 this.managerList_.addPod(userList[i]);
1329 if (userList.length > 0)
1330 this.managerList_.selectPod(this.managerList_.pods[0]);
1334 * Cancels user creation and drops to user screen (either sign).
1336 cancel: function() {
1337 var notSignedInPages = ['intro', 'manager'];
1338 var postCreationPages = ['created'];
1339 if (notSignedInPages.indexOf(this.currentPage_) >= 0) {
1340 // Make sure no manager password is kept:
1341 this.managerList_.clearPods();
1343 $('pod-row').loadLastWallpaper();
1345 Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
1346 Oobe.resetSigninUI(true);
1349 if (postCreationPages.indexOf(this.currentPage_) >= 0) {
1350 chrome.send('finishLocalManagedUserCreation');
1353 chrome.send('abortLocalManagedUserCreation');
1356 updateText_: function() {
1357 var managerDisplayId = this.context_.managerDisplayId;
1358 this.updateElementText_('intro-alternate-text',
1359 'createManagedUserIntroAlternateText');
1360 this.updateElementText_('created-text-1',
1361 'createManagedUserCreatedText1',
1362 this.context_.managedName);
1363 // TODO(antrim): Move wrapping with strong in grd file, and eliminate this
1365 this.updateElementText_('created-text-2',
1366 'createManagedUserCreatedText2',
1368 loadTimeData.getString('managementURL')),
1369 this.context_.managedName);
1370 this.updateElementText_('created-text-3',
1371 'createManagedUserCreatedText3',
1373 this.updateElementText_('name-explanation',
1374 'createManagedUserNameExplanation',
1378 wrapStrong: function(original) {
1379 if (original == undefined)
1381 return '<strong>' + original + '</strong>';
1384 updateElementText_: function(localId, templateName) {
1385 var args = Array.prototype.slice.call(arguments);
1387 this.getScreenElement(localId).innerHTML =
1388 loadTimeData.getStringF.apply(loadTimeData, args);
1391 showIntroPage: function() {
1392 $('managed-user-creation-password').value = '';
1393 $('managed-user-creation-password-confirm').value = '';
1394 $('managed-user-creation-name').value = '';
1396 this.lastVerifiedName_ = null;
1397 this.lastIncorrectUserName_ = null;
1398 this.passwordErrorVisible = false;
1399 $('managed-user-creation-password').classList.remove('password-error');
1400 this.nameErrorVisible = false;
1402 this.setVisiblePage_('intro');
1405 showManagerPage: function() {
1406 this.setVisiblePage_('manager');
1409 showUsernamePage: function() {
1410 this.setVisiblePage_('username');
1413 showTutorialPage: function() {
1414 this.setVisiblePage_('created');
1417 showPage: function(page) {
1418 this.setVisiblePage_(page);
1421 showErrorPage: function(errorTitle, errorText, errorButtonText) {
1422 this.disabled = false;
1423 $('managed-user-creation-error-title').innerHTML = errorTitle;
1424 $('managed-user-creation-error-text').innerHTML = errorText;
1425 $('managed-user-creation-error-button').textContent = errorButtonText;
1426 this.setVisiblePage_('error');
1429 showManagerPasswordError: function() {
1430 this.disabled = false;
1431 this.showSelectedManagerPasswordError_();
1435 TODO(antrim) : this is an explicit code duplications with UserImageScreen.
1436 It should be removed by issue 251179.
1439 * Currently selected user image index (take photo button is with zero
1443 selectedUserImage_: -1,
1446 setDefaultImages: function(imagesData) {
1447 var imageGrid = this.getScreenElement('image-grid');
1448 for (var i = 0, data; data = imagesData[i]; i++) {
1449 var item = imageGrid.addItem(data.url, data.title);
1450 item.type = 'default';
1451 item.author = data.author || '';
1452 item.website = data.website || '';
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 $('managed-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 $('managed-user-creation-image-preview-img').classList.toggle(
1498 'animated-transform', true);
1499 return imageGrid.selectedItem == imageGrid.cameraImage;
1502 $('managed-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 setExistingManagedUsers: 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);