Introduce the settings dialog UI

* Port ext.popups.desktop/ext.popups.settings.js to settingsDialog.js
  * Blank tests for now. Needs Qunit integration tests.
    * Transform into a factory function for future testing.
  * Saving functionality is commented out, will be removed when immplemented in
    the actions
* Add new incomplete action saveSettings
  * Will perform the saveSettings async tasks and then trigger enabling or
    disabling popups.
* Rename action settingsDialogClosed to hideSettings for consistency

Change-Id: I3936d3a4bc476de16d76025139be09f1798796c4
This commit is contained in:
joakin 2016-12-12 20:26:55 +01:00
parent 2f75b42271
commit 4d5269320f
9 changed files with 213 additions and 276 deletions

View file

@ -75,6 +75,7 @@
"resources/ext.popups/changeListeners/eventLogging.js",
"resources/ext.popups/changeListeners/previewCount.js",
"resources/ext.popups/changeListeners/settings.js",
"resources/ext.popups/settingsDialog.js",
"resources/ext.popups/boot.js"
],
"templates": {

View file

@ -1,186 +0,0 @@
( function ( $, mw ) {
var currentLinkLogData,
/**
* @class mw.popups.settings
* @singleton
*/
settings = {};
/**
* The settings' dialog's section element.
* Defined in settings.open
* @property $element
*/
settings.$element = null;
/**
* Renders the relevant form and labels in the settings dialog
*
* @method render
*/
settings.render = function () {
var path = mw.config.get( 'wgExtensionAssetsPath' ) + '/Popups/resources/ext.popups.desktop/',
choices = [
{
id: 'simple',
name: mw.message( 'popups-settings-option-simple' ).text(),
description: mw.message( 'popups-settings-option-simple-description' ).text(),
image: path + 'images/hovercard.svg',
isChecked: true
},
{
id: 'advanced',
name: mw.message( 'popups-settings-option-advanced' ).text(),
description: mw.message( 'popups-settings-option-advanced-description' ).text(),
image: path + 'images/navpop.svg'
},
{
id: 'off',
name: mw.message( 'popups-settings-option-off' ).text()
}
];
// Check if NavigationPopups is enabled
/*global pg: false*/
if ( typeof pg === 'undefined' || pg.fn.disablePopups === undefined ) {
// remove the advanced option
choices.splice( 1, 1 );
}
// render the template
settings.$element = mw.template.get( 'ext.popups.desktop', 'settings.mustache' ).render( {
heading: mw.message( 'popups-settings-title' ).text(),
closeLabel: mw.message( 'popups-settings-cancel' ).text(),
saveLabel: mw.message( 'popups-settings-save' ).text(),
helpText: mw.message( 'popups-settings-help' ).text(),
okLabel: mw.message( 'popups-settings-help-ok' ).text(),
descriptionText: mw.message( 'popups-settings-description' ).text(),
choices: choices
} );
// setup event bindings
settings.$element.find( '.save' ).click( settings.save );
settings.$element.find( '.close' ).click( settings.close );
settings.$element.find( '.okay' ).click( function () {
settings.close();
settings.reloadPage();
} );
$( 'body' ).append( settings.$element );
};
/**
* Save the setting to the device and close the dialog
*
* @method save
*/
settings.save = function () {
var v = $( 'input[name=mwe-popups-setting]:checked', '#mwe-popups-settings' ).val();
if ( v === 'simple' ) {
mw.popups.saveEnabledState( true );
settings.reloadPage();
settings.close();
} else {
mw.popups.saveEnabledState( false );
$( '#mwe-popups-settings-form, #mwe-popups-settings .save' ).hide();
$( '#mwe-popups-settings-help, #mwe-popups-settings .okay' ).show();
mw.track( 'ext.popups.event', $.extend( {}, currentLinkLogData, {
action: 'disabled'
} ) );
}
};
/**
* Show the settings element and position it correctly
*
* @method open
* @param {Object} logData data to log
*/
settings.open = function ( logData ) {
var
h = $( window ).height(),
w = $( window ).width();
currentLinkLogData = logData;
$( 'body' ).append( $( '<div>' ).addClass( 'mwe-popups-overlay' ) );
if ( !settings.$element ) {
settings.render();
}
// FIXME: Should recalc on browser resize
settings.$element
.show()
.css( 'left', ( w - settings.$element.outerWidth( true ) ) / 2 )
.css( 'top', ( h - settings.$element.outerHeight( true ) ) / 2 );
return false;
};
/**
* Close the setting dialog and remove the overlay.
* If the close button is clicked on the help dialog
* save the setting and reload the page.
*
* @method close
*/
settings.close = function () {
if ( $( '#mwe-popups-settings-help' ).is( ':visible' ) ) {
settings.reloadPage();
} else {
$( '.mwe-popups-overlay' ).remove();
settings.$element.hide();
}
};
/**
* Adds a link to the footer to re-enable hovercards
*
* @method addFooterLink
*/
settings.addFooterLink = function () {
var $setting, $footer;
if ( mw.popups.enabled ) {
return false;
}
$setting = $( '<li>' ).append(
$( '<a>' )
.attr( 'href', '#' )
.text( mw.message( 'popups-settings-enable' ).text() )
.click( function ( e ) {
settings.open();
e.preventDefault();
} )
);
$footer = $( '#footer-places, #f-list' );
// From https://en.wikipedia.org/wiki/MediaWiki:Gadget-ReferenceTooltips.js
if ( $footer.length === 0 ) {
$footer = $( '#footer li' ).parent();
}
$footer.append( $setting );
};
/**
* Wrapper around window.location.reload. Exposed for testing purposes only.
*
* @private
* @ignore
*/
settings.reloadPage = function () {
location.reload();
};
$( function () {
if ( !mw.popups.enabled ) {
settings.addFooterLink();
}
} );
mw.popups.settings = settings;
} )( jQuery, mediaWiki );

View file

@ -255,12 +255,24 @@
*
* @return {Object}
*/
actions.settingsDialogClosed = function () {
actions.hideSettings = function () {
return {
type: types.SETTINGS_HIDE
};
};
/**
* Represents the user saving their settings.
*
* @return {Object}
*/
actions.saveSettings = function () {
return function ( dispatch ) {
// ...
return dispatch( actions.hideSettings() );
};
};
/**
* Represents the queued event being logged
* `mw.popups.changeListeners.eventLogging` change listener.

View file

@ -31,8 +31,9 @@
* @param {Object} actions
* @param {mw.eventLog.Schema} schema
* @param {ext.popups.UserSettings} userSettings
* @param {Function} settingsDialog
*/
function registerChangeListeners( store, actions, schema, userSettings ) {
function registerChangeListeners( store, actions, schema, userSettings, settingsDialog ) {
// Sugar.
var changeListeners = mw.popups.changeListeners,
@ -43,6 +44,7 @@
registerChangeListener( store, changeListeners.render( actions ) );
registerChangeListener( store, changeListeners.eventLogging( actions, schema ) );
registerChangeListener( store, changeListeners.previewCount( userSettings ) );
registerChangeListener( store, changeListeners.settings( actions, settingsDialog ) );
}
/**
@ -85,10 +87,12 @@
generateToken = mw.user.generateRandomSessionId,
gateway = createGateway(),
userSettings,
settingsDialog,
isUserInCondition,
schema;
userSettings = mw.popups.createUserSettings( mw.storage, mw.user );
settingsDialog = mw.popups.createSettingsDialogRenderer();
isUserInCondition = mw.popups.createExperiment( mw.config, mw.user, userSettings );
schema = mw.popups.createSchema( mw.config, window );
@ -104,7 +108,7 @@
) )
);
actions = createBoundActions( store );
registerChangeListeners( store, actions, schema, userSettings );
registerChangeListeners( store, actions, schema, userSettings, settingsDialog );
actions.boot(
isUserInCondition,

View file

@ -8,15 +8,22 @@
* @return {ext.popups.ChangeListener}
*/
mw.popups.changeListeners.settings = function ( boundActions, render ) {
var settings;
var settings,
shown = false;
return function ( prevState, state ) {
if ( state.settings.shouldShow && !settings ) {
settings = render( boundActions );
if ( state.settings.shouldShow && !shown ) {
// Lazily instantiate the settings UI
if ( !settings ) {
settings = render( boundActions );
settings.appendTo( document.body );
}
settings.show();
shown = true;
} else if ( !state.settings.shouldShow && settings ) {
settings.hide();
settings = undefined;
shown = false;
}
};
};

View file

@ -0,0 +1,153 @@
( function ( mw, $ ) {
/**
* Creates a render function that will create the settings dialog and return
* a set of methods to operate on it
*/
mw.popups.createSettingsDialogRenderer = function () {
/**
* Cached settings dialog
*
* @type {jQuery}
*/
var $dialog,
/**
* Cached settings overlay
*
* @type {jQuery}
*/
$overlay;
/**
* Renders the relevant form and labels in the settings dialog
*/
return function ( boundActions ) {
if ( !$dialog ) {
$dialog = createSettingsDialog();
$overlay = $( '<div>' ).addClass( 'mwe-popups-overlay' );
// Setup event bindings
$dialog.find( '.save' ).click( boundActions.saveSettings );
$dialog.find( '.close, .okay' ).click( boundActions.hideSettings );
}
return {
/**
* Append the dialog and overlay to a DOM element
* @param {HTMLElement} el
*/
appendTo: function ( el ) {
$overlay.appendTo( el );
$dialog.appendTo( el );
},
/**
* Show the settings element and position it correctly
*/
show: function () {
var h = $( window ).height(),
w = $( window ).width();
$overlay.show();
// FIXME: Should recalc on browser resize
$dialog
.show()
.css( 'left', ( w - $dialog.outerWidth( true ) ) / 2 )
.css( 'top', ( h - $dialog.outerHeight( true ) ) / 2 );
},
/**
* Hide the settings dialog.
*/
hide: function () {
if ( $dialog.find( '#mwe-popups-settings-help' ).is( ':visible' ) ) {
// TODO: Why is this trying to reload the page?
// reloadPage();
return;
} else {
$overlay.hide();
$dialog.hide();
}
}
};
};
};
/**
* Create the settings dialog
*
* @return {jQuery} settings dialog
*/
function createSettingsDialog() {
var $el,
path = mw.config.get( 'wgExtensionAssetsPath' ) + '/Popups/resources/ext.popups/images/',
choices = [
{
id: 'simple',
name: mw.msg( 'popups-settings-option-simple' ),
description: mw.msg( 'popups-settings-option-simple-description' ),
image: path + 'hovercard.svg',
isChecked: true
},
{
id: 'advanced',
name: mw.msg( 'popups-settings-option-advanced' ),
description: mw.msg( 'popups-settings-option-advanced-description' ),
image: path + 'navpop.svg'
},
{
id: 'off',
name: mw.msg( 'popups-settings-option-off' )
}
];
// Check if NavigationPopups is enabled
/*global pg: false*/
if ( typeof pg === 'undefined' || pg.fn.disablePopups === undefined ) {
// remove the advanced option
choices.splice( 1, 1 );
}
// render the template
$el = mw.template.get( 'ext.popups', 'settings.mustache' ).render( {
heading: mw.msg( 'popups-settings-title' ),
closeLabel: mw.msg( 'popups-settings-cancel' ),
saveLabel: mw.msg( 'popups-settings-save' ),
helpText: mw.msg( 'popups-settings-help' ),
okLabel: mw.msg( 'popups-settings-help-ok' ),
descriptionText: mw.msg( 'popups-settings-description' ),
choices: choices
} );
return $el;
}
/**
* Save the setting to the device and close the dialog
*
* @method save
*
function save( boundActions ) {
var v = $( 'input[name=mwe-popups-setting]:checked', '#mwe-popups-settings' ).val(),
userSettings = mw.popups.createUserSettings( mw.storage, mw.user );
if ( v === 'simple' ) {
// Avoid a refresh if 'enabled' -> 'enabled'
if ( !userSettings.getIsEnabled() ) {
userSettings.setIsEnabled( true );
// TODO: Why is this trying to reload the page?
// reloadPage();
}
boundActions.hideSettings();
} else {
userSettings.setIsEnabled( false );
$( '#mwe-popups-settings-form, #mwe-popups-settings .save' ).hide();
$( '#mwe-popups-settings-help, #mwe-popups-settings .okay' ).show();
}
}
*/
} )( mediaWiki, jQuery );

View file

@ -1,83 +0,0 @@
// render, renderOption, and addFooterLink are already covered in the browser tests
( function ( $, mw ) {
QUnit.module( 'ext.popups.settings' );
QUnit.test( 'save', function ( assert ) {
var jQueryInit = this.sandbox.stub( jQuery.fn, 'init' ),
radioButtonValue;
QUnit.expect( 2 );
this.sandbox.stub( mw.popups.settings, 'reloadPage' );
this.sandbox.stub( mw.popups.settings, 'close' );
jQueryInit.withArgs( 'input[name=mwe-popups-setting]:checked', '#mwe-popups-settings' )
.returns( {
val: function () {
return radioButtonValue;
}
} );
jQueryInit.withArgs( '#mwe-popups-settings-form, #mwe-popups-settings .save' )
.returns( {
hide: $.noop
} );
jQueryInit.withArgs( '#mwe-popups-settings-help, #mwe-popups-settings .okay' )
.returns( {
show: $.noop
} );
radioButtonValue = 'simple';
mw.popups.settings.save();
assert.equal(
mw.storage.get( 'mwe-popups-enabled' ),
'1',
'Popups are enabled when the `simple` radio button is checked.'
);
radioButtonValue = 'off';
mw.popups.settings.save();
assert.equal(
mw.storage.get( 'mwe-popups-enabled' ),
'0',
'Popups are disabled when the `off` radio button is checked.'
);
jQueryInit.restore();
mw.popups.settings.reloadPage.restore();
mw.popups.settings.close.restore();
} );
QUnit.test( 'open', function ( assert ) {
QUnit.expect( 2 );
mw.popups.settings.open();
assert.equal(
( $( window ).width() - mw.popups.settings.$element.outerWidth( true ) ) / 2 + 'px',
mw.popups.settings.$element.css( 'left' ),
'Settings dialog is horizontally aligned in the middle.'
);
assert.equal(
( $( window ).height() - mw.popups.settings.$element.outerHeight( true ) ) / 2 + 'px',
mw.popups.settings.$element.css( 'top' ),
'Settings dialog is vertically aligned in the middle.'
);
mw.popups.settings.close();
} );
QUnit.test( 'close', function ( assert ) {
QUnit.expect( 2 );
mw.popups.settings.open();
assert.equal(
mw.popups.settings.$element.is( ':visible' ),
true,
'Settings dialog is visible when settings are opened.'
);
mw.popups.settings.close();
assert.equal(
mw.popups.settings.$element.is( ':visible' ),
false,
'Settings dialog is not visible when settings are closed.'
);
} );
} )( jQuery, mediaWiki );

View file

@ -4,6 +4,7 @@
setup: function () {
this.render = this.sandbox.stub();
this.rendered = {
appendTo: this.sandbox.spy(),
show: this.sandbox.spy(),
hide: this.sandbox.spy()
};
@ -33,6 +34,7 @@
this.settings( this.defaultState, this.showState );
assert.ok( this.render.calledWith( 'actions' ), 'The renderer should be called with the actions' );
assert.ok( this.rendered.appendTo.called, 'The rendered object should be in the DOM' );
assert.ok( this.rendered.show.called, 'The rendered object should be showed' );
} );
@ -42,6 +44,7 @@
this.settings( this.showState, this.showState );
assert.ok( this.render.calledOnce, 'The renderer should be called only the first time' );
assert.ok( this.rendered.appendTo.calledOnce, 'The rendered object should be in the DOM' );
assert.ok( this.rendered.show.calledOnce, 'The rendered object should be showed just once' );
assert.notOk( this.rendered.hide.called, 'The rendered object should not be hidden' );
} );

View file

@ -0,0 +1,26 @@
( function ( $, mw ) {
QUnit.module( 'ext.popups/settingsDialog' );
QUnit.test( '#render', function ( assert ) {
var boundActions = {
saveSettings: function () {},
hideSettings: function () {}
},
expected = {
appendTo: function () {},
show: function () {},
hide: function () {}
},
result = mw.popups.createSettingsDialogRenderer()( boundActions );
// Specifically NOT a deep equal. Only structure.
assert.propEqual(
result,
expected
);
} );
// FIXME: Add Qunit integration tests about the rendering and the behavior of
// the settings dialog.
} )( jQuery, mediaWiki );