1 // Copyright (c) 2012 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 cr.define('options', function() {
6 /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
7 /** @const */ var DeletableItem = options.DeletableItem;
8 /** @const */ var DeletableItemList = options.DeletableItemList;
9 /** @const */ var List = cr.ui.List;
10 /** @const */ var ListItem = cr.ui.ListItem;
11 /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
14 * Creates a new Language list item.
15 * @param {Object} languageInfo The information of the language.
17 * @extends {DeletableItem.ListItem}
19 function LanguageListItem(languageInfo) {
20 var el = cr.doc.createElement('li');
21 el.__proto__ = LanguageListItem.prototype;
22 el.language_ = languageInfo;
27 LanguageListItem.prototype = {
28 __proto__: DeletableItem.prototype,
31 * The language code of this language.
38 decorate: function() {
39 DeletableItem.prototype.decorate.call(this);
41 var languageCode = this.language_.code;
42 var languageOptions = options.LanguageOptions.getInstance();
43 this.deletable = languageOptions.languageIsDeletable(languageCode);
44 this.languageCode = languageCode;
45 this.languageName = cr.doc.createElement('div');
46 this.languageName.className = 'language-name';
47 this.languageName.dir = this.language_.textDirection;
48 this.languageName.textContent = this.language_.displayName;
49 this.contentElement.appendChild(this.languageName);
50 this.title = this.language_.nativeDisplayName;
51 this.draggable = true;
56 * Creates a new language list.
57 * @param {Object=} opt_propertyBag Optional properties.
59 * @extends {cr.ui.List}
61 var LanguageList = cr.ui.define('list');
64 * Gets information of a language from the given language code.
65 * @param {string} languageCode Language code (ex. "fr").
67 LanguageList.getLanguageInfoFromLanguageCode = function(languageCode) {
68 // Build the language code to language info dictionary at first time.
69 if (!this.languageCodeToLanguageInfo_) {
70 this.languageCodeToLanguageInfo_ = {};
71 var languageList = loadTimeData.getValue('languageList');
72 for (var i = 0; i < languageList.length; i++) {
73 var languageInfo = languageList[i];
74 this.languageCodeToLanguageInfo_[languageInfo.code] = languageInfo;
78 return this.languageCodeToLanguageInfo_[languageCode];
82 * Returns true if the given language code is valid.
83 * @param {string} languageCode Language code (ex. "fr").
85 LanguageList.isValidLanguageCode = function(languageCode) {
86 // Having the display name for the language code means that the
87 // language code is valid.
88 if (LanguageList.getLanguageInfoFromLanguageCode(languageCode)) {
94 LanguageList.prototype = {
95 __proto__: DeletableItemList.prototype,
97 // The list item being dragged.
99 // The drop position information: "below" or "above".
101 // The preference is a CSV string that describes preferred languages
102 // in Chrome OS. The language list is used for showing the language
103 // list in "Language and Input" options page.
104 preferredLanguagesPref: 'settings.language.preferred_languages',
105 // The preference is a CSV string that describes accept languages used
106 // for content negotiation. To be more precise, the list will be used
107 // in "Accept-Language" header in HTTP requests.
108 acceptLanguagesPref: 'intl.accept_languages',
111 decorate: function() {
112 DeletableItemList.prototype.decorate.call(this);
113 this.selectionModel = new ListSingleSelectionModel;
115 // HACK(arv): http://crbug.com/40902
116 window.addEventListener('resize', this.redraw.bind(this));
118 // Listen to pref change.
120 Preferences.getInstance().addEventListener(this.preferredLanguagesPref,
121 this.handlePreferredLanguagesPrefChange_.bind(this));
123 Preferences.getInstance().addEventListener(this.acceptLanguagesPref,
124 this.handleAcceptLanguagesPrefChange_.bind(this));
127 // Listen to drag and drop events.
128 this.addEventListener('dragstart', this.handleDragStart_.bind(this));
129 this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
130 this.addEventListener('dragover', this.handleDragOver_.bind(this));
131 this.addEventListener('drop', this.handleDrop_.bind(this));
132 this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
135 createItem: function(languageCode) {
136 languageInfo = LanguageList.getLanguageInfoFromLanguageCode(languageCode);
137 return new LanguageListItem(languageInfo);
141 * For each item, determines whether it's deletable.
143 updateDeletable: function() {
144 var items = this.items;
145 for (var i = 0; i < items.length; ++i) {
147 var languageCode = item.languageCode;
148 var languageOptions = options.LanguageOptions.getInstance();
149 item.deletable = languageOptions.languageIsDeletable(languageCode);
154 * Adds a language to the language list.
155 * @param {string} languageCode language code (ex. "fr").
157 addLanguage: function(languageCode) {
158 // It shouldn't happen but ignore the language code if it's
159 // null/undefined, or already present.
160 if (!languageCode || this.dataModel.indexOf(languageCode) >= 0) {
163 this.dataModel.push(languageCode);
164 // Select the last item, which is the language added.
165 this.selectionModel.selectedIndex = this.dataModel.length - 1;
167 this.savePreference_();
171 * Gets the language codes of the currently listed languages.
173 getLanguageCodes: function() {
174 return this.dataModel.slice();
178 * Clears the selection
180 clearSelection: function() {
181 this.selectionModel.unselectAll();
185 * Gets the language code of the selected language.
187 getSelectedLanguageCode: function() {
188 return this.selectedItem;
192 * Selects the language by the given language code.
193 * @returns {boolean} True if the operation is successful.
195 selectLanguageByCode: function(languageCode) {
196 var index = this.dataModel.indexOf(languageCode);
198 this.selectionModel.selectedIndex = index;
205 deleteItemAtIndex: function(index) {
207 this.dataModel.splice(index, 1);
208 // Once the selected item is removed, there will be no selected item.
209 // Select the item pointed by the lead index.
210 index = this.selectionModel.leadIndex;
211 this.savePreference_();
217 * Computes the target item of drop event.
218 * @param {Event} e The drop or dragover event.
221 getTargetFromDropEvent_: function(e) {
222 var target = e.target;
223 // e.target may be an inner element of the list item
224 while (target != null && !(target instanceof ListItem)) {
225 target = target.parentNode;
231 * Handles the dragstart event.
232 * @param {Event} e The dragstart event.
235 handleDragStart_: function(e) {
236 var target = e.target;
237 // ListItem should be the only draggable element type in the page,
239 if (target instanceof ListItem) {
240 this.draggedItem = target;
241 e.dataTransfer.effectAllowed = 'move';
242 // We need to put some kind of data in the drag or it will be
243 // ignored. Use the display name in case the user drags to a text
244 // field or the desktop.
245 e.dataTransfer.setData('text/plain', target.title);
250 * Handles the dragenter event.
251 * @param {Event} e The dragenter event.
254 handleDragEnter_: function(e) {
259 * Handles the dragover event.
260 * @param {Event} e The dragover event.
263 handleDragOver_: function(e) {
264 var dropTarget = this.getTargetFromDropEvent_(e);
265 // Determines whether the drop target is to accept the drop.
266 // The drop is only successful on another ListItem.
267 if (!(dropTarget instanceof ListItem) ||
268 dropTarget == this.draggedItem) {
269 this.hideDropMarker_();
272 // Compute the drop postion. Should we move the dragged item to
273 // below or above the drop target?
274 var rect = dropTarget.getBoundingClientRect();
275 var dy = e.clientY - rect.top;
276 var yRatio = dy / rect.height;
277 var dropPos = yRatio <= .5 ? 'above' : 'below';
278 this.dropPos = dropPos;
279 this.showDropMarker_(dropTarget, dropPos);
284 * Handles the drop event.
285 * @param {Event} e The drop event.
288 handleDrop_: function(e) {
289 var dropTarget = this.getTargetFromDropEvent_(e);
290 this.hideDropMarker_();
292 // Delete the language from the original position.
293 var languageCode = this.draggedItem.languageCode;
294 var originalIndex = this.dataModel.indexOf(languageCode);
295 this.dataModel.splice(originalIndex, 1);
296 // Insert the language to the new position.
297 var newIndex = this.dataModel.indexOf(dropTarget.languageCode);
298 if (this.dropPos == 'below')
300 this.dataModel.splice(newIndex, 0, languageCode);
301 // The cursor should move to the moved item.
302 this.selectionModel.selectedIndex = newIndex;
303 // Save the preference.
304 this.savePreference_();
308 * Handles the dragleave event.
309 * @param {Event} e The dragleave event
312 handleDragLeave_: function(e) {
313 this.hideDropMarker_();
317 * Shows and positions the marker to indicate the drop target.
318 * @param {HTMLElement} target The current target list item of drop
319 * @param {string} pos 'below' or 'above'
322 showDropMarker_: function(target, pos) {
323 window.clearTimeout(this.hideDropMarkerTimer_);
324 var marker = $('language-options-list-dropmarker');
325 var rect = target.getBoundingClientRect();
326 var markerHeight = 8;
327 if (pos == 'above') {
328 marker.style.top = (rect.top - markerHeight / 2) + 'px';
330 marker.style.top = (rect.bottom - markerHeight / 2) + 'px';
332 marker.style.width = rect.width + 'px';
333 marker.style.left = rect.left + 'px';
334 marker.style.display = 'block';
338 * Hides the drop marker.
341 hideDropMarker_: function() {
342 // Hide the marker in a timeout to reduce flickering as we move between
343 // valid drop targets.
344 window.clearTimeout(this.hideDropMarkerTimer_);
345 this.hideDropMarkerTimer_ = window.setTimeout(function() {
346 $('language-options-list-dropmarker').style.display = '';
351 * Handles preferred languages pref change.
352 * @param {Event} e The change event object.
355 handlePreferredLanguagesPrefChange_: function(e) {
356 var languageCodesInCsv = e.value.value;
357 var languageCodes = languageCodesInCsv.split(',');
359 // Add the UI language to the initial list of languages. This is to avoid
360 // a bug where the UI language would be removed from the preferred
361 // language list by sync on first login.
362 // See: crosbug.com/14283
363 languageCodes.push(navigator.language);
364 languageCodes = this.filterBadLanguageCodes_(languageCodes);
365 this.load_(languageCodes);
369 * Handles accept languages pref change.
370 * @param {Event} e The change event object.
373 handleAcceptLanguagesPrefChange_: function(e) {
374 var languageCodesInCsv = e.value.value;
375 var languageCodes = this.filterBadLanguageCodes_(
376 languageCodesInCsv.split(','));
377 this.load_(languageCodes);
381 * Loads given language list.
382 * @param {Array} languageCodes List of language codes.
385 load_: function(languageCodes) {
386 // Preserve the original selected index. See comments below.
387 var originalSelectedIndex = (this.selectionModel ?
388 this.selectionModel.selectedIndex : -1);
389 this.dataModel = new ArrayDataModel(languageCodes);
390 if (originalSelectedIndex >= 0 &&
391 originalSelectedIndex < this.dataModel.length) {
392 // Restore the original selected index if the selected index is
393 // valid after the data model is loaded. This is neeeded to keep
394 // the selected language after the languge is added or removed.
395 this.selectionModel.selectedIndex = originalSelectedIndex;
396 // The lead index should be updated too.
397 this.selectionModel.leadIndex = originalSelectedIndex;
398 } else if (this.dataModel.length > 0) {
399 // Otherwise, select the first item if it's not empty.
400 // Note that ListSingleSelectionModel won't select an item
401 // automatically, hence we manually select the first item here.
402 this.selectionModel.selectedIndex = 0;
407 * Saves the preference.
409 savePreference_: function() {
410 chrome.send('updateLanguageList', [this.dataModel.slice()]);
411 cr.dispatchSimpleEvent(this, 'save');
415 * Filters bad language codes in case bad language codes are
416 * stored in the preference. Removes duplicates as well.
417 * @param {Array} languageCodes List of language codes.
420 filterBadLanguageCodes_: function(languageCodes) {
421 var filteredLanguageCodes = [];
423 for (var i = 0; i < languageCodes.length; i++) {
424 // Check if the the language code is valid, and not
425 // duplicate. Otherwise, skip it.
426 if (LanguageList.isValidLanguageCode(languageCodes[i]) &&
427 !(languageCodes[i] in seen)) {
428 filteredLanguageCodes.push(languageCodes[i]);
429 seen[languageCodes[i]] = true;
432 return filteredLanguageCodes;
437 LanguageList: LanguageList,
438 LanguageListItem: LanguageListItem