Add CookieStorage.
This commit is contained in:
@@ -5,6 +5,9 @@ These are notable changes in edx-platform. This is a rolling list of changes,
|
||||
in roughly chronological order, most recent first. Add your entries at or near
|
||||
the top. Include a label indicating the component affected.
|
||||
|
||||
Blades: Adds CookieStorage utility for video player that provides convenient
|
||||
way to work with cookies.
|
||||
|
||||
Blades: Fix comparison of float numbers. BLD-434.
|
||||
|
||||
Blades: Allow regexp strings as the correct answer to a string response question. BLD-475.
|
||||
|
||||
149
common/lib/xmodule/xmodule/js/spec/video/cookie_storage_spec.js
Normal file
149
common/lib/xmodule/xmodule/js/spec/video/cookie_storage_spec.js
Normal file
@@ -0,0 +1,149 @@
|
||||
(function (requirejs, require, define) {
|
||||
require(
|
||||
['video/00_cookie_storage.js'],
|
||||
function (CookieStorage) {
|
||||
describe('CookieStorage', function () {
|
||||
var mostRecentCall;
|
||||
|
||||
beforeEach(function () {
|
||||
mostRecentCall = $.cookie.mostRecentCall;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
CookieStorage('test_storage').clear();
|
||||
});
|
||||
|
||||
describe('intialize', function () {
|
||||
it('with namespace', function () {
|
||||
var storage = CookieStorage('test_storage');
|
||||
|
||||
storage.setItem('item_1', 'value_1');
|
||||
expect(mostRecentCall.args[0]).toBe('test_storage');
|
||||
});
|
||||
|
||||
it('without namespace', function () {
|
||||
var storage = CookieStorage();
|
||||
|
||||
storage.setItem('item_1', 'value_1');
|
||||
expect(mostRecentCall.args[0]).toBe('cookieStorage');
|
||||
});
|
||||
});
|
||||
|
||||
it('unload', function () {
|
||||
var expected = JSON.stringify({
|
||||
storage: {
|
||||
'item_2': {
|
||||
value: 'value_2',
|
||||
session: false
|
||||
}
|
||||
},
|
||||
keys: ['item_2']
|
||||
}),
|
||||
storage = CookieStorage('test_storage');
|
||||
|
||||
storage.setItem('item_1', 'value_1', true);
|
||||
storage.setItem('item_2', 'value_2');
|
||||
|
||||
$(window).trigger('unload');
|
||||
expect(mostRecentCall.args[1]).toBe(expected);
|
||||
});
|
||||
|
||||
describe('methods: ', function () {
|
||||
var data = {
|
||||
storage: {
|
||||
'item_1': {
|
||||
value: 'value_1',
|
||||
session: false
|
||||
}
|
||||
},
|
||||
keys: ['item_1']
|
||||
},
|
||||
storage;
|
||||
|
||||
beforeEach(function () {
|
||||
$.cookie.andReturn(JSON.stringify(data));
|
||||
storage = CookieStorage('test_storage');
|
||||
});
|
||||
|
||||
describe('setItem', function () {
|
||||
it('pass correct data', function () {
|
||||
var expected = JSON.stringify({
|
||||
storage: {
|
||||
'item_1': {
|
||||
value: 'value_1',
|
||||
session: false
|
||||
},
|
||||
'item_2': {
|
||||
value: 'value_2',
|
||||
session: false
|
||||
},
|
||||
'item_3': {
|
||||
value: 'value_3',
|
||||
session: true
|
||||
},
|
||||
},
|
||||
keys: ['item_1', 'item_2', 'item_3']
|
||||
});
|
||||
|
||||
storage.setItem('item_2', 'value_2');
|
||||
storage.setItem('item_3', 'value_3', true);
|
||||
expect(mostRecentCall.args[0]).toBe('test_storage');
|
||||
expect(mostRecentCall.args[1]).toBe(expected);
|
||||
});
|
||||
|
||||
it('pass broken arguments', function () {
|
||||
$.cookie.reset();
|
||||
storage.setItem(null, 'value_1');
|
||||
expect($.cookie).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getItem', function () {
|
||||
it('item exist', function () {
|
||||
$.each(data['storage'], function(key, value) {
|
||||
expect(storage.getItem(key)).toBe(value['value']);
|
||||
});
|
||||
});
|
||||
|
||||
it('item does not exist', function () {
|
||||
expect(storage.getItem('nonexistent')).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeItem', function () {
|
||||
it('item exist', function () {
|
||||
var expected = JSON.stringify({
|
||||
storage: {},
|
||||
keys: []
|
||||
});
|
||||
|
||||
storage.removeItem('item_1');
|
||||
expect(mostRecentCall.args[1]).toBe(expected);
|
||||
});
|
||||
|
||||
it('item does not exist', function () {
|
||||
storage.removeItem('nonexistent');
|
||||
expect(mostRecentCall.args[1]).toBe(JSON.stringify(data));
|
||||
});
|
||||
});
|
||||
|
||||
it('clear', function () {
|
||||
storage.clear();
|
||||
expect(mostRecentCall.args[1]).toBe(null);
|
||||
});
|
||||
|
||||
describe('key', function () {
|
||||
it('key exist', function () {
|
||||
$.each(data['keys'], function(index, name) {
|
||||
expect(storage.key(index)).toBe(name);
|
||||
});
|
||||
});
|
||||
|
||||
it('key is grater than keys list', function () {
|
||||
expect(storage.key(100)).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}(RequireJS.requirejs, RequireJS.require, RequireJS.define));
|
||||
@@ -22,11 +22,12 @@
|
||||
describe('constructor', function() {
|
||||
beforeEach(function() {
|
||||
spyOn($.fn, 'slider').andCallThrough();
|
||||
$.cookie.andReturn('75');
|
||||
initialize();
|
||||
});
|
||||
|
||||
it('initialize currentVolume to 100', function() {
|
||||
expect(state.videoVolumeControl.currentVolume).toEqual(1);
|
||||
it('initialize currentVolume to 75', function() {
|
||||
expect(state.videoVolumeControl.currentVolume).toEqual(75);
|
||||
});
|
||||
|
||||
it('render the volume control', function() {
|
||||
|
||||
196
common/lib/xmodule/xmodule/js/src/video/00_cookie_storage.js
Normal file
196
common/lib/xmodule/xmodule/js/src/video/00_cookie_storage.js
Normal file
@@ -0,0 +1,196 @@
|
||||
(function (requirejs, require, define) {
|
||||
|
||||
define(
|
||||
'video/00_cookie_storage.js',
|
||||
[],
|
||||
function() {
|
||||
"use strict";
|
||||
/**
|
||||
* Provides convenient way to work with cookies.
|
||||
*
|
||||
* Maximum 4096 bytes can be stored per namespace.
|
||||
*
|
||||
* @TODO: Uses localStorage if available.
|
||||
*
|
||||
* @param {string} namespace Namespace that is used to store data.
|
||||
* @return {object} CookieStorage API.
|
||||
*/
|
||||
|
||||
|
||||
var CookieStorage = function (namespace) {
|
||||
var Storage;
|
||||
|
||||
/**
|
||||
* Returns an empty storage with proper data structure.
|
||||
*
|
||||
* @private
|
||||
* @return {object} Empty storage.
|
||||
*/
|
||||
var _getEmptyStorage = function () {
|
||||
return {
|
||||
storage: {},
|
||||
keys: []
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the current value associated with the given namespace.
|
||||
* If data doesn't exist or has data with incorrect interface, it creates
|
||||
* an empty storage with proper data structure.
|
||||
*
|
||||
* @private
|
||||
* @param {string} namespace Namespace that is used to store data.
|
||||
* @return {object} Stored data or an empty storage.
|
||||
*/
|
||||
var _getData = function (namespace) {
|
||||
var data;
|
||||
|
||||
try {
|
||||
data = JSON.parse($.cookie(namespace));
|
||||
} catch (err) { }
|
||||
|
||||
if (!data || !data['storage'] || !data['keys']) {
|
||||
return _getEmptyStorage();
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears cookies that has flag `session` equals true.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var _clearSession = function () {
|
||||
Storage['keys'] = $.grep(Storage['keys'], function(key_name, index) {
|
||||
if (Storage['storage'][key_name]['session']) {
|
||||
delete Storage['storage'][key_name];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$.cookie(namespace, JSON.stringify(Storage), {
|
||||
expires: -1,
|
||||
path: '/'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds new value to the storage or rewrites existent.
|
||||
*
|
||||
* @param {string} name Identifier of the data.
|
||||
* @param {any} value Data to store.
|
||||
* @param {boolean} useSession Data with this flag will be removed on
|
||||
* window unload.
|
||||
*/
|
||||
var setItem = function (name, value, useSession) {
|
||||
if (name) {
|
||||
if ($.inArray(name, Storage['keys']) === -1) {
|
||||
Storage['keys'].push(name);
|
||||
}
|
||||
|
||||
Storage['storage'][name] = {
|
||||
value: value,
|
||||
session: useSession ? true : false
|
||||
};
|
||||
|
||||
$.cookie(namespace, JSON.stringify(Storage), {
|
||||
expires: 3650,
|
||||
path: '/'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the current value associated with the given name.
|
||||
*
|
||||
* @param {string} name Identifier of the data.
|
||||
* @return {any} The current value associated with the given name.
|
||||
* If the given key does not exist in the list
|
||||
* associated with the object then this method must return null.
|
||||
*/
|
||||
var getItem = function (name) {
|
||||
try {
|
||||
return Storage['storage'][name]['value'];
|
||||
} catch (err) { }
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the current value associated with the given name.
|
||||
*
|
||||
* @param {string} name Identifier of the data.
|
||||
*/
|
||||
var removeItem = function (name) {
|
||||
delete Storage['storage'][name];
|
||||
|
||||
Storage['keys'] = $.grep(Storage['keys'], function(key_name, index) {
|
||||
return name !== key_name;
|
||||
});
|
||||
|
||||
$.cookie(namespace, JSON.stringify(Storage), {
|
||||
expires: 3650,
|
||||
path: '/'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Empties the storage.
|
||||
*
|
||||
*/
|
||||
var clear = function () {
|
||||
Storage = _getEmptyStorage();
|
||||
$.cookie(namespace, null, {
|
||||
expires: -1,
|
||||
path: '/'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the name of the `n`th key in the list.
|
||||
*
|
||||
* @param {number} n Index of the key.
|
||||
* @return {string} Name of the `n`th key in the list.
|
||||
* If `n` is greater than or equal to the number of key/value pairs
|
||||
* in the object, then this method must return `null`.
|
||||
*/
|
||||
var key = function (n) {
|
||||
if (n >= Storage['keys'].length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Storage['keys'][n];
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the module: creates a storage with proper namespace, binds
|
||||
* `unload` event.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
(function initialize() {
|
||||
if (!namespace) {
|
||||
namespace = 'cookieStorage';
|
||||
}
|
||||
Storage = _getData(namespace);
|
||||
|
||||
$(window).unload(_clearSession);
|
||||
}());
|
||||
|
||||
return {
|
||||
clear: clear,
|
||||
getItem: getItem,
|
||||
key: key,
|
||||
removeItem: removeItem,
|
||||
setItem: setItem
|
||||
};
|
||||
};
|
||||
|
||||
return CookieStorage;
|
||||
});
|
||||
|
||||
}(RequireJS.requirejs, RequireJS.require, RequireJS.define));
|
||||
16
common/lib/xmodule/xmodule/js/src/video/README.rst
Normal file
16
common/lib/xmodule/xmodule/js/src/video/README.rst
Normal file
@@ -0,0 +1,16 @@
|
||||
Video player persists some user preferences between videos and these
|
||||
preferences are stored on server.
|
||||
|
||||
Content for sequential positions is loaded just once on page load and is not
|
||||
updated when the user navigates between sequential positions. So, we doesn't
|
||||
have an actual data from server.
|
||||
To resolve this issue, cookies are used as temporary storage and are removed
|
||||
on page unload.
|
||||
|
||||
How it works:
|
||||
1) On page load: cookies are empty and player get an actual data from server.
|
||||
2) When user change some preferences, new value is stored to cookie;
|
||||
3) If we navigate to another sequential position, video player get an actual data
|
||||
from cookies.
|
||||
4) Close the page: `unload` event fires and we clear our cookies and send user
|
||||
preferences to the server.
|
||||
@@ -25,7 +25,6 @@ from xmodule.x_module import XModule
|
||||
from xmodule.editing_module import TabsEditingDescriptor
|
||||
from xmodule.raw_module import EmptyDataRawDescriptor
|
||||
from xmodule.xml_module import is_pointer_tag, name_to_pathname, deserialize_field
|
||||
from xmodule.modulestore import Location
|
||||
from xblock.fields import Scope, String, Boolean, List, Integer, ScopeIds
|
||||
from xmodule.fields import RelativeTime
|
||||
|
||||
@@ -134,8 +133,11 @@ class VideoModule(VideoFields, XModule):
|
||||
video_time = 0
|
||||
icon_class = 'video'
|
||||
|
||||
# To make sure that js files are called in proper order we use numerical
|
||||
# index. We do that to avoid issues that occurs in tests.
|
||||
js = {
|
||||
'js': [
|
||||
resource_string(__name__, 'js/src/video/00_cookie_storage.js'),
|
||||
resource_string(__name__, 'js/src/video/00_resizer.js'),
|
||||
resource_string(__name__, 'js/src/video/01_initialize.js'),
|
||||
resource_string(__name__, 'js/src/video/025_focus_grabber.js'),
|
||||
|
||||
Reference in New Issue
Block a user