OOUI

ooui is a modern UI toolkit for browsers.

It drives the dialogs, toolbars, and much of the other chrome of the VisualEditor. disentangles it from VE.

How
ooui will be a stand-alone distribution, like MediaWiki itself.

Somehow it will be incorporated into MediaWiki core, like jQuery

Dependencies
For a developer, just depend on  in ResourceLoader.

Initially ooui will be just one module, unlike the many RL modules of, say, jquery.ui

ooui has a lightweight classing model for UI elements, which it gets from

Assumes jQuery

It doesn't assume MediaWiki, not even MediaWiki localization.

oojs
oojs provides
 * class inheritance
 * class mixin
 * event emitters (similar to Nodejs's emitter)
 * mix this in and you get
 * for receiving emitted events, and  to stop receiving
 * to connect multiple names of events to your on handlers, also binding  correctly. It's a nice shortcut, and there's a corresponding   to unplug
 * registries for symbolic names of things
 * Factories which create objects from these symbolic names.
 * plus some utilities.

Event emitter
In VE almost everything is an event emitter.

connect examples
OO.uiTool.js

this.toolbar.connect( this, { 'updateState': 'onUpdateState' } );

Hooks you up to updateState events from a toolbar.

Destruction
Call  from event emitter and you won't get any more events, also helps with GC (without something like this if your handler is bound to some event object then it won't be GC'd even when it goes out of scope).

You usually call disconnect in destroy method.

Registries and factories
Just a wrapper over a map, similar to

E.g. a registry of HTML tag names associated with a model, multiple tag names

Allows you to associate more than one key with the same value, e.g. aliasing.

A factory inherits from registry (using oojs inheritance).

When you register anything in a factory, the value must be a function, and the factory comes up with the key name for it.

See OO.Factory jsduck documentation

function Foo.static = { name: 'whatever' }

Our convention is lowerCamelCase.

Factory uses the symbolic name Projects tend to have multiple factories.

ooui has the notion of a tool factory, E.g. when you construct a toolbar you tell it what factory to get tools from. You only talk about toolbar components by their registry names.


 * First toolbar button created with name 'bold',


 * then if you want to have a different kind of bold button behavior, you can set a different tool.

Useful e.g. the 'link' button in VE has a simple implementation that inserts a plain URL, but mw-ve code supplies a different button class for the same registry name 'link' that can create a Page Name link.

ve.ui.LinkInspector.js function definition creates class (and the constructor), then specifies static.name = 'link';

Later on, ve.ui.inspectorFactory.register( ve.ui.LinkInspector );

A factory inherits from registry, inferring the name, and then knows how to create.

When someone clicks link (using the InspectorFactory) with 'link' in it, it gets the registered inspector for that from the factory.

But then in ve.ui.MWLinkInspector.js, it creates its own class, but by specifying the same static.name, any reference to that from the factory will now use this.

More on factory
Factory accepts any string for names.

ve adds a bunch more, e.g. it has ve. ve uses factories for things other than UI, e.g. a paragraph node is handed to a factory, not really about UI stuff.

Factory isn't for simple things like buttons, but information driving a UI where you can select the right UI configuration. They create an API. An example of creating a factory would be to create your own toolbar.

ooui
We've talked about the elements of oojs above because it's a relatively small library.

ooui is big and does a lot.

Example runthrough
As an introduction, let's consider creating a dialog. The Flow extension displays comments on a topic, and there are buttons you can click to [Hide] and possibly [Delete] and [Suppress] a comment. When you click one of these buttons, so a modal dialog appears with a text field in which you enter a reason why you Hid/Delete/Suppressed the comment.

Building the dialog

 * 1) Create your own subclass of OO.ui.Dialog (see jsduck)  (was ve.ui.Dialog)
 * 2) * mw.flow.ui.ReasonDialog

e.g. ve.ui.BetaWelcomeDialog

define your constructor, in this case, override config using $.extend (By the way the VEcode for this aliases jQuery extend and other functions, but not required)

config
jsduck convention @cfg for the configuration object.

You modify config to get parents do what you want. Convention is it's just static config values.

E.g. flow could have flowReasonType: 'suppress' in config.

then call parent constructor, which also takes the config. (and Dialog's parent, OO.ui.Window, also has config &mdash; it's config all the way down).

Convention is your required constructor parameters come first, then config object (if any), since config itself is optional.

One config thing really low-down is OO.ui.Element, the base class. It has its own config parameters, including  (to come)

Element
Element has a .$ property, which is the jQuery/DOM thing you get.

So after calling parent, your object has a $ property (by default a ), and it's an unattached DOM element.

When returned to your caller, the caller can use jQuery functions like  to put this .$ item into the page's DOM (so that it appears).

When someone uses your class
When some creates an instance of the class, the class's initialize gets called, and e.g. in initialize
 * compose the dialog from other things like layout, a [Continue] button, etc.

All dialogs have a head - body - foot.Foot is where you put buttons. These are jQuery things, so. It's a jQuery thing, not an OO.ui element.


 * Why a separate initialize?
 * With the exception of a Window, you can do anything you want in a constructor. initialize is separate because you have to wait for the iFrame to instantiate. More on lifecycle of objects later.

Want the text of the welcome message to be able to scroll, so it doesn't just $body.html( 'Some stuff here'), it creates a OO.ui.PanelLayout eventually putting that in the body with this.$body.append.

The [Save] button in the dialog
Add a [Save] Button, simply new OO.ui.PushButtonWidget, first and only constructor param is the config, where you say 'label': ''SomeApproachToL10n'.msg( 'name of message') 'flags': ['primary']

continueButton has to do something, so you just. Normally you'd just have a single string in here giving your 'onContinueClick' function name, not an array, and lower-down you would write. Because on close you specify an action signifying (to you, the writer of the dialog) whether to accept what happened, so you can decide whether to abort or toss.

Because you have to pass a parameter, can't just specify 'close'; the array is a shortcut for.

At bottom of class, stick the class in the dialog factory (see later).

Window lifecycle
Window has
 * construct (doesn't do much since have to wait for initialize )
 * initialize
 * each time the thing is used (i.e. before, can happen multiple times)
 * onSetup
 * then visible
 * onOpen
 * onClose
 * then invisible

They're pre-bound by parent class.

Actual object instantiation
When does this dialog appear? Most likely in response to some handler, e.g. the [Delete] button's click handler,

To make the dialog modal, you put it in an overlay div, and such. ooui is a robust system, so it solves this properly, at the cost of overhead.

windowSet
windowSet is a modal dialog manager, ensures only one is active at any time, handles the overlay.

You would have a global-ish mw.flow.windowSet created in UI setup and attach this to the DOM somewhere.

Assuming we have more than one kind of dialog, the same early-on initialization would instantiate a dialog factory.

mw.flow.dialogs = new OO.ui.DialogFactory;

no parameters.

This allows the dialog Class code at end to  mw.flow.dialogs.register( mw.flow.ReasonDialog );

is the thing that adds to the page.

back to the window set, still in initialization setup
mw.flow.windowSet = new OO.ui.WindowSet( mw.flow.dialogs ) $( 'body' ).append( windowSet.$ );

back to the button
The click handler for a Hide button is just mw.flow.windowSet.open( 'reason', { 'type': 'hide', 'author': 'Roan' } )

where the last param is the config.

The way we to change the label for different reasons is in  swap out the label with setLabel

The dialog
Fe button, the dialog has
 * TextInputWidget( { multiline: true } );
 * InputLabelWidget( label: 'Tell Roan why you are censoring him', input: myReasonTextInputWidget
 * a [Save] button

Responding to the button
To do some work, the idiom is 'close, 'apply'

In dialog's onClose if action is 'apply', actually do something, then call parent.close. E.g. MWReferenceDialog.js onDialog, Calling parent, actually have to know parent class to call it. OO.ui.Dialog.prototype.onClose.call( this, action ); N.B. could do OO.ui.Dialog.prototype.onClose.apply( this, arguments ); but often subclasses have different arguments.

And then look at 'action', and do the right thing.

mw.beta.WelcomeDialog had continue button panelLayout.$.append( object );

E.g. in onOpen, could focus the text input.

Smarter dialog handling
If button is conditional, see ReferenceDialog, basic concept is on 'change' event, check if TextInputWidget.getValue then this.submitButton.setDisabled( false );

'change' is smart, doesn't waste time until actual change.