MediaWiki:Gadget-Global-WikiForm.js
Appearance
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
* This gadget interacts with Module:WikiForm to produce forms that can create pages or add content to existing pages
* Documentation: https://www.mediawiki.org/wiki/WikiForm
* Master: https://www.mediawiki.org/wiki/MediaWiki:Gadget-Global-WikiForm.js
* Author: User:Sophivorus
* License: CC-BY-SA-4.0
*/
var WikiForm = {
init: function () {
WikiForm.buildClasses();
$( '.WikiForm' ).each( WikiForm.makeForm );
},
/**
* Finish building our custom classes
*/
buildClasses: function () {
OO.inheritClass( WikiForm.SearchInputWidget, OO.ui.TextInputWidget );
OO.mixinClass( WikiForm.SearchInputWidget, OO.ui.mixin.LookupElement );
OO.inheritClass( WikiForm.PropertyInputWidget, OO.ui.TextInputWidget );
OO.mixinClass( WikiForm.PropertyInputWidget, OO.ui.mixin.LookupElement );
OO.inheritClass( WikiForm.NominatimInputWidget, OO.ui.TextInputWidget );
OO.mixinClass( WikiForm.NominatimInputWidget, OO.ui.mixin.LookupElement );
},
makeForm: function () {
var $template = $( this );
// Set the messages
var messages = {
'wikiform-submit': $template.data( 'submit' ) || 'Submit',
'wikiform-submit-success': $template.data( 'submit-success' ) || 'The form was submitted, thanks!',
'wikiform-submit-error': $template.data( 'submit-error' ) || 'Something went wrong! $1',
'wikiform-template-error': $template.data( 'template-error' ) || 'The "template" parameter is required.',
'wikiform-namespace-error': $template.data( 'namespace-error' ) || "Forms don't work in the Template namespace.",
'wikiform-group-error': $template.data( 'group-error' ) || "This form is restricted to the '$1' group.",
};
mw.messages.set( messages );
// Basic validation
var template = $template.data( 'template' );
if ( !template ) {
$template.addClass( 'error' ).text( mw.msg( 'wikiform-template-error' ) );
return;
}
var group = $template.data( 'group' );
var groups = mw.config.get( 'wgUserGroups' );
if ( group && !groups.includes( group ) ) {
$template.addClass( 'error' ).text( mw.msg( 'wikiform-group-error', group ) );
return;
}
// Make the fields and layouts
var fields = {};
var layouts = [];
for ( var i = 0; i < 100; i++ ) {
var name = $template.data( 'field' + i );
if ( !name ) {
continue;
}
// Set all the parameters that might be relevant
var type = $template.data( 'field' + i + '-type' );
var value = $template.data( 'field' + i + '-value' );
var placeholder = $template.data( 'field' + i + '-placeholder' );
var required = $template.data( 'field' + i + '-required' ) ? true : false;
var disabled = $template.data( 'field' + i + '-disabled' ) ? true : false;
var values = $template.data( 'field' + i + '-values' );
var search = $template.data( 'field' + i + '-values-from-search' );
var property = $template.data( 'field' + i + '-values-from-property' );
var service = $template.data( 'field' + i + '-values-from-service' );
var options = $template.data( 'field' + i + '-options' );
var min = $template.data( 'field' + i + '-min' );
var max = $template.data( 'field' + i + '-max' );
var selected = $template.data( 'field' + i + '-selected' ) ? true : false;
// Make the basic field input
var config = {
name: name,
value: value,
placeholder: placeholder,
required: required,
disabled: disabled,
data: { required: required }
};
var field = new OO.ui.TextInputWidget( config );
// Modify it according to the type and parameters
switch ( type ) {
default:
if ( values ) {
config.options = [];
values.split( ',' ).forEach( function ( value ) {
value = value.trim();
config.options.push( { data: value } );
} );
if ( options ) {
options.split( ',' ).forEach( function ( option, index ) {
option = option.trim();
config.options[ index ].label = option;
} );
}
field = new OO.ui.ComboBoxInputWidget( config );
} else if ( search ) {
config.data.search = search;
field = new WikiForm.SearchInputWidget( config );
} else if ( property ) {
config.data.property = property;
config.allowSuggestionsWhenEmpty = true;
field = new WikiForm.PropertyInputWidget( config );
} else if ( service === 'Nominatim' ) {
field = new WikiForm.NominatimInputWidget( config );
}
break;
case 'tags':
config.allowArbitrary = true;
if ( values ) {
config.options = [];
values.split( ',' ).forEach( function ( value ) {
value = value.trim();
config.options.push( { data: value } );
} );
if ( options ) {
options.split( ',' ).forEach( function ( option, index ) {
option = option.trim();
config.options[ index ].label = option;
} );
}
}
field = new OO.ui.MenuTagMultiselectWidget( config );
break;
case 'file':
field = new OO.ui.SelectFileInputWidget( config );
break;
case 'textarea':
config.autosize = true;
field = new OO.ui.MultilineTextInputWidget( config );
break;
case 'number':
config.min = min;
config.max = max;
field = new OO.ui.NumberInputWidget( config );
break;
case 'boolean':
value = value || 1;
config.selected = selected;
config.value = selected ? value : '';
field = new OO.ui.CheckboxInputWidget( config );
field.on( 'change', function ( field, value, selected ) {
field.setValue( selected ? value : '' );
}, [ field, value ] );
break;
case 'dropdown':
if ( values ) {
config.options = [];
if ( !required ) {
config.options.push( { label: placeholder } );
}
values.split( ',' ).forEach( function ( value ) {
value = value.trim();
config.options.push( { data: value } );
} );
if ( options ) {
options.split( ',' ).forEach( function ( option, index ) {
if ( !required ) {
index = index + 1;
}
option = option;
config.options[ index ].label = option;
} );
}
}
field = new OO.ui.DropdownInputWidget( config );
break;
case 'radio':
if ( values ) {
config.options = [];
if ( !required ) {
config.options.push( { label: placeholder } );
}
values.split( ',' ).forEach( function ( value ) {
value = value.trim();
config.options.push( { data: value } );
} );
if ( options ) {
options.split( ',' ).forEach( function ( option, index ) {
if ( !required ) {
index = index + 1;
}
option = option;
config.options[ index ].label = option;
} );
}
}
field = new OO.ui.RadioSelectInputWidget( config );
break;
case 'checkbox':
if ( values ) {
config.options = [];
values.split( ',' ).forEach( function ( value ) {
value = value.trim();
config.options.push( { data: value } );
} );
if ( options ) {
options.split( ',' ).forEach( function ( option, index ) {
option = option;
config.options[ index ].label = option;
} );
}
}
field = new OO.ui.CheckboxMultiselectInputWidget( config );
break;
case 'hidden':
field = new OO.ui.HiddenInputWidget( config );
break;
}
// Make the field layout
var label = $template.data( 'field' + i + '-label' );
var style = $template.data( 'field' + i + '-style' );
var help = $template.data( 'field' + i + '-help' );
var align = type === 'boolean' ? 'inline' : 'top';
var layout = new OO.ui.FieldLayout( field, { label: label, align: align, help: help, helpInline: true } );
if ( style ) {
layout.$element.attr( 'style', style );
}
fields[ name ] = field;
layouts.push( layout );
}
// Make the submit button
var submitButton = new OO.ui.ButtonInputWidget( { label: mw.msg( 'wikiform-submit' ), flags: [ 'primary', 'progressive' ] } );
var submitButtonLayout = new OO.ui.FieldLayout( submitButton, {} );
submitButton.on( 'click', WikiForm.submit, [ $template, submitButton, fields ] );
layouts.push( submitButtonLayout );
var form = new OO.ui.FormLayout( { items: layouts } );
$template.html( form.$element );
},
submit: function ( $template, submitButton, fields ) {
// Check the required fields
var name, field, data, value;
for ( name in fields ) {
field = fields[ name ];
data = field.getData();
value = field.getValue ? field.getValue() : field.$element.val(); // Hidden inputs don't have getValue
if ( data.required && ( !value || !value.length ) ) {
field.focus();
return;
}
}
// Check the namespace
if ( mw.config.get( 'wgCanonicalNamespace' ) === 'Template' ) {
mw.notify( mw.msg( 'wikiform-namespace-error' ) );
return;
}
// Signal success
submitButton.setDisabled( true );
// Upload any files
for ( name in fields ) {
field = fields[ name ];
if ( field instanceof OO.ui.SelectFileInputWidget && field.currentFiles ) {
var file = field.currentFiles[0];
if ( file ) {
new mw.Api().upload( file, {
filename: file.name,
ignorewarnings: true // @todo Fail gracefully rather than ignore?
} );
}
}
}
// Build the wikitext
var template = $template.data( 'template' );
var wikitext = '{{' + template;
for ( name in fields ) {
field = fields[ name ];
value = field.getValue ? field.getValue() : field.$element.val();
if ( Array.isArray( value ) ) {
value = value.join( ', ' );
}
value = value.replace( 'C:\\fakepath\\', '' ); // Remove fakepath from any file names @todo Do better
wikitext += '\n| ' + name + ' = ' + value;
}
wikitext += '\n}}';
// Figure out the page where to post
var page = $template.data( 'page' );
if ( page ) {
for ( name in fields ) {
field = fields[ name ];
value = field.getValue ? field.getValue() : field.$element.val();
page = page.replace( '{{{' + name + '}}}', value );
}
} else {
page = mw.config.get( 'wgPageName' );
}
// Figure out the section where to post
var section = $template.data( 'section' );
if ( section ) {
for ( name in fields ) {
field = fields[ name ];
value = field.getValue ? field.getValue() : field.$element.val();
section = section.replace( '{{{' + name + '}}}', value );
}
}
// Figure out where to redirect
var redirect = $template.data( 'redirect' );
if ( redirect ) {
for ( name in fields ) {
field = fields[ name ];
value = field.getValue ? field.getValue() : field.$element.val();
redirect = redirect.replace( '{{{' + name + '}}}', value );
}
}
// Append the wikitext to the page
WikiForm.post( wikitext, page, section, redirect, $template );
},
post: function ( wikitext, page, section, redirect, $template ) {
return new mw.Api().get( {
action: 'parse',
page: page,
prop: 'text',
formatversion: 2
} ).always( function ( data ) {
var prefix = $template.data( 'template-inline' ) ? '' : '\n\n';
// Figure out if the section already exists and its number
var sectionNumber;
if ( section ) {
sectionNumber = 'new';
if ( data !== 'missingtitle' ) {
var html = $.parseHTML( data.parse.text );
var $header = $( ':header:contains("' + section + '"), .mw-heading:contains("' + section + '")', html );
if ( $header.length ) {
sectionNumber = 1 + $header.prevAll( ':header, .mw-heading' ).length;
wikitext = prefix + wikitext;
}
}
} else if ( data !== 'missingtitle' ) {
wikitext = prefix + wikitext;
}
var params = {
action: 'edit',
title: page,
section: sectionNumber
};
if ( sectionNumber === 'new' ) {
params.sectiontitle = section;
params.text = wikitext;
} else {
params.appendtext = wikitext;
}
return new mw.Api().postWithEditToken( params ).done( function () {
if ( redirect ) {
var parts = redirect.split( '#' );
if ( parts[0] === page && parts[1] ) {
window.location.hash = '#' + parts[1];
window.location.reload();
} else {
var url = mw.util.getUrl( redirect );
window.location.href = url;
}
} else {
$template.text( mw.msg( 'wikiform-submit-success' ) ).focus();
}
} ).fail( function ( code, info ) {
$template.addClass( 'error' ).text( mw.msg( 'wikiform-submit-error', info ) ).focus();
} );
} );
},
/**
* Custom class for text fields with values from search suggestions
*/
SearchInputWidget: function ( config ) {
OO.ui.TextInputWidget.call( this, config );
OO.ui.mixin.LookupElement.call( this, config );
this.getLookupRequest = function () {
var value = this.getValue();
var data = this.getData();
var search = data.search.replace( '%s', value );
var params = {
format: 'json',
formatversion: 2,
action: 'query',
list: 'search',
srsearch: search,
};
return new mw.Api().get( params );
};
this.getLookupCacheDataFromResponse = function ( response ) {
var values = [];
if ( response && response.query && response.query.search ) {
response.query.search.forEach( function ( result ) {
var title = new mw.Title( result.title, result.ns );
var value = title.getPrefixedText();
values.push( value );
} );
}
return values;
};
this.getLookupMenuOptionsFromData = WikiForm.getLookupMenuOptionsFromData;
},
/**
* Custom class for text fields with values from semantic properties
*/
PropertyInputWidget: function ( config ) {
OO.ui.TextInputWidget.call( this, config );
OO.ui.mixin.LookupElement.call( this, config );
this.getLookupRequest = function () {
var value = this.getValue();
var data = this.getData();
var property = data.property;
var params = {
format: 'json',
formatversion: 2,
action: 'smwbrowse',
browse: 'pvalue',
params: JSON.stringify( { property: property, search: value } ),
};
return new mw.Api().get( params ).fail( console.log );
};
this.getLookupCacheDataFromResponse = function ( response ) {
var values = [];
if ( response ) {
response.query.forEach( function ( value ) {
values.push( value );
} );
}
return values;
};
this.getLookupMenuOptionsFromData = WikiForm.getLookupMenuOptionsFromData;
},
/**
* Custom class for text fields with values from Open Street Map's Nominatim service
*/
NominatimInputWidget: function ( config ) {
OO.ui.TextInputWidget.call( this, config );
OO.ui.mixin.LookupElement.call( this, config );
this.getLookupRequest = function () {
var value = this.getValue();
var data = { q: value, format: 'json' };
return $.get( '//nominatim.openstreetmap.org/search', data );
};
this.getLookupCacheDataFromResponse = function ( response ) {
var values = [];
response.forEach( function ( result ) {
values.push( result.display_name );
} );
return values;
};
this.getLookupMenuOptionsFromData = WikiForm.getLookupMenuOptionsFromData;
},
getLookupMenuOptionsFromData: function ( values ) {
var options = [];
values.forEach( function ( value ) {
var config = { data: value, label: value };
var option = new OO.ui.MenuOptionWidget( config );
options.push( option );
} );
return options;
}
};
mw.loader.using( [
'mediawiki.api',
'mediawiki.user',
'mediawiki.util',
'oojs-ui-core',
'oojs-ui-widgets'
], WikiForm.init );