Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / login / screen_locally_managed_user_creation.js
blob372b744a0f231e874e653f1a8e2d29f3c4e1244e
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.
5 /**
6  * @fileoverview Locally managed user creation flow screen.
7  */
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');
19     return node;
20   });
22   ManagerPod.userImageSalt_ = {};
24   /**
25    * UI element for displaying single account in list of possible managers for
26    * new locally managed user.
27    * @type {Object}
28    */
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 = $('managed-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();
44       };
46       screen.configureTextInput(
47           this.passwordElement,
48           screen.updateNextButtonForManager_.bind(screen),
49           screen.validIfNotEmpty_.bind(screen),
50           function(element) {
51             screen.getScreenButton('next').focus();
52           },
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;
65         }
66       });
67     },
69     /**
70      * Updates UI elements from user data.
71      */
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;
78     },
80     showPasswordError: function() {
81       this.passwordElement.classList.add('password-error');
82       $('bubble').showTextForElement(
83           this.passwordElement,
84           loadTimeData.getString('createManagedUserWrongManagerPasswordText'),
85           cr.ui.Bubble.Attachment.BOTTOM,
86           24, 4);
87     },
89     /**
90      * Brings focus to password field.
91      */
92     focusInput: function() {
93       this.passwordElement.focus();
94     },
96     /**
97      * Gets image element.
98      * @type {!HTMLImageElement}
99      */
100     get imageElement() {
101       return this.querySelector('.managed-user-creation-manager-image');
102     },
104     /**
105      * Gets name element.
106      * @type {!HTMLDivElement}
107      */
108     get nameElement() {
109       return this.querySelector('.managed-user-creation-manager-name');
110     },
112     /**
113      * Gets e-mail element.
114      * @type {!HTMLDivElement}
115      */
116     get emailElement() {
117       return this.querySelector('.managed-user-creation-manager-email');
118     },
120     /**
121      * Gets password element.
122      * @type {!HTMLDivElement}
123      */
124     get passwordElement() {
125       return this.querySelector('.managed-user-creation-manager-password');
126     },
128     /**
129      * Gets password enclosing block.
130      * @type {!HTMLDivElement}
131      */
132     get passwordBlock() {
133       return this.querySelector('.password-block');
134     },
136     /** @override */
137     handleMouseDown_: function(e) {
138       this.parentNode.selectPod(this);
139       // Prevent default so that we don't trigger 'focus' event.
140       e.preventDefault();
141     },
143     /**
144      * The user that this pod represents.
145      * @type {!Object}
146      */
147     user_: undefined,
148     get user() {
149       return this.user_;
150     },
151     set user(userDict) {
152       this.user_ = userDict;
153       this.update();
154     },
155   };
157   var ManagerPodList = cr.ui.define('div');
159   /**
160    * UI element for selecting manager account for new managed user.
161    * @type {Object}
162    */
163   ManagerPodList.prototype = {
164     __proto__: HTMLDivElement.prototype,
166     selectedPod_: null,
168     /** @override */
169     decorate: function() {
170     },
172     /**
173      * Returns all the pods in this pod list.
174      * @type {NodeList}
175      */
176     get pods() {
177       return this.children;
178     },
180     addPod: function(manager) {
181       var managerPod = new ManagerPod({user: manager});
182       this.appendChild(managerPod);
183       managerPod.update();
184     },
186     clearPods: function() {
187       this.innerHTML = '';
188       this.selectedPod_ = null;
189     },
191     selectPod: function(podToSelect) {
192       if ((this.selectedPod_ == podToSelect) && !!podToSelect) {
193         podToSelect.focusInput();
194         return;
195       }
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;
202         }
203       }
204       if (!podToSelect)
205         return;
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]);
212     },
214     /**
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.
219      */
220     selectNextPod: function(direction) {
221       if (!this.selectedPod_)
222         return false;
223       var index = -1;
224       for (var i = 0, pod; pod = this.pods[i]; ++i) {
225         if (pod == this.selectedPod_) {
226           index = i;
227           break;
228         }
229       }
230       if (-1 == index)
231         return false;
232       index = index + direction;
233       if (index < 0 || index >= this.pods.length)
234         return false;
235       this.selectPod(this.pods[index]);
236       return true;
237     }
238   };
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');
244     return node;
245   });
247   /**
248    * UI element for displaying single supervised user in list of possible users
249    * for importing existing users.
250    * @type {Object}
251    */
252   ImportPod.prototype = {
253     __proto__: HTMLDivElement.prototype,
255     /** @override */
256     decorate: function() {
257       // Mousedown has to be used instead of click to be able to prevent 'focus'
258       // event later.
259       this.addEventListener('mousedown',
260                             this.handleMouseDown_.bind(this));
261     },
263     /**
264      * Updates UI elements from user data.
265      */
266     update: function() {
267       this.imageElement.src = this.user.avatarurl;
268       this.nameElement.textContent = this.user.name;
269       if (this.user.exists) {
270         if (this.user.conflict == 'imported') {
271           this.nameElement.textContent =
272               loadTimeData.getStringF('importUserExists', this.user.name);
273         } else {
274           this.nameElement.textContent =
275               loadTimeData.getStringF('importUsernameExists', this.user.name);
276         }
277       }
278       this.classList.toggle('imported', this.user.exists);
279     },
281     /**
282      * Gets image element.
283      * @type {!HTMLImageElement}
284      */
285     get imageElement() {
286       return this.querySelector('.import-pod-image');
287     },
289     /**
290      * Gets name element.
291      * @type {!HTMLDivElement}
292      */
293     get nameElement() {
294       return this.querySelector('.import-pod-name');
295     },
297     /** @override */
298     handleMouseDown_: function(e) {
299       this.parentNode.selectPod(this);
300       // Prevent default so that we don't trigger 'focus' event.
301       e.preventDefault();
302     },
304     /**
305      * The user that this pod represents.
306      * @type {Object}
307      */
308     user_: undefined,
310     get user() {
311       return this.user_;
312     },
314     set user(userDict) {
315       this.user_ = userDict;
316       this.update();
317     },
318   };
320   var ImportPodList = cr.ui.define('div');
322   /**
323    * UI element for selecting existing supervised user for import.
324    * @type {Object}
325    */
326   ImportPodList.prototype = {
327     __proto__: HTMLDivElement.prototype,
329     selectedPod_: null,
331     /** @override */
332     decorate: function() {
333     },
335     /**
336      * Returns all the pods in this pod list.
337      * @type {NodeList}
338      */
339     get pods() {
340       return this.children;
341     },
343     addPod: function(user) {
344       var importPod = new ImportPod({user: user});
345       this.appendChild(importPod);
346       importPod.update();
347     },
349     clearPods: function() {
350       this.innerHTML = '';
351       this.selectedPod_ = null;
352     },
354     scrollIntoView: function(pod) {
355       scroller = this.parentNode;
356       var itemHeight = pod.getBoundingClientRect().height;
357       var scrollTop = scroller.scrollTop;
358       var top = pod.offsetTop - scroller.offsetTop;
359       var clientHeight = scroller.clientHeight;
361       var self = scroller;
363       // Function to adjust the tops of viewport and row.
364       function scrollToAdjustTop() {
365         self.scrollTop = top;
366         return true;
367       };
368       // Function to adjust the bottoms of viewport and row.
369       function scrollToAdjustBottom() {
370         var cs = getComputedStyle(self);
371         var paddingY = parseInt(cs.paddingTop, 10) +
372                        parseInt(cs.paddingBottom, 10);
374         if (top + itemHeight > scrollTop + clientHeight - paddingY) {
375           self.scrollTop = top + itemHeight - clientHeight + paddingY;
376           return true;
377         }
378         return false;
379       };
381       // Check if the entire of given indexed row can be shown in the viewport.
382       if (itemHeight <= clientHeight) {
383         if (top < scrollTop)
384           return scrollToAdjustTop();
385         if (scrollTop + clientHeight < top + itemHeight)
386           return scrollToAdjustBottom();
387       } else {
388         if (scrollTop < top)
389           return scrollToAdjustTop();
390         if (top + itemHeight < scrollTop + clientHeight)
391           return scrollToAdjustBottom();
392       }
393       return false;
394     },
396     /**
397      * @param {Element} podToSelect - pod to select, can be null.
398      */
399     selectPod: function(podToSelect) {
400       if ((this.selectedPod_ == podToSelect) && !!podToSelect) {
401         return;
402       }
403       this.selectedPod_ = podToSelect;
404       for (var i = 0; i < this.pods.length; i++) {
405         var pod = this.pods[i];
406         if (pod != podToSelect)
407           pod.classList.remove('focused');
408       }
409       if (!podToSelect)
410         return;
411       podToSelect.classList.add('focused');
412       var screen = $('managed-user-creation');
413       if (!this.selectedPod_) {
414         screen.getScreenButton('import').disabled = true;
415       } else {
416         screen.getScreenButton('import').disabled =
417             this.selectedPod_.user.exists;
418         if (!this.selectedPod_.user.exists) {
419           chrome.send('userSelectedForImportInManagedUserCreationFlow',
420                       [podToSelect.user.id]);
421         }
422       }
423     },
425     selectUser: function(user_id) {
426       for (var i = 0, pod; pod = this.pods[i]; ++i) {
427         if (pod.user.id == user_id) {
428           this.selectPod(pod);
429           this.scrollIntoView(pod);
430           break;
431         }
432       }
433     },
434   };
436   return {
437     EXTERNAL_API: [
438       'loadManagers',
439       'managedUserSuggestImport',
440       'managedUserNameError',
441       'managedUserNameOk',
442       'showErrorPage',
443       'showIntroPage',
444       'showManagerPage',
445       'showManagerPasswordError',
446       'showPasswordError',
447       'showProgress',
448       'showStatusError',
449       'showTutorialPage',
450       'showUsernamePage',
451       'showPage',
452       'setDefaultImages',
453       'setCameraPresent',
454       'setExistingManagedUsers',
455     ],
457     lastVerifiedName_: null,
458     lastIncorrectUserName_: null,
459     managerList_: null,
460     importList_: null,
462     currentPage_: null,
463     imagesRequested_: false,
465     // Contains data that can be auto-shared with handler.
466     context_: {},
468     /** @override */
469     decorate: function() {
470       this.managerList_ = new ManagerPodList();
471       $('managed-user-creation-managers-pane').appendChild(this.managerList_);
473       this.importList_ = new ImportPodList();
474       $('managed-user-creation-import-pane').appendChild(this.importList_);
476       var userNameField = $('managed-user-creation-name');
477       var passwordField = $('managed-user-creation-password');
478       var password2Field = $('managed-user-creation-password-confirm');
480       var creationScreen = this;
482       var hideUserPasswordError = function(element) {
483         $('bubble').hide();
484         $('managed-user-creation-password').classList.remove('password-error');
485       };
487       this.configureTextInput(userNameField,
488                               this.checkUserName_.bind(this),
489                               this.validIfNotEmpty_.bind(this),
490                               function(element) {
491                                 passwordField.focus();
492                               },
493                               this.clearUserNameError_.bind(this));
494       this.configureTextInput(passwordField,
495                               this.updateNextButtonForUser_.bind(this),
496                               this.validIfNotEmpty_.bind(this),
497                               function(element) {
498                                 password2Field.focus();
499                               },
500                               hideUserPasswordError);
501       this.configureTextInput(password2Field,
502                               this.updateNextButtonForUser_.bind(this),
503                               this.validIfNotEmpty_.bind(this),
504                               function(element) {
505                                 creationScreen.getScreenButton('next').focus();
506                               },
507                               hideUserPasswordError);
509       this.getScreenButton('error').addEventListener('click', function(e) {
510         creationScreen.handleErrorButtonPressed_();
511         e.stopPropagation();
512       });
514       /*
515       TODO(antrim) : this is an explicit code duplications with UserImageScreen.
516       It should be removed by issue 251179.
517       */
518       var imageGrid = this.getScreenElement('image-grid');
519       UserImagesGrid.decorate(imageGrid);
521       // Preview image will track the selected item's URL.
522       var previewElement = this.getScreenElement('image-preview');
523       previewElement.oncontextmenu = function(e) { e.preventDefault(); };
525       imageGrid.previewElement = previewElement;
526       imageGrid.selectionType = 'default';
527       imageGrid.flipPhotoElement = this.getScreenElement('flip-photo');
529       imageGrid.addEventListener('activate',
530                                  this.handleActivate_.bind(this));
531       imageGrid.addEventListener('select',
532                                  this.handleSelect_.bind(this));
533       imageGrid.addEventListener('phototaken',
534                                  this.handlePhotoTaken_.bind(this));
535       imageGrid.addEventListener('photoupdated',
536                                  this.handlePhotoUpdated_.bind(this));
537       // Set the title for camera item in the grid.
538       imageGrid.setCameraTitles(
539           loadTimeData.getString('takePhoto'),
540           loadTimeData.getString('photoFromCamera'));
542       this.getScreenElement('take-photo').addEventListener(
543           'click', this.handleTakePhoto_.bind(this));
544       this.getScreenElement('discard-photo').addEventListener(
545           'click', this.handleDiscardPhoto_.bind(this));
547       // Toggle 'animation' class for the duration of WebKit transition.
548       this.getScreenElement('flip-photo').addEventListener(
549           'click', function(e) {
550             previewElement.classList.add('animation');
551             imageGrid.flipPhoto = !imageGrid.flipPhoto;
552           });
553       this.getScreenElement('image-stream-crop').addEventListener(
554           'webkitTransitionEnd', function(e) {
555             previewElement.classList.remove('animation');
556           });
557       this.getScreenElement('image-preview-img').addEventListener(
558           'webkitTransitionEnd', function(e) {
559             previewElement.classList.remove('animation');
560           });
561     },
563     buttonIds: [],
565     /**
566      * Creates button for adding to controls.
567      * @param {string} buttonId -- id for button, have to be unique within
568      *   screen. Actual id will be prefixed with screen name and appended with
569      *   '-button'. Use getScreenButton(buttonId) to find it later.
570      * @param {string} i18nPrefix -- screen prefix for i18n values.
571      * @param {function} callback -- will be called on button press with
572      *   buttonId parameter.
573      * @param {array} pages -- list of pages where this button should be
574      *   displayed.
575      * @param {array} classes -- list of additional CSS classes for button.
576      */
577     makeButton: function(buttonId, i18nPrefix, callback, pages, classes) {
578       var capitalizedId = buttonId.charAt(0).toUpperCase() + buttonId.slice(1);
579       this.buttonIds.push(buttonId);
580       var result = this.ownerDocument.createElement('button');
581       result.id = this.name() + '-' + buttonId + '-button';
582       result.classList.add('screen-control-button');
583       for (var i = 0; i < classes.length; i++) {
584         result.classList.add(classes[i]);
585       }
586       result.textContent = loadTimeData.
587           getString(i18nPrefix + capitalizedId + 'ButtonTitle');
588       result.addEventListener('click', function(e) {
589         callback(buttonId);
590         e.stopPropagation();
591       });
592       result.pages = pages;
593       return result;
594     },
596     /**
597      * Simple validator for |configureTextInput|.
598      * Element is considered valid if it has any text.
599      * @param {Element} element - element to be validated.
600      * @return {boolean} - true, if element has any text.
601      */
602     validIfNotEmpty_: function(element) {
603       return (element.value.length > 0);
604     },
606     /**
607      * Configure text-input |element|.
608      * @param {Element} element - element to be configured.
609      * @param {function(element)} inputChangeListener - function that will be
610      *    called upon any button press/release.
611      * @param {function(element)} validator - function that will be called when
612      *    Enter is pressed. If it returns |true| then advance to next element.
613      * @param {function(element)} moveFocus - function that will determine next
614      *    element and move focus to it.
615      * @param {function(element)} errorHider - function that is called upon
616      *    every button press, so that any associated error can be hidden.
617      */
618     configureTextInput: function(element,
619                                  inputChangeListener,
620                                  validator,
621                                  moveFocus,
622                                  errorHider) {
623       element.addEventListener('keydown', function(e) {
624         if (e.keyIdentifier == 'Enter') {
625           var dataValid = true;
626           if (validator)
627             dataValid = validator(element);
628           if (!dataValid) {
629             element.focus();
630           } else {
631             if (moveFocus)
632               moveFocus(element);
633           }
634           e.stopPropagation();
635           return;
636         }
637         if (errorHider)
638           errorHider(element);
639         if (inputChangeListener)
640           inputChangeListener(element);
641       });
642       element.addEventListener('keyup', function(e) {
643         if (inputChangeListener)
644           inputChangeListener(element);
645       });
646     },
648     /**
649      * Makes element from template.
650      * @param {string} templateId -- template will be looked up within screen
651      * by class with name "template-<templateId>".
652      * @param {string} elementId -- id for result, uinque within screen. Actual
653      *   id will be prefixed with screen name. Use getScreenElement(id) to find
654      *   it later.
655      */
656     makeFromTemplate: function(templateId, elementId) {
657       var templateClassName = 'template-' + templateId;
658       var templateNode = this.querySelector('.' + templateClassName);
659       var screenPrefix = this.name() + '-';
660       var result = templateNode.cloneNode(true);
661       result.classList.remove(templateClassName);
662       result.id = screenPrefix + elementId;
663       return result;
664     },
666     /**
667      * @param {string} buttonId -- id of button to be found,
668      * @return {Element} button created by makeButton with given buttonId.
669      */
670     getScreenButton: function(buttonId) {
671       var fullId = this.name() + '-' + buttonId + '-button';
672       return this.getScreenElement(buttonId + '-button');
673     },
675     /**
676      * @param {string} elementId -- id of element to be found,
677      * @return {Element} button created by makeFromTemplate with elementId.
678      */
679     getScreenElement: function(elementId) {
680       var fullId = this.name() + '-' + elementId;
681       return $(fullId);
682     },
684     /**
685      * Screen controls.
686      * @type {!Array} Array of Buttons.
687      */
688     get buttons() {
689       var buttons = [];
691       var status = this.makeFromTemplate('status-container', 'status');
692       buttons.push(status);
694       var importLink = this.makeFromTemplate('import-supervised-user-link',
695                                              'import-link');
696       importLink.hidden = true;
697       buttons.push(importLink);
698       var linkElement = importLink.querySelector('.signin-link');
699       linkElement.addEventListener('click',
700           this.importLinkPressed_.bind(this));
702       var createLink = this.makeFromTemplate('create-supervised-user-link',
703                                              'create-link');
704       createLink.hidden = true;
705       buttons.push(createLink);
707       linkElement = createLink.querySelector('.signin-link');
708       linkElement.addEventListener('click',
709           this.createLinkPressed_.bind(this));
711       buttons.push(this.makeButton(
712           'start',
713           'managedUserCreationFlow',
714           this.startButtonPressed_.bind(this),
715           ['intro'],
716           ['custom-appearance', 'button-fancy', 'button-blue']));
718       buttons.push(this.makeButton(
719           'prev',
720           'managedUserCreationFlow',
721           this.prevButtonPressed_.bind(this),
722           ['manager'],
723           []));
725       buttons.push(this.makeButton(
726           'next',
727           'managedUserCreationFlow',
728           this.nextButtonPressed_.bind(this),
729           ['manager', 'username'],
730           []));
732       buttons.push(this.makeButton(
733           'import',
734           'managedUserCreationFlow',
735           this.importButtonPressed_.bind(this),
736           ['import', 'import-password'],
737           []));
739       buttons.push(this.makeButton(
740           'gotit',
741           'managedUserCreationFlow',
742           this.gotItButtonPressed_.bind(this),
743           ['created'],
744           ['custom-appearance', 'button-fancy', 'button-blue']));
745       return buttons;
746     },
748     /**
749      * Does sanity check and calls backend with current user name/password pair
750      * to authenticate manager. May result in showManagerPasswordError.
751      * @private
752      */
753     validateAndLogInAsManager_: function() {
754       var selectedPod = this.managerList_.selectedPod_;
755       if (null == selectedPod)
756         return;
758       var managerId = selectedPod.user.username;
759       var managerDisplayId = selectedPod.user.emailAddress;
760       var managerPassword = selectedPod.passwordElement.value;
761       if (managerPassword.length == 0)
762         return;
763       if (this.disabled)
764         return;
765       this.disabled = true;
766       this.context_.managerId = managerId;
767       this.context_.managerDisplayId = managerDisplayId;
768       this.context_.managerName = selectedPod.user.displayName;
769       chrome.send('authenticateManagerInLocallyManagedUserCreationFlow',
770           [managerId, managerPassword]);
771     },
773     /**
774      * Does sanity check and calls backend with user display name/password pair
775      * to create a user.
776      * @private
777      */
778     validateAndCreateLocallyManagedUser_: function() {
779       var firstPassword = $('managed-user-creation-password').value;
780       var secondPassword =
781           $('managed-user-creation-password-confirm').value;
782       var userName = $('managed-user-creation-name').value;
783       if (firstPassword != secondPassword) {
784         this.showPasswordError(
785             loadTimeData.getString('createManagedUserPasswordMismatchError'));
786         return;
787       }
788       if (this.disabled)
789         return;
790       this.disabled = true;
791       this.context_.managedName = userName;
792       chrome.send('specifyLocallyManagedUserCreationFlowUserData',
793           [userName, firstPassword]);
794     },
796     /**
797      * Does sanity check and calls backend with selected existing supervised
798      * user id to import user.
799      * @private
800      */
801     importSupervisedUser_: function() {
802       if (this.disabled)
803         return;
804       if (this.currentPage_ == 'import-password') {
805         var firstPassword = this.getScreenElement('password').value;
806         var secondPassword = this.getScreenElement('password-confirm').value;
807         if (firstPassword != secondPassword) {
808           this.showPasswordError(
809               loadTimeData.getString('createManagedUserPasswordMismatchError'));
810           return;
811         }
812         var userId = this.context_.importUserId;
813         this.disabled = true;
814         chrome.send('importSupervisedUserWithPassword',
815             [userId, firstPassword]);
816         return;
817       } else {
818         var selectedPod = this.importList_.selectedPod_;
819         if (!selectedPod)
820           return;
821         var user = selectedPod.user;
822         var userId = user.id;
824         this.context_.importUserId = userId;
825         this.context_.managedName = user.name;
826         this.context_.selectedImageUrl = user.avatarurl;
827         if (!user.needPassword) {
828           this.disabled = true;
829           chrome.send('importSupervisedUser', [userId]);
830         } else {
831           this.setVisiblePage_('import-password');
832         }
833       }
834     },
836     /**
837      * Calls backend part to check if current user name is valid/not taken.
838      * Results in call to either managedUserNameOk or managedUserNameError.
839      * @private
840      */
841     checkUserName_: function() {
842       var userName = this.getScreenElement('name').value;
844       // Avoid flickering
845       if (userName == this.lastIncorrectUserName_ ||
846           userName == this.lastVerifiedName_) {
847         return;
848       }
849       if (userName.length > 0) {
850         chrome.send('checkLocallyManagedUserName', [userName]);
851       } else {
852         this.nameErrorVisible = false;
853         this.lastVerifiedName_ = null;
854         this.lastIncorrectUserName_ = null;
855         this.updateNextButtonForUser_();
856       }
857     },
859     /**
860      * Called by backend part in case of successful name validation.
861      * @param {string} name - name that was validated.
862      */
863     managedUserNameOk: function(name) {
864       this.lastVerifiedName_ = name;
865       this.lastIncorrectUserName_ = null;
866       if ($('managed-user-creation-name').value == name)
867         this.clearUserNameError_();
868       this.updateNextButtonForUser_();
869     },
871     /**
872      * Called by backend part in case of name validation failure.
873      * @param {string} name - name that was validated.
874      * @param {string} errorText - reason why this name is invalid.
875      */
876     managedUserNameError: function(name, errorText) {
877       this.disabled = false;
878       this.lastIncorrectUserName_ = name;
879       this.lastVerifiedName_ = null;
881       var userNameField = $('managed-user-creation-name');
882       if (userNameField.value == this.lastIncorrectUserName_) {
883         this.nameErrorVisible = true;
884         $('bubble').showTextForElement(
885             $('managed-user-creation-name'),
886             errorText,
887             cr.ui.Bubble.Attachment.RIGHT,
888             12, 4);
889         this.setButtonDisabledStatus('next', true);
890       }
891     },
893     managedUserSuggestImport: function(name, user_id) {
894       this.disabled = false;
895       this.lastIncorrectUserName_ = name;
896       this.lastVerifiedName_ = null;
898       var userNameField = $('managed-user-creation-name');
899       var creationScreen = this;
901       if (userNameField.value == this.lastIncorrectUserName_) {
902         this.nameErrorVisible = true;
903         var link = this.ownerDocument.createElement('div');
904         link.innerHTML = loadTimeData.getStringF(
905             'importBubbleText',
906             '<a class="signin-link" href="#">',
907             name,
908             '</a>');
909         link.querySelector('.signin-link').addEventListener('click',
910             function(e) {
911               creationScreen.handleSuggestImport_(user_id);
912               e.stopPropagation();
913             });
914         $('bubble').showContentForElement(
915             $('managed-user-creation-name'),
916             cr.ui.Bubble.Attachment.RIGHT,
917             link,
918             12, 4);
919         this.setButtonDisabledStatus('next', true);
920       }
921     },
923     /**
924      * Clears user name error, if name is no more guaranteed to be invalid.
925      * @private
926      */
927     clearUserNameError_: function() {
928       // Avoid flickering
929       if ($('managed-user-creation-name').value ==
930               this.lastIncorrectUserName_) {
931         return;
932       }
933       this.nameErrorVisible = false;
934     },
936     /**
937      * Called by backend part in case of password validation failure.
938      * @param {string} errorText - reason why this password is invalid.
939      */
940     showPasswordError: function(errorText) {
941       $('bubble').showTextForElement(
942           $('managed-user-creation-password'),
943           errorText,
944           cr.ui.Bubble.Attachment.RIGHT,
945           12, 4);
946       $('managed-user-creation-password').classList.add('password-error');
947       $('managed-user-creation-password').focus();
948       this.disabled = false;
949       this.setButtonDisabledStatus('next', true);
950     },
952     /**
953      * True if user name error should be displayed.
954      * @type {boolean}
955      */
956     set nameErrorVisible(value) {
957       $('managed-user-creation-name').
958           classList.toggle('duplicate-name', value);
959       if (!value)
960         $('bubble').hide();
961     },
963     /**
964      * Updates state of Continue button after minimal checks.
965      * @return {boolean} true, if form seems to be valid.
966      * @private
967      */
968     updateNextButtonForManager_: function() {
969       var selectedPod = this.managerList_.selectedPod_;
970       canProceed = null != selectedPod &&
971                    selectedPod.passwordElement.value.length > 0;
973       this.setButtonDisabledStatus('next', !canProceed);
974       return canProceed;
975     },
977     /**
978      * Updates state of Continue button after minimal checks.
979      * @return {boolean} true, if form seems to be valid.
980      * @private
981      */
982     updateNextButtonForUser_: function() {
983       var firstPassword = this.getScreenElement('password').value;
984       var secondPassword = this.getScreenElement('password-confirm').value;
985       var userName = this.getScreenElement('name').value;
987       var passwordOk = (firstPassword.length > 0) &&
988           (firstPassword.length == secondPassword.length);
990       if (this.currentPage_ == 'import-password') {
991         this.setButtonDisabledStatus('import', !passwordOk);
992         return passwordOk;
993       }
994       var imageGrid = this.getScreenElement('image-grid');
995       var imageChosen = !(imageGrid.selectionType == 'camera' &&
996                           imageGrid.cameraLive);
997       var canProceed =
998           passwordOk &&
999           (userName.length > 0) &&
1000           this.lastVerifiedName_ &&
1001           (userName == this.lastVerifiedName_) &&
1002           imageChosen;
1004       this.setButtonDisabledStatus('next', !canProceed);
1005       return canProceed;
1006     },
1008     showSelectedManagerPasswordError_: function() {
1009       var selectedPod = this.managerList_.selectedPod_;
1010       selectedPod.showPasswordError();
1011       selectedPod.passwordElement.value = '';
1012       selectedPod.focusInput();
1013       this.updateNextButtonForManager_();
1014     },
1016     /**
1017      * Enables one particular subpage and hides the rest.
1018      * @param {string} visiblePage - name of subpage.
1019      * @private
1020      */
1021     setVisiblePage_: function(visiblePage) {
1022       this.disabled = false;
1023       this.updateText_();
1024       $('bubble').hide();
1025       if (!this.imagesRequested_) {
1026         chrome.send('supervisedUserGetImages');
1027         this.imagesRequested_ = true;
1028       }
1029       var pageNames = ['intro',
1030                        'manager',
1031                        'username',
1032                        'import',
1033                        'error',
1034                        'created'];
1035       var pageButtons = {'intro' : 'start',
1036                          'error' : 'error',
1037                          'import' : 'import',
1038                          'import-password' : 'import',
1039                          'created' : 'gotit'};
1040       this.hideStatus_();
1041       var pageToDisplay = visiblePage;
1042       if (visiblePage == 'import-password')
1043         pageToDisplay = 'username';
1045       for (i in pageNames) {
1046         var pageName = pageNames[i];
1047         var page = $('managed-user-creation-' + pageName);
1048         page.hidden = (pageName != pageToDisplay);
1049         if (pageName == pageToDisplay)
1050           $('step-logo').hidden = page.classList.contains('step-no-logo');
1051       }
1053       for (i in this.buttonIds) {
1054         var button = this.getScreenButton(this.buttonIds[i]);
1055         button.hidden = button.pages.indexOf(visiblePage) < 0;
1056         button.disabled = false;
1057       }
1059       var pagesWithCancel = ['intro', 'manager', 'username', 'import-password',
1060           'error', 'import'];
1061       var cancelButton = $('cancel-add-user-button');
1062       cancelButton.hidden = pagesWithCancel.indexOf(visiblePage) < 0;
1063       cancelButton.disabled = false;
1065       this.getScreenElement('import-link').hidden = true;
1066       this.getScreenElement('create-link').hidden = true;
1068       if (pageButtons[visiblePage])
1069         this.getScreenButton(pageButtons[visiblePage]).focus();
1071       this.currentPage_ = visiblePage;
1073       if (visiblePage == 'manager' || visiblePage == 'intro') {
1074         $('managed-user-creation-password').classList.remove('password-error');
1075         if (this.managerList_.pods.length > 0)
1076           this.managerList_.selectPod(this.managerList_.pods[0]);
1077       }
1079       if (visiblePage == 'username' || visiblePage == 'import-password') {
1080         var elements = this.getScreenElement(pageToDisplay).
1081             querySelectorAll('.hide-on-import');
1082         for (var i = 0; i < elements.length; i++) {
1083           elements[i].classList.toggle('hidden-on-import',
1084               visiblePage == 'import-password');
1085         }
1086       }
1087       if (visiblePage == 'username') {
1088         var imageGrid = this.getScreenElement('image-grid');
1089         // select some image.
1090         var selected = this.imagesData_[
1091             Math.floor(Math.random() * this.imagesData_.length)];
1092         this.context_.selectedImageUrl = selected.url;
1093         imageGrid.selectedItemUrl = selected.url;
1094         chrome.send('supervisedUserSelectImage',
1095                     [selected.url, 'default']);
1096         this.getScreenElement('image-grid').redraw();
1097         this.checkUserName_();
1098         this.updateNextButtonForUser_();
1099         this.getScreenElement('name').focus();
1100         this.getScreenElement('import-link').hidden =
1101             this.importList_.pods.length == 0;
1102       } else if (visiblePage == 'import-password') {
1103         var imageGrid = this.getScreenElement('image-grid');
1104         var selected;
1105         if ('selectedImageUrl' in this.context_) {
1106           selected = this.context_.selectedImageUrl;
1107         } else {
1108           // select some image.
1109           selected = this.imagesData_[
1110               Math.floor(Math.random() * this.imagesData_.length)].url;
1111           chrome.send('supervisedUserSelectImage',
1112                       [selected, 'default']);
1113         }
1114         imageGrid.selectedItemUrl = selected;
1115         this.getScreenElement('image-grid').redraw();
1117         this.updateNextButtonForUser_();
1119         this.getScreenElement('password').focus();
1120         this.getScreenElement('import-link').hidden = true;
1121       } else {
1122         this.getScreenElement('image-grid').stopCamera();
1123       }
1124       if (visiblePage == 'import') {
1125         this.getScreenElement('create-link').hidden = false;
1126         this.getScreenButton('import').disabled =
1127             !this.importList_.selectedPod_ ||
1128             this.importList_.selectedPod_.user.exists;
1129       }
1130       chrome.send('currentSupervisedUserPage', [this.currentPage_]);
1131     },
1133     setButtonDisabledStatus: function(buttonName, status) {
1134       var button = $('managed-user-creation-' + buttonName + '-button');
1135       button.disabled = status;
1136     },
1138     gotItButtonPressed_: function() {
1139       chrome.send('finishLocalManagedUserCreation');
1140     },
1142     handleErrorButtonPressed_: function() {
1143       chrome.send('abortLocalManagedUserCreation');
1144     },
1146     startButtonPressed_: function() {
1147       this.setVisiblePage_('manager');
1148       this.setButtonDisabledStatus('next', true);
1149     },
1151     nextButtonPressed_: function() {
1152       if (this.currentPage_ == 'manager') {
1153         this.validateAndLogInAsManager_();
1154         return;
1155       }
1156       if (this.currentPage_ == 'username') {
1157         this.validateAndCreateLocallyManagedUser_();
1158       }
1159     },
1161     importButtonPressed_: function() {
1162       this.importSupervisedUser_();
1163     },
1165     importLinkPressed_: function() {
1166       this.setVisiblePage_('import');
1167     },
1169     handleSuggestImport_: function(user_id) {
1170       this.setVisiblePage_('import');
1171       this.importList_.selectUser(user_id);
1172     },
1174     createLinkPressed_: function() {
1175       this.setVisiblePage_('username');
1176       this.lastIncorrectUserName_ = null;
1177       this.lastVerifiedName_ = null;
1178       this.checkUserName_();
1179     },
1181     prevButtonPressed_: function() {
1182       this.setVisiblePage_('intro');
1183     },
1185     showProgress: function(text) {
1186       var status = this.getScreenElement('status');
1187       var statusText = status.querySelector('.id-text');
1188       statusText.textContent = text;
1189       statusText.classList.remove('error');
1190       status.querySelector('.id-spinner').hidden = false;
1191       status.hidden = false;
1192       this.getScreenElement('import-link').hidden = true;
1193       this.getScreenElement('create-link').hidden = true;
1194     },
1196     showStatusError: function(text) {
1197       var status = this.getScreenElement('status');
1198       var statusText = status.querySelector('.id-text');
1199       statusText.textContent = text;
1200       statusText.classList.add('error');
1201       status.querySelector('.id-spinner').hidden = true;
1202       status.hidden = false;
1203       this.getScreenElement('import-link').hidden = true;
1204       this.getScreenElement('create-link').hidden = true;
1205     },
1207     hideStatus_: function() {
1208       var status = this.getScreenElement('status');
1209       status.hidden = true;
1210     },
1212     /**
1213      * Updates state of login header so that necessary buttons are displayed.
1214      **/
1215     onBeforeShow: function(data) {
1216       $('login-header-bar').signinUIState =
1217           SIGNIN_UI_STATE.MANAGED_USER_CREATION_FLOW;
1218       if (data['managers']) {
1219         this.loadManagers(data['managers']);
1220       }
1221       var imageGrid = this.getScreenElement('image-grid');
1222       imageGrid.updateAndFocus();
1223     },
1225     /**
1226      * Update state of login header so that necessary buttons are displayed.
1227      */
1228     onBeforeHide: function() {
1229       $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
1230       this.getScreenElement('image-grid').stopCamera();
1231     },
1233     /**
1234      * Returns a control which should receive an initial focus.
1235      */
1236     get defaultControl() {
1237       return $('managed-user-creation-name');
1238     },
1240     /**
1241      * True if the the screen is disabled (handles no user interaction).
1242      * @type {boolean}
1243      */
1244     disabled_: false,
1246     get disabled() {
1247       return this.disabled_;
1248     },
1250     set disabled(value) {
1251       this.disabled_ = value;
1252       var controls = this.querySelectorAll('button,input');
1253       for (var i = 0, control; control = controls[i]; ++i) {
1254         control.disabled = value;
1255       }
1256       $('login-header-bar').disabled = value;
1257     },
1259     /**
1260      * Called by backend part to propagate list of possible managers.
1261      * @param {Array} userList - list of users that can be managers.
1262      */
1263     loadManagers: function(userList) {
1264       $('managed-user-creation-managers-block').hidden = false;
1265       this.managerList_.clearPods();
1266       for (var i = 0; i < userList.length; ++i)
1267         this.managerList_.addPod(userList[i]);
1268       if (userList.length > 0)
1269         this.managerList_.selectPod(this.managerList_.pods[0]);
1270     },
1272     /**
1273      * Cancels user creation and drops to user screen (either sign).
1274      */
1275     cancel: function() {
1276       var notSignedInPages = ['intro', 'manager'];
1277       var postCreationPages = ['created'];
1278       if (notSignedInPages.indexOf(this.currentPage_) >= 0) {
1279         // Make sure no manager password is kept:
1280         this.managerList_.clearPods();
1282         $('pod-row').loadLastWallpaper();
1284         Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
1285         Oobe.resetSigninUI(true);
1286         return;
1287       }
1288       if (postCreationPages.indexOf(this.currentPage_) >= 0) {
1289         chrome.send('finishLocalManagedUserCreation');
1290         return;
1291       }
1292       chrome.send('abortLocalManagedUserCreation');
1293     },
1295     updateText_: function() {
1296       var managerDisplayId = this.context_.managerDisplayId;
1297       this.updateElementText_('intro-alternate-text',
1298                               'createManagedUserIntroAlternateText');
1299       this.updateElementText_('created-text-1',
1300                               'createManagedUserCreatedText1',
1301                               this.context_.managedName);
1302       // TODO(antrim): Move wrapping with strong in grd file, and eliminate this
1303       //call.
1304       this.updateElementText_('created-text-2',
1305                               'createManagedUserCreatedText2',
1306                               this.wrapStrong(
1307                                   loadTimeData.getString('managementURL')),
1308                                   this.context_.managedName);
1309       this.updateElementText_('created-text-3',
1310                               'createManagedUserCreatedText3',
1311                               managerDisplayId);
1312       this.updateElementText_('name-explanation',
1313                               'createManagedUserNameExplanation',
1314                               managerDisplayId);
1315     },
1317     wrapStrong: function(original) {
1318       if (original == undefined)
1319         return original;
1320       return '<strong>' + original + '</strong>';
1321     },
1323     updateElementText_: function(localId, templateName) {
1324       var args = Array.prototype.slice.call(arguments);
1325       args.shift();
1326       this.getScreenElement(localId).innerHTML =
1327           loadTimeData.getStringF.apply(loadTimeData, args);
1328     },
1330     showIntroPage: function() {
1331       $('managed-user-creation-password').value = '';
1332       $('managed-user-creation-password-confirm').value = '';
1333       $('managed-user-creation-name').value = '';
1335       this.lastVerifiedName_ = null;
1336       this.lastIncorrectUserName_ = null;
1337       this.passwordErrorVisible = false;
1338       $('managed-user-creation-password').classList.remove('password-error');
1339       this.nameErrorVisible = false;
1341       this.setVisiblePage_('intro');
1342     },
1344     showManagerPage: function() {
1345       this.setVisiblePage_('manager');
1346     },
1348     showUsernamePage: function() {
1349       this.setVisiblePage_('username');
1350     },
1352     showTutorialPage: function() {
1353       this.setVisiblePage_('created');
1354     },
1356     showPage: function(page) {
1357       this.setVisiblePage_(page);
1358     },
1360     showErrorPage: function(errorTitle, errorText, errorButtonText) {
1361       this.disabled = false;
1362       $('managed-user-creation-error-title').innerHTML = errorTitle;
1363       $('managed-user-creation-error-text').innerHTML = errorText;
1364       $('managed-user-creation-error-button').textContent = errorButtonText;
1365       this.setVisiblePage_('error');
1366     },
1368     showManagerPasswordError: function() {
1369       this.disabled = false;
1370       this.showSelectedManagerPasswordError_();
1371     },
1373     /*
1374     TODO(antrim) : this is an explicit code duplications with UserImageScreen.
1375     It should be removed by issue 251179.
1376     */
1377     /**
1378      * Currently selected user image index (take photo button is with zero
1379      * index).
1380      * @type {number}
1381      */
1382     selectedUserImage_: -1,
1383     imagesData: [],
1385     setDefaultImages: function(imagesData) {
1386       var imageGrid = this.getScreenElement('image-grid');
1387       for (var i = 0, data; data = imagesData[i]; i++) {
1388         var item = imageGrid.addItem(data.url, data.title);
1389         item.type = 'default';
1390         item.author = data.author || '';
1391         item.website = data.website || '';
1392       }
1393       this.imagesData_ = imagesData;
1394     },
1397     handleActivate_: function() {
1398       var imageGrid = this.getScreenElement('image-grid');
1399       if (imageGrid.selectedItemUrl == ButtonImages.TAKE_PHOTO) {
1400         this.handleTakePhoto_();
1401         return;
1402       }
1403       this.nextButtonPressed_();
1404     },
1406     /**
1407      * Handles selection change.
1408      * @param {Event} e Selection change event.
1409      * @private
1410      */
1411     handleSelect_: function(e) {
1412       var imageGrid = this.getScreenElement('image-grid');
1413       this.updateNextButtonForUser_();
1415       $('managed-user-creation-flip-photo').tabIndex =
1416           (imageGrid.selectionType == 'camera') ? 0 : -1;
1417       if (imageGrid.cameraLive || imageGrid.selectionType != 'camera')
1418         imageGrid.previewElement.classList.remove('phototaken');
1419       else
1420         imageGrid.previewElement.classList.add('phototaken');
1422       if (!imageGrid.cameraLive || imageGrid.selectionType != 'camera') {
1423         this.context_.selectedImageUrl = imageGrid.selectedItemUrl;
1424         chrome.send('supervisedUserSelectImage',
1425                     [imageGrid.selectedItemUrl, imageGrid.selectionType]);
1426       }
1427       // Start/stop camera on (de)selection.
1428       if (!imageGrid.inProgramSelection &&
1429           imageGrid.selectionType != e.oldSelectionType) {
1430         if (imageGrid.selectionType == 'camera') {
1431           // Programmatic selection of camera item is done in
1432           // startCamera callback where streaming is started by itself.
1433           imageGrid.startCamera(
1434               function() {
1435                 // Start capture if camera is still the selected item.
1436                 $('managed-user-creation-image-preview-img').classList.toggle(
1437                     'animated-transform', true);
1438                 return imageGrid.selectedItem == imageGrid.cameraImage;
1439               });
1440         } else {
1441           $('managed-user-creation-image-preview-img').classList.toggle(
1442               'animated-transform', false);
1443           imageGrid.stopCamera();
1444         }
1445       }
1446     },
1448     /**
1449      * Handle photo capture from the live camera stream.
1450      */
1451     handleTakePhoto_: function(e) {
1452       this.getScreenElement('image-grid').takePhoto();
1453     },
1455     handlePhotoTaken_: function(e) {
1456       chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1457     },
1459     /**
1460      * Handle photo updated event.
1461      * @param {Event} e Event with 'dataURL' property containing a data URL.
1462      */
1463     handlePhotoUpdated_: function(e) {
1464       chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1465     },
1467     /**
1468      * Handle discarding the captured photo.
1469      */
1470     handleDiscardPhoto_: function(e) {
1471       var imageGrid = this.getScreenElement('image-grid');
1472       imageGrid.discardPhoto();
1473     },
1475     setCameraPresent: function(present) {
1476       this.getScreenElement('image-grid').cameraPresent = present;
1477     },
1479     setExistingManagedUsers: function(users) {
1480       var userList = users;
1482       userList.sort(function(a, b) {
1483         // Put existing users last.
1484         if (a.exists != b.exists)
1485           return a.exists ? 1 : -1;
1486         // Sort rest by name.
1487         return a.name.localeCompare(b.name, [], {sensitivity: 'base'});
1488       });
1490       this.importList_.clearPods();
1491       for (var i = 0; i < userList.length; ++i)
1492         this.importList_.addPod(userList[i]);
1494       if (userList.length == 1)
1495         this.importList_.selectPod(this.managerList_.pods[0]);
1497       if (userList.length > 0 && this.currentPage_ == 'username')
1498         this.getScreenElement('import-link').hidden = false;
1499     },
1500   };