MDL-73068 timeline: Update courses view to omit courses without events
Previously, the timeline block courses view included all courses in alphabetical order, whether they contained any events matching the relevant filters or not. This could be tedious and misleading if there were many courses with no events appearing before a course which did have matching events. Now, courses are only included if they contain events matching the currently set time filter and search term. When those values are modified, the block will be refreshed and fetch results for the current values, and also takes these into consideration when determining whether to display the show more courses button.
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("block_timeline/event_list",["jquery","core/notification","core/templates","core/str","core/user_date","block_timeline/calendar_events_repository","core/pending"],function(a,b,c,d,e,f,g){var h=!1,i={EMPTY_MESSAGE:"[data-region=\"empty-message\"]",ROOT:"[data-region=\"event-list-container\"]",EVENT_LIST_CONTENT:"[data-region=\"event-list-content\"]",EVENT_LIST_WRAPPER:"[data-region=\"event-list-wrapper\"]",EVENT_LIST_LOADING_PLACEHOLDER:"[data-region=\"event-list-loading-placeholder\"]",TIMELINE_BLOCK:"[data-region=\"timeline\"]",TIMELINE_SEARCH:"[data-action=\"search\"]",MORE_ACTIVITIES_BUTTON:"[data-action=\"more-events\"]",MORE_ACTIVITIES_BUTTON_CONTAINER:"[data-region=\"more-events-button-container\"]"},j={EVENT_LIST_CONTENT:"block_timeline/event-list-content",MORE_ACTIVITIES_BUTTON:"block_timeline/event-list-loadmore",LOADING_ICON:"core/loading"},k=function(a){a.find(i.EVENT_LIST_CONTENT).addClass("hidden");a.find(i.EMPTY_MESSAGE).removeClass("hidden")},l=function(a){a.find(i.EVENT_LIST_CONTENT).removeClass("hidden");a.find(i.EMPTY_MESSAGE).addClass("hidden")},m=function(a){a.find(i.EVENT_LIST_CONTENT).empty()},n=function(a){var b={},c={courseview:h,eventsbyday:[]};a.forEach(function(a){var c=a.timeusermidnight;if(b[c]){b[c].push(a)}else{b[c]=[a]}});Object.keys(b).forEach(function(a){var d=b[a];c.eventsbyday.push({dayTimestamp:a,events:d})});return c},o=function(a){var b=n(a),d=j.EVENT_LIST_CONTENT;return c.render(d,b)},p=function(a,b,c,d,e,g,h){var i=d!=void 0?a+d*86400:!1,j={starttime:a+c*86400,limit:b};if(e){j.aftereventid=e}if(i){j.endtime=i}if(h){j.searchvalue=h}if(g){j.courseid=g;return f.queryByCourse(j)}else{return f.queryByTime(j)}},q=function(a,c,d,e,f,g,h,i,j){return r(a,d,e,f,g,h,i,j).then(function(a){if(a.calendarEvents.length){var b=a.calendarEvents.at(-1).id,d=a.calendarEvents.at(-1).timeusermidnight;c.resolve({hasContent:!0,lastId:b,lastTimeStamp:d,loadedAll:a.loadedAll});return o(a.calendarEvents,e)}else{c.resolve({hasContent:!1,lastId:0,lastTimeStamp:0,loadedAll:!0});return a.calendarEvents}}).catch(b.exception)},r=function(a,b,c,d,f,g,h,i){var j=p(c,b+1,g,h,d,f,i),k=[],l=!0;return j.then(function(d){if(!d.events.length){return{calendarEvents:k,loadedAll:l}}var f=document.querySelector("[data-filtername='overdue']"),g=f&&f.getAttribute("aria-current");k=d.events.filter(function(a){if("open"==a.eventtype||"opensubmission"==a.eventtype){var b=e.getUserMidnightForTimestamp(a.timesort,c);return b>c}return!g||a.overdue});l=k.length<=b;if(!l){k.pop()}if(k.length){var h=k.at(-1).id;u(a,h)}return{calendarEvents:k,loadedAll:l}})},s=function(d){var e=parseInt(d.attr("data-midnight"),10),f=d.attr("data-course-id"),g=parseInt(d.attr("data-days-offset"),10),h=d.attr("data-days-limit"),k=t(d),l=d.find(i.EVENT_LIST_WRAPPER),m=d.closest(i.TIMELINE_BLOCK).find(i.TIMELINE_SEARCH).val(),n=r(d,10,e,k,f,g,h,m);n.then(function(e){if(e.calendarEvents.length){var f=o(e.calendarEvents),g=v(d);f.then(function(b,f){b=a(b);b.find("[data-timestamp=\"".concat(g,"\"]")).remove();c.appendNodeContents(l,b.html(),f);if(!e.loadedAll){c.render(j.MORE_ACTIVITIES_BUTTON,{}).then(function(a){l.append(a);w(d,e.calendarEvents.at(-1).timeusermidnight);z(d);return a}).catch(function(){return!1})}return b}).catch(b.exception)}return e}).then(function(){return y(d)}).catch(b.exception)},t=function(a){return parseInt(a.attr("data-lazyload-offset"),10)},u=function(a,b){a.attr("data-lazyload-offset",b)},v=function(a){return parseInt(a.attr("data-timestamp"),10)},w=function(a,b){a.attr("data-timestamp",b)},x=function(a){var b=a.find(i.MORE_ACTIVITIES_BUTTON);b.prop("disabled",!0);c.render(j.LOADING_ICON,{}).then(function(a){b.append(a);return a}).catch(function(){return!1})},y=function(a){var b=a.find(i.MORE_ACTIVITIES_BUTTON_CONTAINER);b.remove()},z=function(a){var b=a.find(i.MORE_ACTIVITIES_BUTTON);b.on("click",function(){x(a);s(a)})};return{init:function init(d){var e=1<arguments.length&&arguments[1]!==void 0?arguments[1]:{},f=new g("block/timeline:event-init");d=a(d);h=!!e.courseview;var n=a.Deferred(),o=d.find(i.EVENT_LIST_CONTENT),p=d.find(i.EVENT_LIST_LOADING_PLACEHOLDER),r=d.attr("data-course-id"),s=parseInt(d.attr("data-days-offset"),10),t=d.attr("data-days-limit"),u=parseInt(d.attr("data-midnight"),10),v=d.closest(i.TIMELINE_BLOCK).find(i.TIMELINE_SEARCH).val();m(d);l(d);p.removeClass("hidden");if(t!=void 0){t=parseInt(t,10)}return q(d,n,5,u,0,r,s,t,v).then(function(b,e){n.then(function(f){if(!f.hasContent){p.addClass("hidden");return k(d)}b=a(b);b.addClass("hidden");c.replaceNodeContents(o,b,e);b.removeClass("hidden");p.addClass("hidden");if(!f.loadedAll){c.render(j.MORE_ACTIVITIES_BUTTON,{courseview:h}).then(function(a){o.append(a);w(d,f.lastTimeStamp);z(d);return a}).catch(function(){return!1})}return f}).catch(function(){return!1});return b}).then(function(){return f.resolve()}).catch(b.exception)},rootSelector:i.ROOT}});
|
||||
define ("block_timeline/event_list",["jquery","core/notification","core/templates","core/str","core/user_date","block_timeline/calendar_events_repository","core/pending"],function(a,b,c,d,e,f,g){var h=!1,i={EMPTY_MESSAGE:"[data-region=\"no-events-empty-message\"]",ROOT:"[data-region=\"event-list-container\"]",EVENT_LIST_CONTENT:"[data-region=\"event-list-content\"]",EVENT_LIST_WRAPPER:"[data-region=\"event-list-wrapper\"]",EVENT_LIST_LOADING_PLACEHOLDER:"[data-region=\"event-list-loading-placeholder\"]",TIMELINE_BLOCK:"[data-region=\"timeline\"]",TIMELINE_SEARCH:"[data-action=\"search\"]",MORE_ACTIVITIES_BUTTON:"[data-action=\"more-events\"]",MORE_ACTIVITIES_BUTTON_CONTAINER:"[data-region=\"more-events-button-container\"]"},j={EVENT_LIST_CONTENT:"block_timeline/event-list-content",MORE_ACTIVITIES_BUTTON:"block_timeline/event-list-loadmore",LOADING_ICON:"core/loading"},k=function(a){a.find(i.EVENT_LIST_CONTENT).addClass("hidden");a.find(i.EMPTY_MESSAGE).removeClass("hidden")},l=function(a){a.find(i.EVENT_LIST_CONTENT).removeClass("hidden");a.find(i.EMPTY_MESSAGE).addClass("hidden")},m=function(a){a.find(i.EVENT_LIST_CONTENT).empty()},n=function(a){var b={},c={courseview:h,eventsbyday:[]};a.forEach(function(a){var c=a.timeusermidnight;if(b[c]){b[c].push(a)}else{b[c]=[a]}});Object.keys(b).forEach(function(a){var d=b[a];c.eventsbyday.push({dayTimestamp:a,events:d})});return c},o=function(a){var b=n(a),d=j.EVENT_LIST_CONTENT;return c.render(d,b)},p=function(a,b,c,d,e,g,h){var i=d!=void 0?a+d*86400:!1,j={starttime:a+c*86400,limit:b};if(e){j.aftereventid=e}if(i){j.endtime=i}if(h){j.searchvalue=h}if(g){j.courseid=g;return f.queryByCourse(j)}else{return f.queryByTime(j)}},q=function(a,c,d,e,f,g,h,i,j){return r(a,d,e,f,g,h,i,j).then(function(a){if(a.calendarEvents.length){var b=a.calendarEvents.at(-1).id,d=a.calendarEvents.at(-1).timeusermidnight;c.resolve({hasContent:!0,lastId:b,lastTimeStamp:d,loadedAll:a.loadedAll});return o(a.calendarEvents,e)}else{c.resolve({hasContent:!1,lastId:0,lastTimeStamp:0,loadedAll:!0});return a.calendarEvents}}).catch(b.exception)},r=function(a,b,c,d,f,g,h,i){var j=p(c,b+1,g,h,d,f,i),k=[],l=!0;return j.then(function(d){if(!d.events.length){return{calendarEvents:k,loadedAll:l}}var f=document.querySelector("[data-filtername='overdue']"),g=f&&f.getAttribute("aria-current");k=d.events.filter(function(a){if("open"==a.eventtype||"opensubmission"==a.eventtype){var b=e.getUserMidnightForTimestamp(a.timesort,c);return b>c}return!g||a.overdue});l=k.length<=b;if(!l){k.pop()}if(k.length){var h=k.at(-1).id;u(a,h)}return{calendarEvents:k,loadedAll:l}})},s=function(d){var e=parseInt(d.attr("data-midnight"),10),f=d.attr("data-course-id"),g=parseInt(d.attr("data-days-offset"),10),h=d.attr("data-days-limit"),k=t(d),l=d.find(i.EVENT_LIST_WRAPPER),m=d.closest(i.TIMELINE_BLOCK).find(i.TIMELINE_SEARCH).val(),n=r(d,10,e,k,f,g,h,m);n.then(function(e){if(e.calendarEvents.length){var f=o(e.calendarEvents),g=v(d);f.then(function(b,f){b=a(b);b.find("[data-timestamp=\"".concat(g,"\"]")).remove();c.appendNodeContents(l,b.html(),f);if(!e.loadedAll){c.render(j.MORE_ACTIVITIES_BUTTON,{}).then(function(a){l.append(a);w(d,e.calendarEvents.at(-1).timeusermidnight);z(d);return a}).catch(function(){return!1})}return b}).catch(b.exception)}return e}).then(function(){return y(d)}).catch(b.exception)},t=function(a){return parseInt(a.attr("data-lazyload-offset"),10)},u=function(a,b){a.attr("data-lazyload-offset",b)},v=function(a){return parseInt(a.attr("data-timestamp"),10)},w=function(a,b){a.attr("data-timestamp",b)},x=function(a){var b=a.find(i.MORE_ACTIVITIES_BUTTON);b.prop("disabled",!0);c.render(j.LOADING_ICON,{}).then(function(a){b.append(a);return a}).catch(function(){return!1})},y=function(a){var b=a.find(i.MORE_ACTIVITIES_BUTTON_CONTAINER);b.remove()},z=function(a){var b=a.find(i.MORE_ACTIVITIES_BUTTON);b.on("click",function(){x(a);s(a)})};return{init:function init(d){var e=1<arguments.length&&arguments[1]!==void 0?arguments[1]:{},f=new g("block/timeline:event-init");d=a(d);h=!!e.courseview;var n=a.Deferred(),o=d.find(i.EVENT_LIST_CONTENT),p=d.find(i.EVENT_LIST_LOADING_PLACEHOLDER),r=d.attr("data-course-id"),s=parseInt(d.attr("data-days-offset"),10),t=d.attr("data-days-limit"),u=parseInt(d.attr("data-midnight"),10),v=d.closest(i.TIMELINE_BLOCK).find(i.TIMELINE_SEARCH).val();m(d);l(d);p.removeClass("hidden");if(t!=void 0){t=parseInt(t,10)}return q(d,n,5,u,0,r,s,t,v).then(function(b,e){n.then(function(f){if(!f.hasContent){p.addClass("hidden");return k(d)}b=a(b);b.addClass("hidden");c.replaceNodeContents(o,b,e);b.removeClass("hidden");p.addClass("hidden");if(!f.loadedAll){c.render(j.MORE_ACTIVITIES_BUTTON,{courseview:h}).then(function(a){o.append(a);w(d,f.lastTimeStamp);z(d);return a}).catch(function(){return!1})}return f}).catch(function(){return!1});return b}).then(function(){return f.resolve()}).catch(b.exception)},rootSelector:i.ROOT}});
|
||||
//# sourceMappingURL=event_list.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("block_timeline/view_courses",["jquery","core/notification","core/custom_interaction_events","core/templates","block_timeline/event_list","core_course/repository","block_timeline/calendar_events_repository"],function(a,b,c,d,e,f,g){var h={MORE_COURSES_BUTTON:"[data-action=\"more-courses\"]",MORE_COURSES_BUTTON_CONTAINER:"[data-region=\"more-courses-button-container\"]",NO_COURSES_EMPTY_MESSAGE:"[data-region=\"no-courses-empty-message\"]",COURSES_LIST:"[data-region=\"courses-list\"]",COURSE_ITEMS_LOADING_PLACEHOLDER:"[data-region=\"course-items-loading-placeholder\"]",COURSE_EVENTS_CONTAINER:"[data-region=\"course-events-container\"]",COURSE_NAME:"[data-region=\"course-name\"]",LOADING_ICON:".loading-icon",TIMELINE_BLOCK:"[data-region=\"timeline\"]",TIMELINE_SEARCH:"[data-action=\"search\"]"},i={COURSE_ITEMS:"block_timeline/course-items",LOADING_ICON:"core/loading"},j=5,k=86400,l={courseview:!0},m=function(a){a.find(h.COURSE_ITEMS_LOADING_PLACEHOLDER).addClass("hidden")},n=function(a){a.find(h.MORE_COURSES_BUTTON_CONTAINER).addClass("hidden")},o=function(a){a.find(h.MORE_COURSES_BUTTON_CONTAINER).removeClass("hidden")},p=function(a){var b=a.find(h.MORE_COURSES_BUTTON);b.prop("disabled",!0);d.render(i.LOADING_ICON,{}).then(function(a){b.append(a);return a}).catch(function(){return!1})},q=function(a){var b=a.find(h.MORE_COURSES_BUTTON);b.prop("disabled",!1);b.find(h.LOADING_ICON).remove()},r=function(a){a.find(h.NO_COURSES_EMPTY_MESSAGE).removeClass("hidden")},s=function(a,b){var c=a.find(h.COURSES_LIST);d.appendNodeContents(c,b,"")},t=function(a){return 0<a.find(h.COURSE_EVENTS_CONTAINER).length},u=function(a){return parseInt(a.attr("data-offset"),10)},v=function(a,b){a.attr("data-offset",b)},w=function(a){return parseInt(a.attr("data-limit"),10)},x=function(a){return parseInt(a.attr("data-days-offset"),10)},y=function(a){var b=a.attr("data-days-limit");return b!=void 0?parseInt(b,10):void 0},z=function(a){return parseInt(a.attr("data-midnight"),10)},A=function(a){var b=z(a),c=x(a);return b+c*k},B=function(a){var b=z(a),c=y(a);return c!=void 0?b+c*k:!1},C=function(a,b,c,d,e){var f={courseids:a,starttime:b,limit:c};if(d){f.endtime=d}if(e){f.searchvalue=e}return g.queryByCourses(f)},D=function(a){return a.data("last-event-load-time")},E=function(a,b){a.data("last-event-load-time",b)},F=function(a,b){return D(a)>b},G=function(a,b,c,d){var e=a.map(function(a){return a.id});return C(e,b,j+1,c,d)},H=function(a,b,c,e,f){return d.render(i.COURSE_ITEMS,{courses:a,midnight:c,hasdaysoffset:!0,hasdayslimit:f!=void 0,daysoffset:e,dayslimit:f,nodayslimit:f==void 0,courseview:!0}).then(function(a){m(b);if(a){s(b,a)}else{if(!t(b)){r(b)}}return a}).then(function(c){if(a.length<2){n(b)}else{o(b)}return c}).catch(function(){m(b)})},I=function(c){var d=u(c),g=w(c);return f.getEnrolledCoursesByTimelineClassification("inprogress",g,d,"fullname asc").then(function(b){var d=Date.now(),f=b.courses,g=b.nextoffset,i=x(c),j=y(c),k=z(c),m=A(c),n=B(c),o=c.closest(h.TIMELINE_BLOCK).find(h.TIMELINE_SEARCH).val();v(c,g);var p=G(f,m,n,o),q=H(f,c,k,i,j);return a.when(p,q).then(function(a){if(F(c,d)){return a}f.forEach(function(a){var b=a.id,d=c.find("[data-region=\"course-events-container\"][data-course-id=\""+b+"\"]"),f=d.find(e.rootSelector);e.init(f,l)});return a})}).catch(b.exception)},J=function(c){var d=Date.now(),f=A(c),g=B(c),i=c.find(h.COURSE_EVENTS_CONTAINER),k=i.map(function(){return a(this).attr("data-course-id")}).get(),m=c.closest(h.TIMELINE_BLOCK).find(h.TIMELINE_SEARCH).val();E(c,d);return C(k,f,j+1,g,m).then(function(b){if(F(c,d)){return b}i.each(function(b,c){c=a(c);var d=c.find(e.rootSelector);e.init(d,l)});return b}).catch(b.exception)},K=function(a){c.define(a,[c.events.activate]);a.on(c.events.activate,h.MORE_COURSES_BUTTON,function(b,c){p(a);I(a).then(function(){q(a)}).catch(function(){q(a)});if(c){c.originalEvent.preventDefault();c.originalEvent.stopPropagation()}b.stopPropagation()})},L=function(a){if(!a.attr("data-seen")){if(t(a)){J(a)}else{I(a)}a.attr("data-seen",!0)}};return{init:function init(b){b=a(b);E(b,Date.now());if(b.hasClass("active")){I(b);b.attr("data-seen",!0)}K(b)},reset:function reset(a){a.removeAttr("data-seen");if(a.hasClass("active")){L(a)}},shown:L}});
|
||||
define ("block_timeline/view_courses",["jquery","core/notification","core/custom_interaction_events","core/templates","block_timeline/event_list","core_course/repository","block_timeline/calendar_events_repository","core/pending"],function(a,b,c,d,e,f,g,h){var i={MORE_COURSES_BUTTON:"[data-action=\"more-courses\"]",MORE_COURSES_BUTTON_CONTAINER:"[data-region=\"more-courses-button-container\"]",NO_COURSES_EMPTY_MESSAGE:"[data-region=\"no-courses-empty-message\"]",NO_COURSES_WITH_EVENTS_MESSAGE:"[data-region=\"no-events-empty-message\"]",COURSES_LIST:"[data-region=\"courses-list\"]",COURSE_ITEMS_LOADING_PLACEHOLDER:"[data-region=\"course-items-loading-placeholder\"]",COURSE_EVENTS_CONTAINER:"[data-region=\"course-events-container\"]",COURSE_NAME:"[data-region=\"course-name\"]",LOADING_ICON:".loading-icon",TIMELINE_BLOCK:"[data-region=\"timeline\"]",TIMELINE_SEARCH:"[data-action=\"search\"]"},j={COURSE_ITEMS:"block_timeline/course-items",LOADING_ICON:"core/loading"},k=86400,l={courseview:!0},m=function(a){a.find(i.COURSE_ITEMS_LOADING_PLACEHOLDER).addClass("hidden")},n=function(a){a.find(i.COURSE_ITEMS_LOADING_PLACEHOLDER).removeClass("hidden")},o=function(a){a.find(i.MORE_COURSES_BUTTON_CONTAINER).addClass("hidden")},p=function(a){a.find(i.MORE_COURSES_BUTTON_CONTAINER).removeClass("hidden")},q=function(a){var b=a.find(i.MORE_COURSES_BUTTON);b.prop("disabled",!0);d.render(j.LOADING_ICON,{}).then(function(a){b.append(a);return a}).catch(function(){return!1})},r=function(a){var b=a.find(i.MORE_COURSES_BUTTON);b.prop("disabled",!1);b.find(i.LOADING_ICON).remove()},s=function(a){var b=a.find(i.COURSES_LIST);d.replaceNodeContents(b,"","");a.find(i.NO_COURSES_WITH_EVENTS_MESSAGE).removeClass("hidden")},t=function(a){a.find(i.NO_COURSES_WITH_EVENTS_MESSAGE).addClass("hidden")},u=function(a,b){var c=2<arguments.length&&arguments[2]!==void 0?arguments[2]:!1,e=a.find(i.COURSES_LIST);if(c){d.appendNodeContents(e,b,"")}else{d.replaceNodeContents(e,b,"")}},v=function(a){return parseInt(a.attr("data-offset"),10)},w=function(a,b){a.attr("data-offset",b)},x=function(a){return parseInt(a.attr("data-limit"),10)},y=function(a){return parseInt(a.attr("data-days-offset"),10)},z=function(a){var b=a.attr("data-days-limit");return b!=void 0?parseInt(b,10):void 0},A=function(a){return parseInt(a.attr("data-midnight"),10)},B=function(a){var b=A(a),c=y(a);return b+c*k},C=function(a){var b=A(a),c=z(a);return c!=void 0?b+c*k:null},D=function(a,b,c,d,e){var f={courseids:a,starttime:b,limit:c};if(d){f.endtime=d}if(e){f.searchvalue=e}return g.queryByCourses(f)},E=function(a){return a.data("last-event-load-time")},F=function(a,b){a.data("last-event-load-time",b)},G=function(a,b){return E(a)>b},H=function(a,b,c,d){var e=a.map(function(a){return a.id});return D(e,b,5+1,c,d)},I=function(a,b,c,e,f,g){return d.render(j.COURSE_ITEMS,{courses:a,midnight:c,hasdaysoffset:!0,hasdayslimit:f!=void 0,daysoffset:e,dayslimit:f,nodayslimit:f==void 0,courseview:!0,hascourses:!0}).then(function(a){m(b);if(a){u(b,a,g)}return a}).then(function(c){if(a.length<2){o(b)}else{p(b)}return c}).catch(function(){m(b)})},J=function(c){var d=1<arguments.length&&arguments[1]!==void 0?arguments[1]:!1,g=new h("block/timeline:load-more-courses"),j=v(c),k=x(c),m=B(c),n=C(c),q=c.closest(i.TIMELINE_BLOCK).find(i.TIMELINE_SEARCH).val();return f.getEnrolledCoursesWithEventsByTimelineClassification("inprogress",k,j,"fullname asc",q,m,n).then(function(b){var f=Date.now(),g=b.courses,h=b.nextoffset,i=y(c),k=z(c),r=A(c),t=b.morecoursesavailable;w(c,h);var u=H(g,m,n,q),v=I(g,c,r,i,k,d);return a.when(u,v).then(function(a){if(G(c,f)){return a}if(0<g.length){g.forEach(function(a){var b=a.id,d=c.find("[data-region=\"course-events-container\"][data-course-id=\""+b+"\"]"),f=d.find(e.rootSelector);e.init(f,l)});if(!t){o(c)}else{p(c)}}else{o(c);if(0==j){s(c)}}return a})}).then(function(){return g.resolve()}).catch(b.exception)},K=function(a){c.define(a,[c.events.activate]);a.on(c.events.activate,i.MORE_COURSES_BUTTON,function(b,c){q(a);J(a,!0).then(function(){r(a)}).catch(function(){r(a)});if(c){c.originalEvent.preventDefault();c.originalEvent.stopPropagation()}b.stopPropagation()})},L=function(a){if(!a.attr("data-seen")&&!a.find(i.NO_COURSES_EMPTY_MESSAGE).length){J(a);a.attr("data-seen",!0)}};return{init:function init(b){b=a(b);if(!b.find(i.NO_COURSES_EMPTY_MESSAGE).length){F(b,Date.now());if(b.hasClass("active")){J(b);b.attr("data-seen",!0)}K(b)}},reset:function reset(a){w(a,0);n(a);t(a);a.removeAttr("data-seen");if(a.hasClass("active")){L(a)}},shown:L}});
|
||||
//# sourceMappingURL=view_courses.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -45,7 +45,7 @@ function(
|
||||
var courseview = false;
|
||||
|
||||
var SELECTORS = {
|
||||
EMPTY_MESSAGE: '[data-region="empty-message"]',
|
||||
EMPTY_MESSAGE: '[data-region="no-events-empty-message"]',
|
||||
ROOT: '[data-region="event-list-container"]',
|
||||
EVENT_LIST_CONTENT: '[data-region="event-list-content"]',
|
||||
EVENT_LIST_WRAPPER: '[data-region="event-list-wrapper"]',
|
||||
|
||||
@@ -28,7 +28,8 @@ define(
|
||||
'core/templates',
|
||||
'block_timeline/event_list',
|
||||
'core_course/repository',
|
||||
'block_timeline/calendar_events_repository'
|
||||
'block_timeline/calendar_events_repository',
|
||||
'core/pending'
|
||||
],
|
||||
function(
|
||||
$,
|
||||
@@ -37,13 +38,15 @@ function(
|
||||
Templates,
|
||||
EventList,
|
||||
CourseRepository,
|
||||
EventsRepository
|
||||
EventsRepository,
|
||||
Pending
|
||||
) {
|
||||
|
||||
var SELECTORS = {
|
||||
MORE_COURSES_BUTTON: '[data-action="more-courses"]',
|
||||
MORE_COURSES_BUTTON_CONTAINER: '[data-region="more-courses-button-container"]',
|
||||
NO_COURSES_EMPTY_MESSAGE: '[data-region="no-courses-empty-message"]',
|
||||
NO_COURSES_WITH_EVENTS_MESSAGE: '[data-region="no-events-empty-message"]',
|
||||
COURSES_LIST: '[data-region="courses-list"]',
|
||||
COURSE_ITEMS_LOADING_PLACEHOLDER: '[data-region="course-items-loading-placeholder"]',
|
||||
COURSE_EVENTS_CONTAINER: '[data-region="course-events-container"]',
|
||||
@@ -75,6 +78,15 @@ function(
|
||||
root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).addClass('hidden');
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the loading placeholder elements.
|
||||
*
|
||||
* @param {object} root The rool element.
|
||||
*/
|
||||
const showLoadingPlaceholder = function(root) {
|
||||
root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).removeClass('hidden');
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the "more courses" button.
|
||||
*
|
||||
@@ -124,12 +136,24 @@ function(
|
||||
};
|
||||
|
||||
/**
|
||||
* Display the message for when there are no courses available.
|
||||
* Display the message for when courses have no events available (within the current filtering).
|
||||
*
|
||||
* @param {object} root The rool element.
|
||||
*/
|
||||
var showNoCoursesEmptyMessage = function(root) {
|
||||
root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).removeClass('hidden');
|
||||
const showNoCoursesWithEventsMessage = function(root) {
|
||||
// Remove any course list contents, since we will display the no events message.
|
||||
const container = root.find(SELECTORS.COURSES_LIST);
|
||||
Templates.replaceNodeContents(container, '', '');
|
||||
root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).removeClass('hidden');
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the message for when courses have no events available (within the current filtering).
|
||||
*
|
||||
* @param {object} root The rool element.
|
||||
*/
|
||||
const hideNoCoursesWithEventsMessage = function(root) {
|
||||
root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).addClass('hidden');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -137,20 +161,17 @@ function(
|
||||
*
|
||||
* @param {object} root The rool element.
|
||||
* @param {string} html The course items HTML to render.
|
||||
* @param {boolean} append Whether the HTML should be appended (eg pressed "show more courses").
|
||||
* Defaults to false - replaces the existing content (eg when modifying filter values).
|
||||
*/
|
||||
var renderCourseItemsHTML = function(root, html) {
|
||||
var renderCourseItemsHTML = function(root, html, append = false) {
|
||||
var container = root.find(SELECTORS.COURSES_LIST);
|
||||
Templates.appendNodeContents(container, html, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if any courses have been loaded.
|
||||
*
|
||||
* @param {object} root The rool element.
|
||||
* @return {bool}
|
||||
*/
|
||||
var hasLoadedCourses = function(root) {
|
||||
return root.find(SELECTORS.COURSE_EVENTS_CONTAINER).length > 0;
|
||||
if (append) {
|
||||
Templates.appendNodeContents(container, html, '');
|
||||
} else {
|
||||
Templates.replaceNodeContents(container, html, '');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -241,7 +262,7 @@ function(
|
||||
var getEndTime = function(root) {
|
||||
var midnight = getMidnight(root);
|
||||
var daysLimit = getDaysLimit(root);
|
||||
return daysLimit != undefined ? midnight + (daysLimit * SECONDS_IN_DAY) : false;
|
||||
return daysLimit != undefined ? midnight + (daysLimit * SECONDS_IN_DAY) : null;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -330,9 +351,10 @@ function(
|
||||
* @param {Number} midnight The midnight timestamp in the user's timezone.
|
||||
* @param {Number} daysOffset Number of days from today to offset the events.
|
||||
* @param {Number} daysLimit Number of days from today to limit the events to.
|
||||
* @param {boolean} append Whether new content should be appended instead of replaced (eg "show more courses").
|
||||
* @return {object} jQuery promise resolved after rendering is complete.
|
||||
*/
|
||||
var updateDisplayFromCourses = function(courses, root, midnight, daysOffset, daysLimit) {
|
||||
var updateDisplayFromCourses = function(courses, root, midnight, daysOffset, daysLimit, append) {
|
||||
// Render the courses template.
|
||||
return Templates.render(TEMPLATES.COURSE_ITEMS, {
|
||||
courses: courses,
|
||||
@@ -343,19 +365,14 @@ function(
|
||||
dayslimit: daysLimit,
|
||||
nodayslimit: daysLimit == undefined,
|
||||
courseview: true,
|
||||
hascourses: true
|
||||
}).then(function(html) {
|
||||
hideLoadingPlaceholder(root);
|
||||
|
||||
if (html) {
|
||||
// Template rendering is complete and we have the HTML so we can
|
||||
// add it to the DOM.
|
||||
renderCourseItemsHTML(root, html);
|
||||
} else {
|
||||
if (!hasLoadedCourses(root)) {
|
||||
// There were no courses to render so show the empty placeholder
|
||||
// message for the user to tell them.
|
||||
showNoCoursesEmptyMessage(root);
|
||||
}
|
||||
renderCourseItemsHTML(root, html, append);
|
||||
}
|
||||
|
||||
return html;
|
||||
@@ -382,18 +399,28 @@ function(
|
||||
* list module to being loading the events for the course block.
|
||||
*
|
||||
* @param {object} root The root element for the timeline courses view.
|
||||
* @param {boolean} append Whether content should be appended instead of replaced (eg "show more courses"). False by default.
|
||||
* @return {object} jQuery promise resolved with courses and events.
|
||||
*/
|
||||
var loadMoreCourses = function(root) {
|
||||
var loadMoreCourses = function(root, append = false) {
|
||||
const pendingPromise = new Pending('block/timeline:load-more-courses');
|
||||
var offset = getOffset(root);
|
||||
var limit = getLimit(root);
|
||||
const startTime = getStartTime(root);
|
||||
const endTime = getEndTime(root);
|
||||
const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();
|
||||
|
||||
// Start loading the next set of courses.
|
||||
return CourseRepository.getEnrolledCoursesByTimelineClassification(
|
||||
// Fetch up to limit number of courses with at least one action event in the time filtering specified.
|
||||
// Courses without events will also be fetched, but hidden in case they have events in other timespans.
|
||||
return CourseRepository.getEnrolledCoursesWithEventsByTimelineClassification(
|
||||
COURSE_CLASSIFICATION,
|
||||
limit,
|
||||
offset,
|
||||
COURSE_SORT
|
||||
COURSE_SORT,
|
||||
searchValue,
|
||||
startTime,
|
||||
endTime
|
||||
).then(function(result) {
|
||||
var startEventLoadingTime = Date.now();
|
||||
var courses = result.courses;
|
||||
@@ -401,16 +428,14 @@ function(
|
||||
var daysOffset = getDaysOffset(root);
|
||||
var daysLimit = getDaysLimit(root);
|
||||
var midnight = getMidnight(root);
|
||||
var startTime = getStartTime(root);
|
||||
var endTime = getEndTime(root);
|
||||
const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();
|
||||
const moreCoursesAvailable = result.morecoursesavailable;
|
||||
|
||||
// Record the next offset if we want to request more courses.
|
||||
setOffset(root, nextOffset);
|
||||
// Load the events for these courses.
|
||||
var eventsPromise = loadEventsForCourses(courses, startTime, endTime, searchValue);
|
||||
// Render the courses in the DOM.
|
||||
var renderPromise = updateDisplayFromCourses(courses, root, midnight, daysOffset, daysLimit);
|
||||
var renderPromise = updateDisplayFromCourses(courses, root, midnight, daysOffset, daysLimit, append);
|
||||
|
||||
return $.when(eventsPromise, renderPromise)
|
||||
.then(function(eventsByCourse) {
|
||||
@@ -419,61 +444,41 @@ function(
|
||||
return eventsByCourse;
|
||||
}
|
||||
|
||||
// When we've got all of the courses and events we can render the events in the
|
||||
// correct course event list.
|
||||
courses.forEach(function(course) {
|
||||
var courseId = course.id;
|
||||
var containerSelector = '[data-region="course-events-container"][data-course-id="' + courseId + '"]';
|
||||
var courseEventsContainer = root.find(containerSelector);
|
||||
var eventListRoot = courseEventsContainer.find(EventList.rootSelector);
|
||||
if (courses.length > 0) {
|
||||
// Render the events in the correct course event list.
|
||||
courses.forEach(function(course) {
|
||||
const courseId = course.id;
|
||||
const containerSelector = '[data-region="course-events-container"][data-course-id="' + courseId + '"]';
|
||||
const courseEventsContainer = root.find(containerSelector);
|
||||
const eventListRoot = courseEventsContainer.find(EventList.rootSelector);
|
||||
|
||||
EventList.init(eventListRoot, additionalConfig);
|
||||
});
|
||||
EventList.init(eventListRoot, additionalConfig);
|
||||
});
|
||||
|
||||
if (!moreCoursesAvailable) {
|
||||
// If no more courses with events matching the current filtering exist, hide the more courses button.
|
||||
hideMoreCoursesButton(root);
|
||||
} else {
|
||||
// If more courses exist with events matching the current filtering, show the more courses button.
|
||||
showMoreCoursesButton(root);
|
||||
}
|
||||
} else {
|
||||
// No more courses to load, hide the more courses button.
|
||||
hideMoreCoursesButton(root);
|
||||
|
||||
// A zero offset means this was not loading "more courses", so we need to display the no results message.
|
||||
if (offset == 0) {
|
||||
showNoCoursesWithEventsMessage(root);
|
||||
}
|
||||
}
|
||||
|
||||
return eventsByCourse;
|
||||
});
|
||||
}).then(() => {
|
||||
return pendingPromise.resolve();
|
||||
}).catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reload the events for all of the visible courses. These events will be loaded
|
||||
* in a single request to the server.
|
||||
*
|
||||
* @param {object} root The root element.
|
||||
* @return {object} jQuery promise resolved with courses and events.
|
||||
*/
|
||||
var reloadCourseEvents = function(root) {
|
||||
var startReloadTime = Date.now();
|
||||
var startTime = getStartTime(root);
|
||||
var endTime = getEndTime(root);
|
||||
var courseEventsContainers = root.find(SELECTORS.COURSE_EVENTS_CONTAINER);
|
||||
var courseIds = courseEventsContainers.map(function() {
|
||||
return $(this).attr('data-course-id');
|
||||
}).get();
|
||||
const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();
|
||||
|
||||
// Record when we started our request.
|
||||
setEventReloadTime(root, startReloadTime);
|
||||
|
||||
// Load all of the events for the given courses.
|
||||
return getEventsForCourseIds(courseIds, startTime, COURSE_EVENT_LIMIT + 1, endTime, searchValue)
|
||||
.then(function(eventsByCourse) {
|
||||
if (hasReloadedEventsSince(root, startReloadTime)) {
|
||||
// A new reload has begun so ignore our results.
|
||||
return eventsByCourse;
|
||||
}
|
||||
|
||||
courseEventsContainers.each(function(index, container) {
|
||||
container = $(container);
|
||||
var eventListContainer = container.find(EventList.rootSelector);
|
||||
|
||||
EventList.init(eventListContainer, additionalConfig);
|
||||
});
|
||||
|
||||
return eventsByCourse;
|
||||
}).catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add event listeners to load more courses for the courses view.
|
||||
*
|
||||
@@ -481,11 +486,10 @@ function(
|
||||
*/
|
||||
var registerEventListeners = function(root) {
|
||||
CustomEvents.define(root, [CustomEvents.events.activate]);
|
||||
// Show more courses and load their events when the user clicks the "more courses"
|
||||
// button.
|
||||
// Show more courses and load their events when the user clicks the "more courses" button.
|
||||
root.on(CustomEvents.events.activate, SELECTORS.MORE_COURSES_BUTTON, function(e, data) {
|
||||
enableMoreCoursesButtonLoading(root);
|
||||
loadMoreCourses(root)
|
||||
loadMoreCourses(root, true)
|
||||
.then(function() {
|
||||
disableMoreCoursesButtonLoading(root);
|
||||
return;
|
||||
@@ -514,15 +518,18 @@ function(
|
||||
var init = function(root) {
|
||||
root = $(root);
|
||||
|
||||
setEventReloadTime(root, Date.now());
|
||||
// Only need to handle course loading if the user is actively enrolled in a course.
|
||||
if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {
|
||||
setEventReloadTime(root, Date.now());
|
||||
|
||||
if (root.hasClass('active')) {
|
||||
// Only load if this is active otherwise it will be lazy loaded later.
|
||||
loadMoreCourses(root);
|
||||
root.attr('data-seen', true);
|
||||
if (root.hasClass('active')) {
|
||||
// Only load if this is active otherwise it will be lazy loaded later.
|
||||
loadMoreCourses(root);
|
||||
root.attr('data-seen', true);
|
||||
}
|
||||
|
||||
registerEventListeners(root);
|
||||
}
|
||||
|
||||
registerEventListeners(root);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -532,29 +539,25 @@ function(
|
||||
* @param {object} root The root element for the timeline courses view.
|
||||
*/
|
||||
var reset = function(root) {
|
||||
|
||||
setOffset(root, 0);
|
||||
showLoadingPlaceholder(root);
|
||||
hideNoCoursesWithEventsMessage(root);
|
||||
root.removeAttr('data-seen');
|
||||
|
||||
if (root.hasClass('active')) {
|
||||
shown(root);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If this is the first time this view has been displayed then begin loading
|
||||
* the events.
|
||||
* Begin loading the events unless we know there are no actively enrolled courses.
|
||||
*
|
||||
* @param {object} root The root element for the timeline courses view.
|
||||
*/
|
||||
var shown = function(root) {
|
||||
if (!root.attr('data-seen')) {
|
||||
if (hasLoadedCourses(root)) {
|
||||
// This isn't the first time this view is shown so just reload the
|
||||
// events for the courses we've already loaded.
|
||||
reloadCourseEvents(root);
|
||||
} else {
|
||||
// We haven't loaded any courses yet so do that now.
|
||||
loadMoreCourses(root);
|
||||
}
|
||||
|
||||
if (!root.attr('data-seen') && !root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {
|
||||
loadMoreCourses(root);
|
||||
root.attr('data-seen', true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -42,19 +42,5 @@
|
||||
</div>
|
||||
</div>
|
||||
<div data-region="event-list-content"></div>
|
||||
{{#urls.noevents}}
|
||||
<div class="hidden text-xs-center text-center mt-3" data-region="empty-message">
|
||||
<img
|
||||
src="{{urls.noevents}}"
|
||||
alt=""
|
||||
style="height: 70px; width: 70px"
|
||||
>
|
||||
<p class="text-muted mt-1">{{#str}} noevents, block_timeline {{/str}}</p>
|
||||
</div>
|
||||
{{/urls.noevents}}
|
||||
{{^urls.noevents}}
|
||||
<div class="hidden text-xs-left text-left mt-3" data-region="empty-message">
|
||||
<p class="text-muted mt-1">{{#str}} noevents, block_timeline {{/str}}</p>
|
||||
</div>
|
||||
{{/urls.noevents}}
|
||||
{{> block_timeline/no-events }}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template block_timeline/no-courses
|
||||
|
||||
This template renders a notice that the user has no active courses.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"hascourses": false,
|
||||
"urls": {
|
||||
"noevents": "#"
|
||||
}
|
||||
}
|
||||
}}
|
||||
{{^hascourses}}
|
||||
<div class="text-xs-center text-center mt-3" data-region="no-courses-empty-message">
|
||||
<img
|
||||
src="{{urls.noevents}}"
|
||||
alt=""
|
||||
style="height: 70px; width: 70px"
|
||||
>
|
||||
<p class="text-muted mt-1">{{#str}} nocoursesinprogress, block_timeline {{/str}}</p>
|
||||
</div>
|
||||
{{/hascourses}}
|
||||
@@ -0,0 +1,38 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template block_timeline/no-events
|
||||
|
||||
This template renders confirmation that no events require action.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"urls": {
|
||||
"noevents": "#"
|
||||
}
|
||||
}
|
||||
}}
|
||||
{{#urls.noevents}}
|
||||
<div class="hidden text-xs-center text-center mt-3" data-region="no-events-empty-message">
|
||||
<img
|
||||
src="{{urls.noevents}}"
|
||||
alt=""
|
||||
style="height: 70px; width: 70px"
|
||||
>
|
||||
<p class="text-muted mt-1">{{#str}} noevents, block_timeline {{/str}}</p>
|
||||
</div>
|
||||
{{/urls.noevents}}
|
||||
@@ -22,13 +22,15 @@
|
||||
Example context (json):
|
||||
{}
|
||||
}}
|
||||
<div data-region="course-items-loading-placeholder">
|
||||
<ul class="list-group unstyled">
|
||||
{{> block_timeline/course-item-loading-placeholder }}
|
||||
{{> block_timeline/course-item-loading-placeholder }}
|
||||
</ul>
|
||||
<div class="bg-pulse-grey mt-1" style="width: 100px; height: 30px; margin-left: auto; margin-right: auto"></div>
|
||||
</div>
|
||||
{{#hascourses}}
|
||||
<div data-region="course-items-loading-placeholder">
|
||||
<ul class="list-group unstyled">
|
||||
{{> block_timeline/course-item-loading-placeholder }}
|
||||
{{> block_timeline/course-item-loading-placeholder }}
|
||||
</ul>
|
||||
<div class="bg-pulse-grey mt-1" style="width: 100px; height: 30px; margin-left: auto; margin-right: auto"></div>
|
||||
</div>
|
||||
{{/hascourses}}
|
||||
<ul class="list-group unstyled" data-region="courses-list"></ul>
|
||||
<div class="hidden text-xs-center text-center pt-3" data-region="more-courses-button-container">
|
||||
<button type="button" class="btn btn-primary" data-action="more-courses">
|
||||
@@ -38,11 +40,5 @@
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="hidden text-xs-center text-center mt-3" data-region="no-courses-empty-message">
|
||||
<img
|
||||
src="{{urls.noevents}}"
|
||||
alt=""
|
||||
style="height: 70px; width: 70px"
|
||||
>
|
||||
<p class="text-muted mt-1">{{#str}} nocoursesinprogress, block_timeline {{/str}}</p>
|
||||
</div>
|
||||
{{> block_timeline/no-events }}
|
||||
{{> block_timeline/no-courses }}
|
||||
|
||||
@@ -44,7 +44,7 @@ Feature: The timeline block allows users to see upcoming courses
|
||||
When I click on "Sort by courses" "link" in the "Timeline" "block"
|
||||
Then I should see "Course 1" in the ".block_timeline [data-region='view-courses']" "css_element"
|
||||
And I should see "Course 2" in the ".block_timeline [data-region='view-courses']" "css_element"
|
||||
And I should see "Show more courses" in the "Timeline" "block"
|
||||
And I should not see "Show more courses" in the "Timeline" "block"
|
||||
And "Test choice 1" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Choice closes" in the "Timeline" "block"
|
||||
And "Test feedback 1" "link" should exist in the "Timeline" "block"
|
||||
@@ -92,7 +92,7 @@ Feature: The timeline block allows users to see upcoming courses
|
||||
When I reload the page
|
||||
Then I should see "Course 1" in the "Timeline" "block"
|
||||
And I should see "Course 2" in the "Timeline" "block"
|
||||
And I should see "Show more courses" in the "Timeline" "block"
|
||||
And I should not see "Show more courses" in the "Timeline" "block"
|
||||
And "Test choice 1" "link" should exist in the "Timeline" "block"
|
||||
And "Test feedback 1" "link" should exist in the "Timeline" "block"
|
||||
And "Test feedback 5" "link" should exist in the "Timeline" "block"
|
||||
@@ -111,6 +111,9 @@ Feature: The timeline block allows users to see upcoming courses
|
||||
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
|
||||
And I click on "All" "link" in the "Timeline" "block"
|
||||
When I reload the page
|
||||
And I should not see "Course 3" in the "Timeline" "block"
|
||||
And I should see "Course 2" in the "Timeline" "block"
|
||||
And I should see "Course 1" in the "Timeline" "block"
|
||||
And I click on "Show more courses" "button" in the "Timeline" "block"
|
||||
Then I should see "Course 3" in the "Timeline" "block"
|
||||
And I should see "Course 2" in the "Timeline" "block"
|
||||
@@ -180,3 +183,50 @@ Feature: The timeline block allows users to see upcoming courses
|
||||
And "Test choice 1" "link" should not exist in the "Timeline" "block"
|
||||
And "Test choice 2" "link" should not exist in the "Timeline" "block"
|
||||
And "Test choice 3" "link" should not exist in the "Timeline" "block"
|
||||
|
||||
Scenario: Student not enrolled in any courses sees a message
|
||||
Given I log in as "student2"
|
||||
When I click on "Sort timeline items" "button" in the "Timeline" "block"
|
||||
And I click on "Sort by courses" "link" in the "Timeline" "block"
|
||||
Then I should see "No in-progress courses" in the "Timeline" "block"
|
||||
And I should not see "Test choice 1"
|
||||
|
||||
Scenario: Only courses with matching events are displayed and are refreshed when filtering changes
|
||||
Given I log in as "student1"
|
||||
And I click on "Sort timeline items" "button" in the "Timeline" "block"
|
||||
And I click on "Sort by courses" "link" in the "Timeline" "block"
|
||||
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
|
||||
And I click on "All" "link" in the "Timeline" "block"
|
||||
And I click on "Show more courses" "button" in the "Timeline" "block"
|
||||
And I should see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And "Test assign 1" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And "Test choice 1" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And "Test choice 3" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And "Test feedback 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
|
||||
When I click on "Overdue" "link" in the "Timeline" "block"
|
||||
Then I should see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Course 3" in the "Timeline" "block"
|
||||
And I should not see "Show more courses" in the "Timeline" "block"
|
||||
And "Test assign 1" "link" should exist in the "Timeline" "block"
|
||||
And "Test assign 2" "link" should exist in the "Timeline" "block"
|
||||
And "Test choice 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And "Test choice 2" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And "Test choice 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And "Test feedback 5" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
|
||||
And I click on "Next 7 days" "link" in the "Timeline" "block"
|
||||
And I should see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Course 3" in the "Timeline" "block"
|
||||
And I should not see "Show more courses" in the "Timeline" "block"
|
||||
And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And "Test choice 1" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And "Test feedback 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And "Test choice 2" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And "Test choice 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
|
||||
@@ -5,22 +5,26 @@ Feature: The timeline block allows users to search for upcoming activities
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email | idnumber |
|
||||
| username | firstname | lastname | email | idnumber |
|
||||
| student1 | Student | 1 | student1@example.com | S1 |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category | startdate | enddate |
|
||||
| Course 1 | C1 | 0 | ##1 month ago## | ##15 days ago## |
|
||||
| Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## |
|
||||
| fullname | shortname | category | startdate | enddate |
|
||||
| Course 1 | C1 | 0 | ##1 month ago## | ##15 days ago## |
|
||||
| Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## |
|
||||
| Course 3 | C3 | 0 | ##first day of next month## | ##last day of next month## |
|
||||
| Course 4 | C4 | 0 | ##1 month ago## | ##tomorrow## |
|
||||
| Course 5 | C5 | 0 | ##first day of last month## | ##last day of next month## |
|
||||
And the following "activities" exist:
|
||||
| activity | course | idnumber | name | intro | timeopen | timeclose |
|
||||
| choice | C2 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## |
|
||||
| activity | course | idnumber | name | intro | timeopen | timeclose |
|
||||
| choice | C2 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## |
|
||||
| choice | C1 | choice2 | Test choice 2 | Test choice description | ##first day of +5 months## | ##last day of +5 months## |
|
||||
| choice | C3 | choice3 | Test choice 3 | Test choice description | ##first day of +5 months## | ##last day of +10 months## |
|
||||
| choice | C2 | choice4 | Test choice 4 | Test choice description | ##first day of +5 months## | ##last day of +15 months## |
|
||||
| choice | C1 | choice5 | Test choice 5 | Test choice description | ##first day of +5 months## | ##last day of +20 months## |
|
||||
| choice | C3 | choice6 | Test choice 6 | Test choice description | ##first day of +5 months## | ##last day of +25 months## |
|
||||
| feedback | C2 | feedback1 | Test feedback 1 | Test feedback description | ##yesterday## | ##tomorrow## |
|
||||
| choice | C4 | choice7 | Test choice 7 | Test choice description | ##first day of +5 months## | ##last day of +5 months## |
|
||||
| choice | C5 | choice8 | Test choice 8 | Test choice description | ##first day of +5 months## | ##last day of +10 months## |
|
||||
| feedback | C2 | feedback1 | Test feedback 1 | Test feedback description | ##yesterday## | ##tomorrow## |
|
||||
| feedback | C1 | feedback2 | Test feedback 2 | Test feedback description | ##first day of +5 months## | ##last day of +5 months## |
|
||||
| feedback | C3 | feedback3 | Test feedback 3 | Test feedback description | ##first day of +5 months## | ##last day of +10 months## |
|
||||
And the following "course enrolments" exist:
|
||||
@@ -28,6 +32,8 @@ Feature: The timeline block allows users to search for upcoming activities
|
||||
| student1 | C1 | student |
|
||||
| student1 | C2 | student |
|
||||
| student1 | C3 | student |
|
||||
| student1 | C4 | student |
|
||||
| student1 | C5 | student |
|
||||
|
||||
Scenario: The search should return no events if I enter the wrong value
|
||||
Given I log in as "student1"
|
||||
@@ -88,8 +94,42 @@ Feature: The timeline block allows users to search for upcoming activities
|
||||
Then I should see "Test choice 1" in the "Timeline" "block"
|
||||
And I should see "Test choice 2" in the "Timeline" "block"
|
||||
And I should see "Test choice 3" in the "Timeline" "block"
|
||||
And I should see "Test choice 4" in the "Timeline" "block"
|
||||
And I should see "Test choice 5" in the "Timeline" "block"
|
||||
And I should see "Test choice 7" in the "Timeline" "block"
|
||||
And I should see "Test choice 8" in the "Timeline" "block"
|
||||
And I should not see "Test choice 4" in the "Timeline" "block"
|
||||
And I should not see "Test choice 5" in the "Timeline" "block"
|
||||
And I should not see "Test choice 6" in the "Timeline" "block"
|
||||
And I click on "Show more activities" "button"
|
||||
And I should see "Test choice 4" in the "Timeline" "block"
|
||||
And I should see "Test choice 5" in the "Timeline" "block"
|
||||
And I should see "Test choice 6" in the "Timeline" "block"
|
||||
|
||||
Scenario: Courses view is refreshed when search changes
|
||||
Given I log in as "student1"
|
||||
And I click on "Sort timeline items" "button" in the "Timeline" "block"
|
||||
And I click on "Sort by courses" "link" in the "Timeline" "block"
|
||||
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
|
||||
And I click on "All" "link" in the "Timeline" "block"
|
||||
And I click on "Show more courses" "button" in the "Timeline" "block"
|
||||
And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should see "Test choice 1" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should see "Test choice 7" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should see "Test choice 8" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Test choice 2" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Test choice 3" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
When I set the field "Search by activity type or name" to "choice 1"
|
||||
And I wait until "Course 4" "text" does not exist
|
||||
Then I should see "Test choice 1" in the "Timeline" "block"
|
||||
And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Test choice 2" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Test choice 3" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Test choice 7" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
And I should not see "Test choice 8" in the ".block-timeline [data-region='view-courses']" "css_element"
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
This file describes API changes in the timeline block code.
|
||||
|
||||
=== 4.0 ===
|
||||
* The timeline block courses view has been updated to only list courses which contain at least one action event within the chosen
|
||||
filter, so now uses \core_course\external\get_enrolled_courses_with_action_events_by_timeline_classification to fetch courses
|
||||
instead of core_course_external::get_enrolled_courses_by_timeline_classification, which fetches all courses within the limit.
|
||||
|
||||
=== 3.7 ===
|
||||
|
||||
* The 'block/timeline:addinstance' capability has been removed. It has never been used in code.
|
||||
Vendored
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("core_course/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a,c,d,e){var f={classification:a};if("undefined"!=typeof c){f.limit=c}if("undefined"!=typeof d){f.offset=d}if("undefined"!=typeof e){f.sort=e}return b.default.call([{methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:f}])[0]},d=function(a,c,d,e){var f={};if("undefined"!=typeof a){f.userid=a}if("undefined"!=typeof c){f.limit=c}if("undefined"!=typeof d){f.offset=d}if("undefined"!=typeof e){f.sort=e}return b.default.call([{methodname:"core_course_get_recent_courses",args:f}])[0]},e=function(a,c){return b.default.call([{methodname:"core_course_get_enrolled_users_by_cmid",args:{cmid:a,groupid:c}}])[0]},f=function(a,c){return b.default.call([{methodname:"core_completion_update_activity_completion_status_manually",args:{cmid:a,completed:c}}])[0]};a.default={getEnrolledCoursesByTimelineClassification:c,getLastAccessedCourses:d,getUsersFromCourseModuleID:e,toggleManualCompletion:f};return a.default});
|
||||
define ("core_course/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a,c,d,e){var f={classification:a};if("undefined"!=typeof c){f.limit=c}if("undefined"!=typeof d){f.offset=d}if("undefined"!=typeof e){f.sort=e}return b.default.call([{methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:f}])[0]},d=function(a){var c=1<arguments.length&&arguments[1]!==void 0?arguments[1]:0,d=2<arguments.length&&arguments[2]!==void 0?arguments[2]:0,e=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null,f=4<arguments.length&&arguments[4]!==void 0?arguments[4]:null,g=5<arguments.length&&arguments[5]!==void 0?arguments[5]:null,h=6<arguments.length&&arguments[6]!==void 0?arguments[6]:null;return b.default.call([{methodname:"core_course_get_enrolled_courses_with_action_events_by_timeline_classification",args:{classification:a,limit:c,offset:d,sort:e,eventsfrom:g,eventsto:h,searchvalue:f}}])[0]},e=function(a,c,d,e){var f={};if("undefined"!=typeof a){f.userid=a}if("undefined"!=typeof c){f.limit=c}if("undefined"!=typeof d){f.offset=d}if("undefined"!=typeof e){f.sort=e}return b.default.call([{methodname:"core_course_get_recent_courses",args:f}])[0]},f=function(a,c){return b.default.call([{methodname:"core_course_get_enrolled_users_by_cmid",args:{cmid:a,groupid:c}}])[0]},g=function(a,c){return b.default.call([{methodname:"core_completion_update_activity_completion_status_manually",args:{cmid:a,completed:c}}])[0]};a.default={getEnrolledCoursesByTimelineClassification:c,getLastAccessedCourses:e,getUsersFromCourseModuleID:f,toggleManualCompletion:g,getEnrolledCoursesWithEventsByTimelineClassification:d};return a.default});
|
||||
//# sourceMappingURL=repository.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -58,6 +58,40 @@ const getEnrolledCoursesByTimelineClassification = (classification, limit, offse
|
||||
return Ajax.call([request])[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list of courses that the logged in user is enrolled in, where they have at least one action event,
|
||||
* for a given timeline classification.
|
||||
*
|
||||
* @param {string} classification past, inprogress, or future
|
||||
* @param {int} limit The maximum number of courses to return
|
||||
* @param {int} offset Skip this many results from the start of the result set
|
||||
* @param {string} sort Column to sort by and direction, e.g. 'shortname asc'
|
||||
* @param {string} searchValue Optional text search value
|
||||
* @param {int} eventsFrom Optional start timestamp (inclusive) that the course should have event(s) in
|
||||
* @param {int} eventsTo Optional end timestamp (inclusive) that the course should have event(s) in
|
||||
* @return {object} jQuery promise resolved with courses.
|
||||
*/
|
||||
const getEnrolledCoursesWithEventsByTimelineClassification = (classification, limit = 0, offset = 0, sort = null,
|
||||
searchValue = null, eventsFrom = null, eventsTo = null) => {
|
||||
|
||||
const args = {
|
||||
classification: classification,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
sort: sort,
|
||||
eventsfrom: eventsFrom,
|
||||
eventsto: eventsTo,
|
||||
searchvalue: searchValue,
|
||||
};
|
||||
|
||||
const request = {
|
||||
methodname: 'core_course_get_enrolled_courses_with_action_events_by_timeline_classification',
|
||||
args: args
|
||||
};
|
||||
|
||||
return Ajax.call([request])[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of courses that the user has most recently accessed.
|
||||
*
|
||||
@@ -137,4 +171,5 @@ export default {
|
||||
getLastAccessedCourses,
|
||||
getUsersFromCourseModuleID: getEnrolledUsersFromCourseModuleID,
|
||||
toggleManualCompletion,
|
||||
getEnrolledCoursesWithEventsByTimelineClassification,
|
||||
};
|
||||
|
||||
Vendored
+220
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_course\external;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context_user;
|
||||
use core_calendar_external;
|
||||
use core_course_external;
|
||||
use external_api;
|
||||
use external_function_parameters;
|
||||
use external_multiple_structure;
|
||||
use external_single_structure;
|
||||
use external_value;
|
||||
|
||||
require_once("{$CFG->dirroot}/calendar/externallib.php");
|
||||
require_once("{$CFG->dirroot}/course/externallib.php");
|
||||
|
||||
/**
|
||||
* Class for fetching courses which have action event(s) and match given filter parameters.
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2022 Michael Hawkins <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class get_enrolled_courses_with_action_events_by_timeline_classification extends external_api {
|
||||
|
||||
/**
|
||||
* Returns the description of method parameters.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters(
|
||||
[
|
||||
'classification' => new external_value(PARAM_ALPHA, 'future, inprogress, or past'),
|
||||
'limit' => new external_value(PARAM_INT, 'Result set limit', VALUE_DEFAULT, 0),
|
||||
'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0),
|
||||
'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null),
|
||||
'customfieldname' => new external_value(PARAM_ALPHANUMEXT, 'Used when classification = customfield',
|
||||
VALUE_DEFAULT, null),
|
||||
'customfieldvalue' => new external_value(PARAM_RAW, 'Used when classification = customfield',
|
||||
VALUE_DEFAULT, null),
|
||||
'searchvalue' => new external_value(PARAM_TEXT, 'The value a user wishes to search against',
|
||||
VALUE_DEFAULT, null),
|
||||
'eventsfrom' => new external_value(PARAM_INT, 'Optional starting timestamp for action events',
|
||||
VALUE_DEFAULT, null),
|
||||
'eventsto' => new external_value(PARAM_INT, 'Optional ending timestamp for action events',
|
||||
VALUE_DEFAULT, null),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get courses matching the given timeline classification which have action event(s).
|
||||
*
|
||||
* Fetches courses by timeline classification which have at least one action event within the specified filtering.
|
||||
*
|
||||
* @param string $classification past, inprogress, or future
|
||||
* @param int $limit Number of courses with events to attempt to fetch
|
||||
* @param int $offset Offset the full course set before timeline classification is applied
|
||||
* @param string $sort SQL sort string for results
|
||||
* @param string $customfieldname Custom field name used when when classification is customfield
|
||||
* @param string $customfieldvalue Custom field value used when when classification is customfield
|
||||
* @param string $searchvalue Text search being applied
|
||||
* @param int $eventsfrom The start timestamp (inclusive) to search from for action events in the course
|
||||
* @param int $eventsto The end timestamp (inclusive) to search to for action events in the course
|
||||
* @return array list of courses and any warnings
|
||||
*/
|
||||
public static function execute(
|
||||
string $classification,
|
||||
int $limit = 0,
|
||||
int $offset = 0,
|
||||
string $sort = null,
|
||||
string $customfieldname = null,
|
||||
string $customfieldvalue = null,
|
||||
string $searchvalue = null,
|
||||
int $eventsfrom = null,
|
||||
int $eventsto = null
|
||||
): array {
|
||||
global $USER;
|
||||
|
||||
self::validate_context(context_user::instance($USER->id));
|
||||
|
||||
$params = self::validate_parameters(
|
||||
self::execute_parameters(),
|
||||
[
|
||||
'classification' => $classification,
|
||||
'limit' => $limit,
|
||||
'offset' => $offset,
|
||||
'sort' => $sort,
|
||||
'customfieldname' => $customfieldname,
|
||||
'customfieldvalue' => $customfieldvalue,
|
||||
'searchvalue' => $searchvalue,
|
||||
'eventsfrom' => $eventsfrom,
|
||||
'eventsto' => $eventsto,
|
||||
]
|
||||
);
|
||||
|
||||
$classification = $params['classification'];
|
||||
$limit = $params['limit'];
|
||||
$offset = $params['offset'];
|
||||
$sort = $params['sort'];
|
||||
$customfieldname = $params['customfieldname'];
|
||||
$customfieldvalue = $params['customfieldvalue'];
|
||||
$searchvalue = $params['searchvalue'];
|
||||
$eventsfrom = $params['eventsfrom'];
|
||||
$eventsto = $params['eventsto'];
|
||||
$morecoursestofetch = true;
|
||||
$morecoursespossible = true;
|
||||
$coursesfinal = [];
|
||||
|
||||
do {
|
||||
// Fetch courses.
|
||||
[
|
||||
'courses' => $coursesfetched,
|
||||
'nextoffset' => $offset,
|
||||
] = core_course_external::get_enrolled_courses_by_timeline_classification($classification, $limit,
|
||||
$offset, $sort, $customfieldname, $customfieldvalue, $searchvalue);
|
||||
|
||||
$courseids = array_column($coursesfetched, 'id');
|
||||
$coursesfetched = array_combine($courseids, $coursesfetched);
|
||||
|
||||
if (!empty($courseids)) {
|
||||
// Need to check this to know how many are expected (since it is possible for this to be less than the limit).
|
||||
$numcoursesfetched = count($courseids);
|
||||
$numfetchedwithevents = 0;
|
||||
|
||||
// If less courses are fetched than we requested, we know it is not possible for more courses to be available.
|
||||
if ($numcoursesfetched < $limit) {
|
||||
$morecoursestofetch = false;
|
||||
$morecoursespossible = false;
|
||||
}
|
||||
|
||||
// Try to fetch one action event within the time/search parameters for each course.
|
||||
$events = core_calendar_external::get_calendar_action_events_by_courses($courseids, $eventsfrom, $eventsto, 1,
|
||||
$searchvalue);
|
||||
|
||||
foreach ($events->groupedbycourse as $courseevents) {
|
||||
$courseid = $courseevents->courseid;
|
||||
|
||||
// Only include courses which contain at least one event.
|
||||
if (empty($courseevents->events)) {
|
||||
unset($coursesfetched[$courseid]);
|
||||
} else {
|
||||
$numfetchedwithevents++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add courses with events to the final course list in order.
|
||||
$coursesfinal = array_merge($coursesfinal, $coursesfetched);
|
||||
|
||||
// If any courses did not have events, adjust the limit so we can attempt to fetch as many as are still required.
|
||||
if ($numfetchedwithevents < $numcoursesfetched) {
|
||||
$limit -= $numfetchedwithevents;
|
||||
} else {
|
||||
// If we have found as many courses as required or are available, no need to attempt fetching more.
|
||||
$morecoursestofetch = false;
|
||||
}
|
||||
} else {
|
||||
$morecoursestofetch = false;
|
||||
$morecoursespossible = false;
|
||||
}
|
||||
|
||||
} while ($morecoursestofetch);
|
||||
|
||||
static $isrecursivecall = false;
|
||||
$morecoursesavailable = false;
|
||||
|
||||
// Recursively call this method to check if at least one more course is available if we know that is a possibility.
|
||||
if (!$isrecursivecall && $morecoursespossible) {
|
||||
// Prevent infinite recursion.
|
||||
$isrecursivecall = true;
|
||||
|
||||
$additionalcourses = self::execute(
|
||||
$classification, 1, $offset, $sort, $customfieldname, $customfieldvalue, $searchvalue, $eventsfrom, $eventsto
|
||||
);
|
||||
|
||||
if (!empty($additionalcourses['courses'])) {
|
||||
$morecoursesavailable = true;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'courses' => $coursesfinal,
|
||||
'nextoffset' => $offset,
|
||||
'morecoursesavailable' => $morecoursesavailable,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method result value.
|
||||
*
|
||||
* @return external_description
|
||||
*/
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure(
|
||||
[
|
||||
'courses' => new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Course'),
|
||||
'nextoffset' => new external_value(PARAM_INT, 'Offset for the next request'),
|
||||
'morecoursesavailable' => new external_value(PARAM_BOOL,
|
||||
'Whether more courses with events exist within the provided parameters'),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,10 @@ course formats don't have their own renderer.
|
||||
* Calendar menu entry is now moved to User menu items, so the following methods will not return the calendar item anymore:
|
||||
- course_get_user_navigation_options
|
||||
- core_course_externalcore_course_external::get_user_navigation_options
|
||||
* New external \core_course\external\get_enrolled_courses_with_action_events_by_timeline_classification:
|
||||
- Returns courses the user is enrolled in which contain at least one action event in the supplied time/text filtering parameters.
|
||||
- Provides a similar response to get_enrolled_courses_by_timeline_classification, but omits courses which have no matching
|
||||
action events.
|
||||
|
||||
=== 3.11 ===
|
||||
* A new callback xxx_coursemodule_definition_after_data that allows plugins to extend activity forms after the data is set.
|
||||
|
||||
@@ -648,6 +648,15 @@ $functions = array(
|
||||
'ajax' => true,
|
||||
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
|
||||
),
|
||||
'core_course_get_enrolled_courses_with_action_events_by_timeline_classification' => array(
|
||||
'classname' => '\core_course\external\get_enrolled_courses_with_action_events_by_timeline_classification',
|
||||
'methodname' => 'execute',
|
||||
'classpath' => '',
|
||||
'description' => 'List of enrolled courses with action events in a given timeframe, for the given timeline classification.',
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
|
||||
),
|
||||
'core_course_get_recent_courses' => array(
|
||||
'classname' => 'core_course_external',
|
||||
'methodname' => 'get_recent_courses',
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2022011400.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2022011400.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '4.0dev+ (Build: 20220114)'; // Human-friendly version name
|
||||
|
||||
Reference in New Issue
Block a user