Merge branch 'MDL-63793-master' of git://github.com/peterRd/moodle

This commit is contained in:
Andrew Nicols 2018-11-19 09:57:50 +08:00
commit ff85fe3e53
14 changed files with 233 additions and 27 deletions

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,8 @@ define(
'core/notification',
'core/templates',
'core_course/events',
'block_myoverview/selectors'
'block_myoverview/selectors',
'core/paged_content_events',
],
function(
$,
@ -42,7 +43,8 @@ function(
Notification,
Templates,
CourseEvents,
Selectors
Selectors,
PagedContentEvents
) {
var SELECTORS = {
@ -75,6 +77,8 @@ function(
var lastLimit = 0;
var namespace = null;
/**
* Get filter values from DOM.
*
@ -95,6 +99,7 @@ function(
var DEFAULT_PAGED_CONTENT_CONFIG = {
ignoreControlWhileLoading: true,
controlPlacementBottom: true,
persistentLimitKey: 'block_myoverview_user_paging_preference'
};
/**
@ -377,17 +382,58 @@ function(
}
};
/**
* Return the callback to be passed to the subscribe event
*
* @param {Number} limit The paged limit that is passed through the event
*/
var setLimit = function(limit) {
this.find(Selectors.courseView.region).attr('data-paging', limit);
};
/**
* Intialise the paged list and cards views on page load.
* Returns an array of paged contents that we would like to handle here
*
* @param {object} root The root element for the courses view
* @param {string} namespace The namespace for all the events attached
*/
var registerPagedEventHandlers = function(root, namespace) {
var event = namespace + PagedContentEvents.SET_ITEMS_PER_PAGE_LIMIT;
PubSub.subscribe(event, setLimit.bind(root));
};
/**
* Intialise the courses list and cards views on page load.
*
* @param {object} root The root element for the courses view.
* @param {object} content The content element for the courses view.
*/
var initializePagedContent = function(root) {
namespace = "block_myoverview_" + root.attr('id') + "_" + Math.random();
var itemsPerPage = NUMCOURSES_PERPAGE;
var pagingLimit = parseInt(root.find(Selectors.courseView.region).attr('data-paging'), 10);
if (pagingLimit) {
itemsPerPage = NUMCOURSES_PERPAGE.map(function(value) {
var active = false;
if (value == pagingLimit) {
active = true;
}
return {
value: value,
active: active
};
});
}
var filters = getFilterValues(root);
var config = $.extend({}, DEFAULT_PAGED_CONTENT_CONFIG);
config.eventNamespace = namespace;
var pagedContentPromise = PagedContentFactory.createWithLimit(
NUMCOURSES_PERPAGE,
itemsPerPage,
function(pagesData, actions) {
var promises = [];
@ -471,10 +517,11 @@ function(
return promises;
},
DEFAULT_PAGED_CONTENT_CONFIG
config
);
pagedContentPromise.then(function(html, js) {
registerPagedEventHandlers(root, namespace);
return Templates.replaceNodeContents(root.find(Selectors.courseView.region), html, js);
}).catch(Notification.exception);
};
@ -556,12 +603,12 @@ function(
lastPage = 0;
courseOffset = 0;
initializePagedContent(root);
if (!root.attr('data-init')) {
registerEventListeners(root);
root.attr('data-init', true);
}
initializePagedContent(root);
};
/**

View File

@ -52,8 +52,9 @@ class block_myoverview extends block_base {
$group = get_user_preferences('block_myoverview_user_grouping_preference');
$sort = get_user_preferences('block_myoverview_user_sort_preference');
$view = get_user_preferences('block_myoverview_user_view_preference');
$paging = get_user_preferences('block_myoverview_user_paging_preference');
$renderable = new \block_myoverview\output\main($group, $sort, $view);
$renderable = new \block_myoverview\output\main($group, $sort, $view, $paging);
$renderer = $this->page->get_renderer('block_myoverview');
$this->content = new stdClass();

View File

@ -59,6 +59,13 @@ class main implements renderable, templatable {
*/
private $view;
/**
* Store the paging preference
*
* @var string String matching the paging constants defined in myoverview/lib.php
*/
private $paging;
/**
* main constructor.
* Initialize the user preferences
@ -67,10 +74,11 @@ class main implements renderable, templatable {
* @param string $sort Sort user preference
* @param string $view Display user preference
*/
public function __construct($grouping, $sort, $view) {
public function __construct($grouping, $sort, $view, $paging) {
$this->grouping = $grouping ? $grouping : BLOCK_MYOVERVIEW_GROUPING_ALL;
$this->sort = $sort ? $sort : BLOCK_MYOVERVIEW_SORTING_TITLE;
$this->view = $view ? $view : BLOCK_MYOVERVIEW_VIEW_CARD;
$this->paging = $paging ? $paging : BLOCK_MYOVERVIEW_PAGING_12;
}
/**
@ -101,7 +109,8 @@ class main implements renderable, templatable {
'nocoursesimg' => $nocoursesurl,
'grouping' => $this->grouping,
'sort' => $this->sort == BLOCK_MYOVERVIEW_SORTING_TITLE ? 'fullname' : 'ul.timeaccess desc',
'view' => $this->view
'view' => $this->view,
'paging' => $this->paging
];
$preferences = $this->get_preferences_as_booleans();

View File

@ -49,6 +49,8 @@ class provider implements \core_privacy\local\metadata\provider, user_preference
$collection->add_user_preference('block_myoverview_user_view_preference', 'privacy:metadata:overviewviewpreference');
$collection->add_user_preference('block_myoverview_user_grouping_preference',
'privacy:metadata:overviewgroupingpreference');
$collection->add_user_preference('block_myoverview_user_paging_preference',
'privacy:metadata:overviewpagingpreference');
return $collection;
}
/**
@ -94,5 +96,13 @@ class provider implements \core_privacy\local\metadata\provider, user_preference
);
}
}
$preference = get_user_preferences('block_myoverview_user_paging_preference', null, $userid);
if (isset($preference)) {
\core_privacy\local\request\writer::export_user_preference('block_myoverview',
'block_myoverview_user_paging_preference',
$preference,
get_string('privacy:metadata:overviewpagingpreference', 'block_myoverview'));
}
}
}

View File

@ -60,6 +60,7 @@ $string['pluginname'] = 'Course overview';
$string['privacy:metadata:overviewsortpreference'] = 'The Course overview block sort preference.';
$string['privacy:metadata:overviewviewpreference'] = 'The Course overview block view preference.';
$string['privacy:metadata:overviewgroupingpreference'] = 'The Course overview block grouping preference.';
$string['privacy:metadata:overviewpagingpreference'] = 'The Course overview block paging preference.';
$string['removefromfavourites'] = 'Unstar this course';
$string['summary'] = 'Summary';
$string['title'] = 'Title';

View File

@ -48,6 +48,13 @@ define('BLOCK_MYOVERVIEW_VIEW_CARD', 'cards');
define('BLOCK_MYOVERVIEW_VIEW_LIST', 'list');
define('BLOCK_MYOVERVIEW_VIEW_SUMMARY', 'summary');
/**
* Constants for the user paging preferences
*/
define('BLOCK_MYOVERVIEW_PAGING_12', 12);
define('BLOCK_MYOVERVIEW_PAGING_24', 24);
define('BLOCK_MYOVERVIEW_PAGING_48', 48);
/**
* Get the current user preferences that are available
*
@ -95,5 +102,16 @@ function block_myoverview_user_preferences() {
'default' => 'none'
);
$preferences['block_myoverview_user_paging_preference'] = array(
'null' => NULL_NOT_ALLOWED,
'default' => BLOCK_MYOVERVIEW_PAGING_12,
'type' => PARAM_INT,
'choices' => array(
BLOCK_MYOVERVIEW_PAGING_12,
BLOCK_MYOVERVIEW_PAGING_24,
BLOCK_MYOVERVIEW_PAGING_48
)
);
return $preferences;
}

View File

@ -33,6 +33,7 @@
data-grouping="{{grouping}}"
data-sort="{{sort}}"
data-prev-display="{{view}}"
data-paging="{{paging}}"
data-nocoursesimg="{{nocoursesimg}}">
<div data-region="course-view-content">
<div data-region="courses-loading-placeholder">

View File

@ -0,0 +1,57 @@
@block @block_myoverview @javascript
Feature: The my overview block allows users to persistence of their page limits
Background:
Given the following "users" exist:
| username | firstname | lastname | email | idnumber |
| student1 | Student | X | student1@example.com | S1 |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
| Course 2 | C2 | 0 |
| Course 3 | C3 | 0 |
| Course 4 | C4 | 0 |
| Course 5 | C5 | 0 |
| Course 6 | C6 | 0 |
| Course 7 | C7 | 0 |
| Course 8 | C8 | 0 |
| Course 9 | C9 | 0 |
| Course 10 | C10 | 0 |
| Course 11 | C11 | 0 |
| Course 12 | C12 | 0 |
| Course 13 | C13 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
| student1 | C4 | student |
| student1 | C5 | student |
| student1 | C6 | student |
| student1 | C7 | student |
| student1 | C8 | student |
| student1 | C9 | student |
| student1 | C10 | student |
| student1 | C11 | student |
| student1 | C12 | student |
| student1 | C13 | student |
Scenario: Toggle the page limit between page reloads
Given I log in as "student1"
When I click on "Show 12 items per page" "button" in the "Course overview" "block"
And I click on "24" "link"
Then I should see "Course 9"
And I reload the page
Then I should see "Course 9"
And I should see "24" in the "[data-action='limit-toggle']" "css_element"
And I log out
Scenario: Toggle the page limit between grouping changes
Given I log in as "student1"
When I click on "Show 12 items per page" "button" in the "Course overview" "block"
And I click on "24" "link"
And I click on "All" "button" in the "Course overview" "block"
And I click on "In progress" "link" in the "Course overview" "block"
Then I should see "Course 9"
And I should see "24" in the "[data-action='limit-toggle']" "css_element"
And I log out

View File

@ -50,14 +50,17 @@ class block_myoverview_privacy_testcase extends \core_privacy\tests\provider_tes
*
* @dataProvider user_preference_provider
*/
public function test_export_user_preferences($type, $value) {
public function test_export_user_preferences($type, $value, $expected) {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
set_user_preference($type, $value, $user);
provider::export_user_preferences($user->id);
$writer = writer::with_context(\context_system::instance());
$blockpreferences = $writer->get_user_preferences('block_myoverview');
$this->assertEquals(get_string($value, 'block_myoverview'), $blockpreferences->{$type}->value);
if (!$expected) {
$expected = get_string($value, 'block_myoverview');
}
$this->assertEquals($expected, $blockpreferences->{$type}->value);
}
/**
@ -67,15 +70,16 @@ class block_myoverview_privacy_testcase extends \core_privacy\tests\provider_tes
*/
public function user_preference_provider() {
return array(
array('block_myoverview_user_sort_preference', 'lastaccessed'),
array('block_myoverview_user_sort_preference', 'title'),
array('block_myoverview_user_grouping_preference', 'all'),
array('block_myoverview_user_grouping_preference', 'inprogress'),
array('block_myoverview_user_grouping_preference', 'future'),
array('block_myoverview_user_grouping_preference', 'past'),
array('block_myoverview_user_view_preference', 'card'),
array('block_myoverview_user_view_preference', 'list'),
array('block_myoverview_user_view_preference', 'summary')
array('block_myoverview_user_sort_preference', 'lastaccessed', ''),
array('block_myoverview_user_sort_preference', 'title', ''),
array('block_myoverview_user_grouping_preference', 'all', ''),
array('block_myoverview_user_grouping_preference', 'inprogress', ''),
array('block_myoverview_user_grouping_preference', 'future', ''),
array('block_myoverview_user_grouping_preference', 'past', ''),
array('block_myoverview_user_view_preference', 'card', ''),
array('block_myoverview_user_view_preference', 'list', ''),
array('block_myoverview_user_view_preference', 'summary', ''),
array('block_myoverview_user_paging_preference', 12, 12)
);
}

View File

@ -1 +1 @@
define(["jquery","core/paged_content_pages","core/paged_content_paging_bar","core/paged_content_paging_bar_limit_selector","core/paged_content_paging_dropdown"],function(a,b,c,d,e){var f=function(f,g){f=a(f);var h=f.find(b.rootSelector),i=f.find(c.rootSelector),j=f.find(e.rootSelector),k=f.find(d.rootSelector),l=f.attr("id");b.init(h,l,g),i.length&&c.init(i,l),k.length&&d.init(k,l),j.length&&e.init(j,l)};return{init:f,rootSelector:'[data-region="paged-content-container"]'}});
define(["jquery","core/paged_content_pages","core/paged_content_paging_bar","core/paged_content_paging_bar_limit_selector","core/paged_content_paging_dropdown"],function(a,b,c,d,e){var f=function(f,g,h){f=a(f);var i=f.find(b.rootSelector),j=f.find(c.rootSelector),k=f.find(e.rootSelector),l=f.find(d.rootSelector),m=f.attr("id");h&&(m=h),b.init(i,m,g),j.length&&c.init(j,m),l.length&&d.init(l,m),k.length&&e.init(k,m)};return{init:f,rootSelector:'[data-region="paged-content-container"]'}});

View File

@ -1 +1 @@
define(["jquery","core/templates","core/notification","core/paged_content","core/paged_content_events","core/pubsub"],function(a,b,c,d,e,f){var g={PAGED_CONTENT:"core/paged_content"},h={ITEMS_PER_PAGE_SINGLE:25,ITEMS_PER_PAGE_ARRAY:[25,50,100,0],MAX_PAGES:3},i=function(){return{pagingbar:!1,pagingdropdown:!1,skipjs:!0,ignorecontrolwhileloading:!0,controlplacementbottom:!1}},j=function(){return{showitemsperpageselector:!1,itemsperpage:35,previous:!0,next:!0,activepagenumber:1,hidecontrolonsinglepage:!0,pages:[]}},k=function(a,b){var c=1;if(a>0){var d=a%b;d?(a-=d,c=a/b+1):c=a/b}return c},l=function(b,c){null===c&&(c=h.ITEMS_PER_PAGE_SINGLE),a.isArray(c)&&(c=c[0]);var d=j();d.itemsperpage=c;for(var e=k(b,c),f=1;f<=e;f++){var g={number:f,page:""+f};1===f&&(g.active=!0),d.pages.push(g)}return d},m=function(b){if(a.isArray(b)){var c=b.map(function(a){return"number"==typeof a?{value:a,active:!1}:a}),d=c.filter(function(a){return a.active});return d.length||(c[0].active=!0),c}return b},n=function(b){null===b&&(b=h.ITEMS_PER_PAGE_ARRAY);var c=j();return c.itemsperpage=m(b),c.showitemsperpageselector=a.isArray(b),c},o=function(a,b){return a?l(a,b):n(b)},p=function(b,c){if(null===b&&(b=h.ITEMS_PER_PAGE_SINGLE),a.isArray(b))return{options:b};var d={options:[]},e=0,f=0,g=h.MAX_PAGES;c.hasOwnProperty("maxPages")&&(g=c.maxPages);for(var i=1;i<=g;i++){var j=0;i<=2?(j=b,f=b):(f=2*f,j=f),e+=j;var k={itemcount:j,content:e};1===i&&(k.active=!0),d.options.push(k)}return d},q=function(a,b,c){var d=i();return c.hasOwnProperty("ignoreControlWhileLoading")&&(d.ignorecontrolwhileloading=c.ignoreControlWhileLoading),c.hasOwnProperty("controlPlacementBottom")&&(d.controlplacementbottom=c.controlPlacementBottom),c.hasOwnProperty("hideControlOnSinglePage")&&(d.hidecontrolonsinglepage=c.hideControlOnSinglePage),c.hasOwnProperty("ariaLabels")&&(d.arialabels=c.ariaLabels),c.hasOwnProperty("dropdown")&&c.dropdown?d.pagingdropdown=p(b,c):d.pagingbar=o(a,b),d},r=function(a,b){return t(null,null,a,b)},s=function(a,b,c){return t(null,a,b,c)},t=function(e,f,h,i){i=i||{};var j=a.Deferred(),k=q(e,f,i);return b.render(g.PAGED_CONTENT,k).then(function(b,c){b=a(b);var e=b;d.init(e,h),j.resolve(b,c)}).fail(function(a){j.reject(a)}).fail(c.exception),j.promise()},u=function(a,b,c,d){"undefined"==typeof d&&(d={});var e=a.length;return t(e,b,function(b){var d=[];return b.forEach(function(b){var c=b.offset,f=b.limit?c+b.limit:e,g=a.slice(c,f);d.push(g)}),c(d)},d)},v=function(a,b){f.publish(a+e.ALL_ITEMS_LOADED,b)};return{create:r,createWithLimit:s,createWithTotalAndLimit:t,createFromStaticList:u,createFromAjax:t,resetLastPageNumber:v}});
define(["jquery","core/templates","core/notification","core/paged_content","core/paged_content_events","core/pubsub","core/ajax"],function(a,b,c,d,e,f,g){var h={PAGED_CONTENT:"core/paged_content"},i={ITEMS_PER_PAGE_SINGLE:25,ITEMS_PER_PAGE_ARRAY:[25,50,100,0],MAX_PAGES:3},j=function(){return{pagingbar:!1,pagingdropdown:!1,skipjs:!0,ignorecontrolwhileloading:!0,controlplacementbottom:!1}},k=function(){return{showitemsperpageselector:!1,itemsperpage:35,previous:!0,next:!0,activepagenumber:1,hidecontrolonsinglepage:!0,pages:[]}},l=function(a,b){var c=1;if(a>0){var d=a%b;d?(a-=d,c=a/b+1):c=a/b}return c},m=function(b,c){null===c&&(c=i.ITEMS_PER_PAGE_SINGLE),a.isArray(c)&&(c=c[0]);var d=k();d.itemsperpage=c;for(var e=l(b,c),f=1;f<=e;f++){var g={number:f,page:""+f};1===f&&(g.active=!0),d.pages.push(g)}return d},n=function(b){if(a.isArray(b)){var c=b.map(function(a){return"number"==typeof a?{value:a,active:!1}:a}),d=c.filter(function(a){return a.active});return d.length||(c[0].active=!0),c}return b},o=function(b){null===b&&(b=i.ITEMS_PER_PAGE_ARRAY);var c=k();return c.itemsperpage=n(b),c.showitemsperpageselector=a.isArray(b),c},p=function(a,b){return a?m(a,b):o(b)},q=function(b,c){if(null===b&&(b=i.ITEMS_PER_PAGE_SINGLE),a.isArray(b))return{options:b};var d={options:[]},e=0,f=0,g=i.MAX_PAGES;c.hasOwnProperty("maxPages")&&(g=c.maxPages);for(var h=1;h<=g;h++){var j=0;h<=2?(j=b,f=b):(f=2*f,j=f),e+=j;var k={itemcount:j,content:e};1===h&&(k.active=!0),d.options.push(k)}return d},r=function(a,b,c){var d=j();return c.hasOwnProperty("ignoreControlWhileLoading")&&(d.ignorecontrolwhileloading=c.ignoreControlWhileLoading),c.hasOwnProperty("controlPlacementBottom")&&(d.controlplacementbottom=c.controlPlacementBottom),c.hasOwnProperty("hideControlOnSinglePage")&&(d.hidecontrolonsinglepage=c.hideControlOnSinglePage),c.hasOwnProperty("ariaLabels")&&(d.arialabels=c.ariaLabels),c.hasOwnProperty("dropdown")&&c.dropdown?d.pagingdropdown=q(b,c):d.pagingbar=p(a,b),d},s=function(a,b){return u(null,null,a,b)},t=function(a,b,c){return u(null,a,b,c)},u=function(e,f,g,i){i=i||{};var j=a.Deferred(),k=r(e,f,i);return b.render(h.PAGED_CONTENT,k).then(function(b,c){b=a(b);var e=b.attr("id");i.hasOwnProperty("eventNamespace")&&(e=i.eventNamespace);var f=b;d.init(f,g,e),y(e,i),j.resolve(b,c)}).fail(function(a){j.reject(a)}).fail(c.exception),j.promise()},v=function(a,b,c,d){"undefined"==typeof d&&(d={});var e=a.length;return u(e,b,function(b){var d=[];return b.forEach(function(b){var c=b.offset,f=b.limit?c+b.limit:e,g=a.slice(c,f);d.push(g)}),c(d)},d)},w=function(a,b){f.publish(a+e.ALL_ITEMS_LOADED,b)},x=function(a){var b=function(b){var c={preferences:[{type:a,value:b}]},d={methodname:"core_user_update_user_preferences",args:c};g.call([d])};return b},y=function(a,b){b.hasOwnProperty("persistentLimitKey")&&f.subscribe(a+e.SET_ITEMS_PER_PAGE_LIMIT,x(b.persistentLimitKey))};return{create:s,createWithLimit:t,createWithTotalAndLimit:u,createFromStaticList:v,createFromAjax:u,resetLastPageNumber:w}});

View File

@ -44,8 +44,10 @@ function(
* @param {function} renderPagesContentCallback (optional) A callback function to render a
* content page. See core/paged_content_pages for
* more defails.
* @param {string} namespaceOverride (optional) Provide a unique namespace override. If none provided defaults
* to generate html's id
*/
var init = function(root, renderPagesContentCallback) {
var init = function(root, renderPagesContentCallback, namespaceOverride) {
root = $(root);
var pagesContainer = root.find(Pages.rootSelector);
var pagingBarContainer = root.find(PagingBar.rootSelector);
@ -53,6 +55,11 @@ function(
var pagingBarLimitSelectorContainer = root.find(PagingBarLimitSelector.rootSelector);
var id = root.attr('id');
// Set the id to the custom namespace provided
if (namespaceOverride) {
id = namespaceOverride;
}
Pages.init(pagesContainer, id, renderPagesContentCallback);
if (pagingBarContainer.length) {

View File

@ -27,7 +27,8 @@ define(
'core/notification',
'core/paged_content',
'core/paged_content_events',
'core/pubsub'
'core/pubsub',
'core/ajax'
],
function(
$,
@ -35,7 +36,8 @@ function(
Notification,
PagedContent,
PagedContentEvents,
PubSub
PubSub,
Ajax
) {
var TEMPLATES = {
PAGED_CONTENT: 'core/paged_content'
@ -425,10 +427,18 @@ function(
Templates.render(TEMPLATES.PAGED_CONTENT, templateContext)
.then(function(html, js) {
html = $(html);
var id = html.attr('id');
// Set the id to the custom namespace provided
if (config.hasOwnProperty('eventNamespace')) {
id = config.eventNamespace;
}
var container = html;
PagedContent.init(container, renderPagesContentCallback);
PagedContent.init(container, renderPagesContentCallback, id);
registerEvents(id, config);
deferred.resolve(html, js);
return;
@ -494,6 +504,47 @@ function(
PubSub.publish(id + PagedContentEvents.ALL_ITEMS_LOADED, lastPageNumber);
};
/**
* Generate the callback handler for the page limit persistence functionality
*
* @param {String} persistentLimitKey
* @return {callback}
*/
var generateLimitHandler = function(persistentLimitKey) {
var callback = function(limit) {
var args = {
preferences: [
{
type: persistentLimitKey,
value: limit
}
]
};
var request = {
methodname: 'core_user_update_user_preferences',
args: args
};
Ajax.call([request]);
};
return callback;
};
/**
* Set up any events based on config key values
*
* @param {string} namespace The namespace for this component
* @param {object} config Config options passed to the factory
*/
var registerEvents = function(namespace, config) {
if (config.hasOwnProperty('persistentLimitKey')) {
PubSub.subscribe(namespace + PagedContentEvents.SET_ITEMS_PER_PAGE_LIMIT,
generateLimitHandler(config.persistentLimitKey));
}
};
return {
create: create,
createWithLimit: createWithLimit,