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 {options.DeletableItem}
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 {options.DeletableItemList}
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));
137 * @param {string} languageCode
139 createItem: function(languageCode
) {
141 LanguageList
.getLanguageInfoFromLanguageCode(languageCode
);
142 return new LanguageListItem(languageInfo
);
146 * For each item, determines whether it's deletable.
148 updateDeletable: function() {
149 var items
= this.items
;
150 for (var i
= 0; i
< items
.length
; ++i
) {
152 var languageCode
= item
.languageCode
;
153 var languageOptions
= options
.LanguageOptions
.getInstance();
154 item
.deletable
= languageOptions
.languageIsDeletable(languageCode
);
159 * Adds a language to the language list.
160 * @param {string} languageCode language code (ex. "fr").
162 addLanguage: function(languageCode
) {
163 // It shouldn't happen but ignore the language code if it's
164 // null/undefined, or already present.
165 if (!languageCode
|| this.dataModel
.indexOf(languageCode
) >= 0) {
168 this.dataModel
.push(languageCode
);
169 // Select the last item, which is the language added.
170 this.selectionModel
.selectedIndex
= this.dataModel
.length
- 1;
172 this.savePreference_();
176 * Gets the language codes of the currently listed languages.
178 getLanguageCodes: function() {
179 return this.dataModel
.slice();
183 * Clears the selection
185 clearSelection: function() {
186 this.selectionModel
.unselectAll();
190 * Gets the language code of the selected language.
192 getSelectedLanguageCode: function() {
193 return this.selectedItem
;
197 * Selects the language by the given language code.
198 * @return {boolean} True if the operation is successful.
200 selectLanguageByCode: function(languageCode
) {
201 var index
= this.dataModel
.indexOf(languageCode
);
203 this.selectionModel
.selectedIndex
= index
;
210 deleteItemAtIndex: function(index
) {
212 this.dataModel
.splice(index
, 1);
213 // Once the selected item is removed, there will be no selected item.
214 // Select the item pointed by the lead index.
215 index
= this.selectionModel
.leadIndex
;
216 this.savePreference_();
222 * Computes the target item of drop event.
223 * @param {Event} e The drop or dragover event.
226 getTargetFromDropEvent_: function(e
) {
227 var target
= e
.target
;
228 // e.target may be an inner element of the list item
229 while (target
!= null && !(target
instanceof ListItem
)) {
230 target
= target
.parentNode
;
236 * Handles the dragstart event.
237 * @param {Event} e The dragstart event.
240 handleDragStart_: function(e
) {
241 var target
= e
.target
;
242 // ListItem should be the only draggable element type in the page,
244 if (target
instanceof ListItem
) {
245 this.draggedItem
= target
;
246 e
.dataTransfer
.effectAllowed
= 'move';
247 // We need to put some kind of data in the drag or it will be
248 // ignored. Use the display name in case the user drags to a text
249 // field or the desktop.
250 e
.dataTransfer
.setData('text/plain', target
.title
);
255 * Handles the dragenter event.
256 * @param {Event} e The dragenter event.
259 handleDragEnter_: function(e
) {
264 * Handles the dragover event.
265 * @param {Event} e The dragover event.
268 handleDragOver_: function(e
) {
269 var dropTarget
= this.getTargetFromDropEvent_(e
);
270 // Determines whether the drop target is to accept the drop.
271 // The drop is only successful on another ListItem.
272 if (!(dropTarget
instanceof ListItem
) ||
273 dropTarget
== this.draggedItem
) {
274 this.hideDropMarker_();
277 // Compute the drop postion. Should we move the dragged item to
278 // below or above the drop target?
279 var rect
= dropTarget
.getBoundingClientRect();
280 var dy
= e
.clientY
- rect
.top
;
281 var yRatio
= dy
/ rect
.height
;
282 var dropPos
= yRatio
<= .5 ? 'above' : 'below';
283 this.dropPos
= dropPos
;
284 this.showDropMarker_(dropTarget
, dropPos
);
289 * Handles the drop event.
290 * @param {Event} e The drop event.
293 handleDrop_: function(e
) {
294 var dropTarget
= this.getTargetFromDropEvent_(e
);
295 this.hideDropMarker_();
297 // Delete the language from the original position.
298 var languageCode
= this.draggedItem
.languageCode
;
299 var originalIndex
= this.dataModel
.indexOf(languageCode
);
300 this.dataModel
.splice(originalIndex
, 1);
301 // Insert the language to the new position.
302 var newIndex
= this.dataModel
.indexOf(dropTarget
.languageCode
);
303 if (this.dropPos
== 'below')
305 this.dataModel
.splice(newIndex
, 0, languageCode
);
306 // The cursor should move to the moved item.
307 this.selectionModel
.selectedIndex
= newIndex
;
308 // Save the preference.
309 this.savePreference_();
313 * Handles the dragleave event.
314 * @param {Event} e The dragleave event
317 handleDragLeave_: function(e
) {
318 this.hideDropMarker_();
322 * Shows and positions the marker to indicate the drop target.
323 * @param {HTMLElement} target The current target list item of drop
324 * @param {string} pos 'below' or 'above'
327 showDropMarker_: function(target
, pos
) {
328 window
.clearTimeout(this.hideDropMarkerTimer_
);
329 var marker
= $('language-options-list-dropmarker');
330 var rect
= target
.getBoundingClientRect();
331 var markerHeight
= 8;
332 if (pos
== 'above') {
333 marker
.style
.top
= (rect
.top
- markerHeight
/ 2) + 'px';
335 marker
.style
.top
= (rect
.bottom
- markerHeight
/ 2) + 'px';
337 marker
.style
.width
= rect
.width
+ 'px';
338 marker
.style
.left
= rect
.left
+ 'px';
339 marker
.style
.display
= 'block';
343 * Hides the drop marker.
346 hideDropMarker_: function() {
347 // Hide the marker in a timeout to reduce flickering as we move between
348 // valid drop targets.
349 window
.clearTimeout(this.hideDropMarkerTimer_
);
350 this.hideDropMarkerTimer_
= window
.setTimeout(function() {
351 $('language-options-list-dropmarker').style
.display
= '';
356 * Handles preferred languages pref change.
357 * @param {Event} e The change event object.
360 handlePreferredLanguagesPrefChange_: function(e
) {
361 var languageCodesInCsv
= e
.value
.value
;
362 var languageCodes
= languageCodesInCsv
.split(',');
364 // Add the UI language to the initial list of languages. This is to avoid
365 // a bug where the UI language would be removed from the preferred
366 // language list by sync on first login.
367 // See: crosbug.com/14283
368 languageCodes
.push(navigator
.language
);
369 languageCodes
= this.filterBadLanguageCodes_(languageCodes
);
370 this.load_(languageCodes
);
374 * Handles accept languages pref change.
375 * @param {Event} e The change event object.
378 handleAcceptLanguagesPrefChange_: function(e
) {
379 var languageCodesInCsv
= e
.value
.value
;
380 var languageCodes
= this.filterBadLanguageCodes_(
381 languageCodesInCsv
.split(','));
382 this.load_(languageCodes
);
386 * Loads given language list.
387 * @param {!Array} languageCodes List of language codes.
390 load_: function(languageCodes
) {
391 // Preserve the original selected index. See comments below.
392 var originalSelectedIndex
= (this.selectionModel
?
393 this.selectionModel
.selectedIndex
: -1);
394 this.dataModel
= new ArrayDataModel(languageCodes
);
395 if (originalSelectedIndex
>= 0 &&
396 originalSelectedIndex
< this.dataModel
.length
) {
397 // Restore the original selected index if the selected index is
398 // valid after the data model is loaded. This is neeeded to keep
399 // the selected language after the languge is added or removed.
400 this.selectionModel
.selectedIndex
= originalSelectedIndex
;
401 // The lead index should be updated too.
402 this.selectionModel
.leadIndex
= originalSelectedIndex
;
403 } else if (this.dataModel
.length
> 0) {
404 // Otherwise, select the first item if it's not empty.
405 // Note that ListSingleSelectionModel won't select an item
406 // automatically, hence we manually select the first item here.
407 this.selectionModel
.selectedIndex
= 0;
412 * Saves the preference.
414 savePreference_: function() {
415 chrome
.send('updateLanguageList', [this.dataModel
.slice()]);
416 cr
.dispatchSimpleEvent(this, 'save');
420 * Filters bad language codes in case bad language codes are
421 * stored in the preference. Removes duplicates as well.
422 * @param {Array} languageCodes List of language codes.
425 filterBadLanguageCodes_: function(languageCodes
) {
426 var filteredLanguageCodes
= [];
428 for (var i
= 0; i
< languageCodes
.length
; i
++) {
429 // Check if the the language code is valid, and not
430 // duplicate. Otherwise, skip it.
431 if (LanguageList
.isValidLanguageCode(languageCodes
[i
]) &&
432 !(languageCodes
[i
] in seen
)) {
433 filteredLanguageCodes
.push(languageCodes
[i
]);
434 seen
[languageCodes
[i
]] = true;
437 return filteredLanguageCodes
;
442 LanguageList
: LanguageList
,
443 LanguageListItem
: LanguageListItem