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

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

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

Powered by Google App Engine
This is Rietveld 408576698