Problem/Motivation

After several months of use, it has become evident that the Toolbar JavaScript code is unstable and buggy in several places. We have been porting the more JavaScript-complex modules to Backbone.js. For example contextual and edit. Toolbar will benefit as well from a structured JavaScript foundation that models state more robustly.

Proposed resolution

Refactor the code onto Backbone.js.

Remaining tasks

None.

User interface changes

None.

API changes

Originally, this issue was intended to introduce a JavaScript interaction layer with the Toolbar. It has evolved into a simple refactoring onto Backbone.js, something we've done for other modules in core. There should be no API changes in this issue.

CommentFileSizeAuthor
#79 drupal-refactor-the-toolbar-1860434-79.patch45.98 KBmarkhalliwell
#79 interdiff.txt929 bytesmarkhalliwell
#66 interdiff_50-to-66.txt3.48 KBjessebeach
#66 toolbar-backbone-1860434-66.patch46.25 KBjessebeach
#66 Drupal_Core__D8__-_Status__1_Local_Change_.png34.09 KBjessebeach
#64 toolbar-backbone-1860434-64.patch46.25 KBjessebeach
#64 interdiff_61-to-64.txt3.48 KBjessebeach
#63 interdiff.txt1.84 KBmarkhalliwell
#62 interdiff.txt864 bytesmarkhalliwell
#61 drupal-refactor-the-toolbar-1860434-61.patch46.03 KBmarkhalliwell
#59 drupal-refactor-the-toolbar-1860434-59.patch46.04 KBmarkhalliwell
#59 interdiff.txt1.85 KBmarkhalliwell
#50 interdiff_48-to-50.txt8.12 KBjessebeach
#50 toolbar-backbone-1860434-50.patch45 KBjessebeach
#48 Windows_7_64__Running_.png15.89 KBjessebeach
#48 toolbar-backbone-1860434-48.patch43.91 KBjessebeach
#48 interdiff_45-to-48.txt10.61 KBjessebeach
#45 toolbar-backbone-1860434-45.patch40.86 KBWim Leers
#45 interdiff.txt7.13 KBWim Leers
#44 toolbar-backbone-1860434-44.patch41.4 KBjessebeach
#39 backbone.patch40.1 KBdroplet
#39 interdiff-do-not-test.patch9.37 KBdroplet
#33 toolbar-backbone-1860434-33.patch40.46 KBWim Leers
#33 interdiff.txt10.38 KBWim Leers
#29 toolbar-backbone-1860434-29.patch37.69 KBjessebeach
#29 interdiff_23-to-29.txt9.31 KBjessebeach
#23 toolbar-backbone-1860434-23.patch38.44 KBjessebeach
#19 toolbar-backbone-1860434-19.patch38.4 KBjessebeach
#17 12nrE.png13.02 KBoresh
#17 12nrK.png10.85 KBoresh
#17 12nrz.png20.4 KBoresh
#15 toolbar-backbone-1860434-15.patch31.91 KBjessebeach
#15 interdiff_14-to-15.txt22.22 KBjessebeach
#14 interdiff_11-to-14.txt889 bytesjessebeach
#14 toolbar-api-1860436-14.patch33.13 KBjessebeach
#11 toolbar_api-1860436-11.patch32.52 KBjessebeach
#11 interdiff_9-to-11.txt5.53 KBjessebeach
#9 toolbar_api-1860436-9.patch31.79 KBWim Leers
#9 interdiff.txt11.23 KBWim Leers
#6 toolbar-api-1860436-6.patch30.9 KBjessebeach
#6 interdiff_4-to-6.txt17.54 KBjessebeach
#4 toolbar-api-1860436-4.patch23.37 KBjessebeach
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers’s picture

I wonder if it'd be appropriate to leverage Backbone here? What's currently in the issue summary obviously related to state, and reacting to state. If captured in a Backbone Model, with the toolbar just being a View, wouldn't that simplify things a lot?

jessebeach’s picture

My fear is that this would require Backbone to load on all admin pages. I think we can get the same behavior with slightly-more-clever JS than what's there now.

Wim Leers’s picture

Many parts of the back-end are slow anyway. Backbone + Underscore is 10.3 KiB. If you use Edit (which you likely do if you're an administrator), you're going to need Backbone + Underscore anyway.
So: does it really outweigh that?

I agree with your POV, I'm just not sure if it's really such a big difference overall.

Wim Leers’s picture

Issue summary: View changes

added a bit about an a11y consideration

jessebeach’s picture

Status: Active » Needs work
FileSize
23.37 KB

The JS in the Edit and Tour modules convinced me that we should be using Backbone in Toolbar. It makes the code much easier to read and less unstable.

This patch ports the toolbar.js code to Backbone.

I'll be further enhancing it with better event hooks for modules to attach behaviors to.

webchick’s picture

Issue tags: +sprint, +Spark

Tagging as something we're working on this sprint.

jessebeach’s picture

This patch completes the Backbone port.

I also start exploring how we might enable communication between the toolbar items so that the state of the toolbar is properly maintained across the disparate modules that have tabs in it.

mgifford’s picture

Please let me know when it is in a good place to test it. Also, any reason not to mark it needs review to get the bot to look it over?

jessebeach’s picture

mgifford, thank you for the attention. It really needs a code review more than a functional test right now. I need someone to check my approach and make sure I'm not off the rails so far.

Wim Leers’s picture

FileSize
11.23 KB
31.79 KB

Changes

  • Doxygen fixes.
  • Drupal.behaviors.toolbar.attach() was very dense due to zero blank lines. It may well be that Drupal uses too much blank lines too often, but here, especially combined with indented chaining, the code becomes very hard to parse for humans.
  • toolbar.js file-level closure didn't include all used global objects (missing: Backbone, document, window). Similar for toolbar.menu.js.
  • I changed StateModel's mql to mqMatches, so that it's a simple boolean instead of a complex object. This simplifies code elsewhere (e.g. no more need for manual triggering of the change event), and reduces the amount of data in the model. Since Toolbar's JS is using only a single media query (to determine tray orientation), I think this should be okay.
  • Drupal.behaviors.toolbar.options was labeled as being "default options", hence I renamed it to Drupal.behaviors.toolbar.defaults.
  • When the tray was oriented vertically manually (i.e. "locked"), and then the page was reloaded, then the tray orientation toggle would still indicate you could switch to vertical. In other words, the locking was not being respected and was being overridden by the media query-based tray orientation.
  • model and view were "toolbar.js closure variables", which means they were available as "kinda globals" for any piece of code within toolbar.js. IMHO this is a bad practice. Especially if you take into account that 1) not all code within toolbar.js needs access to them, 2) the two Backbone Views in particular can then access the model via both this.model and model. This was trivial to fix for view, for model I had to move some things around.

Feedback

  • I think the concept of "expose a limited API via Drupal.toolbar" is good, but I'm wondering why we should enforce this limitedness? For the "handler" functions, wouldn't it be more elegant if we would just have a publicly available Backbone View. E.g., you could add the following to any JS file that loads after contextual.toolbar.js (e.g. tour.js):
    Drupal.contextualToolbar.views.EditToggleView.prototype.onClick = function (event) {
      alert('overridden');
      this.model.set('isViewing', !this.model.get('isViewing'));
    };
    More importantly, are there even valid use cases for the three "handler" functions?
    I think <code>Drupal.toolbar.setActiveItem

    is fine though.

  • AFAICT the only media query that is needed in the JS and must be reacted to, is the one that determines whether the tray orientation should change. This implies the JS can be simplified further, less JS settings can be passed to it (i.e. drupalSettings.toolbar.trayOrientationMQ = "" instead of drupalSettings.toolbar.breakpoints = {})
  • The premise of Drupal.toolbar.setActiveItem does not match with how it's used in tour.module nor contextual.module. In those two modules, making the tab/toggle "active" means activating a mode that does not preclude the menu/shortcuts/user tabs from being active. ToolbarView.refreshTabs() already deals with this correctly (but only by accident), but that's besides the point. The point is that neither of those two modules should be calling setActiveItem() in the first place, AFAICT.
  • ToolbarView.refreshTabs() and ToolbarView.changeOrientation() should be prefixed with underscores, because they're internal/helper methods. This makes it easier for others to navigate the code. Furthermore, I think _render(Tabs|Orientation)() would be better names. In particular changeOrientation() is a poor name because it suggests it will make changes, whereas it's only rendering what the current model state indicates. The comments for them in render() can then also go away; the docs on these private functions should be sufficient.
  • I dislike how the "subtrees" stuff is being handled. It's too vague and disconnected, IMO. Plus, no JSON requests are being made for sublevels — the PHP says "To conserve bandwidth, we only include the top-level links in the HTML.", but in reality, it's all being fetched at once.
+++ b/core/modules/shortcut/shortcut.moduleundefined
@@ -568,7 +568,7 @@ function shortcut_toolbar() {
+            'class' => array('icon', 'icon-shortcut', 'toolbar-tab'),

Why are we having shortcut.module (and user.module, and …) having add a toolbar.module-owned class?

+++ b/core/modules/toolbar/config/toolbar.breakpoints.ymlundefined
@@ -1,3 +1 @@
-narrow: 'only screen and (min-width: 16.5em)'
-standard: 'only screen and (min-width: 38.125em)'
 wide: 'only screen and (min-width: 50em)'

There now is a discrepancy between the (sole) breakpoint in this .yml file versus the ones in toolbar.base.css.

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -46,269 +22,446 @@ var mql = {
+        // Setting the mql won't trigger a change in the model. It needs to be
+        // triggered manually.
+        model.trigger('change:mql');

This was only necessary because mql is an object.

+++ b/core/modules/toolbar/js/toolbar.menu.jsundefined
@@ -12,7 +12,7 @@
   $.fn.toolbarMenu = function () {

Shouldn't this be Drupal-prefixed, i.e. $.fn.drupalToolbarMenu?

jessebeach’s picture

More importantly, are there even valid use cases for the three "handler" functions?

Not that they should be exposed on Drupal.toolbar, no. I moved them into the IIFE scope as named function declarations.

The three handlers respond to events that the toolbar dispatches when its container might need to respond by resizing or positioning other page elements. So the toolbar itself subscribes to these events. Really though, I'd love it if it wasn't pushing and pulling page elements like the body around so much.

// Respond to toolbar events.
$(document)
  .on('drupalToolbarDimensionsChanged.toolbar', Drupal.toolbar.dimensionsChangeHandler)
  .on('drupalToolbarOrientationChanged.toolbar', Drupal.toolbar.orientationChangeHandler)
  .on('drupalToolbarTrayChanged.toolbar', Drupal.toolbar.trayChangeHandler);
AFAICT the only media query that is needed in the JS and must be reacted to, is the one that determines whether the tray orientation should change. This implies the JS can be simplified further, less JS settings can be passed to it (i.e. drupalSettings.toolbar.trayOrientationMQ = "" instead of drupalSettings.toolbar.breakpoints = {})

Good change. Originally we implemented this configuration using the breakpoint system. That hasn't been integrated all that well yet and I"m not sure if it gives us any advantages over just hard-coding this one value. Time will tell.

The premise of Drupal.toolbar.setActiveItem does not match with how it's used in tour.module nor contextual.module. In those two modules, making the tab/toggle "active" means activating a mode that does not preclude the menu/shortcuts/user tabs from being active. ToolbarView.refreshTabs() already deals with this correctly (but only by accident), but that's besides the point. The point is that neither of those two modules should be calling setActiveItem() in the first place, AFAICT.

I'd like the toolbar to know what items are active, even if the rest of the items aren't automatically deactivated. This would allow us to, let's say, deactivate quick editing if the Overlay is activated (or at least put up a warning about work being lost).

Maybe rather than passing the trigger that does the activating, we can express activation through events and callbacks. I'll think about this more. Agreed that it's not ideal yet.

ToolbarView.refreshTabs() and ToolbarView.changeOrientation() should be prefixed with underscores, because they're internal/helper methods.

What about these methods as well?

onTabClick
onOrientationToggleClick
onMediaQueryChange
setHeight

Is the suggestion to put an underscore before any method of a View or Model that isn't part of that object's defined API? I can follow that pattern, otherwise I'd rather not put any underscores and just let the comments describe the methods and their purpose.

I dislike how the "subtrees" stuff is being handled. It's too vague and disconnected, IMO. Plus, no JSON requests are being made for sublevels — the PHP says "To conserve bandwidth, we only include the top-level links in the HTML.", but in reality, it's all being fetched at once.

Me too, this has always been a separate todo, but never in an issue. I've added it #1927174: Complete the work to make the Toolbar administration menu sub tree rendering more efficient.

+++ b/core/modules/shortcut/shortcut.moduleundefined
@@ -568,7 +568,7 @@ function shortcut_toolbar() {
+ 'class' => array('icon', 'icon-shortcut', 'toolbar-tab'),
Why are we having shortcut.module (and user.module, and …) having add a toolbar.module-owned class?

An excellent point. This is so the toolbar can discover the tabs and apply behaviors through delegation. This is a bit magical though. Maybe we need the modules to explicitly register their "tab" with the toolbar through an API call. I kinda dislike the overhead, but it does mean less magic.

+++ b/core/modules/toolbar/config/toolbar.breakpoints.ymlundefined
@@ -1,3 +1 @@
-narrow: 'only screen and (min-width: 16.5em)'
-standard: 'only screen and (min-width: 38.125em)'
wide: 'only screen and (min-width: 50em)'
There now is a discrepancy between the (sole) breakpoint in this .yml file versus the ones in toolbar.base.css.

The CSS should be agnostic to when the trays flip from horizontal to vertical and instead be keyed off the presence of classes, which the configured breakpoint controls by determining when those classes are swapped. I'll double check this. The rest of the media queries in the CSS should be concerned with content presentation, not positioning or orientation.

+++ b/core/modules/toolbar/js/toolbar.menu.jsundefined
@@ -12,7 +12,7 @@
$.fn.toolbarMenu = function () {
Shouldn't this be Drupal-prefixed, i.e. $.fn.drupalToolbarMenu?

Yes.

jessebeach’s picture

Mostly this is code that responds to Wim Leers' comments in #9. I want to make sure that further work continues from the same base patch.

There is one change I made to a change Wim made that I want to point out so it doesn't slip in silently.

+    var defaults = this.defaults;
     $(context).find('#toolbar-administration').once('toolbar', function () {
-      var options = $.extend(Drupal.behaviors.toolbar.defaults, drupalSettings.toolbar);
-
+      // Create a reference to the defaults in this function scope.
+      var options = $.extend(defaults, drupalSettings.toolbar);

I removed the reference to Drupal.behaviors.toolbar.defaults and replaced it with a this reference. I prefer not to reach out into the global object where possible to get variable values that are defined inside the scope.

nod_’s picture

Issue tags: +JavaScript

tag

webchick’s picture

Priority: Normal » Major

In talking to Jesse today, it sounded like this was pretty necessary refactoring, so escalating to major.

jessebeach’s picture

reroll.

jessebeach’s picture

Title: Provide a JavaScript API to control the state of various Toolbar components and triggered events to respond to state changes. » Refactor the Toolbar JavaScript to use Backbone; fix several poorly functioning behaviors
FileSize
22.22 KB
31.91 KB

This patch refactors the JavaScript onto Backbone. Many behavioral quirks are resolved with this refactor. For instance, under the current code, it is possible to have more than one tab be activated. By mapping the view to a state model in Backbone, only one tab can be active at a time and this issue is resolved.

Several superfluous configurations are removed in this patch.

I tried not to do too much in this patch, focusing primarily on the refactor to Backbone so that the code follows patterns established in Contextual and Edit modules.

We are addressing CSS cleanup in #1847314: Reduce the dependency on JavaScript for the toolbar to display properly.
Further Toolbar API refinements will be addressed in #1894964: Make the Toolbar PHP and JavaScript API more flexible so that it enables contrib to leverage it.

jessebeach’s picture

Status: Needs work » Needs review

Setting to needs review.

oresh’s picture

Status: Needs review » Needs work
FileSize
20.4 KB
10.85 KB
13.02 KB

- Error on line 47 of toolbar.js - you didn't remove ;
- For wide screen toolbar doesn't push the content to the right ( but it used to )
- On mobile screen toolbar tray doesn't stick to the top, so there appears a white space.
- For my local build it takes about a second for script to run, and you get a fast toolbar pop, when js is executed. Can we replace it with some smoother animation ( if it won't kill the performance )
- And the toolbar switches to mobile for 285px, thought iPhone has 320 and it's more convenient to make that width for breakpoint :) And for mobile screen toolbar now stick to the bottom and there's even a larget gap. (see screenshots)

Code:
- Drupal.toolbar.setActiveItem($('.js .toolbar .bar .contextual-toolbar-tab'));
better reduce to $('.toolbar .contextual-toolbar-tab'). this is already js you don't need js class.
- for // Broadcast model changes to other modules // can you cache the $(document) variable? you use it 4 times
- for // Implements Backbone.View.prototype.initialize() // may be get rid of this.model repetition?
- var barHeight = this.$el.find('.bar').outerHeight(); change to var barHeight = this.$el.find('.bar').outerHeight(true);
I think overwise it won't work properly in IE

Everything else looks great! But it's hard to review large patches )

jessebeach’s picture

Thank you for the feedback oresh! I'm looking through your comments now.

jessebeach’s picture

oresh, I'm fairly certain after some forensics that the issues you list in #17 were caused by me uploading the wrong version of this patch. I'm very sorry about that. I was very surprised to see so much going wrong with it!

This is the correct version.

oresh’s picture

Status: Needs work » Needs review
oresh’s picture

Status: Needs review » Needs work

@jessebeach, it took me some time to return and review the patch.
But now i can't apply it - probably somebody changed the js of the toolbar )
Can you please reroll for latest core changes? Thx!

tim.plunkett’s picture

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -44,290 +16,473 @@ var mql = {
-            $('#toolbar-link-' + id).after(subtrees.id);

It only doesn't apply because this line changed, it went from subtrees.id to subtrees[id]

jessebeach’s picture

oresh, this is a reroll of #19 that takes the changed mentioned by tim.plunkett in #22 into account.

You can force git to apply as much of a patch as it can manage by passing the --reject option when you call git apply. git will create a .rej file that contains the portions of the patch that failed to apply. In this case, the failed portions were significant because this is a large refactor, so it would have been tricky to deal with the collision.

webchick’s picture

Status: Needs work » Needs review

Moving back to needs review.

oresh’s picture

Status: Needs review » Reviewed & tested by the community

Awesome!
Works perfectly now! Covered all issues from #17.

Status: Reviewed & tested by the community » Needs work
Issue tags: -JavaScript, -sprint, -Spark, -toolbar-followup

The last submitted patch, toolbar-backbone-1860434-23.patch, failed testing.

jessebeach’s picture

Status: Needs work » Needs review
Issue tags: +JavaScript, +sprint, +Spark, +toolbar-followup

#23: toolbar-backbone-1860434-23.patch queued for re-testing.

droplet’s picture

Really hard to review this patch, mixed jQuery event, Drupal behaviors and Backbone way :)

Anyway, after applied patch:

DrupalBehaviorError: attach ; contextualToolbar: Drupal.toolbar.setActiveItem is not a function
http://127.0.0.1/drupal8x/core/modules/contextual/contextual.toolbar.js?...
Line 64

+++ b/core/modules/toolbar/toolbar.moduleundefined
@@ -628,6 +630,7 @@ function toolbar_library_info() {
       array('system', 'drupal.debounce'),

when and where use denounce ? (and why not just use underscore _.debounce)

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -1,358 +1,493 @@
+        locked: JSON.parse(localStorage.getItem('Drupal.toolbar.trayVerticalLocked')) || false,

default value set in Model.defaults ?

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -1,358 +1,493 @@
+        .on('drupalViewportOffsetChange.toolbar', function (event, offsets) {
+          model.set('offsets', {
+            top: offsets.top,
+            right: offsets.right,
+            bottom: offsets.bottom,
+            left: offsets.left

model.set('offsets', offsets) ???

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -1,358 +1,493 @@
+          $('body').css('padding-top', offsets.top);
...
+        });

shouldn't it a change event in Backbone.view ? (above code trigger a change event)

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -1,358 +1,493 @@
+      model
+        .on('change:orientation', function (model, orientation) {
+          $(document).trigger('drupalToolbarOrientationChange', orientation);
+        })
+        .on('change:activeTab', function (model, tab) {
+          $(document).trigger('drupalToolbarTabChange', tab);
+        })
+        .on('change:activeTray', function (model, tray) {
+          $(document).trigger('drupalToolbarTrayChange', tray);

Why not inside View.initialize

and more..

jessebeach’s picture

DrupalBehaviorError: attach ; contextualToolbar: Drupal.toolbar.setActiveItem is not a function
http://127.0.0.1/drupal8x/core/modules/contextual/contextual.toolbar.js?...
Line 64

Fixed this error.

when and where use denounce ? (and why not just use underscore _.debounce)

Yes, this is no longer needed. The debounce was moved to Drupal.displace. To answer the question of why not use Underscore's debounce, We had been trying to avoid making our scripts dependent on Underscore. That was before we reached a general consensus on using Backbone, which itself requires Underscore. Now that this is generally available, I think we can consider dropping our custom displace method and using Underscore.

model.set('offsets', offsets) ???

Right :) Much better. I updated it.

Ok, that was the straightforward stuff. Now to the meatier comments.

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -1,358 +1,493 @@
+          $('body').css('padding-top', offsets.top);
...
+        });

shouldn't it a change event in Backbone.view ? (above code trigger a change event)

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -1,358 +1,493 @@
+      model
+        .on('change:orientation', function (model, orientation) {
+          $(document).trigger('drupalToolbarOrientationChange', orientation);
+        })
+        .on('change:activeTab', function (model, tab) {
+          $(document).trigger('drupalToolbarTabChange', tab);
+        })
+        .on('change:activeTray', function (model, tray) {
+          $(document).trigger('drupalToolbarTrayChange', tray);

Why not inside View.initialize

Here's my understanding of a Backbone View. Each one is associated with an element -- its el property. A View controls this element and its descendants. A View should never change DOM elements outside its view, such as the body. Once it does, we lose the power of the View as a contained controller of data output.

Your comments made me realize that I should be treating the changes to the body element as just another view of the Toolbar model. Rather than handling changes to the toolbar and their effects on the body through events, we can just attach a view to the body and associate this view with the same model that drives the toolbar. I've made this update in the patch and I really like this. It continues the pattern of driving visual changes from the same model source.

The distinction between the View/Models declared in Drupal.toolbar and the code in Drupal.behaviors.toolbar is that Drupal.toolbar properties are like classes whereas code in Drupal.behaviors.toolbar represents the instances of those classes in a context. We want to keep the Views and Model definitions clean of context as much as possible so that we can write unit tests against it. That's why we shouldn't set this code

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -1,358 +1,493 @@
+        locked: JSON.parse(localStorage.getItem('Drupal.toolbar.trayVerticalLocked')) || false,

in a model default, because this depends on the context of the implementation and it makes writing tests difficult. We don't really care that the locked value came from local storage -- we care that the model deals with the value correctly and that views that refer to it render correctly.

Your comments were very helpful! They gave me more clarity on how to organize the code. What do you think of my explanations? Do they make sense?

jessebeach’s picture

Also, we might eventually have an auralToolbarView that is responsible for implementing Drupal.announce to provide audio updates to model changes rather than visual. This is another reason to keep the defaults and contextual values of the models and views outside of any one View definition.

nod_’s picture

I'm all for using backbone if it makes the code better and if it doesn't increase the size too much.

I minified toolbar.js before and after the patch (so no whitespace or comments). Before patch: 4.3kb, after: 6.0kb. that's a 130% increase. I would have expected backbonned code to be smaller. I haven't looked too much into it yet. If someone can point out the reason of this increase before I get to it that'd be nice.

Wim Leers’s picture

Assigned: Unassigned » Wim Leers

#31: minified size delta confirmed. When gzipped, the difference is "only" 21% though, not 30% (when not gzipped, I get 37–39% depending on the minifier, so I'd love to know what minifier you're using :)).
Let's see if we can reduce that difference.


Currently working on a deep review, hence assigning to myself.

Wim Leers’s picture

Changes (in attached reroll)

  1. Simple 80 cols docs fixes and minor code improvements.
  2. Reordered the code in attach() a bit, so that all initializations happen first, and all event binding/model updating at the end.
  3. Stop using jQuery.extend({}, options) in BB view initializations; instead explicitly pass in options.strings</strong>; the views don't need the <code>options.breakpoints.
  4. The current calls to Drupal.announce are broken in many ways: when the page is loaded, the orientation is announced, while it should not, and if it's vertical upon page load, it's announced twice. The tray announcement is also broken in that it announced something like toolbar-item--2-tray tray opened, which is of course not very informative. I introduced a Drupal.Toolbar.AuralToolbarView to cleanly implement this, made the #heading property for the tray required instead of optional and am using that as the tray name instead, which is more consistent with what the screen reader user gets to hear *and* it's much easier to understand.
    In doing so, I'm afraid the minified code size has increased by another 500 bytes, to 6.5 KB.

Concerns

  1. I understand that setting the top padding on the body element is essential, but all the classes that we're setting there, are all those also really necessary?
  2. In contextual.toolbar.js, Drupal.MODULENAME.(models|views) refers to the "classes", in toolbar.js they currently refer to "instances". We should standardize on one.
  3. (Continuing on the previous.) Generally, I think it makes sense for "external" code to be able to find BB models "instances" and listen to them. This makes sense, especially because that means we'd rely less on DOM events, which are inherently slower and less granular. However, I don't see a clear rationale for being able to access BB views "instances". What's the rationale for this? If another module wants to override BB views, then it would probably override the view "class"? (That's also what I've done in contextual.toolbar.js.)

Explanation for the increased size (even after minification)

  1. The previous codebase was impossible to test. The current codebase is very much testable. Instead of having everything "hardcoded", the code now consists of independent components with very clearly separated responsibilities (the BB models & views). Jesse already explained this at the bottom of #29. Because of the better separation, an increase in size is unavoidable.
  2. Many small things have been fixed (as per the issue title), like buggy orientation toggle, etc. That means more code.
  3. However, the nice thing is: the Backbone models by definition contain only *essential* data. For example, Drupal.Toolbar.ToolbarModel contains only 7 "attributes". If you think you can build the same functionality with *less* code, then instead of rewriting everything, you can just write more efficient Backbone views :)
Wim Leers’s picture

As per change 4 in #33, this is probably warranted.

Status: Needs review » Needs work

The last submitted patch, toolbar-backbone-1860434-33.patch, failed testing.

Wim Leers’s picture

Test failures due to #heading still being assumed to be optional in the tests. Easy fix. Let's first see whether Jesse agrees with my direction :)

jessebeach’s picture

Stop using jQuery.extend({}, options) in BB view initializations; instead explicitly pass in options.strings; the views don't need the options.breakpoints.

My thinking around using extend here is it provides a way for settings to get into a View from contrib modules.

See this line in toolbar.js above the View instantiation:

var options = $.extend({}, this.defaults, (drupalSettings.toolbar || {}));

The defaults and settings are merged into a new options object. If a contrib module overrides one of the View definitions, it will still get instantiated in the same way and get any new options because we're just dumping the whole object in and not plucking specific properties in the attach function.

Wim Leers’s picture

#37: Fair enough :)

droplet’s picture

my thoughts:

- Keep all defaults value in the Backbone Model.
- Passing all of them into Model rather than Views. So, you can add more custom value and make use of BB.
- MenuView, I converted to use a Backbone.Collection. It lets you easily to extend and interact with BB , eg. reordering (collection.sort)
- move some events to ToolbarView view. This is teh main view of Toolbar, it should take over of events and let you override stuff with BB.
- minor code improvements.

// Add subtrees.
// @todo Optimize this to delay adding each subtree to the DOM until it is
// needed; however, take into account screen readers for determining
// when the DOM elements are needed.

I planed to drop a placeholder at beginning, and add a event handler to insert HTML code.
However seems like jQuery.once working based on element ID. I couldn't find a PHP way to change the subtress menu ID. any hint to me, thanks :)
For any purpose, I though it should be a unique ID/Class for each menu.

Furthermore changes:
- hack PHP toolbar_subtrees_jsonp and simplify JS Drupal.toolbar.setSubtrees.done

Cheers :)

oresh’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, backbone.patch, failed testing.

jessebeach’s picture

droplet, that's for proposing these ideas! We're actively experimenting with our Backbone patterns in Drupal. I have some questions for you below.

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -18,97 +18,78 @@
-      var menuModel = that.models.menuModel = new Drupal.toolbar.MenuModel();
-      that.views.menuView = new Drupal.toolbar.MenuView(
-        $.extend({
-          el: $(this).find('.toolbar-menu-administration').get(0),
-          model: menuModel
-        }, options)
-      );

I'd like to understand better what advantage a Collection gives us in the case of the Toolbar. We only have 2 models and they not really related -- not in the way that a Library Collection would be related to Book Models. I don't believe we need to act on model changes as a group, which is what a Collection would do for us. What use cases are you anticipating that this change would allow us to support?

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -18,97 +18,78 @@
-  // Default options.
-  defaults: {
-    breakpoints: {
-      'module.toolbar.wide': ''
-    },
-    strings: {
-      opened: Drupal.t('opened'),
-      closed: Drupal.t('closed'),
-      horizontal: Drupal.t('Horizontal orientation'),
-      vertical: Drupal.t('Vertical orientation')
-    }
-  }
+  // A map of View instances.
+  views: {},
+
 };
 
 /**
@@ -158,6 +139,15 @@

@@ -158,6 +139,15 @@
         right: 0,
         bottom: 0,
         left: 0
+      },
+      breakpoints: {
+        'module.toolbar.wide': ''
+      },
+      strings: {
+        opened: Drupal.t('opened'),
+        closed: Drupal.t('closed'),
+        horizontal: Drupal.t('Horizontal orientation'),
+        vertical: Drupal.t('Vertical orientation')
       }
     }

One of the reasons we've been keeping the defaults in the behaviors object is our recent pattern of splitting the visual view from, let's say, aural views or keyboard views, which might need those strings as well. This patch really lays out the division of Views well #1971108-20: Convert contextual.js to use Backbone (and support dynamic contextual links).

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -451,47 +457,48 @@
     render: function () {
-      var subtrees = this.model.get('subtrees');
+      var that = this;
       // Add subtrees.
       // @todo Optimize this to delay adding each subtree to the DOM until it is
       //   needed; however, take into account screen readers for determining
       //   when the DOM elements are needed.
-      for (var id in subtrees) {
-        if (subtrees.hasOwnProperty(id)) {
-          this.$el
-            .find('#toolbar-link-' + id)
+      _.each(this.collection.models, function(model){
+        if(model.get('collapsible')) {
+          that.$el
+            .find('#toolbar-link-' + model.id)
             .once('toolbar-subtrees')
-            .after(subtrees[id]);
+            .after(model.get('html'));
         }
-      }
-      // Render the main menu as a nested, collapsible accordion.
-      if ('drupalToolbarMenu' in $.fn) {
-        this.$el
-          .find('> .menu')
-          .drupalToolbarMenu();
-      }
+      })
+
+      this.trigger('drupalToobarMenu');

I don't think we can change the code in this way. The subtrees are loaded through an asynchronous AJAX request. We can't guarantee that we'll have the link data when this code is executed. Using the jQuery Promise object let's us set up the framework for inserting the links once we have the data, but it doesn't matter when that data arrives. Do the changes you support here still support that asynchronous loading? Maybe I'm just not grokking it.

droplet’s picture

I'd like to understand better what advantage a Collection gives us in the case of the Toolbar. We only have 2 models and they not really related -- not in the way that a Library Collection would be related to Book Models. I don't believe we need to act on model changes as a group, which is what a Collection would do for us. What use cases are you anticipating that this change would allow us to support?

OLD PATCH: All menu sections is one model
NEW PATCH: each menu section is a model

the menuCollection is a set of models of menu sections (Content, Structure, Appearance..etc).

- Collection is more flexible than model (http://backbonejs.org/#Collection)
Lets say when I dynamically add new menu section to toolbar via JS, and sort by "X". I can use http://backbonejs.org/#Collection-comparator directly.

I can find a menu item via http://backbonejs.org/#Collection-findWhere

More than that.

Ideally, Drupal backend should output an array of menu items rather than rendered HTML. Menu items html control by BB.render() & BB templates. smaller JSON response, easier to change the html structure

(Shortcuts-like modules may take advantage of it)

One of the reasons we've been keeping the defaults in the behaviors object is our recent pattern of splitting the visual view from, let's say, aural views or keyboard views, which might need those strings as well. This patch really lays out the division of Views well #1971108-20: Convert contextual.js to use Backbone (and support dynamic contextual links).

All views sharing SAME toolbarModel. I see no reason to pass it into View directly.

I don't think we can change the code in this way. The subtrees are loaded through an asynchronous AJAX request. We can't guarantee that we'll have the link data when this code is executed. Using the jQuery Promise object let's us set up the framework for inserting the links once we have the data, but it doesn't matter when that data arrives. Do the changes you support here still support that asynchronous loading? Maybe I'm just not grokking it.

It's same.

Old way: Drupal.toolbar.setSubtrees.done() trigger Model change event -> Render()
New way: Drupal.toolbar.setSubtrees.done() trigger Collection.reset event -> Render().

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -1,333 +1,547 @@
+      // Render the main menu as a nested, collapsible accordion.
+      this.on('drupalToobarMenu', function(){
+        if ('drupalToolbarMenu' in $.fn) {
+        this.$el
+          .find('> .menu')
+          .drupalToolbarMenu();
+        }
+      });

Developers may change the render() function. Recopy the code to execute $.drupalToolbarMenu(); is meaningless.
This is the main reason I move it out.

Ideally, I'd split Render() into addOne & addAll.

addOne, dynamically insert a menu section
addAll, optimized for render all items at beginning (no reflow)

However, it's too much in CORE, so i leave as it :)

jessebeach’s picture

@droplet, I must first apologize that this patch doesn't include the changes you introduced #39. I can't yet grok the advantages of using a Collection for thesub menu items when don't really support sub-menu item fetching from the server-side. Although I think this will be a great enhancement, it strikes me as pre-optimizing right now. Plus, it took me all day to refactor what I had in #33 on the many changes to core since last we looked at this patch, so I simply ran out of time.

This patch is based on a couple refactorings to Toolbar CSS (see below for exact issue comments). I didn't want to have to adjust the CSS in this patch, since it's just about JavaScript, so I prepared those other two patches today.

I modeled the update on the JavaScript in Contextual module, which represents our best patterns to date in Drupal core JS. We're not trying to introduce any new behavior here. The goal is to rationalize the JavaScript and remove some quirks that exist in the D8 codebase today, like the incorrect highlighting of active tabs.

Based on (apply from top to bottom):
0329b221e3c812de72c76a9a9205dc53d63ded93
#1847314-24: Reduce the dependency on JavaScript for the toolbar to display properly
#1938044-66: Prefix all toolbar classes to prevent theme clashes

Wim Leers’s picture

I tested all permutations of actions I could think of, in combination with the Overlay module, but only in Chrome. The changes here should work cross-browser.

UX review

Found a bug: 0. do this on a laptop/desktop, 1. make viewport as wide as possible, 2. open "menu" tray, 3. toggle tray orientation to "vertical" (now Drupal.toolbar.trayVerticalLocked = true is set in localStorage), 4. reload the page (note that the localStorage entry remains unchanged), 5. attempt to toggle back from vertical to horizontal: the first try will fail, the second will succeed.

What I also think to be a bug: when the viewport is as narrow as possible and the tray orientation *must* be vertical, the orientation toggle is still visible and allows me to switch the orientation to horizontal. When I do that, the end result looks broken. IMHO this should be forbidden.

Finally, I find it rather confusing that the "Home" tab is always pressed on the home page. If you click the "Menu" tab while you're on the homepage, it looks like two tabs are active simultaneously.

Code review

In the attached reroll: fixed a bunch of typos in the docs, made some minor changes and renamed a few functions for better consistency.

Further feedback:

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -45,329 +51,520 @@ var mql = {
+          // Curry the model and the label of the media query breakpoint to the
+          // mediaQueryChangeHandler function.
+          mql.addListener(Drupal.toolbar.mediaQueryChangeHandler.bind(null, model, label));

I was going to say this won't work in IE9, but AFAICT it will: http://kangax.github.io/es5-compat-table/#Function.prototype.bind

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -45,329 +51,520 @@ var mql = {
+    // Update the page and toolbar dimension indicators.
+    //updatePeripherals();

This needs to be either removed or used.

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -45,329 +51,520 @@ var mql = {
+      // The active toolbar item. All other items should be inactive under

s/item/tab/ ? The description does not match the variable name.

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -45,329 +51,520 @@ var mql = {
+      // Indicated whether the toolbar is positioned absolute (false) or fixed
+      // (true).
+      isPaneled: false,

"isPaneled" hardly matches the description. Can you think of a more descriptive name?

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -45,329 +51,520 @@ var mql = {
+      // @todo Optimize this to delay adding each subtree to the DOM until it is
+      //   needed; however, take into account screen readers for determining
+      //   when the DOM elements are needed.

… but AFAIK it is *impossible* to detect screen readers. So, do we instead mean "if we detect keyboard navigation"? Or, how is this planned to be done?

In any case, any remaining @todos must have issues for them.

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -45,329 +51,520 @@ var mql = {
+    /**
+     * {@inheritdoc}
+     */
+    render: function () {
+      var $body = $('body');
+      var orientation = this.checkOrientationLock(this.model.get('orientation'));
+      var isOriented = this.model.get('isOriented');
+      var isViewportOverflowConstrained = this.model.get('isViewportOverflowConstrained');
+
+      $body
+        // Apply classes to the body element that reflect the current
+        // orientation of the active toolbar.
+        .toggleClass('toolbar-vertical', (orientation === 'vertical'))
+        .toggleClass('toolbar-horizontal', (isOriented && orientation === 'horizontal'))
+        .toggleClass('toolbar-paneled', (isViewportOverflowConstrained || this.model.get('isPaneled')))
+        // Toggle the toolbar-tray-open class on the body elment. The class is
+        // applied when a toolbar tray is active.
+        .toggleClass('toolbar-tray-open', !!this.model.get('activeTray'))
+        // Apply padding to the top of the body to offset the placement of the
+        // toolbar bar element.
+        .css('padding-top', this.model.get('offsets').top);

This is all rather puzzling at first sight: why would the toolbar need to touch the body element?

I think we must very explicitly document here that the overall architecture for the toolbar's CSS requires these classes to be set on the body element. It should possibly here, and otherwise elsewhere, also explain why we let the JS handle media queries rather than the CSS.


Based on (apply from top to bottom):
37e0bf87ed682b4de3b8570416d98c226c912a87
#1847314-24: Reduce the dependency on JavaScript for the toolbar to display properly
#1938044-66: Prefix all toolbar classes to prevent theme clashes

falcon03’s picture

I was going to test this patch, but it doesn't apply! :(

In the last few days, I've noticed something weird with the toolbar: after expanding an item (let's say "menu"), the drupal.announce works as expected, but Voiceover does not intercept the element that appears in the page unless you reload it. I'm not sure that this is not a bug in my setup, tough. I'm using Mac OS X 10.8.3+Safari.

@Wim Leers, @Jessebeach, what do you think about the bug I mentioned? Could it be related to the toolbar JS? If so, will it be fixed in this issue or should I file another (at that point critical) bug report against the toolbar module?

jessebeach’s picture

Status: Needs work » Postponed
jessebeach’s picture

Rerolled and addressed input from Wim Leers in comment #45.

Found a bug: 0. do this on a laptop/desktop, 1. make viewport as wide as possible, 2. open "menu" tray, 3. toggle tray orientation to "vertical" (now Drupal.toolbar.trayVerticalLocked = true is set in localStorage), 4. reload the page (note that the localStorage entry remains unchanged), 5. attempt to toggle back from vertical to horizontal: the first try will fail, the second will succeed.

Fixed this by introducing a validation step to the call to set for orientation. This was happening because the orientation was getting set to horizontal before it was checked for being locked in a vertical position. Using validate() before set() can occur cleans up the code a bit more.

What I also think to be a bug: when the viewport is as narrow as possible and the tray orientation *must* be vertical, the orientation toggle is still visible and allows me to switch the orientation to horizontal. When I do that, the end result looks broken. IMHO this should be forbidden.

I'm inclined to leave this because (1) it's not destructive and easily reversible and (2) some trays with very little content might display well on a narrow screen in a horizontal orientation.

Finally, I find it rather confusing that the "Home" tab is always pressed on the home page. If you click the "Menu" tab while you're on the homepage, it looks like two tabs are active simultaneously.

Yes, me too. But I don't want to solve that there. There are several design issues opened against the Toolbar. We're going to end up addressing this point before Drupal 8 ships for sure.

Function.prototype.bind is defined in IE9 as far as IE claims.

IE9 development tools showing that Function.prototype.bind is defined as native code.

"isPaneled" hardly matches the description. Can you think of a more descriptive name?

I changed isPaneled to isFixed.

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -45,329 +51,520 @@ var mql = {
+      // @todo Optimize this to delay adding each subtree to the DOM until it is
+      //   needed; however, take into account screen readers for determining
+      //   when the DOM elements are needed.

… but AFAIK it is *impossible* to detect screen readers. So, do we instead mean "if we detect keyboard navigation"? Or, how is this planned to be done?

In any case, any remaining @todos must have issues for them.

Right, this @todo ends up being outdated with the introduction of Promise and the done handler. We do have an issue for this already: #1927174: Complete the work to make the Toolbar administration menu sub tree rendering more efficient.

This is all rather puzzling at first sight: why would the toolbar need to touch the body element?

I think we must very explicitly document here that the overall architecture for the toolbar's CSS requires these classes to be set on the body element. It should possibly here, and otherwise elsewhere, also explain why we let the JS handle media queries rather than the CSS.

Right, lots of comments added. We're using JS to handle media-query handling for two reasons: (1) Using JS let's us leverage the breakpoint configurations and (2) the CSS is really complex if we try to hide some styling from browsers that don't understand media queries. If we drive the CSS from JS, the CSS becomes more robust.

Based on (apply from top to bottom):
e0f675f3b24735df93b03a6983aa7cfc23235322
#1847314-37: Reduce the dependency on JavaScript for the toolbar to display properly
#1938044-72: Prefix all toolbar classes to prevent theme clashes

Wim Leers’s picture

#48 addresses all questions I had in #45.

Only one point I disagree with:

What I also think to be a bug: when the viewport is as narrow as possible and the tray orientation *must* be vertical, the orientation toggle is still visible and allows me to switch the orientation to horizontal. When I do that, the end result looks broken. IMHO this should be forbidden.

I'm inclined to leave this because (1) it's not destructive and easily reversible and (2) some trays with very little content might display well on a narrow screen in a horizontal orientation.

Your points are right, but you omit one big negative point: the fact that in most cases, it looks terribly broken. IMHO it's very simple: when the media query makes the tray default to vertical orientation, then also remove the toggle completely.
Better to prevent "WTF THIS LOOKS WEIRD AND KINDA BROKEN!?!!" than to allow for a feature that would probably be used only very rarely.


+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -235,6 +213,44 @@ Drupal.toolbar = {
+     * Call to set() are not automatically validated in Backbone 1.0. The option

This refers to Backbone 1.0, but we're using Backbone 0.9.2 in current D8.

Is this future proofing?

Also: that's a whole bunch of code. Why not just do set(attributes, {validate:true}) to avoid all this?

Right, lots of comments added. We're using JS to handle media-query handling for two reasons: (1) Using JS let's us leverage the breakpoint configurations and (2) the CSS is really complex if we try to hide some styling from browsers that don't understand media queries. If we drive the CSS from JS, the CSS becomes more robust.

I see the additional comments, but they lack the clarity that this explanation has in #48. Could you maybe make the docs in the JS read more like this explanation?


I again tested this manually, and it's still working great. The bug I found in #45 has indeed been fixed in #48.

jessebeach’s picture

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -235,6 +213,44 @@ Drupal.toolbar = {
+     * Call to set() are not automatically validated in Backbone 1.0. The option

This refers to Backbone 1.0, but we're using Backbone 0.9.2 in current D8.

Is this future proofing?

Yes, we discovered in the backporting of D8 features to D7 (where we're already using Backbone 1.0), calls to set() do not trigger validate unless {validate: true} is passed (I know Wim knows this already :) Just elucidating for folks who review this issue). There are three places in the code where I need to induce validation, so I removed the set() method override and just pass in validate option in those cases.

Your points are right, but you omit one big negative point: the fact that in most cases, it looks terribly broken. IMHO it's very simple: when the media query makes the tray default to vertical orientation, then also remove the toggle completely.
Better to prevent "WTF THIS LOOKS WEIRD AND KINDA BROKEN!?!!" than to allow for a feature that would probably be used only very rarely.

The toggle is now hidden below the wide breakpoint and available above the wide breakpoint. This prevents the user from clicking themselves into a situation of ugliness.

I also changed instances of .find('> .something') to .children('.something').

Wim Leers’s picture

I think this is RTBC now :)

I can't RTBC until #1938044: Prefix all toolbar classes to prevent theme clashes is committed though, until it is committed, this issue needs to continue to be marked as postponed.

Gábor Hojtsy’s picture

Status: Postponed » Needs review
Wim Leers’s picture

Status: Needs review » Needs work
+++ b/core/modules/toolbar/css/toolbar.icons.cssundefined
@@ -1,5 +1,5 @@
- * @file toolbar.toolbar-icons.css
+ * @file toolbar.icons.css

This hunk will make the patch no longer apply to HEAD.

Fix that, and this will be RTBC.

Wim Leers’s picture

Status: Needs work » Reviewed & tested by the community

My bad #53 was a hunk that only existed in the interdiff; it is not in the patch itself. Which means the patch should work just fine. RTBC as per #51.

Status: Reviewed & tested by the community » Needs work
Issue tags: -JavaScript, -Needs accessibility review, -sprint, -Spark, -toolbar-followup

The last submitted patch, toolbar-backbone-1860434-50.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review

#50: toolbar-backbone-1860434-50.patch queued for re-testing.

Status: Needs review » Needs work
Issue tags: +JavaScript, +Needs accessibility review, +sprint, +Spark, +toolbar-followup

The last submitted patch, toolbar-backbone-1860434-50.patch, failed testing.

Wim Leers’s picture

These look like legitimate test exceptions. Jesse, can you reroll?

markhalliwell’s picture

Status: Needs work » Needs review
FileSize
1.85 KB
46.04 KB

Appeared that the toolbar_test module wasn't setting #header which was causing the tests to throw an exception. Went ahead and refactored that theme function to check if #header isset too though.

Status: Needs review » Needs work

The last submitted patch, drupal-refactor-the-toolbar-1860434-59.patch, failed testing.

markhalliwell’s picture

Status: Needs work » Needs review
FileSize
46.03 KB

wh00ps! Didn't mean to change the logic :)

markhalliwell’s picture

FileSize
864 bytes

Interdiff for #59 -> #61, sorry using drush_iq... it happens sometimes :)

markhalliwell’s picture

FileSize
1.84 KB

Here's the interdiff.txt for #50 -> #61.

jessebeach’s picture

I made a slight improvement to the way that displacement is calculated on the initial load of the page, so that the when a horizontal tray is presented, the padding on the top of the body will always be sufficient to push the page content down. There's was a race-condition for this calculation that is eliminated with a call to setTimeout.

The interdiff includes markcarver's test change because it was just easier to roll it that way because of the order that I applied his fix to my dev branch.

markhalliwell’s picture

Status: Needs review » Needs work
+++ b/core/modules/toolbar/js/toolbar.js
@@ -324,6 +318,12 @@ Drupal.toolbar = {
+      window.setTimeout(function () {
+        Drupal.displace(true);
+      });

Delay milliseconds are required on setTimeout(), (not sure about newer browsers/js engines, but for BC this should probably be set to a minimum of 10).

window.setTimeout(function () {
  Drupal.displace(true);
}, 10);
jessebeach’s picture

DERP! Great catch markcarver. In this case, a delay time of zero will be sufficient to put the invocation of the callback function into the next execution cycle.

I just made one change in this patch from #64.

Screenshot of the commit diff. Shows the addition of the argument 0 to the window.setTimeout function

We have an issue to do one more pass at accessibility: #1800614: Improve the responsive toolbar accessibility

I'd like to keep these issues separate, since this issue gets us to a great, improved JavaScript base on which to build the Views to improve the Aural experience of the Toolbar. The Spark team has shown commitment to making these improvements. I can personally promise that I will get these accessibility improvements in during this release cycle.

markhalliwell’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Needs accessibility review

Reviewed with Dreditor, looks good. Nothing standouts out to me, patch passed :)

markhalliwell’s picture

dsnopek’s picture

Did some quick manual testing. Everything seems to be working fine!

droplet’s picture

Quick reviews:

+++ b/core/modules/toolbar/css/toolbar.module.cssundefined
@@ -55,7 +55,7 @@
+body.toolbar-fixed .toolbar-oriented,

@@ -69,13 +69,13 @@ body.toolbar-paneled .toolbar-oriented,
+body.toolbar-fixed .toolbar-oriented .toolbar-bar {
...
+body.toolbar-tray-open.toolbar-fixed.toolbar-vertical .toolbar-oriented {

@@ -175,13 +175,13 @@ body.toolbar-tray-open.toolbar-paneled.toolbar-vertical .toolbar-oriented {
+body.toolbar-fixed .toolbar .toolbar-tray-horizontal {
...
+body.toolbar-fixed .toolbar .toolbar-tray-vertical {

@@ -200,11 +200,11 @@ body.toolbar-paneled .toolbar .toolbar-tray-vertical {
  * the page content away from the edge of the viewport. */
...
+[dir="rtl"] body.toolbar-tray-open.toolbar-vertical.toolbar-fixed {

Can we remove "body." ?

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -45,329 +31,547 @@ var mql = {
+      var $tray = $();

What is it?

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -45,329 +31,547 @@ var mql = {
+      if ($tab.length > 0) {

`if($tab.length)` is fine ?

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -45,329 +31,547 @@ var mql = {
+        var id = $tab.get(0).id;

Access DOM directly ? $tab[0].id

+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -45,329 +31,547 @@ var mql = {
+        this.$el.find('.toolbar-bar').attr('data-offset-top', '');

Not sure if it is essential. It sets an empty value.

Wim Leers’s picture

#70: please don't do "just stopping by for a quick review" reviews on patches that are already RTBC, especially if you're not even bothering to understand what's going on.

RE: "body": this is for good reason, see the comments above.
RE: "$tray": that's for defaulting to an empty jQuery set, it gets filled later on in that function.
RE: "length > 0": besides your suggestion containing a coding standard violation, using "length > 0" is fine: it is more explicit.
RE: "$tab[0] rather than $tab.get(0)": they're identical…
RE: "empty data-offset-top": this is for good reason, see elsewhere in the patch where "data-offset-top" appears.

These sorts of reviews are useful when a patch has not yet been vetted, but at this point in the lifecycle of a patch, these superficial reviews are most unhelpful.

jessebeach’s picture

#66: toolbar-backbone-1860434-66.patch queued for re-testing.

Dries’s picture

This looks good but can we please document the API changes in the issue summary per https://drupal.org/node/2045345. Given that we are past API freeze, API changes need to be approved and documented.

If another Javascript maintainer could review this, that would be great, but not necessary.

nod_’s picture

I reviewed a bunch of times, I'm happy with the code overall.

I'd agree with droplet that tray = $() isn't needed here. It was relevant in previous versions of the code but now the patch sets $tray = this.$el.find('[data-toolbar-tray="' + name + '"].toolbar-tray'); in all cases so the initialisation is not needed.

for .length and .get(0), it's a little different from the rest of core but it's valid use of jQuery API so no big deal.

for the data-offset-top, Drupal.displace uses the fact that the attribute exists and figure out itself the height of the element so even an empty attibute is used. I'm not really found of the behavior but it was discussed in another issue.

maybe another minor nitpick:

  mediaQueryChangeHandler: function (model, label, mql) {
    switch (label) {
      case 'module.toolbar.narrow':
        model.set({
          'isOriented': mql.matches,
          'isTrayToggleVisible': false
        });
        // If the toolbar doesn't have an explicit orientation yet, or if the
        // narrow media query doesn't match then set the orientation to
        // vertical.
        if (!mql.matches || !model.get('orientation')) {
          model.set({'orientation': 'vertical'}, {validate: true});
        }
        break;
      case 'module.toolbar.standard':
        model.set({
          'isFixed': mql.matches
        });
        break;
      case 'module.toolbar.wide':
        model.set({
          'orientation': ((mql.matches) ? 'horizontal' : 'vertical')
        }, {validate: true});
        // The tray orientation toggle visibility does not need to be validated.
        model.set({
          'isTrayToggleVisible': mql.matches
        });
        break;
      default:
        break;
    }
  },

it ought to be an if/else instead of a switch.

RTBC +1

xjm’s picture

Issue tags: +API change

Thanks @nod_! Do you think the minor cleanups of #74 merit a followup?

We just need the API changes documented, then.

nod_’s picture

It's either in this patch or we forget about it.

nod_’s picture

Issue summary: View changes

added some use case notes.

jessebeach’s picture

Issue summary: View changes

change the api heading content

markhalliwell’s picture

Issue tags: -API change

Issue summary was updated by @jessebeach, no API changes.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/toolbar/js/toolbar.jsundefined
@@ -45,329 +31,547 @@ var mql = {
+      var $tray = $();

If this is unnecessary as suggested by #70 and #74 let's remove it... extra lines of code need to maintained and in this case delivered to the client...

markhalliwell’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
929 bytes
45.98 KB

Alright. I went ahead and removed it and initalized it farther down. TBH it really doesn't matter and this is some serious over-nitpicking. The amount of "client performance" saved by this, is extremely negligible. This issue has been set as RTBC several times now with minor (uncessary) nitpicks, setting back to RTBC. As far as the if/else/switch issues, it's fine the way it is (more than two and less than 10 use switch: http://oreilly.com/server-administration/excerpts/even-faster-websites/w...). No need to change it.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed f6ac79e and pushed to 8.x. Thanks!

hass’s picture

Status: Fixed » Needs work

+ opened: Drupal.t('opened'),
+ closed: Drupal.t('closed'),

Need to be Ucfirst per #421118: [Meta] Standardize capitalization on actions and consistency.

effulgentsia’s picture

Status: Needs work » Fixed

But "opened" and "closed" aren't actions, they're states. Here's the code that uses those strings:

onActiveTrayChange: function (model, tray) {
      var relevantTray = (tray === null) ? model.previous('activeTray') : tray;
      var state = (tray === null) ? this.strings.closed : this.strings.opened;
      Drupal.announce(Drupal.t('"@tray" tray @state.', {
        '@tray': relevantTray.querySelector('.toolbar-tray-name').textContent,
        '@state': state
      }));
    }

In other words "Foo tray opened." and "Foo tray closed.".

hass’s picture

Status: Fixed » Needs work

Do not be so nitpicking, please. It's not really about actions only. We are changing all lcfirst strings. :-)

In other words "Foo tray opened." and "Foo tray closed.".

That's not the point. A string Foo tray opened is ucfirst and therefore correct. A string like foo tray opened needs work, too.

markhalliwell’s picture

Status: Needs work » Fixed

@effulgentsia is correct. #421118: [Meta] Standardize capitalization on actions is about single worded "actions" or "operations" (commonly found in tables), this is a completely different scenario and is located towards the end of a string where it shouldn't be ucfirst. If you really feel this should be done differently, we can can come up with a different solution in a separate issue. This was already committed in #80.

Sigh, I retract my statement, @nod_ pointed out that this is only used for accessibility (screen-readers) so it really doesn't matter if it's capitalized in the middle. Created new issue to follow up this "bug": #2052973: Improve translatability of strings in toolbar.js.

hass’s picture

I don't like to translate both and this is why we need to be consistent.

jessebeach’s picture

Removing the sprint tag.

Wim Leers’s picture

Issue tags: -sprint

.

Automatically closed -- issue fixed for 2 weeks with no activity.

Anonymous’s picture

Issue summary: View changes

updated the summary

mgifford’s picture

Issue summary: View changes
Issue tags: +Drupal.announce