Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ the top. Include a label indicating the component affected.

Blades: Add possibility to use multiple LTI tools per page.

Blades: LTI module can now load external content in a new window.

LMS: Disable data download buttons on the instructor dashboard for large courses

LMS: Ported bulk emailing to the beta instructor dashboard.
Expand Down
1 change: 1 addition & 0 deletions cms/static/sass/_mixins-inherited.scss
4 changes: 4 additions & 0 deletions cms/static/sass/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ $lightBluishGrey: rgb(197, 207, 223);
$lightBluishGrey2: rgb(213, 220, 228);
$error-red: rgb(253, 87, 87);


//carryover from LMS for xmodules
$sidebar-color: rgb(246, 246, 246);

// type
$sans-serif: $f-sans-serif;
$body-line-height: golden-ratio(.875em, 1);
Expand Down
31 changes: 18 additions & 13 deletions common/lib/xmodule/xmodule/css/lti/lti.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,23 @@ div.lti {
// align center
margin: 0 auto;

h3.error_message {
display: block;
.wrapper-lti-link {
@include font-size(14);
position: relative;
background-color: $sidebar-color;
padding: ($baseline*1.8) ($baseline*1.5) ($baseline*1.1) $baseline;

.lti-link {
position: absolute;
top: ($baseline*1.8);
right: $baseline;

.link_lti_new_window {
@extend .gray-button;
@include font-size(13);
@include line-height(14);
}
}
}

form.ltiLaunchForm {
Expand All @@ -13,18 +28,8 @@ div.lti {
iframe.ltiLaunchFrame {
width: 100%;
height: 800px;
display: none;
display: block;
border: 0px;
overflow-x: hidden;
}

&.rendered {
iframe.ltiLaunchFrame {
display: block;
}

h3.error_message {
display: none;
}
}
}
59 changes: 27 additions & 32 deletions common/lib/xmodule/xmodule/js/fixtures/lti.html
Original file line number Diff line number Diff line change
@@ -1,36 +1,31 @@
<div id="lti_id" class="lti">
<div class="lti-wrapper">
<div id="lti_id" class="lti" data-open_in_a_new_page="true">

<form
action="http://www.example.com"
name="ltiLaunchForm"
class="ltiLaunchForm"
method="post"
target="ltiLaunchFrame"
enctype="application/x-www-form-urlencoded"
>
<input name="launch_presentation_return_url" value="" />
<input name="lti_version" value="LTI-1p0" />
<input name="user_id" value="student" />
<input name="oauth_nonce" value="28347958723982798572" />
<input name="oauth_timestamp" value="2389479832" />
<input name="oauth_consumer_key" value="" />
<input name="lis_result_sourcedid" value="" />
<input name="oauth_signature_method" value="HMAC-SHA1" />
<input name="oauth_version" value="1.0" />
<input name="role" value="student" />
<input name="lis_outcome_service_url" value="" />
<input name="oauth_signature" value="89ru3289r3ry283y3r82ryr38yr" />
<input name="lti_message_type" value="basic-lti-launch-request" />
<input name="oauth_callback" value="about:blank" />
<form
action=""
name="ltiLaunchForm"
class="ltiLaunchForm"
method="post"
target="_blank"
enctype="application/x-www-form-urlencoded"
>
<input name="launch_presentation_return_url" value="" />
<input name="lti_version" value="LTI-1p0" />
<input name="user_id" value="student" />
<input name="oauth_nonce" value="28347958723982798572" />
<input name="oauth_timestamp" value="2389479832" />
<input name="oauth_consumer_key" value="" />
<input name="lis_result_sourcedid" value="" />
<input name="oauth_signature_method" value="HMAC-SHA1" />
<input name="oauth_version" value="1.0" />
<input name="role" value="student" />
<input name="lis_outcome_service_url" value="" />
<input name="oauth_signature" value="89ru3289r3ry283y3r82ryr38yr" />
<input name="lti_message_type" value="basic-lti-launch-request" />
<input name="oauth_callback" value="about:blank" />

<input type="submit" value="Press to Launch" />
</form>

<h3 class="error_message">
Please provide launch_url. Click "Edit", and fill in the
required fields.
</h3>

<iframe name="ltiLaunchFrame" class="ltiLaunchFrame" src=""></iframe>
<input type="submit" value="Press to Launch" />
</form>

</div>
</div>
155 changes: 118 additions & 37 deletions common/lib/xmodule/xmodule/js/spec/lti/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,77 +5,158 @@
*
*
* The front-end part of the LTI module is really simple. If an action
* is set for the hidden LTI form, then it is submited, and the results are
* redirected to an iframe.
* is set for the hidden LTI form, then it is submitted, and the results are
* redirected to an iframe or to a new window (based on the
* "open_in_a_new_page" attribute).
*
* We will test that the form is only submited when the action is set (i.e.
* not empty).
* We will test that the form is only submitted when the action is set (i.e.
* not empty, and not the default one).
*
* Other aspects of LTI module will be covered by Python unit tests and
* acceptance tests.
*
*/

/*
* "Hence that general is skilful in attack whose opponent does not know what
* to defend; and he is skilful in defense whose opponent does not know what
* "Hence that general is skillful in attack whose opponent does not know what
* to defend; and he is skillful in defense whose opponent does not know what
* to attack."
*
* ~ Sun Tzu
*/

(function () {
var element, container, form, link,
IN_NEW_WINDOW = 'true',
IN_IFRAME = 'false',
EMPTY_URL = '',
DEFAULT_URL = 'http://www.example.com',
NEW_URL = 'http://www.example.com/some_book';

function initialize(target, action) {
var tempEl;

loadFixtures('lti.html');

element = $('.lti-wrapper');
container = element.find('.lti');
form = container.find('.ltiLaunchForm');

if (target === IN_IFRAME) {
container.data('open_in_a_new_page', 'false');
form.attr('target', 'ltiLaunchFrame');
}

form.attr('action', action);

// If we have a new proper action (non-default), we create either
// a link that will submit the form, or an iframe that will contain
// the answer of auto submitted form.
if (action !== EMPTY_URL && action !== DEFAULT_URL) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@valera-rozuvan this is already checked in template. No need to check it twice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case we need this test in order to know if we have to generate an <a /> or an <iframe /> element.

if (target === IN_NEW_WINDOW) {
$('<a />', {
href: '#',
class: 'link_lti_new_window'
}).appendTo(container);

link = container.find('.link_lti_new_window');
} else {
$('<iframe />', {
name: 'ltiLaunchFrame',
class: 'ltiLaunchFrame',
src: ''
}).appendTo(container);
}
}

spyOnEvent(form, 'submit');

LTI(element);
}

describe('LTI', function () {
describe('constructor', function () {
describe('before settings were filled in', function () {
var element, errorMessage, frame;
describe('initialize', function () {
describe(
'open_in_a_new_page is "true", launch URL is empty',
function () {

// This function will be executed before each of the it() specs
// in this suite.
beforeEach(function () {
loadFixtures('lti.html');
initialize(IN_NEW_WINDOW, EMPTY_URL);
});

it('form is not submitted', function () {
expect('submit').not.toHaveBeenTriggeredOn(form);
});
});

element = $('#lti_id');
errorMessage = element.find('.error_message');
form = element.find('.ltiLaunchForm');
frame = element.find('.ltiLaunchFrame');
describe(
'open_in_a_new_page is "true", launch URL is default',
function () {

spyOnEvent(form, 'submit');
beforeEach(function () {
initialize(IN_NEW_WINDOW, DEFAULT_URL);
});

LTI(element);
it('form is not submitted', function () {
expect('submit').not.toHaveBeenTriggeredOn(form);
});
});

it(
'when URL setting is not filled form is not submited',
function () {
describe(
'open_in_a_new_page is "true", launch URL is not empty, and ' +
'not default',
function () {

beforeEach(function () {
initialize(IN_NEW_WINDOW, NEW_URL);
});

it('form is not submitted', function () {
expect('submit').not.toHaveBeenTriggeredOn(form);
});

it('after link is clicked, form is submitted', function () {
link.trigger('click');

expect('submit').toHaveBeenTriggeredOn(form);
});
});

describe('After the settings were filled in', function () {
var element, errorMessage, frame;
describe(
'open_in_a_new_page is "false", launch URL is empty',
function () {

// This function will be executed before each of the it() specs
// in this suite.
beforeEach(function () {
loadFixtures('lti.html');
initialize(IN_IFRAME, EMPTY_URL);
});

element = $('#lti_id');
errorMessage = element.find('.error_message');
form = element.find('.ltiLaunchForm');
frame = element.find('.ltiLaunchFrame');
it('form is not submitted', function () {
expect('submit').not.toHaveBeenTriggeredOn(form);
});
});

spyOnEvent(form, 'submit');
describe(
'open_in_a_new_page is "false", launch URL is default',
function () {

// The user "fills in" the necessary settings, and the
// form will get an action URL.
form.attr('action', 'http://www.example.com/test_submit');
beforeEach(function () {
initialize(IN_IFRAME, DEFAULT_URL);
});

LTI(element);
it('form is not submitted', function () {
expect('submit').not.toHaveBeenTriggeredOn(form);
});
});

describe(
'open_in_a_new_page is "false", launch URL is not empty, ' +
'and not default',
function () {

beforeEach(function () {
initialize(IN_IFRAME, NEW_URL);
});

it('when URL setting is filled form is submited', function () {
it('form is submitted', function () {
expect('submit').toHaveBeenTriggeredOn(form);
});
});
Expand Down
Loading