Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(36)

Side by Side Diff: chrome/browser/resources/options/language_list.js

Issue 9814030: get rid of old options pages (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: more fixes Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 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.
4
5 cr.define('options', function() {
6 const ArrayDataModel = cr.ui.ArrayDataModel;
7 const DeletableItem = options.DeletableItem;
8 const DeletableItemList = options.DeletableItemList;
9 const List = cr.ui.List;
10 const ListItem = cr.ui.ListItem;
11 const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
12
13 /**
14 * Creates a new Language list item.
15 * @param {String} languageCode the languageCode.
16 * @constructor
17 * @extends {DeletableItem.ListItem}
18 */
19 function LanguageListItem(languageCode) {
20 var el = cr.doc.createElement('li');
21 el.__proto__ = LanguageListItem.prototype;
22 el.languageCode_ = languageCode;
23 el.decorate();
24 return el;
25 };
26
27 LanguageListItem.prototype = {
28 __proto__: DeletableItem.prototype,
29
30 /**
31 * The language code of this language.
32 * @type {String}
33 * @private
34 */
35 languageCode_: null,
36
37 /** @inheritDoc */
38 decorate: function() {
39 DeletableItem.prototype.decorate.call(this);
40
41 var languageCode = this.languageCode_;
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.textContent =
48 LanguageList.getDisplayNameFromLanguageCode(languageCode);
49 this.contentElement.appendChild(this.languageName);
50 this.title =
51 LanguageList.getNativeDisplayNameFromLanguageCode(languageCode);
52 this.draggable = true;
53 },
54 };
55
56 /**
57 * Creates a new language list.
58 * @param {Object=} opt_propertyBag Optional properties.
59 * @constructor
60 * @extends {cr.ui.List}
61 */
62 var LanguageList = cr.ui.define('list');
63
64 /**
65 * Gets display name from the given language code.
66 * @param {string} languageCode Language code (ex. "fr").
67 */
68 LanguageList.getDisplayNameFromLanguageCode = function(languageCode) {
69 // Build the language code to display name dictionary at first time.
70 if (!this.languageCodeToDisplayName_) {
71 this.languageCodeToDisplayName_ = {};
72 var languageList = templateData.languageList;
73 for (var i = 0; i < languageList.length; i++) {
74 var language = languageList[i];
75 this.languageCodeToDisplayName_[language.code] = language.displayName;
76 }
77 }
78
79 return this.languageCodeToDisplayName_[languageCode];
80 }
81
82 /**
83 * Gets native display name from the given language code.
84 * @param {string} languageCode Language code (ex. "fr").
85 */
86 LanguageList.getNativeDisplayNameFromLanguageCode = function(languageCode) {
87 // Build the language code to display name dictionary at first time.
88 if (!this.languageCodeToNativeDisplayName_) {
89 this.languageCodeToNativeDisplayName_ = {};
90 var languageList = templateData.languageList;
91 for (var i = 0; i < languageList.length; i++) {
92 var language = languageList[i];
93 this.languageCodeToNativeDisplayName_[language.code] =
94 language.nativeDisplayName;
95 }
96 }
97
98 return this.languageCodeToNativeDisplayName_[languageCode];
99 }
100
101 /**
102 * Returns true if the given language code is valid.
103 * @param {string} languageCode Language code (ex. "fr").
104 */
105 LanguageList.isValidLanguageCode = function(languageCode) {
106 // Having the display name for the language code means that the
107 // language code is valid.
108 if (LanguageList.getDisplayNameFromLanguageCode(languageCode)) {
109 return true;
110 }
111 return false;
112 }
113
114 LanguageList.prototype = {
115 __proto__: DeletableItemList.prototype,
116
117 // The list item being dragged.
118 draggedItem: null,
119 // The drop position information: "below" or "above".
120 dropPos: null,
121 // The preference is a CSV string that describes preferred languages
122 // in Chrome OS. The language list is used for showing the language
123 // list in "Language and Input" options page.
124 preferredLanguagesPref: 'settings.language.preferred_languages',
125 // The preference is a CSV string that describes accept languages used
126 // for content negotiation. To be more precise, the list will be used
127 // in "Accept-Language" header in HTTP requests.
128 acceptLanguagesPref: 'intl.accept_languages',
129
130 /** @inheritDoc */
131 decorate: function() {
132 DeletableItemList.prototype.decorate.call(this);
133 this.selectionModel = new ListSingleSelectionModel;
134
135 // HACK(arv): http://crbug.com/40902
136 window.addEventListener('resize', this.redraw.bind(this));
137
138 // Listen to pref change.
139 if (cr.isChromeOS) {
140 Preferences.getInstance().addEventListener(this.preferredLanguagesPref,
141 this.handlePreferredLanguagesPrefChange_.bind(this));
142 } else {
143 Preferences.getInstance().addEventListener(this.acceptLanguagesPref,
144 this.handleAcceptLanguagesPrefChange_.bind(this));
145 }
146
147 // Listen to drag and drop events.
148 this.addEventListener('dragstart', this.handleDragStart_.bind(this));
149 this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
150 this.addEventListener('dragover', this.handleDragOver_.bind(this));
151 this.addEventListener('drop', this.handleDrop_.bind(this));
152 this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
153 },
154
155 createItem: function(languageCode) {
156 return new LanguageListItem(languageCode);
157 },
158
159 /*
160 * For each item, determines whether it's deletable.
161 */
162 updateDeletable: function() {
163 var items = this.items;
164 for (var i = 0; i < items.length; ++i) {
165 var item = items[i];
166 var languageCode = item.languageCode;
167 var languageOptions = options.LanguageOptions.getInstance();
168 item.deletable = languageOptions.languageIsDeletable(languageCode);
169 }
170 },
171
172 /*
173 * Adds a language to the language list.
174 * @param {string} languageCode language code (ex. "fr").
175 */
176 addLanguage: function(languageCode) {
177 // It shouldn't happen but ignore the language code if it's
178 // null/undefined, or already present.
179 if (!languageCode || this.dataModel.indexOf(languageCode) >= 0) {
180 return;
181 }
182 this.dataModel.push(languageCode);
183 // Select the last item, which is the language added.
184 this.selectionModel.selectedIndex = this.dataModel.length - 1;
185
186 this.savePreference_();
187 },
188
189 /*
190 * Gets the language codes of the currently listed languages.
191 */
192 getLanguageCodes: function() {
193 return this.dataModel.slice();
194 },
195
196 /*
197 * Gets the language code of the selected language.
198 */
199 getSelectedLanguageCode: function() {
200 return this.selectedItem;
201 },
202
203 /*
204 * Selects the language by the given language code.
205 * @returns {boolean} True if the operation is successful.
206 */
207 selectLanguageByCode: function(languageCode) {
208 var index = this.dataModel.indexOf(languageCode);
209 if (index >= 0) {
210 this.selectionModel.selectedIndex = index;
211 return true;
212 }
213 return false;
214 },
215
216 /** @inheritDoc */
217 deleteItemAtIndex: function(index) {
218 if (index >= 0) {
219 this.dataModel.splice(index, 1);
220 // Once the selected item is removed, there will be no selected item.
221 // Select the item pointed by the lead index.
222 index = this.selectionModel.leadIndex;
223 this.savePreference_();
224 }
225 return index;
226 },
227
228 /*
229 * Computes the target item of drop event.
230 * @param {Event} e The drop or dragover event.
231 * @private
232 */
233 getTargetFromDropEvent_ : function(e) {
234 var target = e.target;
235 // e.target may be an inner element of the list item
236 while (target != null && !(target instanceof ListItem)) {
237 target = target.parentNode;
238 }
239 return target;
240 },
241
242 /*
243 * Handles the dragstart event.
244 * @param {Event} e The dragstart event.
245 * @private
246 */
247 handleDragStart_: function(e) {
248 var target = e.target;
249 // ListItem should be the only draggable element type in the page,
250 // but just in case.
251 if (target instanceof ListItem) {
252 this.draggedItem = target;
253 e.dataTransfer.effectAllowed = 'move';
254 // We need to put some kind of data in the drag or it will be
255 // ignored. Use the display name in case the user drags to a text
256 // field or the desktop.
257 e.dataTransfer.setData('text/plain', target.title);
258 }
259 },
260
261 /*
262 * Handles the dragenter event.
263 * @param {Event} e The dragenter event.
264 * @private
265 */
266 handleDragEnter_: function(e) {
267 e.preventDefault();
268 },
269
270 /*
271 * Handles the dragover event.
272 * @param {Event} e The dragover event.
273 * @private
274 */
275 handleDragOver_: function(e) {
276 var dropTarget = this.getTargetFromDropEvent_(e);
277 // Determines whether the drop target is to accept the drop.
278 // The drop is only successful on another ListItem.
279 if (!(dropTarget instanceof ListItem) ||
280 dropTarget == this.draggedItem) {
281 this.hideDropMarker_();
282 return;
283 }
284 // Compute the drop postion. Should we move the dragged item to
285 // below or above the drop target?
286 var rect = dropTarget.getBoundingClientRect();
287 var dy = e.clientY - rect.top;
288 var yRatio = dy / rect.height;
289 var dropPos = yRatio <= .5 ? 'above' : 'below';
290 this.dropPos = dropPos;
291 this.showDropMarker_(dropTarget, dropPos);
292 e.preventDefault();
293 },
294
295 /*
296 * Handles the drop event.
297 * @param {Event} e The drop event.
298 * @private
299 */
300 handleDrop_: function(e) {
301 var dropTarget = this.getTargetFromDropEvent_(e);
302 this.hideDropMarker_();
303
304 // Delete the language from the original position.
305 var languageCode = this.draggedItem.languageCode;
306 var originalIndex = this.dataModel.indexOf(languageCode);
307 this.dataModel.splice(originalIndex, 1);
308 // Insert the language to the new position.
309 var newIndex = this.dataModel.indexOf(dropTarget.languageCode);
310 if (this.dropPos == 'below')
311 newIndex += 1;
312 this.dataModel.splice(newIndex, 0, languageCode);
313 // The cursor should move to the moved item.
314 this.selectionModel.selectedIndex = newIndex;
315 // Save the preference.
316 this.savePreference_();
317 },
318
319 /*
320 * Handles the dragleave event.
321 * @param {Event} e The dragleave event
322 * @private
323 */
324 handleDragLeave_ : function(e) {
325 this.hideDropMarker_();
326 },
327
328 /*
329 * Shows and positions the marker to indicate the drop target.
330 * @param {HTMLElement} target The current target list item of drop
331 * @param {string} pos 'below' or 'above'
332 * @private
333 */
334 showDropMarker_ : function(target, pos) {
335 window.clearTimeout(this.hideDropMarkerTimer_);
336 var marker = $('language-options-list-dropmarker');
337 var rect = target.getBoundingClientRect();
338 var markerHeight = 8;
339 if (pos == 'above') {
340 marker.style.top = (rect.top - markerHeight/2) + 'px';
341 } else {
342 marker.style.top = (rect.bottom - markerHeight/2) + 'px';
343 }
344 marker.style.width = rect.width + 'px';
345 marker.style.left = rect.left + 'px';
346 marker.style.display = 'block';
347 },
348
349 /*
350 * Hides the drop marker.
351 * @private
352 */
353 hideDropMarker_ : function() {
354 // Hide the marker in a timeout to reduce flickering as we move between
355 // valid drop targets.
356 window.clearTimeout(this.hideDropMarkerTimer_);
357 this.hideDropMarkerTimer_ = window.setTimeout(function() {
358 $('language-options-list-dropmarker').style.display = '';
359 }, 100);
360 },
361
362 /**
363 * Handles preferred languages pref change.
364 * @param {Event} e The change event object.
365 * @private
366 */
367 handlePreferredLanguagesPrefChange_: function(e) {
368 var languageCodesInCsv = e.value.value;
369 var languageCodes = languageCodesInCsv.split(',');
370
371 // Add the UI language to the initial list of languages. This is to avoid
372 // a bug where the UI language would be removed from the preferred
373 // language list by sync on first login.
374 // See: crosbug.com/14283
375 languageCodes.push(navigator.language);
376 languageCodes = this.filterBadLanguageCodes_(languageCodes);
377 this.load_(languageCodes);
378 },
379
380 /**
381 * Handles accept languages pref change.
382 * @param {Event} e The change event object.
383 * @private
384 */
385 handleAcceptLanguagesPrefChange_: function(e) {
386 var languageCodesInCsv = e.value.value;
387 var languageCodes = this.filterBadLanguageCodes_(
388 languageCodesInCsv.split(','));
389 this.load_(languageCodes);
390 },
391
392 /**
393 * Loads given language list.
394 * @param {Array} languageCodes List of language codes.
395 * @private
396 */
397 load_: function(languageCodes) {
398 // Preserve the original selected index. See comments below.
399 var originalSelectedIndex = (this.selectionModel ?
400 this.selectionModel.selectedIndex : -1);
401 this.dataModel = new ArrayDataModel(languageCodes);
402 if (originalSelectedIndex >= 0 &&
403 originalSelectedIndex < this.dataModel.length) {
404 // Restore the original selected index if the selected index is
405 // valid after the data model is loaded. This is neeeded to keep
406 // the selected language after the languge is added or removed.
407 this.selectionModel.selectedIndex = originalSelectedIndex;
408 } else if (this.dataModel.length > 0){
409 // Otherwise, select the first item if it's not empty.
410 // Note that ListSingleSelectionModel won't select an item
411 // automatically, hence we manually select the first item here.
412 this.selectionModel.selectedIndex = 0;
413 }
414 },
415
416 /**
417 * Saves the preference.
418 */
419 savePreference_: function() {
420 // Encode the language codes into a CSV string.
421 if (cr.isChromeOS)
422 Preferences.setStringPref(this.preferredLanguagesPref,
423 this.dataModel.slice().join(','));
424 // Save the same language list as accept languages preference as
425 // well, but we need to expand the language list, to make it more
426 // acceptable. For instance, some web sites don't understand 'en-US'
427 // but 'en'. See crosbug.com/9884.
428 var acceptLanguages = this.expandLanguageCodes(this.dataModel.slice());
429 Preferences.setStringPref(this.acceptLanguagesPref,
430 acceptLanguages.join(','));
431 cr.dispatchSimpleEvent(this, 'save');
432 },
433
434 /**
435 * Expands language codes to make these more suitable for Accept-Language.
436 * Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA'].
437 * 'en' won't appear twice as this function eliminates duplicates.
438 * @param {Array} languageCodes List of language codes.
439 * @private
440 */
441 expandLanguageCodes: function(languageCodes) {
442 var expandedLanguageCodes = [];
443 var seen = {}; // Used to eliminiate duplicates.
444 for (var i = 0; i < languageCodes.length; i++) {
445 var languageCode = languageCodes[i];
446 if (!(languageCode in seen)) {
447 expandedLanguageCodes.push(languageCode);
448 seen[languageCode] = true;
449 }
450 var parts = languageCode.split('-');
451 if (!(parts[0] in seen)) {
452 expandedLanguageCodes.push(parts[0]);
453 seen[parts[0]] = true;
454 }
455 }
456 return expandedLanguageCodes;
457 },
458
459 /**
460 * Filters bad language codes in case bad language codes are
461 * stored in the preference. Removes duplicates as well.
462 * @param {Array} languageCodes List of language codes.
463 * @private
464 */
465 filterBadLanguageCodes_: function(languageCodes) {
466 var filteredLanguageCodes = [];
467 var seen = {};
468 for (var i = 0; i < languageCodes.length; i++) {
469 // Check if the the language code is valid, and not
470 // duplicate. Otherwise, skip it.
471 if (LanguageList.isValidLanguageCode(languageCodes[i]) &&
472 !(languageCodes[i] in seen)) {
473 filteredLanguageCodes.push(languageCodes[i]);
474 seen[languageCodes[i]] = true;
475 }
476 }
477 return filteredLanguageCodes;
478 },
479 };
480
481 return {
482 LanguageList: LanguageList,
483 LanguageListItem: LanguageListItem
484 };
485 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698