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');
620 $('supervised-user-creation-close-button-item').addEventListener(
621 'click', function(e) {
630 * Creates button for adding to controls.
631 * @param {string} buttonId -- id for button, have to be unique within
632 * screen. Actual id will be prefixed with screen name and appended with
633 * '-button'. Use getScreenButton(buttonId) to find it later.
634 * @param {string} i18nPrefix -- screen prefix for i18n values.
635 * @param {function} callback -- will be called on button press with
636 * buttonId parameter.
637 * @param {array} pages -- list of pages where this button should be
639 * @param {array} classes -- list of additional CSS classes for button.
641 makeButton: function(buttonId, i18nPrefix, callback, pages, classes) {
642 var capitalizedId = buttonId.charAt(0).toUpperCase() + buttonId.slice(1);
643 this.buttonIds.push(buttonId);
644 var result = this.ownerDocument.createElement('button');
645 result.id = this.name() + '-' + buttonId + '-button';
646 result.classList.add('screen-control-button');
647 for (var i = 0; i < classes.length; i++) {
648 result.classList.add(classes[i]);
650 result.textContent = loadTimeData.
651 getString(i18nPrefix + capitalizedId + 'ButtonTitle');
652 result.addEventListener('click', function(e) {
656 result.pages = pages;
661 * Simple validator for |configureTextInput|.
662 * Element is considered valid if it has any text.
663 * @param {Element} element - element to be validated.
664 * @return {boolean} - true, if element has any text.
666 validIfNotEmpty_: function(element) {
667 return (element.value.length > 0);
671 * Configure text-input |element|.
672 * @param {Element} element - element to be configured.
673 * @param {function(element)} inputChangeListener - function that will be
674 * called upon any button press/release.
675 * @param {function(element)} validator - function that will be called when
676 * Enter is pressed. If it returns |true| then advance to next element.
677 * @param {function(element)} moveFocus - function that will determine next
678 * element and move focus to it.
679 * @param {function(element)} errorHider - function that is called upon
680 * every button press, so that any associated error can be hidden.
682 configureTextInput: function(element,
687 element.addEventListener('keydown', function(e) {
688 if (e.keyIdentifier == 'Enter') {
689 var dataValid = true;
691 dataValid = validator(element);
703 if (inputChangeListener)
704 inputChangeListener(element);
706 element.addEventListener('keyup', function(e) {
707 if (inputChangeListener)
708 inputChangeListener(element);
713 * Makes element from template.
714 * @param {string} templateId -- template will be looked up within screen
715 * by class with name "template-<templateId>".
716 * @param {string} elementId -- id for result, uinque within screen. Actual
717 * id will be prefixed with screen name. Use getScreenElement(id) to find
720 makeFromTemplate: function(templateId, elementId) {
721 var templateClassName = 'template-' + templateId;
722 var templateNode = this.querySelector('.' + templateClassName);
723 var screenPrefix = this.name() + '-';
724 var result = templateNode.cloneNode(true);
725 result.classList.remove(templateClassName);
726 result.id = screenPrefix + elementId;
731 * @param {string} buttonId -- id of button to be found,
732 * @return {Element} button created by makeButton with given buttonId.
734 getScreenButton: function(buttonId) {
735 var fullId = this.name() + '-' + buttonId + '-button';
736 return this.getScreenElement(buttonId + '-button');
740 * @param {string} elementId -- id of element to be found,
741 * @return {Element} button created by makeFromTemplate with elementId.
743 getScreenElement: function(elementId) {
744 var fullId = this.name() + '-' + elementId;
750 * @type {!Array} Array of Buttons.
753 var links = this.ownerDocument.createElement('div');
754 var buttons = this.ownerDocument.createElement('div');
755 links.classList.add('controls-links');
756 buttons.classList.add('controls-buttons');
758 var importLink = this.makeFromTemplate('import-supervised-user-link',
760 importLink.hidden = true;
761 links.appendChild(importLink);
763 var linkElement = importLink.querySelector('.signin-link');
764 linkElement.addEventListener('click',
765 this.importLinkPressed_.bind(this));
767 var createLink = this.makeFromTemplate('create-supervised-user-link',
769 createLink.hidden = true;
770 links.appendChild(createLink);
772 var status = this.makeFromTemplate('status-container', 'status');
773 buttons.appendChild(status);
775 linkElement = createLink.querySelector('.signin-link');
776 linkElement.addEventListener('click',
777 this.createLinkPressed_.bind(this));
779 buttons.appendChild(this.makeButton(
781 'supervisedUserCreationFlow',
782 this.startButtonPressed_.bind(this),
784 ['custom-appearance', 'button-fancy', 'button-blue']));
786 buttons.appendChild(this.makeButton(
788 'supervisedUserCreationFlow',
789 this.prevButtonPressed_.bind(this),
793 buttons.appendChild(this.makeButton(
795 'supervisedUserCreationFlow',
796 this.nextButtonPressed_.bind(this),
797 ['manager', 'username'],
800 buttons.appendChild(this.makeButton(
802 'supervisedUserCreationFlow',
803 this.importButtonPressed_.bind(this),
804 ['import', 'import-password'],
807 buttons.appendChild(this.makeButton(
809 'supervisedUserCreationFlow',
810 this.gotItButtonPressed_.bind(this),
812 ['custom-appearance', 'button-fancy', 'button-blue']));
813 return [links, buttons];
817 * Does sanity check and calls backend with current user name/password pair
818 * to authenticate manager. May result in showManagerPasswordError.
821 validateAndLogInAsManager_: function() {
822 var selectedPod = this.managerList_.selectedPod_;
823 if (null == selectedPod)
826 var managerId = selectedPod.user.username;
827 var managerDisplayId = selectedPod.user.emailAddress;
828 var managerPassword = selectedPod.passwordElement.value;
829 if (managerPassword.length == 0)
833 this.disabled = true;
834 this.context_.managerId = managerId;
835 this.context_.managerDisplayId = managerDisplayId;
836 this.context_.managerName = selectedPod.user.displayName;
837 chrome.send('authenticateManagerInSupervisedUserCreationFlow',
838 [managerId, managerPassword]);
842 * Does sanity check and calls backend with user display name/password pair
846 validateAndCreateSupervisedUser_: function() {
847 var firstPassword = $('supervised-user-creation-password').value;
849 $('supervised-user-creation-password-confirm').value;
850 var userName = $('supervised-user-creation-name').value;
851 if (firstPassword != secondPassword) {
852 this.showPasswordError(loadTimeData.getString(
853 'createSupervisedUserPasswordMismatchError'));
858 this.disabled = true;
860 this.context_.supervisedName = userName;
861 chrome.send('specifySupervisedUserCreationFlowUserData',
862 [userName, firstPassword]);
866 * Does sanity check and calls backend with selected existing supervised
867 * user id to import user.
870 importSupervisedUser_: function() {
873 if (this.currentPage_ == 'import-password') {
874 var firstPassword = this.getScreenElement('password').value;
875 var secondPassword = this.getScreenElement('password-confirm').value;
876 if (firstPassword != secondPassword) {
877 this.showPasswordError(loadTimeData.getString(
878 'createSupervisedUserPasswordMismatchError'));
881 var userId = this.context_.importUserId;
882 this.disabled = true;
883 chrome.send('importSupervisedUserWithPassword',
884 [userId, firstPassword]);
887 var selectedPod = this.importList_.selectedPod_;
890 var user = selectedPod.user;
891 var userId = user.id;
893 this.context_.importUserId = userId;
894 this.context_.supervisedName = user.name;
895 this.context_.selectedImageUrl = user.avatarurl;
896 if (!user.needPassword) {
897 this.disabled = true;
898 chrome.send('importSupervisedUser', [userId]);
900 this.setVisiblePage_('import-password');
906 * Calls backend part to check if current user name is valid/not taken.
907 * Results in a call to either supervisedUserNameOk or
908 * supervisedUserNameError.
911 checkUserName_: function() {
912 var userName = this.getScreenElement('name').value;
915 if (userName == this.lastIncorrectUserName_ ||
916 userName == this.lastVerifiedName_) {
919 if (userName.length > 0) {
920 chrome.send('checkSupervisedUserName', [userName]);
922 this.nameErrorVisible = false;
923 this.lastVerifiedName_ = null;
924 this.lastIncorrectUserName_ = null;
925 this.updateNextButtonForUser_();
930 * Called by backend part in case of successful name validation.
931 * @param {string} name - name that was validated.
933 supervisedUserNameOk: function(name) {
934 this.lastVerifiedName_ = name;
935 this.lastIncorrectUserName_ = null;
936 if ($('supervised-user-creation-name').value == name)
937 this.clearUserNameError_();
938 this.updateNextButtonForUser_();
942 * Called by backend part in case of name validation failure.
943 * @param {string} name - name that was validated.
944 * @param {string} errorText - reason why this name is invalid.
946 supervisedUserNameError: function(name, errorText) {
947 this.disabled = false;
948 this.lastIncorrectUserName_ = name;
949 this.lastVerifiedName_ = null;
951 var userNameField = $('supervised-user-creation-name');
952 if (userNameField.value == this.lastIncorrectUserName_) {
953 this.nameErrorVisible = true;
954 $('bubble').showTextForElement(
955 $('supervised-user-creation-name'),
957 cr.ui.Bubble.Attachment.RIGHT,
959 this.setButtonDisabledStatus('next', true);
963 supervisedUserSuggestImport: function(name, user_id) {
964 this.disabled = false;
965 this.lastIncorrectUserName_ = name;
966 this.lastVerifiedName_ = null;
968 var userNameField = $('supervised-user-creation-name');
969 var creationScreen = this;
971 if (userNameField.value == this.lastIncorrectUserName_) {
972 this.nameErrorVisible = true;
973 var link = this.ownerDocument.createElement('div');
974 link.innerHTML = loadTimeData.getStringF(
976 '<a class="signin-link" href="#">',
979 link.querySelector('.signin-link').addEventListener('click',
981 creationScreen.handleSuggestImport_(user_id);
984 $('bubble').showContentForElement(
985 $('supervised-user-creation-name'),
986 cr.ui.Bubble.Attachment.RIGHT,
989 this.setButtonDisabledStatus('next', true);
994 * Clears user name error, if name is no more guaranteed to be invalid.
997 clearUserNameError_: function() {
999 if ($('supervised-user-creation-name').value ==
1000 this.lastIncorrectUserName_) {
1003 this.nameErrorVisible = false;
1007 * Called by backend part in case of password validation failure.
1008 * @param {string} errorText - reason why this password is invalid.
1010 showPasswordError: function(errorText) {
1011 $('bubble').showTextForElement(
1012 $('supervised-user-creation-password'),
1014 cr.ui.Bubble.Attachment.RIGHT,
1016 $('supervised-user-creation-password').classList.add('password-error');
1017 $('supervised-user-creation-password').focus();
1018 this.disabled = false;
1019 this.setButtonDisabledStatus('next', true);
1023 * True if user name error should be displayed.
1026 set nameErrorVisible(value) {
1027 $('supervised-user-creation-name').
1028 classList.toggle('duplicate-name', value);
1034 * Updates state of Continue button after minimal checks.
1035 * @return {boolean} true, if form seems to be valid.
1038 updateNextButtonForManager_: function() {
1039 var selectedPod = this.managerList_.selectedPod_;
1040 canProceed = null != selectedPod &&
1041 selectedPod.passwordElement.value.length > 0;
1043 this.setButtonDisabledStatus('next', !canProceed);
1048 * Updates state of Continue button after minimal checks.
1049 * @return {boolean} true, if form seems to be valid.
1052 updateNextButtonForUser_: function() {
1053 var firstPassword = this.getScreenElement('password').value;
1054 var secondPassword = this.getScreenElement('password-confirm').value;
1055 var userName = this.getScreenElement('name').value;
1057 var passwordOk = (firstPassword.length > 0) &&
1058 (firstPassword.length == secondPassword.length);
1060 if (this.currentPage_ == 'import-password') {
1061 this.setButtonDisabledStatus('import', !passwordOk);
1064 var imageGrid = this.getScreenElement('image-grid');
1065 var imageChosen = !(imageGrid.selectionType == 'camera' &&
1066 imageGrid.cameraLive);
1069 (userName.length > 0) &&
1070 this.lastVerifiedName_ &&
1071 (userName == this.lastVerifiedName_) &&
1074 this.setButtonDisabledStatus('next', !canProceed);
1078 showSelectedManagerPasswordError_: function() {
1079 var selectedPod = this.managerList_.selectedPod_;
1080 selectedPod.showPasswordError();
1081 selectedPod.passwordElement.value = '';
1082 selectedPod.focusInput();
1083 this.updateNextButtonForManager_();
1087 * Enables one particular subpage and hides the rest.
1088 * @param {string} visiblePage - name of subpage.
1091 setVisiblePage_: function(visiblePage) {
1092 this.disabled = false;
1095 if (!this.imagesRequested_) {
1096 chrome.send('supervisedUserGetImages');
1097 this.imagesRequested_ = true;
1099 var pageNames = ['intro',
1105 var pageButtons = {'intro' : 'start',
1107 'import' : 'import',
1108 'import-password' : 'import',
1109 'created' : 'gotit'};
1111 var pageToDisplay = visiblePage;
1112 if (visiblePage == 'import-password')
1113 pageToDisplay = 'username';
1115 for (i in pageNames) {
1116 var pageName = pageNames[i];
1117 var page = $('supervised-user-creation-' + pageName);
1118 page.hidden = (pageName != pageToDisplay);
1119 if (pageName == pageToDisplay)
1120 $('step-logo').hidden = page.classList.contains('step-no-logo');
1123 for (i in this.buttonIds) {
1124 var button = this.getScreenButton(this.buttonIds[i]);
1125 button.hidden = button.pages.indexOf(visiblePage) < 0;
1126 button.disabled = false;
1129 var pagesWithCancel = ['intro', 'manager', 'username', 'import-password',
1131 $('login-header-bar').allowCancel =
1132 pagesWithCancel.indexOf(visiblePage) > -1;
1133 $('cancel-add-user-button').disabled = false;
1135 this.getScreenElement('import-link').hidden = true;
1136 this.getScreenElement('create-link').hidden = true;
1138 if (pageButtons[visiblePage])
1139 this.getScreenButton(pageButtons[visiblePage]).focus();
1141 this.currentPage_ = visiblePage;
1143 if (visiblePage == 'manager' || visiblePage == 'intro') {
1144 $('supervised-user-creation-password').classList.remove(
1146 if (this.managerList_.pods.length > 0)
1147 this.managerList_.selectPod(this.managerList_.pods[0]);
1148 $('login-header-bar').updateUI_();
1151 if (visiblePage == 'username' || visiblePage == 'import-password') {
1152 var elements = this.getScreenElement(pageToDisplay).
1153 querySelectorAll('.hide-on-import');
1154 for (var i = 0; i < elements.length; i++) {
1155 elements[i].classList.toggle('hidden-on-import',
1156 visiblePage == 'import-password');
1159 if (visiblePage == 'username') {
1160 var imageGrid = this.getScreenElement('image-grid');
1161 // select some image.
1162 var selected = this.imagesData_[
1163 Math.floor(Math.random() * this.imagesData_.length)];
1164 this.context_.selectedImageUrl = selected.url;
1165 imageGrid.selectedItemUrl = selected.url;
1166 chrome.send('supervisedUserSelectImage',
1167 [selected.url, 'default']);
1168 this.getScreenElement('image-grid').redraw();
1169 this.checkUserName_();
1170 this.updateNextButtonForUser_();
1171 this.getScreenElement('name').focus();
1172 this.getScreenElement('import-link').hidden =
1173 this.importList_.pods.length == 0;
1174 } else if (visiblePage == 'import-password') {
1175 var imageGrid = this.getScreenElement('image-grid');
1177 if ('selectedImageUrl' in this.context_) {
1178 selected = this.context_.selectedImageUrl;
1180 // select some image.
1181 selected = this.imagesData_[
1182 Math.floor(Math.random() * this.imagesData_.length)].url;
1183 chrome.send('supervisedUserSelectImage',
1184 [selected, 'default']);
1186 imageGrid.selectedItemUrl = selected;
1187 this.getScreenElement('image-grid').redraw();
1189 this.updateNextButtonForUser_();
1191 this.getScreenElement('password').focus();
1192 this.getScreenElement('import-link').hidden = true;
1194 this.getScreenElement('image-grid').stopCamera();
1196 if (visiblePage == 'import') {
1197 this.getScreenElement('create-link').hidden = false;
1198 this.getScreenButton('import').disabled =
1199 !this.importList_.selectedPod_ ||
1200 this.importList_.selectedPod_.user.exists;
1202 $('supervised-user-creation-close-button-item').hidden =
1203 (visiblePage == 'created');
1205 chrome.send('currentSupervisedUserPage', [this.currentPage_]);
1208 setButtonDisabledStatus: function(buttonName, status) {
1209 var button = $('supervised-user-creation-' + buttonName + '-button');
1210 button.disabled = status;
1213 gotItButtonPressed_: function() {
1214 chrome.send('finishLocalSupervisedUserCreation');
1217 handleErrorButtonPressed_: function() {
1218 chrome.send('abortLocalSupervisedUserCreation');
1221 startButtonPressed_: function() {
1222 this.setVisiblePage_('manager');
1223 this.setButtonDisabledStatus('next', true);
1226 nextButtonPressed_: function() {
1227 if (this.currentPage_ == 'manager') {
1228 this.validateAndLogInAsManager_();
1231 if (this.currentPage_ == 'username') {
1232 this.validateAndCreateSupervisedUser_();
1236 importButtonPressed_: function() {
1237 this.importSupervisedUser_();
1240 importLinkPressed_: function() {
1241 this.setVisiblePage_('import');
1244 handleSuggestImport_: function(user_id) {
1245 this.setVisiblePage_('import');
1246 this.importList_.selectUser(user_id);
1249 createLinkPressed_: function() {
1250 this.setVisiblePage_('username');
1251 this.lastIncorrectUserName_ = null;
1252 this.lastVerifiedName_ = null;
1253 this.checkUserName_();
1256 prevButtonPressed_: function() {
1257 this.setVisiblePage_('intro');
1260 showProgress: function(text) {
1261 var status = this.getScreenElement('status');
1262 var statusText = status.querySelector('.id-text');
1263 statusText.textContent = text;
1264 statusText.classList.remove('error');
1265 status.querySelector('.id-spinner').hidden = false;
1266 status.hidden = false;
1267 this.getScreenElement('import-link').hidden = true;
1268 this.getScreenElement('create-link').hidden = true;
1271 showStatusError: function(text) {
1272 var status = this.getScreenElement('status');
1273 var statusText = status.querySelector('.id-text');
1274 statusText.textContent = text;
1275 statusText.classList.add('error');
1276 status.querySelector('.id-spinner').hidden = true;
1277 status.hidden = false;
1278 this.getScreenElement('import-link').hidden = true;
1279 this.getScreenElement('create-link').hidden = true;
1282 hideStatus_: function() {
1283 var status = this.getScreenElement('status');
1284 status.hidden = true;
1288 * Updates state of login header so that necessary buttons are displayed.
1290 onBeforeShow: function(data) {
1291 $('login-header-bar').signinUIState =
1292 SIGNIN_UI_STATE.SUPERVISED_USER_CREATION_FLOW;
1293 if (data['managers']) {
1294 this.loadManagers(data['managers']);
1296 var imageGrid = this.getScreenElement('image-grid');
1297 imageGrid.updateAndFocus();
1301 * Update state of login header so that necessary buttons are displayed.
1303 onBeforeHide: function() {
1304 $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
1305 this.getScreenElement('image-grid').stopCamera();
1309 * Returns a control which should receive an initial focus.
1311 get defaultControl() {
1312 return $('supervised-user-creation-name');
1316 * True if the the screen is disabled (handles no user interaction).
1322 return this.disabled_;
1325 set disabled(value) {
1326 this.disabled_ = value;
1327 var controls = this.querySelectorAll('button,input');
1328 for (var i = 0, control; control = controls[i]; ++i) {
1329 control.disabled = value;
1331 $('login-header-bar').disabled = value;
1332 $('cancel-add-user-button').disabled = false;
1336 * Called by backend part to propagate list of possible managers.
1337 * @param {Array} userList - list of users that can be managers.
1339 loadManagers: function(userList) {
1340 $('supervised-user-creation-managers-block').hidden = false;
1341 this.managerList_.clearPods();
1342 for (var i = 0; i < userList.length; ++i)
1343 this.managerList_.addPod(userList[i]);
1344 if (userList.length > 0)
1345 this.managerList_.selectPod(this.managerList_.pods[0]);
1349 * Cancels user creation and drops to user screen (either sign).
1351 cancel: function() {
1352 var notSignedInPages = ['intro', 'manager'];
1353 var postCreationPages = ['created'];
1354 if (notSignedInPages.indexOf(this.currentPage_) >= 0) {
1355 chrome.send('hideLocalSupervisedUserCreation');
1357 // Make sure no manager password is kept:
1358 this.managerList_.clearPods();
1360 Oobe.showUserPods();
1363 if (postCreationPages.indexOf(this.currentPage_) >= 0) {
1364 chrome.send('finishLocalSupervisedUserCreation');
1367 chrome.send('abortLocalSupervisedUserCreation');
1370 updateText_: function() {
1371 var managerDisplayId = this.context_.managerDisplayId;
1372 this.updateElementText_('intro-alternate-text',
1373 'createSupervisedUserIntroAlternateText');
1374 this.updateElementText_('created-text-1',
1375 'createSupervisedUserCreatedText1',
1376 this.context_.supervisedName);
1377 // TODO(antrim): Move wrapping with strong in grd file, and eliminate this
1379 this.updateElementText_('created-text-2',
1380 'createSupervisedUserCreatedText2',
1382 loadTimeData.getString('managementURL')),
1383 this.context_.supervisedName);
1384 this.updateElementText_('created-text-3',
1385 'createSupervisedUserCreatedText3',
1387 this.updateElementText_('name-explanation',
1388 'createSupervisedUserNameExplanation',
1392 wrapStrong: function(original) {
1393 if (original == undefined)
1395 return '<strong>' + original + '</strong>';
1398 updateElementText_: function(localId, templateName) {
1399 var args = Array.prototype.slice.call(arguments);
1401 this.getScreenElement(localId).innerHTML =
1402 loadTimeData.getStringF.apply(loadTimeData, args);
1405 showIntroPage: function() {
1406 $('supervised-user-creation-password').value = '';
1407 $('supervised-user-creation-password-confirm').value = '';
1408 $('supervised-user-creation-name').value = '';
1410 this.lastVerifiedName_ = null;
1411 this.lastIncorrectUserName_ = null;
1412 this.passwordErrorVisible = false;
1413 $('supervised-user-creation-password').classList.remove('password-error');
1414 this.nameErrorVisible = false;
1416 this.setVisiblePage_('intro');
1419 showManagerPage: function() {
1420 this.setVisiblePage_('manager');
1423 showUsernamePage: function() {
1424 this.setVisiblePage_('username');
1427 showTutorialPage: function() {
1428 this.setVisiblePage_('created');
1431 showPage: function(page) {
1432 this.setVisiblePage_(page);
1435 showErrorPage: function(errorTitle, errorText, errorButtonText) {
1436 this.disabled = false;
1437 $('supervised-user-creation-error-title').innerHTML = errorTitle;
1438 $('supervised-user-creation-error-text').innerHTML = errorText;
1439 $('supervised-user-creation-error-button').textContent = errorButtonText;
1440 this.setVisiblePage_('error');
1443 showManagerPasswordError: function() {
1444 this.disabled = false;
1445 this.showSelectedManagerPasswordError_();
1449 TODO(antrim) : this is an explicit code duplications with UserImageScreen.
1450 It should be removed by issue 251179.
1453 * Currently selected user image index (take photo button is with zero
1457 selectedUserImage_: -1,
1460 setDefaultImages: function(imagesData) {
1461 var imageGrid = this.getScreenElement('image-grid');
1462 imageGrid.setDefaultImages(imagesData);
1463 this.imagesData_ = imagesData;
1467 handleActivate_: function() {
1468 var imageGrid = this.getScreenElement('image-grid');
1469 if (imageGrid.selectedItemUrl == ButtonImages.TAKE_PHOTO) {
1470 this.handleTakePhoto_();
1473 this.nextButtonPressed_();
1477 * Handles selection change.
1478 * @param {Event} e Selection change event.
1481 handleSelect_: function(e) {
1482 var imageGrid = this.getScreenElement('image-grid');
1483 this.updateNextButtonForUser_();
1485 $('supervised-user-creation-flip-photo').tabIndex =
1486 (imageGrid.selectionType == 'camera') ? 0 : -1;
1487 if (imageGrid.cameraLive || imageGrid.selectionType != 'camera')
1488 imageGrid.previewElement.classList.remove('phototaken');
1490 imageGrid.previewElement.classList.add('phototaken');
1492 if (!imageGrid.cameraLive || imageGrid.selectionType != 'camera') {
1493 this.context_.selectedImageUrl = imageGrid.selectedItemUrl;
1494 chrome.send('supervisedUserSelectImage',
1495 [imageGrid.selectedItemUrl, imageGrid.selectionType]);
1497 // Start/stop camera on (de)selection.
1498 if (!imageGrid.inProgramSelection &&
1499 imageGrid.selectionType != e.oldSelectionType) {
1500 if (imageGrid.selectionType == 'camera') {
1501 // Programmatic selection of camera item is done in
1502 // startCamera callback where streaming is started by itself.
1503 imageGrid.startCamera(
1505 // Start capture if camera is still the selected item.
1506 $('supervised-user-creation-image-preview-img').classList.
1507 toggle('animated-transform', true);
1508 return imageGrid.selectedItem == imageGrid.cameraImage;
1511 $('supervised-user-creation-image-preview-img').classList.toggle(
1512 'animated-transform', false);
1513 imageGrid.stopCamera();
1519 * Handle camera-photo flip.
1521 handleFlipPhoto_: function() {
1522 var imageGrid = this.getScreenElement('image-grid');
1523 imageGrid.previewElement.classList.add('animation');
1524 imageGrid.flipPhoto = !imageGrid.flipPhoto;
1525 var flipMessageId = imageGrid.flipPhoto ?
1526 'photoFlippedAccessibleText' : 'photoFlippedBackAccessibleText';
1527 announceAccessibleMessage(loadTimeData.getString(flipMessageId));
1531 * Handle photo capture from the live camera stream.
1533 handleTakePhoto_: function(e) {
1534 this.getScreenElement('image-grid').takePhoto();
1535 chrome.send('supervisedUserTakePhoto');
1538 handlePhotoTaken_: function(e) {
1539 chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1540 announceAccessibleMessage(
1541 loadTimeData.getString('photoCaptureAccessibleText'));
1545 * Handle photo updated event.
1546 * @param {Event} e Event with 'dataURL' property containing a data URL.
1548 handlePhotoUpdated_: function(e) {
1549 chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1553 * Handle discarding the captured photo.
1555 handleDiscardPhoto_: function(e) {
1556 var imageGrid = this.getScreenElement('image-grid');
1557 imageGrid.discardPhoto();
1558 chrome.send('supervisedUserDiscardPhoto');
1559 announceAccessibleMessage(
1560 loadTimeData.getString('photoDiscardAccessibleText'));
1563 setCameraPresent: function(present) {
1564 this.getScreenElement('image-grid').cameraPresent = present;
1567 setExistingSupervisedUsers: function(users) {
1568 var selectedUser = null;
1569 // Store selected user
1570 if (this.importList_.selectedPod)
1571 selectedUser = this.importList_.selectedPod.user.id;
1573 var userList = users;
1574 userList.sort(function(a, b) {
1575 // Put existing users last.
1576 if (a.exists != b.exists)
1577 return a.exists ? 1 : -1;
1578 // Sort rest by name.
1579 return a.name.localeCompare(b.name, [], {sensitivity: 'base'});
1582 this.importList_.clearPods();
1583 var selectedIndex = -1;
1584 for (var i = 0; i < userList.length; ++i) {
1585 this.importList_.addPod(userList[i]);
1586 if (selectedUser == userList[i].id)
1590 if (userList.length == 1)
1591 this.importList_.selectPod(this.importList_.pods[0]);
1593 if (selectedIndex >= 0)
1594 this.importList_.selectPod(this.importList_.pods[selectedIndex]);
1596 if (this.currentPage_ == 'username')
1597 this.getScreenElement('import-link').hidden = (userList.length == 0);