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

Side by Side Diff: chrome/browser/resources/new_new_tab.js

Issue 3250002: Add an accordian effect to NTP. (Closed)
Patch Set: Fix menu positioning when there is a scrollbar. Created 10 years, 3 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
OLDNEW
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 // To avoid creating tons of unnecessary nodes. We assume we cannot fit more 5 // To avoid creating tons of unnecessary nodes. We assume we cannot fit more
6 // than this many items in the miniview. 6 // than this many items in the miniview.
7 var MAX_MINIVIEW_ITEMS = 15; 7 var MAX_MINIVIEW_ITEMS = 15;
8 8
9 var loading = true; 9 var loading = true;
10 10
11 function updateSimpleSection(id, section) { 11 function updateSimpleSection(id, section) {
12 if (shownSections & section) 12 if (shownSections & section)
13 $(id).classList.remove('hidden'); 13 $(id).classList.remove('hidden');
14 else 14 else
15 $(id).classList.add('hidden'); 15 $(id).classList.add('hidden');
16 } 16 }
17 17
18 function recentlyClosedTabs(data) { 18 function recentlyClosedTabs(data) {
19 logEvent('received recently closed tabs'); 19 logEvent('received recently closed tabs');
20 // We need to store the recent items so we can update the layout on a resize. 20 // We need to store the recent items so we can update the layout on a resize.
21 recentItems = data; 21 recentItems = data;
22 renderRecentlyClosed(); 22 renderRecentlyClosed();
23 layoutSections();
23 } 24 }
24 25
25 var recentItems = []; 26 var recentItems = [];
26 27
27 function renderRecentlyClosed() { 28 function renderRecentlyClosed() {
28 // Remove all existing items and create new items. 29 // Remove all existing items and create new items.
29 var recentElement = $('recently-closed'); 30 var recentElement = $('recently-closed');
30 var parentEl = recentElement.lastElementChild; 31 var parentEl = recentElement.lastElementChild;
31 parentEl.textContent = ''; 32 parentEl.textContent = '';
32 33
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
80 81
81 var oldLayoutMode = layoutMode; 82 var oldLayoutMode = layoutMode;
82 var b = useSmallGrid(); 83 var b = useSmallGrid();
83 layoutMode = b ? LayoutMode.SMALL : LayoutMode.NORMAL 84 layoutMode = b ? LayoutMode.SMALL : LayoutMode.NORMAL
84 85
85 if (layoutMode != oldLayoutMode){ 86 if (layoutMode != oldLayoutMode){
86 mostVisited.useSmallGrid = b; 87 mostVisited.useSmallGrid = b;
87 mostVisited.layout(); 88 mostVisited.layout();
88 renderRecentlyClosed(); 89 renderRecentlyClosed();
89 } 90 }
91
92 layoutSections();
93 }
94
95 // Stores some information about each section necessary to layout. A new
96 // instance is constructed for each section on each layout.
97 function SectionLayoutInfo(section) {
98 this.section = section;
99 this.header = section.getElementsByTagName('h2')[0];
100 this.miniview = section.getElementsByClassName('miniview')[0];
101 this.maxiview = section.getElementsByClassName('maxiview')[0];
102 this.expanded = !section.classList.contains('hidden');
103 this.fixedHeight = this.header.offsetHeight;
104 this.scrollingHeight = 0;
105
106 if (this.expanded) {
107 this.scrollingHeight = this.maxiview.offsetHeight;
108 } else if (this.miniview) {
109 this.fixedHeight += this.miniview.offsetHeight;
110 }
111 }
112
113 // Get all sections to be layed out.
114 SectionLayoutInfo.getAll = function() {
115 var sections = document.querySelectorAll('.section:not(.disabled)');
116 var result = [];
117 for (var i = 0, section; section = sections[i]; i++) {
118 result.push(new SectionLayoutInfo(section));
119 }
120 return result;
121 };
122
123 // Layout the sections in a modified accordian. The header and miniview, if
arv (Not doing code reviews) 2010/08/31 21:13:59 accordion
124 // visible are fixed within the viewport. If there is an expanded section, its
125 // it scrolls.
126 //
127 // =============================
128 // | collapsed section | <- Any collapsed sections are fixed position.
129 // | and miniview |
130 // |---------------------------|
131 // | expanded section |
132 // | | <- There can be one expanded section and it
133 // | and maxiview | is absolutely positioned so that it can
134 // | | scroll "underneath" the fixed elements.
135 // | |
136 // |---------------------------|
137 // | another collapsed section |
138 // |---------------------------|
139 //
140 // We want the main frame scrollbar to be the one that scrolls the expanded
141 // region. To get this effect, we make the fixed elements position:fixed and the
142 // scrollable element position:absolute. We also artificially increase the
143 // height of the document so that it is possible to scroll down enough to
144 // display the end of the document, even with any fixed elements at the bottom
145 // of the viewport.
146 //
147 // There is a final twist: If the intrinsic height of the expanded section is
148 // less than the available height (because the window is tall), any collapsed
149 // sections sinch up and sit below the expanded section. This is so that we
150 // don't have a bunch of dead whitespace in the case of expanded sections that
151 // aren't very tall.
152 function layoutSections() {
153 var sections = SectionLayoutInfo.getAll();
154 var expandedSection = null;
155 var headerHeight = 0;
156 var footerHeight = 0;
157
158 // Calculate the height of the fixed elements above the expanded section. Also
159 // take note of the expanded section, if there is one.
160 var i;
161 var section;
162 for (i = 0; section = sections[i]; i++) {
163 headerHeight += section.fixedHeight;
164 if (section.expanded) {
165 expandedSection = section;
166 i++;
167 break;
168 }
169 }
170
171 // Calculate the height of the fixed elements below the expanded section, if
172 // any.
173 for (; section = sections[i]; i++) {
174 footerHeight += section.fixedHeight;
175 }
176
177 // Determine the height to use for the expanded section. If there isn't enough
178 // space to show the expanded section completely, this will be the available
179 // height. Otherwise, we use the intrinsic height of the expanded section.
180 var expandedSectionHeight;
181 if (expandedSection) {
182 var flexHeight = window.innerHeight - headerHeight - footerHeight;
183 if (flexHeight < expandedSection.scrollingHeight) {
184 expandedSectionHeight = flexHeight;
185
186 // Also, artificially expand the height of the document so that we can see
187 // the entire expanded section.
188 //
189 // TODO(aa): Where does this come from? It is the difference between what
190 // we set document.body.style.height to and what
191 // document.body.scrollHeight measures afterward. I expect them to be the
192 // same if document.body has no margins.
arv (Not doing code reviews) 2010/08/31 21:13:59 I think we had a padding or margin on the body to
193 var fudge = 44;
194 document.body.style.height =
195 headerHeight +
196 expandedSection.scrollingHeight +
197 footerHeight +
198 fudge +
199 'px';
200 } else {
201 expandedSectionHeight = expandedSection.scrollingHeight;
202 document.body.style.height = '';
203 }
204 }
205
206 // Now position all the elements.
207 var y = 0;
208 for (i = 0, section; section = sections[i]; i++) {
209 section.header.style.top = y + 'px';
210 y += section.header.offsetHeight;
211
212 if (section.miniview) {
213 section.miniview.style.top = y + 'px';
214 if (section != expandedSection) {
215 y += section.miniview.offsetHeight;
216 }
217 }
218
219 if (section.maxiview) {
220 section.maxiview.style.top = y + 'px';
221 if (section == expandedSection) {
222 y += expandedSectionHeight;
223 }
224 }
225 }
90 } 226 }
91 227
92 window.addEventListener('resize', handleWindowResize); 228 window.addEventListener('resize', handleWindowResize);
93 229
94 var sectionToElementMap; 230 var sectionToElementMap;
95 function getSectionElement(section) { 231 function getSectionElement(section) {
96 if (!sectionToElementMap) { 232 if (!sectionToElementMap) {
97 sectionToElementMap = {}; 233 sectionToElementMap = {};
98 for (var key in Section) { 234 for (var key in Section) {
99 sectionToElementMap[Section[key]] = 235 sectionToElementMap[Section[key]] =
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
140 * Callback when the shown sections changes in another NTP. 276 * Callback when the shown sections changes in another NTP.
141 * @param {number} newShownSections Bitmask of the shown sections. 277 * @param {number} newShownSections Bitmask of the shown sections.
142 */ 278 */
143 function setShownSections(newShownSections) { 279 function setShownSections(newShownSections) {
144 for (var key in Section) { 280 for (var key in Section) {
145 if (newShownSections & Section[key]) 281 if (newShownSections & Section[key])
146 showSection(Section[key]); 282 showSection(Section[key]);
147 else 283 else
148 hideSection(Section[key]); 284 hideSection(Section[key]);
149 } 285 }
286 layoutSections();
150 } 287 }
151 288
152 // Recently closed 289 // Recently closed
153 290
154 function layoutRecentlyClosed() { 291 function layoutRecentlyClosed() {
155 var recentElement = $('recently-closed'); 292 var recentElement = $('recently-closed');
156 // We cannot use clientWidth here since the width has a transition. 293 // We cannot use clientWidth here since the width has a transition.
157 var availWidth = useSmallGrid() ? 692 : 920; 294 var availWidth = useSmallGrid() ? 692 : 920;
158 var parentEl = recentElement.lastElementChild; 295 var parentEl = recentElement.lastElementChild;
159 296
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after
296 var lines = []; 433 var lines = [];
297 var start = log[0][1]; 434 var start = log[0][1];
298 435
299 for (var i = 0; i < log.length; i++) { 436 for (var i = 0; i < log.length; i++) {
300 lines.push((log[i][1] - start) + ': ' + log[i][0]); 437 lines.push((log[i][1] - start) + ': ' + log[i][0]);
301 } 438 }
302 439
303 console.log(lines.join('\n')); 440 console.log(lines.join('\n'));
304 } 441 }
305 442
306 // Updates the visibility of the menu items.
307 function updateOptionMenu() {
308 var menuItems = $('option-menu').children;
309 for (var i = 0; i < menuItems.length; i++) {
310 var item = menuItems[i];
311 var command = item.getAttribute('command');
312 if (command == 'show' || command == 'hide') {
313 var section = Section[item.getAttribute('section')];
314 var visible = shownSections & section;
315 item.setAttribute('command', visible ? 'hide' : 'show');
316 }
317 }
318 }
319
320 // We apply the size class here so that we don't trigger layout animations 443 // We apply the size class here so that we don't trigger layout animations
321 // onload. 444 // onload.
322 445
323 handleWindowResize(); 446 handleWindowResize();
324 447
325 var localStrings = new LocalStrings(); 448 var localStrings = new LocalStrings();
326 449
327 /////////////////////////////////////////////////////////////////////////////// 450 ///////////////////////////////////////////////////////////////////////////////
328 // Things we know are not needed at startup go below here 451 // Things we know are not needed at startup go below here
329 452
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
409 532
410 /** 533 /**
411 * This handles the option menu. 534 * This handles the option menu.
412 * @param {Element} button The button element. 535 * @param {Element} button The button element.
413 * @param {Element} menu The menu element. 536 * @param {Element} menu The menu element.
414 * @constructor 537 * @constructor
415 */ 538 */
416 function OptionMenu(button, menu) { 539 function OptionMenu(button, menu) {
417 this.button = button; 540 this.button = button;
418 this.menu = menu; 541 this.menu = menu;
542 this.button.onclick = bind(this.handleClick, this);
419 this.button.onmousedown = bind(this.handleMouseDown, this); 543 this.button.onmousedown = bind(this.handleMouseDown, this);
420 this.button.onkeydown = bind(this.handleKeyDown, this); 544 this.button.onkeydown = bind(this.handleKeyDown, this);
421 this.boundHideMenu_ = bind(this.hide, this); 545 this.boundHideMenu_ = bind(this.hide, this);
422 this.boundMaybeHide_ = bind(this.maybeHide_, this); 546 this.boundMaybeHide_ = bind(this.maybeHide_, this);
423 this.menu.onmouseover = bind(this.handleMouseOver, this); 547 this.menu.onmouseover = bind(this.handleMouseOver, this);
424 this.menu.onmouseout = bind(this.handleMouseOut, this); 548 this.menu.onmouseout = bind(this.handleMouseOut, this);
425 this.menu.onmouseup = bind(this.handleMouseUp, this); 549 this.menu.onmouseup = bind(this.handleMouseUp, this);
426 } 550 }
427 551
428 OptionMenu.prototype = { 552 OptionMenu.prototype = {
429 show: function() { 553 show: function() {
430 updateOptionMenu();
431 this.positionMenu_(); 554 this.positionMenu_();
432 this.menu.style.display = 'block'; 555 this.menu.style.display = 'block';
433 this.button.classList.add('open'); 556 this.button.classList.add('open');
434 this.button.focus(); 557 this.button.focus();
435 558
436 // Listen to document and window events so that we hide the menu when the 559 // Listen to document and window events so that we hide the menu when the
437 // user clicks outside the menu or tabs away or the whole window is blurred. 560 // user clicks outside the menu or tabs away or the whole window is blurred.
438 document.addEventListener('focus', this.boundMaybeHide_, true); 561 document.addEventListener('focus', this.boundMaybeHide_, true);
439 document.addEventListener('mousedown', this.boundMaybeHide_, true); 562 document.addEventListener('mousedown', this.boundMaybeHide_, true);
440 }, 563 },
441 564
442 positionMenu_: function() { 565 positionMenu_: function() {
443 this.menu.style.top = this.button.getBoundingClientRect().bottom + 'px'; 566 var rect = this.button.getBoundingClientRect();
567 this.menu.style.top = rect.bottom + 'px';
568 this.menu.style.right = (document.body.clientWidth - rect.right) + 'px'
444 }, 569 },
445 570
446 hide: function() { 571 hide: function() {
447 this.menu.style.display = 'none'; 572 this.menu.style.display = 'none';
448 this.button.classList.remove('open'); 573 this.button.classList.remove('open');
449 this.setSelectedIndex(-1); 574 this.setSelectedIndex(-1);
450 575
451 document.removeEventListener('focus', this.boundMaybeHide_, true); 576 document.removeEventListener('focus', this.boundMaybeHide_, true);
452 document.removeEventListener('mousedown', this.boundMaybeHide_, true); 577 document.removeEventListener('mousedown', this.boundMaybeHide_, true);
453 }, 578 },
(...skipping 15 matching lines...) Expand all
469 }, 594 },
470 595
471 handleMouseDown: function(e) { 596 handleMouseDown: function(e) {
472 if (this.isShown()) { 597 if (this.isShown()) {
473 this.hide(); 598 this.hide();
474 } else { 599 } else {
475 this.show(); 600 this.show();
476 } 601 }
477 }, 602 },
478 603
604 handleClick: function(e) {
605 e.stopPropagation();
606 },
607
479 handleMouseOver: function(e) { 608 handleMouseOver: function(e) {
480 var el = e.target; 609 var el = e.target;
481 if (!el.hasAttribute('command')) { 610 if (!el.hasAttribute('command')) {
482 this.setSelectedIndex(-1); 611 this.setSelectedIndex(-1);
483 } else { 612 } else {
484 var index = Array.prototype.indexOf.call(this.menu.children, el); 613 var index = Array.prototype.indexOf.call(this.menu.children, el);
485 this.setSelectedIndex(index); 614 this.setSelectedIndex(index);
486 } 615 }
487 }, 616 },
488 617
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
580 executeItem: function(item) { 709 executeItem: function(item) {
581 var command = item.getAttribute('command'); 710 var command = item.getAttribute('command');
582 if (command in this.commands) { 711 if (command in this.commands) {
583 this.commands[command].call(this, item); 712 this.commands[command].call(this, item);
584 } 713 }
585 714
586 this.hide(); 715 this.hide();
587 } 716 }
588 }; 717 };
589 718
590 // TODO(aa): The 'clear-all-blacklisted' feature needs to move into a menu in 719 var optionMenu = new OptionMenu(
591 // the most visited section. 720 document.querySelector('#most-visited-section h2 .settings'),
592 /* 721 $('option-menu'));
593 var optionMenu = new OptionMenu($('option-button'), $('option-menu'));
594 optionMenu.commands = { 722 optionMenu.commands = {
595 'clear-all-blacklisted' : function() { 723 'clear-all-blacklisted' : function() {
596 mostVisited.clearAllBlacklisted(); 724 mostVisited.clearAllBlacklisted();
597 chrome.send('getMostVisited'); 725 chrome.send('getMostVisited');
598 },
599 'show': function(item) {
600 var section = Section[item.getAttribute('section')];
601 showSection(section);
602 saveShownSections();
603 },
604 'hide': function(item) {
605 var section = Section[item.getAttribute('section')];
606 hideSection(section);
607 saveShownSections();
608 } 726 }
609 }; 727 };
610 */
611 728
612 $('main').addEventListener('click', function(e) { 729 $('main').addEventListener('click', function(e) {
613 var p = e.target; 730 var p = e.target;
614 while (p && p.tagName != 'H2') { 731 while (p && p.tagName != 'H2') {
615 p = p.parentNode; 732 p = p.parentNode;
616 } 733 }
617 734
618 if (!p) { 735 if (!p) {
619 return; 736 return;
620 } 737 }
621 738
622 p = p.parentNode; 739 p = p.parentNode;
623 if (p.noexpand) { 740 if (p.noexpand) {
624 return; 741 return;
625 } 742 }
626 743
627 var section = p.getAttribute('section'); 744 var section = p.getAttribute('section');
628 if (section) { 745 if (section) {
629 if (shownSections & Section[section]) 746 if (shownSections & Section[section]) {
630 hideSection(Section[section]); 747 hideSection(Section[section]);
631 else 748 } else {
632 showSection(Section[section]); 749 for (var p in Section) {
750 if (p == section)
751 showSection(Section[p]);
752 else
753 hideSection(Section[p]);
754 }
755 }
756 layoutSections();
633 saveShownSections(); 757 saveShownSections();
634 } 758 }
635 }); 759 });
636 760
637 function handleIfEnterKey(f) { 761 function handleIfEnterKey(f) {
638 return function(e) { 762 return function(e) {
639 if (e.keyIdentifier == 'Enter') { 763 if (e.keyIdentifier == 'Enter') {
640 f(e); 764 f(e);
641 } 765 }
642 }; 766 };
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after
814 * make sure we don't send the initial sync message to the backend unless the 938 * make sure we don't send the initial sync message to the backend unless the
815 * backend told us that the sync code is present. 939 * backend told us that the sync code is present.
816 */ 940 */
817 function callGetSyncMessageIfSyncIsPresent() { 941 function callGetSyncMessageIfSyncIsPresent() {
818 if (document.documentElement.getAttribute('syncispresent') == 'true') { 942 if (document.documentElement.getAttribute('syncispresent') == 'true') {
819 chrome.send('GetSyncMessage'); 943 chrome.send('GetSyncMessage');
820 } 944 }
821 } 945 }
822 946
823 function hideAllMenus() { 947 function hideAllMenus() {
824 // TODO(aa): See comment in definition of optionMenu. 948 optionMenu.hide();
825 //optionMenu.hide();
826 } 949 }
827 950
828 window.addEventListener('blur', hideAllMenus); 951 window.addEventListener('blur', hideAllMenus);
829 window.addEventListener('keydown', function(e) { 952 window.addEventListener('keydown', function(e) {
830 if (e.keyIdentifier == 'Alt' || e.keyIdentifier == 'Meta') { 953 if (e.keyIdentifier == 'Alt' || e.keyIdentifier == 'Meta') {
831 hideAllMenus(); 954 hideAllMenus();
832 } 955 }
833 }, true); 956 }, true);
834 957
835 // Tooltip for elements that have text that overflows. 958 // Tooltip for elements that have text that overflows.
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
871 while (el.hasChildNodes()) { 994 while (el.hasChildNodes()) {
872 span.appendChild(el.firstChild); 995 span.appendChild(el.firstChild);
873 } 996 }
874 el.appendChild(span); 997 el.appendChild(span);
875 } 998 }
876 999
877 updateAttribution(); 1000 updateAttribution();
878 1001
879 var mostVisited = new MostVisited( 1002 var mostVisited = new MostVisited(
880 $('most-visited'), 1003 $('most-visited'),
881 $('most-visited-section').getElementsByClassName('miniview')[0], 1004 document.querySelector('#most-visited-section .miniview'),
882 useSmallGrid(), 1005 useSmallGrid(),
883 shownSections & Section.THUMB); 1006 shownSections & Section.THUMB);
884 1007
885 function mostVisitedPages(data, firstRun) { 1008 function mostVisitedPages(data, firstRun) {
886 logEvent('received most visited pages'); 1009 logEvent('received most visited pages');
887 1010
888 mostVisited.data = data; 1011 mostVisited.data = data;
889 mostVisited.layout(); 1012 mostVisited.layout();
1013 layoutSections();
890 1014
891 loading = false; 1015 loading = false;
892 1016
893 // Remove class name in a timeout so that changes done in this JS thread are 1017 // Remove class name in a timeout so that changes done in this JS thread are
894 // not animated. 1018 // not animated.
895 window.setTimeout(function() { 1019 window.setTimeout(function() {
896 mostVisited.ensureSmallGridCorrect(); 1020 mostVisited.ensureSmallGridCorrect();
897 document.body.classList.remove('loading'); 1021 document.body.classList.remove('loading');
898 }, 1); 1022 }, 1);
899 1023
900 // Only show the first run notification if first run. 1024 // Only show the first run notification if first run.
901 if (firstRun) { 1025 if (firstRun) {
902 showFirstRunNotification(); 1026 showFirstRunNotification();
903 } 1027 }
904 } 1028 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698