Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / login / screen_supervised_user_creation.js
blobd3a7f897489ee97aa9a904b322f6253fcca7daa7
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6 * @fileoverview Legacy supervised user creation flow screen.
7 */
9 login.createScreen('SupervisedUserCreationScreen',
10 'supervised-user-creation', function() {
11 var MAX_NAME_LENGTH = 50;
12 var UserImagesGrid = options.UserImagesGrid;
13 var ButtonImages = UserImagesGrid.ButtonImages;
15 var ManagerPod = cr.ui.define(function() {
16 var node = $('supervised-user-creation-manager-template').cloneNode(true);
17 node.removeAttribute('id');
18 node.removeAttribute('hidden');
19 return node;
20 });
22 ManagerPod.userImageSalt_ = {};
24 /**
25 * UI element for displaying single account in list of possible managers for
26 * new supervised user.
27 * @type {Object}
29 ManagerPod.prototype = {
30 __proto__: HTMLDivElement.prototype,
32 /** @override */
33 decorate: function() {
34 // Mousedown has to be used instead of click to be able to prevent 'focus'
35 // event later.
36 this.addEventListener('mousedown',
37 this.handleMouseDown_.bind(this));
38 var screen = $('supervised-user-creation');
39 var managerPod = this;
40 var managerPodList = screen.managerList_;
41 var hideManagerPasswordError = function(element) {
42 managerPod.passwordElement.classList.remove('password-error');
43 $('bubble').hide();
46 screen.configureTextInput(
47 this.passwordElement,
48 screen.updateNextButtonForManager_.bind(screen),
49 screen.validIfNotEmpty_.bind(screen),
50 function(element) {
51 screen.getScreenButton('next').focus();
53 hideManagerPasswordError);
55 this.passwordElement.addEventListener('keydown', function(e) {
56 switch (e.keyIdentifier) {
57 case 'Up':
58 managerPodList.selectNextPod(-1);
59 e.stopPropagation();
60 break;
61 case 'Down':
62 managerPodList.selectNextPod(+1);
63 e.stopPropagation();
64 break;
66 });
69 /**
70 * Updates UI elements from user data.
72 update: function() {
73 this.imageElement.src = 'chrome://userimage/' + this.user.username +
74 '?id=' + ManagerPod.userImageSalt_[this.user.username];
76 this.nameElement.textContent = this.user.displayName;
77 this.emailElement.textContent = this.user.emailAddress;
80 showPasswordError: function() {
81 this.passwordElement.classList.add('password-error');
82 $('bubble').showTextForElement(
83 this.passwordElement,
84 loadTimeData.getString(
85 'createSupervisedUserWrongManagerPasswordText'),
86 cr.ui.Bubble.Attachment.BOTTOM,
87 24, 4);
90 /**
91 * Brings focus to password field.
93 focusInput: function() {
94 this.passwordElement.focus();
97 /**
98 * Gets image element.
99 * @type {!HTMLImageElement}
101 get imageElement() {
102 return this.querySelector('.supervised-user-creation-manager-image');
106 * Gets name element.
107 * @type {!HTMLDivElement}
109 get nameElement() {
110 return this.querySelector('.supervised-user-creation-manager-name');
114 * Gets e-mail element.
115 * @type {!HTMLDivElement}
117 get emailElement() {
118 return this.querySelector('.supervised-user-creation-manager-email');
122 * Gets password element.
123 * @type {!HTMLDivElement}
125 get passwordElement() {
126 return this.querySelector('.supervised-user-creation-manager-password');
130 * Gets password enclosing block.
131 * @type {!HTMLDivElement}
133 get passwordBlock() {
134 return this.querySelector('.password-block');
137 /** @override */
138 handleMouseDown_: function(e) {
139 this.parentNode.selectPod(this);
140 // Prevent default so that we don't trigger 'focus' event.
141 e.preventDefault();
145 * The user that this pod represents.
146 * @type {!Object}
148 user_: undefined,
149 get user() {
150 return this.user_;
152 set user(userDict) {
153 this.user_ = userDict;
154 this.update();
158 var ManagerPodList = cr.ui.define('div');
161 * UI element for selecting manager account for new supervised user.
162 * @type {Object}
164 ManagerPodList.prototype = {
165 __proto__: HTMLDivElement.prototype,
167 selectedPod_: null,
169 /** @override */
170 decorate: function() {
174 * Returns all the pods in this pod list.
175 * @type {NodeList}
177 get pods() {
178 return this.children;
181 addPod: function(manager) {
182 var managerPod = new ManagerPod({user: manager});
183 this.appendChild(managerPod);
184 managerPod.update();
187 clearPods: function() {
188 this.innerHTML = '';
189 this.selectedPod_ = null;
192 selectPod: function(podToSelect) {
193 if ((this.selectedPod_ == podToSelect) && !!podToSelect) {
194 podToSelect.focusInput();
195 return;
197 this.selectedPod_ = podToSelect;
198 for (var i = 0, pod; pod = this.pods[i]; ++i) {
199 if (pod != podToSelect) {
200 pod.classList.remove('focused');
201 pod.passwordElement.value = '';
202 pod.passwordBlock.hidden = true;
205 if (!podToSelect)
206 return;
207 podToSelect.classList.add('focused');
208 podToSelect.passwordBlock.hidden = false;
209 podToSelect.passwordElement.value = '';
210 podToSelect.focusInput();
211 chrome.send('managerSelectedOnSupervisedUserCreationFlow',
212 [podToSelect.user.username]);
216 * Select pod next to currently selected one in given |direction|.
217 * @param {integer} direction - +1 for selecting pod below current, -1 for
218 * selecting pod above current.
219 * @type {boolean} returns if selected pod has changed.
221 selectNextPod: function(direction) {
222 if (!this.selectedPod_)
223 return false;
224 var index = -1;
225 for (var i = 0, pod; pod = this.pods[i]; ++i) {
226 if (pod == this.selectedPod_) {
227 index = i;
228 break;
231 if (-1 == index)
232 return false;
233 index = index + direction;
234 if (index < 0 || index >= this.pods.length)
235 return false;
236 this.selectPod(this.pods[index]);
237 return true;
241 var ImportPod = cr.ui.define(function() {
242 var node = $('supervised-user-creation-import-template').cloneNode(true);
243 node.removeAttribute('id');
244 node.removeAttribute('hidden');
245 return node;
249 * UI element for displaying single supervised user in list of possible users
250 * for importing existing users.
251 * @type {Object}
253 ImportPod.prototype = {
254 __proto__: HTMLDivElement.prototype,
256 /** @override */
257 decorate: function() {
258 // Mousedown has to be used instead of click to be able to prevent 'focus'
259 // event later.
260 this.addEventListener('mousedown', this.handleMouseDown_.bind(this));
261 var screen = $('supervised-user-creation');
262 var importList = screen.importList_;
266 * Updates UI elements from user data.
268 update: function() {
269 this.imageElement.src = this.user.avatarurl;
270 this.nameElement.textContent = this.user.name;
271 if (this.user.exists) {
272 if (this.user.conflict == 'imported') {
273 this.nameElement.textContent =
274 loadTimeData.getStringF('importUserExists', this.user.name);
275 } else {
276 this.nameElement.textContent =
277 loadTimeData.getStringF('importUsernameExists', this.user.name);
280 this.classList.toggle('imported', this.user.exists);
284 * Gets image element.
285 * @type {!HTMLImageElement}
287 get imageElement() {
288 return this.querySelector('.import-pod-image');
292 * Gets name element.
293 * @type {!HTMLDivElement}
295 get nameElement() {
296 return this.querySelector('.import-pod-name');
299 /** @override */
300 handleMouseDown_: function(e) {
301 this.parentNode.selectPod(this);
302 // Prevent default so that we don't trigger 'focus' event.
303 e.preventDefault();
307 * The user that this pod represents.
308 * @type {Object}
310 user_: undefined,
312 get user() {
313 return this.user_;
316 set user(userDict) {
317 this.user_ = userDict;
318 this.update();
322 var ImportPodList = cr.ui.define('div');
325 * UI element for selecting existing supervised user for import.
326 * @type {Object}
328 ImportPodList.prototype = {
329 __proto__: HTMLDivElement.prototype,
331 selectedPod_: null,
333 /** @override */
334 decorate: function() {
335 this.setAttribute('tabIndex', 0);
336 this.classList.add('nofocus');
337 var importList = this;
338 var screen = $('supervised-user-creation');
340 this.addEventListener('focus', function(e) {
341 if (importList.selectedPod_ == null) {
342 if (importList.pods.length > 0)
343 importList.selectPod(importList.pods[0]);
347 this.addEventListener('keydown', function(e) {
348 switch (e.keyIdentifier) {
349 case 'Up':
350 importList.selectNextPod(-1);
351 e.stopPropagation();
352 break;
353 case 'Enter':
354 if (importList.selectedPod_ != null)
355 screen.importSupervisedUser_();
356 e.stopPropagation();
357 break;
358 case 'Down':
359 importList.selectNextPod(+1);
360 e.stopPropagation();
361 break;
367 * Returns all the pods in this pod list.
368 * @type {NodeList}
370 get pods() {
371 return this.children;
375 * Returns selected pod.
376 * @type {Node}
378 get selectedPod() {
379 return this.selectedPod_;
382 addPod: function(user) {
383 var importPod = new ImportPod({user: user});
384 this.appendChild(importPod);
385 importPod.update();
388 clearPods: function() {
389 this.innerHTML = '';
390 this.selectedPod_ = null;
393 scrollIntoView: function(pod) {
394 scroller = this.parentNode;
395 var itemHeight = pod.getBoundingClientRect().height;
396 var scrollTop = scroller.scrollTop;
397 var top = pod.offsetTop - scroller.offsetTop;
398 var clientHeight = scroller.clientHeight;
400 var self = scroller;
402 // Function to adjust the tops of viewport and row.
403 function scrollToAdjustTop() {
404 self.scrollTop = top;
405 return true;
407 // Function to adjust the bottoms of viewport and row.
408 function scrollToAdjustBottom() {
409 var cs = getComputedStyle(self);
410 var paddingY = parseInt(cs.paddingTop, 10) +
411 parseInt(cs.paddingBottom, 10);
413 if (top + itemHeight > scrollTop + clientHeight - paddingY) {
414 self.scrollTop = top + itemHeight - clientHeight + paddingY;
415 return true;
417 return false;
420 // Check if the entire of given indexed row can be shown in the viewport.
421 if (itemHeight <= clientHeight) {
422 if (top < scrollTop)
423 return scrollToAdjustTop();
424 if (scrollTop + clientHeight < top + itemHeight)
425 return scrollToAdjustBottom();
426 } else {
427 if (scrollTop < top)
428 return scrollToAdjustTop();
429 if (top + itemHeight < scrollTop + clientHeight)
430 return scrollToAdjustBottom();
432 return false;
436 * @param {Element} podToSelect - pod to select, can be null.
438 selectPod: function(podToSelect) {
439 if ((this.selectedPod_ == podToSelect) && !!podToSelect) {
440 return;
442 this.selectedPod_ = podToSelect;
443 for (var i = 0; i < this.pods.length; i++) {
444 var pod = this.pods[i];
445 if (pod != podToSelect)
446 pod.classList.remove('focused');
448 if (!podToSelect)
449 return;
450 podToSelect.classList.add('focused');
451 podToSelect.focus();
452 var screen = $('supervised-user-creation');
453 if (!this.selectedPod_) {
454 screen.getScreenButton('import').disabled = true;
455 } else {
456 screen.getScreenButton('import').disabled =
457 this.selectedPod_.user.exists;
458 if (!this.selectedPod_.user.exists) {
459 chrome.send('userSelectedForImportInSupervisedUserCreationFlow',
460 [podToSelect.user.id]);
465 selectNextPod: function(direction) {
466 if (!this.selectedPod_)
467 return false;
468 var index = -1;
469 for (var i = 0, pod; pod = this.pods[i]; ++i) {
470 if (pod == this.selectedPod_) {
471 index = i;
472 break;
475 if (-1 == index)
476 return false;
477 index = index + direction;
478 if (index < 0 || index >= this.pods.length)
479 return false;
480 this.selectPod(this.pods[index]);
481 return true;
484 selectUser: function(user_id) {
485 for (var i = 0, pod; pod = this.pods[i]; ++i) {
486 if (pod.user.id == user_id) {
487 this.selectPod(pod);
488 this.scrollIntoView(pod);
489 break;
495 return {
496 EXTERNAL_API: [
497 'loadManagers',
498 'setCameraPresent',
499 'setDefaultImages',
500 'setExistingSupervisedUsers',
501 'showErrorPage',
502 'showIntroPage',
503 'showManagerPage',
504 'showManagerPasswordError',
505 'showPage',
506 'showPasswordError',
507 'showProgress',
508 'showStatusError',
509 'showTutorialPage',
510 'showUsernamePage',
511 'supervisedUserNameError',
512 'supervisedUserNameOk',
513 'supervisedUserSuggestImport',
516 lastVerifiedName_: null,
517 lastIncorrectUserName_: null,
518 managerList_: null,
519 importList_: null,
521 currentPage_: null,
522 imagesRequested_: false,
524 // Contains data that can be auto-shared with handler.
525 context_: {},
527 /** @override */
528 decorate: function() {
529 this.managerList_ = new ManagerPodList();
530 $('supervised-user-creation-managers-pane').appendChild(
531 this.managerList_);
533 this.importList_ = new ImportPodList();
534 $('supervised-user-creation-import-pane').appendChild(this.importList_);
536 var userNameField = $('supervised-user-creation-name');
537 var passwordField = $('supervised-user-creation-password');
538 var password2Field = $('supervised-user-creation-password-confirm');
540 var creationScreen = this;
542 var hideUserPasswordError = function(element) {
543 $('bubble').hide();
544 $('supervised-user-creation-password').classList.remove(
545 'password-error');
548 this.configureTextInput(userNameField,
549 this.checkUserName_.bind(this),
550 this.validIfNotEmpty_.bind(this),
551 function(element) {
552 passwordField.focus();
554 this.clearUserNameError_.bind(this));
555 this.configureTextInput(passwordField,
556 this.updateNextButtonForUser_.bind(this),
557 this.validIfNotEmpty_.bind(this),
558 function(element) {
559 password2Field.focus();
561 hideUserPasswordError);
562 this.configureTextInput(password2Field,
563 this.updateNextButtonForUser_.bind(this),
564 this.validIfNotEmpty_.bind(this),
565 function(element) {
566 creationScreen.getScreenButton('next').focus();
568 hideUserPasswordError);
570 this.getScreenButton('error').addEventListener('click', function(e) {
571 creationScreen.handleErrorButtonPressed_();
572 e.stopPropagation();
576 TODO(antrim) : this is an explicit code duplications with UserImageScreen.
577 It should be removed by issue 251179.
579 var imageGrid = this.getScreenElement('image-grid');
580 UserImagesGrid.decorate(imageGrid);
582 // Preview image will track the selected item's URL.
583 var previewElement = this.getScreenElement('image-preview');
584 previewElement.oncontextmenu = function(e) { e.preventDefault(); };
586 imageGrid.previewElement = previewElement;
587 imageGrid.selectionType = 'default';
588 imageGrid.flipPhotoElement = this.getScreenElement('flip-photo');
590 imageGrid.addEventListener('activate',
591 this.handleActivate_.bind(this));
592 imageGrid.addEventListener('select',
593 this.handleSelect_.bind(this));
594 imageGrid.addEventListener('phototaken',
595 this.handlePhotoTaken_.bind(this));
596 imageGrid.addEventListener('photoupdated',
597 this.handlePhotoUpdated_.bind(this));
598 // Set the title for camera item in the grid.
599 imageGrid.setCameraTitles(
600 loadTimeData.getString('takePhoto'),
601 loadTimeData.getString('photoFromCamera'));
603 this.getScreenElement('take-photo').addEventListener(
604 'click', this.handleTakePhoto_.bind(this));
605 this.getScreenElement('discard-photo').addEventListener(
606 'click', this.handleDiscardPhoto_.bind(this));
608 // Toggle 'animation' class for the duration of WebKit transition.
609 this.getScreenElement('flip-photo').addEventListener(
610 'click', this.handleFlipPhoto_.bind(this));
611 this.getScreenElement('image-stream-crop').addEventListener(
612 'webkitTransitionEnd', function(e) {
613 previewElement.classList.remove('animation');
615 this.getScreenElement('image-preview-img').addEventListener(
616 'webkitTransitionEnd', function(e) {
617 previewElement.classList.remove('animation');
621 buttonIds: [],
624 * Creates button for adding to controls.
625 * @param {string} buttonId -- id for button, have to be unique within
626 * screen. Actual id will be prefixed with screen name and appended with
627 * '-button'. Use getScreenButton(buttonId) to find it later.
628 * @param {string} i18nPrefix -- screen prefix for i18n values.
629 * @param {function} callback -- will be called on button press with
630 * buttonId parameter.
631 * @param {array} pages -- list of pages where this button should be
632 * displayed.
633 * @param {array} classes -- list of additional CSS classes for button.
635 makeButton: function(buttonId, i18nPrefix, callback, pages, classes) {
636 var capitalizedId = buttonId.charAt(0).toUpperCase() + buttonId.slice(1);
637 this.buttonIds.push(buttonId);
638 var result = this.ownerDocument.createElement('button');
639 result.id = this.name() + '-' + buttonId + '-button';
640 result.classList.add('screen-control-button');
641 for (var i = 0; i < classes.length; i++) {
642 result.classList.add(classes[i]);
644 result.textContent = loadTimeData.
645 getString(i18nPrefix + capitalizedId + 'ButtonTitle');
646 result.addEventListener('click', function(e) {
647 callback(buttonId);
648 e.stopPropagation();
650 result.pages = pages;
651 return result;
655 * Simple validator for |configureTextInput|.
656 * Element is considered valid if it has any text.
657 * @param {Element} element - element to be validated.
658 * @return {boolean} - true, if element has any text.
660 validIfNotEmpty_: function(element) {
661 return (element.value.length > 0);
665 * Configure text-input |element|.
666 * @param {Element} element - element to be configured.
667 * @param {function(element)} inputChangeListener - function that will be
668 * called upon any button press/release.
669 * @param {function(element)} validator - function that will be called when
670 * Enter is pressed. If it returns |true| then advance to next element.
671 * @param {function(element)} moveFocus - function that will determine next
672 * element and move focus to it.
673 * @param {function(element)} errorHider - function that is called upon
674 * every button press, so that any associated error can be hidden.
676 configureTextInput: function(element,
677 inputChangeListener,
678 validator,
679 moveFocus,
680 errorHider) {
681 element.addEventListener('keydown', function(e) {
682 if (e.keyIdentifier == 'Enter') {
683 var dataValid = true;
684 if (validator)
685 dataValid = validator(element);
686 if (!dataValid) {
687 element.focus();
688 } else {
689 if (moveFocus)
690 moveFocus(element);
692 e.stopPropagation();
693 return;
695 if (errorHider)
696 errorHider(element);
697 if (inputChangeListener)
698 inputChangeListener(element);
700 element.addEventListener('keyup', function(e) {
701 if (inputChangeListener)
702 inputChangeListener(element);
707 * Makes element from template.
708 * @param {string} templateId -- template will be looked up within screen
709 * by class with name "template-<templateId>".
710 * @param {string} elementId -- id for result, uinque within screen. Actual
711 * id will be prefixed with screen name. Use getScreenElement(id) to find
712 * it later.
714 makeFromTemplate: function(templateId, elementId) {
715 var templateClassName = 'template-' + templateId;
716 var templateNode = this.querySelector('.' + templateClassName);
717 var screenPrefix = this.name() + '-';
718 var result = templateNode.cloneNode(true);
719 result.classList.remove(templateClassName);
720 result.id = screenPrefix + elementId;
721 return result;
725 * @param {string} buttonId -- id of button to be found,
726 * @return {Element} button created by makeButton with given buttonId.
728 getScreenButton: function(buttonId) {
729 var fullId = this.name() + '-' + buttonId + '-button';
730 return this.getScreenElement(buttonId + '-button');
734 * @param {string} elementId -- id of element to be found,
735 * @return {Element} button created by makeFromTemplate with elementId.
737 getScreenElement: function(elementId) {
738 var fullId = this.name() + '-' + elementId;
739 return $(fullId);
743 * Screen controls.
744 * @type {!Array} Array of Buttons.
746 get buttons() {
747 var links = this.ownerDocument.createElement('div');
748 var buttons = this.ownerDocument.createElement('div');
749 links.classList.add('controls-links');
750 buttons.classList.add('controls-buttons');
752 var importLink = this.makeFromTemplate('import-supervised-user-link',
753 'import-link');
754 importLink.hidden = true;
755 links.appendChild(importLink);
757 var linkElement = importLink.querySelector('.signin-link');
758 linkElement.addEventListener('click',
759 this.importLinkPressed_.bind(this));
761 var createLink = this.makeFromTemplate('create-supervised-user-link',
762 'create-link');
763 createLink.hidden = true;
764 links.appendChild(createLink);
766 var status = this.makeFromTemplate('status-container', 'status');
767 buttons.appendChild(status);
769 linkElement = createLink.querySelector('.signin-link');
770 linkElement.addEventListener('click',
771 this.createLinkPressed_.bind(this));
773 buttons.appendChild(this.makeButton(
774 'start',
775 'supervisedUserCreationFlow',
776 this.startButtonPressed_.bind(this),
777 ['intro'],
778 ['custom-appearance', 'button-fancy', 'button-blue']));
780 buttons.appendChild(this.makeButton(
781 'prev',
782 'supervisedUserCreationFlow',
783 this.prevButtonPressed_.bind(this),
784 ['manager'],
785 []));
787 buttons.appendChild(this.makeButton(
788 'next',
789 'supervisedUserCreationFlow',
790 this.nextButtonPressed_.bind(this),
791 ['manager', 'username'],
792 []));
794 buttons.appendChild(this.makeButton(
795 'import',
796 'supervisedUserCreationFlow',
797 this.importButtonPressed_.bind(this),
798 ['import', 'import-password'],
799 []));
801 buttons.appendChild(this.makeButton(
802 'gotit',
803 'supervisedUserCreationFlow',
804 this.gotItButtonPressed_.bind(this),
805 ['created'],
806 ['custom-appearance', 'button-fancy', 'button-blue']));
807 return [links, buttons];
811 * Does sanity check and calls backend with current user name/password pair
812 * to authenticate manager. May result in showManagerPasswordError.
813 * @private
815 validateAndLogInAsManager_: function() {
816 var selectedPod = this.managerList_.selectedPod_;
817 if (null == selectedPod)
818 return;
820 var managerId = selectedPod.user.username;
821 var managerDisplayId = selectedPod.user.emailAddress;
822 var managerPassword = selectedPod.passwordElement.value;
823 if (managerPassword.length == 0)
824 return;
825 if (this.disabled)
826 return;
827 this.disabled = true;
828 this.context_.managerId = managerId;
829 this.context_.managerDisplayId = managerDisplayId;
830 this.context_.managerName = selectedPod.user.displayName;
831 chrome.send('authenticateManagerInSupervisedUserCreationFlow',
832 [managerId, managerPassword]);
836 * Does sanity check and calls backend with user display name/password pair
837 * to create a user.
838 * @private
840 validateAndCreateSupervisedUser_: function() {
841 var firstPassword = $('supervised-user-creation-password').value;
842 var secondPassword =
843 $('supervised-user-creation-password-confirm').value;
844 var userName = $('supervised-user-creation-name').value;
845 if (firstPassword != secondPassword) {
846 this.showPasswordError(loadTimeData.getString(
847 'createSupervisedUserPasswordMismatchError'));
848 return;
850 if (this.disabled)
851 return;
852 this.disabled = true;
854 this.context_.supervisedName = userName;
855 chrome.send('specifySupervisedUserCreationFlowUserData',
856 [userName, firstPassword]);
860 * Does sanity check and calls backend with selected existing supervised
861 * user id to import user.
862 * @private
864 importSupervisedUser_: function() {
865 if (this.disabled)
866 return;
867 if (this.currentPage_ == 'import-password') {
868 var firstPassword = this.getScreenElement('password').value;
869 var secondPassword = this.getScreenElement('password-confirm').value;
870 if (firstPassword != secondPassword) {
871 this.showPasswordError(loadTimeData.getString(
872 'createSupervisedUserPasswordMismatchError'));
873 return;
875 var userId = this.context_.importUserId;
876 this.disabled = true;
877 chrome.send('importSupervisedUserWithPassword',
878 [userId, firstPassword]);
879 return;
880 } else {
881 var selectedPod = this.importList_.selectedPod_;
882 if (!selectedPod)
883 return;
884 var user = selectedPod.user;
885 var userId = user.id;
887 this.context_.importUserId = userId;
888 this.context_.supervisedName = user.name;
889 this.context_.selectedImageUrl = user.avatarurl;
890 if (!user.needPassword) {
891 this.disabled = true;
892 chrome.send('importSupervisedUser', [userId]);
893 } else {
894 this.setVisiblePage_('import-password');
900 * Calls backend part to check if current user name is valid/not taken.
901 * Results in a call to either supervisedUserNameOk or
902 * supervisedUserNameError.
903 * @private
905 checkUserName_: function() {
906 var userName = this.getScreenElement('name').value;
908 // Avoid flickering
909 if (userName == this.lastIncorrectUserName_ ||
910 userName == this.lastVerifiedName_) {
911 return;
913 if (userName.length > 0) {
914 chrome.send('checkSupervisedUserName', [userName]);
915 } else {
916 this.nameErrorVisible = false;
917 this.lastVerifiedName_ = null;
918 this.lastIncorrectUserName_ = null;
919 this.updateNextButtonForUser_();
924 * Called by backend part in case of successful name validation.
925 * @param {string} name - name that was validated.
927 supervisedUserNameOk: function(name) {
928 this.lastVerifiedName_ = name;
929 this.lastIncorrectUserName_ = null;
930 if ($('supervised-user-creation-name').value == name)
931 this.clearUserNameError_();
932 this.updateNextButtonForUser_();
936 * Called by backend part in case of name validation failure.
937 * @param {string} name - name that was validated.
938 * @param {string} errorText - reason why this name is invalid.
940 supervisedUserNameError: function(name, errorText) {
941 this.disabled = false;
942 this.lastIncorrectUserName_ = name;
943 this.lastVerifiedName_ = null;
945 var userNameField = $('supervised-user-creation-name');
946 if (userNameField.value == this.lastIncorrectUserName_) {
947 this.nameErrorVisible = true;
948 $('bubble').showTextForElement(
949 $('supervised-user-creation-name'),
950 errorText,
951 cr.ui.Bubble.Attachment.RIGHT,
952 12, 4);
953 this.setButtonDisabledStatus('next', true);
957 supervisedUserSuggestImport: function(name, user_id) {
958 this.disabled = false;
959 this.lastIncorrectUserName_ = name;
960 this.lastVerifiedName_ = null;
962 var userNameField = $('supervised-user-creation-name');
963 var creationScreen = this;
965 if (userNameField.value == this.lastIncorrectUserName_) {
966 this.nameErrorVisible = true;
967 var link = this.ownerDocument.createElement('div');
968 link.innerHTML = loadTimeData.getStringF(
969 'importBubbleText',
970 '<a class="signin-link" href="#">',
971 name,
972 '</a>');
973 link.querySelector('.signin-link').addEventListener('click',
974 function(e) {
975 creationScreen.handleSuggestImport_(user_id);
976 e.stopPropagation();
978 $('bubble').showContentForElement(
979 $('supervised-user-creation-name'),
980 cr.ui.Bubble.Attachment.RIGHT,
981 link,
982 12, 4);
983 this.setButtonDisabledStatus('next', true);
988 * Clears user name error, if name is no more guaranteed to be invalid.
989 * @private
991 clearUserNameError_: function() {
992 // Avoid flickering
993 if ($('supervised-user-creation-name').value ==
994 this.lastIncorrectUserName_) {
995 return;
997 this.nameErrorVisible = false;
1001 * Called by backend part in case of password validation failure.
1002 * @param {string} errorText - reason why this password is invalid.
1004 showPasswordError: function(errorText) {
1005 $('bubble').showTextForElement(
1006 $('supervised-user-creation-password'),
1007 errorText,
1008 cr.ui.Bubble.Attachment.RIGHT,
1009 12, 4);
1010 $('supervised-user-creation-password').classList.add('password-error');
1011 $('supervised-user-creation-password').focus();
1012 this.disabled = false;
1013 this.setButtonDisabledStatus('next', true);
1017 * True if user name error should be displayed.
1018 * @type {boolean}
1020 set nameErrorVisible(value) {
1021 $('supervised-user-creation-name').
1022 classList.toggle('duplicate-name', value);
1023 if (!value)
1024 $('bubble').hide();
1028 * Updates state of Continue button after minimal checks.
1029 * @return {boolean} true, if form seems to be valid.
1030 * @private
1032 updateNextButtonForManager_: function() {
1033 var selectedPod = this.managerList_.selectedPod_;
1034 canProceed = null != selectedPod &&
1035 selectedPod.passwordElement.value.length > 0;
1037 this.setButtonDisabledStatus('next', !canProceed);
1038 return canProceed;
1042 * Updates state of Continue button after minimal checks.
1043 * @return {boolean} true, if form seems to be valid.
1044 * @private
1046 updateNextButtonForUser_: function() {
1047 var firstPassword = this.getScreenElement('password').value;
1048 var secondPassword = this.getScreenElement('password-confirm').value;
1049 var userName = this.getScreenElement('name').value;
1051 var passwordOk = (firstPassword.length > 0) &&
1052 (firstPassword.length == secondPassword.length);
1054 if (this.currentPage_ == 'import-password') {
1055 this.setButtonDisabledStatus('import', !passwordOk);
1056 return passwordOk;
1058 var imageGrid = this.getScreenElement('image-grid');
1059 var imageChosen = !(imageGrid.selectionType == 'camera' &&
1060 imageGrid.cameraLive);
1061 var canProceed =
1062 passwordOk &&
1063 (userName.length > 0) &&
1064 this.lastVerifiedName_ &&
1065 (userName == this.lastVerifiedName_) &&
1066 imageChosen;
1068 this.setButtonDisabledStatus('next', !canProceed);
1069 return canProceed;
1072 showSelectedManagerPasswordError_: function() {
1073 var selectedPod = this.managerList_.selectedPod_;
1074 selectedPod.showPasswordError();
1075 selectedPod.passwordElement.value = '';
1076 selectedPod.focusInput();
1077 this.updateNextButtonForManager_();
1081 * Enables one particular subpage and hides the rest.
1082 * @param {string} visiblePage - name of subpage.
1083 * @private
1085 setVisiblePage_: function(visiblePage) {
1086 this.disabled = false;
1087 this.updateText_();
1088 $('bubble').hide();
1089 if (!this.imagesRequested_) {
1090 chrome.send('supervisedUserGetImages');
1091 this.imagesRequested_ = true;
1093 var pageNames = ['intro',
1094 'manager',
1095 'username',
1096 'import',
1097 'error',
1098 'created'];
1099 var pageButtons = {'intro' : 'start',
1100 'error' : 'error',
1101 'import' : 'import',
1102 'import-password' : 'import',
1103 'created' : 'gotit'};
1104 this.hideStatus_();
1105 var pageToDisplay = visiblePage;
1106 if (visiblePage == 'import-password')
1107 pageToDisplay = 'username';
1109 for (i in pageNames) {
1110 var pageName = pageNames[i];
1111 var page = $('supervised-user-creation-' + pageName);
1112 page.hidden = (pageName != pageToDisplay);
1113 if (pageName == pageToDisplay)
1114 $('step-logo').hidden = page.classList.contains('step-no-logo');
1117 for (i in this.buttonIds) {
1118 var button = this.getScreenButton(this.buttonIds[i]);
1119 button.hidden = button.pages.indexOf(visiblePage) < 0;
1120 button.disabled = false;
1123 var pagesWithCancel = ['intro', 'manager', 'username', 'import-password',
1124 'error', 'import'];
1125 $('login-header-bar').allowCancel =
1126 pagesWithCancel.indexOf(visiblePage) > 0;
1127 $('cancel-add-user-button').disabled = false;
1129 this.getScreenElement('import-link').hidden = true;
1130 this.getScreenElement('create-link').hidden = true;
1132 if (pageButtons[visiblePage])
1133 this.getScreenButton(pageButtons[visiblePage]).focus();
1135 this.currentPage_ = visiblePage;
1137 if (visiblePage == 'manager' || visiblePage == 'intro') {
1138 $('supervised-user-creation-password').classList.remove(
1139 'password-error');
1140 if (this.managerList_.pods.length > 0)
1141 this.managerList_.selectPod(this.managerList_.pods[0]);
1144 if (visiblePage == 'username' || visiblePage == 'import-password') {
1145 var elements = this.getScreenElement(pageToDisplay).
1146 querySelectorAll('.hide-on-import');
1147 for (var i = 0; i < elements.length; i++) {
1148 elements[i].classList.toggle('hidden-on-import',
1149 visiblePage == 'import-password');
1152 if (visiblePage == 'username') {
1153 var imageGrid = this.getScreenElement('image-grid');
1154 // select some image.
1155 var selected = this.imagesData_[
1156 Math.floor(Math.random() * this.imagesData_.length)];
1157 this.context_.selectedImageUrl = selected.url;
1158 imageGrid.selectedItemUrl = selected.url;
1159 chrome.send('supervisedUserSelectImage',
1160 [selected.url, 'default']);
1161 this.getScreenElement('image-grid').redraw();
1162 this.checkUserName_();
1163 this.updateNextButtonForUser_();
1164 this.getScreenElement('name').focus();
1165 this.getScreenElement('import-link').hidden =
1166 this.importList_.pods.length == 0;
1167 } else if (visiblePage == 'import-password') {
1168 var imageGrid = this.getScreenElement('image-grid');
1169 var selected;
1170 if ('selectedImageUrl' in this.context_) {
1171 selected = this.context_.selectedImageUrl;
1172 } else {
1173 // select some image.
1174 selected = this.imagesData_[
1175 Math.floor(Math.random() * this.imagesData_.length)].url;
1176 chrome.send('supervisedUserSelectImage',
1177 [selected, 'default']);
1179 imageGrid.selectedItemUrl = selected;
1180 this.getScreenElement('image-grid').redraw();
1182 this.updateNextButtonForUser_();
1184 this.getScreenElement('password').focus();
1185 this.getScreenElement('import-link').hidden = true;
1186 } else {
1187 this.getScreenElement('image-grid').stopCamera();
1189 if (visiblePage == 'import') {
1190 this.getScreenElement('create-link').hidden = false;
1191 this.getScreenButton('import').disabled =
1192 !this.importList_.selectedPod_ ||
1193 this.importList_.selectedPod_.user.exists;
1195 chrome.send('currentSupervisedUserPage', [this.currentPage_]);
1198 setButtonDisabledStatus: function(buttonName, status) {
1199 var button = $('supervised-user-creation-' + buttonName + '-button');
1200 button.disabled = status;
1203 gotItButtonPressed_: function() {
1204 chrome.send('finishLocalSupervisedUserCreation');
1207 handleErrorButtonPressed_: function() {
1208 chrome.send('abortLocalSupervisedUserCreation');
1211 startButtonPressed_: function() {
1212 this.setVisiblePage_('manager');
1213 this.setButtonDisabledStatus('next', true);
1216 nextButtonPressed_: function() {
1217 if (this.currentPage_ == 'manager') {
1218 this.validateAndLogInAsManager_();
1219 return;
1221 if (this.currentPage_ == 'username') {
1222 this.validateAndCreateSupervisedUser_();
1226 importButtonPressed_: function() {
1227 this.importSupervisedUser_();
1230 importLinkPressed_: function() {
1231 this.setVisiblePage_('import');
1234 handleSuggestImport_: function(user_id) {
1235 this.setVisiblePage_('import');
1236 this.importList_.selectUser(user_id);
1239 createLinkPressed_: function() {
1240 this.setVisiblePage_('username');
1241 this.lastIncorrectUserName_ = null;
1242 this.lastVerifiedName_ = null;
1243 this.checkUserName_();
1246 prevButtonPressed_: function() {
1247 this.setVisiblePage_('intro');
1250 showProgress: function(text) {
1251 var status = this.getScreenElement('status');
1252 var statusText = status.querySelector('.id-text');
1253 statusText.textContent = text;
1254 statusText.classList.remove('error');
1255 status.querySelector('.id-spinner').hidden = false;
1256 status.hidden = false;
1257 this.getScreenElement('import-link').hidden = true;
1258 this.getScreenElement('create-link').hidden = true;
1261 showStatusError: function(text) {
1262 var status = this.getScreenElement('status');
1263 var statusText = status.querySelector('.id-text');
1264 statusText.textContent = text;
1265 statusText.classList.add('error');
1266 status.querySelector('.id-spinner').hidden = true;
1267 status.hidden = false;
1268 this.getScreenElement('import-link').hidden = true;
1269 this.getScreenElement('create-link').hidden = true;
1272 hideStatus_: function() {
1273 var status = this.getScreenElement('status');
1274 status.hidden = true;
1278 * Updates state of login header so that necessary buttons are displayed.
1280 onBeforeShow: function(data) {
1281 $('login-header-bar').signinUIState =
1282 SIGNIN_UI_STATE.SUPERVISED_USER_CREATION_FLOW;
1283 if (data['managers']) {
1284 this.loadManagers(data['managers']);
1286 var imageGrid = this.getScreenElement('image-grid');
1287 imageGrid.updateAndFocus();
1291 * Update state of login header so that necessary buttons are displayed.
1293 onBeforeHide: function() {
1294 $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
1295 this.getScreenElement('image-grid').stopCamera();
1299 * Returns a control which should receive an initial focus.
1301 get defaultControl() {
1302 return $('supervised-user-creation-name');
1306 * True if the the screen is disabled (handles no user interaction).
1307 * @type {boolean}
1309 disabled_: false,
1311 get disabled() {
1312 return this.disabled_;
1315 set disabled(value) {
1316 this.disabled_ = value;
1317 var controls = this.querySelectorAll('button,input');
1318 for (var i = 0, control; control = controls[i]; ++i) {
1319 control.disabled = value;
1321 $('login-header-bar').disabled = value;
1322 $('cancel-add-user-button').disabled = false;
1326 * Called by backend part to propagate list of possible managers.
1327 * @param {Array} userList - list of users that can be managers.
1329 loadManagers: function(userList) {
1330 $('supervised-user-creation-managers-block').hidden = false;
1331 this.managerList_.clearPods();
1332 for (var i = 0; i < userList.length; ++i)
1333 this.managerList_.addPod(userList[i]);
1334 if (userList.length > 0)
1335 this.managerList_.selectPod(this.managerList_.pods[0]);
1339 * Cancels user creation and drops to user screen (either sign).
1341 cancel: function() {
1342 var notSignedInPages = ['intro', 'manager'];
1343 var postCreationPages = ['created'];
1344 if (notSignedInPages.indexOf(this.currentPage_) >= 0) {
1345 // Make sure no manager password is kept:
1346 this.managerList_.clearPods();
1348 $('pod-row').loadLastWallpaper();
1350 Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
1351 Oobe.resetSigninUI(true);
1352 return;
1354 if (postCreationPages.indexOf(this.currentPage_) >= 0) {
1355 chrome.send('finishLocalSupervisedUserCreation');
1356 return;
1358 chrome.send('abortLocalSupervisedUserCreation');
1361 updateText_: function() {
1362 var managerDisplayId = this.context_.managerDisplayId;
1363 this.updateElementText_('intro-alternate-text',
1364 'createSupervisedUserIntroAlternateText');
1365 this.updateElementText_('created-text-1',
1366 'createSupervisedUserCreatedText1',
1367 this.context_.supervisedName);
1368 // TODO(antrim): Move wrapping with strong in grd file, and eliminate this
1369 //call.
1370 this.updateElementText_('created-text-2',
1371 'createSupervisedUserCreatedText2',
1372 this.wrapStrong(
1373 loadTimeData.getString('managementURL')),
1374 this.context_.supervisedName);
1375 this.updateElementText_('created-text-3',
1376 'createSupervisedUserCreatedText3',
1377 managerDisplayId);
1378 this.updateElementText_('name-explanation',
1379 'createSupervisedUserNameExplanation',
1380 managerDisplayId);
1383 wrapStrong: function(original) {
1384 if (original == undefined)
1385 return original;
1386 return '<strong>' + original + '</strong>';
1389 updateElementText_: function(localId, templateName) {
1390 var args = Array.prototype.slice.call(arguments);
1391 args.shift();
1392 this.getScreenElement(localId).innerHTML =
1393 loadTimeData.getStringF.apply(loadTimeData, args);
1396 showIntroPage: function() {
1397 $('supervised-user-creation-password').value = '';
1398 $('supervised-user-creation-password-confirm').value = '';
1399 $('supervised-user-creation-name').value = '';
1401 this.lastVerifiedName_ = null;
1402 this.lastIncorrectUserName_ = null;
1403 this.passwordErrorVisible = false;
1404 $('supervised-user-creation-password').classList.remove('password-error');
1405 this.nameErrorVisible = false;
1407 this.setVisiblePage_('intro');
1410 showManagerPage: function() {
1411 this.setVisiblePage_('manager');
1414 showUsernamePage: function() {
1415 this.setVisiblePage_('username');
1418 showTutorialPage: function() {
1419 this.setVisiblePage_('created');
1422 showPage: function(page) {
1423 this.setVisiblePage_(page);
1426 showErrorPage: function(errorTitle, errorText, errorButtonText) {
1427 this.disabled = false;
1428 $('supervised-user-creation-error-title').innerHTML = errorTitle;
1429 $('supervised-user-creation-error-text').innerHTML = errorText;
1430 $('supervised-user-creation-error-button').textContent = errorButtonText;
1431 this.setVisiblePage_('error');
1434 showManagerPasswordError: function() {
1435 this.disabled = false;
1436 this.showSelectedManagerPasswordError_();
1440 TODO(antrim) : this is an explicit code duplications with UserImageScreen.
1441 It should be removed by issue 251179.
1444 * Currently selected user image index (take photo button is with zero
1445 * index).
1446 * @type {number}
1448 selectedUserImage_: -1,
1449 imagesData: [],
1451 setDefaultImages: function(imagesData) {
1452 var imageGrid = this.getScreenElement('image-grid');
1453 imageGrid.setDefaultImages(imagesData);
1454 this.imagesData_ = imagesData;
1458 handleActivate_: function() {
1459 var imageGrid = this.getScreenElement('image-grid');
1460 if (imageGrid.selectedItemUrl == ButtonImages.TAKE_PHOTO) {
1461 this.handleTakePhoto_();
1462 return;
1464 this.nextButtonPressed_();
1468 * Handles selection change.
1469 * @param {Event} e Selection change event.
1470 * @private
1472 handleSelect_: function(e) {
1473 var imageGrid = this.getScreenElement('image-grid');
1474 this.updateNextButtonForUser_();
1476 $('supervised-user-creation-flip-photo').tabIndex =
1477 (imageGrid.selectionType == 'camera') ? 0 : -1;
1478 if (imageGrid.cameraLive || imageGrid.selectionType != 'camera')
1479 imageGrid.previewElement.classList.remove('phototaken');
1480 else
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(
1495 function() {
1496 // Start capture if camera is still the selected item.
1497 $('supervised-user-creation-image-preview-img').classList.
1498 toggle('animated-transform', true);
1499 return imageGrid.selectedItem == imageGrid.cameraImage;
1501 } else {
1502 $('supervised-user-creation-image-preview-img').classList.toggle(
1503 'animated-transform', false);
1504 imageGrid.stopCamera();
1510 * Handle camera-photo flip.
1512 handleFlipPhoto_: function() {
1513 var imageGrid = this.getScreenElement('image-grid');
1514 imageGrid.previewElement.classList.add('animation');
1515 imageGrid.flipPhoto = !imageGrid.flipPhoto;
1516 var flipMessageId = imageGrid.flipPhoto ?
1517 'photoFlippedAccessibleText' : 'photoFlippedBackAccessibleText';
1518 announceAccessibleMessage(loadTimeData.getString(flipMessageId));
1522 * Handle photo capture from the live camera stream.
1524 handleTakePhoto_: function(e) {
1525 this.getScreenElement('image-grid').takePhoto();
1526 chrome.send('supervisedUserTakePhoto');
1529 handlePhotoTaken_: function(e) {
1530 chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1531 announceAccessibleMessage(
1532 loadTimeData.getString('photoCaptureAccessibleText'));
1536 * Handle photo updated event.
1537 * @param {Event} e Event with 'dataURL' property containing a data URL.
1539 handlePhotoUpdated_: function(e) {
1540 chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1544 * Handle discarding the captured photo.
1546 handleDiscardPhoto_: function(e) {
1547 var imageGrid = this.getScreenElement('image-grid');
1548 imageGrid.discardPhoto();
1549 chrome.send('supervisedUserDiscardPhoto');
1550 announceAccessibleMessage(
1551 loadTimeData.getString('photoDiscardAccessibleText'));
1554 setCameraPresent: function(present) {
1555 this.getScreenElement('image-grid').cameraPresent = present;
1558 setExistingSupervisedUsers: function(users) {
1559 var selectedUser = null;
1560 // Store selected user
1561 if (this.importList_.selectedPod)
1562 selectedUser = this.importList_.selectedPod.user.id;
1564 var userList = users;
1565 userList.sort(function(a, b) {
1566 // Put existing users last.
1567 if (a.exists != b.exists)
1568 return a.exists ? 1 : -1;
1569 // Sort rest by name.
1570 return a.name.localeCompare(b.name, [], {sensitivity: 'base'});
1573 this.importList_.clearPods();
1574 var selectedIndex = -1;
1575 for (var i = 0; i < userList.length; ++i) {
1576 this.importList_.addPod(userList[i]);
1577 if (selectedUser == userList[i].id)
1578 selectedIndex = i;
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);