MediaWiki:WMDE FR2015/Resources/SensitiveBanner.js
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.
/*jshint latedef: nofunc */
/*jshint unused: false */
/* globals getAmount */
var isOpen = false,
addressType = 'private';
$( function () {
var paymentButtons = $( '#WMDE_BannerForm-payment button' ),
fundsBox = new BannerModalInfobox( 'funds' ),
taxesBox = new BannerModalInfobox( 'taxes' ),
bitcoinBox = new BannerModalInfobox( 'bitcoin' ),
dataProtectionBox = new BannerModalInfobox( 'dataprotection' );
unlockForm();
toggleDebitType();
initWatchingInitialValues();
$( '#interval_onetime' ).on( 'click', function () {
$( '#WMDE_BannerForm-wrapper' ).css( 'height', '158px' );
$( '.interval-options input[name=interval]' ).prop( 'checked', false );
} );
$( '#interval_multiple' ).on( 'click', function () {
$( '#WMDE_BannerForm-wrapper' ).css( 'height', '204px' );
} );
paymentButtons.on( 'click', function ( e ) {
e.preventDefault();
} );
$( '#WMDE_BannerFullForm-finish' ).on( 'click', function ( e ) {
$( this ).trigger( 'blur' );
$( this ).addClass( 'waiting' );
lockForm();
} );
$( '#WMDE_BannerFullForm-finish-sepa' ).on( 'click', handleSepaValidation );
$( '#WMDE_BannerFullForm-close-step1' ).on( 'click', function () {
hideFullForm();
unlockForm();
} );
$( '#WMDE_BannerFullForm-close-step2' ).on( 'click', function () {
$( '#WMDE_BannerFullForm-step2' ).slideToggle( 400, function () {
$( '#WMDE_BannerFullForm-step1' ).slideToggle();
} );
hideFullForm();
unlockForm();
} );
$( '.WMDE_BannerFullForm-confirm-edit' ).on( 'click', function () {
debitBackToFirstStep();
} );
paymentButtons.hover( function () {
if ( !isOpen ) {
$( '#WMDE_BannerFullForm-arrow' ).show();
}
},
function () {
$( '#WMDE_BannerFullForm-arrow' ).hide();
} );
$( '#WMDE_BannerFullForm-hint' ).on( 'click', function () {
$( '#btn-ppl' ).trigger( 'click' );
} );
$( 'input[name=\'debit-type\']' ).on( 'click', function () {
toggleDebitType();
} );
$( '#address-type-1' ).on( 'click', function () {
$( '#WMDE_BannerFullForm-company' ).slideUp();
$( '#WMDE_Banner-person' ).slideDown();
$( '#WMDE_Banner-address' ).slideDown();
addressType = 'private';
} );
$( '#address-type-2' ).on( 'click', function () {
$( '#WMDE_Banner-person' ).slideUp();
$( '#WMDE_BannerFullForm-company' ).slideDown();
$( '#WMDE_Banner-address' ).slideDown();
addressType = 'company';
} );
$( '#address-type-3' ).on( 'click', function () {
$( '#WMDE_BannerFullForm-company' ).slideUp();
$( '#WMDE_Banner-person' ).slideUp();
$( '#WMDE_Banner-address' ).slideUp();
$( '#send-information' ).prop( 'checked', false );
addressType = 'anonymous';
} );
// Enter key in input fields should trigger the correct submit button
$( '#donationForm input[type=text]' ).keypress( function ( evt ) {
if ( evt.key === 'Enter' || evt.which === 13 ) {
evt.preventDefault();
$( '#donationForm .submit:visible' ).first().trigger( 'click' );
}
} );
// set validation event handlers
$( '#donationForm' ).on( 'banner:validationFailed', function () {
unlockForm();
$( '#WMDE_BannerFullForm-finish' ).removeClass( 'waiting' );
} );
$( '#donationForm' ).on( 'banner:validationSucceeded', function ( evt ) {
var zahlweiseVal = $( '#zahlweise' ).val(),
// Paypal page takes some time to load
spinnerTimout = zahlweiseVal === 'PPL' ? 4000 : 0 ;
unlockForm();
window.setTimeout( function () {
$( '#WMDE_BannerFullForm-finish' ).removeClass( 'waiting' );
}, spinnerTimout );
if ( zahlweiseVal === 'BEZ' ) {
debitNextStep();
evt.preventDefault();
} else {
this.submit();
}
} );
} );
function initWatchingInitialValues() {
initWatchingInitialPeriodValues();
initWatchingInitialAmountValues();
// Payment type initial value is handled by the payment button event handlers
}
/**
* If a payment period is selected, store its value, otherwise add event
* handlers to store the first period that is selected.
*/
function initWatchingInitialPeriodValues() {
var intervalOnetime = $( '#interval_onetime' ),
intervalMultipleIsSelected = $( '#interval_multiple:checked' ),
intervals = $( 'input[name=interval]' ),
selectedInterval = intervals.filter( ':checked' );
if ( intervalOnetime.prop( 'checked' ) ) {
storeIntervalSelection( intervalOnetime );
} else if ( intervalMultipleIsSelected.length && selectedInterval.length ) {
storeIntervalSelection( selectedInterval );
} else {
intervalOnetime.one( 'click', function () {
storeIntervalSelection( $( this ) );
} );
intervals.one( 'click', function () {
storeIntervalSelection( $( this ) );
} );
}
}
function storeIntervalSelection( selectedInput ) {
if ( selectedInput.prop( 'id' ) === 'interval_onetime' ) {
setInitialValue( 'periode', 0 );
} else {
setInitialValue( 'periode', selectedInput.val() );
}
}
function initWatchingInitialAmountValues() {
var amounts = $( '.amount-options .amount-radio' ),
selectedAmount = amounts.filter( ':checked' );
if ( selectedAmount.length ) {
setInitialValue( 'betrag', getAmountSelectionOrInput( selectedAmount ) );
} else {
amounts.one( 'click', function () {
getAmountSelectionOrInput( $( this ) );
} );
}
}
function getAmountSelectionOrInput( selected ) {
if ( selected.prop( 'id' ) === 'amount-other' ) {
return $( 'amount-other-input' ).val();
} else {
return selected.val();
}
}
/**
* Store the first value a user selected for for payment type, amount and period
*/
function setInitialValue( name, value ) {
var elem = $( '#' + name + '_orig' );
if ( !elem.data( 'has-value' ) ) {
elem.data( 'has-value', true );
elem.val( value );
}
}
/**
* Handle clicks on the button on the SEPA confirmation page.
*
* When checkboxes are ok, submit the form, if not, highlight missing checkboxes.
*
* @param {Event} evt Button click event
*/
function handleSepaValidation( evt ) {
evt.preventDefault();
if ( $( '#confirm_sepa' ).prop( 'checked' ) && $( '#confirm_shortterm' ).prop( 'checked' ) ) {
$( '#donationForm' ).submit();
} else {
$( '#confirm_sepa, #confirm_shortterm' ).each( function ( index, element ) {
var $element = $( element ),
$parent;
if ( $element.prop( 'checked' ) ) {
return;
}
$parent = $element.parent();
$parent.css( { border: 'red 1px solid' } );
$element.one( 'click', function () {
$parent.css( { border: 'none' } );
} );
} );
}
}
function lockForm() {
$( '#WMDE_BannerForm-payment button' ).prop( 'disabled', true );
$( 'input' ).prop( 'disabled', true );
$( 'select' ).prop( 'disabled', true );
}
function unlockForm() {
$( '#WMDE_BannerForm-payment button' ).prop( 'disabled', false );
$( 'input' ).prop( 'disabled', false );
$( 'select' ).prop( 'disabled', false );
}
function toggleDebitType() {
if ( $( 'input:radio[name=debit-type]:checked' ).val() === 'sepa' ) {
$( '#WMDE_BannerFullForm-nosepa' ).slideUp();
$( '#WMDE_Banner-sepa' ).slideDown();
} else {
$( '#WMDE_Banner-sepa' ).slideUp();
$( '#WMDE_BannerFullForm-nosepa' ).slideDown();
}
}
function showFullForm() {
$( '#WMDE_BannerFullForm-arrow' ).hide();
$( '#WMDE_BannerFullForm-hint' ).hide();
$( '#WMDE_BannerFullForm-shadow' ).fadeIn();
$( '#WMDE_BannerFullForm-details' ).slideDown();
$( '#WMDE_BannerFullForm-info' ).slideDown();
$( '#WMDE_Banner' ).css( 'position', 'absolute' );
$( 'html, body' ).animate( {
scrollTop: 0
}, 'slow' );
isOpen = true;
}
function hideFullForm() {
$( '#zahlweise' ).val( '' );
$( '#form_action' ).prop( 'name', '' );
$( '#donationIframe' ).val( '' );
isOpen = false;
$( '#WMDE_BannerFullForm-details' ).slideUp( 400, function () {
$( '#WMDE_Banner' ).css( 'position', 'fixed' );
resetButtons();
$( '#WMDE_BannerFullForm-hint' ).show();
} );
$( '#WMDE_BannerFullForm-info' ).hide();
$( '#WMDE_BannerFullForm-shadow' ).fadeOut();
}
function debitNextStep() {
$( '#WMDE_BannerFullForm-step1' ).slideToggle( 400, function () {
$( '#WMDE_BannerFullForm-step2' ).slideToggle();
} );
fillConfirmationValues();
$( 'html, body' ).animate( {
scrollTop: 0
}, 'slow' );
}
function fillConfirmationValues() {
$( '#WMDE_BannerFullForm-confirm-amount' ).text( getAmount() );
$( '#WMDE_BannerFullForm-confirm-salutation' ).text( getSalutation() );
$( '#WMDE_BannerFullForm-confirm-street' ).text( $( '#street' ).val() );
$( '#WMDE_BannerFullForm-confirm-city' ).text( $( '#post-code' ).val() + ' ' + $( '#city' ).val() );
$( '#WMDE_BannerFullForm-confirm-country' ).text( getCountryByCode( $( '#country' ).val() ) );
$( '#WMDE_BannerFullForm-confirm-mail' ).text( $( '#email' ).val() );
$( '#WMDE_BannerFullForm-confirm-IBAN' ).text( $( '#iban' ).val() );
$( '#WMDE_BannerFullForm-confirm-BIC' ).text( $( '#bic' ).val() );
$( '#WMDE_BannerFullForm-confirm-bankname' ).text( $( '#bank-name' ).val() );
$( '#WMDE_BannerFullForm-confirm-date' ).text( getCurrentDateString() );
}
function getSalutation() {
var companyName = $( '#company-name' ).val(),
firstName = $( '#first-name' ).val(),
lastName = $( '#last-name' ).val(),
title = $( '#personal-title' ).val(),
salutation = '';
if ( companyName !== '' ) {
return companyName;
}
if ( firstName !== '' && lastName !== '' ) {
salutation += $( 'input[name=anrede]:checked' ).val();
if ( title !== 'Kein Titel' ) {
salutation += ' ' + title;
}
salutation += ' ' + firstName + ' ' + lastName;
return salutation;
}
return false;
}
function getCountryByCode( code ) {
switch ( code ) {
case 'DE':
return 'Deutschland';
case 'AT':
return 'Österreich';
case 'CH':
return 'Schweiz';
case 'IT':
return 'Italien';
case 'LI':
return 'Liechtenstein';
case 'LU':
return 'Luxemburg';
case 'BE':
return 'Belgien';
default:
return '';
}
}
function getCurrentDateString() {
var now = new Date(),
day = now.getDate(),
month = now.getMonth() + 1;
return ( day < 10 ? '0' : '' )
+ day
+ '.'
+ ( month < 10 ? '0' : '' )
+ month
+ '.'
+ now.getFullYear();
}
function debitBackToFirstStep() {
$( '#donationForm' ).trigger( 'banner:validationReset' );
$( '#WMDE_BannerFullForm-step2' ).slideToggle( 400, function () {
$( '#WMDE_BannerFullForm-step1' ).slideToggle();
} );
$( 'html, body' ).animate( {
scrollTop: 0
}, 'slow' );
}
/* Payment methods show and hide */
function showDebitDonation( button ) {
if ( $( '#zahlweise' ).val() === 'BEZ' ) {
hideFullForm();
} else {
$( '#zahlweise' ).val( 'BEZ' );
$( '#form_action' ).prop( 'name', 'go_prepare--pay:einzug' );
$( '#donationIframe' ).val( '' );
$( '#WMDE_Banner-debit-type' ).slideDown();
$( '#WMDE_Banner-anonymous' ).slideUp();
$( '#WMDE_BannerFullForm-finish' ).hide();
$( '#WMDE_BannerFullForm-next' ).show();
resetAddressType();
resetButtons();
$( button ).addClass( 'active' );
showFullForm();
setInitialValue( 'zahlweise', 'BEZ' );
}
}
function resetAddressType() {
$( '#address-type-1' ).trigger( 'click' );
}
function showDepositDonation( button ) {
if ( $( '#zahlweise' ).val() === 'UEB' ) {
hideFullForm();
} else {
$( '#zahlweise' ).val( 'UEB' );
$( '#form_action' ).prop( 'name', 'go_prepare--pay:ueberweisung' );
$( '#donationIframe' ).val( '' );
showNonDebitParts( button );
setInitialValue( 'zahlweise', 'UEB' );
}
}
function showCreditDonation( button ) {
if ( $( '#zahlweise' ).val() === 'MCP' ) {
hideFullForm();
} else {
$( '#zahlweise' ).val( 'MCP' );
$( '#form_action' ).prop( 'name', 'go_prepare--pay:micropayment-i' );
$( '#donationIframe' ).val( 'micropayment-iframe' );
showNonDebitParts( button );
setInitialValue( 'zahlweise', 'MCP' );
}
}
function showPayPalDonation( button ) {
if ( $( '#zahlweise' ).val() === 'PPL' ) {
hideFullForm();
} else {
$( '#zahlweise' ).val( 'PPL' );
$( '#form_action' ).prop( 'name', 'go_prepare--pay:paypal' );
$( '#donationIframe' ).val( '' );
showNonDebitParts( button );
setInitialValue( 'zahlweise', 'PPL' );
}
}
function showNonDebitParts( button ) {
resetButtons();
$( button ).addClass( 'active' );
$( '#WMDE_Banner-debit-type' ).slideUp();
$( '#WMDE_Banner-anonymous' ).slideDown();
$( '#WMDE_BannerFullForm-finish' ).show();
$( '#WMDE_BannerFullForm-next' ).hide();
showFullForm();
}
function resetButtons() {
$( '#WMDE_BannerForm-payment button' ).trigger( 'blur' );
$( '#WMDE_BannerForm-payment button' ).removeClass( 'active' );
}
/* LightBoxes show and hide */
/**
* This class handles showing and hiding the info boxes that are linked.
* Whenever an infobox is opened, the other in foxes are closed.
*/
function BannerModalInfobox( boxName ) {
this.$box = $( '#WMDE_BannerFullForm-' + boxName );
this.$link = $( '#WMDE_BannerFullForm-' + boxName + '-link' );
this.boxName = boxName;
this.$box.on( 'banner:openInfobox', this.open.bind( this ) );
this.$box.on( 'banner:closeInfobox', this.close.bind( this ) );
this.$link.on( 'click', this.toggle.bind( this ) );
$( '.banner-lightbox-close', this.$box ).on( 'click', this.close.bind( this ) );
}
BannerModalInfobox.prototype.toggle = function ( e ) {
if ( this.$box.hasClass( 'opened' ) ) {
this.$box.trigger( 'banner:closeInfobox' );
} else {
this.$box.trigger( 'banner:openInfobox' );
}
};
BannerModalInfobox.prototype.open = function ( e ) {
// close other banners
$( '.banner-unique' ).trigger( 'banner:closeInfobox' );
// wait for the slide-out to be done before showing banner
$( '.banner-unique' ).promise().done( function () {
$( '#WMDE_BannerFullForm-info' ).addClass( this.boxName );
this.$box.addClass( 'opened' );
this.$box.slideDown();
}.bind( this ) );
};
BannerModalInfobox.prototype.close = function ( e ) {
if ( !this.$box.hasClass( 'opened' ) ) {
return;
}
this.$box.slideUp( 400, function () {
this.$box.removeClass( 'opened' );
this.$link.removeClass( 'opened' );
$( '#WMDE_BannerFullForm-info' ).removeClass( this.boxName );
}.bind( this ) );
};
$( document ).ready( function () {
$( '.list-item-title' ).on( 'click', function () {
$( this ).toggleClass( 'opened' );
$( this ).next().toggle();
} );
} );
/* Function.prototype.bind polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Polyfill */
Function.prototype.bind||(Function.prototype.bind=function(t){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var o=Array.prototype.slice.call(arguments,1),n=this,r=function(){},i=function(){return n.apply(this instanceof r?this:t,o.concat(Array.prototype.slice.call(arguments)))};return this.prototype&&(r.prototype=this.prototype),i.prototype=new r,i});/* Polyfill for browsers which don't provide window.btoa and window.atob https://github.com/davidchambers/Base64.js */
!function(){function t(t){this.message=t}var r="undefined"!=typeof exports?exports:this,e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";t.prototype=new Error,t.prototype.name="InvalidCharacterError",r.btoa||(r.btoa=function(r){for(var o,n,a=String(r),i=0,c=e,d="";a.charAt(0|i)||(c="=",i%1);d+=c.charAt(63&o>>8-i%1*8)){if(n=a.charCodeAt(i+=.75),n>255)throw new t("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");o=o<<8|n}return d}),r.atob||(r.atob=function(r){var o=String(r).replace(/=+$/,"");if(o.length%4==1)throw new t("'atob' failed: The string to be decoded is not correctly encoded.");for(var n,a,i=0,c=0,d="";a=o.charAt(c++);~a&&(n=i%4?64*n+a:a,i++%4)?d+=String.fromCharCode(255&n>>(-2*i&6)):0)a=e.indexOf(a);return d})}();/**
* JavaScript library for general banner functionalities
*
* @licence GNU GPL v2+
* @author Kai Nissen <kai.nissen@wikimedia.de>
*/
var Banner;
( function () {
'use strict';
Banner = {};
} )();
/**
* Banner configuration
*
* @licence GNU GPL v2+
* @author Kai Nissen <kai.nissen@wikimedia.de>
*/
( function ( Banner ) {
'use strict';
Banner.config = {
encryption: {
libUrl: '../js/lib/openpgp.min.js',
legacyLibUrl: '../js/lib/pgp-legacy-libs.js',
publicKey: ''
},
form: {
formId: '',
formAction: 'https://spenden.wikimedia.de/spenden/'
},
tracking: {
campaign: 'default',
keyword: 'default',
accessCode: 'default',
baseUrl: 'https://spenden.wikimedia.de/',
libUrl: 'https://spenden.wikimedia.de/piwik/piwik.js',
trackerUrl: 'https://spenden.wikimedia.de/piwik/piwik.php',
siteId: 1,
events: {
BANNER_CLOSED: {
pathName: 'banner-closed',
clickElement: '',
sample: 0
},
BANNER_EXPANDED: {
pathName: 'banner-expanded',
clickElement: '',
sample: 0
},
BANNER_SHOWN: {
pathName: 'banner-shown',
clickElement: '',
sample: 0
},
LIGHTBOX_CLICKED: {
pathName: 'lightbox-clicked',
clickElement: '',
sample: 0
},
LINK_CLICKED: {
pathName: 'link-clicked',
clickElement: '',
sample: 0
}
}
},
setConfig: function ( settings ) {
$.extend( true, Banner.config, settings );
}
};
} )( Banner );
/**
* JavaScript library for tracking functionalities
*
* @licence GNU GPL v2+
* @author Kai Nissen <kai.nissen@wikimedia.de>
*/
( function ( Banner, $ ) {
'use strict';
var TP;
function Tracking() {
var self = this;
this._tracker = null;
this._eventsTracked = [];
$( document ).ready( function () {
self.initTrackingLib();
self.initClickHandlers();
} );
}
TP = Tracking.prototype;
/**
* Set the tracker lib
*
* @param {Tracker} tracker an instance of Piwik's Tracker class
*/
TP.setTracker = function ( tracker ) {
this._tracker = tracker;
};
/**
* Track a virtual page view
*
* @param {string} eventName
*/
TP.trackVirtualPageView = function ( eventName ) {
var virtualUrl;
if ( this.shouldTrack( eventName, this.getRandomNumber() ) ) {
virtualUrl = Banner.config.tracking.baseUrl +
Banner.config.tracking.events[ eventName ].pathName +
'/' +
Banner.config.tracking.keyword;
this._tracker.setCustomUrl( virtualUrl );
this._tracker.trackPageView( virtualUrl );
}
};
/**
* Determines whether an event should be tracked
*
* @param {string} eventName event name based on the property keys of Banner.tracking.events
* @param {number} randomNumber randomly generated number to compare against the configured sample size
* @return {boolean}
*/
TP.shouldTrack = function ( eventName, randomNumber ) {
return this._tracker &&
Banner.config.tracking.events[ eventName ] &&
Banner.config.tracking.events[ eventName ].sample > randomNumber;
};
/**
* @return {number}
*/
TP.getRandomNumber = function () {
return Math.random() * ( 1 - 0.01 ) + 0.01;
};
/**
* fetch the piwik library and get the specified tracker from it
*/
TP.initTrackingLib = function () {
var self = this;
$.ajax( {
url: Banner.config.tracking.libUrl,
dataType: 'script',
cache: true,
success: function () {
var trackingConfig = Banner.config.tracking;
self.setTracker( Piwik.getTracker( trackingConfig.trackerUrl, trackingConfig.siteId ) );
}
} );
};
/**
* bind click events to elements as configured
*/
TP.initClickHandlers = function () {
var self = this;
$.each( Banner.config.tracking.events, function ( key, settings ) {
$( settings.clickElement ).click( function () {
if ( $.inArray( key, self._eventsTracked ) === -1 ) {
Banner.tracking.trackVirtualPageView( key );
self._eventsTracked.push( key );
}
} );
} );
};
Banner.tracking = new Tracking();
} )( Banner, jQuery );
/**
* JavaScript library for encryption functionalities
*
* @licence GNU GPL v2+
* @author Kai Nissen <kai.nissen@wikimedia.de>
*/
( function ( Banner ) {
'use strict';
var EP;
function Encryption() {
var self = this;
this.initialized = false;
this.useLegacyEncryption = !this._browserSupportsCryptoAPI();
$( document ).ready( function () {
self.initCryptLib();
} );
}
EP = Encryption.prototype;
/**
* Check if the browser supports the window.crypto API
*/
EP._browserSupportsCryptoAPI = function () {
if ( typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues ) {
return true;
} else if ( typeof window !== 'undefined' && typeof window.msCrypto === 'object'
&& typeof window.msCrypto.getRandomValues === 'function' ) {
return true;
}
return false;
};
/**
* Load the encryption library
*/
EP.initCryptLib = function () {
var self = this,
libUrl = Banner.config.encryption.libUrl;
if ( this.useLegacyEncryption ) {
libUrl = Banner.config.encryption.legacyLibUrl;
}
$.ajax( {
url: libUrl,
dataType: 'script',
cache: true,
success: function () {
var legacyEncryption;
if ( self.useLegacyEncryption ) {
/*jshint -W117 *//* Ignore undefined LegacyPGP (it's loaded dynamically) */
legacyEncryption = new LegacyPGP();
/*jshint +W117 */
self.encrypt = legacyEncryption.encrypt.bind( legacyEncryption );
}
self.initialized = true;
}
} );
};
/**
* Encrypts a message and puts the encrypted data into the given field
*
* @param {string} data The message to encrypt
* @return {Promise}
*/
EP.encrypt = function ( data ) {
var publicKey;
if ( this.initialized ) {
publicKey = openpgp.key.readArmored( Banner.config.encryption.publicKey );
return openpgp.encryptMessage( publicKey.keys, data )
.then( function ( pgpMessage ) {
return pgpMessage;
} );
}
};
Banner.encryption = new Encryption();
} )( Banner );
/**
* Donation API used in the banner
*
* @licence GNU GPL v2+
* @author Leszek Manicki <leszek.manicki@wikimedia.de>
*/
( function ( banner, $ ) {
'use strict';
function Api() {
banner.config.setConfig( {
api: {
apiUrl: 'https://spenden.wikimedia.de/ajax.php',
validationModule: 'ajaxValidation',
validationAction: 'validateDonation',
ibanModule: 'action',
ibanGenerationAction: 'generateIban',
ibanCheckAction: 'checkIban'
}
} );
}
/**
* Encrypts request data and sends a request to the API.
*
* @param {string} module
* @param {string} action
* @param {Object} data
* @return {Promise}
*/
Api.prototype._sendEncryptedRequest = function ( module, action, data ) {
var self = this;
return banner.encryption.encrypt( $.param( data ) )
.then( function ( encryptedData ) {
return self._sendRequest( module, action, {
enc: self._encodeBase64( encryptedData )
} );
} );
};
/**
* Returns base64-encoded string representation of the object.
*
* @param {Object} data
* @return {string}
*/
Api.prototype._encodeBase64 = function ( data ) {
return window.btoa( data );
};
/**
* Sends a request to API and returns results.
*
* @param {string} module
* @param {string} action
* @param {Object} data
* @return {Object} jQuery XMLHttpRequest (jqXHR)
*/
Api.prototype._sendRequest = function ( module, action, data ) {
var requestData = data;
$.extend( requestData, { module: module, action: action } );
return $.ajax( {
url: banner.config.api.apiUrl,
data: requestData,
dataType: 'jsonp'
} );
};
/**
* Sends a data validation request to API and returns results.
*
* @param {Object} data
* @return {Promise}
* Resolved parameter:
* {Object} responseData object with keys:
* - status: "OK" or "ERR",
* - invalid: array containing names of fields with invalid values,
* - missing: array containing names of missing obligatory fields.
*/
Api.prototype.sendValidationRequest = function ( data ) {
return this._sendEncryptedRequest(
banner.config.api.validationModule,
banner.config.api.validationAction,
data
);
};
/**
* Sends a reqeust to the API to generate IBAN and BIC from bank data
*
* @param {Object} data
* @return {Promise}
* Resolved parameter:
* {Object} responseData object with keys:
* - status: "OK" or "ERR",
* - iban
* - bic
* - bankCode
* - bankName
* - account
* - message ( if status is ERR )
*/
Api.prototype.sendIbanGenerationRequest = function ( data ) {
return this._sendEncryptedRequest(
banner.config.api.ibanModule,
banner.config.api.ibanGenerationAction,
data
);
};
/**
* Sends a reqeust to the API to check the validate of an IBAN
*
* @param {Object} data
* @return {Promise}
* Resolved parameter:
* {Object} responseData object with keys:
* - status: "OK" or "ERR",
* - iban
* - bic
* - bankCode
* - bankName
* - account
* - message ( if status is ERR )
*/
Api.prototype.sendIbanCheckRequest = function ( data ) {
return this._sendEncryptedRequest(
banner.config.api.ibanModule,
banner.config.api.ibanCheckAction,
data
);
};
banner.api = new Api();
}( Banner, jQuery ) );
/**
* Class handling submitting of the donation form embedded in the banner.
*
* @licence GNU GPL v2+
* @author Leszek Manicki <leszek.manicki@wikimedia.de>
*/
( function ( banner, $ ) {
'use strict';
function Form() {
var self = this;
this.validated = false;
this.validationPending = false;
this.bankCheckPending = false;
this.invalidElements = 0;
$( document ).ready( function () {
self.amountValidationAnchor = $( '#amount75' );
self._initSubmitHandler();
self._initBankDataHandler();
self._initFieldClearHandlers();
self._initValidationResetHandler();
} );
}
Form.prototype._initSubmitHandler = function () {
/* globals alert */
var self = this,
form = $( '#' + banner.config.form.formId ),
formAction = banner.config.form.formAction.replace( '&', '&' ), // wikipedia.de mangles & chars
formData;
form.prop( 'action', formAction );
form.on( 'submit', function () {
if ( !self.validated && !self.validationPending ) {
self.validated = false;
self.validationPending = true;
self._clearValidity();
try {
formData = self._getFormData();
} catch ( err ) {
if ( err.alertMessage ) {
alert( err.alertMessage );
}
self.validationPending = false;
form.trigger( 'banner:validationFailed' );
return self.validated;
}
self.validateData( formData )
.then( function ( validationResult ) {
self.validationPending = false;
if ( validationResult.validated ) {
self.validated = true;
form.trigger( 'banner:validationSucceeded' );
} else {
self._applyValidationErrors( validationResult.fieldsMissingValue, validationResult.fieldsWithInvalidValue );
form.trigger( 'banner:validationFailed' );
}
} );
}
return self.validated;
} );
};
Form.prototype._initFieldClearHandlers = function () {
var clearBankData = function () {
$( '#' + banner.config.form.formId + ' input[name=bic]' ).val( '' );
$( '#' + banner.config.form.formId + ' input[name=iban]' ).val( '' );
$( '#' + banner.config.form.formId + ' input[name=konto]' ).val( '' );
$( '#' + banner.config.form.formId + ' input[name=blz]' ).val( '' );
$( '#' + banner.config.form.formId + ' input[name=bankname]' ).val( '' );
};
$( '#address-type-1' ).on( 'click', function () {
$( '#' + banner.config.form.formId + ' input[name=firma]' ).val( '' );
} );
$( '#address-type-2' ).on( 'click', function () {
$( '#' + banner.config.form.formId + ' input[name=vorname]' ).val( '' );
$( '#' + banner.config.form.formId + ' input[name=nachname]' ).val( '' );
$( '#' + banner.config.form.formId + ' select[name=titel]' ).val( '' );
} );
$( '#address-type-3' ).on( 'click', function () {
$( '#' + banner.config.form.formId + ' input[name=firma]' ).val( '' );
$( '#' + banner.config.form.formId + ' input[name=vorname]' ).val( '' );
$( '#' + banner.config.form.formId + ' input[name=nachname]' ).val( '' );
$( '#' + banner.config.form.formId + ' input[name=anrede]' ).prop( 'checked', false );
$( '#' + banner.config.form.formId + ' select[name=titel]' ).val( '' );
$( '#' + banner.config.form.formId + ' input[name=email]' ).val( '' );
$( '#' + banner.config.form.formId + ' input[name=plz]' ).val( '' );
$( '#' + banner.config.form.formId + ' input[name=ort]' ).val( '' );
$( '#' + banner.config.form.formId + ' input[name=strasse]' ).val( '' );
} );
$( '#WMDE_BannerForm-payment button' ).each( function ( index, element ) {
var $element = $( element );
if ( $element.data( 'behavior' ) === 'clearBankData' ) {
$element.on( 'click', clearBankData );
}
} );
};
Form.prototype._initBankDataHandler = function () {
$( '#account-number, #bank-code, #iban' ).on( 'change', this.checkBankData.bind( this ) );
};
Form.prototype._initValidationResetHandler = function () {
var self = this,
form = $( '#' + banner.config.form.formId );
form.on( 'banner:validationReset', function () {
self.validated = false;
} );
};
Form.prototype.checkBankData = function ( evt ) {
var cleanBankData = function ( data, isIBAN ) {
data = data.toString();
if ( isIBAN ) {
data = data.toUpperCase();
return data.replace( /[^0-9A-Z]/g, '' );
} else {
return data.replace( /[^0-9]/g, '' );
}
},
self = this,
data,
apiMethod,
accNumElm = $( '#account-number' ),
bankCodeElm = $( '#bank-code' );
if ( evt.target.id === 'account-number' || evt.target.id === 'bank-code' ) {
if ( accNumElm.val() === '' || bankCodeElm.val() === '' ) {
return;
}
data = {
accNum: cleanBankData( accNumElm.val(), false ),
bankCode: cleanBankData( bankCodeElm.val(), false )
};
apiMethod = banner.api.sendIbanGenerationRequest.bind( banner.api );
} else if ( evt.target.id === 'iban' ) {
data = { iban: cleanBankData( evt.target.value, true ) };
apiMethod = banner.api.sendIbanCheckRequest.bind( banner.api );
}
$( '#bic, #iban, #account-number, #bank-code' ).each( function ( index, element ) {
self._clearElementValidity( $( element ) );
} );
this.bankCheckPending = true;
apiMethod( data ).then( this._setBankdataAfterCheck.bind( this ) );
};
Form.prototype._setBankdataAfterCheck = function ( data ) {
var $iban = $( '#iban' ),
$accNumber = $( '#account-number' ),
errorMessage;
// payment method might have been switched already, check whether the related fields are visible
if ( $iban.is( ':visible' ) || $accNumber.is( ':visible' ) ) {
if ( data.status === 'OK' ) {
$iban.val( data.iban ? data.iban : '' );
$( '#bic' ).val( data.bic ? data.bic : '' );
$accNumber.val( data.account ? data.account : '' );
$( '#bank-code' ).val( data.bankCode ? data.bankCode : '' );
$( '#bank-name' ).val( data.bankName ? data.bankName : '' );
} else {
errorMessage = 'Die eingegebenen Bankdaten sind nicht korrekt.';
this._showError( $iban, errorMessage );
this._showError( $accNumber, errorMessage );
}
}
this.bankCheckPending = false;
};
/**
* Remove positive and negative validation information from all form fields
*/
Form.prototype._clearValidity = function () {
var self = this;
this.invalidElements = 0;
$( '#' + banner.config.form.formId + ' :input:not([type=hidden])' ).each( function ( index, element ) {
self._clearElementValidity( $( element ) );
} );
self._clearElementValidity( this.amountValidationAnchor );
};
Form.prototype._clearElementValidity = function ( $element ) {
var $parent = $element.parent();
$element.removeClass( 'invalid' ).removeClass( 'valid' );
$( '.validation', $parent ).removeClass( 'icon-bug' ).removeClass( 'icon-ok' );
$( '.form-field-error-box', $parent ).remove();
};
/**
* @return {Object}
*/
Form.prototype._getFormData = function () {
/* globals getAmount, validateAndSetPeriod */
var formId = banner.config.form.formId,
formData, amount, err;
if ( validateAndSetPeriod() === false ) {
throw new Error( 'Period was not set.' );
}
amount = getAmount();
if ( amount === false || amount < 1 ) {
err = new Error( 'Amount was not set.' );
err.alertMessage = 'Der Mindestbetrag beträgt 1 Euro.';
throw err;
}
formData = {
adresstyp: $( '#' + formId + ' input[name="adresstyp"]:checked' ).val(),
betrag: amount,
periode: $( '#' + formId + ' :input[name="periode"]' ).val(),
zahlweise: $( '#zahlweise' ).val()
};
if ( formData.adresstyp !== 'anonym' ) {
$.extend( formData, {
country: $( '#' + formId + ' select[name="country"]' ).val(),
email: $( '#' + formId + ' :input[name="email"]' ).val(),
ort: $( '#' + formId + ' :input[name="ort"]' ).val(),
plz: $( '#' + formId + ' :input[name="plz"]' ).val(),
strasse: $( '#' + formId + ' :input[name="strasse"]' ).val()
} );
}
if ( formData.adresstyp === 'person' ) {
$.extend( formData, {
anrede: $( '#' + formId + ' input[name="anrede"]:checked' ).val(),
titel: $( '#' + formId + ' select[name="titel"]' ).val(),
vorname: $( '#' + formId + ' :input[name="vorname"]' ).val(),
nachname: $( '#' + formId + ' :input[name="nachname"]' ).val()
} );
} else if ( formData.adresstyp === 'firma' ) {
$.extend( formData, {
firma: $( '#' + formId + ' :input[name="firma"]' ).val(),
anrede: 'Firma'
} );
}
if ( formData.zahlweise === 'BEZ' ) {
$.extend( formData, {
bankname: $( '#' + formId + ' :input[name="bankname"]' ).val(),
bic: $( '#' + formId + ' :input[name="bic"]' ).val(),
blz: $( '#' + formId + ' :input[name="blz"]' ).val(),
iban: $( '#' + formId + ' :input[name="iban"]' ).val(),
konto: $( '#' + formId + ' :input[name="konto"]' ).val()
} );
}
return formData;
};
/**
* Validates given data using API.
*
* @param {Object} data
* @return {Promise}
* Resolved parameter:
* {Object} object with keys:
* - validated: true or false,
* - fieldsMissingValue: array containing names of fields with invalid values,
* - fieldsWithInvalidValue: array containing names of missing obligatory fields.
*/
Form.prototype.validateData = function ( data ) {
return banner.api.sendValidationRequest( data )
.then( function ( responseData ) {
if ( responseData.status === 'OK' ) {
return {
validated: true
};
} else {
return {
validated: false,
fieldsMissingValue: responseData.missing,
fieldsWithInvalidValue: responseData.invalid
};
}
} );
};
/**
* @param {string[]} fieldsMissingValue
* @param {string[]} fieldsWithInvalidValue
*/
Form.prototype._applyValidationErrors = function ( fieldsMissingValue, fieldsWithInvalidValue ) {
var self = this;
this._applyAmountValidationErrors( fieldsMissingValue, fieldsWithInvalidValue );
$( '#' + banner.config.form.formId + ' :input:not([type=hidden])' ).each( function ( index, element ) {
if ( $.inArray( $( element ).attr( 'name' ), fieldsMissingValue ) > -1 ) {
self._markMissing( $( element ) );
self.invalidElements++;
return true;
}
if ( $.inArray( $( element ).attr( 'name' ), fieldsWithInvalidValue ) > -1 ) {
self._markInvalid( $( element ) );
self.invalidElements++;
return true;
}
self._markValid( $( element ) );
} );
};
Form.prototype._applyAmountValidationErrors = function ( fieldsMissingValue, fieldsWithInvalidValue ) {
var valueMissingIndex = $.inArray( 'betrag', fieldsMissingValue ),
valueInvalidIndex = $.inArray( 'betrag', fieldsWithInvalidValue ),
errorBox, errorText = '';
if ( valueInvalidIndex > -1 ) {
errorText = 'Bitte geben Sie einen gültigen Betrag ein.' ;
fieldsWithInvalidValue.splice( valueInvalidIndex, 1 );
// Invalid values cause this
if ( valueMissingIndex > -1 ) {
fieldsMissingValue.splice( valueMissingIndex, 1 );
}
}
if ( valueMissingIndex > -1 ) {
errorText = 'Bitte geben Sie einen Betrag ein.';
fieldsMissingValue.splice( valueMissingIndex, 1 );
}
if ( errorText ) {
errorBox = this._showError( this.amountValidationAnchor, errorText );
if ( errorBox ) {
this._addErrorRemovalHandler( $( '#' + banner.config.form.formId + ' .amount-radio' ), errorBox, 'click' );
this._addErrorRemovalHandler( $( '#amount-other-input' ), errorBox );
}
this.invalidElements++;
}
};
Form.prototype._markInvalid = function ( $element ) {
this._showError( $element );
};
Form.prototype._markMissing = function ( $element ) {
// Since the server-side validation does not get missing fields except for amount,
// we use a more generic error message and handle the errors the same
this._showError( $element );
};
/**
* Set the element parent class to "invalid", show invalid icon and add error text
*
* @param {jQuery} $element
* @return {jQuery} The error box
*/
Form.prototype._showError = function ( $element ) {
var $parent = $element.parent(),
errorText, $errorBox;
if ( arguments.length > 1 ) {
errorText = arguments[ 1 ];
} else {
errorText = 'Bitte korrigieren Sie dieses Feld.';
}
$element.addClass( 'invalid' );
$( '.validation', $parent ).addClass( 'icon-bug' );
if ( !$( '.form-field-error-box', $parent ).length ) {
// Only show one error box
if ( this.invalidElements ) {
return;
}
$errorBox = $(
'<div class="form-field-error-box"><div class="form-field-error-arrow"></div><span class="form-field-error-text">' +
errorText +
'</span></div></div>'
);
$parent.append( $errorBox );
this._addErrorRemovalHandler( $element, $errorBox );
this._addErrorRemovalHandler( $( '#WMDE_BannerForm-payment button' ), $errorBox, 'click' );
}
return $errorBox;
};
Form.prototype._addErrorRemovalHandler = function ( $element, $errorBox, eventName ) {
eventName = eventName || 'focus';
$element.one( eventName, function () {
$errorBox.remove();
} );
};
Form.prototype._markValid = function ( $element ) {
var $parent = $element.parent();
$element.addClass( 'valid' );
$( '.validation', $parent ).addClass( 'icon-ok' );
};
banner.form = new Form();
}( Banner, jQuery ) );