Calendar:Event in a Tab/documentation

From MozillaWiki
Jump to: navigation, search

This page provides some high-level documentation for the UI for editing events and tasks (event/task tabs and dialog windows) in Mozilla Calendar / Lightning – namely where the code is located and how it is organized. This page and much of the code it documents came out of the "Event in a Tab" project undertaken by Paul Morris for Google Summer of Code 2016.

Overview

The terrain is not simple:

  • events or tasks
  • read-write or read-only events/tasks
  • editing in a window dialog or a tab
  • XUL or HTML (current or new) implementation

For both tabs and window dialogs the main UI is contained inside an iframe which makes it possible to load either a XUL file or an HTML file into the iframe. Message passing is used to communicate between the inside and outside of the iframe. Toolbar, menubar, and statusbar elements are located outside the iframe.

(The toolbar could eventually be moved inside the iframe at some point. It currently is outside for a more incremental path to a full HTML implementation.)

The boolean pref "calendar.item.editInTab" determines tab or window editing.

The boolean pref "calendar.item.useNewItemUI" determines XUL or HTML implementation.

Whether to open a tab or a window dialog happens in:

 calendar/base/content/calendar-item-editing.js

For a tab, determining XUL or HTML (i.e. which file is loaded into the iframe) also happens in "calendar-item-editing.js".

For a window dialog, determining XUL or HTML happens in the load handler function for the window (in "lightning-item-panel.js"). (This allows us to dynamically determine which file to load into the iframe.)

Read-only items are currently always opened in a window dialog, and this also happens in "calendar-item-editing.js". Eventually, at least for the HTML implementation, it would be good to allow read-only items to open in a tab as well. The XUL file used for the read-only case is:

 calendar/base/content/dialogs/calendar-summary-dialog.xul

Tab implementation code

The tab type for events and tasks is defined in:

 calendar/lightning/content/messenger-overlay-sidebar.js

This tab definition also contains the code that opens, closes, persists, restores, etc. the tabs.

General note on file names and locations

The files under the "base" directory are there for historical reasons. For greater consistency they eventually could/should be moved under the "lightning" directory and renamed (e.g. "calendar-event-dialog.xul" --> "lightning-item-dialog.xul").

Inside the iframe

Inside the iframe things are the same for the tab and window dialog cases.

- XUL and HTML files

In both tabs and window dialogs the main UI is inside an iframe, and its content is provided by one of two files, one for the XUL implementation and one for the new HTML implementation:

 calendar/lightning/content/lightning-item-iframe.xul
 calendar/lightning/content/html-item-editing/lightning-item-iframe.html

- Javascript files

The primary javascript code that is used inside the iframe is located in this file:

 calendar/lightning/content/lightning-item-iframe.js

In that file a boolean const "gNewItemUI" is used to conditionally execute code for either the XUL or HTML implementations. The HTML implementation also uses the following file which produces the UI using React files:

 calendar/lightning/content/html-item-editing/react-code.js

The HTML implementation currently uses the "react.js" and "react-dom.js" source files that are used by devtools, rather than including a separate copy for use by Lightning.

- CSS files

CSS styling inside the iframe is currently provided by this file for the HTML implementation:

 calendar/lightning/themes/common/html-item-editing.css

And for the XUL implementation, this file (along with other OS-specific files):

 calendar/base/themes/common/dialogs/calendar-event-dialog.css

This is done in calendar/base/jar.mn:

 % style chrome://lightning/content/lightning-item-iframe.xul chrome://calendar-common/skin/dialogs/calendar-event-dialog.css

Outside the iframe

Outside the iframe things are fairly different for the tab and window dialog cases.

- XUL files

For the window dialog:

 calendar/base/content/dialogs/calendar-event-dialog.xul

For the tab:

 calendar/lightning/content/lightning-item-panel.xul

For both:

 calendar/lightning/content/lightning-item-toolbar.xul

The "lightning-item-toolbar.xul" file contains content related to the toolbox/toolbar that is used in both the window dialog and tab cases. It is overlaid into the other two xul files.

- Javascript files

For both the window dialog and the tab:

 calendar/lightning/content/lightning-item-panel.js

- CSS files

For the window dialog case these CSS files (along with other OS-specific files) are included in calendar/base/content/dialogs/calendar-event-dialog.xul and also via calendar/base/jar.mn

 calendar/base/themes/common/dialogs/calendar-event-dialog.css
 calendar/base/content/dialogs/calendar-event-dialog.css

For the tab case these CSS files (and additional OS-specific files) are included in calendar/lightning/content/messenger-overlay-sidebar.xul

 calendar/base/themes/common/dialogs/calendar-event-dialog.css
 calendar/base/content/dialogs/calendar-event-dialog.css

Differences between tab and window dialog implementations

The window dialog case is fairly straightforward. The xul file is loaded in a dialog window and the js and css files are used for that xul file in that window.

The tab case is more complicated and differs in several ways. The tab xul file ("lightning-item-panel.xul") is loaded as an overlay of "messenger/content/messenger.xul" in "calendar/lightning/jar.mn":

 % overlay chrome://messenger/content/messenger.xul chrome://lightning/content/lightning-item-panel.xul

That means the tab xul file is immediately loaded in the DOM at TB/Lightning startup. Then when an item tab is opened, the relevant XUL content is cloned and that cloned copy is loaded into the tab. Each item tab has a fresh copy of the XUL content.

The exception is the toolbox/toolbar. The toolbox is part of the XUL content, but when an item tab is opened or activated the toolbar is moved into place (not copied/cloned) where it is visible for that tab. When the tab is closed or deactivated the toolbar is moved back to its original (hidden) location in the DOM. Thus there is always only one toolbar for item tabs. (If there were copies of the toolbar for each item tab that was opened, then any customizations to one of those toolbars would have to be propagated to every toolbar in each of those tabs.) All of this is done in:

 calendar/lightning/content/messenger-overlay-sidebar.js

For javascript this means that the js file (calendar/lightning/content/lightning-item-panel.js) is also loaded at TB/Lightning startup. And a single instance of that code is used for all open item tabs. Thus this code has to take account of which tab it is working with, particularly when passing messages to/from the iframe it contains. This is typically done with gTabmail.currentTab or when that is not sufficient, the id of the iframe element.

Having a single active instance of the js code also means that when switching to/from item tabs, the state of the tab has to be persisted when switching away from the tab and reloaded when switching back to the tab. This state is stored in var gConfig and the persisting and restoring happens in:

 calendar/lightning/content/messenger-overlay-sidebar.js

Load handling for iframe and parent context

In general, when the content of an iframe is defined directly in the XUL or HTML file that contains the iframe, the load event for the iframe's content fires before the load event of its parent context.

However, for both window dialog and tab, the load order is outside-in. The load handler function for the parent context fires first because we only load a file into the iframe in that handler function. This lets us dynamically set the src for the iframe (XUL or HTML) in that load handler function. (This approach is especially needed for the window dialog. Although the same result could be achieved via multiple XUL files, a dynamic Javascript based approach is simpler.)

For a tab, there is actually no load event for the parent context to respond to, like there is with a window. So we call the load handler function from the openTab function in:

 calendar/lightning/content/messenger-overlay-sidebar.js

Another reason that outside-in order is preferable is that having the parent context's load handler fire first also lets us make particular pieces of data available to the iframe as soon as it loads, information that is needed immediately in the load handler inside the iframe (e.g. are timezones enabled?). In this case we don't want the delay of asynchronous message passing and we want to avoid having to 'reach out' to access this data synchronously (using 'parent') from inside the iframe.

So in the parent context's load handler this information is placed at, for example, "iframe.contentWindow.gTimezonesEnabled" where it is accessible inside the iframe. This lets us restrict the access that the iframe has to its parent context to only message passing. At some point this will allow us to make the code inside the iframe run with reduced privileges, like a website in a browser.

State of HTML Implementation

The HTML implementation, based on React.js is still in early stages of development, basically a starting point to build on. For example, saving an event/task does not work yet. When loading existing events or tabs, only some of the data is loaded. The more complicated elements of the dialog still need implementing, like the date picker, etc. The appearance and CSS styling still has a long way to go. That said, the responsive design part works and the interaction with some of the toolbar buttons works (privacy, priority, etc.).

Using React.js means that instead of storing state in DOM elements and querying them for it (or rather, in addition to this, since we still support the XUL implementation), state is centralized in a top-level React component and direct interaction with the DOM elements is not needed. When the data/state changes based on input the UI is automatically updated to reflect those changes.

One of the challenges will be refactoring the code in lightning-item-iframe.js to work with this different approach. Basically, the code needs to be refactored into four categories: 1. code that interacts with the outside of the iframe, 2. code that modifies and/or formats the data to be displayed, 3. code that updates or interacts with the XUL UI inside the iframe, 4. code that updates or interacts with the React/HTML UI inside the iframe. Currently 1, 2, and 3 are usually intertwined.