ECOM-4904 Move the program editor backbone app to Studio (#12962)

This commit is contained in:
Simon Chen
2016-07-19 19:09:51 -04:00
committed by GitHub
parent 7745e7cdf7
commit 1d768cde7a
65 changed files with 3283 additions and 108 deletions

View File

@@ -0,0 +1,21 @@
define([
'js/programs/models/api_config_model'
],
function( ApiConfigModel ) {
'use strict';
/**
* This js module implements the Singleton pattern for an instance
* of the ApiConfigModel Backbone model. It returns the same shared
* instance of that model anywhere it is required.
*/
var instance;
if (instance === undefined) {
instance = new ApiConfigModel();
}
return instance;
}
);

View File

@@ -0,0 +1,89 @@
define([
'jquery',
'underscore',
'js/programs/utils/api_config'
],
function( $, _, apiConfig ) {
'use strict';
var auth = {
autoSync: {
/**
* Override Backbone.sync to seamlessly attempt (re-)authentication when necessary.
*
* If a 401 error response is encountered while making a request to the Programs,
* API, this wrapper will attempt to request an id token from a custom endpoint
* via AJAX. Then the original request will be retried once more.
*
* Any other response than 401 on the original API request, or any error occurring
* on the retried API request (including 401), will be handled by the base sync
* implementation.
*
*/
sync: function( method, model, options ) {
var oldError = options.error;
this._setHeaders( options );
options.notifyOnError = false; // suppress Studio error pop-up that will happen if we get a 401
options.error = function(xhr, textStatus, errorThrown) {
if (xhr && xhr.status === 401) {
// attempt auth and retry
this._updateToken(function() {
// restore the original error handler
options.error = oldError;
options.notifyOnError = true; // if it fails again, let Studio notify.
delete options.xhr; // remove the failed (401) xhr from the last try.
// update authorization header
this._setHeaders( options );
Backbone.sync.call(this, method, model, options);
}.bind(this));
} else if (oldError) {
// fall back to the original error handler
oldError.call(this, xhr, textStatus, errorThrown);
}
}.bind(this);
return Backbone.sync.call(this, method, model, options);
},
/**
* Fix up headers on an imminent AJAX sync, ensuring that the JWT token is enclosed
* and that credentials are included when the request is being made cross-domain.
*/
_setHeaders: function( ajaxOptions ) {
ajaxOptions.headers = _.extend ( ajaxOptions.headers || {}, {
Authorization: 'JWT ' + apiConfig.get( 'idToken' )
});
ajaxOptions.xhrFields = _.extend( ajaxOptions.xhrFields || {}, {
withCredentials: true
});
},
/**
* Fetch a new id token from the configured endpoint, update the api config,
* and invoke the specified callback.
*/
_updateToken: function( success ) {
$.ajax({
url: apiConfig.get('authUrl'),
xhrFields: {
// See: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
withCredentials: true
},
crossDomain: true
}).done(function ( data ) {
// save the newly-retrieved id token
apiConfig.set( 'idToken', data.id_token );
}).done( success );
}
}
};
return auth;
}
);

View File

@@ -0,0 +1,16 @@
/**
* Reusable constants
*/
define([], function() {
'use strict';
return {
keyCodes: {
tab: 9,
enter: 13,
esc: 27,
up: 38,
down: 40
}
};
});

View File

@@ -0,0 +1,70 @@
define([
'backbone',
'backbone.validation',
'underscore',
'gettext'
],
function( Backbone, BackboneValidation, _ ) {
'use strict';
var errorClass = 'has-error',
messageEl = '.field-message',
messageContent = '.field-message-content';
// These are the same messages provided by Backbone.Validation,
// marked for translation.
// See: http://thedersen.com/projects/backbone-validation/#overriding-the-default-error-messages.
_.extend( Backbone.Validation.messages, {
required: gettext( '{0} is required' ),
acceptance: gettext( '{0} must be accepted' ),
min: gettext( '{0} must be greater than or equal to {1}' ),
max: gettext( '{0} must be less than or equal to {1}' ),
range: gettext( '{0} must be between {1} and {2}' ),
length: gettext( '{0} must be {1} characters' ),
minLength: gettext( '{0} must be at least {1} characters' ),
maxLength: gettext( '{0} must be at most {1} characters' ),
rangeLength: gettext( '{0} must be between {1} and {2} characters' ),
oneOf: gettext( '{0} must be one of: gettext( {1}' ),
equalTo: gettext( '{0} must be the same as {1}' ),
digits: gettext( '{0} must only contain digits' ),
number: gettext( '{0} must be a number' ),
email: gettext( '{0} must be a valid email' ),
url: gettext( '{0} must be a valid url' ),
inlinePattern: gettext( '{0} is invalid' )
});
_.extend( Backbone.Validation.callbacks, {
// Gets called when a previously invalid field in the
// view becomes valid. Removes any error message.
valid: function( view, attr, selector ) {
var $input = view.$( '[' + selector + '~="' + attr + '"]' ),
$message = $input.siblings( messageEl );
$input.removeClass( errorClass )
.removeAttr( 'data-error' );
$message.removeClass( errorClass )
.find( messageContent )
.text( '' );
},
// Gets called when a field in the view becomes invalid.
// Adds a error message.
invalid: function( view, attr, error, selector ) {
var $input = view.$( '[' + selector + '~="' + attr + '"]' ),
$message = $input.siblings( messageEl );
$input.addClass( errorClass )
.attr( 'data-error', error );
$message.addClass( errorClass )
.find( messageContent )
.text( $input.data('error') );
}
});
Backbone.Validation.configure({
labelFormatter: 'label'
});
}
);