diff --git a/course/amd/build/actions.min.js b/course/amd/build/actions.min.js index 611546dbe2e..ba34f49aa12 100644 --- a/course/amd/build/actions.min.js +++ b/course/amd/build/actions.min.js @@ -1,2 +1,2 @@ -define ("core_course/actions",["jquery","core/ajax","core/templates","core/notification","core/str","core/url","core/yui","core/modal_factory","core/modal_events","core/key_codes","core/log","core_courseformat/courseeditor"],function(a,b,c,d,e,f,g,h,i,j,k,l){var m=["moveSection","moveCm"],n=l.getCurrentCourseEditor(),o={EDITINPROGRESS:"editinprogress",SECTIONDRAGGABLE:"sectiondraggable",EDITINGMOVE:"editing_move"},p={ACTIVITYLI:"li.activity",ACTIONAREA:".actions",ACTIVITYACTION:"a.cm-edit-action",MENU:".moodle-actionmenu[data-enhance=moodle-core-actionmenu]",TOGGLE:".toggle-display,.dropdown-toggle",SECTIONLI:"li.section",SECTIONACTIONMENU:".section_action_menu",ADDSECTIONS:"#changenumsections [data-add-sections]"};g.use("moodle-course-coursebase",function(){var a=M.course.format.get_section_selector();if(a){p.SECTIONLI=a}});var q=function(a){var b=a.get(0);if(b.dataset.id){return b.dataset.id}var c;g.use("moodle-course-util",function(a){c=a.Moodle.core_course.util.cm.getId(a.Node(b))});return c},r=function(a){var b;g.use("moodle-course-util",function(c){b=c.Moodle.core_course.util.cm.getName(c.Node(a.get(0)))});var c=n.state,d=q(a);if(!b&&c&&d){var e;b=null===(e=c.cm.get(d))||void 0===e?void 0:e.name}return b},s=function(a){a.addClass(o.EDITINPROGRESS);var b=a.find(p.ACTIONAREA).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();if(a.data("id")!==void 0){n.dispatch("cmLock",[a.data("id")],!0)}return c}return null},t=function(a){a.addClass(o.EDITINPROGRESS);var b=a.find(p.SECTIONACTIONMENU).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();if(a.data("id")!==void 0){n.dispatch("sectionLock",[a.data("id")],!0)}return c}return null},u=function(a){var b=M.util.add_lightbox(g,g.Node(a.get(0)));b.show();return b},v=function(a,b,c){window.setTimeout(function(){a.removeClass(o.EDITINPROGRESS);if(b){b.hide()}if(a.data("id")!==void 0){var c="section"===a.data("for")?"sectionLock":"cmLock";n.dispatch(c,[a.data("id")],!1)}},c)},w=function(a,b){if(a){window.setTimeout(function(){a.hide()},b)}},x=function(a){g.use("moodle-course-coursebase",function(){M.course.coursebase.invoke_function("setup_for_resource","#"+a)});if(M.core.actionmenu&&M.core.actionmenu.newDOMNode){M.core.actionmenu.newDOMNode(g.one("#"+a))}},y=function(b,c){var d=a("#"+b),e="[data-action="+c+"]";if("groupsseparate"===c||"groupsvisible"===c||"groupsnone"===c){e="[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]"}if(d.find(e).is(":visible")){d.find(e).focus()}else{d.find(p.MENU).find(p.TOGGLE).focus()}},z=function(b){var c=a("a:visible"),d=!1,e=null;c.each(function(){if(a.contains(b[0],this)){d=!0}else if(d){e=this;return!1}});return e},A=function(c,e,f){var g=f.attr("data-action"),h=s(c),i=b.call([{methodname:"core_course_edit_module",args:{id:e,action:g,sectionreturn:f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0}}],!0),j;if("duplicate"===g){j=u(f.closest(p.SECTIONLI))}a.when.apply(a,i).done(function(b){var d=z(c);c.replaceWith(b);var f=[];a("
"+b+"
").find(p.ACTIVITYLI).each(function(b){x(a(this).attr("id"));if(0===b){y(a(this).attr("id"),g);d=null}f.push(q(a(this)))});if(d){d.focus()}v(c,h,400);w(j,400);c.trigger(a.Event("coursemoduleedited",{ajaxreturn:b,action:g}));n.dispatch("legacyActivityAction",g,e,f)}).fail(function(b){v(c,h);w(j);var f=a.Event("coursemoduleeditfailed",{exception:b,action:g});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}})},B=function(c,d,e){var f=a(c),g=s(f),h=b.call([{methodname:"core_course_get_module",args:{id:d,sectionreturn:e}}],!0);return new Promise(function(b,c){a.when.apply(a,h).done(function(a){v(f,g,400);G(a);b(a)}).fail(function(){v(f,g);c()})})},C=function(a,b){var c=a.attr("class").match(/modtype_([^\s]*)/)[1],f=r(a);e.get_string("pluginname",c).done(function(a){e.get_strings([{key:"confirm"},{key:null===f?"deletechecktype":"deletechecktypename",param:{type:a,name:f}},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],b)})})},D=function(a,b){e.get_strings([{key:"confirm"},{key:"yes"},{key:"no"}]).done(function(c){d.confirm(c[0],a,c[1],c[2],b)})},E=function(a,b,f,g,h){return e.get_strings([{key:f,component:g}]).then(function(d){a.find("span.menu-action-text").html(d[0]);return c.renderPix(b,"core")}).then(function(b){a.find(".icon").replaceWith(b);a.attr("data-action",h)}).catch(d.exception)},F=function(b,c,d,e,f){var g=c.attr("data-action");if("hide"===g||"show"===g){if("hide"===g){b.addClass("hidden");E(c,"i/show","showfromothers","format_"+e,"show")}else{b.removeClass("hidden");E(c,"i/hide","hidefromothers","format_"+e,"hide")}if(d.modules!==void 0){for(var h in d.modules){G(d.modules[h])}}if(d.section_availability!==void 0){b.find(".section_availability").first().replaceWith(d.section_availability)}var k=n.state.section.get(f);if(k!==void 0){n.dispatch("sectionState",[f])}}else if("setmarker"===g){var i=a(p.SECTIONLI+".current"),j=i.find(p.SECTIONACTIONMENU+" a[data-action=removemarker]");i.removeClass("current");E(j,"i/marker","highlight","core","setmarker");b.addClass("current");E(c,"i/marked","highlightoff","core","removemarker");n.dispatch("legacySectionAction",g,f)}else if("removemarker"===g){b.removeClass("current");E(c,"i/marker","highlight","core","setmarker");n.dispatch("legacySectionAction",g,f)}},G=function(b){a("
"+b+"
").find(p.ACTIVITYLI).each(function(){var c=a(this).attr("id");a(p.ACTIVITYLI+"#"+c).replaceWith(b);x(c)})},H=function(c,e,f,g){var h=f.attr("data-action"),i=f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0;if(n.supportComponents&&m.includes(h)){return!1}var j=t(c),k=b.call([{methodname:"core_course_edit_section",args:{id:e,action:h,sectionreturn:i}}],!0),l=u(c);a.when.apply(a,k).done(function(b){var d=a.parseJSON(b);v(c,j);w(l);c.find(p.SECTIONACTIONMENU).find(p.TOGGLE).focus();var i=a.Event("coursesectionedited",{ajaxreturn:d,action:h});c.trigger(i);if(!i.isDefaultPrevented()){F(c,f,d,g,e)}}).fail(function(b){v(c,j);w(l);var f=a.Event("coursesectioneditfailed",{exception:b,action:h});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}});return!0};g.use("moodle-course-coursebase",function(){M.course.coursebase.register_module({set_visibility_resource_ui:function set_visibility_resource_ui(b){var c=a(b.element.getDOMNode()),d=q(c);if(d){var e=c.find("."+o.EDITINGMOVE).attr("data-sectionreturn");B(c,d,e)}},updateMovedCmState:function updateMovedCmState(a){var b=n.state,c=b.cm.get(a.cmid);if(c!==void 0){n.dispatch("sectionState",[c.sectionid])}n.dispatch("cmState",[a.cmid])},updateMovedSectionState:function updateMovedSectionState(){n.dispatch("courseState")}})});n.addMutations({legacyActivityAction:function legacyActivityAction(a,b,c,d){var e=a.state,f=e.cm.get(c);if(f===void 0){return}var g=e.section.get(f.sectionid);if(g===void 0){return}a.setReadOnly(!1);f.locked=!0;a.setReadOnly(!0);a.setReadOnly(!1);f.locked=!1;switch(b){case"delete":g.cmlist=g.cmlist.reduce(function(a,b){if(b!=c){a.push(b)}return a},[]);e.cm.delete(c);break;case"hide":case"show":f.visible="show"===b?!0:!1;break;case"duplicate":n.dispatch("cmState",d);break;}a.setReadOnly(!0)},legacySectionAction:function legacySectionAction(a,b,c){var d=a.state,e=d.section.get(c);if(e===void 0){return}a.setReadOnly(!1);e.locked=!0;a.setReadOnly(!0);a.setReadOnly(!1);e.locked=!1;switch(b){case"setmarker":d.section.forEach(function(a){if(a.id!=c){a.current=!1}});e.current=!0;break;case"removemarker":e.current=!1;break;}a.setReadOnly(!0)}});return{initCoursePage:function initCoursePage(b){a("body").on("click keypress",p.ACTIVITYLI+" "+p.ACTIVITYACTION+"[data-action]",function(b){if("keypress"===b.type&&13!==b.keyCode){return}var c=a(this),d=c.closest(p.ACTIVITYLI),e=c.attr("data-action"),f=q(d);switch(e){case"moveleft":case"moveright":case"delete":case"duplicate":case"hide":case"stealth":case"show":case"groupsseparate":case"groupsvisible":case"groupsnone":break;default:return;}if(!f){return}b.preventDefault();if("delete"===e){C(d,function(){A(d,f,c)})}else{A(d,f,c)}});a("body").on("click keypress",p.SECTIONLI+" "+p.SECTIONACTIONMENU+"[data-sectionid] a[data-action]",function(c){if("keypress"===c.type&&13!==c.keyCode){return}var d=a(this),e=d.closest(p.SECTIONLI),f=d.closest(p.SECTIONACTIONMENU).attr("data-sectionid"),g=!0;if(d.attr("data-confirm")){D(d.attr("data-confirm"),function(){g=H(e,f,d,b)})}else{g=H(e,f,d,b)}if(g){c.preventDefault()}});a("body").on("updated","".concat(p.SECTIONLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){var b=n.state,c=b.section.get(a.ajaxreturn.itemid);if(c!==void 0){n.dispatch("sectionState",[a.ajaxreturn.itemid])}}});a("body").on("updated","".concat(p.ACTIVITYLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){n.dispatch("cmState",[a.ajaxreturn.itemid])}});e.get_string("numberweeks").done(function(b){var c=a(p.ADDSECTIONS),d=c.attr("data-add-sections"),e=c.attr("data-new-sections"),f=a("
");f.find("label").html(b);h.create({title:d,type:h.types.SAVE_CANCEL,body:f.html()},c).done(function(b){var e=a(b.getBody()).find("#add_section_numsections"),f=function(){if(""+parseInt(e.val())===e.val()&&1<=parseInt(e.val())){document.location=c.attr("href")+"&numsections="+parseInt(e.val())}};b.setSaveButtonText(d);b.getRoot().on(i.shown,function(){e.focus().select().on("keydown",function(a){if(a.keyCode===j.enter){f()}})});b.getRoot().on(i.save,function(a){a.preventDefault();f()})})})},replaceSectionActionItem:function replaceSectionActionItem(a,b,c,d,e,f){k.debug("replaceSectionActionItem() is deprecated and will be removed.");var g=a.find(p.SECTIONACTIONMENU+" "+b);E(g,c,d,e,f)},refreshModule:B}}); +define ("core_course/actions",["jquery","core/ajax","core/templates","core/notification","core/str","core/url","core/yui","core/modal_factory","core/modal_events","core/key_codes","core/log","core_courseformat/courseeditor","core/event_dispatcher","core_course/events"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n){var o=["moveSection","moveCm","addSection","deleteSection"],p=l.getCurrentCourseEditor(),q,r={EDITINPROGRESS:"editinprogress",SECTIONDRAGGABLE:"sectiondraggable",EDITINGMOVE:"editing_move"},s={ACTIVITYLI:"li.activity",ACTIONAREA:".actions",ACTIVITYACTION:"a.cm-edit-action",MENU:".moodle-actionmenu[data-enhance=moodle-core-actionmenu]",TOGGLE:".toggle-display,.dropdown-toggle",SECTIONLI:"li.section",SECTIONACTIONMENU:".section_action_menu",ADDSECTIONS:".changenumsections [data-add-sections]"};g.use("moodle-course-coursebase",function(){var a=M.course.format.get_section_selector();if(a){s.SECTIONLI=a}});var t=function(a,b,c,d){if(!(c instanceof Element)&&c.get!==void 0){c=c.get(0)}return m.dispatchEvent(a,b,c,d)},u=function(a){var b=a.get(0);if(b.dataset.id){return b.dataset.id}var c;g.use("moodle-course-util",function(a){c=a.Moodle.core_course.util.cm.getId(a.Node(b))});return c},v=function(a){var b;g.use("moodle-course-util",function(c){b=c.Moodle.core_course.util.cm.getName(c.Node(a.get(0)))});var c=p.state,d=u(a);if(!b&&c&&d){var e;b=null===(e=c.cm.get(d))||void 0===e?void 0:e.name}return b},w=function(a){a.addClass(r.EDITINPROGRESS);var b=a.find(s.ACTIONAREA).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();if(a.data("id")!==void 0){p.dispatch("cmLock",[a.data("id")],!0)}return c}return null},x=function(a){a.addClass(r.EDITINPROGRESS);var b=a.find(s.SECTIONACTIONMENU).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();if(a.data("id")!==void 0){p.dispatch("sectionLock",[a.data("id")],!0)}return c}return null},y=function(a){var b=M.util.add_lightbox(g,g.Node(a.get(0)));b.show();return b},z=function(a,b,c){window.setTimeout(function(){a.removeClass(r.EDITINPROGRESS);if(b){b.hide()}if(a.data("id")!==void 0){var c="section"===a.data("for")?"sectionLock":"cmLock";p.dispatch(c,[a.data("id")],!1)}},c)},A=function(a,b){if(a){window.setTimeout(function(){a.hide()},b)}},B=function(a){g.use("moodle-course-coursebase",function(){M.course.coursebase.invoke_function("setup_for_resource","#"+a)});if(M.core.actionmenu&&M.core.actionmenu.newDOMNode){M.core.actionmenu.newDOMNode(g.one("#"+a))}},C=function(b,c){var d=a("#"+b),e="[data-action="+c+"]";if("groupsseparate"===c||"groupsvisible"===c||"groupsnone"===c){e="[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]"}if(d.find(e).is(":visible")){d.find(e).focus()}else{d.find(s.MENU).find(s.TOGGLE).focus()}},D=function(b){var c=a("a:visible"),d=!1,e=null;c.each(function(){if(a.contains(b[0],this)){d=!0}else if(d){e=this;return!1}return!0});return e},E=function(c,e,f){var g=f.attr("data-action"),h=w(c),i=b.call([{methodname:"core_course_edit_module",args:{id:e,action:g,sectionreturn:f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0}}],!0),j;if("duplicate"===g){j=y(f.closest(s.SECTIONLI))}a.when.apply(a,i).done(function(b){var d=D(c);c.replaceWith(b);var f=[];a("
"+b+"
").find(s.ACTIVITYLI).each(function(b){B(a(this).attr("id"));if(0===b){C(a(this).attr("id"),g);d=null}f.push(u(a(this)))});if(d){d.focus()}z(c,h,400);A(j,400);c.trigger(a.Event("coursemoduleedited",{ajaxreturn:b,action:g}));p.dispatch("legacyActivityAction",g,e,f)}).fail(function(b){z(c,h);A(j);var f=a.Event("coursemoduleeditfailed",{exception:b,action:g});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}})},F=function(c,d,e){if(e===void 0){e=p.sectionReturn}var f=a(c),g=w(f),h=b.call([{methodname:"core_course_get_module",args:{id:d,sectionreturn:e}}],!0);return new Promise(function(b,c){a.when.apply(a,h).done(function(a){z(f,g,400);K(a);b(a)}).fail(function(){z(f,g);c()})})},G=function(a,b){var c=a.attr("class").match(/modtype_([^\s]*)/)[1],f=v(a);e.get_string("pluginname",c).done(function(a){e.get_strings([{key:"confirm",component:"core"},{key:null===f?"deletechecktype":"deletechecktypename",param:{type:a,name:f}},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],b)})})},H=function(a,b){e.get_strings([{key:"confirm"},{key:"yes"},{key:"no"}]).done(function(c){d.confirm(c[0],a,c[1],c[2],b)})},I=function(a,b,f,g,h){return e.get_strings([{key:f,component:g}]).then(function(d){a.find("span.menu-action-text").html(d[0]);return c.renderPix(b,"core")}).then(function(b){a.find(".icon").replaceWith(b);a.attr("data-action",h)}).catch(d.exception)},J=function(b,c,d,e,f){var g=c.attr("data-action");if("hide"===g||"show"===g){if("hide"===g){b.addClass("hidden");I(c,"i/show","showfromothers","format_"+e,"show")}else{b.removeClass("hidden");I(c,"i/hide","hidefromothers","format_"+e,"hide")}if(d.modules!==void 0){for(var h in d.modules){K(d.modules[h])}}if(d.section_availability!==void 0){b.find(".section_availability").first().replaceWith(d.section_availability)}var k=p.state.section.get(f);if(k!==void 0){p.dispatch("sectionState",[f])}}else if("setmarker"===g){var i=a(s.SECTIONLI+".current"),j=i.find(s.SECTIONACTIONMENU+" a[data-action=removemarker]");i.removeClass("current");I(j,"i/marker","highlight","core","setmarker");b.addClass("current");I(c,"i/marked","highlightoff","core","removemarker");p.dispatch("legacySectionAction",g,f)}else if("removemarker"===g){b.removeClass("current");I(c,"i/marker","highlight","core","setmarker");p.dispatch("legacySectionAction",g,f)}},K=function(b){a("
"+b+"
").find(s.ACTIVITYLI).each(function(){var c=a(this).attr("id");a(s.ACTIVITYLI+"#"+c).replaceWith(b);B(c)})},L=function(c,e,f,g){var h=f.attr("data-action"),i=f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0;if(p.supportComponents&&o.includes(h)){return!1}var j=x(c),k=b.call([{methodname:"core_course_edit_section",args:{id:e,action:h,sectionreturn:i}}],!0),l=y(c);a.when.apply(a,k).done(function(b){var d=a.parseJSON(b);z(c,j);A(l);c.find(s.SECTIONACTIONMENU).find(s.TOGGLE).focus();var i=a.Event("coursesectionedited",{ajaxreturn:d,action:h});c.trigger(i);if(!i.isDefaultPrevented()){J(c,f,d,g,e)}}).fail(function(b){z(c,j);A(l);var f=a.Event("coursesectioneditfailed",{exception:b,action:h});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}});return!0};g.use("moodle-course-coursebase",function(){M.course.coursebase.register_module({set_visibility_resource_ui:function set_visibility_resource_ui(b){var c=a(b.element.getDOMNode()),d=u(c);if(d){var e=c.find("."+r.EDITINGMOVE).attr("data-sectionreturn");F(c,d,e)}},updateMovedCmState:function updateMovedCmState(a){var b=p.state,c=b.cm.get(a.cmid);if(c!==void 0){p.dispatch("sectionState",[c.sectionid])}p.dispatch("cmState",[a.cmid])},updateMovedSectionState:function updateMovedSectionState(){p.dispatch("courseState")}})});p.addMutations({legacyActivityAction:function legacyActivityAction(a,b,c,d){var e=a.state,f=e.cm.get(c);if(f===void 0){return}var g=e.section.get(f.sectionid);if(g===void 0){return}a.setReadOnly(!1);f.locked=!0;a.setReadOnly(!0);a.setReadOnly(!1);f.locked=!1;switch(b){case"delete":g.cmlist=g.cmlist.reduce(function(a,b){if(b!=c){a.push(b)}return a},[]);e.cm.delete(c);break;case"hide":case"show":f.visible="show"===b?!0:!1;break;case"duplicate":p.dispatch("cmState",d);break;}a.setReadOnly(!0)},legacySectionAction:function legacySectionAction(a,b,c){var d=a.state,e=d.section.get(c);if(e===void 0){return}a.setReadOnly(!1);e.locked=!0;a.setReadOnly(!0);a.setReadOnly(!1);e.locked=!1;switch(b){case"setmarker":d.section.forEach(function(a){if(a.id!=c){a.current=!1}});e.current=!0;break;case"removemarker":e.current=!1;break;}a.setReadOnly(!0)}});return{initCoursePage:function initCoursePage(b){q=b;a("body").on("click keypress",s.ACTIVITYLI+" "+s.ACTIVITYACTION+"[data-action]",function(b){if("keypress"===b.type&&13!==b.keyCode){return}var c=a(this),d=c.closest(s.ACTIVITYLI),e=c.attr("data-action"),f=u(d);switch(e){case"moveleft":case"moveright":case"delete":case"duplicate":case"hide":case"stealth":case"show":case"groupsseparate":case"groupsvisible":case"groupsnone":break;default:return;}if(!f){return}b.preventDefault();if("delete"===e){G(d,function(){E(d,f,c)})}else{E(d,f,c)}});a("body").on("click keypress",s.SECTIONLI+" "+s.SECTIONACTIONMENU+"[data-sectionid] a[data-action]",function(c){if("keypress"===c.type&&13!==c.keyCode){return}var d=a(this),e=d.closest(s.SECTIONLI),f=d.closest(s.SECTIONACTIONMENU).attr("data-sectionid"),g=!0;if(d.attr("data-confirm")){H(d.attr("data-confirm"),function(){g=L(e,f,d,b)})}else{g=L(e,f,d,b)}if(g){c.preventDefault()}});a("body").on("updated","".concat(s.SECTIONLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){var b=p.state,c=b.section.get(a.ajaxreturn.itemid);if(c!==void 0){p.dispatch("sectionState",[a.ajaxreturn.itemid])}}});a("body").on("updated","".concat(s.ACTIVITYLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){p.dispatch("cmState",[a.ajaxreturn.itemid])}});if(p.supportComponents&&o.includes("addSection")){return}e.get_string("numberweeks").done(function(b){var c=a(s.ADDSECTIONS),d=c.attr("data-add-sections"),e=c.attr("data-new-sections"),f=a("
");f.find("label").html(b);h.create({title:d,type:h.types.SAVE_CANCEL,body:f.html()},c).done(function(b){var e=a(b.getBody()).find("#add_section_numsections"),f=function(){if(""+parseInt(e.val())===e.val()&&1<=parseInt(e.val())){document.location=c.attr("href")+"&numsections="+parseInt(e.val())}};b.setSaveButtonText(d);b.getRoot().on(i.shown,function(){e.focus().select().on("keydown",function(a){if(a.keyCode===j.enter){f()}})});b.getRoot().on(i.save,function(a){a.preventDefault();f()})})})},replaceSectionActionItem:function replaceSectionActionItem(a,b,c,d,e,f){k.debug("replaceSectionActionItem() is deprecated and will be removed.");var g=a.find(s.SECTIONACTIONMENU+" "+b);I(g,c,d,e,f)},refreshModule:F,refreshSection:function refreshSection(c,e,f){if(f===void 0){f=p.sectionReturn}var g=a(c),h="refresh",i=b.call([{methodname:"core_course_edit_section",args:{id:e,action:h,sectionreturn:f}}],!0),j=x(g);return new Promise(function(b,c){a.when.apply(a,i).done(function(c){z(g,j);var d=a.parseJSON(c),f=a(d.content);g.replaceWith(f);a("".concat(s.SECTIONLI,"#").concat(e," ").concat(s.ACTIVITYLI)).each(function(a,b){B(b.data("id"))});var i=t(n.sectionRefreshed,{ajaxreturn:d,action:h,newSectionElement:f.get(0)},f);if(!i.defaultPrevented){J(f,a(s.SECTIONLI+"#"+e),d,q,e)}b(d)}).fail(function(a){var b=t("coursesectionrefreshfailed",{exception:a,action:h},g);if(!b.defaultPrevented){d.exception(a)}c()})})}}}); //# sourceMappingURL=actions.min.js.map diff --git a/course/amd/build/actions.min.js.map b/course/amd/build/actions.min.js.map index 77f0685d3bb..3d4218153e8 100644 --- a/course/amd/build/actions.min.js.map +++ b/course/amd/build/actions.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/actions.js"],"names":["define","$","ajax","templates","notification","str","url","Y","ModalFactory","ModalEvents","KeyCodes","log","editor","componentActions","courseeditor","getCurrentCourseEditor","CSS","EDITINPROGRESS","SECTIONDRAGGABLE","EDITINGMOVE","SELECTOR","ACTIVITYLI","ACTIONAREA","ACTIVITYACTION","MENU","TOGGLE","SECTIONLI","SECTIONACTIONMENU","ADDSECTIONS","use","courseformatselector","M","course","format","get_section_selector","getModuleId","element","item","get","dataset","id","Moodle","core_course","util","cm","getId","Node","getModuleName","name","getName","state","cmid","addActivitySpinner","activity","addClass","actionarea","find","spinner","add_spinner","show","data","dispatch","addSectionSpinner","sectionelement","addSectionLightbox","lightbox","add_lightbox","removeSpinner","delay","window","setTimeout","removeClass","hide","mutation","removeLightbox","initActionMenu","elementid","coursebase","invoke_function","core","actionmenu","newDOMNode","one","focusActionItem","elementId","action","mainelement","selector","is","focus","findNextFocusable","mainElement","tabables","isInside","foundElement","each","contains","editModule","moduleElement","target","attr","promises","call","methodname","args","sectionreturn","closest","when","apply","done","elementToFocus","replaceWith","affectedids","index","push","trigger","Event","ajaxreturn","fail","ex","e","exception","isDefaultPrevented","refreshModule","activityElement","Promise","resolve","reject","replaceActivityHtmlWith","confirmDeleteModule","onconfirm","modtypename","match","modulename","get_string","pluginname","get_strings","key","param","type","s","confirm","confirmEditSection","message","replaceActionItem","actionitem","image","stringname","stringcomponent","newaction","component","then","strings","html","renderPix","pixhtml","catch","defaultEditSectionHandler","sectionElement","actionItem","courseformat","sectionid","modules","i","section_availability","first","section","oldmarker","oldActionItem","activityHTML","editSection","supportComponents","includes","dataencoded","parseJSON","register_module","set_visibility_resource_ui","getDOMNode","updateMovedCmState","params","updateMovedSectionState","addMutations","legacyActivityAction","statemanager","setReadOnly","locked","cmlist","reduce","current","delete","visible","legacySectionAction","forEach","initCoursePage","on","keyCode","moduleId","preventDefault","sectionId","isExecuted","itemid","strNumberSections","modalTitle","newSections","modalBody","create","title","types","SAVE_CANCEL","body","modal","numSections","getBody","addSections","parseInt","val","document","location","setSaveButtonText","getRoot","shown","select","enter","save","replaceSectionActionItem","debug"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Various actions on modules and sections in the editing mode - hiding, duplicating, deleting, etc.\n *\n * @module core_course/actions\n * @copyright 2016 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.3\n */\ndefine(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str', 'core/url', 'core/yui',\n 'core/modal_factory', 'core/modal_events', 'core/key_codes', 'core/log', 'core_courseformat/courseeditor'],\n function($, ajax, templates, notification, str, url, Y, ModalFactory, ModalEvents, KeyCodes, log, editor) {\n\n // Eventually, core_courseformat/local/content/actions will handle all actions for\n // component compatible formats and the default actions.js won't be necessary anymore.\n // Meanwhile, we filter the migrated actions.\n const componentActions = ['moveSection', 'moveCm'];\n\n // The course reactive instance.\n const courseeditor = editor.getCurrentCourseEditor();\n\n var CSS = {\n EDITINPROGRESS: 'editinprogress',\n SECTIONDRAGGABLE: 'sectiondraggable',\n EDITINGMOVE: 'editing_move'\n };\n var SELECTOR = {\n ACTIVITYLI: 'li.activity',\n ACTIONAREA: '.actions',\n ACTIVITYACTION: 'a.cm-edit-action',\n MENU: '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',\n TOGGLE: '.toggle-display,.dropdown-toggle',\n SECTIONLI: 'li.section',\n SECTIONACTIONMENU: '.section_action_menu',\n ADDSECTIONS: '#changenumsections [data-add-sections]'\n };\n\n Y.use('moodle-course-coursebase', function() {\n var courseformatselector = M.course.format.get_section_selector();\n if (courseformatselector) {\n SELECTOR.SECTIONLI = courseformatselector;\n }\n });\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getId\n *\n * @param {JQuery} element\n * @returns {Integer}\n */\n var getModuleId = function(element) {\n // Check if we have a data-id first.\n const item = element.get(0);\n if (item.dataset.id) {\n return item.dataset.id;\n }\n // Use YUI way if data-id is not present.\n let id;\n Y.use('moodle-course-util', function(Y) {\n id = Y.Moodle.core_course.util.cm.getId(Y.Node(item));\n });\n return id;\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getName\n *\n * @param {JQuery} element\n * @returns {String}\n */\n var getModuleName = function(element) {\n var name;\n Y.use('moodle-course-util', function(Y) {\n name = Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)));\n });\n // Check if we have the name in the course state.\n const state = courseeditor.state;\n const cmid = getModuleId(element);\n if (!name && state && cmid) {\n name = state.cm.get(cmid)?.name;\n }\n return name;\n };\n\n /**\n * Wrapper for M.util.add_spinner for an activity\n *\n * @param {JQuery} activity\n * @returns {Node}\n */\n var addActivitySpinner = function(activity) {\n activity.addClass(CSS.EDITINPROGRESS);\n var actionarea = activity.find(SELECTOR.ACTIONAREA).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the activity state element.\n if (activity.data('id') !== undefined) {\n courseeditor.dispatch('cmLock', [activity.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_spinner for a section\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionSpinner = function(sectionelement) {\n sectionelement.addClass(CSS.EDITINPROGRESS);\n var actionarea = sectionelement.find(SELECTOR.SECTIONACTIONMENU).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the section state element.\n if (sectionelement.data('id') !== undefined) {\n courseeditor.dispatch('sectionLock', [sectionelement.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_lightbox\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionLightbox = function(sectionelement) {\n var lightbox = M.util.add_lightbox(Y, Y.Node(sectionelement.get(0)));\n lightbox.show();\n return lightbox;\n };\n\n /**\n * Removes the spinner element\n *\n * @param {JQuery} element\n * @param {Node} spinner\n * @param {Number} delay\n */\n var removeSpinner = function(element, spinner, delay) {\n window.setTimeout(function() {\n element.removeClass(CSS.EDITINPROGRESS);\n if (spinner) {\n spinner.hide();\n }\n // Unlock the state element.\n if (element.data('id') !== undefined) {\n const mutation = (element.data('for') === 'section') ? 'sectionLock' : 'cmLock';\n courseeditor.dispatch(mutation, [element.data('id')], false);\n }\n }, delay);\n };\n\n /**\n * Removes the lightbox element\n *\n * @param {Node} lightbox lighbox YUI element returned by addSectionLightbox\n * @param {Number} delay\n */\n var removeLightbox = function(lightbox, delay) {\n if (lightbox) {\n window.setTimeout(function() {\n lightbox.hide();\n }, delay);\n }\n };\n\n /**\n * Initialise action menu for the element (section or module)\n *\n * @param {String} elementid CSS id attribute of the element\n */\n var initActionMenu = function(elementid) {\n // Initialise action menu in the new activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.invoke_function('setup_for_resource', '#' + elementid);\n });\n if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {\n M.core.actionmenu.newDOMNode(Y.one('#' + elementid));\n }\n };\n\n /**\n * Returns focus to the element that was clicked or \"Edit\" link if element is no longer visible.\n *\n * @param {String} elementId CSS id attribute of the element\n * @param {String} action data-action property of the element that was clicked\n */\n var focusActionItem = function(elementId, action) {\n var mainelement = $('#' + elementId);\n var selector = '[data-action=' + action + ']';\n if (action === 'groupsseparate' || action === 'groupsvisible' || action === 'groupsnone') {\n // New element will have different data-action.\n selector = '[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]';\n }\n if (mainelement.find(selector).is(':visible')) {\n mainelement.find(selector).focus();\n } else {\n // Element not visible, focus the \"Edit\" link.\n mainelement.find(SELECTOR.MENU).find(SELECTOR.TOGGLE).focus();\n }\n };\n\n /**\n * Find next after the element\n *\n * @param {JQuery} mainElement element that is about to be deleted\n * @returns {JQuery}\n */\n var findNextFocusable = function(mainElement) {\n var tabables = $(\"a:visible\");\n var isInside = false;\n var foundElement = null;\n tabables.each(function() {\n if ($.contains(mainElement[0], this)) {\n isInside = true;\n } else if (isInside) {\n foundElement = this;\n return false; // Returning false in .each() is equivalent to \"break;\" inside the loop in php.\n }\n });\n return foundElement;\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} moduleElement activity element we perform action on\n * @param {Number} cmid\n * @param {JQuery} target the element (menu item) that was clicked\n */\n var editModule = function(moduleElement, cmid, target) {\n var action = target.attr('data-action');\n var spinner = addActivitySpinner(moduleElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_module',\n args: {id: cmid,\n action: action,\n sectionreturn: target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0\n }\n }], true);\n\n var lightbox;\n if (action === 'duplicate') {\n lightbox = addSectionLightbox(target.closest(SELECTOR.SECTIONLI));\n }\n $.when.apply($, promises)\n .done(function(data) {\n var elementToFocus = findNextFocusable(moduleElement);\n moduleElement.replaceWith(data);\n let affectedids = [];\n // Initialise action menu for activity(ies) added as a result of this.\n $('
' + data + '
').find(SELECTOR.ACTIVITYLI).each(function(index) {\n initActionMenu($(this).attr('id'));\n if (index === 0) {\n focusActionItem($(this).attr('id'), action);\n elementToFocus = null;\n }\n // Save any activity id in cmids.\n affectedids.push(getModuleId($(this)));\n });\n // In case of activity deletion focus the next focusable element.\n if (elementToFocus) {\n elementToFocus.focus();\n }\n // Remove spinner and lightbox with a delay.\n removeSpinner(moduleElement, spinner, 400);\n removeLightbox(lightbox, 400);\n // Trigger event that can be observed by course formats.\n moduleElement.trigger($.Event('coursemoduleedited', {ajaxreturn: data, action: action}));\n\n // Modify cm state.\n courseeditor.dispatch('legacyActivityAction', action, cmid, affectedids);\n\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(moduleElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursemoduleeditfailed', {exception: ex, action: action});\n moduleElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n };\n\n /**\n * Requests html for the module via WS core_course_get_module and updates the module on the course page\n *\n * Used after d&d of the module to another section\n *\n * @param {JQuery|Element} element\n * @param {Number} cmid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshModule = function(element, cmid, sectionreturn) {\n const activityElement = $(element);\n var spinner = addActivitySpinner(activityElement);\n var promises = ajax.call([{\n methodname: 'core_course_get_module',\n args: {id: cmid, sectionreturn: sectionreturn}\n }], true);\n\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(function(data) {\n removeSpinner(activityElement, spinner, 400);\n replaceActivityHtmlWith(data);\n resolve(data);\n }).fail(function() {\n removeSpinner(activityElement, spinner);\n reject();\n });\n });\n };\n\n /**\n * Displays the delete confirmation to delete a module\n *\n * @param {JQuery} mainelement activity element we perform action on\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmDeleteModule = function(mainelement, onconfirm) {\n var modtypename = mainelement.attr('class').match(/modtype_([^\\s]*)/)[1];\n var modulename = getModuleName(mainelement);\n\n str.get_string('pluginname', modtypename).done(function(pluginname) {\n var plugindata = {\n type: pluginname,\n name: modulename\n };\n str.get_strings([\n {key: 'confirm'},\n {key: modulename === null ? 'deletechecktype' : 'deletechecktypename', param: plugindata},\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], s[1], s[2], s[3], onconfirm);\n }\n );\n });\n };\n\n /**\n * Displays the delete confirmation to delete a section\n *\n * @param {String} message confirmation message\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmEditSection = function(message, onconfirm) {\n str.get_strings([\n {key: 'confirm'}, // TODO link text\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], message, s[1], s[2], onconfirm);\n }\n );\n };\n\n /**\n * Replaces an action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * @param {JQuery} actionitem\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n * @return {Promise} promise which is resolved when the replacement has completed\n */\n var replaceActionItem = function(actionitem, image, stringname,\n stringcomponent, newaction) {\n\n var stringRequests = [{key: stringname, component: stringcomponent}];\n // Do not provide an icon with duplicate, different text to the menu item.\n\n return str.get_strings(stringRequests).then(function(strings) {\n actionitem.find('span.menu-action-text').html(strings[0]);\n\n return templates.renderPix(image, 'core');\n }).then(function(pixhtml) {\n actionitem.find('.icon').replaceWith(pixhtml);\n actionitem.attr('data-action', newaction);\n return;\n }).catch(notification.exception);\n };\n\n /**\n * Default post-processing for section AJAX edit actions.\n *\n * This can be overridden in course formats by listening to event coursesectionedited:\n *\n * $('body').on('coursesectionedited', 'li.section', function(e) {\n * var action = e.action,\n * sectionElement = $(e.target),\n * data = e.ajaxreturn;\n * // ... Do some processing here.\n * e.preventDefault(); // Prevent default handler.\n * });\n *\n * @param {JQuery} sectionElement\n * @param {JQuery} actionItem\n * @param {Object} data\n * @param {String} courseformat\n * @param {Number} sectionid\n */\n var defaultEditSectionHandler = function(sectionElement, actionItem, data, courseformat, sectionid) {\n var action = actionItem.attr('data-action');\n if (action === 'hide' || action === 'show') {\n if (action === 'hide') {\n sectionElement.addClass('hidden');\n replaceActionItem(actionItem, 'i/show',\n 'showfromothers', 'format_' + courseformat, 'show');\n } else {\n sectionElement.removeClass('hidden');\n replaceActionItem(actionItem, 'i/hide',\n 'hidefromothers', 'format_' + courseformat, 'hide');\n }\n // Replace the modules with new html (that indicates that they are now hidden or not hidden).\n if (data.modules !== undefined) {\n for (var i in data.modules) {\n replaceActivityHtmlWith(data.modules[i]);\n }\n }\n // Replace the section availability information.\n if (data.section_availability !== undefined) {\n sectionElement.find('.section_availability').first().replaceWith(data.section_availability);\n }\n // Modify course state.\n const section = courseeditor.state.section.get(sectionid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [sectionid]);\n }\n } else if (action === 'setmarker') {\n var oldmarker = $(SELECTOR.SECTIONLI + '.current'),\n oldActionItem = oldmarker.find(SELECTOR.SECTIONACTIONMENU + ' ' + 'a[data-action=removemarker]');\n oldmarker.removeClass('current');\n replaceActionItem(oldActionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n sectionElement.addClass('current');\n replaceActionItem(actionItem, 'i/marked',\n 'highlightoff', 'core', 'removemarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n } else if (action === 'removemarker') {\n sectionElement.removeClass('current');\n replaceActionItem(actionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n }\n };\n\n /**\n * Replaces the course module with the new html (used to update module after it was edited or its visibility was changed).\n *\n * @param {String} activityHTML\n */\n var replaceActivityHtmlWith = function(activityHTML) {\n $('
' + activityHTML + '
').find(SELECTOR.ACTIVITYLI).each(function() {\n // Extract id from the new activity html.\n var id = $(this).attr('id');\n // Find the existing element with the same id and replace its contents with new html.\n $(SELECTOR.ACTIVITYLI + '#' + id).replaceWith(activityHTML);\n // Initialise action menu.\n initActionMenu(id);\n });\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} sectionElement section element we perform action on\n * @param {Nunmber} sectionid\n * @param {JQuery} target the element (menu item) that was clicked\n * @param {String} courseformat\n * @return {boolean} true the action call is sent to the server or false if it is ignored.\n */\n var editSection = function(sectionElement, sectionid, target, courseformat) {\n var action = target.attr('data-action'),\n sectionreturn = target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0;\n\n // Filter direct component handled actions.\n if (courseeditor.supportComponents && componentActions.includes(action)) {\n return false;\n }\n\n var spinner = addSectionSpinner(sectionElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action: action, sectionreturn: sectionreturn}\n }], true);\n\n var lightbox = addSectionLightbox(sectionElement);\n $.when.apply($, promises)\n .done(function(dataencoded) {\n var data = $.parseJSON(dataencoded);\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectionedited', {ajaxreturn: data, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n defaultEditSectionHandler(sectionElement, target, data, courseformat, sectionid);\n }\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectioneditfailed', {exception: ex, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n return true;\n };\n\n // Register a function to be executed after D&D of an activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.register_module({\n // Ignore camelcase eslint rule for the next line because it is an expected name of the callback.\n // eslint-disable-next-line camelcase\n set_visibility_resource_ui: function(args) {\n var mainelement = $(args.element.getDOMNode());\n var cmid = getModuleId(mainelement);\n if (cmid) {\n var sectionreturn = mainelement.find('.' + CSS.EDITINGMOVE).attr('data-sectionreturn');\n refreshModule(mainelement, cmid, sectionreturn);\n }\n },\n /**\n * Update the course state when some cm is moved via YUI.\n * @param {*} params\n */\n updateMovedCmState: (params) => {\n const state = courseeditor.state;\n\n // Update old section.\n const cm = state.cm.get(params.cmid);\n if (cm !== undefined) {\n courseeditor.dispatch('sectionState', [cm.sectionid]);\n }\n // Update cm state.\n courseeditor.dispatch('cmState', [params.cmid]);\n },\n /**\n * Update the course state when some section is moved via YUI.\n */\n updateMovedSectionState: () => {\n courseeditor.dispatch('courseState');\n },\n });\n });\n\n // From Moodle 4.0 all edit actions are being re-implemented as state mutation.\n // This means all method from this \"actions\" module will be deprecated when all the course\n // interface is migrated to reactive components.\n // Most legacy actions did not provide enough information to regenarate the course so they\n // use the mutations courseState, sectionState and cmState to get the updated state from\n // the server. However, some activity actions where we can prevent an extra webservice\n // call by implementing an adhoc mutation.\n courseeditor.addMutations({\n /**\n * Compatibility function to update Moodle 4.0 course state using legacy actions.\n *\n * This method only updates some actions which does not require to use cmState mutation\n * to get updated data form the server.\n *\n * @param {Object} statemanager the current state in read write mode\n * @param {String} action the performed action\n * @param {Number} cmid the affected course module id\n * @param {Array} affectedids all affected cm ids (for duplicate action)\n */\n legacyActivityAction: function(statemanager, action, cmid, affectedids) {\n\n const state = statemanager.state;\n const cm = state.cm.get(cmid);\n if (cm === undefined) {\n return;\n }\n const section = state.section.get(cm.sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked. Reactive events are only triggered when the state\n // read only mode is restored. We want to notify the interface the element is\n // locked so we need to do a quick lock operation before performing the rest\n // of the mutation.\n statemanager.setReadOnly(false);\n cm.locked = true;\n statemanager.setReadOnly(true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This locked will take effect when the read only is restored.\n cm.locked = false;\n\n switch (action) {\n case 'delete':\n // Remove from section.\n section.cmlist = section.cmlist.reduce(\n (cmlist, current) => {\n if (current != cmid) {\n cmlist.push(current);\n }\n return cmlist;\n },\n []\n );\n // Delete form list.\n state.cm.delete(cmid);\n break;\n\n case 'hide':\n case 'show':\n cm.visible = (action === 'show') ? true : false;\n break;\n\n case 'duplicate':\n // Duplicate requires to get extra data from the server.\n courseeditor.dispatch('cmState', affectedids);\n break;\n }\n statemanager.setReadOnly(true);\n },\n legacySectionAction: function(statemanager, action, sectionid) {\n\n const state = statemanager.state;\n const section = state.section.get(sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked. Reactive events are only triggered when the state\n // read only mode is restored. We want to notify the interface the element is\n // locked so we need to do a quick lock operation before performing the rest\n // of the mutation.\n statemanager.setReadOnly(false);\n section.locked = true;\n statemanager.setReadOnly(true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This locked will take effect when the read only is restored.\n section.locked = false;\n\n switch (action) {\n case 'setmarker':\n // Remove previous marker.\n state.section.forEach((current) => {\n if (current.id != sectionid) {\n current.current = false;\n }\n });\n section.current = true;\n break;\n\n case 'removemarker':\n section.current = false;\n break;\n }\n statemanager.setReadOnly(true);\n },\n });\n\n return /** @alias module:core_course/actions */ {\n\n /**\n * Initialises course page\n *\n * @method init\n * @param {String} courseformat name of the current course format (for fetching strings)\n */\n initCoursePage: function(courseformat) {\n\n // Add a handler for course module actions.\n $('body').on('click keypress', SELECTOR.ACTIVITYLI + ' ' +\n SELECTOR.ACTIVITYACTION + '[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n moduleElement = actionItem.closest(SELECTOR.ACTIVITYLI),\n action = actionItem.attr('data-action'),\n moduleId = getModuleId(moduleElement);\n switch (action) {\n case 'moveleft':\n case 'moveright':\n case 'delete':\n case 'duplicate':\n case 'hide':\n case 'stealth':\n case 'show':\n case 'groupsseparate':\n case 'groupsvisible':\n case 'groupsnone':\n break;\n default:\n // Nothing to do here!\n return;\n }\n if (!moduleId) {\n return;\n }\n e.preventDefault();\n if (action === 'delete') {\n // Deleting requires confirmation.\n confirmDeleteModule(moduleElement, function() {\n editModule(moduleElement, moduleId, actionItem);\n });\n } else {\n editModule(moduleElement, moduleId, actionItem);\n }\n });\n\n // Add a handler for section show/hide actions.\n $('body').on('click keypress', SELECTOR.SECTIONLI + ' ' +\n SELECTOR.SECTIONACTIONMENU + '[data-sectionid] ' +\n 'a[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n sectionElement = actionItem.closest(SELECTOR.SECTIONLI),\n sectionId = actionItem.closest(SELECTOR.SECTIONACTIONMENU).attr('data-sectionid');\n\n let isExecuted = true;\n if (actionItem.attr('data-confirm')) {\n // Action requires confirmation.\n confirmEditSection(actionItem.attr('data-confirm'), function() {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n });\n } else {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n }\n // Prevent any other module from capturing the action if it is already in execution.\n if (isExecuted) {\n e.preventDefault();\n }\n });\n\n // The section and activity names are edited using inplace editable.\n // The \"update\" jQuery event must be captured in order to update the course state.\n $('body').on('updated', `${SELECTOR.SECTIONLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n const state = courseeditor.state;\n const section = state.section.get(e.ajaxreturn.itemid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [e.ajaxreturn.itemid]);\n }\n }\n });\n $('body').on('updated', `${SELECTOR.ACTIVITYLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n courseeditor.dispatch('cmState', [e.ajaxreturn.itemid]);\n }\n });\n\n // Add a handler for \"Add sections\" link to ask for a number of sections to add.\n str.get_string('numberweeks').done(function(strNumberSections) {\n var trigger = $(SELECTOR.ADDSECTIONS),\n modalTitle = trigger.attr('data-add-sections'),\n newSections = trigger.attr('data-new-sections');\n var modalBody = $('
' +\n '
');\n modalBody.find('label').html(strNumberSections);\n ModalFactory.create({\n title: modalTitle,\n type: ModalFactory.types.SAVE_CANCEL,\n body: modalBody.html()\n }, trigger)\n .done(function(modal) {\n var numSections = $(modal.getBody()).find('#add_section_numsections'),\n addSections = function() {\n // Check if value of the \"Number of sections\" is a valid positive integer and redirect\n // to adding a section script.\n if ('' + parseInt(numSections.val()) === numSections.val() && parseInt(numSections.val()) >= 1) {\n document.location = trigger.attr('href') + '&numsections=' + parseInt(numSections.val());\n }\n };\n modal.setSaveButtonText(modalTitle);\n modal.getRoot().on(ModalEvents.shown, function() {\n // When modal is shown focus and select the input and add a listener to keypress of \"Enter\".\n numSections.focus().select().on('keydown', function(e) {\n if (e.keyCode === KeyCodes.enter) {\n addSections();\n }\n });\n });\n modal.getRoot().on(ModalEvents.save, function(e) {\n // When modal \"Add\" button is pressed.\n e.preventDefault();\n addSections();\n });\n });\n });\n },\n\n /**\n * Replaces a section action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * This method can be used by course formats in their listener to the coursesectionedited event\n *\n * @deprecated since Moodle 3.9\n * @param {JQuery} sectionelement\n * @param {String} selector CSS selector inside the section element, for example \"a[data-action=show]\"\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n */\n replaceSectionActionItem: function(sectionelement, selector, image, stringname,\n stringcomponent, newaction) {\n log.debug('replaceSectionActionItem() is deprecated and will be removed.');\n var actionitem = sectionelement.find(SELECTOR.SECTIONACTIONMENU + ' ' + selector);\n replaceActionItem(actionitem, image, stringname, stringcomponent, newaction);\n },\n // Method to refresh a module.\n refreshModule,\n };\n });\n"],"file":"actions.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/actions.js"],"names":["define","$","ajax","templates","notification","str","url","Y","ModalFactory","ModalEvents","KeyCodes","log","editor","EventDispatcher","CourseEvents","componentActions","courseeditor","getCurrentCourseEditor","formatname","CSS","EDITINPROGRESS","SECTIONDRAGGABLE","EDITINGMOVE","SELECTOR","ACTIVITYLI","ACTIONAREA","ACTIVITYACTION","MENU","TOGGLE","SECTIONLI","SECTIONACTIONMENU","ADDSECTIONS","use","courseformatselector","M","course","format","get_section_selector","dispatchEvent","eventName","detail","container","options","Element","get","getModuleId","element","item","dataset","id","Moodle","core_course","util","cm","getId","Node","getModuleName","name","getName","state","cmid","addActivitySpinner","activity","addClass","actionarea","find","spinner","add_spinner","show","data","dispatch","addSectionSpinner","sectionelement","addSectionLightbox","lightbox","add_lightbox","removeSpinner","delay","window","setTimeout","removeClass","hide","mutation","removeLightbox","initActionMenu","elementid","coursebase","invoke_function","core","actionmenu","newDOMNode","one","focusActionItem","elementId","action","mainelement","selector","is","focus","findNextFocusable","mainElement","tabables","isInside","foundElement","each","contains","editModule","moduleElement","target","attr","promises","call","methodname","args","sectionreturn","closest","when","apply","done","elementToFocus","replaceWith","affectedids","index","push","trigger","Event","ajaxreturn","fail","ex","e","exception","isDefaultPrevented","refreshModule","sectionReturn","activityElement","Promise","resolve","reject","replaceActivityHtmlWith","confirmDeleteModule","onconfirm","modtypename","match","modulename","get_string","pluginname","get_strings","key","component","param","type","s","confirm","confirmEditSection","message","replaceActionItem","actionitem","image","stringname","stringcomponent","newaction","then","strings","html","renderPix","pixhtml","catch","defaultEditSectionHandler","sectionElement","actionItem","courseformat","sectionid","modules","i","section_availability","first","section","oldmarker","oldActionItem","activityHTML","editSection","supportComponents","includes","dataencoded","parseJSON","register_module","set_visibility_resource_ui","getDOMNode","updateMovedCmState","params","updateMovedSectionState","addMutations","legacyActivityAction","statemanager","setReadOnly","locked","cmlist","reduce","current","delete","visible","legacySectionAction","forEach","initCoursePage","on","keyCode","moduleId","preventDefault","sectionId","isExecuted","itemid","strNumberSections","modalTitle","newSections","modalBody","create","title","types","SAVE_CANCEL","body","modal","numSections","getBody","addSections","parseInt","val","document","location","setSaveButtonText","getRoot","shown","select","enter","save","replaceSectionActionItem","debug","refreshSection","newSectionElement","content","event","sectionRefreshed","defaultPrevented"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Various actions on modules and sections in the editing mode - hiding, duplicating, deleting, etc.\n *\n * @module core_course/actions\n * @copyright 2016 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.3\n */\ndefine(\n [\n 'jquery',\n 'core/ajax',\n 'core/templates',\n 'core/notification',\n 'core/str',\n 'core/url',\n 'core/yui',\n 'core/modal_factory',\n 'core/modal_events',\n 'core/key_codes',\n 'core/log',\n 'core_courseformat/courseeditor',\n 'core/event_dispatcher',\n 'core_course/events'\n ],\n function(\n $,\n ajax,\n templates,\n notification,\n str,\n url,\n Y,\n ModalFactory,\n ModalEvents,\n KeyCodes,\n log,\n editor,\n EventDispatcher,\n CourseEvents\n ) {\n\n // Eventually, core_courseformat/local/content/actions will handle all actions for\n // component compatible formats and the default actions.js won't be necessary anymore.\n // Meanwhile, we filter the migrated actions.\n const componentActions = ['moveSection', 'moveCm', 'addSection', 'deleteSection'];\n\n // The course reactive instance.\n const courseeditor = editor.getCurrentCourseEditor();\n\n // The current course format name (loaded on init).\n let formatname;\n\n var CSS = {\n EDITINPROGRESS: 'editinprogress',\n SECTIONDRAGGABLE: 'sectiondraggable',\n EDITINGMOVE: 'editing_move'\n };\n var SELECTOR = {\n ACTIVITYLI: 'li.activity',\n ACTIONAREA: '.actions',\n ACTIVITYACTION: 'a.cm-edit-action',\n MENU: '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',\n TOGGLE: '.toggle-display,.dropdown-toggle',\n SECTIONLI: 'li.section',\n SECTIONACTIONMENU: '.section_action_menu',\n ADDSECTIONS: '.changenumsections [data-add-sections]'\n };\n\n Y.use('moodle-course-coursebase', function() {\n var courseformatselector = M.course.format.get_section_selector();\n if (courseformatselector) {\n SELECTOR.SECTIONLI = courseformatselector;\n }\n });\n\n /**\n * Dispatch event wrapper.\n *\n * Old jQuery events will be replaced by native events gradually.\n *\n * @method dispatchEvent\n * @param {String} eventName The name of the event\n * @param {Object} detail Any additional details to pass into the eveent\n * @param {Node|HTMLElement} container The point at which to dispatch the event\n * @param {Object} options\n * @param {Boolean} options.bubbles Whether to bubble up the DOM\n * @param {Boolean} options.cancelable Whether preventDefault() can be called\n * @param {Boolean} options.composed Whether the event can bubble across the ShadowDOM boundary\n * @returns {CustomEvent}\n */\n const dispatchEvent = function(eventName, detail, container, options) {\n // Most actions still uses jQuery node instead of regular HTMLElement.\n if (!(container instanceof Element) && container.get !== undefined) {\n container = container.get(0);\n }\n return EventDispatcher.dispatchEvent(eventName, detail, container, options);\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getId\n *\n * @param {JQuery} element\n * @returns {Integer}\n */\n var getModuleId = function(element) {\n // Check if we have a data-id first.\n const item = element.get(0);\n if (item.dataset.id) {\n return item.dataset.id;\n }\n // Use YUI way if data-id is not present.\n let id;\n Y.use('moodle-course-util', function(Y) {\n id = Y.Moodle.core_course.util.cm.getId(Y.Node(item));\n });\n return id;\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getName\n *\n * @param {JQuery} element\n * @returns {String}\n */\n var getModuleName = function(element) {\n var name;\n Y.use('moodle-course-util', function(Y) {\n name = Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)));\n });\n // Check if we have the name in the course state.\n const state = courseeditor.state;\n const cmid = getModuleId(element);\n if (!name && state && cmid) {\n name = state.cm.get(cmid)?.name;\n }\n return name;\n };\n\n /**\n * Wrapper for M.util.add_spinner for an activity\n *\n * @param {JQuery} activity\n * @returns {Node}\n */\n var addActivitySpinner = function(activity) {\n activity.addClass(CSS.EDITINPROGRESS);\n var actionarea = activity.find(SELECTOR.ACTIONAREA).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the activity state element.\n if (activity.data('id') !== undefined) {\n courseeditor.dispatch('cmLock', [activity.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_spinner for a section\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionSpinner = function(sectionelement) {\n sectionelement.addClass(CSS.EDITINPROGRESS);\n var actionarea = sectionelement.find(SELECTOR.SECTIONACTIONMENU).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the section state element.\n if (sectionelement.data('id') !== undefined) {\n courseeditor.dispatch('sectionLock', [sectionelement.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_lightbox\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionLightbox = function(sectionelement) {\n var lightbox = M.util.add_lightbox(Y, Y.Node(sectionelement.get(0)));\n lightbox.show();\n return lightbox;\n };\n\n /**\n * Removes the spinner element\n *\n * @param {JQuery} element\n * @param {Node} spinner\n * @param {Number} delay\n */\n var removeSpinner = function(element, spinner, delay) {\n window.setTimeout(function() {\n element.removeClass(CSS.EDITINPROGRESS);\n if (spinner) {\n spinner.hide();\n }\n // Unlock the state element.\n if (element.data('id') !== undefined) {\n const mutation = (element.data('for') === 'section') ? 'sectionLock' : 'cmLock';\n courseeditor.dispatch(mutation, [element.data('id')], false);\n }\n }, delay);\n };\n\n /**\n * Removes the lightbox element\n *\n * @param {Node} lightbox lighbox YUI element returned by addSectionLightbox\n * @param {Number} delay\n */\n var removeLightbox = function(lightbox, delay) {\n if (lightbox) {\n window.setTimeout(function() {\n lightbox.hide();\n }, delay);\n }\n };\n\n /**\n * Initialise action menu for the element (section or module)\n *\n * @param {String} elementid CSS id attribute of the element\n */\n var initActionMenu = function(elementid) {\n // Initialise action menu in the new activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.invoke_function('setup_for_resource', '#' + elementid);\n });\n if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {\n M.core.actionmenu.newDOMNode(Y.one('#' + elementid));\n }\n };\n\n /**\n * Returns focus to the element that was clicked or \"Edit\" link if element is no longer visible.\n *\n * @param {String} elementId CSS id attribute of the element\n * @param {String} action data-action property of the element that was clicked\n */\n var focusActionItem = function(elementId, action) {\n var mainelement = $('#' + elementId);\n var selector = '[data-action=' + action + ']';\n if (action === 'groupsseparate' || action === 'groupsvisible' || action === 'groupsnone') {\n // New element will have different data-action.\n selector = '[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]';\n }\n if (mainelement.find(selector).is(':visible')) {\n mainelement.find(selector).focus();\n } else {\n // Element not visible, focus the \"Edit\" link.\n mainelement.find(SELECTOR.MENU).find(SELECTOR.TOGGLE).focus();\n }\n };\n\n /**\n * Find next
after the element\n *\n * @param {JQuery} mainElement element that is about to be deleted\n * @returns {JQuery}\n */\n var findNextFocusable = function(mainElement) {\n var tabables = $(\"a:visible\");\n var isInside = false;\n var foundElement = null;\n tabables.each(function() {\n if ($.contains(mainElement[0], this)) {\n isInside = true;\n } else if (isInside) {\n foundElement = this;\n return false; // Returning false in .each() is equivalent to \"break;\" inside the loop in php.\n }\n return true;\n });\n return foundElement;\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} moduleElement activity element we perform action on\n * @param {Number} cmid\n * @param {JQuery} target the element (menu item) that was clicked\n */\n var editModule = function(moduleElement, cmid, target) {\n var action = target.attr('data-action');\n var spinner = addActivitySpinner(moduleElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_module',\n args: {id: cmid,\n action: action,\n sectionreturn: target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0\n }\n }], true);\n\n var lightbox;\n if (action === 'duplicate') {\n lightbox = addSectionLightbox(target.closest(SELECTOR.SECTIONLI));\n }\n $.when.apply($, promises)\n .done(function(data) {\n var elementToFocus = findNextFocusable(moduleElement);\n moduleElement.replaceWith(data);\n let affectedids = [];\n // Initialise action menu for activity(ies) added as a result of this.\n $('
' + data + '
').find(SELECTOR.ACTIVITYLI).each(function(index) {\n initActionMenu($(this).attr('id'));\n if (index === 0) {\n focusActionItem($(this).attr('id'), action);\n elementToFocus = null;\n }\n // Save any activity id in cmids.\n affectedids.push(getModuleId($(this)));\n });\n // In case of activity deletion focus the next focusable element.\n if (elementToFocus) {\n elementToFocus.focus();\n }\n // Remove spinner and lightbox with a delay.\n removeSpinner(moduleElement, spinner, 400);\n removeLightbox(lightbox, 400);\n // Trigger event that can be observed by course formats.\n moduleElement.trigger($.Event('coursemoduleedited', {ajaxreturn: data, action: action}));\n\n // Modify cm state.\n courseeditor.dispatch('legacyActivityAction', action, cmid, affectedids);\n\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(moduleElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursemoduleeditfailed', {exception: ex, action: action});\n moduleElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n };\n\n /**\n * Requests html for the module via WS core_course_get_module and updates the module on the course page\n *\n * Used after d&d of the module to another section\n *\n * @param {JQuery|Element} element\n * @param {Number} cmid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshModule = function(element, cmid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const activityElement = $(element);\n var spinner = addActivitySpinner(activityElement);\n var promises = ajax.call([{\n methodname: 'core_course_get_module',\n args: {id: cmid, sectionreturn: sectionreturn}\n }], true);\n\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(function(data) {\n removeSpinner(activityElement, spinner, 400);\n replaceActivityHtmlWith(data);\n resolve(data);\n }).fail(function() {\n removeSpinner(activityElement, spinner);\n reject();\n });\n });\n };\n\n /**\n * Requests html for the section via WS core_course_edit_section and updates the section on the course page\n *\n * @param {JQuery|Element} element\n * @param {Number} sectionid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshSection = function(element, sectionid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const sectionElement = $(element);\n const action = 'refresh';\n const promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action, sectionreturn},\n }], true);\n\n var spinner = addSectionSpinner(sectionElement);\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(dataencoded => {\n\n removeSpinner(sectionElement, spinner);\n const data = $.parseJSON(dataencoded);\n\n const newSectionElement = $(data.content);\n sectionElement.replaceWith(newSectionElement);\n\n // Init modules menus.\n $(`${SELECTOR.SECTIONLI}#${sectionid} ${SELECTOR.ACTIVITYLI}`).each(\n (index, activity) => {\n initActionMenu(activity.data('id'));\n }\n );\n\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n CourseEvents.sectionRefreshed,\n {\n ajaxreturn: data,\n action: action,\n newSectionElement: newSectionElement.get(0),\n },\n newSectionElement\n );\n\n if (!event.defaultPrevented) {\n defaultEditSectionHandler(\n newSectionElement, $(SELECTOR.SECTIONLI + '#' + sectionid),\n data,\n formatname,\n sectionid\n );\n }\n resolve(data);\n }).fail(ex => {\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n 'coursesectionrefreshfailed',\n {exception: ex, action: action},\n sectionElement\n );\n if (!event.defaultPrevented) {\n notification.exception(ex);\n }\n reject();\n });\n });\n };\n\n /**\n * Displays the delete confirmation to delete a module\n *\n * @param {JQuery} mainelement activity element we perform action on\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmDeleteModule = function(mainelement, onconfirm) {\n var modtypename = mainelement.attr('class').match(/modtype_([^\\s]*)/)[1];\n var modulename = getModuleName(mainelement);\n\n str.get_string('pluginname', modtypename).done(function(pluginname) {\n var plugindata = {\n type: pluginname,\n name: modulename\n };\n str.get_strings([\n {key: 'confirm', component: 'core'},\n {key: modulename === null ? 'deletechecktype' : 'deletechecktypename', param: plugindata},\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], s[1], s[2], s[3], onconfirm);\n }\n );\n });\n };\n\n /**\n * Displays the delete confirmation to delete a section\n *\n * @param {String} message confirmation message\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmEditSection = function(message, onconfirm) {\n str.get_strings([\n {key: 'confirm'}, // TODO link text\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], message, s[1], s[2], onconfirm);\n }\n );\n };\n\n /**\n * Replaces an action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * @param {JQuery} actionitem\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n * @return {Promise} promise which is resolved when the replacement has completed\n */\n var replaceActionItem = function(actionitem, image, stringname,\n stringcomponent, newaction) {\n\n var stringRequests = [{key: stringname, component: stringcomponent}];\n // Do not provide an icon with duplicate, different text to the menu item.\n\n return str.get_strings(stringRequests).then(function(strings) {\n actionitem.find('span.menu-action-text').html(strings[0]);\n\n return templates.renderPix(image, 'core');\n }).then(function(pixhtml) {\n actionitem.find('.icon').replaceWith(pixhtml);\n actionitem.attr('data-action', newaction);\n return;\n }).catch(notification.exception);\n };\n\n /**\n * Default post-processing for section AJAX edit actions.\n *\n * This can be overridden in course formats by listening to event coursesectionedited:\n *\n * $('body').on('coursesectionedited', 'li.section', function(e) {\n * var action = e.action,\n * sectionElement = $(e.target),\n * data = e.ajaxreturn;\n * // ... Do some processing here.\n * e.preventDefault(); // Prevent default handler.\n * });\n *\n * @param {JQuery} sectionElement\n * @param {JQuery} actionItem\n * @param {Object} data\n * @param {String} courseformat\n * @param {Number} sectionid\n */\n var defaultEditSectionHandler = function(sectionElement, actionItem, data, courseformat, sectionid) {\n var action = actionItem.attr('data-action');\n if (action === 'hide' || action === 'show') {\n if (action === 'hide') {\n sectionElement.addClass('hidden');\n replaceActionItem(actionItem, 'i/show',\n 'showfromothers', 'format_' + courseformat, 'show');\n } else {\n sectionElement.removeClass('hidden');\n replaceActionItem(actionItem, 'i/hide',\n 'hidefromothers', 'format_' + courseformat, 'hide');\n }\n // Replace the modules with new html (that indicates that they are now hidden or not hidden).\n if (data.modules !== undefined) {\n for (var i in data.modules) {\n replaceActivityHtmlWith(data.modules[i]);\n }\n }\n // Replace the section availability information.\n if (data.section_availability !== undefined) {\n sectionElement.find('.section_availability').first().replaceWith(data.section_availability);\n }\n // Modify course state.\n const section = courseeditor.state.section.get(sectionid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [sectionid]);\n }\n } else if (action === 'setmarker') {\n var oldmarker = $(SELECTOR.SECTIONLI + '.current'),\n oldActionItem = oldmarker.find(SELECTOR.SECTIONACTIONMENU + ' ' + 'a[data-action=removemarker]');\n oldmarker.removeClass('current');\n replaceActionItem(oldActionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n sectionElement.addClass('current');\n replaceActionItem(actionItem, 'i/marked',\n 'highlightoff', 'core', 'removemarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n } else if (action === 'removemarker') {\n sectionElement.removeClass('current');\n replaceActionItem(actionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n }\n };\n\n /**\n * Replaces the course module with the new html (used to update module after it was edited or its visibility was changed).\n *\n * @param {String} activityHTML\n */\n var replaceActivityHtmlWith = function(activityHTML) {\n $('
' + activityHTML + '
').find(SELECTOR.ACTIVITYLI).each(function() {\n // Extract id from the new activity html.\n var id = $(this).attr('id');\n // Find the existing element with the same id and replace its contents with new html.\n $(SELECTOR.ACTIVITYLI + '#' + id).replaceWith(activityHTML);\n // Initialise action menu.\n initActionMenu(id);\n });\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} sectionElement section element we perform action on\n * @param {Nunmber} sectionid\n * @param {JQuery} target the element (menu item) that was clicked\n * @param {String} courseformat\n * @return {boolean} true the action call is sent to the server or false if it is ignored.\n */\n var editSection = function(sectionElement, sectionid, target, courseformat) {\n var action = target.attr('data-action'),\n sectionreturn = target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0;\n\n // Filter direct component handled actions.\n if (courseeditor.supportComponents && componentActions.includes(action)) {\n return false;\n }\n\n var spinner = addSectionSpinner(sectionElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action: action, sectionreturn: sectionreturn}\n }], true);\n\n var lightbox = addSectionLightbox(sectionElement);\n $.when.apply($, promises)\n .done(function(dataencoded) {\n var data = $.parseJSON(dataencoded);\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectionedited', {ajaxreturn: data, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n defaultEditSectionHandler(sectionElement, target, data, courseformat, sectionid);\n }\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectioneditfailed', {exception: ex, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n return true;\n };\n\n // Register a function to be executed after D&D of an activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.register_module({\n // Ignore camelcase eslint rule for the next line because it is an expected name of the callback.\n // eslint-disable-next-line camelcase\n set_visibility_resource_ui: function(args) {\n var mainelement = $(args.element.getDOMNode());\n var cmid = getModuleId(mainelement);\n if (cmid) {\n var sectionreturn = mainelement.find('.' + CSS.EDITINGMOVE).attr('data-sectionreturn');\n refreshModule(mainelement, cmid, sectionreturn);\n }\n },\n /**\n * Update the course state when some cm is moved via YUI.\n * @param {*} params\n */\n updateMovedCmState: (params) => {\n const state = courseeditor.state;\n\n // Update old section.\n const cm = state.cm.get(params.cmid);\n if (cm !== undefined) {\n courseeditor.dispatch('sectionState', [cm.sectionid]);\n }\n // Update cm state.\n courseeditor.dispatch('cmState', [params.cmid]);\n },\n /**\n * Update the course state when some section is moved via YUI.\n */\n updateMovedSectionState: () => {\n courseeditor.dispatch('courseState');\n },\n });\n });\n\n // From Moodle 4.0 all edit actions are being re-implemented as state mutation.\n // This means all method from this \"actions\" module will be deprecated when all the course\n // interface is migrated to reactive components.\n // Most legacy actions did not provide enough information to regenarate the course so they\n // use the mutations courseState, sectionState and cmState to get the updated state from\n // the server. However, some activity actions where we can prevent an extra webservice\n // call by implementing an adhoc mutation.\n courseeditor.addMutations({\n /**\n * Compatibility function to update Moodle 4.0 course state using legacy actions.\n *\n * This method only updates some actions which does not require to use cmState mutation\n * to get updated data form the server.\n *\n * @param {Object} statemanager the current state in read write mode\n * @param {String} action the performed action\n * @param {Number} cmid the affected course module id\n * @param {Array} affectedids all affected cm ids (for duplicate action)\n */\n legacyActivityAction: function(statemanager, action, cmid, affectedids) {\n\n const state = statemanager.state;\n const cm = state.cm.get(cmid);\n if (cm === undefined) {\n return;\n }\n const section = state.section.get(cm.sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked. Reactive events are only triggered when the state\n // read only mode is restored. We want to notify the interface the element is\n // locked so we need to do a quick lock operation before performing the rest\n // of the mutation.\n statemanager.setReadOnly(false);\n cm.locked = true;\n statemanager.setReadOnly(true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This locked will take effect when the read only is restored.\n cm.locked = false;\n\n switch (action) {\n case 'delete':\n // Remove from section.\n section.cmlist = section.cmlist.reduce(\n (cmlist, current) => {\n if (current != cmid) {\n cmlist.push(current);\n }\n return cmlist;\n },\n []\n );\n // Delete form list.\n state.cm.delete(cmid);\n break;\n\n case 'hide':\n case 'show':\n cm.visible = (action === 'show') ? true : false;\n break;\n\n case 'duplicate':\n // Duplicate requires to get extra data from the server.\n courseeditor.dispatch('cmState', affectedids);\n break;\n }\n statemanager.setReadOnly(true);\n },\n legacySectionAction: function(statemanager, action, sectionid) {\n\n const state = statemanager.state;\n const section = state.section.get(sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked. Reactive events are only triggered when the state\n // read only mode is restored. We want to notify the interface the element is\n // locked so we need to do a quick lock operation before performing the rest\n // of the mutation.\n statemanager.setReadOnly(false);\n section.locked = true;\n statemanager.setReadOnly(true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This locked will take effect when the read only is restored.\n section.locked = false;\n\n switch (action) {\n case 'setmarker':\n // Remove previous marker.\n state.section.forEach((current) => {\n if (current.id != sectionid) {\n current.current = false;\n }\n });\n section.current = true;\n break;\n\n case 'removemarker':\n section.current = false;\n break;\n }\n statemanager.setReadOnly(true);\n },\n });\n\n return /** @alias module:core_course/actions */ {\n\n /**\n * Initialises course page\n *\n * @method init\n * @param {String} courseformat name of the current course format (for fetching strings)\n */\n initCoursePage: function(courseformat) {\n\n formatname = courseformat;\n\n // Add a handler for course module actions.\n $('body').on('click keypress', SELECTOR.ACTIVITYLI + ' ' +\n SELECTOR.ACTIVITYACTION + '[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n moduleElement = actionItem.closest(SELECTOR.ACTIVITYLI),\n action = actionItem.attr('data-action'),\n moduleId = getModuleId(moduleElement);\n switch (action) {\n case 'moveleft':\n case 'moveright':\n case 'delete':\n case 'duplicate':\n case 'hide':\n case 'stealth':\n case 'show':\n case 'groupsseparate':\n case 'groupsvisible':\n case 'groupsnone':\n break;\n default:\n // Nothing to do here!\n return;\n }\n if (!moduleId) {\n return;\n }\n e.preventDefault();\n if (action === 'delete') {\n // Deleting requires confirmation.\n confirmDeleteModule(moduleElement, function() {\n editModule(moduleElement, moduleId, actionItem);\n });\n } else {\n editModule(moduleElement, moduleId, actionItem);\n }\n });\n\n // Add a handler for section show/hide actions.\n $('body').on('click keypress', SELECTOR.SECTIONLI + ' ' +\n SELECTOR.SECTIONACTIONMENU + '[data-sectionid] ' +\n 'a[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n sectionElement = actionItem.closest(SELECTOR.SECTIONLI),\n sectionId = actionItem.closest(SELECTOR.SECTIONACTIONMENU).attr('data-sectionid');\n\n let isExecuted = true;\n if (actionItem.attr('data-confirm')) {\n // Action requires confirmation.\n confirmEditSection(actionItem.attr('data-confirm'), function() {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n });\n } else {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n }\n // Prevent any other module from capturing the action if it is already in execution.\n if (isExecuted) {\n e.preventDefault();\n }\n });\n\n // The section and activity names are edited using inplace editable.\n // The \"update\" jQuery event must be captured in order to update the course state.\n $('body').on('updated', `${SELECTOR.SECTIONLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n const state = courseeditor.state;\n const section = state.section.get(e.ajaxreturn.itemid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [e.ajaxreturn.itemid]);\n }\n }\n });\n $('body').on('updated', `${SELECTOR.ACTIVITYLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n courseeditor.dispatch('cmState', [e.ajaxreturn.itemid]);\n }\n });\n\n // Component-based formats don't use modals to create sections.\n if (courseeditor.supportComponents && componentActions.includes('addSection')) {\n return;\n }\n\n // Add a handler for \"Add sections\" link to ask for a number of sections to add.\n str.get_string('numberweeks').done(function(strNumberSections) {\n var trigger = $(SELECTOR.ADDSECTIONS),\n modalTitle = trigger.attr('data-add-sections'),\n newSections = trigger.attr('data-new-sections');\n var modalBody = $('
' +\n '
');\n modalBody.find('label').html(strNumberSections);\n ModalFactory.create({\n title: modalTitle,\n type: ModalFactory.types.SAVE_CANCEL,\n body: modalBody.html()\n }, trigger)\n .done(function(modal) {\n var numSections = $(modal.getBody()).find('#add_section_numsections'),\n addSections = function() {\n // Check if value of the \"Number of sections\" is a valid positive integer and redirect\n // to adding a section script.\n if ('' + parseInt(numSections.val()) === numSections.val() && parseInt(numSections.val()) >= 1) {\n document.location = trigger.attr('href') + '&numsections=' + parseInt(numSections.val());\n }\n };\n modal.setSaveButtonText(modalTitle);\n modal.getRoot().on(ModalEvents.shown, function() {\n // When modal is shown focus and select the input and add a listener to keypress of \"Enter\".\n numSections.focus().select().on('keydown', function(e) {\n if (e.keyCode === KeyCodes.enter) {\n addSections();\n }\n });\n });\n modal.getRoot().on(ModalEvents.save, function(e) {\n // When modal \"Add\" button is pressed.\n e.preventDefault();\n addSections();\n });\n });\n });\n },\n\n /**\n * Replaces a section action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * This method can be used by course formats in their listener to the coursesectionedited event\n *\n * @deprecated since Moodle 3.9\n * @param {JQuery} sectionelement\n * @param {String} selector CSS selector inside the section element, for example \"a[data-action=show]\"\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n */\n replaceSectionActionItem: function(sectionelement, selector, image, stringname,\n stringcomponent, newaction) {\n log.debug('replaceSectionActionItem() is deprecated and will be removed.');\n var actionitem = sectionelement.find(SELECTOR.SECTIONACTIONMENU + ' ' + selector);\n replaceActionItem(actionitem, image, stringname, stringcomponent, newaction);\n },\n // Method to refresh a module.\n refreshModule,\n refreshSection,\n };\n });\n"],"file":"actions.min.js"} \ No newline at end of file diff --git a/course/amd/build/events.min.js b/course/amd/build/events.min.js index a8d0f3b2d93..551c510053a 100644 --- a/course/amd/build/events.min.js +++ b/course/amd/build/events.min.js @@ -1,2 +1,2 @@ -define ("core_course/events",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={favourited:"core_course:favourited",unfavorited:"core_course:unfavorited",manualCompletionToggled:"core_course:manualcompletiontoggled",stateChanged:"core_course:stateChanged"};return a.default}); +define ("core_course/events",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={favourited:"core_course:favourited",unfavorited:"core_course:unfavorited",manualCompletionToggled:"core_course:manualcompletiontoggled",stateChanged:"core_course:stateChanged",sectionRefreshed:"core_course:sectionRefreshed"};return a.default}); //# sourceMappingURL=events.min.js.map diff --git a/course/amd/build/events.min.js.map b/course/amd/build/events.min.js.map index e93588bbb0e..de5813bbf13 100644 --- a/course/amd/build/events.min.js.map +++ b/course/amd/build/events.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/events.js"],"names":["favourited","unfavorited","manualCompletionToggled","stateChanged"],"mappings":"8IAsBe,CACXA,UAAU,CAAE,wBADD,CAEXC,WAAW,CAAE,yBAFF,CAGXC,uBAAuB,CAAE,qCAHd,CAIXC,YAAY,CAAE,0BAJH,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Contain the events the course component can trigger.\n *\n * @module core_course/events\n * @copyright 2018 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n favourited: 'core_course:favourited',\n unfavorited: 'core_course:unfavorited',\n manualCompletionToggled: 'core_course:manualcompletiontoggled',\n stateChanged: 'core_course:stateChanged',\n};\n"],"file":"events.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/events.js"],"names":["favourited","unfavorited","manualCompletionToggled","stateChanged","sectionRefreshed"],"mappings":"8IAsBe,CACXA,UAAU,CAAE,wBADD,CAEXC,WAAW,CAAE,yBAFF,CAGXC,uBAAuB,CAAE,qCAHd,CAIXC,YAAY,CAAE,0BAJH,CAKXC,gBAAgB,CAAE,8BALP,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Contain the events the course component can trigger.\n *\n * @module core_course/events\n * @copyright 2018 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n favourited: 'core_course:favourited',\n unfavorited: 'core_course:unfavorited',\n manualCompletionToggled: 'core_course:manualcompletiontoggled',\n stateChanged: 'core_course:stateChanged',\n sectionRefreshed: 'core_course:sectionRefreshed',\n};\n"],"file":"events.min.js"} \ No newline at end of file diff --git a/course/amd/src/actions.js b/course/amd/src/actions.js index 41f1fed0f6f..1742aa0c85c 100644 --- a/course/amd/src/actions.js +++ b/course/amd/src/actions.js @@ -21,18 +21,51 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 3.3 */ -define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str', 'core/url', 'core/yui', - 'core/modal_factory', 'core/modal_events', 'core/key_codes', 'core/log', 'core_courseformat/courseeditor'], - function($, ajax, templates, notification, str, url, Y, ModalFactory, ModalEvents, KeyCodes, log, editor) { +define( + [ + 'jquery', + 'core/ajax', + 'core/templates', + 'core/notification', + 'core/str', + 'core/url', + 'core/yui', + 'core/modal_factory', + 'core/modal_events', + 'core/key_codes', + 'core/log', + 'core_courseformat/courseeditor', + 'core/event_dispatcher', + 'core_course/events' + ], + function( + $, + ajax, + templates, + notification, + str, + url, + Y, + ModalFactory, + ModalEvents, + KeyCodes, + log, + editor, + EventDispatcher, + CourseEvents + ) { // Eventually, core_courseformat/local/content/actions will handle all actions for // component compatible formats and the default actions.js won't be necessary anymore. // Meanwhile, we filter the migrated actions. - const componentActions = ['moveSection', 'moveCm']; + const componentActions = ['moveSection', 'moveCm', 'addSection', 'deleteSection']; // The course reactive instance. const courseeditor = editor.getCurrentCourseEditor(); + // The current course format name (loaded on init). + let formatname; + var CSS = { EDITINPROGRESS: 'editinprogress', SECTIONDRAGGABLE: 'sectiondraggable', @@ -46,7 +79,7 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' TOGGLE: '.toggle-display,.dropdown-toggle', SECTIONLI: 'li.section', SECTIONACTIONMENU: '.section_action_menu', - ADDSECTIONS: '#changenumsections [data-add-sections]' + ADDSECTIONS: '.changenumsections [data-add-sections]' }; Y.use('moodle-course-coursebase', function() { @@ -56,6 +89,29 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' } }); + /** + * Dispatch event wrapper. + * + * Old jQuery events will be replaced by native events gradually. + * + * @method dispatchEvent + * @param {String} eventName The name of the event + * @param {Object} detail Any additional details to pass into the eveent + * @param {Node|HTMLElement} container The point at which to dispatch the event + * @param {Object} options + * @param {Boolean} options.bubbles Whether to bubble up the DOM + * @param {Boolean} options.cancelable Whether preventDefault() can be called + * @param {Boolean} options.composed Whether the event can bubble across the ShadowDOM boundary + * @returns {CustomEvent} + */ + const dispatchEvent = function(eventName, detail, container, options) { + // Most actions still uses jQuery node instead of regular HTMLElement. + if (!(container instanceof Element) && container.get !== undefined) { + container = container.get(0); + } + return EventDispatcher.dispatchEvent(eventName, detail, container, options); + }; + /** * Wrapper for Y.Moodle.core_course.util.cm.getId * @@ -238,6 +294,7 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' foundElement = this; return false; // Returning false in .each() is equivalent to "break;" inside the loop in php. } + return true; }); return foundElement; }; @@ -316,6 +373,11 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' * @return {Promise} the refresh promise */ var refreshModule = function(element, cmid, sectionreturn) { + + if (sectionreturn === undefined) { + sectionreturn = courseeditor.sectionReturn; + } + const activityElement = $(element); var spinner = addActivitySpinner(activityElement); var promises = ajax.call([{ @@ -336,6 +398,80 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' }); }; + /** + * Requests html for the section via WS core_course_edit_section and updates the section on the course page + * + * @param {JQuery|Element} element + * @param {Number} sectionid + * @param {Number} sectionreturn + * @return {Promise} the refresh promise + */ + var refreshSection = function(element, sectionid, sectionreturn) { + + if (sectionreturn === undefined) { + sectionreturn = courseeditor.sectionReturn; + } + + const sectionElement = $(element); + const action = 'refresh'; + const promises = ajax.call([{ + methodname: 'core_course_edit_section', + args: {id: sectionid, action, sectionreturn}, + }], true); + + var spinner = addSectionSpinner(sectionElement); + return new Promise((resolve, reject) => { + $.when.apply($, promises) + .done(dataencoded => { + + removeSpinner(sectionElement, spinner); + const data = $.parseJSON(dataencoded); + + const newSectionElement = $(data.content); + sectionElement.replaceWith(newSectionElement); + + // Init modules menus. + $(`${SELECTOR.SECTIONLI}#${sectionid} ${SELECTOR.ACTIVITYLI}`).each( + (index, activity) => { + initActionMenu(activity.data('id')); + } + ); + + // Trigger event that can be observed by course formats. + const event = dispatchEvent( + CourseEvents.sectionRefreshed, + { + ajaxreturn: data, + action: action, + newSectionElement: newSectionElement.get(0), + }, + newSectionElement + ); + + if (!event.defaultPrevented) { + defaultEditSectionHandler( + newSectionElement, $(SELECTOR.SECTIONLI + '#' + sectionid), + data, + formatname, + sectionid + ); + } + resolve(data); + }).fail(ex => { + // Trigger event that can be observed by course formats. + const event = dispatchEvent( + 'coursesectionrefreshfailed', + {exception: ex, action: action}, + sectionElement + ); + if (!event.defaultPrevented) { + notification.exception(ex); + } + reject(); + }); + }); + }; + /** * Displays the delete confirmation to delete a module * @@ -352,7 +488,7 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' name: modulename }; str.get_strings([ - {key: 'confirm'}, + {key: 'confirm', component: 'core'}, {key: modulename === null ? 'deletechecktype' : 'deletechecktypename', param: plugindata}, {key: 'yes'}, {key: 'no'} @@ -699,6 +835,8 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' */ initCoursePage: function(courseformat) { + formatname = courseformat; + // Add a handler for course module actions. $('body').on('click keypress', SELECTOR.ACTIVITYLI + ' ' + SELECTOR.ACTIVITYACTION + '[data-action]', function(e) { @@ -782,6 +920,11 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' } }); + // Component-based formats don't use modals to create sections. + if (courseeditor.supportComponents && componentActions.includes('addSection')) { + return; + } + // Add a handler for "Add sections" link to ask for a number of sections to add. str.get_string('numberweeks').done(function(strNumberSections) { var trigger = $(SELECTOR.ADDSECTIONS), @@ -843,5 +986,6 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' }, // Method to refresh a module. refreshModule, + refreshSection, }; }); diff --git a/course/amd/src/events.js b/course/amd/src/events.js index 9ea6ffa115a..531e1aee995 100644 --- a/course/amd/src/events.js +++ b/course/amd/src/events.js @@ -25,4 +25,5 @@ export default { unfavorited: 'core_course:unfavorited', manualCompletionToggled: 'core_course:manualcompletiontoggled', stateChanged: 'core_course:stateChanged', + sectionRefreshed: 'core_course:sectionRefreshed', }; diff --git a/course/dndupload.js b/course/dndupload.js index b1bc3689ef9..0f8ff98323b 100644 --- a/course/dndupload.js +++ b/course/dndupload.js @@ -96,13 +96,38 @@ M.course_dndupload = { this.add_status_div(); } - // Any change to the course must be applied also to the course state via the courseeditor module. var self = this; - require(['core_courseformat/courseeditor'], function(editor) { - self.courseeditor = editor.getCurrentCourseEditor(); + require([ + 'core_courseformat/courseeditor', + 'core_course/events' + ], function( + Editor, + CourseEvents + ) { + // Any change to the course must be applied also to the course state via the courseeditor module. + self.courseeditor = Editor.getCurrentCourseEditor(); + + // Some formats can add sections without reloading the page. + document.querySelector('#' + self.pagecontentid).addEventListener( + CourseEvents.sectionRefreshed, + self.sectionRefreshed.bind(self) + ); }); }, + /** + * Setup Drag and Drop in a section. + * @param {CustomEvent} event The custom event + */ + sectionRefreshed: function(event) { + if (event.detail.newSectionElement === undefined) { + return; + } + var element = this.Y.one(event.detail.newSectionElement); + this.add_preview_element(element); + this.init_events(element); + }, + /** * Add a div element to tell the user that drag and drop upload * is available (or to explain why it is not available) diff --git a/course/format/amd/build/local/content.min.js b/course/format/amd/build/local/content.min.js index 489861bba8b..e454bf77831 100644 --- a/course/format/amd/build/local/content.min.js +++ b/course/format/amd/build/local/content.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/content",["exports","core/reactive","core_courseformat/courseeditor","core/inplace_editable","core_courseformat/local/content/section","core_courseformat/local/content/section/cmitem","core_course/actions","core_courseformat/local/content/actions"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=i(d);e=i(e);f=i(f);g=i(g);h=i(h);function i(a){return a&&a.__esModule?a:{default:a}}function j(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){j=function(a){return typeof a}}else{j=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return j(a)}function k(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function l(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){k(h,d,e,f,g,"next",a)}function g(a){k(h,d,e,f,g,"throw",a)}f(void 0)})}}function m(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function n(a){for(var b=1,c;bc.length){j=b.lastChild;if(null===j||void 0===j?void 0:null===(i=j.classList)||void 0===i?void 0:i.contains("dndupload-preview")){h=j}else{e[null!==(k=null===j||void 0===j?void 0:null===(l=j.dataset)||void 0===l?void 0:l.id)&&void 0!==k?k:0]=j}b.removeChild(j)}if(h){b.append(h)}case 10:case"end":return a.stop();}}},a)}));return function _fixOrder(){return a.apply(this,arguments)}}()}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=z;return a.default}); +define ("core_courseformat/local/content",["exports","core/reactive","core_courseformat/courseeditor","core/inplace_editable","core_courseformat/local/content/section","core_courseformat/local/content/section/cmitem","core_course/actions","core_courseformat/local/content/actions"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=i(d);e=i(e);f=i(f);g=i(g);h=i(h);function i(a){return a&&a.__esModule?a:{default:a}}function j(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){j=function(a){return typeof a}}else{j=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return j(a)}function k(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function l(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){k(h,d,e,f,g,"next",a)}function g(a){k(h,d,e,f,g,"throw",a)}f(void 0)})}}function m(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function n(a){for(var b=1,c;bc.length){j=b.lastChild;if(null===j||void 0===j?void 0:null===(i=j.classList)||void 0===i?void 0:i.contains("dndupload-preview")){h=j}else{e[null!==(k=null===j||void 0===j?void 0:null===(l=j.dataset)||void 0===l?void 0:l.id)&&void 0!==k?k:0]=j}b.removeChild(j)}if(h){b.append(h)}case 10:case"end":return a.stop();}}},a)}));return function _fixOrder(){return a.apply(this,arguments)}}()}],[{key:"init",value:function init(a,d,e){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d,sectionReturn:e})}}]);return b}(b.BaseComponent);a.default=z;return a.default}); //# sourceMappingURL=content.min.js.map diff --git a/course/format/amd/build/local/content.min.js.map b/course/format/amd/build/local/content.min.js.map index ddda39f8b0a..f123f4bf51d 100644 --- a/course/format/amd/build/local/content.min.js.map +++ b/course/format/amd/build/local/content.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/local/content.js"],"names":["Component","name","selectors","SECTION","SECTION_ITEM","SECTION_CMLIST","COURSE_SECTIONLIST","CM","TOGGLER","COLLAPSE","ACTIVITYTAG","classes","COLLAPSED","ACTIVITY","STATEDREADY","dettachedCms","dettachedSections","sections","cms","_indexContents","addEventListener","element","_sectionTogglers","reactive","supportComponents","isEditing","DispatchActions","classList","add","event","sectionlink","target","closest","isChevron","section","toggler","querySelector","isCollapsed","contains","sectionId","getAttribute","dispatch","contentexpanded","watch","handler","_reloadCm","_refreshSectionNumber","_refreshSectionCollapsed","_startProcessing","_refreshCourseSectionlist","_refreshSectionCmlist","cmitem","getElement","id","courseActions","refreshModule","Error","click","number","dataset","sectionid","inplace","inplaceeditable","getInplaceEditable","currentvalue","getValue","currentitemid","getItemId","rawtitle","setValue","cmlist","listparent","createCm","_createCmItem","bind","_fixOrder","sectionlist","createSection","_scanIndex","item","Section","CmItem","selector","index","creationhandler","items","getElements","forEach","unregister","indexed","promise","then","catch","container","cmid","newItem","document","createElement","for","append","get","neworder","dettachedelements","createMethod","length","innerHTML","remove","itemid","currentitem","children","insertBefore","lastchild","lastChild","dndFakeActivity","removeChild","getElementById","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/content\n * @class core_courseformat/local/content\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport inplaceeditable from 'core/inplace_editable';\nimport Section from 'core_courseformat/local/content/section';\nimport CmItem from 'core_courseformat/local/content/section/cmitem';\n// Course actions is needed for actions that are not migrated to components.\nimport courseActions from 'core_course/actions';\nimport DispatchActions from 'core_courseformat/local/content/actions';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'course_format';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_ITEM: `[data-for='section_title']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n COURSE_SECTIONLIST: `[data-for='course_sectionlist']`,\n CM: `[data-for='cmitem']`,\n TOGGLER: `[data-action=\"togglecoursecontentsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n // Formats can override the activity tag but a default one is needed to create new elements.\n ACTIVITYTAG: 'li',\n };\n // Default classes to toggle on refresh.\n this.classes = {\n COLLAPSED: `collapsed`,\n // Course content classes.\n ACTIVITY: `activity`,\n STATEDREADY: `stateready`,\n };\n // Array to save dettached elements during element resorting.\n this.dettachedCms = {};\n this.dettachedSections = {};\n // Index of sections and cms components.\n this.sections = {};\n this.cms = {};\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n this._indexContents();\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n if (this.reactive.supportComponents) {\n // Actions are only available in edit mode.\n if (this.reactive.isEditing) {\n new DispatchActions(this);\n }\n\n // Mark content as state ready.\n this.element.classList.add(this.classes.STATEDREADY);\n }\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course content element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionPreferences',\n [sectionId],\n {\n contentexpanded: isCollapsed,\n },\n );\n }\n }\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n // Check if the course format is compatible with reactive components.\n if (!this.reactive.supportComponents) {\n return [];\n }\n return [\n // State changes that require to reload some course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n // Update section number and title.\n {watch: `section.number:updated`, handler: this._refreshSectionNumber},\n // Collapse and expand sections.\n {watch: `section.contentexpanded:updated`, handler: this._refreshSectionCollapsed},\n // Sections and cm sorting.\n {watch: `transaction:start`, handler: this._startProcessing},\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n // Reindex sections and cms.\n {watch: `state:updated`, handler: this._indexContents},\n // State changes thaty require to reload course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n {watch: `cm.sectionid:updated`, handler: this._reloadCm},\n ];\n }\n\n /**\n * Reload a course module.\n *\n * Most course module HTML is still strongly backend dependant.\n * Some changes require to get a new version af the module.\n *\n * @param {Object} param\n * @param {Object} param.element update the state update data\n */\n _reloadCm({element}) {\n const cmitem = this.getElement(this.selectors.CM, element.id);\n if (cmitem) {\n courseActions.refreshModule(cmitem, element.id);\n }\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {Object} args.element The element to update\n */\n _refreshSectionCollapsed({element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unknown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.contentexpanded === isCollapsed) {\n toggler.click();\n }\n }\n\n /**\n * Setup the component to start a transaction.\n *\n * Some of the course actions replaces the current DOM element with a new one before updating the\n * course state. This means the component cannot preload any index properly until the transaction starts.\n *\n */\n _startProcessing() {\n // During a section or cm sorting, some elements could be dettached from the DOM and we\n // need to store somewhare in case they are needed later.\n this.dettachedCms = {};\n this.dettachedSections = {};\n }\n\n /**\n * Update a course section when the section number changes.\n *\n * The courseActions module used for most course section tools still depends on css classes and\n * section numbers (not id). To prevent inconsistencies when a section is moved, we need to refresh\n * the\n *\n * Course formats can override the section title rendering so the frontend depends heavily on backend\n * rendering. Luckily in edit mode we can trigger a title update using the inplace_editable module.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSectionNumber({element}) {\n // Find the element.\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n // Job done. Nothing to refresh.\n return;\n }\n // Update section numbers in all data, css and YUI attributes.\n target.id = `section-${element.number}`;\n // YUI uses section number as section id in data-sectionid, in principle if a format use components\n // don't need this sectionid attribute anymore, but we keep the compatibility in case some plugin\n // use it for legacy purposes.\n target.dataset.sectionid = element.number;\n // The data-number is the attribute used by components to store the section number.\n target.dataset.number = element.number;\n\n // Update title and title inplace editable, if any.\n const inplace = inplaceeditable.getInplaceEditable(target.querySelector(this.selectors.SECTION_ITEM));\n if (inplace) {\n // The course content HTML can be modified at any moment, so the function need to do some checkings\n // to make sure the inplace editable still represents the same itemid.\n const currentvalue = inplace.getValue();\n const currentitemid = inplace.getItemId();\n // Unnamed sections must be recalculated.\n if (inplace.getValue() === '') {\n // The value to send can be an empty value if it is a default name.\n if (currentitemid == element.id && (currentvalue != element.rawtitle || element.rawtitle == '')) {\n inplace.setValue(element.rawtitle);\n }\n }\n }\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const section = this.getElement(this.selectors.SECTION, element.id);\n const listparent = section?.querySelector(this.selectors.SECTION_CMLIST);\n // A method to create a fake element to be replaced when the item is ready.\n const createCm = this._createCmItem.bind(this);\n if (listparent) {\n this._fixOrder(listparent, cmlist, this.selectors.CM, this.dettachedCms, createCm);\n }\n }\n\n /**\n * Refresh the section list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n const listparent = this.getElement(this.selectors.COURSE_SECTIONLIST);\n // For now section cannot be created at a frontend level.\n const createSection = () => undefined;\n if (listparent) {\n this._fixOrder(listparent, sectionlist, this.selectors.SECTION, this.dettachedSections, createSection);\n }\n }\n\n /**\n * Regenerate content indexes.\n *\n * This method is used when a legacy action refresh some content element.\n */\n _indexContents() {\n // Find unindexed sections.\n this._scanIndex(\n this.selectors.SECTION,\n this.sections,\n (item) => {\n return new Section(item);\n }\n );\n\n // Find unindexed cms.\n this._scanIndex(\n this.selectors.CM,\n this.cms,\n (item) => {\n return new CmItem(item);\n }\n );\n }\n\n /**\n * Reindex a content (section or cm) of the course content.\n *\n * This method is used internally by _indexContents.\n *\n * @param {string} selector the DOM selector to scan\n * @param {*} index the index attribute to update\n * @param {*} creationhandler method to create a new indexed element\n */\n _scanIndex(selector, index, creationhandler) {\n const items = this.getElements(`${selector}:not([data-indexed])`);\n items.forEach((item) => {\n if (!item?.dataset?.id) {\n return;\n }\n // Delete previous item component.\n if (index[item.dataset.id] !== undefined) {\n index[item.dataset.id].unregister();\n }\n // Create the new component.\n index[item.dataset.id] = creationhandler({\n ...this,\n element: item,\n });\n // Mark as indexed.\n item.dataset.indexed = true;\n });\n }\n\n /**\n * Reload a course module contents.\n *\n * Most course module HTML is still strongly backend dependant.\n * Some changes require to get a new version of the module.\n *\n * @param {object} param0 the watcher details\n * @param {object} param0.element the state object\n */\n _reloadCm({element}) {\n const cmitem = this.getElement(this.selectors.CM, element.id);\n if (cmitem) {\n const promise = courseActions.refreshModule(cmitem, element.id);\n promise.then(() => {\n this._indexContents();\n return;\n }).catch();\n }\n }\n\n /**\n * Create a new course module item in a section.\n *\n * Thos method will append a fake item in the container and trigger an ajax request to\n * replace the fake element by the real content.\n *\n * @param {Element} container the container element (section)\n * @param {Number} cmid the course-module ID\n * @returns {Element} the created element\n */\n _createCmItem(container, cmid) {\n const newItem = document.createElement(this.selectors.ACTIVITYTAG);\n newItem.dataset.for = 'cmitem';\n newItem.dataset.id = cmid;\n // The legacy actions.js requires a specific ID and class to refresh the CM.\n newItem.id = `module-${cmid}`;\n newItem.classList.add(this.classes.ACTIVITY);\n container.append(newItem);\n this._reloadCm({\n element: this.reactive.get('cm', cmid),\n });\n return newItem;\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {string} selector the element selector\n * @param {Object} dettachedelements a list of dettached elements\n * @param {function} createMethod method to create missing elements\n */\n async _fixOrder(container, neworder, selector, dettachedelements, createMethod) {\n if (container === undefined) {\n return;\n }\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n let item = this.getElement(selector, itemid) ?? dettachedelements[itemid] ?? createMethod(container, itemid);\n if (item === undefined) {\n // Missing elements cannot be sorted.\n return;\n }\n // Get the current elemnt at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n\n // Dndupload add a fake element we need to keep.\n let dndFakeActivity;\n\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n const lastchild = container.lastChild;\n if (lastchild?.classList?.contains('dndupload-preview')) {\n dndFakeActivity = lastchild;\n } else {\n dettachedelements[lastchild?.dataset?.id ?? 0] = lastchild;\n }\n container.removeChild(lastchild);\n }\n // Restore dndupload fake element.\n if (dndFakeActivity) {\n container.append(dndFakeActivity);\n }\n }\n}\n"],"file":"content.min.js"} \ No newline at end of file +{"version":3,"sources":["../../src/local/content.js"],"names":["Component","descriptor","name","selectors","SECTION","SECTION_ITEM","SECTION_CMLIST","COURSE_SECTIONLIST","CM","TOGGLER","COLLAPSE","ACTIVITYTAG","SECTIONTAG","classes","COLLAPSED","ACTIVITY","STATEDREADY","dettachedCms","dettachedSections","sections","cms","sectionReturn","_indexContents","addEventListener","element","_sectionTogglers","reactive","supportComponents","isEditing","DispatchActions","classList","add","event","sectionlink","target","closest","isChevron","section","toggler","querySelector","isCollapsed","contains","sectionId","getAttribute","dispatch","contentexpanded","watch","handler","_reloadCm","_refreshSectionNumber","_refreshSectionCollapsed","_startProcessing","_refreshCourseSectionlist","_refreshSectionCmlist","getElement","id","Error","click","number","dataset","sectionid","inplace","inplaceeditable","getInplaceEditable","currentvalue","getValue","currentitemid","getItemId","rawtitle","setValue","cmlist","listparent","createCm","_createCmItem","bind","_fixOrder","sectionlist","createSection","_createSectionItem","_scanIndex","item","Section","CmItem","selector","index","creationhandler","items","getElements","forEach","unregister","indexed","cmitem","promise","courseActions","refreshModule","then","catch","sectionitem","refreshSection","container","cmid","newItem","document","createElement","for","append","get","_reloadSection","neworder","dettachedelements","createMethod","length","innerHTML","remove","itemid","currentitem","children","insertBefore","lastchild","lastChild","dndFakeActivity","removeChild","getElementById","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/content\n * @class core_courseformat/local/content\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport inplaceeditable from 'core/inplace_editable';\nimport Section from 'core_courseformat/local/content/section';\nimport CmItem from 'core_courseformat/local/content/section/cmitem';\n// Course actions is needed for actions that are not migrated to components.\nimport courseActions from 'core_course/actions';\nimport DispatchActions from 'core_courseformat/local/content/actions';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n *\n * @param {Object} descriptor the component descriptor\n */\n create(descriptor) {\n // Optional component name for debugging.\n this.name = 'course_format';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_ITEM: `[data-for='section_title']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n COURSE_SECTIONLIST: `[data-for='course_sectionlist']`,\n CM: `[data-for='cmitem']`,\n TOGGLER: `[data-action=\"togglecoursecontentsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n // Formats can override the activity tag but a default one is needed to create new elements.\n ACTIVITYTAG: 'li',\n SECTIONTAG: 'li',\n };\n // Default classes to toggle on refresh.\n this.classes = {\n COLLAPSED: `collapsed`,\n // Course content classes.\n ACTIVITY: `activity`,\n STATEDREADY: `stateready`,\n SECTION: `section`,\n };\n // Array to save dettached elements during element resorting.\n this.dettachedCms = {};\n this.dettachedSections = {};\n // Index of sections and cms components.\n this.sections = {};\n this.cms = {};\n // The page section return.\n this.sectionReturn = descriptor.sectionReturn ?? 0;\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @param {number} sectionReturn the content section return\n * @return {Component}\n */\n static init(target, selectors, sectionReturn) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n sectionReturn,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n this._indexContents();\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n if (this.reactive.supportComponents) {\n // Actions are only available in edit mode.\n if (this.reactive.isEditing) {\n new DispatchActions(this);\n }\n\n // Mark content as state ready.\n this.element.classList.add(this.classes.STATEDREADY);\n }\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course content element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionPreferences',\n [sectionId],\n {\n contentexpanded: isCollapsed,\n },\n );\n }\n }\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n // Section return is a global page variable but most formats define it just before start printing\n // the course content. This is the reason why we define this page setting here.\n this.reactive.sectionReturn = this.sectionReturn;\n\n // Check if the course format is compatible with reactive components.\n if (!this.reactive.supportComponents) {\n return [];\n }\n return [\n // State changes that require to reload some course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n // Update section number and title.\n {watch: `section.number:updated`, handler: this._refreshSectionNumber},\n // Collapse and expand sections.\n {watch: `section.contentexpanded:updated`, handler: this._refreshSectionCollapsed},\n // Sections and cm sorting.\n {watch: `transaction:start`, handler: this._startProcessing},\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n // Reindex sections and cms.\n {watch: `state:updated`, handler: this._indexContents},\n // State changes thaty require to reload course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n {watch: `cm.sectionid:updated`, handler: this._reloadCm},\n ];\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {Object} args.element The element to update\n */\n _refreshSectionCollapsed({element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unknown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.contentexpanded === isCollapsed) {\n toggler.click();\n }\n }\n\n /**\n * Setup the component to start a transaction.\n *\n * Some of the course actions replaces the current DOM element with a new one before updating the\n * course state. This means the component cannot preload any index properly until the transaction starts.\n *\n */\n _startProcessing() {\n // During a section or cm sorting, some elements could be dettached from the DOM and we\n // need to store somewhare in case they are needed later.\n this.dettachedCms = {};\n this.dettachedSections = {};\n }\n\n /**\n * Update a course section when the section number changes.\n *\n * The courseActions module used for most course section tools still depends on css classes and\n * section numbers (not id). To prevent inconsistencies when a section is moved, we need to refresh\n * the\n *\n * Course formats can override the section title rendering so the frontend depends heavily on backend\n * rendering. Luckily in edit mode we can trigger a title update using the inplace_editable module.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSectionNumber({element}) {\n // Find the element.\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n // Job done. Nothing to refresh.\n return;\n }\n // Update section numbers in all data, css and YUI attributes.\n target.id = `section-${element.number}`;\n // YUI uses section number as section id in data-sectionid, in principle if a format use components\n // don't need this sectionid attribute anymore, but we keep the compatibility in case some plugin\n // use it for legacy purposes.\n target.dataset.sectionid = element.number;\n // The data-number is the attribute used by components to store the section number.\n target.dataset.number = element.number;\n\n // Update title and title inplace editable, if any.\n const inplace = inplaceeditable.getInplaceEditable(target.querySelector(this.selectors.SECTION_ITEM));\n if (inplace) {\n // The course content HTML can be modified at any moment, so the function need to do some checkings\n // to make sure the inplace editable still represents the same itemid.\n const currentvalue = inplace.getValue();\n const currentitemid = inplace.getItemId();\n // Unnamed sections must be recalculated.\n if (inplace.getValue() === '') {\n // The value to send can be an empty value if it is a default name.\n if (currentitemid == element.id && (currentvalue != element.rawtitle || element.rawtitle == '')) {\n inplace.setValue(element.rawtitle);\n }\n }\n }\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const section = this.getElement(this.selectors.SECTION, element.id);\n const listparent = section?.querySelector(this.selectors.SECTION_CMLIST);\n // A method to create a fake element to be replaced when the item is ready.\n const createCm = this._createCmItem.bind(this);\n if (listparent) {\n this._fixOrder(listparent, cmlist, this.selectors.CM, this.dettachedCms, createCm);\n }\n }\n\n /**\n * Refresh the section list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCourseSectionlist({element}) {\n // If we have a section return means we only show a single section so no need to fix order.\n if (this.reactive.sectionReturn != 0) {\n return;\n }\n const sectionlist = element.sectionlist ?? [];\n const listparent = this.getElement(this.selectors.COURSE_SECTIONLIST);\n // For now section cannot be created at a frontend level.\n const createSection = this._createSectionItem.bind(this);\n if (listparent) {\n this._fixOrder(listparent, sectionlist, this.selectors.SECTION, this.dettachedSections, createSection);\n }\n }\n\n /**\n * Regenerate content indexes.\n *\n * This method is used when a legacy action refresh some content element.\n */\n _indexContents() {\n // Find unindexed sections.\n this._scanIndex(\n this.selectors.SECTION,\n this.sections,\n (item) => {\n return new Section(item);\n }\n );\n\n // Find unindexed cms.\n this._scanIndex(\n this.selectors.CM,\n this.cms,\n (item) => {\n return new CmItem(item);\n }\n );\n }\n\n /**\n * Reindex a content (section or cm) of the course content.\n *\n * This method is used internally by _indexContents.\n *\n * @param {string} selector the DOM selector to scan\n * @param {*} index the index attribute to update\n * @param {*} creationhandler method to create a new indexed element\n */\n _scanIndex(selector, index, creationhandler) {\n const items = this.getElements(`${selector}:not([data-indexed])`);\n items.forEach((item) => {\n if (!item?.dataset?.id) {\n return;\n }\n // Delete previous item component.\n if (index[item.dataset.id] !== undefined) {\n index[item.dataset.id].unregister();\n }\n // Create the new component.\n index[item.dataset.id] = creationhandler({\n ...this,\n element: item,\n });\n // Mark as indexed.\n item.dataset.indexed = true;\n });\n }\n\n /**\n * Reload a course module contents.\n *\n * Most course module HTML is still strongly backend dependant.\n * Some changes require to get a new version of the module.\n *\n * @param {object} param0 the watcher details\n * @param {object} param0.element the state object\n */\n _reloadCm({element}) {\n const cmitem = this.getElement(this.selectors.CM, element.id);\n if (cmitem) {\n const promise = courseActions.refreshModule(cmitem, element.id);\n promise.then(() => {\n this._indexContents();\n return;\n }).catch();\n }\n }\n\n /**\n * Reload a course section contents.\n *\n * Section HTML is still strongly backend dependant.\n * Some changes require to get a new version of the section.\n *\n * @param {details} param0 the watcher details\n * @param {object} param0.element the state object\n */\n _reloadSection({element}) {\n const sectionitem = this.getElement(this.selectors.SECTION, element.id);\n if (sectionitem) {\n const promise = courseActions.refreshSection(sectionitem, element.id);\n promise.then(() => {\n this._indexContents();\n return;\n }).catch();\n }\n }\n\n /**\n * Create a new course module item in a section.\n *\n * Thos method will append a fake item in the container and trigger an ajax request to\n * replace the fake element by the real content.\n *\n * @param {Element} container the container element (section)\n * @param {Number} cmid the course-module ID\n * @returns {Element} the created element\n */\n _createCmItem(container, cmid) {\n const newItem = document.createElement(this.selectors.ACTIVITYTAG);\n newItem.dataset.for = 'cmitem';\n newItem.dataset.id = cmid;\n // The legacy actions.js requires a specific ID and class to refresh the CM.\n newItem.id = `module-${cmid}`;\n newItem.classList.add(this.classes.ACTIVITY);\n container.append(newItem);\n this._reloadCm({\n element: this.reactive.get('cm', cmid),\n });\n return newItem;\n }\n\n /**\n * Create a new section item.\n *\n * This method will append a fake item in the container and trigger an ajax request to\n * replace the fake element by the real content.\n *\n * @param {Element} container the container element (section)\n * @param {Number} sectionid the course-module ID\n * @returns {Element} the created element\n */\n _createSectionItem(container, sectionid) {\n const section = this.reactive.get('section', sectionid);\n const newItem = document.createElement(this.selectors.SECTIONTAG);\n newItem.dataset.for = 'section';\n newItem.dataset.id = sectionid;\n newItem.dataset.number = section.number;\n // The legacy actions.js requires a specific ID and class to refresh the section.\n newItem.id = `section-${sectionid}`;\n newItem.classList.add(this.classes.SECTION);\n container.append(newItem);\n this._reloadSection({\n element: section,\n });\n return newItem;\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {string} selector the element selector\n * @param {Object} dettachedelements a list of dettached elements\n * @param {function} createMethod method to create missing elements\n */\n async _fixOrder(container, neworder, selector, dettachedelements, createMethod) {\n if (container === undefined) {\n return;\n }\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n let item = this.getElement(selector, itemid) ?? dettachedelements[itemid] ?? createMethod(container, itemid);\n if (item === undefined) {\n // Missing elements cannot be sorted.\n return;\n }\n // Get the current elemnt at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n\n // Dndupload add a fake element we need to keep.\n let dndFakeActivity;\n\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n const lastchild = container.lastChild;\n if (lastchild?.classList?.contains('dndupload-preview')) {\n dndFakeActivity = lastchild;\n } else {\n dettachedelements[lastchild?.dataset?.id ?? 0] = lastchild;\n }\n container.removeChild(lastchild);\n }\n // Restore dndupload fake element.\n if (dndFakeActivity) {\n container.append(dndFakeActivity);\n }\n }\n}\n"],"file":"content.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/content/actions.min.js b/course/format/amd/build/local/content/actions.min.js index 6269fe683d4..cca5dcbafd8 100644 --- a/course/format/amd/build/local/content/actions.min.js +++ b/course/format/amd/build/local/content/actions.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/content/actions",["exports","core/reactive","core/modal_factory","core/modal_events","core/templates","core/prefetch","core/str","core/normalise"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;c=i(c);d=i(d);e=i(e);function i(a){return a&&a.__esModule?a:{default:a}}function j(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){j=function(a){return typeof a}}else{j=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return j(a)}function k(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function l(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){k(h,d,e,f,g,"next",a)}function g(a){k(h,d,e,f,g,"throw",a)}f(void 0)})}}function m(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function n(a,b){for(var c=0,d;cb.course.maxsections)}},{key:"_requestMoveSection",value:function(){var a=o(regeneratorRuntime.mark(function a(b,c){var d=this,f,i,j,k,l,m,n,o,p;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:f=b.dataset.id;if(f){a.next=3;break}return a.abrupt("return");case 3:i=this.reactive.get("section",f);c.preventDefault();j=this.reactive.getExporter();k=j.course(this.reactive.state);k.sectionid=i.id;k.sectiontitle=i.title;l={title:(0,g.get_string)("movecoursesection","core"),body:e.default.render("core_courseformat/local/content/movesection",k)};a.next=12;return this._modalBodyRenderedPromise(l);case 12:m=a.sent;n=(0,h.getList)(m.getBody())[0];o=n.querySelector("".concat(this.selectors.SECTIONLINK,"[data-id='").concat(f,"']"));this._disableLink(o);p=n.querySelector("".concat(this.selectors.SECTIONLINK,"[data-number='0']"));this._disableLink(p);n.addEventListener("click",function(a){var b=a.target;if(!b.matches("a")||"section"!=b.dataset.for||b.dataset.id===void 0){return}if(b.getAttribute("aria-disabled")){return}a.preventDefault();d.reactive.dispatch("sectionMove",[f],b.dataset.id);m.destroy()});case 19:case"end":return a.stop();}}},a,this)}));return function _requestMoveSection(){return a.apply(this,arguments)}}()},{key:"_requestMoveCm",value:function(){var a=o(regeneratorRuntime.mark(function a(b,c){var d,f,i=this,j,k,l,m,n,o,p,q;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:j=b.dataset.id;if(j){a.next=3;break}return a.abrupt("return");case 3:k=this.reactive.get("cm",j);c.preventDefault();l=this.reactive.getExporter();m=l.course(this.reactive.state);m.cmid=k.id;m.cmname=k.name;n={title:(0,g.get_string)("movecoursemodule","core"),body:e.default.render("core_courseformat/local/content/movecm",m)};a.next=12;return this._modalBodyRenderedPromise(n);case 12:o=a.sent;p=(0,h.getList)(o.getBody())[0];q=p.querySelector("".concat(this.selectors.CMLINK,"[data-id='").concat(j,"']"));this._disableLink(q);null===(d=q.closest(this.selectors.SECTIONNODE))||void 0===d?void 0:null===(f=d.querySelector(this.selectors.TOGGLER))||void 0===f?void 0:f.click();p.addEventListener("click",function(a){var b=a.target;if(!b.matches("a")||b.dataset.for===void 0||b.dataset.id===void 0){return}if(b.getAttribute("aria-disabled")){return}a.preventDefault();var c,d;if("cm"==b.dataset.for){var e=l.cmDraggableData(i.reactive.state,b.dataset.id);c=e.sectionid;d=e.nextcmid}else{var f=i.reactive.get("section",b.dataset.id);c=b.dataset.id;d=null===f||void 0===f?void 0:f.cmlist[0]}i.reactive.dispatch("cmMove",[j],c,d);o.destroy()});case 18:case"end":return a.stop();}}},a,this)}));return function _requestMoveCm(){return a.apply(this,arguments)}}()},{key:"_requestAddSection",value:function(){var a=o(regeneratorRuntime.mark(function a(b,c){var d;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:c.preventDefault();this.reactive.dispatch("addSection",null!==(d=b.dataset.id)&&void 0!==d?d:0);case 2:case"end":return a.stop();}}},a,this)}));return function _requestAddSection(){return a.apply(this,arguments)}}()},{key:"_requestDeleteSection",value:function(){var a=o(regeneratorRuntime.mark(function a(b,e){var f,h=this,i,j,k,l,m;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:i=b.dataset.id;if(i){a.next=3;break}return a.abrupt("return");case 3:j=this.reactive.get("section",i);e.preventDefault();k=null!==(f=j.cmlist)&&void 0!==f?f:[];if(!(k.length||j.hassummary||j.rawtitle)){a.next=15;break}l={title:(0,g.get_string)("confirm","core"),body:(0,g.get_string)("confirmdeletesection","moodle",j.title),saveButtonText:(0,g.get_string)("delete","core"),type:c.default.types.SAVE_CANCEL};a.next=10;return this._modalBodyRenderedPromise(l);case 10:m=a.sent;m.getRoot().on(d.default.save,function(a){a.preventDefault();m.destroy();h.reactive.dispatch("sectionDelete",[i])});return a.abrupt("return");case 15:this.reactive.dispatch("sectionDelete",[i]);case 16:case"end":return a.stop();}}},a,this)}));return function _requestDeleteSection(){return a.apply(this,arguments)}}()},{key:"_setAddSectionLocked",value:function _setAddSectionLocked(a){var b=this,c=this.getElements(this.selectors.ADDSECTION);c.forEach(function(c){c.classList.toggle(b.classes.DISABLED,a);b.setElementLocked(c,a)})}},{key:"_disableLink",value:function _disableLink(a){if(a){a.style.pointerEvents="none";a.style.userSelect="none";a.classList.add(this.classes.DISABLED);a.setAttribute("aria-disabled",!0);a.addEventListener("click",function(a){return a.preventDefault()})}}},{key:"_modalBodyRenderedPromise",value:function _modalBodyRenderedPromise(a){return new Promise(function(b,e){c.default.create(a).then(function(c){c.getRoot().on(d.default.bodyRendered,function(){b(c)});if(a.saveButtonText!==void 0){c.setSaveButtonText(a.saveButtonText)}c.show()}).catch(function(){e("Cannot load modal content")})})}}]);return b}(b.BaseComponent);a.default=z;return a.default}); //# sourceMappingURL=actions.min.js.map diff --git a/course/format/amd/build/local/content/actions.min.js.map b/course/format/amd/build/local/content/actions.min.js.map index 98da9bb0073..e69cb48bcee 100644 --- a/course/format/amd/build/local/content/actions.min.js.map +++ b/course/format/amd/build/local/content/actions.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/content/actions.js"],"names":["name","selectors","ACTIONLINK","SECTIONLINK","CMLINK","SECTIONNODE","TOGGLER","classes","DISABLED","addEventListener","element","_dispatchClick","event","target","closest","methodName","_actionMethodName","dataset","action","requestName","charAt","toUpperCase","slice","sectionId","id","sectionInfo","reactive","get","preventDefault","exporter","getExporter","data","course","state","sectionid","sectiontitle","title","modalParams","body","Templates","render","_modalBodyRenderedPromise","modal","modalBody","getBody","currentElement","querySelector","_disableLink","generalSection","matches","for","getAttribute","dispatch","destroy","cmId","cmInfo","cmid","cmname","click","targetSectionId","targetCmId","dropData","cmDraggableData","nextcmid","section","cmlist","style","pointerEvents","userSelect","classList","add","setAttribute","Promise","resolve","reject","ModalFactory","create","then","getRoot","on","ModalEvents","bodyRendered","show","catch","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course state actions dispatcher.\n *\n * This module captures all data-dispatch links in the course content and dispatch the proper\n * state mutation, including any confirmation and modal required.\n *\n * @module core_courseformat/local/content/actions\n * @class core_courseformat/local/content/actions\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport Templates from 'core/templates';\nimport {prefetchStrings} from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\nimport {getList} from 'core/normalise';\n\n// Load global strings.\nprefetchStrings('core', ['movecoursesection', 'movecoursemodule']);\n\nexport default class extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_actions';\n // Default query selectors.\n this.selectors = {\n ACTIONLINK: `[data-action]`,\n SECTIONLINK: `[data-for='section']`,\n CMLINK: `[data-for='cm']`,\n SECTIONNODE: `[data-for='sectionnode']`,\n TOGGLER: `[data-toggle='collapse']`,\n };\n // Component css classes.\n this.classes = {\n DISABLED: `disabled`,\n };\n }\n\n /**\n * Initial state ready method.\n *\n */\n stateReady() {\n // Delegate dispatch clicks.\n this.addEventListener(\n this.element,\n 'click',\n this._dispatchClick\n );\n }\n\n _dispatchClick(event) {\n const target = event.target.closest(this.selectors.ACTIONLINK);\n if (!target) {\n return;\n }\n\n // Invoke proper method.\n const methodName = this._actionMethodName(target.dataset.action);\n\n if (this[methodName] !== undefined) {\n this[methodName](target, event);\n }\n }\n\n _actionMethodName(name) {\n const requestName = name.charAt(0).toUpperCase() + name.slice(1);\n return `_request${requestName}`;\n }\n\n /**\n * Handle a move section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveSection(target, event) {\n // Check we have an id.\n const sectionId = target.dataset.id;\n if (!sectionId) {\n return;\n }\n const sectionInfo = this.reactive.get('section', sectionId);\n\n event.preventDefault();\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target section id and title.\n data.sectionid = sectionInfo.id;\n data.sectiontitle = sectionInfo.title;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursesection', 'core'),\n body: Templates.render('core_courseformat/local/content/movesection', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element and section zero.\n const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`);\n this._disableLink(currentElement);\n const generalSection = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-number='0']`);\n this._disableLink(generalSection);\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for != 'section' || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch('sectionMove', [sectionId], target.dataset.id);\n modal.destroy();\n });\n }\n\n /**\n * Handle a move cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveCm(target, event) {\n // Check we have an id.\n const cmId = target.dataset.id;\n if (!cmId) {\n return;\n }\n const cmInfo = this.reactive.get('cm', cmId);\n\n event.preventDefault();\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target cm info.\n data.cmid = cmInfo.id;\n data.cmname = cmInfo.name;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursemodule', 'core'),\n body: Templates.render('core_courseformat/local/content/movecm', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element.\n let currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n this._disableLink(currentElement);\n\n // Open the cm section node if possible.\n currentElement.closest(this.selectors.SECTIONNODE)?.querySelector(this.selectors.TOGGLER)?.click();\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for === undefined || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n\n // Get draggable data from cm or section to dispatch.\n let targetSectionId;\n let targetCmId;\n if (target.dataset.for == 'cm') {\n const dropData = exporter.cmDraggableData(this.reactive.state, target.dataset.id);\n targetSectionId = dropData.sectionid;\n targetCmId = dropData.nextcmid;\n } else {\n const section = this.reactive.get('section', target.dataset.id);\n targetSectionId = target.dataset.id;\n targetCmId = section?.cmlist[0];\n }\n\n this.reactive.dispatch('cmMove', [cmId], targetSectionId, targetCmId);\n modal.destroy();\n });\n }\n\n /**\n * Replace an element with a copy with a different tag name.\n *\n * @param {Element} element the original element\n */\n _disableLink(element) {\n if (element) {\n element.style.pointerEvents = 'none';\n element.style.userSelect = 'none';\n element.classList.add(this.classes.DISABLED);\n element.setAttribute('aria-disabled', true);\n element.addEventListener('click', event => event.preventDefault());\n }\n }\n\n /**\n * Render a modal and return a body ready promise.\n *\n * @param {object} modalParams the modal params\n * @return {Promise} the modal body ready promise\n */\n _modalBodyRenderedPromise(modalParams) {\n return new Promise((resolve, reject) => {\n ModalFactory.create(modalParams).then((modal) => {\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n}\n"],"file":"actions.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/content/actions.js"],"names":["name","selectors","ACTIONLINK","SECTIONLINK","CMLINK","SECTIONNODE","TOGGLER","ADDSECTION","classes","DISABLED","state","addEventListener","element","_dispatchClick","_checkSectionlist","CourseEvents","sectionRefreshed","watch","handler","event","target","closest","classList","contains","preventDefault","methodName","_actionMethodName","dataset","action","requestName","charAt","toUpperCase","slice","_setAddSectionLocked","course","sectionlist","length","maxsections","sectionId","id","sectionInfo","reactive","get","exporter","getExporter","data","sectionid","sectiontitle","title","modalParams","body","Templates","render","_modalBodyRenderedPromise","modal","modalBody","getBody","currentElement","querySelector","_disableLink","generalSection","matches","for","getAttribute","dispatch","destroy","cmId","cmInfo","cmid","cmname","click","targetSectionId","targetCmId","dropData","cmDraggableData","nextcmid","section","cmlist","cmList","hassummary","rawtitle","saveButtonText","type","ModalFactory","types","SAVE_CANCEL","getRoot","on","ModalEvents","save","e","locked","targets","getElements","forEach","toggle","setElementLocked","style","pointerEvents","userSelect","add","setAttribute","Promise","resolve","reject","create","then","bodyRendered","setSaveButtonText","show","catch","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course state actions dispatcher.\n *\n * This module captures all data-dispatch links in the course content and dispatch the proper\n * state mutation, including any confirmation and modal required.\n *\n * @module core_courseformat/local/content/actions\n * @class core_courseformat/local/content/actions\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport Templates from 'core/templates';\nimport {prefetchStrings} from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\nimport {getList} from 'core/normalise';\nimport * as CourseEvents from 'core_course/events';\n\n// Load global strings.\nprefetchStrings('core', ['movecoursesection', 'movecoursemodule', 'confirm', 'delete']);\n\nexport default class extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_actions';\n // Default query selectors.\n this.selectors = {\n ACTIONLINK: `[data-action]`,\n SECTIONLINK: `[data-for='section']`,\n CMLINK: `[data-for='cm']`,\n SECTIONNODE: `[data-for='sectionnode']`,\n TOGGLER: `[data-toggle='collapse']`,\n ADDSECTION: `[data-action='addSection']`,\n };\n // Component css classes.\n this.classes = {\n DISABLED: `disabled`,\n };\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data.\n *\n */\n stateReady(state) {\n // Delegate dispatch clicks.\n this.addEventListener(\n this.element,\n 'click',\n this._dispatchClick\n );\n // Check section limit.\n this._checkSectionlist({state});\n // Add an Event listener to recalculate limits it if a section HTML is altered.\n this.addEventListener(\n this.element,\n CourseEvents.sectionRefreshed,\n () => this._checkSectionlist({state})\n );\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n // Check section limit.\n {watch: `course.sectionlist:updated`, handler: this._checkSectionlist},\n ];\n }\n\n _dispatchClick(event) {\n const target = event.target.closest(this.selectors.ACTIONLINK);\n if (!target) {\n return;\n }\n if (target.classList.contains(this.classes.DISABLED)) {\n event.preventDefault();\n return;\n }\n\n // Invoke proper method.\n const methodName = this._actionMethodName(target.dataset.action);\n\n if (this[methodName] !== undefined) {\n this[methodName](target, event);\n }\n }\n\n _actionMethodName(name) {\n const requestName = name.charAt(0).toUpperCase() + name.slice(1);\n return `_request${requestName}`;\n }\n\n /**\n * Check the section list and disable some options if needed.\n *\n * @param {Object} detail the update details.\n * @param {Object} detail.state the state object.\n */\n _checkSectionlist({state}) {\n // Disable \"add section\" actions if the course max sections has been exceeded.\n this._setAddSectionLocked(state.course.sectionlist.length > state.course.maxsections);\n }\n\n /**\n * Handle a move section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveSection(target, event) {\n // Check we have an id.\n const sectionId = target.dataset.id;\n if (!sectionId) {\n return;\n }\n const sectionInfo = this.reactive.get('section', sectionId);\n\n event.preventDefault();\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target section id and title.\n data.sectionid = sectionInfo.id;\n data.sectiontitle = sectionInfo.title;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursesection', 'core'),\n body: Templates.render('core_courseformat/local/content/movesection', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element and section zero.\n const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`);\n this._disableLink(currentElement);\n const generalSection = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-number='0']`);\n this._disableLink(generalSection);\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for != 'section' || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch('sectionMove', [sectionId], target.dataset.id);\n modal.destroy();\n });\n }\n\n /**\n * Handle a move cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveCm(target, event) {\n // Check we have an id.\n const cmId = target.dataset.id;\n if (!cmId) {\n return;\n }\n const cmInfo = this.reactive.get('cm', cmId);\n\n event.preventDefault();\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target cm info.\n data.cmid = cmInfo.id;\n data.cmname = cmInfo.name;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursemodule', 'core'),\n body: Templates.render('core_courseformat/local/content/movecm', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element.\n let currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n this._disableLink(currentElement);\n\n // Open the cm section node if possible.\n currentElement.closest(this.selectors.SECTIONNODE)?.querySelector(this.selectors.TOGGLER)?.click();\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for === undefined || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n\n // Get draggable data from cm or section to dispatch.\n let targetSectionId;\n let targetCmId;\n if (target.dataset.for == 'cm') {\n const dropData = exporter.cmDraggableData(this.reactive.state, target.dataset.id);\n targetSectionId = dropData.sectionid;\n targetCmId = dropData.nextcmid;\n } else {\n const section = this.reactive.get('section', target.dataset.id);\n targetSectionId = target.dataset.id;\n targetCmId = section?.cmlist[0];\n }\n\n this.reactive.dispatch('cmMove', [cmId], targetSectionId, targetCmId);\n modal.destroy();\n });\n }\n\n /**\n * Handle a create section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestAddSection(target, event) {\n event.preventDefault();\n this.reactive.dispatch('addSection', target.dataset.id ?? 0);\n }\n\n /**\n * Handle a delete section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestDeleteSection(target, event) {\n // Check we have an id.\n const sectionId = target.dataset.id;\n\n if (!sectionId) {\n return;\n }\n const sectionInfo = this.reactive.get('section', sectionId);\n\n event.preventDefault();\n\n const cmList = sectionInfo.cmlist ?? [];\n if (cmList.length || sectionInfo.hassummary || sectionInfo.rawtitle) {\n // We need confirmation if the section has something.\n const modalParams = {\n title: getString('confirm', 'core'),\n body: getString('confirmdeletesection', 'moodle', sectionInfo.title),\n saveButtonText: getString('delete', 'core'),\n type: ModalFactory.types.SAVE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.save,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this.reactive.dispatch('sectionDelete', [sectionId]);\n }\n );\n return;\n } else {\n // We don't need confirmation to delete empty sections.\n this.reactive.dispatch('sectionDelete', [sectionId]);\n }\n }\n\n /**\n * Disable all add sections actions.\n *\n * @param {boolean} locked the new locked value.\n */\n _setAddSectionLocked(locked) {\n const targets = this.getElements(this.selectors.ADDSECTION);\n targets.forEach(element => {\n element.classList.toggle(this.classes.DISABLED, locked);\n this.setElementLocked(element, locked);\n });\n }\n\n /**\n * Replace an element with a copy with a different tag name.\n *\n * @param {Element} element the original element\n */\n _disableLink(element) {\n if (element) {\n element.style.pointerEvents = 'none';\n element.style.userSelect = 'none';\n element.classList.add(this.classes.DISABLED);\n element.setAttribute('aria-disabled', true);\n element.addEventListener('click', event => event.preventDefault());\n }\n }\n\n /**\n * Render a modal and return a body ready promise.\n *\n * @param {object} modalParams the modal params\n * @return {Promise} the modal body ready promise\n */\n _modalBodyRenderedPromise(modalParams) {\n return new Promise((resolve, reject) => {\n ModalFactory.create(modalParams).then((modal) => {\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n // Configure some extra modal params.\n if (modalParams.saveButtonText !== undefined) {\n modal.setSaveButtonText(modalParams.saveButtonText);\n }\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n}\n"],"file":"actions.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/content/section.min.js b/course/format/amd/build/local/content/section.min.js index 03849de3a35..ee16817c65a 100644 --- a/course/format/amd/build/local/content/section.min.js +++ b/course/format/amd/build/local/content/section.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/content/section",["exports","core_courseformat/local/content/section/header","core_courseformat/local/courseeditor/dndsection"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}function e(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){e=function(a){return typeof a}}else{e=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return e(a)}function f(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function g(a){for(var b=1,c;b.\n\n/**\n * Course section format component.\n *\n * @module core_courseformat/local/content/section\n * @class core_courseformat/local/content/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Header from 'core_courseformat/local/content/section/header';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\n\nexport default class extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_title']`,\n CM: `[data-for=\"cmitem\"]`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Section zero and other formats sections may not have a title to drag.\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n if (sectionItem) {\n // Init the inner dragable element.\n const headerComponent = new Header({\n ...this,\n element: sectionItem,\n fullregion: this.element,\n });\n this.configDragDrop(headerComponent);\n }\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n const cms = this.getElements(this.selectors.CM);\n // DndUpload may add extra elements so :last-child selector cannot be used.\n if (!cms || cms.length === 0) {\n return null;\n }\n return cms[cms.length - 1];\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSection({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.locked = element.locked;\n }\n}\n"],"file":"section.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/content/section.js"],"names":["name","selectors","SECTION_ITEM","CM","classes","LOCKED","id","element","dataset","state","configState","reactive","isEditing","supportComponents","sectionItem","getElement","headerComponent","Header","fullregion","configDragDrop","watch","handler","_refreshSection","dropdata","type","sectionReturn","cms","getElements","length","classList","toggle","DRAGGING","dragging","locked","DndSection"],"mappings":"gQAwBA,OACA,O,60FAOa,CAEL,KAAKA,IAAL,CAAY,iBAAZ,CAEA,KAAKC,SAAL,CAAiB,CACbC,YAAY,6BADC,CAEbC,EAAE,wBAFW,CAAjB,CAKA,KAAKC,OAAL,CAAe,CACXC,MAAM,CAAE,gBADG,CAAf,CAKA,KAAKC,EAAL,CAAU,KAAKC,OAAL,CAAaC,OAAb,CAAqBF,EAClC,C,8CAOUG,C,CAAO,CACd,KAAKC,WAAL,CAAiBD,CAAjB,EAEA,GAAI,KAAKE,QAAL,CAAcC,SAAd,EAA2B,KAAKD,QAAL,CAAcE,iBAA7C,CAAgE,CAE5D,GAAMC,CAAAA,CAAW,CAAG,KAAKC,UAAL,CAAgB,KAAKd,SAAL,CAAeC,YAA/B,CAApB,CACA,GAAIY,CAAJ,CAAiB,CAEb,GAAME,CAAAA,CAAe,CAAG,GAAIC,UAAJ,MACjB,IADiB,EAEpBV,OAAO,CAAEO,CAFW,CAGpBI,UAAU,CAAE,KAAKX,OAHG,GAAxB,CAKA,KAAKY,cAAL,CAAoBH,CAApB,CACH,CACJ,CACJ,C,iDAOa,CACV,MAAO,CACH,CAACI,KAAK,mBAAa,KAAKd,EAAlB,aAAN,CAAuCe,OAAO,CAAE,KAAKC,eAArD,CADG,CAGV,C,0DAQeC,C,CAAU,CAEvB,GAAuB,SAAnB,WAAAA,CAAQ,WAARA,SAAAA,CAAQ,CAAEC,IAAV,GAA+D,CAA/B,OAAKb,QAAL,CAAcc,aAAlD,CAAsE,CACjE,QACH,CACD,2DAA8BF,CAA9B,CACH,C,6CAOW,CACR,GAAMG,CAAAA,CAAG,CAAG,KAAKC,WAAL,CAAiB,KAAK1B,SAAL,CAAeE,EAAhC,CAAZ,CAEA,GAAI,CAACuB,CAAD,EAAuB,CAAf,GAAAA,CAAG,CAACE,MAAhB,CAA8B,CAC1B,MAAO,KACV,CACD,MAAOF,CAAAA,CAAG,CAACA,CAAG,CAACE,MAAJ,CAAa,CAAd,CACb,C,0DAQ0B,SAAVrB,CAAU,GAAVA,OAAU,CAEvB,KAAKA,OAAL,CAAasB,SAAb,CAAuBC,MAAvB,CAA8B,KAAK1B,OAAL,CAAa2B,QAA3C,WAAqDxB,CAAO,CAACyB,QAA7D,oBACA,KAAKzB,OAAL,CAAasB,SAAb,CAAuBC,MAAvB,CAA8B,KAAK1B,OAAL,CAAaC,MAA3C,WAAmDE,CAAO,CAAC0B,MAA3D,oBACA,KAAKA,MAAL,CAAc1B,CAAO,CAAC0B,MACzB,C,cA/FwBC,S","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course section format component.\n *\n * @module core_courseformat/local/content/section\n * @class core_courseformat/local/content/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Header from 'core_courseformat/local/content/section/header';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\n\nexport default class extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_title']`,\n CM: `[data-for=\"cmitem\"]`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Section zero and other formats sections may not have a title to drag.\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n if (sectionItem) {\n // Init the inner dragable element.\n const headerComponent = new Header({\n ...this,\n element: sectionItem,\n fullregion: this.element,\n });\n this.configDragDrop(headerComponent);\n }\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // If the format uses one section per page sections dropping in the content is ignored.\n if (dropdata?.type === 'section' && this.reactive.sectionReturn != 0) {\n return false;\n }\n return super.validateDropData(dropdata);\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n const cms = this.getElements(this.selectors.CM);\n // DndUpload may add extra elements so :last-child selector cannot be used.\n if (!cms || cms.length === 0) {\n return null;\n }\n return cms[cms.length - 1];\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSection({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.locked = element.locked;\n }\n}\n"],"file":"section.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/courseeditor.min.js b/course/format/amd/build/local/courseeditor/courseeditor.min.js index 89a03362d34..fb5e774e8f8 100644 --- a/course/format/amd/build/local/courseeditor/courseeditor.min.js +++ b/course/format/amd/build/local/courseeditor/courseeditor.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseeditor/courseeditor",["exports","core/reactive","core/notification","core_courseformat/local/courseeditor/exporter","core/log","core/ajax","core/sessionstorage"],function(a,b,c,d,e,f,g){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;c=j(c);d=j(d);e=j(e);f=j(f);g=i(g);function h(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;h=function(){return a};return a}function i(a){if(a&&a.__esModule){return a}if(null===a||"object"!==k(a)&&"function"!=typeof a){return{default:a}}var b=h();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function j(a){return a&&a.__esModule?a:{default:a}}function k(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){k=function(a){return typeof a}}else{k=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return k(a)}function l(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function m(a){for(var b=1,c;b.\n\nimport {Reactive} from 'core/reactive';\nimport notification from 'core/notification';\nimport Exporter from 'core_courseformat/local/courseeditor/exporter';\nimport log from 'core/log';\nimport ajax from 'core/ajax';\nimport * as Storage from 'core/sessionstorage';\n\n/**\n * Main course editor module.\n *\n * All formats can register new components on this object to create new reactive\n * UI components that watch the current course state.\n *\n * @module core_courseformat/local/courseeditor/courseeditor\n * @class core_courseformat/local/courseeditor/courseeditor\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class extends Reactive {\n\n /**\n * The current state cache key\n *\n * The state cache is considered dirty if the state changes from the last page or\n * if the page has editing mode on.\n *\n * @attribute stateKey\n * @type number|null\n * @default 1\n * @package\n */\n stateKey = 1;\n\n /**\n * Set up the course editor when the page is ready.\n *\n * The course can only be loaded once per instance. Otherwise an error is thrown.\n *\n * @param {number} courseId course id\n */\n async loadCourse(courseId) {\n\n if (this.courseId) {\n throw new Error(`Cannot load ${courseId}, course already loaded with id ${this.courseId}`);\n }\n\n // Default view format setup.\n this._editing = false;\n this._supportscomponents = false;\n\n this.courseId = courseId;\n\n let stateData;\n\n try {\n stateData = await this.getServerCourseState();\n } catch (error) {\n log.error(\"EXCEPTION RAISED WHILE INIT COURSE EDITOR\");\n log.error(error);\n return;\n }\n\n this.setInitialState(stateData);\n\n // In editing mode, the session cache is considered dirty always.\n if (this.isEditing) {\n this.stateKey = null;\n } else {\n // Check if the last state is the same as the cached one.\n const newState = JSON.stringify(stateData);\n const previousState = Storage.get(`course/${courseId}/staticState`);\n if (previousState !== newState) {\n Storage.set(`course/${courseId}/staticState`, newState);\n Storage.set(`course/${courseId}/stateKey`, Date.now());\n }\n this.stateKey = Storage.get(`course/${courseId}/stateKey`);\n }\n }\n\n /**\n * Setup the current view settings\n *\n * @param {Object} setup format, page and course settings\n * @param {boolean} setup.editing if the page is in edit mode\n * @param {boolean} setup.supportscomponents if the format supports components for content\n */\n setViewFormat(setup) {\n this._editing = setup.editing ?? false;\n this._supportscomponents = setup.supportscomponents ?? false;\n }\n\n /**\n * Load the current course state from the server.\n *\n * @returns {Object} the current course state\n */\n async getServerCourseState() {\n const courseState = await ajax.call([{\n methodname: 'core_courseformat_get_state',\n args: {\n courseid: this.courseId,\n }\n }])[0];\n\n const stateData = JSON.parse(courseState);\n\n return {\n course: {},\n section: [],\n cm: [],\n ...stateData,\n };\n }\n\n /**\n * Return the current edit mode.\n *\n * Components should use this method to check if edit mode is active.\n *\n * @return {boolean} if edit is enabled\n */\n get isEditing() {\n return this._editing ?? false;\n }\n\n /**\n * Return a data exporter to transform state part into mustache contexts.\n *\n * @return {Exporter} the exporter class\n */\n getExporter() {\n return new Exporter(this);\n }\n\n /**\n * Return if the current course support components to refresh the content.\n *\n * @returns {boolean} if the current content support components\n */\n get supportComponents() {\n return this._supportscomponents ?? false;\n }\n\n /**\n * Get a value from the course editor static storage if any.\n *\n * The course editor static storage uses the sessionStorage to store values from the\n * components. This is used to prevent unnecesary template loadings on every page. However,\n * the storage does not work if no sessionStorage can be used (in debug mode for example),\n * if the page is in editing mode or if the initial state change from the last page.\n *\n * @param {string} key the key to get\n * @return {boolean|string} the storage value or false if cannot be loaded\n */\n getStorageValue(key) {\n if (this.isEditing || !this.stateKey) {\n return false;\n }\n const dataJson = Storage.get(`course/${this.courseId}/${key}`);\n if (!dataJson) {\n return false;\n }\n // Check the stateKey.\n try {\n const data = JSON.parse(dataJson);\n if (data?.stateKey !== this.stateKey) {\n return false;\n }\n return data.value;\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Stores a value into the course editor static storage if available\n *\n * @param {String} key the key to store\n * @param {*} value the value to store (must be compatible with JSON,stringify)\n * @returns {boolean} true if the value is stored\n */\n setStorageValue(key, value) {\n // Values cannot be stored on edit mode.\n if (this.isEditing) {\n return false;\n }\n const data = {\n stateKey: this.stateKey,\n value,\n };\n return Storage.set(`course/${this.courseId}/${key}`, JSON.stringify(data));\n }\n\n /**\n * Dispatch a change in the state.\n *\n * Usually reactive modules throw an error directly to the components when something\n * goes wrong. However, course editor can directly display a notification.\n *\n * @method dispatch\n * @param {mixed} args any number of params the mutation needs.\n */\n async dispatch(...args) {\n try {\n await super.dispatch(...args);\n } catch (error) {\n // Display error modal.\n notification.exception(error);\n // Force unlock all elements.\n super.dispatch('unlockAll');\n }\n }\n}\n"],"file":"courseeditor.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseeditor/courseeditor.js"],"names":["courseId","Error","_editing","_supportscomponents","getServerCourseState","stateData","log","error","setInitialState","isEditing","stateKey","newState","JSON","stringify","previousState","Storage","get","set","Date","now","setup","editing","supportscomponents","ajax","call","methodname","args","courseid","courseState","parse","course","section","cm","Exporter","key","dataJson","data","value","notification","exception","Reactive"],"mappings":"gTAgBA,OACA,OACA,OACA,OACA,O,muHA0Be,C,yBAQK,C,8FASCA,C,iGAET,KAAKA,Q,sBACC,IAAIC,CAAAA,KAAJ,uBAAyBD,CAAzB,4CAAoE,KAAKA,QAAzE,E,QAIV,KAAKE,QAAL,IACA,KAAKC,mBAAL,IAEA,KAAKH,QAAL,CAAgBA,CAAhB,C,wBAKsB,MAAKI,oBAAL,E,QAAlBC,C,6DAEAC,UAAIC,KAAJ,CAAU,2CAAV,EACAD,UAAIC,KAAJ,O,kCAIJ,KAAKC,eAAL,CAAqBH,CAArB,EAGA,GAAI,KAAKI,SAAT,CAAoB,CAChB,KAAKC,QAAL,CAAgB,IACnB,CAFD,IAEO,CAEGC,CAFH,CAEcC,IAAI,CAACC,SAAL,CAAeR,CAAf,CAFd,CAGGS,CAHH,CAGmBC,CAAO,CAACC,GAAR,kBAAsBhB,CAAtB,iBAHnB,CAIH,GAAIc,CAAa,GAAKH,CAAtB,CAAgC,CAC5BI,CAAO,CAACE,GAAR,kBAAsBjB,CAAtB,iBAA8CW,CAA9C,EACAI,CAAO,CAACE,GAAR,kBAAsBjB,CAAtB,cAA2CkB,IAAI,CAACC,GAAL,EAA3C,CACH,CACD,KAAKT,QAAL,CAAgBK,CAAO,CAACC,GAAR,kBAAsBhB,CAAtB,cACnB,C,6KAUSoB,C,CAAO,SACjB,KAAKlB,QAAL,WAAgBkB,CAAK,CAACC,OAAtB,mBACA,KAAKlB,mBAAL,WAA2BiB,CAAK,CAACE,kBAAjC,kBACH,C,oMAQ6BC,WAAKC,IAAL,CAAU,CAAC,CACjCC,UAAU,CAAE,6BADqB,CAEjCC,IAAI,CAAE,CACFC,QAAQ,CAAE,KAAK3B,QADb,CAF2B,CAAD,CAAV,EAKtB,CALsB,C,QAApB4B,C,QAOAvB,C,CAAYO,IAAI,CAACiB,KAAL,CAAWD,CAAX,C,6BAGdE,MAAM,CAAE,E,CACRC,OAAO,CAAE,E,CACTC,EAAE,CAAE,E,EACD3B,C,6KAoBG,CACV,MAAO,IAAI4B,UAAJ,CAAa,IAAb,CACV,C,wDAsBeC,C,CAAK,CACjB,GAAI,KAAKzB,SAAL,EAAkB,CAAC,KAAKC,QAA5B,CAAsC,CAClC,QACH,CACD,GAAMyB,CAAAA,CAAQ,CAAGpB,CAAO,CAACC,GAAR,kBAAsB,KAAKhB,QAA3B,aAAuCkC,CAAvC,EAAjB,CACA,GAAI,CAACC,CAAL,CAAe,CACX,QACH,CAED,GAAI,CACA,GAAMC,CAAAA,CAAI,CAAGxB,IAAI,CAACiB,KAAL,CAAWM,CAAX,CAAb,CACA,GAAI,QAAAC,CAAI,WAAJA,SAAAA,CAAI,CAAE1B,QAAN,IAAmB,KAAKA,QAA5B,CAAsC,CAClC,QACH,CACD,MAAO0B,CAAAA,CAAI,CAACC,KACf,CAAC,MAAO9B,CAAP,CAAc,CACZ,QACH,CACJ,C,wDASe2B,C,CAAKG,C,CAAO,CAExB,GAAI,KAAK5B,SAAT,CAAoB,CAChB,QACH,CACD,GAAM2B,CAAAA,CAAI,CAAG,CACT1B,QAAQ,CAAE,KAAKA,QADN,CAET2B,KAAK,CAALA,CAFS,CAAb,CAIA,MAAOtB,CAAAA,CAAO,CAACE,GAAR,kBAAsB,KAAKjB,QAA3B,aAAuCkC,CAAvC,EAA8CtB,IAAI,CAACC,SAAL,CAAeuB,CAAf,CAA9C,CACV,C,iNAWiBV,C,uBAAAA,C,yFAEcA,C,6DAGxBY,UAAaC,SAAb,OAEA,4CAAe,WAAf,E,2JAxFQ,OACZ,iBAAO,KAAKrC,QAAZ,kBACH,C,6CAgBuB,OACpB,iBAAO,KAAKC,mBAAZ,kBACH,C,cAnIwBqC,U","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport {Reactive} from 'core/reactive';\nimport notification from 'core/notification';\nimport Exporter from 'core_courseformat/local/courseeditor/exporter';\nimport log from 'core/log';\nimport ajax from 'core/ajax';\nimport * as Storage from 'core/sessionstorage';\n\n/**\n * Main course editor module.\n *\n * All formats can register new components on this object to create new reactive\n * UI components that watch the current course state.\n *\n * @module core_courseformat/local/courseeditor/courseeditor\n * @class core_courseformat/local/courseeditor/courseeditor\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class extends Reactive {\n\n /**\n * The current state cache key\n *\n * The state cache is considered dirty if the state changes from the last page or\n * if the page has editing mode on.\n *\n * @attribute stateKey\n * @type number|null\n * @default 1\n * @package\n */\n stateKey = 1;\n\n /**\n * The current page section return\n * @attribute sectionReturn\n * @type number\n * @default 0\n */\n sectionReturn = 0;\n\n /**\n * Set up the course editor when the page is ready.\n *\n * The course can only be loaded once per instance. Otherwise an error is thrown.\n *\n * @param {number} courseId course id\n */\n async loadCourse(courseId) {\n\n if (this.courseId) {\n throw new Error(`Cannot load ${courseId}, course already loaded with id ${this.courseId}`);\n }\n\n // Default view format setup.\n this._editing = false;\n this._supportscomponents = false;\n\n this.courseId = courseId;\n\n let stateData;\n\n try {\n stateData = await this.getServerCourseState();\n } catch (error) {\n log.error(\"EXCEPTION RAISED WHILE INIT COURSE EDITOR\");\n log.error(error);\n return;\n }\n\n this.setInitialState(stateData);\n\n // In editing mode, the session cache is considered dirty always.\n if (this.isEditing) {\n this.stateKey = null;\n } else {\n // Check if the last state is the same as the cached one.\n const newState = JSON.stringify(stateData);\n const previousState = Storage.get(`course/${courseId}/staticState`);\n if (previousState !== newState) {\n Storage.set(`course/${courseId}/staticState`, newState);\n Storage.set(`course/${courseId}/stateKey`, Date.now());\n }\n this.stateKey = Storage.get(`course/${courseId}/stateKey`);\n }\n }\n\n /**\n * Setup the current view settings\n *\n * @param {Object} setup format, page and course settings\n * @param {boolean} setup.editing if the page is in edit mode\n * @param {boolean} setup.supportscomponents if the format supports components for content\n */\n setViewFormat(setup) {\n this._editing = setup.editing ?? false;\n this._supportscomponents = setup.supportscomponents ?? false;\n }\n\n /**\n * Load the current course state from the server.\n *\n * @returns {Object} the current course state\n */\n async getServerCourseState() {\n const courseState = await ajax.call([{\n methodname: 'core_courseformat_get_state',\n args: {\n courseid: this.courseId,\n }\n }])[0];\n\n const stateData = JSON.parse(courseState);\n\n return {\n course: {},\n section: [],\n cm: [],\n ...stateData,\n };\n }\n\n /**\n * Return the current edit mode.\n *\n * Components should use this method to check if edit mode is active.\n *\n * @return {boolean} if edit is enabled\n */\n get isEditing() {\n return this._editing ?? false;\n }\n\n /**\n * Return a data exporter to transform state part into mustache contexts.\n *\n * @return {Exporter} the exporter class\n */\n getExporter() {\n return new Exporter(this);\n }\n\n /**\n * Return if the current course support components to refresh the content.\n *\n * @returns {boolean} if the current content support components\n */\n get supportComponents() {\n return this._supportscomponents ?? false;\n }\n\n /**\n * Get a value from the course editor static storage if any.\n *\n * The course editor static storage uses the sessionStorage to store values from the\n * components. This is used to prevent unnecesary template loadings on every page. However,\n * the storage does not work if no sessionStorage can be used (in debug mode for example),\n * if the page is in editing mode or if the initial state change from the last page.\n *\n * @param {string} key the key to get\n * @return {boolean|string} the storage value or false if cannot be loaded\n */\n getStorageValue(key) {\n if (this.isEditing || !this.stateKey) {\n return false;\n }\n const dataJson = Storage.get(`course/${this.courseId}/${key}`);\n if (!dataJson) {\n return false;\n }\n // Check the stateKey.\n try {\n const data = JSON.parse(dataJson);\n if (data?.stateKey !== this.stateKey) {\n return false;\n }\n return data.value;\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Stores a value into the course editor static storage if available\n *\n * @param {String} key the key to store\n * @param {*} value the value to store (must be compatible with JSON,stringify)\n * @returns {boolean} true if the value is stored\n */\n setStorageValue(key, value) {\n // Values cannot be stored on edit mode.\n if (this.isEditing) {\n return false;\n }\n const data = {\n stateKey: this.stateKey,\n value,\n };\n return Storage.set(`course/${this.courseId}/${key}`, JSON.stringify(data));\n }\n\n /**\n * Dispatch a change in the state.\n *\n * Usually reactive modules throw an error directly to the components when something\n * goes wrong. However, course editor can directly display a notification.\n *\n * @method dispatch\n * @param {mixed} args any number of params the mutation needs.\n */\n async dispatch(...args) {\n try {\n await super.dispatch(...args);\n } catch (error) {\n // Display error modal.\n notification.exception(error);\n // Force unlock all elements.\n super.dispatch('unlockAll');\n }\n }\n}\n"],"file":"courseeditor.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/dndsection.min.js.map b/course/format/amd/build/local/courseeditor/dndsection.min.js.map index 061f5d8b889..2d25fdc1730 100644 --- a/course/format/amd/build/local/courseeditor/dndsection.min.js.map +++ b/course/format/amd/build/local/courseeditor/dndsection.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/courseeditor/dndsection.js"],"names":["state","id","element","dataset","section","get","course","sectionitem","reactive","isEditing","supportComponents","dragdrop","DragDrop","classes","getClasses","unregister","dropdata","dispatch","type","sectionzeroid","sectionlist","getLastCm","classList","add","DROPDOWN","number","remove","DROPUP","BaseComponent"],"mappings":"47DAoCgBA,C,CAAO,CACf,KAAKC,EAAL,CAAU,KAAKC,OAAL,CAAaC,OAAb,CAAqBF,EAA/B,CACA,KAAKG,OAAL,CAAeJ,CAAK,CAACI,OAAN,CAAcC,GAAd,CAAkB,KAAKJ,EAAvB,CAAf,CACA,KAAKK,MAAL,CAAcN,CAAK,CAACM,MACvB,C,sDAOcC,C,CAAa,CAExB,GAAI,KAAKC,QAAL,CAAcC,SAAd,EAA2B,KAAKD,QAAL,CAAcE,iBAA7C,CAAgE,CAE5D,KAAKH,WAAL,CAAmBA,CAAnB,CAEA,KAAKI,QAAL,CAAgB,GAAIC,WAAJ,CAAa,IAAb,CAAhB,CAEA,KAAKC,OAAL,CAAe,KAAKF,QAAL,CAAcG,UAAd,EAClB,CACJ,C,yCAKS,CACN,GAAI,KAAKP,WAAL,SAAJ,CAAoC,CAChC,KAAKA,WAAL,CAAiBQ,UAAjB,EACH,CACD,GAAI,KAAKJ,QAAL,SAAJ,CAAiC,CAC7B,KAAKA,QAAL,CAAcI,UAAd,EACH,CACJ,C,6CAOW,CACR,MAAO,KACV,C,4CASSC,C,CAAU,CAChB,KAAKR,QAAL,CAAcS,QAAd,CAAuB,aAAvB,CAAsC,CAACD,CAAQ,CAACf,EAAV,CAAtC,IACH,C,wCAOOe,C,CAAU,CACd,KAAKR,QAAL,CAAcS,QAAd,CAAuB,aAAvB,CAAsC,CAACD,CAAQ,CAACf,EAAV,CAAtC,IACH,C,0DAQgBe,C,CAAU,CAEvB,GAAuB,IAAnB,WAAAA,CAAQ,WAARA,SAAAA,CAAQ,CAAEE,IAAV,CAAJ,CAA6B,CACzB,QACH,CAED,GAAuB,SAAnB,WAAAF,CAAQ,WAARA,SAAAA,CAAQ,CAAEE,IAAV,CAAJ,CAAkC,CAC9B,GAAMC,CAAAA,CAAa,CAAG,KAAKb,MAAL,CAAYc,WAAZ,CAAwB,CAAxB,CAAtB,CACA,MAAO,QAAAJ,CAAQ,WAARA,SAAAA,CAAQ,CAAEf,EAAV,GAAgB,KAAKA,EAArB,EAA2B,QAAAe,CAAQ,WAARA,SAAAA,CAAQ,CAAEf,EAAV,GAAgBkB,CAA3C,EAA4D,KAAKlB,EAAL,EAAWkB,CACjF,CACD,QACH,C,kDAOYH,C,CAAU,CACnB,GAAqB,IAAjB,EAAAA,CAAQ,CAACE,IAAb,CAA2B,OACvB,eAAKG,SAAL,yBAAkBC,SAAlB,CAA4BC,GAA5B,CAAgC,KAAKV,OAAL,CAAaW,QAA7C,CACH,CACD,GAAqB,SAAjB,EAAAR,CAAQ,CAACE,IAAb,CAAgC,CAE5B,GAAI,KAAKd,OAAL,CAAaqB,MAAb,CAAsBT,CAAQ,CAACS,MAAnC,CAA2C,CACvC,KAAKvB,OAAL,CAAaoB,SAAb,CAAuBI,MAAvB,CAA8B,KAAKb,OAAL,CAAac,MAA3C,EACA,KAAKzB,OAAL,CAAaoB,SAAb,CAAuBC,GAAvB,CAA2B,KAAKV,OAAL,CAAaW,QAAxC,CACH,CAHD,IAGO,CACH,KAAKtB,OAAL,CAAaoB,SAAb,CAAuBC,GAAvB,CAA2B,KAAKV,OAAL,CAAac,MAAxC,EACA,KAAKzB,OAAL,CAAaoB,SAAb,CAAuBI,MAAvB,CAA8B,KAAKb,OAAL,CAAaW,QAA3C,CACH,CACJ,CACJ,C,mDAKc,OACX,eAAKH,SAAL,yBAAkBC,SAAlB,CAA4BI,MAA5B,CAAmC,KAAKb,OAAL,CAAaW,QAAhD,EACA,KAAKtB,OAAL,CAAaoB,SAAb,CAAuBI,MAAvB,CAA8B,KAAKb,OAAL,CAAac,MAA3C,EACA,KAAKzB,OAAL,CAAaoB,SAAb,CAAuBI,MAAvB,CAA8B,KAAKb,OAAL,CAAaW,QAA3C,CACH,C,kCAOIR,C,CAAU,CAEX,GAAqB,IAAjB,EAAAA,CAAQ,CAACE,IAAb,CAA2B,CACvB,KAAKV,QAAL,CAAcS,QAAd,CAAuB,QAAvB,CAAiC,CAACD,CAAQ,CAACf,EAAV,CAAjC,CAAgD,KAAKA,EAArD,CACH,CACD,GAAqB,SAAjB,EAAAe,CAAQ,CAACE,IAAb,CAAgC,CAC5B,KAAKV,QAAL,CAAcS,QAAd,CAAuB,aAAvB,CAAsC,CAACD,CAAQ,CAACf,EAAV,CAAtC,CAAqD,KAAKA,EAA1D,CACH,CACJ,C,cArIwB2B,e","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index section component.\n *\n * This component is used to control specific course section interactions like drag and drop\n * in both course index and course content.\n *\n * @module core_courseformat/local/courseeditor/dndsection\n * @class core_courseformat/local/courseeditor/dndsection\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent, DragDrop} from 'core/reactive';\n\nexport default class extends BaseComponent {\n\n /**\n * Save some values form the state.\n *\n * @param {Object} state the current state\n */\n configState(state) {\n this.id = this.element.dataset.id;\n this.section = state.section.get(this.id);\n this.course = state.course;\n }\n\n /**\n * Register state values and the drag and drop subcomponent.\n *\n * @param {BaseComponent} sectionitem section item component\n */\n configDragDrop(sectionitem) {\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init the inner dragable element.\n this.sectionitem = sectionitem;\n // Init the dropzone.\n this.dragdrop = new DragDrop(this);\n // Save dropzone classes.\n this.classes = this.dragdrop.getClasses();\n }\n }\n\n /**\n * Remove all subcomponents dependencies.\n */\n destroy() {\n if (this.sectionitem !== undefined) {\n this.sectionitem.unregister();\n }\n if (this.dragdrop !== undefined) {\n this.dragdrop.unregister();\n }\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null} the las course module element of the section.\n */\n getLastCm() {\n return null;\n }\n\n // Drag and drop methods.\n\n /**\n * The element drop start hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragStart(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], true);\n }\n\n /**\n * The element drop end hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragEnd(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], false);\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // We accept any course module.\n if (dropdata?.type === 'cm') {\n return true;\n }\n // We accept any section bu the section 0 or ourself\n if (dropdata?.type === 'section') {\n const sectionzeroid = this.course.sectionlist[0];\n return dropdata?.id != this.id && dropdata?.id != sectionzeroid && this.id != sectionzeroid;\n }\n return false;\n }\n\n /**\n * Display the component dropzone.\n *\n * @param {Object} dropdata the accepted drop data\n */\n showDropZone(dropdata) {\n if (dropdata.type == 'cm') {\n this.getLastCm()?.classList.add(this.classes.DROPDOWN);\n }\n if (dropdata.type == 'section') {\n // The relative move of section depends on the section number.\n if (this.section.number > dropdata.number) {\n this.element.classList.remove(this.classes.DROPUP);\n this.element.classList.add(this.classes.DROPDOWN);\n } else {\n this.element.classList.add(this.classes.DROPUP);\n this.element.classList.remove(this.classes.DROPDOWN);\n }\n }\n }\n\n /**\n * Hide the component dropzone.\n */\n hideDropZone() {\n this.getLastCm()?.classList.remove(this.classes.DROPDOWN);\n this.element.classList.remove(this.classes.DROPUP);\n this.element.classList.remove(this.classes.DROPDOWN);\n }\n\n /**\n * Drop event handler.\n *\n * @param {Object} dropdata the accepted drop data\n */\n drop(dropdata) {\n // Call the move mutation.\n if (dropdata.type == 'cm') {\n this.reactive.dispatch('cmMove', [dropdata.id], this.id);\n }\n if (dropdata.type == 'section') {\n this.reactive.dispatch('sectionMove', [dropdata.id], this.id);\n }\n }\n}\n"],"file":"dndsection.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseeditor/dndsection.js"],"names":["state","id","element","dataset","section","get","course","sectionitem","reactive","isEditing","supportComponents","dragdrop","DragDrop","classes","getClasses","unregister","dropdata","dispatch","type","sectionzeroid","sectionlist","getLastCm","classList","add","DROPDOWN","number","remove","DROPUP","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index section component.\n *\n * This component is used to control specific course section interactions like drag and drop\n * in both course index and course content.\n *\n * @module core_courseformat/local/courseeditor/dndsection\n * @class core_courseformat/local/courseeditor/dndsection\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent, DragDrop} from 'core/reactive';\n\nexport default class extends BaseComponent {\n\n /**\n * Save some values form the state.\n *\n * @param {Object} state the current state\n */\n configState(state) {\n this.id = this.element.dataset.id;\n this.section = state.section.get(this.id);\n this.course = state.course;\n }\n\n /**\n * Register state values and the drag and drop subcomponent.\n *\n * @param {BaseComponent} sectionitem section item component\n */\n configDragDrop(sectionitem) {\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init the inner dragable element.\n this.sectionitem = sectionitem;\n // Init the dropzone.\n this.dragdrop = new DragDrop(this);\n // Save dropzone classes.\n this.classes = this.dragdrop.getClasses();\n }\n }\n\n /**\n * Remove all subcomponents dependencies.\n */\n destroy() {\n if (this.sectionitem !== undefined) {\n this.sectionitem.unregister();\n }\n if (this.dragdrop !== undefined) {\n this.dragdrop.unregister();\n }\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null} the las course module element of the section.\n */\n getLastCm() {\n return null;\n }\n\n // Drag and drop methods.\n\n /**\n * The element drop start hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragStart(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], true);\n }\n\n /**\n * The element drop end hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragEnd(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], false);\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // We accept any course module.\n if (dropdata?.type === 'cm') {\n return true;\n }\n // We accept any section but the section 0 or ourself\n if (dropdata?.type === 'section') {\n const sectionzeroid = this.course.sectionlist[0];\n return dropdata?.id != this.id && dropdata?.id != sectionzeroid && this.id != sectionzeroid;\n }\n return false;\n }\n\n /**\n * Display the component dropzone.\n *\n * @param {Object} dropdata the accepted drop data\n */\n showDropZone(dropdata) {\n if (dropdata.type == 'cm') {\n this.getLastCm()?.classList.add(this.classes.DROPDOWN);\n }\n if (dropdata.type == 'section') {\n // The relative move of section depends on the section number.\n if (this.section.number > dropdata.number) {\n this.element.classList.remove(this.classes.DROPUP);\n this.element.classList.add(this.classes.DROPDOWN);\n } else {\n this.element.classList.add(this.classes.DROPUP);\n this.element.classList.remove(this.classes.DROPDOWN);\n }\n }\n }\n\n /**\n * Hide the component dropzone.\n */\n hideDropZone() {\n this.getLastCm()?.classList.remove(this.classes.DROPDOWN);\n this.element.classList.remove(this.classes.DROPUP);\n this.element.classList.remove(this.classes.DROPDOWN);\n }\n\n /**\n * Drop event handler.\n *\n * @param {Object} dropdata the accepted drop data\n */\n drop(dropdata) {\n // Call the move mutation.\n if (dropdata.type == 'cm') {\n this.reactive.dispatch('cmMove', [dropdata.id], this.id);\n }\n if (dropdata.type == 'section') {\n this.reactive.dispatch('sectionMove', [dropdata.id], this.id);\n }\n }\n}\n"],"file":"dndsection.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/mutations.min.js b/course/format/amd/build/local/courseeditor/mutations.min.js index 0e9d8dedf9a..4d9d6494077 100644 --- a/course/format/amd/build/local/courseeditor/mutations.min.js +++ b/course/format/amd/build/local/courseeditor/mutations.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseeditor/mutations",["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);function c(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function d(a){return function(){var b=this,d=arguments;return new Promise(function(e,f){var i=a.apply(b,d);function g(a){c(i,e,f,g,h,"next",a)}function h(a){c(i,e,f,g,h,"throw",a)}g(void 0)})}}function e(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function f(a,b){for(var c=0,d;c.\n\nimport ajax from 'core/ajax';\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n\n /**\n * Mutation module initialize.\n *\n * The reactive instance will execute this method when addMutations or setMutation is invoked.\n *\n * @param {StateManager} stateManager the state manager\n */\n init(stateManager) {\n // Add a method to prepare the fields when some update is comming from the server.\n stateManager.addUpdateTypes({\n prepareFields: this._prepareFields,\n });\n }\n\n /**\n * Add default values to state elements.\n *\n * This method is called every time a webservice returns a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n _prepareFields(stateManager, updateName, fields) {\n // Any update should unlock the element.\n fields.locked = false;\n return fields;\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmids, true);\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n async sectionMove(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n /**\n * Lock or unlock course modules.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} lockValue the new locked value\n */\n cmLock(stateManager, cmIds, lockValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);\n }\n\n /**\n * Lock or unlock course sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} lockValue the new locked value\n */\n sectionLock(stateManager, sectionIds, lockValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Unlock all course elements.\n *\n * @param {StateManager} stateManager the current state manager\n */\n unlockAll(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.section.forEach((section) => {\n section.locked = false;\n });\n state.cm.forEach((cm) => {\n cm.locked = false;\n });\n stateManager.setReadOnly(true);\n }\n\n /*\n * Get updated user preferences and state data related to some section ids.\n *\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n * @param {Object} preferences the new preferences values\n */\n async sectionPreferences(stateManager, sectionIds, preferences) {\n stateManager.setReadOnly(false);\n // Check if we need to update preferences.\n let updatePreferences = false;\n sectionIds.forEach(sectionId => {\n const section = stateManager.get('section', sectionId);\n if (section === undefined) {\n return;\n }\n let newValue = preferences.contentexpanded ?? section.contentexpanded;\n if (section.contentexpanded != newValue) {\n section.contentexpanded = newValue;\n updatePreferences = true;\n }\n newValue = preferences.isactive ?? section.isactive;\n if (section.isactive != newValue) {\n section.isactive = newValue;\n updatePreferences = true;\n }\n });\n stateManager.setReadOnly(true);\n\n if (updatePreferences) {\n // Build the preference structures.\n const course = stateManager.get('course');\n const state = stateManager.state;\n const prefKey = `coursesectionspreferences_${course.id}`;\n const preferences = {\n contentcollapsed: [],\n indexcollapsed: [],\n };\n state.section.forEach(section => {\n if (!section.contentexpanded) {\n preferences.contentcollapsed.push(section.id);\n }\n if (!section.isactive) {\n preferences.indexcollapsed.push(section.id);\n }\n });\n const jsonString = JSON.stringify(preferences);\n M.util.set_user_preference(prefKey, jsonString);\n }\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n this.cmLock(stateManager, cmids, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n this.sectionLock(stateManager, sectionIds, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"file":"mutations.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseeditor/mutations.js"],"names":["action","courseId","ids","targetSectionId","targetCmId","args","courseid","targetsectionid","targetcmid","ajax","call","methodname","ajaxresult","JSON","parse","stateManager","addUpdateTypes","prepareFields","_prepareFields","updateName","fields","locked","cmids","Error","course","get","cmLock","_callEditWebservice","id","updates","processUpdates","sectionIds","sectionLock","cmIds","dragValue","_setElementsValue","lockValue","name","fieldName","newValue","setReadOnly","forEach","element","state","section","cm","preferences","updatePreferences","sectionId","contentexpanded","isactive","prefKey","contentcollapsed","indexcollapsed","push","jsonString","stringify","M","util","set_user_preference"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport ajax from 'core/ajax';\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n\n /**\n * Mutation module initialize.\n *\n * The reactive instance will execute this method when addMutations or setMutation is invoked.\n *\n * @param {StateManager} stateManager the state manager\n */\n init(stateManager) {\n // Add a method to prepare the fields when some update is comming from the server.\n stateManager.addUpdateTypes({\n prepareFields: this._prepareFields,\n });\n }\n\n /**\n * Add default values to state elements.\n *\n * This method is called every time a webservice returns a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n _prepareFields(stateManager, updateName, fields) {\n // Any update should unlock the element.\n fields.locked = false;\n return fields;\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmids, true);\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n async sectionMove(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Add a new section to a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {number} targetSectionId optional the target section id\n */\n async addSection(stateManager, targetSectionId) {\n if (!targetSectionId) {\n targetSectionId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of course modules ids\n */\n async sectionDelete(stateManager, sectionIds) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_delete', course.id, sectionIds);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n /**\n * Lock or unlock course modules.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} lockValue the new locked value\n */\n cmLock(stateManager, cmIds, lockValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);\n }\n\n /**\n * Lock or unlock course sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} lockValue the new locked value\n */\n sectionLock(stateManager, sectionIds, lockValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Unlock all course elements.\n *\n * @param {StateManager} stateManager the current state manager\n */\n unlockAll(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.section.forEach((section) => {\n section.locked = false;\n });\n state.cm.forEach((cm) => {\n cm.locked = false;\n });\n stateManager.setReadOnly(true);\n }\n\n /*\n * Get updated user preferences and state data related to some section ids.\n *\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n * @param {Object} preferences the new preferences values\n */\n async sectionPreferences(stateManager, sectionIds, preferences) {\n stateManager.setReadOnly(false);\n // Check if we need to update preferences.\n let updatePreferences = false;\n sectionIds.forEach(sectionId => {\n const section = stateManager.get('section', sectionId);\n if (section === undefined) {\n return;\n }\n let newValue = preferences.contentexpanded ?? section.contentexpanded;\n if (section.contentexpanded != newValue) {\n section.contentexpanded = newValue;\n updatePreferences = true;\n }\n newValue = preferences.isactive ?? section.isactive;\n if (section.isactive != newValue) {\n section.isactive = newValue;\n updatePreferences = true;\n }\n });\n stateManager.setReadOnly(true);\n\n if (updatePreferences) {\n // Build the preference structures.\n const course = stateManager.get('course');\n const state = stateManager.state;\n const prefKey = `coursesectionspreferences_${course.id}`;\n const preferences = {\n contentcollapsed: [],\n indexcollapsed: [],\n };\n state.section.forEach(section => {\n if (!section.contentexpanded) {\n preferences.contentcollapsed.push(section.id);\n }\n if (!section.isactive) {\n preferences.indexcollapsed.push(section.id);\n }\n });\n const jsonString = JSON.stringify(preferences);\n M.util.set_user_preference(prefKey, jsonString);\n }\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n this.cmLock(stateManager, cmids, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n this.sectionLock(stateManager, sectionIds, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"file":"mutations.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseindex/courseindex.min.js b/course/format/amd/build/local/courseindex/courseindex.min.js index 7b8524dd747..62ed1c88960 100644 --- a/course/format/amd/build/local/courseindex/courseindex.min.js +++ b/course/format/amd/build/local/courseindex/courseindex.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseindex/courseindex",["exports","core/reactive","core_courseformat/courseeditor","jquery","core_courseformat/local/courseindex/keyboardnav"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=f(d);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}function g(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){g=function(a){return typeof a}}else{g=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return g(a)}function h(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}function j(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function k(a,b){for(var c=0,d;cb.length){a.removeChild(a.lastChild)}}},{key:"_deleteCm",value:function _deleteCm(a){var b=a.element;delete this.cms[b.id]}}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=t;return a.default}); +define ("core_courseformat/local/courseindex/courseindex",["exports","core/reactive","core_courseformat/courseeditor","jquery","core_courseformat/local/courseindex/keyboardnav"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=f(d);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}function g(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){g=function(a){return typeof a}}else{g=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return g(a)}function h(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}function j(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function k(a,b){for(var c=0,d;cb.length){a.removeChild(a.lastChild)}}},{key:"_deleteCm",value:function _deleteCm(a){var b=a.element;delete this.cms[b.id]}},{key:"_deleteSection",value:function _deleteSection(a){var b=a.element;delete this.sections[b.id]}}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=t;return a.default}); //# sourceMappingURL=courseindex.min.js.map diff --git a/course/format/amd/build/local/courseindex/courseindex.min.js.map b/course/format/amd/build/local/courseindex/courseindex.min.js.map index 322c6c659ae..79c16e934cf 100644 --- a/course/format/amd/build/local/courseindex/courseindex.min.js.map +++ b/course/format/amd/build/local/courseindex/courseindex.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/courseindex/courseindex.js"],"names":["Component","name","selectors","SECTION","SECTION_CMLIST","CM","TOGGLER","COLLAPSE","DRAWER","classes","SECTIONHIDDEN","CMHIDDEN","SECTIONCURRENT","COLLAPSED","SHOW","sections","cms","addEventListener","element","_sectionTogglers","getElements","forEach","section","dataset","id","cm","treeNav","TreeNav","watch","handler","_refreshSectionCollapsed","_createCm","_deleteCm","_refreshCourseSectionlist","_refreshSectionCmlist","event","sectionlink","target","closest","isChevron","toggler","querySelector","isCollapsed","classList","contains","sectionId","getAttribute","reactive","dispatch","isactive","getElement","Error","_expandSectionNode","forceValue","collapsibleId","replace","collapsible","document","getElementById","togglerValue","collapse","state","fakeelement","createElement","add","innerHTML","get","sectionid","exporter","getExporter","data","renderComponent","newcomponent","newelement","parentNode","replaceChild","cmlist","listparent","_fixOrder","sectionlist","container","neworder","allitems","length","remove","itemid","index","item","currentitem","children","append","insertBefore","removeChild","lastChild","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/courseindex/courseindex\n * @class core_courseformat/local/courseindex/courseindex\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport jQuery from 'jquery';\nimport TreeNav from 'core_courseformat/local/courseindex/keyboardnav';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n CM: `[data-for='cm']`,\n TOGGLER: `[data-action=\"togglecourseindexsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n DRAWER: `.drawer`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n CMHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n COLLAPSED: `collapsed`,\n SHOW: `show`,\n };\n // Arrays to keep cms and sections elements.\n this.sections = {};\n this.cms = {};\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n // Get cms and sections elements.\n const sections = this.getElements(this.selectors.SECTION);\n sections.forEach((section) => {\n this.sections[section.dataset.id] = section;\n });\n const cms = this.getElements(this.selectors.CM);\n cms.forEach((cm) => {\n this.cms[cm.dataset.id] = cm;\n });\n\n // Configure Aria Tree.\n this.treeNav = new TreeNav(this);\n }\n\n getWatchers() {\n return [\n {watch: `section.isactive:updated`, handler: this._refreshSectionCollapsed},\n {watch: `cm:created`, handler: this._createCm},\n {watch: `cm:deleted`, handler: this._deleteCm},\n // Sections and cm sorting.\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n ];\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course index element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionPreferences',\n [sectionId],\n {\n isactive: isCollapsed,\n },\n );\n }\n }\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {object} args.element The leement to be expanded\n */\n _refreshSectionCollapsed({element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unkown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.isactive === isCollapsed) {\n this._expandSectionNode(element);\n }\n }\n\n /**\n * Expand a section node.\n *\n * By default the method will use element.isactive to decide if the\n * section is opened or closed. However, using forceValue it is possible\n * to open or close a section independant from the isactive attribute.\n *\n * @param {Object} element the course module state element\n * @param {boolean} forceValue optional forced expanded value\n */\n _expandSectionNode(element, forceValue) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n let collapsibleId = toggler.dataset.target ?? toggler.getAttribute(\"href\");\n if (!collapsibleId) {\n return;\n }\n collapsibleId = collapsibleId.replace('#', '');\n const collapsible = document.getElementById(collapsibleId);\n if (!collapsible) {\n return;\n }\n\n if (forceValue === undefined) {\n forceValue = (element.isactive) ? true : false;\n }\n\n // Course index is based on Bootstrap 4 collapsibles. To collapse them we need jQuery to\n // interact with collapsibles methods. Hopefully, this will change in Bootstrap 5 because\n // it does not require jQuery anymore.\n const togglerValue = (forceValue) ? 'show' : 'hide';\n jQuery(collapsible).collapse(togglerValue);\n }\n\n /**\n * Create a newcm instance.\n *\n * @param {object} param\n * @param {Object} param.state\n * @param {Object} param.element\n */\n async _createCm({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('li');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.cms[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshSectionCmlist({\n state,\n element: state.section.get(element.sectionid),\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.cm(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/cm', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.cms[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const listparent = this.getElement(this.selectors.SECTION_CMLIST, element.id);\n this._fixOrder(listparent, cmlist, this.cms);\n }\n\n /**\n * Refresh the section list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n this._fixOrder(this.element, sectionlist, this.sections);\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {Array} allitems the list of html elements that can be placed in the container\n */\n _fixOrder(container, neworder, allitems) {\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n const item = allitems[itemid];\n // Get the current element at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n container.removeChild(container.lastChild);\n }\n }\n\n /**\n * Remove a cm from the list.\n *\n * The actual DOM element removal is delegated to the cm component.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _deleteCm({element}) {\n delete this.cms[element.id];\n }\n}\n"],"file":"courseindex.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseindex/courseindex.js"],"names":["Component","name","selectors","SECTION","SECTION_CMLIST","CM","TOGGLER","COLLAPSE","DRAWER","classes","SECTIONHIDDEN","CMHIDDEN","SECTIONCURRENT","COLLAPSED","SHOW","sections","cms","addEventListener","element","_sectionTogglers","getElements","forEach","section","dataset","id","cm","treeNav","TreeNav","watch","handler","_refreshSectionCollapsed","_createCm","_deleteCm","_createSection","_deleteSection","_refreshCourseSectionlist","_refreshSectionCmlist","event","sectionlink","target","closest","isChevron","toggler","querySelector","isCollapsed","classList","contains","sectionId","getAttribute","reactive","dispatch","isactive","getElement","Error","_expandSectionNode","forceValue","collapsibleId","replace","collapsible","document","getElementById","togglerValue","collapse","state","fakeelement","createElement","add","innerHTML","get","sectionid","exporter","getExporter","data","renderComponent","newcomponent","newelement","parentNode","replaceChild","course","cmlist","listparent","_fixOrder","sectionlist","container","neworder","allitems","length","remove","itemid","index","item","currentitem","children","append","insertBefore","removeChild","lastChild","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/courseindex/courseindex\n * @class core_courseformat/local/courseindex/courseindex\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport jQuery from 'jquery';\nimport TreeNav from 'core_courseformat/local/courseindex/keyboardnav';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n CM: `[data-for='cm']`,\n TOGGLER: `[data-action=\"togglecourseindexsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n DRAWER: `.drawer`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n CMHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n COLLAPSED: `collapsed`,\n SHOW: `show`,\n };\n // Arrays to keep cms and sections elements.\n this.sections = {};\n this.cms = {};\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n // Get cms and sections elements.\n const sections = this.getElements(this.selectors.SECTION);\n sections.forEach((section) => {\n this.sections[section.dataset.id] = section;\n });\n const cms = this.getElements(this.selectors.CM);\n cms.forEach((cm) => {\n this.cms[cm.dataset.id] = cm;\n });\n\n // Configure Aria Tree.\n this.treeNav = new TreeNav(this);\n }\n\n getWatchers() {\n return [\n {watch: `section.isactive:updated`, handler: this._refreshSectionCollapsed},\n {watch: `cm:created`, handler: this._createCm},\n {watch: `cm:deleted`, handler: this._deleteCm},\n {watch: `section:created`, handler: this._createSection},\n {watch: `section:deleted`, handler: this._deleteSection},\n // Sections and cm sorting.\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n ];\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course index element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionPreferences',\n [sectionId],\n {\n isactive: isCollapsed,\n },\n );\n }\n }\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {object} args.element The leement to be expanded\n */\n _refreshSectionCollapsed({element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unkown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.isactive === isCollapsed) {\n this._expandSectionNode(element);\n }\n }\n\n /**\n * Expand a section node.\n *\n * By default the method will use element.isactive to decide if the\n * section is opened or closed. However, using forceValue it is possible\n * to open or close a section independant from the isactive attribute.\n *\n * @param {Object} element the course module state element\n * @param {boolean} forceValue optional forced expanded value\n */\n _expandSectionNode(element, forceValue) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n let collapsibleId = toggler.dataset.target ?? toggler.getAttribute(\"href\");\n if (!collapsibleId) {\n return;\n }\n collapsibleId = collapsibleId.replace('#', '');\n const collapsible = document.getElementById(collapsibleId);\n if (!collapsible) {\n return;\n }\n\n if (forceValue === undefined) {\n forceValue = (element.isactive) ? true : false;\n }\n\n // Course index is based on Bootstrap 4 collapsibles. To collapse them we need jQuery to\n // interact with collapsibles methods. Hopefully, this will change in Bootstrap 5 because\n // it does not require jQuery anymore.\n const togglerValue = (forceValue) ? 'show' : 'hide';\n jQuery(collapsible).collapse(togglerValue);\n }\n\n /**\n * Create a newcm instance.\n *\n * @param {object} param\n * @param {Object} param.state\n * @param {Object} param.element\n */\n async _createCm({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('li');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.cms[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshSectionCmlist({\n state,\n element: state.section.get(element.sectionid),\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.cm(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/cm', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.cms[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Create a new section instance.\n *\n * @param {Object} details the update details.\n * @param {Object} details.state the state data.\n * @param {Object} details.element the element data.\n */\n async _createSection({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('div');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.sections[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshCourseSectionlist({\n state,\n element: state.course,\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.section(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/section', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.sections[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const listparent = this.getElement(this.selectors.SECTION_CMLIST, element.id);\n this._fixOrder(listparent, cmlist, this.cms);\n }\n\n /**\n * Refresh the section list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n this._fixOrder(this.element, sectionlist, this.sections);\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {Array} allitems the list of html elements that can be placed in the container\n */\n _fixOrder(container, neworder, allitems) {\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n const item = allitems[itemid];\n // Get the current element at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n container.removeChild(container.lastChild);\n }\n }\n\n /**\n * Remove a cm from the list.\n *\n * The actual DOM element removal is delegated to the cm component.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _deleteCm({element}) {\n delete this.cms[element.id];\n }\n\n /**\n * Remove a section from the list.\n *\n * The actual DOM element removal is delegated to the section component.\n *\n * @param {Object} details the update details.\n * @param {Object} details.element the element data.\n */\n _deleteSection({element}) {\n delete this.sections[element.id];\n }\n}\n"],"file":"courseindex.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseindex/section.min.js b/course/format/amd/build/local/courseindex/section.min.js index 4a1ce94cb05..1adbaf6b84e 100644 --- a/course/format/amd/build/local/courseindex/section.min.js +++ b/course/format/amd/build/local/courseindex/section.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseindex/section",["exports","core_courseformat/local/courseindex/sectiontitle","core_courseformat/local/courseeditor/dndsection"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}function e(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){e=function(a){return typeof a}}else{e=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return e(a)}function f(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function g(a){for(var b=1,c;b.\n\n/**\n * Course index section component.\n *\n * This component is used to control specific course section interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/section\n * @class core_courseformat/local/courseindex/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport SectionTitle from 'core_courseformat/local/courseindex/sectiontitle';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\n\nexport default class Component extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_item']`,\n SECTION_TITLE: `[data-for='section_title']`,\n CM_LAST: `[data-for=\"cm\"]:last-child`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n LOCKED: 'editinprogress',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init the inner dragable element passing the full section as affected region.\n const titleitem = new SectionTitle({\n ...this,\n element: this.getElement(this.selectors.SECTION_ITEM),\n fullregion: this.element,\n });\n this.configDragDrop(titleitem);\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n return this.getElement(this.selectors.CM_LAST);\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {Object} param details the update details.\n * @param {Object} param.element\n */\n _refreshSection({element}) {\n // Update classes.\n const sectionitem = this.getElement(this.selectors.SECTION_ITEM);\n sectionitem.classList.toggle(this.classes.SECTIONHIDDEN, !element.visible);\n this.element.classList.toggle(this.classes.SECTIONCURRENT, element.current);\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.locked = element.locked;\n // Update title.\n this.getElement(this.selectors.SECTION_TITLE).innerHTML = element.title;\n }\n}\n"],"file":"section.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseindex/section.js"],"names":["Component","name","selectors","SECTION_ITEM","SECTION_TITLE","CM_LAST","classes","SECTIONHIDDEN","SECTIONCURRENT","LOCKED","id","element","dataset","state","configState","reactive","isEditing","supportComponents","titleitem","SectionTitle","getElement","fullregion","configDragDrop","watch","handler","remove","_refreshSection","sectionitem","classList","toggle","visible","current","DRAGGING","dragging","locked","innerHTML","title","target","document","getElementById","DndSection"],"mappings":"sQA0BA,OACA,O,63EAEqBA,CAAAA,C,+HAKR,CAEL,KAAKC,IAAL,CAAY,qBAAZ,CAEA,KAAKC,SAAL,CAAiB,CACbC,YAAY,4BADC,CAEbC,aAAa,6BAFA,CAGbC,OAAO,+BAHM,CAAjB,CAMA,KAAKC,OAAL,CAAe,CACXC,aAAa,CAAE,QADJ,CAEXC,cAAc,CAAE,SAFL,CAGXC,MAAM,CAAE,gBAHG,CAAf,CAOA,KAAKC,EAAL,CAAU,KAAKC,OAAL,CAAaC,OAAb,CAAqBF,EAClC,C,8CAqBUG,C,CAAO,CACd,KAAKC,WAAL,CAAiBD,CAAjB,EAEA,GAAI,KAAKE,QAAL,CAAcC,SAAd,EAA2B,KAAKD,QAAL,CAAcE,iBAA7C,CAAgE,CAE5D,GAAMC,CAAAA,CAAS,CAAG,GAAIC,UAAJ,MACX,IADW,EAEdR,OAAO,CAAE,KAAKS,UAAL,CAAgB,KAAKlB,SAAL,CAAeC,YAA/B,CAFK,CAGdkB,UAAU,CAAE,KAAKV,OAHH,GAAlB,CAKA,KAAKW,cAAL,CAAoBJ,CAApB,CACH,CACJ,C,iDAOa,CACV,MAAO,CACH,CAACK,KAAK,mBAAa,KAAKb,EAAlB,aAAN,CAAuCc,OAAO,CAAE,KAAKC,MAArD,CADG,CAEH,CAACF,KAAK,mBAAa,KAAKb,EAAlB,aAAN,CAAuCc,OAAO,CAAE,KAAKE,eAArD,CAFG,CAIV,C,6CAOW,CACR,MAAO,MAAKN,UAAL,CAAgB,KAAKlB,SAAL,CAAeG,OAA/B,CACV,C,0DAQ0B,SAAVM,CAAU,GAAVA,OAAU,CAEjBgB,CAAW,CAAG,KAAKP,UAAL,CAAgB,KAAKlB,SAAL,CAAeC,YAA/B,CAFG,CAGvBwB,CAAW,CAACC,SAAZ,CAAsBC,MAAtB,CAA6B,KAAKvB,OAAL,CAAaC,aAA1C,CAAyD,CAACI,CAAO,CAACmB,OAAlE,EACA,KAAKnB,OAAL,CAAaiB,SAAb,CAAuBC,MAAvB,CAA8B,KAAKvB,OAAL,CAAaE,cAA3C,CAA2DG,CAAO,CAACoB,OAAnE,EACA,KAAKpB,OAAL,CAAaiB,SAAb,CAAuBC,MAAvB,CAA8B,KAAKvB,OAAL,CAAa0B,QAA3C,WAAqDrB,CAAO,CAACsB,QAA7D,oBACA,KAAKtB,OAAL,CAAaiB,SAAb,CAAuBC,MAAvB,CAA8B,KAAKvB,OAAL,CAAaG,MAA3C,WAAmDE,CAAO,CAACuB,MAA3D,oBACA,KAAKA,MAAL,CAAcvB,CAAO,CAACuB,MAAtB,CAEA,KAAKd,UAAL,CAAgB,KAAKlB,SAAL,CAAeE,aAA/B,EAA8C+B,SAA9C,CAA0DxB,CAAO,CAACyB,KACrE,C,oCA/DWC,C,CAAQnC,C,CAAW,CAC3B,MAAO,IAAIF,CAAAA,CAAJ,CAAc,CACjBW,OAAO,CAAE2B,QAAQ,CAACC,cAAT,CAAwBF,CAAxB,CADQ,CAEjBnC,SAAS,CAATA,CAFiB,CAAd,CAIV,C,cArCkCsC,S","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index section component.\n *\n * This component is used to control specific course section interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/section\n * @class core_courseformat/local/courseindex/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport SectionTitle from 'core_courseformat/local/courseindex/sectiontitle';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\n\nexport default class Component extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_item']`,\n SECTION_TITLE: `[data-for='section_title']`,\n CM_LAST: `[data-for=\"cm\"]:last-child`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n LOCKED: 'editinprogress',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init the inner dragable element passing the full section as affected region.\n const titleitem = new SectionTitle({\n ...this,\n element: this.getElement(this.selectors.SECTION_ITEM),\n fullregion: this.element,\n });\n this.configDragDrop(titleitem);\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:deleted`, handler: this.remove},\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n return this.getElement(this.selectors.CM_LAST);\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {Object} param details the update details.\n * @param {Object} param.element\n */\n _refreshSection({element}) {\n // Update classes.\n const sectionitem = this.getElement(this.selectors.SECTION_ITEM);\n sectionitem.classList.toggle(this.classes.SECTIONHIDDEN, !element.visible);\n this.element.classList.toggle(this.classes.SECTIONCURRENT, element.current);\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.locked = element.locked;\n // Update title.\n this.getElement(this.selectors.SECTION_TITLE).innerHTML = element.title;\n }\n}\n"],"file":"section.min.js"} \ No newline at end of file diff --git a/course/format/amd/src/local/content.js b/course/format/amd/src/local/content.js index 4eabf5ba9f9..5e5ba1e8e45 100644 --- a/course/format/amd/src/local/content.js +++ b/course/format/amd/src/local/content.js @@ -35,8 +35,10 @@ export default class Component extends BaseComponent { /** * Constructor hook. + * + * @param {Object} descriptor the component descriptor */ - create() { + create(descriptor) { // Optional component name for debugging. this.name = 'course_format'; // Default query selectors. @@ -50,6 +52,7 @@ export default class Component extends BaseComponent { COLLAPSE: `[data-toggle="collapse"]`, // Formats can override the activity tag but a default one is needed to create new elements. ACTIVITYTAG: 'li', + SECTIONTAG: 'li', }; // Default classes to toggle on refresh. this.classes = { @@ -57,6 +60,7 @@ export default class Component extends BaseComponent { // Course content classes. ACTIVITY: `activity`, STATEDREADY: `stateready`, + SECTION: `section`, }; // Array to save dettached elements during element resorting. this.dettachedCms = {}; @@ -64,6 +68,8 @@ export default class Component extends BaseComponent { // Index of sections and cms components. this.sections = {}; this.cms = {}; + // The page section return. + this.sectionReturn = descriptor.sectionReturn ?? 0; } /** @@ -71,13 +77,15 @@ export default class Component extends BaseComponent { * * @param {string} target the DOM main element or its ID * @param {object} selectors optional css selector overrides + * @param {number} sectionReturn the content section return * @return {Component} */ - static init(target, selectors) { + static init(target, selectors, sectionReturn) { return new Component({ element: document.getElementById(target), reactive: getCurrentCourseEditor(), selectors, + sectionReturn, }); } @@ -138,6 +146,10 @@ export default class Component extends BaseComponent { * @returns {Array} of watchers */ getWatchers() { + // Section return is a global page variable but most formats define it just before start printing + // the course content. This is the reason why we define this page setting here. + this.reactive.sectionReturn = this.sectionReturn; + // Check if the course format is compatible with reactive components. if (!this.reactive.supportComponents) { return []; @@ -161,22 +173,6 @@ export default class Component extends BaseComponent { ]; } - /** - * Reload a course module. - * - * Most course module HTML is still strongly backend dependant. - * Some changes require to get a new version af the module. - * - * @param {Object} param - * @param {Object} param.element update the state update data - */ - _reloadCm({element}) { - const cmitem = this.getElement(this.selectors.CM, element.id); - if (cmitem) { - courseActions.refreshModule(cmitem, element.id); - } - } - /** * Update section collapsed. * @@ -281,10 +277,14 @@ export default class Component extends BaseComponent { * @param {Object} param.element details the update details. */ _refreshCourseSectionlist({element}) { + // If we have a section return means we only show a single section so no need to fix order. + if (this.reactive.sectionReturn != 0) { + return; + } const sectionlist = element.sectionlist ?? []; const listparent = this.getElement(this.selectors.COURSE_SECTIONLIST); // For now section cannot be created at a frontend level. - const createSection = () => undefined; + const createSection = this._createSectionItem.bind(this); if (listparent) { this._fixOrder(listparent, sectionlist, this.selectors.SECTION, this.dettachedSections, createSection); } @@ -364,6 +364,26 @@ export default class Component extends BaseComponent { } } + /** + * Reload a course section contents. + * + * Section HTML is still strongly backend dependant. + * Some changes require to get a new version of the section. + * + * @param {details} param0 the watcher details + * @param {object} param0.element the state object + */ + _reloadSection({element}) { + const sectionitem = this.getElement(this.selectors.SECTION, element.id); + if (sectionitem) { + const promise = courseActions.refreshSection(sectionitem, element.id); + promise.then(() => { + this._indexContents(); + return; + }).catch(); + } + } + /** * Create a new course module item in a section. * @@ -388,6 +408,32 @@ export default class Component extends BaseComponent { return newItem; } + /** + * Create a new section item. + * + * This method will append a fake item in the container and trigger an ajax request to + * replace the fake element by the real content. + * + * @param {Element} container the container element (section) + * @param {Number} sectionid the course-module ID + * @returns {Element} the created element + */ + _createSectionItem(container, sectionid) { + const section = this.reactive.get('section', sectionid); + const newItem = document.createElement(this.selectors.SECTIONTAG); + newItem.dataset.for = 'section'; + newItem.dataset.id = sectionid; + newItem.dataset.number = section.number; + // The legacy actions.js requires a specific ID and class to refresh the section. + newItem.id = `section-${sectionid}`; + newItem.classList.add(this.classes.SECTION); + container.append(newItem); + this._reloadSection({ + element: section, + }); + return newItem; + } + /** * Fix/reorder the section or cms order. * diff --git a/course/format/amd/src/local/content/actions.js b/course/format/amd/src/local/content/actions.js index b1db6e8ca5a..d249a8164ed 100644 --- a/course/format/amd/src/local/content/actions.js +++ b/course/format/amd/src/local/content/actions.js @@ -32,9 +32,10 @@ import Templates from 'core/templates'; import {prefetchStrings} from 'core/prefetch'; import {get_string as getString} from 'core/str'; import {getList} from 'core/normalise'; +import * as CourseEvents from 'core_course/events'; // Load global strings. -prefetchStrings('core', ['movecoursesection', 'movecoursemodule']); +prefetchStrings('core', ['movecoursesection', 'movecoursemodule', 'confirm', 'delete']); export default class extends BaseComponent { @@ -51,6 +52,7 @@ export default class extends BaseComponent { CMLINK: `[data-for='cm']`, SECTIONNODE: `[data-for='sectionnode']`, TOGGLER: `[data-toggle='collapse']`, + ADDSECTION: `[data-action='addSection']`, }; // Component css classes. this.classes = { @@ -61,14 +63,36 @@ export default class extends BaseComponent { /** * Initial state ready method. * + * @param {Object} state the state data. + * */ - stateReady() { + stateReady(state) { // Delegate dispatch clicks. this.addEventListener( this.element, 'click', this._dispatchClick ); + // Check section limit. + this._checkSectionlist({state}); + // Add an Event listener to recalculate limits it if a section HTML is altered. + this.addEventListener( + this.element, + CourseEvents.sectionRefreshed, + () => this._checkSectionlist({state}) + ); + } + + /** + * Return the component watchers. + * + * @returns {Array} of watchers + */ + getWatchers() { + return [ + // Check section limit. + {watch: `course.sectionlist:updated`, handler: this._checkSectionlist}, + ]; } _dispatchClick(event) { @@ -76,6 +100,10 @@ export default class extends BaseComponent { if (!target) { return; } + if (target.classList.contains(this.classes.DISABLED)) { + event.preventDefault(); + return; + } // Invoke proper method. const methodName = this._actionMethodName(target.dataset.action); @@ -90,6 +118,17 @@ export default class extends BaseComponent { return `_request${requestName}`; } + /** + * Check the section list and disable some options if needed. + * + * @param {Object} detail the update details. + * @param {Object} detail.state the state object. + */ + _checkSectionlist({state}) { + // Disable "add section" actions if the course max sections has been exceeded. + this._setAddSectionLocked(state.course.sectionlist.length > state.course.maxsections); + } + /** * Handle a move section request. * @@ -217,6 +256,75 @@ export default class extends BaseComponent { }); } + /** + * Handle a create section request. + * + * @param {Element} target the dispatch action element + * @param {Event} event the triggered event + */ + async _requestAddSection(target, event) { + event.preventDefault(); + this.reactive.dispatch('addSection', target.dataset.id ?? 0); + } + + /** + * Handle a delete section request. + * + * @param {Element} target the dispatch action element + * @param {Event} event the triggered event + */ + async _requestDeleteSection(target, event) { + // Check we have an id. + const sectionId = target.dataset.id; + + if (!sectionId) { + return; + } + const sectionInfo = this.reactive.get('section', sectionId); + + event.preventDefault(); + + const cmList = sectionInfo.cmlist ?? []; + if (cmList.length || sectionInfo.hassummary || sectionInfo.rawtitle) { + // We need confirmation if the section has something. + const modalParams = { + title: getString('confirm', 'core'), + body: getString('confirmdeletesection', 'moodle', sectionInfo.title), + saveButtonText: getString('delete', 'core'), + type: ModalFactory.types.SAVE_CANCEL, + }; + + const modal = await this._modalBodyRenderedPromise(modalParams); + + modal.getRoot().on( + ModalEvents.save, + e => { + // Stop the default save button behaviour which is to close the modal. + e.preventDefault(); + modal.destroy(); + this.reactive.dispatch('sectionDelete', [sectionId]); + } + ); + return; + } else { + // We don't need confirmation to delete empty sections. + this.reactive.dispatch('sectionDelete', [sectionId]); + } + } + + /** + * Disable all add sections actions. + * + * @param {boolean} locked the new locked value. + */ + _setAddSectionLocked(locked) { + const targets = this.getElements(this.selectors.ADDSECTION); + targets.forEach(element => { + element.classList.toggle(this.classes.DISABLED, locked); + this.setElementLocked(element, locked); + }); + } + /** * Replace an element with a copy with a different tag name. * @@ -245,6 +353,10 @@ export default class extends BaseComponent { modal.getRoot().on(ModalEvents.bodyRendered, () => { resolve(modal); }); + // Configure some extra modal params. + if (modalParams.saveButtonText !== undefined) { + modal.setSaveButtonText(modalParams.saveButtonText); + } modal.show(); return; }).catch(() => { diff --git a/course/format/amd/src/local/content/section.js b/course/format/amd/src/local/content/section.js index d0d38700ede..362adf266e6 100644 --- a/course/format/amd/src/local/content/section.js +++ b/course/format/amd/src/local/content/section.js @@ -81,6 +81,20 @@ export default class extends DndSection { ]; } + /** + * Validate if the drop data can be dropped over the component. + * + * @param {Object} dropdata the exported drop data. + * @returns {boolean} + */ + validateDropData(dropdata) { + // If the format uses one section per page sections dropping in the content is ignored. + if (dropdata?.type === 'section' && this.reactive.sectionReturn != 0) { + return false; + } + return super.validateDropData(dropdata); + } + /** * Get the last CM element of that section. * diff --git a/course/format/amd/src/local/courseeditor/courseeditor.js b/course/format/amd/src/local/courseeditor/courseeditor.js index e14b99a325e..0db1eb2aa90 100644 --- a/course/format/amd/src/local/courseeditor/courseeditor.js +++ b/course/format/amd/src/local/courseeditor/courseeditor.js @@ -46,6 +46,14 @@ export default class extends Reactive { */ stateKey = 1; + /** + * The current page section return + * @attribute sectionReturn + * @type number + * @default 0 + */ + sectionReturn = 0; + /** * Set up the course editor when the page is ready. * diff --git a/course/format/amd/src/local/courseeditor/dndsection.js b/course/format/amd/src/local/courseeditor/dndsection.js index 581673965e8..52a3a53ddf6 100644 --- a/course/format/amd/src/local/courseeditor/dndsection.js +++ b/course/format/amd/src/local/courseeditor/dndsection.js @@ -109,7 +109,7 @@ export default class extends BaseComponent { if (dropdata?.type === 'cm') { return true; } - // We accept any section bu the section 0 or ourself + // We accept any section but the section 0 or ourself if (dropdata?.type === 'section') { const sectionzeroid = this.course.sectionlist[0]; return dropdata?.id != this.id && dropdata?.id != sectionzeroid && this.id != sectionzeroid; diff --git a/course/format/amd/src/local/courseeditor/mutations.js b/course/format/amd/src/local/courseeditor/mutations.js index 64bb5490a13..26efd6e6ed9 100644 --- a/course/format/amd/src/local/courseeditor/mutations.js +++ b/course/format/amd/src/local/courseeditor/mutations.js @@ -131,6 +131,33 @@ export default class { this.sectionLock(stateManager, sectionIds, false); } + /** + * Add a new section to a specific course location. + * + * @param {StateManager} stateManager the current state manager + * @param {number} targetSectionId optional the target section id + */ + async addSection(stateManager, targetSectionId) { + if (!targetSectionId) { + targetSectionId = 0; + } + const course = stateManager.get('course'); + const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId); + stateManager.processUpdates(updates); + } + + /** + * Delete sections. + * + * @param {StateManager} stateManager the current state manager + * @param {array} sectionIds the list of course modules ids + */ + async sectionDelete(stateManager, sectionIds) { + const course = stateManager.get('course'); + const updates = await this._callEditWebservice('section_delete', course.id, sectionIds); + stateManager.processUpdates(updates); + } + /** * Mark or unmark course modules as dragging. * diff --git a/course/format/amd/src/local/courseindex/courseindex.js b/course/format/amd/src/local/courseindex/courseindex.js index 7057504311c..65bcdf60f19 100644 --- a/course/format/amd/src/local/courseindex/courseindex.js +++ b/course/format/amd/src/local/courseindex/courseindex.js @@ -98,6 +98,8 @@ export default class Component extends BaseComponent { {watch: `section.isactive:updated`, handler: this._refreshSectionCollapsed}, {watch: `cm:created`, handler: this._createCm}, {watch: `cm:deleted`, handler: this._deleteCm}, + {watch: `section:created`, handler: this._createSection}, + {watch: `section:deleted`, handler: this._deleteSection}, // Sections and cm sorting. {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist}, {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist}, @@ -219,6 +221,35 @@ export default class Component extends BaseComponent { fakeelement.parentNode.replaceChild(newelement, fakeelement); } + /** + * Create a new section instance. + * + * @param {Object} details the update details. + * @param {Object} details.state the state data. + * @param {Object} details.element the element data. + */ + async _createSection({state, element}) { + // Create a fake node while the component is loading. + const fakeelement = document.createElement('div'); + fakeelement.classList.add('bg-pulse-grey', 'w-100'); + fakeelement.innerHTML = ' '; + this.sections[element.id] = fakeelement; + // Place the fake node on the correct position. + this._refreshCourseSectionlist({ + state, + element: state.course, + }); + // Collect render data. + const exporter = this.reactive.getExporter(); + const data = exporter.section(state, element); + // Create the new content. + const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/section', data); + // Replace the fake node with the real content. + const newelement = newcomponent.getElement(); + this.sections[element.id] = newelement; + fakeelement.parentNode.replaceChild(newelement, fakeelement); + } + /** * Refresh a section cm list. * @@ -291,4 +322,16 @@ export default class Component extends BaseComponent { _deleteCm({element}) { delete this.cms[element.id]; } + + /** + * Remove a section from the list. + * + * The actual DOM element removal is delegated to the section component. + * + * @param {Object} details the update details. + * @param {Object} details.element the element data. + */ + _deleteSection({element}) { + delete this.sections[element.id]; + } } diff --git a/course/format/amd/src/local/courseindex/section.js b/course/format/amd/src/local/courseindex/section.js index d76c56a830d..cde594d60dd 100644 --- a/course/format/amd/src/local/courseindex/section.js +++ b/course/format/amd/src/local/courseindex/section.js @@ -92,6 +92,7 @@ export default class Component extends DndSection { */ getWatchers() { return [ + {watch: `section[${this.id}]:deleted`, handler: this.remove}, {watch: `section[${this.id}]:updated`, handler: this._refreshSection}, ]; } diff --git a/course/format/classes/base.php b/course/format/classes/base.php index 1cfc6279eeb..98b95c7c9b9 100644 --- a/course/format/classes/base.php +++ b/course/format/classes/base.php @@ -1504,20 +1504,8 @@ abstract class base { $course = $this->get_course(); $coursecontext = context_course::instance($course->id); - switch($action) { - case 'hide': - case 'show': - require_capability('moodle/course:sectionvisibility', $coursecontext); - $visible = ($action === 'hide') ? 0 : 1; - course_update_section($course, $section, array('visible' => $visible)); - break; - default: - throw new moodle_exception('sectionactionnotsupported', 'core', null, s($action)); - } - - $modules = []; - $modinfo = $this->get_modinfo(); + $renderer = $this->get_renderer($PAGE); if (!($section instanceof section_info)) { $section = $modinfo->get_section_info($section->section); @@ -1527,9 +1515,24 @@ abstract class base { $this->set_section_number($sr); } - // Load the cmlist output. - $renderer = $this->get_renderer($PAGE); + switch($action) { + case 'hide': + case 'show': + require_capability('moodle/course:sectionvisibility', $coursecontext); + $visible = ($action === 'hide') ? 0 : 1; + course_update_section($course, $section, array('visible' => $visible)); + break; + case 'refresh': + return [ + 'content' => $renderer->course_section_updated($this, $section), + ]; + default: + throw new moodle_exception('sectionactionnotsupported', 'core', null, s($action)); + } + $modules = []; + + // Load the cmlist output. $coursesections = $modinfo->sections; if (array_key_exists($section->section, $coursesections)) { foreach ($coursesections[$section->section] as $cmid) { diff --git a/course/format/classes/output/local/content.php b/course/format/classes/output/local/content.php index 038818d79a8..af6fbf6bb30 100644 --- a/course/format/classes/output/local/content.php +++ b/course/format/classes/output/local/content.php @@ -54,6 +54,9 @@ class content implements renderable, templatable { /** @var string section selector class name */ protected $sectionselectorclass; + /** @var bool if uses add section */ + protected $hasaddsection = true; + /** * Constructor. * @@ -78,8 +81,6 @@ class content implements renderable, templatable { public function export_for_template(\renderer_base $output) { $format = $this->format; - $addsection = new $this->addsectionclass($format); - // Most formats uses section 0 as a separate section so we remove from the list. $sections = $this->export_sections($output); $initialsection = ''; @@ -91,8 +92,8 @@ class content implements renderable, templatable { 'title' => $format->page_title(), // This method should be in the course_format class. 'initialsection' => $initialsection, 'sections' => $sections, - 'numsections' => $addsection->export_for_template($output), 'format' => $format->get_format(), + 'sectionreturn' => 0, ]; // The single section format has extra navigation. @@ -106,6 +107,12 @@ class content implements renderable, templatable { $data->hasnavigation = true; $data->singlesection = array_shift($data->sections); + $data->sectionreturn = $singlesection; + } + + if ($this->hasaddsection) { + $addsection = new $this->addsectionclass($format); + $data->numsections = $addsection->export_for_template($output); } return $data; diff --git a/course/format/classes/output/local/content/addsection.php b/course/format/classes/output/local/content/addsection.php index 23b2cd4e235..e7ba7f1aef8 100644 --- a/course/format/classes/output/local/content/addsection.php +++ b/course/format/classes/output/local/content/addsection.php @@ -73,6 +73,9 @@ class addsection implements renderable, templatable { return $data; } + // Component based formats handle add section button in the frontend. + $show = ($lastsection < $maxsections) || course_get_format($course)->supports_components(); + $supportsnumsections = array_key_exists('numsections', $options); if ($supportsnumsections) { // Current course format has 'numsections' option, which is very confusing and we suggest course format @@ -96,7 +99,7 @@ class addsection implements renderable, templatable { ]; } - } else if (course_get_format($course)->uses_sections() && $lastsection < $maxsections) { + } else if (course_get_format($course)->uses_sections() && $show) { // Current course format does not have 'numsections' option but it has multiple sections suppport. // Display the "Add section" link that will insert a section in the end. // Note to course format developers: inserting sections in the other positions should check both diff --git a/course/format/classes/output/local/content/section.php b/course/format/classes/output/local/content/section.php index 446aa2ab3b5..bc21974bc14 100644 --- a/course/format/classes/output/local/content/section.php +++ b/course/format/classes/output/local/content/section.php @@ -132,6 +132,7 @@ class section implements renderable, templatable { 'num' => $thissection->section ?? '0', 'id' => $thissection->id, 'sectionreturnid' => $singlesection, + 'insertafter' => false, 'summary' => $summary->export_for_template($output), 'availability' => $availability->export_for_template($output), ]; diff --git a/course/format/classes/output/local/content/section/controlmenu.php b/course/format/classes/output/local/content/section/controlmenu.php index 9f6b91a72fb..d556e89a1ef 100644 --- a/course/format/classes/output/local/content/section/controlmenu.php +++ b/course/format/classes/output/local/content/section/controlmenu.php @@ -255,7 +255,11 @@ class controlmenu implements renderable, templatable { 'icon' => 'i/delete', 'name' => $strdelete, 'pixattr' => ['class' => ''], - 'attr' => ['class' => 'icon editing_delete'], + 'attr' => [ + 'class' => 'icon editing_delete', + 'data-action' => 'deleteSection', + 'data-id' => $section->id, + ], ]; } } diff --git a/course/format/classes/output/local/state/course.php b/course/format/classes/output/local/state/course.php index cd936cf71ff..6737f6cda90 100644 --- a/course/format/classes/output/local/state/course.php +++ b/course/format/classes/output/local/state/course.php @@ -17,6 +17,7 @@ namespace core_courseformat\output\local\state; use core_courseformat\base as course_format; +use course_modinfo; use renderable; use stdClass; @@ -50,7 +51,8 @@ class course implements renderable { public function export_for_template(\renderer_base $output): stdClass { $format = $this->format; $course = $format->get_course(); - $modinfo = $this->format->get_modinfo(); + // State must represent always the most updated version of the course. + $modinfo = course_modinfo::instance($course); $data = (object)[ 'id' => $course->id, @@ -58,6 +60,7 @@ class course implements renderable { 'sectionlist' => [], 'editmode' => $format->show_editor(), 'highlighted' => $format->get_section_highlighted_name(), + 'maxsections' => $format->get_max_sections(), ]; $sections = $modinfo->get_section_info_all(); diff --git a/course/format/classes/output/local/state/section.php b/course/format/classes/output/local/state/section.php index fe5e4dc5798..afabd2cc8f1 100644 --- a/course/format/classes/output/local/state/section.php +++ b/course/format/classes/output/local/state/section.php @@ -78,6 +78,7 @@ class section implements renderable { 'section' => $section->section, 'number' => $section->section, 'title' => $format->get_section_name($section), + 'hassummary' => !empty($section->summary), 'rawtitle' => $section->name, 'cmlist' => [], 'visible' => !empty($section->visible), diff --git a/course/format/classes/output/section_renderer.php b/course/format/classes/output/section_renderer.php index 5d638e1cc6f..ab911b56a30 100644 --- a/course/format/classes/output/section_renderer.php +++ b/course/format/classes/output/section_renderer.php @@ -166,6 +166,7 @@ abstract class section_renderer extends core_course_renderer { * @param section_info $section the section info * @param cm_info $cm the course module ionfo * @param array $displayoptions optional extra display options + * @return string the rendered element */ public function course_section_updated_cm_item( course_format $format, @@ -179,6 +180,28 @@ abstract class section_renderer extends core_course_renderer { return $this->render($cmitem); } + /** + * Get the updated rendered version of a section. + * + * This method will only be used when the course editor requires to get an updated cm item HTML + * to perform partial page refresh. It will be used for supporting the course editor webservices. + * + * By default, the template used for update a section is the same as when it renders initially, + * but format plugins are free to override this method to provide extra effects or so. + * + * @param course_format $format the course format + * @param section_info $section the section info + * @return string the rendered element + */ + public function course_section_updated( + course_format $format, + section_info $section + ): string { + $sectionclass = $format->get_output_classname('content\\section'); + $output = new $sectionclass($format, $section); + return $this->render($output); + } + /** * Get the course index drawer with placeholder. * diff --git a/course/format/classes/stateactions.php b/course/format/classes/stateactions.php index c42d463ac62..8c6995cce68 100644 --- a/course/format/classes/stateactions.php +++ b/course/format/classes/stateactions.php @@ -163,6 +163,99 @@ class stateactions { $updates->add_course_put(); } + /** + * Create a course section. + * + * This method follows the same logic as changenumsections.php. + * + * @param stateupdates $updates the affected course elements track + * @param stdClass $course the course object + * @param int[] $ids not used + * @param int $targetsectionid optional target section id (if not passed section will be appended) + * @param int $targetcmid not used + */ + public function section_add( + stateupdates $updates, + stdClass $course, + array $ids = [], + ?int $targetsectionid = null, + ?int $targetcmid = null + ): void { + + $coursecontext = context_course::instance($course->id); + require_capability('moodle/course:update', $coursecontext); + + // Get course format settings. + $format = course_get_format($course->id); + $lastsectionnumber = $format->get_last_section_number(); + $maxsections = $format->get_max_sections(); + + if ($lastsectionnumber >= $maxsections) { + throw new moodle_exception('maxsectionslimit', 'moodle', $maxsections); + } + + $modinfo = get_fast_modinfo($course); + + // Get target section. + if ($targetsectionid) { + $this->validate_sections($course, [$targetsectionid], __FUNCTION__); + $targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST); + // Inserting sections at any position except in the very end requires capability to move sections. + require_capability('moodle/course:movesections', $coursecontext); + $insertposition = $targetsection->section + 1; + } else { + // Get last section. + $insertposition = 0; + } + + course_create_section($course, $insertposition); + + // Adding a section affects the full course structure. + $this->course_state($updates, $course); + } + + /** + * Delete course sections. + * + * This method follows the same logic as editsection.php. + * + * @param stateupdates $updates the affected course elements track + * @param stdClass $course the course object + * @param int[] $ids section ids + * @param int $targetsectionid not used + * @param int $targetcmid not used + */ + public function section_delete( + stateupdates $updates, + stdClass $course, + array $ids = [], + ?int $targetsectionid = null, + ?int $targetcmid = null + ): void { + + $coursecontext = context_course::instance($course->id); + require_capability('moodle/course:update', $coursecontext); + require_capability('moodle/course:movesections', $coursecontext); + + $modinfo = get_fast_modinfo($course); + + foreach ($ids as $sectionid) { + $section = $modinfo->get_section_info_by_id($sectionid, MUST_EXIST); + // Send all activity deletions. + if (!empty($modinfo->sections[$section->section])) { + foreach ($modinfo->sections[$section->section] as $modnumber) { + $cm = $modinfo->cms[$modnumber]; + $updates->add_cm_delete($cm->id); + } + } + course_delete_section($course, $section, true, true); + $updates->add_section_delete($sectionid); + } + + // Removing a section affects the full course structure. + $this->course_state($updates, $course); + } + /** * Extract several cm_info from the course_modinfo. * diff --git a/course/format/classes/stateupdates.php b/course/format/classes/stateupdates.php index 7cf2d530c82..e4922e449f6 100644 --- a/course/format/classes/stateupdates.php +++ b/course/format/classes/stateupdates.php @@ -130,7 +130,7 @@ class stateupdates implements JsonSerializable { * @param int $sectionid The affected section id. */ public function add_section_delete(int $sectionid): void { - $this->add_update('section', 'delete', (object)['id' => $sectionid]); + $this->add_update('section', 'remove', (object)['id' => $sectionid]); } /** @@ -179,7 +179,7 @@ class stateupdates implements JsonSerializable { * @param int $cmid the affected course module id */ public function add_cm_delete(int $cmid): void { - $this->add_update('cm', 'delete', (object)['id' => $cmid]); + $this->add_update('cm', 'remove', (object)['id' => $cmid]); } /** diff --git a/course/format/templates/local/content.mustache b/course/format/templates/local/content.mustache index b36d7be1918..cf0714433f9 100644 --- a/course/format/templates/local/content.mustache +++ b/course/format/templates/local/content.mustache @@ -127,6 +127,7 @@ "nextname": "Section 5", "selector": "" }, + "sectionreturn": 1, "singlesection": { "num": 1, "id": 35, @@ -183,6 +184,6 @@ {{#js}} require(['core_courseformat/local/content'], function(component) { - component.init('{{uniqid}}-course-format'); + component.init('{{uniqid}}-course-format', {}, {{sectionreturn}}); }); {{/js}} diff --git a/course/format/templates/local/content/addsection.mustache b/course/format/templates/local/content/addsection.mustache index 7fcaecff643..2b485b105fe 100644 --- a/course/format/templates/local/content/addsection.mustache +++ b/course/format/templates/local/content/addsection.mustache @@ -22,6 +22,9 @@ Example context (json): { "showaddsection": true, + "id": 42, + "insertafter": true, + "num": 0, "increase": { "url": "#" }, @@ -48,7 +51,14 @@
{{/decrease}} {{#addsections}} - + {{#pix}} t/add, moodle {{/pix}} {{title}} {{/addsections}} diff --git a/course/format/templates/local/content/section.mustache b/course/format/templates/local/content/section.mustache index fd442390689..e7ff168e683 100644 --- a/course/format/templates/local/content/section.mustache +++ b/course/format/templates/local/content/section.mustache @@ -74,6 +74,8 @@ "iscoursedisplaymultipage": true, "sectionreturnid": 0, "contentexpanded": true, + "insertafter": true, + "numsections": 42, "sitehome": false } }} @@ -110,5 +112,10 @@ {{#cmsummary}} {{> core_courseformat/local/content/section/cmsummary }} {{/cmsummary}} {{#cmlist}} {{> core_courseformat/local/content/section/cmlist }} {{/cmlist}} {{{cmcontrols}}} + {{#insertafter}} + {{#numsections}} + {{> core_courseformat/local/content/addsection}} + {{/numsections}} + {{/insertafter}} diff --git a/course/format/tests/behat/content_preferences.feature b/course/format/tests/behat/content_preferences.feature index 2e085751beb..82caaad2604 100644 --- a/course/format/tests/behat/content_preferences.feature +++ b/course/format/tests/behat/content_preferences.feature @@ -68,7 +68,7 @@ Feature: Course content collapsed user preferences And I click on "#collapssesection4" "css_element" And I turn editing mode on And I delete section "1" - And I press "Delete" + And I click on "Delete" "button" in the ".modal" "css_element" And I should not see "Activity sample 1" in the "region-main" "region" And I should see "Activity sample 2" in the "region-main" "region" And I should see "Activity sample 3" in the "region-main" "region" diff --git a/course/format/tests/behat/course_courseindex.feature b/course/format/tests/behat/course_courseindex.feature index 58b95a07814..55c831d7d1d 100644 --- a/course/format/tests/behat/course_courseindex.feature +++ b/course/format/tests/behat/course_courseindex.feature @@ -203,10 +203,39 @@ Feature: Course index depending on role # Delete section 1 And I turn editing mode on And I delete section "1" - And I click on "Delete" "button" + And I click on "Delete" "button" in the ".modal" "css_element" And I reload the page And I should not see "Activity sample 1" in the "courseindex-content" "region" And I should see "Topic 1" in the "courseindex-content" "region" And I should see "Activity sample 2" in the "courseindex-content" "region" And I should see "Topic 2" in the "courseindex-content" "region" And I should not see "Activity sample 3" in the "courseindex-content" "region" + + @javascript + Scenario: Adding section should alter the course index + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I click on "Side panel" "button" + And I click on "Open course index drawer" "button" + When I click on "Add topic after" "link" in the "Topic 4" "section" + Then I should see "Topic 5" in the "courseindex-content" "region" + + @javascript + Scenario: Remove a section should alter the course index + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I click on "Side panel" "button" + And I click on "Open course index drawer" "button" + When I delete section "4" + Then I should not see "Topic 4" in the "courseindex-content" "region" + + @javascript + Scenario: Delete a previous section should alter the course index unnamed sections + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I click on "Side panel" "button" + And I click on "Open course index drawer" "button" + When I delete section "1" + And I click on "Delete" "button" in the ".modal" "css_element" + Then I should not see "Topic 4" in the "courseindex-content" "region" + And I should not see "Activity sample 1" in the "courseindex-content" "region" diff --git a/course/format/topics/classes/output/courseformat/content.php b/course/format/topics/classes/output/courseformat/content.php new file mode 100644 index 00000000000..8d3e6308108 --- /dev/null +++ b/course/format/topics/classes/output/courseformat/content.php @@ -0,0 +1,45 @@ +. + +/** + * Contains the default content output class. + * + * @package format_topics + * @copyright 2020 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace format_topics\output\courseformat; + +use core_courseformat\output\local\content as content_base; + +/** + * Base class to render a course content. + * + * @package format_topics + * @copyright 2020 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class content extends content_base { + + /** + * @var bool Topic format has add section after each topic. + * + * The responsible for the buttons is core_courseformat\output\local\content\section. + */ + protected $hasaddsection = false; + +} diff --git a/course/format/topics/classes/output/courseformat/content/section.php b/course/format/topics/classes/output/courseformat/content/section.php new file mode 100644 index 00000000000..c4a8ac1a23b --- /dev/null +++ b/course/format/topics/classes/output/courseformat/content/section.php @@ -0,0 +1,57 @@ +. + +/** + * Contains the default section controls output class. + * + * @package format_topics + * @copyright 2020 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace format_topics\output\courseformat\content; + +use core_courseformat\base as course_format; +use core_courseformat\output\local\content\section as section_base; +use stdClass; + +/** + * Base class to render a course section. + * + * @package format_topics + * @copyright 2020 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class section extends section_base { + + /** @var course_format the course format */ + protected $format; + + public function export_for_template(\renderer_base $output): stdClass { + $format = $this->format; + + $data = parent::export_for_template($output); + + if (!$this->format->get_section_number()) { + $addsectionclass = $format->get_output_classname('content\\addsection'); + $addsection = new $addsectionclass($format); + $data->numsections = $addsection->export_for_template($output); + $data->insertafter = true; + } + + return $data; + } +} diff --git a/course/format/topics/lang/en/format_topics.php b/course/format/topics/lang/en/format_topics.php index 6f5306bfee3..452cc59183d 100644 --- a/course/format/topics/lang/en/format_topics.php +++ b/course/format/topics/lang/en/format_topics.php @@ -22,7 +22,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -$string['addsections'] = 'Add topics'; +$string['addsections'] = 'Add topic after'; $string['currentsection'] = 'This topic'; $string['editsection'] = 'Edit topic'; $string['editsectionname'] = 'Edit topic name'; diff --git a/course/format/topics/tests/behat/edit_delete_sections.feature b/course/format/topics/tests/behat/edit_delete_sections.feature index 573c7e50fcd..d52c5309958 100644 --- a/course/format/topics/tests/behat/edit_delete_sections.feature +++ b/course/format/topics/tests/behat/edit_delete_sections.feature @@ -29,10 +29,11 @@ Feature: Sections can be edited and deleted in topics format And the field "New value for Section name" matches value "General" Scenario: Edit the default name of the general section in topics format + Given I should see "General" in the "General" "section" When I edit the section "0" and I fill the form with: | Custom | 1 | | New value for Section name | This is the general section | - Then I should see "This is the general section" in the "li#section-0" "css_element" + Then I should see "This is the general section" in the "This is the general section" "section" Scenario: View the default name of the second section in topics format When I edit the section "2" @@ -42,24 +43,24 @@ Feature: Sections can be edited and deleted in topics format Scenario: Edit section summary in topics format When I edit the section "2" and I fill the form with: | Summary | Welcome to section 2 | - Then I should see "Welcome to section 2" in the "li#section-2" "css_element" + Then I should see "Welcome to section 2" in the "Topic 2" "section" Scenario: Edit section default name in topics format When I edit the section "2" and I fill the form with: | Custom | 1 | | New value for Section name | This is the second topic | - Then I should see "This is the second topic" in the "li#section-2" "css_element" - And I should not see "Topic 2" in the "li#section-2" "css_element" + Then I should see "This is the second topic" in the "This is the second topic" "section" + And I should not see "Topic 2" in the "region-main" "region" @javascript Scenario: Inline edit section name in topics format - When I set the field "Edit topic name" in the "li#section-1" "css_element" to "Midterm evaluation" + When I set the field "Edit topic name" in the "Topic 1" "section" to "Midterm evaluation" Then I should not see "Topic 1" in the "region-main" "region" And "New name for topic" "field" should not exist - And I should see "Midterm evaluation" in the "li#section-1" "css_element" + And I should see "Midterm evaluation" in the "Midterm evaluation" "section" And I am on "Course 1" course homepage And I should not see "Topic 1" in the "region-main" "region" - And I should see "Midterm evaluation" in the "li#section-1" "css_element" + And I should see "Midterm evaluation" in the "Midterm evaluation" "section" Scenario: Deleting the last section in topics format When I delete section "5" @@ -73,20 +74,18 @@ Feature: Sections can be edited and deleted in topics format And I press "Delete" Then I should not see "Topic 5" And I should not see "Test chat name" - And I should see "Test choice name" in the "li#section-4" "css_element" + And I should see "Test choice name" in the "Topic 4" "section" And I should see "Topic 4" @javascript - Scenario: Adding sections in topics format - When I follow "Add topics" - Then the field "Number of sections" matches value "1" - And I press "Add topics" - And I should see "Topic 6" in the "li#section-6" "css_element" - And "li#section-7" "css_element" should not exist - And I follow "Add topics" - And I set the field "Number of sections" to "3" - And I press "Add topics" - And I should see "Topic 7" in the "li#section-7" "css_element" - And I should see "Topic 8" in the "li#section-8" "css_element" - And I should see "Topic 9" in the "li#section-9" "css_element" - And "li#section-10" "css_element" should not exist + Scenario: Adding sections at the end of a topics format + When I click on "Add topic after" "link" in the "Topic 5" "section" + Then I should see "Topic 6" in the "Topic 6" "section" + And I should see "Test choice name" in the "Topic 5" "section" + + @javascript + Scenario: Adding sections between topics in topics format + When I click on "Add topic after" "link" in the "Topic 4" "section" + Then I should see "Topic 6" in the "Topic 6" "section" + And I should not see "Test choice name" in the "Topic 5" "section" + And I should see "Test choice name" in the "Topic 6" "section" diff --git a/course/format/weeks/lang/en/format_weeks.php b/course/format/weeks/lang/en/format_weeks.php index e73e477df02..a649eb40ac6 100644 --- a/course/format/weeks/lang/en/format_weeks.php +++ b/course/format/weeks/lang/en/format_weeks.php @@ -23,7 +23,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -$string['addsections'] = 'Add weeks'; +$string['addsections'] = 'Add week'; $string['currentsection'] = 'This week'; $string['editsection'] = 'Edit week'; $string['editsectionname'] = 'Edit week name'; diff --git a/course/format/weeks/tests/behat/edit_delete_sections.feature b/course/format/weeks/tests/behat/edit_delete_sections.feature index 6fdc14aef26..4cd1416b85d 100644 --- a/course/format/weeks/tests/behat/edit_delete_sections.feature +++ b/course/format/weeks/tests/behat/edit_delete_sections.feature @@ -32,7 +32,7 @@ Feature: Sections can be edited and deleted in weeks format When I edit the section "0" and I fill the form with: | Custom | 1 | | New value for Section name | This is the general section | - Then I should see "This is the general section" in the "li#section-0" "css_element" + Then I should see "This is the general section" in the "This is the general section" "section" Scenario: View the default name of the second section in weeks format When I edit the section "2" @@ -42,28 +42,28 @@ Feature: Sections can be edited and deleted in weeks format Scenario: Edit section summary in weeks format When I edit the section "2" and I fill the form with: | Summary | Welcome to section 2 | - Then I should see "Welcome to section 2" in the "li#section-2" "css_element" + Then I should see "Welcome to section 2" in the "8 May - 14 May" "section" Scenario: Edit section default name in weeks format - Given I should see "8 May - 14 May" in the "li#section-2" "css_element" + Given I should see "8 May - 14 May" in the "8 May - 14 May" "section" When I edit the section "2" and I fill the form with: | Custom | 1 | | New value for Section name | This is the second week | - Then I should see "This is the second week" in the "li#section-2" "css_element" - And I should not see "8 May - 14 May" in the "li#section-2" "css_element" + Then I should see "This is the second week" in the "This is the second week" "section" + And I should not see "8 May - 14 May" @javascript Scenario: Inline edit section name in weeks format - When I set the field "Edit week name" in the "li#section-1" "css_element" to "Midterm evaluation" + When I set the field "Edit week name" in the "1 May - 7 May" "section" to "Midterm evaluation" Then I should not see "1 May - 7 May" in the "region-main" "region" And "New name for week" "field" should not exist - And I should see "Midterm evaluation" in the "li#section-1" "css_element" + And I should see "Midterm evaluation" in the "Midterm evaluation" "section" And I am on "Course 1" course homepage And I should not see "1 May - 7 May" in the "region-main" "region" - And I should see "Midterm evaluation" in the "li#section-1" "css_element" + And I should see "Midterm evaluation" in the "Midterm evaluation" "section" Scenario: Deleting the last section in weeks format - Given I should see "29 May - 4 June" in the "li#section-5" "css_element" + Given I should see "29 May - 4 June" in the "29 May - 4 June" "section" When I delete section "5" Then I should see "Are you absolutely sure you want to completely delete \"29 May - 4 June\" and all the activities it contains?" And I press "Delete" @@ -71,25 +71,15 @@ Feature: Sections can be edited and deleted in weeks format And I should see "22 May - 28 May" Scenario: Deleting the middle section in weeks format - Given I should see "29 May - 4 June" in the "li#section-5" "css_element" + Given I should see "29 May - 4 June" in the "29 May - 4 June" "section" When I delete section "4" And I press "Delete" Then I should not see "29 May - 4 June" And I should not see "Test chat name" - And I should see "Test choice name" in the "li#section-4" "css_element" + And I should see "Test choice name" in the "22 May - 28 May" "section" And I should see "22 May - 28 May" @javascript Scenario: Adding sections in weeks format - When I follow "Add weeks" - Then the field "Number of sections" matches value "1" - And I press "Add weeks" - And I should see "5 June - 11 June" in the "li#section-6" "css_element" - And "li#section-7" "css_element" should not exist - And I follow "Add weeks" - And I set the field "Number of sections" to "3" - And I press "Add weeks" - And I should see "12 June - 18 June" in the "li#section-7" "css_element" - And I should see "19 June - 25 June" in the "li#section-8" "css_element" - And I should see "26 June - 2 July" in the "li#section-9" "css_element" - And "li#section-10" "css_element" should not exist + When I follow "Add week" + Then I should see "5 June - 11 June" in the "5 June - 11 June" "section" diff --git a/lib/amd/build/local/reactive/basecomponent.min.js b/lib/amd/build/local/reactive/basecomponent.min.js index b854a6fb4f1..229e16eea47 100644 --- a/lib/amd/build/local/reactive/basecomponent.min.js +++ b/lib/amd/build/local/reactive/basecomponent.min.js @@ -1,2 +1,2 @@ -define ("core/local/reactive/basecomponent",["exports","core/templates"],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);function c(a,b){return h(a)||g(a,b)||e(a,b)||d()}function d(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function e(a,b){if(!a)return;if("string"==typeof a)return f(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return f(a,b)}function f(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ca.length)b=a.length;for(var c=0,d=Array(b);c.\n\nimport Templates from 'core/templates';\n\n/**\n * Reactive UI component base class.\n *\n * Each UI reactive component should extend this class to interact with a reactive state.\n *\n * @module core/local/reactive/basecomponent\n * @class core/local/reactive/basecomponent\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n /**\n * The component descriptor data structure.\n *\n * This structure is used by any component and init method to define the way the component will interact\n * with the interface and whith reactive instance operates. The logic behind this object is to avoid\n * unnecessary dependancies between the final interface and the state logic.\n *\n * Any component interacts with a single main DOM element (description.element) but it can use internal\n * selector to select elements within this main element (descriptor.selectors). By default each component\n * will provide it's own default selectors, but those can be overridden by the \"descriptor.selectors\"\n * property in case the mustache wants to reuse the same component logic but with a different interface.\n *\n * @typedef {object} descriptor\n * @property {Reactive} reactive an optional reactive module to register in\n * @property {DOMElement} element all components needs an element to anchor events\n * @property {object} [selectors] an optional object to override query selectors\n */\n\n /**\n * The class constructor.\n *\n * The only param this method gets is a constructor with all the mandatory\n * and optional component data. Component will receive the same descriptor\n * as create method param.\n *\n * This method will call the \"create\" method before registering the component into\n * the reactive module. This way any component can add default selectors and events.\n *\n * @param {descriptor} descriptor data to create the object.\n */\n constructor(descriptor) {\n\n if (descriptor.element === undefined || !(descriptor.element instanceof HTMLElement)) {\n throw Error(`Reactive components needs a main DOM element to dispatch events`);\n }\n\n this.element = descriptor.element;\n\n // Variable to track event listeners.\n this.eventHandlers = new Map([]);\n this.eventListeners = [];\n\n // Empty default component selectors.\n this.selectors = {};\n\n // Empty default event list from the static method.\n this.events = this.constructor.getEvents();\n\n // Call create function to get the component defaults.\n this.create(descriptor);\n\n // Overwrite the components selectors if necessary.\n if (descriptor.selectors !== undefined) {\n this.addSelectors(descriptor.selectors);\n }\n\n // Register into a reactive instance.\n if (descriptor.reactive === undefined) {\n // Ask parent components for registration.\n this.element.dispatchEvent(new CustomEvent(\n 'core/reactive:requestRegistration',\n {\n bubbles: true,\n detail: {component: this},\n }\n ));\n } else {\n this.reactive = descriptor.reactive;\n this.reactive.registerComponent(this);\n // Add a listener to register child components.\n this.addEventListener(\n this.element,\n 'core/reactive:requestRegistration',\n (event) => {\n if (event?.detail?.component) {\n event.stopPropagation();\n this.registerChildComponent(event.detail.component);\n }\n }\n );\n }\n }\n\n /**\n * Return the component custom event names.\n *\n * Components may override this method to provide their own events.\n *\n * Component custom events is an important part of component reusability. This function\n * is static because is part of the component definition and should be accessible from\n * outsite the instances. However, values will be available at instance level in the\n * this.events object.\n *\n * @returns {Object} the component events.\n */\n static getEvents() {\n return {};\n }\n\n /**\n * Component create function.\n *\n * Default init method will call \"create\" when all internal attributes are set\n * but before the component is not yet registered in the reactive module.\n *\n * In this method any component can define its own defaults such as:\n * - this.selectors {object} the default query selectors of this component.\n * - this.events {object} a list of event names this component dispatch\n * - extract any data from the main dom element (this.element)\n * - set any other data the component uses\n *\n * @param {descriptor} descriptor the component descriptor\n */\n // eslint-disable-next-line no-unused-vars\n create(descriptor) {\n // Components may override this method to initialize selects, events or other data.\n }\n\n /**\n * Component destroy hook.\n *\n * BaseComponent call this method when a component is unregistered or removed.\n *\n * Components may override this method to clean the HTML or do some action when the\n * component is unregistered or removed.\n */\n destroy() {\n // Components can override this method.\n }\n\n /**\n * Return the list of watchers that component has.\n *\n * Each watcher is represented by an object with two attributes:\n * - watch (string) the specific state event to watch. Example 'section.visible:updated'\n * - handler (function) the function to call when the watching state change happens\n *\n * Any component shoudl override this method to define their state watchers.\n *\n * @returns {array} array of watchers.\n */\n getWatchers() {\n return [];\n }\n\n /**\n * Reactive module will call this method when the state is ready.\n *\n * Component can override this method to update/load the component HTML or to bind\n * listeners to HTML entities.\n */\n stateReady() {\n // Components can override this method.\n }\n\n /**\n * Get the main DOM element of this component or a subelement.\n *\n * @param {string|undefined} query optional subelement query\n * @param {string|undefined} dataId optional data-id value\n * @returns {element|undefined} the DOM element (if any)\n */\n getElement(query, dataId) {\n if (query === undefined && dataId === undefined) {\n return this.element;\n }\n const dataSelector = (dataId) ? `[data-id='${dataId}']` : '';\n const selector = `${query ?? ''}${dataSelector}`;\n return this.element.querySelector(selector);\n }\n\n /**\n * Get the all subelement that match a query selector.\n *\n * @param {string|undefined} query optional subelement query\n * @param {string|undefined} dataId optional data-id value\n * @returns {NodeList} the DOM elements\n */\n getElements(query, dataId) {\n const dataSelector = (dataId) ? `[data-id='${dataId}']` : '';\n const selector = `${query ?? ''}${dataSelector}`;\n return this.element.querySelectorAll(selector);\n }\n\n /**\n * Add or update the component selectors.\n *\n * @param {Object} newSelectors an object of new selectors.\n */\n addSelectors(newSelectors) {\n for (const [selectorName, selector] of Object.entries(newSelectors)) {\n this.selectors[selectorName] = selector;\n }\n }\n\n /**\n * Return a component selector.\n *\n * @param {string} selectorName the selector name\n * @return {string|undefined} the query selector\n */\n getSelector(selectorName) {\n return this.selectors[selectorName];\n }\n\n /**\n * Dispatch a custom event on this.element.\n *\n * This is just a convenient method to dispatch custom events from within a component.\n * Components are free to use an alternative function to dispatch custom\n * events. The only restriction is that it should be dispatched on this.element\n * and specify \"bubbles:true\" to alert any component listeners.\n *\n * @param {string} eventName the event name\n * @param {*} detail event detail data\n */\n dispatchEvent(eventName, detail) {\n this.element.dispatchEvent(new CustomEvent(eventName, {\n bubbles: true,\n detail: detail,\n }));\n }\n\n /**\n * Render a new Component using a mustache file.\n *\n * It is important to note that this method should NOT be used for loading regular mustache files\n * as it returns a Promise that will only be resolved if the mustache registers a component instance.\n *\n * @param {element} target the DOM element that contains the component\n * @param {string} file the component mustache file to render\n * @param {*} data the mustache data\n * @return {Promise} a promise of the resulting component instance\n */\n renderComponent(target, file, data) {\n return new Promise((resolve, reject) => {\n target.addEventListener('ComponentRegistration:Success', ({detail}) => {\n resolve(detail.component);\n });\n target.addEventListener('ComponentRegistration:Fail', () => {\n reject(`Registration of ${file} fails.`);\n });\n Templates.renderForPromise(\n file,\n data\n ).then(({html, js}) => {\n Templates.replaceNodeContents(target, html, js);\n return true;\n }).catch(error => {\n reject(`Rendering of ${file} throws an error.`);\n throw error;\n });\n });\n }\n\n /**\n * Add and bind an event listener to a target and keep track of all event listeners.\n *\n * The native element.addEventListener method is not object oriented friently as the\n * \"this\" represents the element that triggers the event and not the listener class.\n * As components can be unregister and removed at any time, the BaseComponent provides\n * this method to keep track of all component listeners and do all of the bind stuff.\n *\n * @param {Element} target the event target\n * @param {string} type the event name\n * @param {function} listener the class method that recieve the event\n */\n addEventListener(target, type, listener) {\n\n // Check if we have the bind version of that listener.\n let bindListener = this.eventHandlers.get(listener);\n\n if (bindListener === undefined) {\n bindListener = listener.bind(this);\n this.eventHandlers.set(listener, bindListener);\n }\n\n target.addEventListener(type, bindListener);\n\n // Keep track of all component event listeners in case we need to remove them.\n this.eventListeners.push({\n target,\n type,\n bindListener,\n });\n\n }\n\n /**\n * Remove an event listener from a component.\n *\n * This method allows components to remove listeners without keeping track of the\n * listeners bind versions of the method. Both addEventListener and removeEventListener\n * keeps internally the relation between the original class method and the bind one.\n *\n * @param {Element} target the event target\n * @param {string} type the event name\n * @param {function} listener the class method that recieve the event\n */\n removeEventListener(target, type, listener) {\n // Check if we have the bind version of that listener.\n let bindListener = this.eventHandlers.get(listener);\n\n if (bindListener === undefined) {\n // This listener has not been added.\n return;\n }\n\n target.removeEventListener(type, bindListener);\n }\n\n /**\n * Remove all event listeners from this component.\n *\n * This method is called also when the component is unregistered or removed.\n *\n * Note that only listeners registered with the addEventListener method\n * will be removed. Other manual listeners will keep active.\n */\n removeAllEventListeners() {\n this.eventListeners.forEach(({target, type, bindListener}) => {\n target.removeEventListener(type, bindListener);\n });\n this.eventListeners = [];\n }\n\n /**\n * Remove a previously rendered component instance.\n *\n * This method will remove the component HTML and unregister it from the\n * reactive module.\n */\n remove() {\n this.unregister();\n this.element.remove();\n }\n\n /**\n * Unregister the component from the reactive module.\n *\n * This method will disable the component logic, event listeners and watchers\n * but it won't remove any HTML created by the component. However, it will trigger\n * the destroy hook to allow the component to clean parts of the interface.\n */\n unregister() {\n this.reactive.unregisterComponent(this);\n this.removeAllEventListeners();\n this.destroy();\n }\n\n /**\n * Dispatch a component registration event to inform the parent node.\n *\n * The registration event is different from the rest of the component events because\n * is the only way in which components can communicate its existence to a possible parent.\n * Most components will be created by including a mustache file, child components\n * must emit a registration event to the parent DOM element to alert about the registration.\n */\n dispatchRegistrationSuccess() {\n // The registration event does not bubble because we just want to comunicate with the parentNode.\n // Otherwise, any component can get multiple registrations events and could not differentiate\n // between child components and grand child components.\n if (this.element.parentNode === undefined) {\n return;\n }\n // This custom element is captured by renderComponent method.\n this.element.parentNode.dispatchEvent(new CustomEvent(\n 'ComponentRegistration:Success',\n {\n bubbles: false,\n detail: {component: this},\n }\n ));\n }\n\n /**\n * Dispatch a component registration fail event to inform the parent node.\n *\n * As dispatchRegistrationSuccess, this method will communicate the registration fail to the\n * parent node to inform the possible parent component.\n */\n dispatchRegistrationFail() {\n if (this.element.parentNode === undefined) {\n return;\n }\n // This custom element is captured only by renderComponent method.\n this.element.parentNode.dispatchEvent(new CustomEvent(\n 'ComponentRegistration:Fail',\n {\n bubbles: false,\n detail: {component: this},\n }\n ));\n }\n\n /**\n * Register a child component into the reactive instance.\n *\n * @param {self} component the component to register.\n */\n registerChildComponent(component) {\n component.reactive = this.reactive;\n this.reactive.registerComponent(component);\n }\n\n /**\n * Set the lock value and locks or unlocks the element.\n *\n * @param {boolean} locked the new locked value\n */\n set locked(locked) {\n this.element.dataset.locked = locked ?? false;\n if (locked) {\n // Disable interactions.\n this.element.style.pointerEvents = 'none';\n this.element.style.userSelect = 'none';\n // Check if it is draggable.\n if (this.element.hasAttribute('draggable')) {\n this.element.setAttribute('draggable', false);\n }\n } else {\n // Reanable interactions.\n this.element.style.pointerEvents = null;\n this.element.style.userSelect = null;\n // Check if it was draggable.\n if (this.element.hasAttribute('draggable')) {\n this.element.setAttribute('draggable', true);\n }\n }\n }\n\n /**\n * Get the current locket value from the element.\n *\n * @return {boolean}\n */\n get locked() {\n return this.element.dataset.locked ?? false;\n }\n}\n"],"file":"basecomponent.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/reactive/basecomponent.js"],"names":["descriptor","element","HTMLElement","Error","eventHandlers","Map","eventListeners","selectors","events","constructor","getEvents","create","addSelectors","reactive","dispatchEvent","CustomEvent","bubbles","detail","component","registerComponent","addEventListener","event","stopPropagation","registerChildComponent","query","dataId","dataSelector","selector","querySelector","querySelectorAll","newSelectors","Object","entries","selectorName","eventName","target","file","data","Promise","resolve","reject","Templates","renderForPromise","then","html","js","replaceNodeContents","catch","error","type","listener","bindListener","get","bind","set","push","removeEventListener","forEach","unregister","remove","unregisterComponent","removeAllEventListeners","destroy","parentNode","locked","dataset","style","pointerEvents","userSelect","hasAttribute","setAttribute","setElementLocked","getElementLocked"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport Templates from 'core/templates';\n\n/**\n * Reactive UI component base class.\n *\n * Each UI reactive component should extend this class to interact with a reactive state.\n *\n * @module core/local/reactive/basecomponent\n * @class core/local/reactive/basecomponent\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n /**\n * The component descriptor data structure.\n *\n * This structure is used by any component and init method to define the way the component will interact\n * with the interface and whith reactive instance operates. The logic behind this object is to avoid\n * unnecessary dependancies between the final interface and the state logic.\n *\n * Any component interacts with a single main DOM element (description.element) but it can use internal\n * selector to select elements within this main element (descriptor.selectors). By default each component\n * will provide it's own default selectors, but those can be overridden by the \"descriptor.selectors\"\n * property in case the mustache wants to reuse the same component logic but with a different interface.\n *\n * @typedef {object} descriptor\n * @property {Reactive} reactive an optional reactive module to register in\n * @property {DOMElement} element all components needs an element to anchor events\n * @property {object} [selectors] an optional object to override query selectors\n */\n\n /**\n * The class constructor.\n *\n * The only param this method gets is a constructor with all the mandatory\n * and optional component data. Component will receive the same descriptor\n * as create method param.\n *\n * This method will call the \"create\" method before registering the component into\n * the reactive module. This way any component can add default selectors and events.\n *\n * @param {descriptor} descriptor data to create the object.\n */\n constructor(descriptor) {\n\n if (descriptor.element === undefined || !(descriptor.element instanceof HTMLElement)) {\n throw Error(`Reactive components needs a main DOM element to dispatch events`);\n }\n\n this.element = descriptor.element;\n\n // Variable to track event listeners.\n this.eventHandlers = new Map([]);\n this.eventListeners = [];\n\n // Empty default component selectors.\n this.selectors = {};\n\n // Empty default event list from the static method.\n this.events = this.constructor.getEvents();\n\n // Call create function to get the component defaults.\n this.create(descriptor);\n\n // Overwrite the components selectors if necessary.\n if (descriptor.selectors !== undefined) {\n this.addSelectors(descriptor.selectors);\n }\n\n // Register into a reactive instance.\n if (descriptor.reactive === undefined) {\n // Ask parent components for registration.\n this.element.dispatchEvent(new CustomEvent(\n 'core/reactive:requestRegistration',\n {\n bubbles: true,\n detail: {component: this},\n }\n ));\n } else {\n this.reactive = descriptor.reactive;\n this.reactive.registerComponent(this);\n // Add a listener to register child components.\n this.addEventListener(\n this.element,\n 'core/reactive:requestRegistration',\n (event) => {\n if (event?.detail?.component) {\n event.stopPropagation();\n this.registerChildComponent(event.detail.component);\n }\n }\n );\n }\n }\n\n /**\n * Return the component custom event names.\n *\n * Components may override this method to provide their own events.\n *\n * Component custom events is an important part of component reusability. This function\n * is static because is part of the component definition and should be accessible from\n * outsite the instances. However, values will be available at instance level in the\n * this.events object.\n *\n * @returns {Object} the component events.\n */\n static getEvents() {\n return {};\n }\n\n /**\n * Component create function.\n *\n * Default init method will call \"create\" when all internal attributes are set\n * but before the component is not yet registered in the reactive module.\n *\n * In this method any component can define its own defaults such as:\n * - this.selectors {object} the default query selectors of this component.\n * - this.events {object} a list of event names this component dispatch\n * - extract any data from the main dom element (this.element)\n * - set any other data the component uses\n *\n * @param {descriptor} descriptor the component descriptor\n */\n // eslint-disable-next-line no-unused-vars\n create(descriptor) {\n // Components may override this method to initialize selects, events or other data.\n }\n\n /**\n * Component destroy hook.\n *\n * BaseComponent call this method when a component is unregistered or removed.\n *\n * Components may override this method to clean the HTML or do some action when the\n * component is unregistered or removed.\n */\n destroy() {\n // Components can override this method.\n }\n\n /**\n * Return the list of watchers that component has.\n *\n * Each watcher is represented by an object with two attributes:\n * - watch (string) the specific state event to watch. Example 'section.visible:updated'\n * - handler (function) the function to call when the watching state change happens\n *\n * Any component shoudl override this method to define their state watchers.\n *\n * @returns {array} array of watchers.\n */\n getWatchers() {\n return [];\n }\n\n /**\n * Reactive module will call this method when the state is ready.\n *\n * Component can override this method to update/load the component HTML or to bind\n * listeners to HTML entities.\n */\n stateReady() {\n // Components can override this method.\n }\n\n /**\n * Get the main DOM element of this component or a subelement.\n *\n * @param {string|undefined} query optional subelement query\n * @param {string|undefined} dataId optional data-id value\n * @returns {element|undefined} the DOM element (if any)\n */\n getElement(query, dataId) {\n if (query === undefined && dataId === undefined) {\n return this.element;\n }\n const dataSelector = (dataId) ? `[data-id='${dataId}']` : '';\n const selector = `${query ?? ''}${dataSelector}`;\n return this.element.querySelector(selector);\n }\n\n /**\n * Get the all subelement that match a query selector.\n *\n * @param {string|undefined} query optional subelement query\n * @param {string|undefined} dataId optional data-id value\n * @returns {NodeList} the DOM elements\n */\n getElements(query, dataId) {\n const dataSelector = (dataId) ? `[data-id='${dataId}']` : '';\n const selector = `${query ?? ''}${dataSelector}`;\n return this.element.querySelectorAll(selector);\n }\n\n /**\n * Add or update the component selectors.\n *\n * @param {Object} newSelectors an object of new selectors.\n */\n addSelectors(newSelectors) {\n for (const [selectorName, selector] of Object.entries(newSelectors)) {\n this.selectors[selectorName] = selector;\n }\n }\n\n /**\n * Return a component selector.\n *\n * @param {string} selectorName the selector name\n * @return {string|undefined} the query selector\n */\n getSelector(selectorName) {\n return this.selectors[selectorName];\n }\n\n /**\n * Dispatch a custom event on this.element.\n *\n * This is just a convenient method to dispatch custom events from within a component.\n * Components are free to use an alternative function to dispatch custom\n * events. The only restriction is that it should be dispatched on this.element\n * and specify \"bubbles:true\" to alert any component listeners.\n *\n * @param {string} eventName the event name\n * @param {*} detail event detail data\n */\n dispatchEvent(eventName, detail) {\n this.element.dispatchEvent(new CustomEvent(eventName, {\n bubbles: true,\n detail: detail,\n }));\n }\n\n /**\n * Render a new Component using a mustache file.\n *\n * It is important to note that this method should NOT be used for loading regular mustache files\n * as it returns a Promise that will only be resolved if the mustache registers a component instance.\n *\n * @param {element} target the DOM element that contains the component\n * @param {string} file the component mustache file to render\n * @param {*} data the mustache data\n * @return {Promise} a promise of the resulting component instance\n */\n renderComponent(target, file, data) {\n return new Promise((resolve, reject) => {\n target.addEventListener('ComponentRegistration:Success', ({detail}) => {\n resolve(detail.component);\n });\n target.addEventListener('ComponentRegistration:Fail', () => {\n reject(`Registration of ${file} fails.`);\n });\n Templates.renderForPromise(\n file,\n data\n ).then(({html, js}) => {\n Templates.replaceNodeContents(target, html, js);\n return true;\n }).catch(error => {\n reject(`Rendering of ${file} throws an error.`);\n throw error;\n });\n });\n }\n\n /**\n * Add and bind an event listener to a target and keep track of all event listeners.\n *\n * The native element.addEventListener method is not object oriented friently as the\n * \"this\" represents the element that triggers the event and not the listener class.\n * As components can be unregister and removed at any time, the BaseComponent provides\n * this method to keep track of all component listeners and do all of the bind stuff.\n *\n * @param {Element} target the event target\n * @param {string} type the event name\n * @param {function} listener the class method that recieve the event\n */\n addEventListener(target, type, listener) {\n\n // Check if we have the bind version of that listener.\n let bindListener = this.eventHandlers.get(listener);\n\n if (bindListener === undefined) {\n bindListener = listener.bind(this);\n this.eventHandlers.set(listener, bindListener);\n }\n\n target.addEventListener(type, bindListener);\n\n // Keep track of all component event listeners in case we need to remove them.\n this.eventListeners.push({\n target,\n type,\n bindListener,\n });\n\n }\n\n /**\n * Remove an event listener from a component.\n *\n * This method allows components to remove listeners without keeping track of the\n * listeners bind versions of the method. Both addEventListener and removeEventListener\n * keeps internally the relation between the original class method and the bind one.\n *\n * @param {Element} target the event target\n * @param {string} type the event name\n * @param {function} listener the class method that recieve the event\n */\n removeEventListener(target, type, listener) {\n // Check if we have the bind version of that listener.\n let bindListener = this.eventHandlers.get(listener);\n\n if (bindListener === undefined) {\n // This listener has not been added.\n return;\n }\n\n target.removeEventListener(type, bindListener);\n }\n\n /**\n * Remove all event listeners from this component.\n *\n * This method is called also when the component is unregistered or removed.\n *\n * Note that only listeners registered with the addEventListener method\n * will be removed. Other manual listeners will keep active.\n */\n removeAllEventListeners() {\n this.eventListeners.forEach(({target, type, bindListener}) => {\n target.removeEventListener(type, bindListener);\n });\n this.eventListeners = [];\n }\n\n /**\n * Remove a previously rendered component instance.\n *\n * This method will remove the component HTML and unregister it from the\n * reactive module.\n */\n remove() {\n this.unregister();\n this.element.remove();\n }\n\n /**\n * Unregister the component from the reactive module.\n *\n * This method will disable the component logic, event listeners and watchers\n * but it won't remove any HTML created by the component. However, it will trigger\n * the destroy hook to allow the component to clean parts of the interface.\n */\n unregister() {\n this.reactive.unregisterComponent(this);\n this.removeAllEventListeners();\n this.destroy();\n }\n\n /**\n * Dispatch a component registration event to inform the parent node.\n *\n * The registration event is different from the rest of the component events because\n * is the only way in which components can communicate its existence to a possible parent.\n * Most components will be created by including a mustache file, child components\n * must emit a registration event to the parent DOM element to alert about the registration.\n */\n dispatchRegistrationSuccess() {\n // The registration event does not bubble because we just want to comunicate with the parentNode.\n // Otherwise, any component can get multiple registrations events and could not differentiate\n // between child components and grand child components.\n if (this.element.parentNode === undefined) {\n return;\n }\n // This custom element is captured by renderComponent method.\n this.element.parentNode.dispatchEvent(new CustomEvent(\n 'ComponentRegistration:Success',\n {\n bubbles: false,\n detail: {component: this},\n }\n ));\n }\n\n /**\n * Dispatch a component registration fail event to inform the parent node.\n *\n * As dispatchRegistrationSuccess, this method will communicate the registration fail to the\n * parent node to inform the possible parent component.\n */\n dispatchRegistrationFail() {\n if (this.element.parentNode === undefined) {\n return;\n }\n // This custom element is captured only by renderComponent method.\n this.element.parentNode.dispatchEvent(new CustomEvent(\n 'ComponentRegistration:Fail',\n {\n bubbles: false,\n detail: {component: this},\n }\n ));\n }\n\n /**\n * Register a child component into the reactive instance.\n *\n * @param {self} component the component to register.\n */\n registerChildComponent(component) {\n component.reactive = this.reactive;\n this.reactive.registerComponent(component);\n }\n\n /**\n * Set the lock value and locks or unlocks the element.\n *\n * @param {boolean} locked the new locked value\n */\n set locked(locked) {\n this.setElementLocked(this.element, locked);\n }\n\n /**\n * Get the current locked value from the element.\n *\n * @return {boolean}\n */\n get locked() {\n return this.getElementLocked(this.element);\n }\n\n /**\n * Lock/unlock an element.\n *\n * @param {Element} target the event target\n * @param {boolean} locked the new locked value\n */\n setElementLocked(target, locked) {\n target.dataset.locked = locked ?? false;\n if (locked) {\n // Disable interactions.\n target.style.pointerEvents = 'none';\n target.style.userSelect = 'none';\n // Check if it is draggable.\n if (target.hasAttribute('draggable')) {\n target.setAttribute('draggable', false);\n }\n } else {\n // Enable interactions.\n target.style.pointerEvents = null;\n target.style.userSelect = null;\n // Check if it was draggable.\n if (target.hasAttribute('draggable')) {\n target.setAttribute('draggable', true);\n }\n }\n }\n\n /**\n * Get the current locked value from the element.\n *\n * @param {Element} target the event target\n * @return {boolean}\n */\n getElementLocked(target) {\n return target.dataset.locked ?? false;\n }\n}\n"],"file":"basecomponent.min.js"} \ No newline at end of file diff --git a/lib/amd/build/local/reactive/statemanager.min.js b/lib/amd/build/local/reactive/statemanager.min.js index b1b6afca721..1a5ec9eb9e5 100644 --- a/lib/amd/build/local/reactive/statemanager.min.js +++ b/lib/amd/build/local/reactive/statemanager.min.js @@ -1,2 +1,2 @@ -define ("core/local/reactive/statemanager",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function b(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){b=function(a){return typeof a}}else{b=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return b(a)}function c(a,b,e){if("undefined"!=typeof Reflect&&Reflect.get){c=Reflect.get}else{c=function(a,b,c){var e=d(a,b);if(!e)return;var f=Object.getOwnPropertyDescriptor(e,b);if(f.get){return f.get.call(c)}return f.value}}return c(a,b,e||a)}function d(a,b){while(!Object.prototype.hasOwnProperty.call(a,b)){a=n(a);if(null===a)break}return a}function e(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)m(a,b)}function f(a){return function(){var b=n(a),c;if(k()){var d=n(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return g(this,c)}}function g(a,c){if(c&&("object"===b(c)||"function"==typeof c)){return c}return h(a)}function h(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function i(a){var b="function"==typeof Map?new Map:void 0;i=function(a){if(null===a||!l(a))return a;if("function"!=typeof a){throw new TypeError("Super expression must either be null or a function")}if("undefined"!=typeof b){if(b.has(a))return b.get(a);b.set(a,c)}function c(){return j(a,arguments,n(this).constructor)}c.prototype=Object.create(a.prototype,{constructor:{value:c,enumerable:!1,writable:!0,configurable:!0}});return m(c,a)};return i(a)}function j(){if(k()){j=Reflect.construct}else{j=function(b,c,d){var e=[null];e.push.apply(e,c);var a=Function.bind.apply(b,e),f=new a;if(d)m(f,d.prototype);return f}}return j.apply(null,arguments)}function k(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function l(a){return-1!==Function.toString.call(a).indexOf("[native code]")}function m(a,b){m=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return m(a,b)}function n(a){n=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return n(a)}function o(a,b){return t(a)||s(a,b)||q(a,b)||p()}function p(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function q(a,b){if(!a)return;if("string"==typeof a)return r(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return r(a,b)}function r(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ca.length)b=a.length;for(var c=0,d=Array(b);c.\n\n/**\n * Reactive simple state manager.\n *\n * The state manager contains the state data, trigger update events and\n * can lock and unlock the state data.\n *\n * This file contains the three main elements of the state manager:\n * - State manager: the public class to alter the state, dispatch events and process update messages.\n * - Proxy handler: a private class to keep track of the state object changes.\n * - StateMap class: a private class extending Map class that triggers event when a state list is modifed.\n *\n * @module core/local/reactive/stateManager\n * @class core/local/reactive/stateManager\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * State manager class.\n *\n * This class handle the reactive state and ensure only valid mutations can modify the state.\n * It also provide methods to apply batch state update messages (see processUpdates function doc\n * for more details on update messages).\n *\n * Implementing a deep state manager is complex and will require many frontend resources. To keep\n * the state fast and simple, the state can ONLY store two kind of data:\n * - Object with attributes\n * - Sets of objects with id attributes.\n *\n * This is an example of a valid state:\n *\n * {\n * course: {\n * name: 'course name',\n * shortname: 'courseshort',\n * sectionlist: [21, 34]\n * },\n * sections: [\n * {id: 21, name: 'Topic 1', visible: true},\n * {id: 34, name: 'Topic 2', visible: false,\n * ],\n * }\n *\n * The following cases are NOT allowed at a state ROOT level (throws an exception if they are assigned):\n * - Simple values (strings, boolean...).\n * - Arrays of simple values.\n * - Array of objects without ID attribute (all arrays will be converted to maps and requires an ID).\n *\n * Thanks to those limitations it can simplify the state update messages and the event names. If You\n * need to store simple data, just group them in an object.\n *\n * To grant any state change triggers the proper events, the class uses two private structures:\n * - proxy handler: any object stored in the state is proxied using this class.\n * - StateMap class: any object set in the state will be converted to StateMap using the\n * objects id attribute.\n */\nexport default class StateManager {\n\n /**\n * Create a basic reactive state store.\n *\n * The state manager is meant to work with native JS events. To ensure each reactive module can use\n * it in its own way, the parent element must provide a valid event dispatcher function and an optional\n * DOM element to anchor the event.\n *\n * @param {function} dispatchEvent the function to dispatch the custom event when the state changes.\n * @param {element} target the state changed custom event target (document if none provided)\n */\n constructor(dispatchEvent, target) {\n\n // The dispatch event function.\n /** @package */\n this.dispatchEvent = dispatchEvent;\n\n // The DOM container to trigger events.\n /** @package */\n this.target = target ?? document;\n\n // State can be altered freely until initial state is set.\n /** @package */\n this.readonly = false;\n\n // List of state changes pending to be published as events.\n /** @package */\n this.eventsToPublish = [];\n\n // The update state types functions.\n /** @package */\n this.updateTypes = {\n \"create\": this.defaultCreate.bind(this),\n \"update\": this.defaultUpdate.bind(this),\n \"delete\": this.defaultDelete.bind(this),\n \"put\": this.defaultPut.bind(this),\n \"override\": this.defaultOverride.bind(this),\n \"prepareFields\": this.defaultPrepareFields.bind(this),\n };\n\n // The state_loaded event is special because it only happens one but all components\n // may react to that state, even if they are registered after the setIinitialState.\n // For these reason we use a promise for that event.\n this.initialPromise = new Promise((resolve) => {\n const initialStateDone = (event) => {\n resolve(event.detail.state);\n };\n this.target.addEventListener('state:loaded', initialStateDone);\n });\n }\n\n /**\n * Loads the initial state.\n *\n * Note this method will trigger a state changed event with \"state:loaded\" actionname.\n *\n * The state mode will be set to read only when the initial state is loaded.\n *\n * @param {object} initialState\n */\n setInitialState(initialState) {\n\n if (this.state !== undefined) {\n throw Error('Initial state can only be initialized ones');\n }\n\n // Create the state object.\n const state = new Proxy({}, new Handler('state', this, true));\n for (const [prop, propValue] of Object.entries(initialState)) {\n state[prop] = propValue;\n }\n this.state = state;\n\n // When the state is loaded we can lock it to prevent illegal changes.\n this.readonly = true;\n\n this.dispatchEvent({\n action: 'state:loaded',\n state: this.state,\n }, this.target);\n }\n\n /**\n * Generate a promise that will be resolved when the initial state is loaded.\n *\n * In most cases the final state will be loaded using an ajax call. This is the reason\n * why states manager are created unlocked and won't be reactive until the initial state is set.\n *\n * @return {Promise} the resulting promise\n */\n getInitialPromise() {\n return this.initialPromise;\n }\n\n /**\n * Locks or unlocks the state to prevent illegal updates.\n *\n * Mutations use this method to modify the state. Once the state is updated, they must\n * block again the state.\n *\n * All changes done while the state is writable will be registered using registerStateAction.\n * When the state is set again to read only the method will trigger _publishEvents to communicate\n * changes to all watchers.\n *\n * @param {bool} readonly if the state is in read only mode enabled\n */\n setReadOnly(readonly) {\n\n this.readonly = readonly;\n\n let mode = 'off';\n\n // When the state is in readonly again is time to publish all pending events.\n if (this.readonly) {\n mode = 'on';\n this._publishEvents();\n }\n\n // Dispatch a read only event.\n this.dispatchEvent({\n action: `readmode:${mode}`,\n state: this.state,\n element: null,\n }, this.target);\n }\n\n /**\n * Add methods to process update state messages.\n *\n * The state manager provide a default update, create and delete methods. However,\n * some applications may require to override the default methods or even add new ones\n * like \"refresh\" or \"error\".\n *\n * @param {Object} newFunctions the new update types functions.\n */\n addUpdateTypes(newFunctions) {\n for (const [updateType, updateFunction] of Object.entries(newFunctions)) {\n if (typeof updateFunction === 'function') {\n this.updateTypes[updateType] = updateFunction.bind(newFunctions);\n }\n }\n }\n\n /**\n * Process a state updates array and do all the necessary changes.\n *\n * Note this method unlocks the state while it is executing and relocks it\n * when finishes.\n *\n * @param {array} updates\n * @param {Object} updateTypes optional functions to override the default update types.\n */\n processUpdates(updates, updateTypes) {\n if (!Array.isArray(updates)) {\n throw Error('State updates must be an array');\n }\n this.setReadOnly(false);\n updates.forEach((update) => {\n if (update.name === undefined) {\n throw Error('Missing state update name');\n }\n this.processUpdate(\n update.name,\n update.action,\n update.fields,\n updateTypes\n );\n });\n this.setReadOnly(true);\n }\n\n /**\n * Process a single state update.\n *\n * Note this method will not lock or unlock the state by itself.\n *\n * @param {string} updateName the state element to update\n * @param {string} action to action to perform\n * @param {object} fields the new data\n * @param {Object} updateTypes optional functions to override the default update types.\n */\n processUpdate(updateName, action, fields, updateTypes) {\n\n if (!fields) {\n throw Error('Missing state update fields');\n }\n\n if (updateTypes === undefined) {\n updateTypes = {};\n }\n\n action = action ?? 'update';\n\n const method = updateTypes[action] ?? this.updateTypes[action];\n\n if (method === undefined) {\n throw Error(`Unkown update action ${action}`);\n }\n\n // Some state data may require some cooking before sending to the\n // state. Reactive instances can overrdide the default fieldDefaults\n // method to add extra logic to all updates.\n const prepareFields = updateTypes.prepareFields ?? this.updateTypes.prepareFields;\n\n method(this, updateName, prepareFields(this, updateName, fields));\n }\n\n /**\n * Prepare fields for processing.\n *\n * This method is used to add default values or calculations from the frontend side.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n defaultPrepareFields(stateManager, updateName, fields) {\n return fields;\n }\n\n\n /**\n * Process a create state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultCreate(stateManager, updateName, fields) {\n\n let state = stateManager.state;\n\n // Create can be applied only to lists, not to objects.\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n\n /**\n * Process a delete state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultDelete(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (!current) {\n throw Error(`Inexistent ${updateName} ${fields.id}`);\n }\n\n // Process deletion.\n let state = stateManager.state;\n\n if (state[updateName] instanceof StateMap) {\n state[updateName].delete(fields.id);\n return;\n }\n delete state[updateName];\n }\n\n /**\n * Process a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultUpdate(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (!current) {\n throw Error(`Inexistent ${updateName} ${fields.id}`);\n }\n\n // Execute updates.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n }\n\n /**\n * Process a put state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultPut(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (current) {\n // Update attributes.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n } else {\n // Create new object.\n let state = stateManager.state;\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n }\n\n /**\n * Process an override state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultOverride(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (current) {\n // Remove any unnecessary fields.\n for (const [fieldName] of Object.entries(current)) {\n if (fields[fieldName] === undefined) {\n delete current[fieldName];\n }\n }\n // Update field.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n } else {\n // Create the element if not exists.\n let state = stateManager.state;\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n }\n\n /**\n * Get an element from the state or form an alternative state object.\n *\n * The altstate param is used by external update functions that gets the current\n * state as param.\n *\n * @param {String} name the state object name\n * @param {*} id and object id for state maps.\n * @return {Object|undefined} the state object found\n */\n get(name, id) {\n const state = this.state;\n\n let current = state[name];\n if (current instanceof StateMap) {\n if (id === undefined) {\n throw Error(`Missing id for ${name} state update`);\n }\n current = state[name].get(id);\n }\n\n return current;\n }\n\n /**\n * Register a state modification and generate the necessary events.\n *\n * This method is used mainly by proxy helpers to dispatch state change event.\n * However, mutations can use it to inform components about non reactive changes\n * in the state (only the two first levels of the state are reactive).\n *\n * Each action can produce several events:\n * - The specific attribute updated, created or deleter (example: \"cm.visible:updated\")\n * - The general state object updated, created or deleted (example: \"cm:updated\")\n * - If the element has an ID attribute, the specific event with id (example: \"cm[42].visible:updated\")\n * - If the element has an ID attribute, the general event with id (example: \"cm[42]:updated\")\n * - A generic state update event \"state:update\"\n *\n * @param {string} field the affected state field name\n * @param {string|null} prop the affecter field property (null if affect the full object)\n * @param {string} action the action done (created/updated/deleted)\n * @param {*} data the affected data\n */\n registerStateAction(field, prop, action, data) {\n\n let parentAction = 'updated';\n\n if (prop !== null) {\n this.eventsToPublish.push({\n eventName: `${field}.${prop}:${action}`,\n eventData: data,\n action,\n });\n } else {\n parentAction = action;\n }\n\n // Trigger extra events if the element has an ID attribute.\n if (data.id !== undefined) {\n if (prop !== null) {\n this.eventsToPublish.push({\n eventName: `${field}[${data.id}].${prop}:${action}`,\n eventData: data,\n action,\n });\n }\n this.eventsToPublish.push({\n eventName: `${field}[${data.id}]:${parentAction}`,\n eventData: data,\n action: parentAction,\n });\n }\n\n // Register the general change.\n this.eventsToPublish.push({\n eventName: `${field}:${parentAction}`,\n eventData: data,\n action: parentAction,\n });\n\n // Register state updated event.\n this.eventsToPublish.push({\n eventName: `state:updated`,\n eventData: data,\n action: 'updated',\n });\n }\n\n /**\n * Internal method to publish events.\n *\n * This is a private method, it will be invoked when the state is set back to read only mode.\n */\n _publishEvents() {\n const fieldChanges = this.eventsToPublish;\n this.eventsToPublish = [];\n\n // Dispatch a transaction start event.\n this.dispatchEvent({\n action: 'transaction:start',\n state: this.state,\n element: null,\n changes: fieldChanges,\n }, this.target);\n\n // State changes can be registered in any order. However it will avoid many\n // components errors if they are sorted to have creations-updates-deletes in case\n // some component needs to create or destroy DOM elements before updating them.\n fieldChanges.sort((a, b) => {\n const weights = {\n created: 0,\n updated: 1,\n deleted: 2,\n };\n const aweight = weights[a.action] ?? 0;\n const bweight = weights[b.action] ?? 0;\n // In case both have the same weight, the eventName length decide.\n if (aweight === bweight) {\n return a.eventName.length - b.eventName.length;\n }\n return aweight - bweight;\n });\n\n // List of the published events to prevent redundancies.\n let publishedEvents = new Set();\n\n fieldChanges.forEach((event) => {\n\n const eventkey = `${event.eventName}.${event.eventData.id ?? 0}`;\n\n if (!publishedEvents.has(eventkey)) {\n this.dispatchEvent({\n action: event.eventName,\n state: this.state,\n element: event.eventData\n }, this.target);\n\n publishedEvents.add(eventkey);\n }\n });\n\n // Dispatch a transaction end event.\n this.dispatchEvent({\n action: 'transaction:end',\n state: this.state,\n element: null,\n }, this.target);\n }\n}\n\n// Proxy helpers.\n\n/**\n * The proxy handler.\n *\n * This class will inform any value change directly to the state manager.\n *\n * The proxied variable will throw an error if it is altered when the state manager is\n * in read only mode.\n */\nclass Handler {\n\n /**\n * Class constructor.\n *\n * @param {string} name the variable name used for identify triggered actions\n * @param {StateManager} stateManager the state manager object\n * @param {boolean} proxyValues if new values must be proxied (used only at state root level)\n */\n constructor(name, stateManager, proxyValues) {\n this.name = name;\n this.stateManager = stateManager;\n this.proxyValues = proxyValues ?? false;\n }\n\n /**\n * Set trap to trigger events when the state changes.\n *\n * @param {object} obj the source object (not proxied)\n * @param {string} prop the attribute to set\n * @param {*} value the value to save\n * @param {*} receiver the proxied element to be attached to events\n * @returns {boolean} if the value is set\n */\n set(obj, prop, value, receiver) {\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${prop} value in ${this.name}.`);\n }\n\n // Check any data change.\n if (JSON.stringify(obj[prop]) === JSON.stringify(value)) {\n return true;\n }\n\n const action = (obj[prop] !== undefined) ? 'updated' : 'created';\n\n // Proxy value if necessary (used at state root level).\n if (this.proxyValues) {\n if (Array.isArray(value)) {\n obj[prop] = new StateMap(prop, this.stateManager).loadValues(value);\n } else {\n obj[prop] = new Proxy(value, new Handler(prop, this.stateManager));\n }\n } else {\n obj[prop] = value;\n }\n\n // If the state is not ready yet means the initial state is not yet loaded.\n if (this.stateManager.state === undefined) {\n return true;\n }\n\n this.stateManager.registerStateAction(this.name, prop, action, receiver);\n\n return true;\n }\n\n /**\n * Delete property trap to trigger state change events.\n *\n * @param {*} obj the affected object (not proxied)\n * @param {*} prop the prop to delete\n * @returns {boolean} if prop is deleted\n */\n deleteProperty(obj, prop) {\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to delete ${prop} in ${this.name}.`);\n }\n if (prop in obj) {\n\n delete obj[prop];\n\n this.stateManager.registerStateAction(this.name, prop, 'deleted', obj);\n }\n return true;\n }\n}\n\n/**\n * Class to add events dispatching to the JS Map class.\n *\n * When the state has a list of objects (with IDs) it will be converted into a StateMap.\n * StateMap is used almost in the same way as a regular JS map. Because all elements have an\n * id attribute, it has some specific methods:\n * - add: a convenient method to add an element without specifying the key (\"id\" attribute will be used as a key).\n * - loadValues: to add many elements at once wihout specifying keys (\"id\" attribute will be used).\n *\n * Apart, the main difference between regular Map and MapState is that this one will inform any change to the\n * state manager.\n */\nclass StateMap extends Map {\n\n /**\n * Create a reactive Map.\n *\n * @param {string} name the property name\n * @param {StateManager} stateManager the state manager\n * @param {iterable} iterable an iterable object to create the Map\n */\n constructor(name, stateManager, iterable) {\n // We don't have any \"this\" until be call super.\n super(iterable);\n this.name = name;\n this.stateManager = stateManager;\n }\n\n /**\n * Set an element into the map.\n *\n * Each value needs it's own id attribute. Objects without id will be rejected.\n * The function will throw an error if the value id and the key are not the same.\n *\n * @param {*} key the key to store\n * @param {*} value the value to store\n * @returns {Map} the resulting Map object\n */\n set(key, value) {\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${key} value in ${this.name}.`);\n }\n\n // Normalize keys as string to prevent json decoding errors.\n key = this.normalizeKey(key);\n\n this.checkValue(value);\n\n if (key === undefined || key === null) {\n throw Error('State lists keys cannot be null or undefined');\n }\n\n // ID is mandatory and should be the same as the key.\n if (this.normalizeKey(value.id) !== key) {\n throw new Error(`State error: ${this.name} list element ID (${value.id}) and key (${key}) mismatch`);\n }\n\n const action = (super.has(key)) ? 'updated' : 'created';\n\n // Save proxied data into the list.\n const result = super.set(key, new Proxy(value, new Handler(this.name, this.stateManager)));\n\n // If the state is not ready yet means the initial state is not yet loaded.\n if (this.stateManager.state === undefined) {\n return result;\n }\n\n this.stateManager.registerStateAction(this.name, null, action, super.get(key));\n\n return result;\n }\n\n /**\n * Check if a value is valid to be stored in a a State List.\n *\n * Only objects with id attribute can be stored in State lists.\n *\n * This method throws an error if the value is not valid.\n *\n * @param {object} value (with ID)\n */\n checkValue(value) {\n if (!typeof value === 'object' && value !== null) {\n throw Error('State lists can contain objects only');\n }\n\n if (value.id === undefined) {\n throw Error('State lists elements must contain at least an id attribute');\n }\n }\n\n /**\n * Return a normalized key value for state map.\n *\n * Regular maps uses strict key comparissons but state maps are indexed by ID.JSON conversions\n * and webservices sometimes do unexpected types conversions so we convert any integer key to string.\n *\n * @param {*} key the provided key\n * @returns {string}\n */\n normalizeKey(key) {\n return String(key).valueOf();\n }\n\n /**\n * Insert a new element int a list.\n *\n * Each value needs it's own id attribute. Objects withouts id will be rejected.\n *\n * @param {object} value the value to add (needs an id attribute)\n * @returns {Map} the resulting Map object\n */\n add(value) {\n this.checkValue(value);\n return this.set(value.id, value);\n }\n\n /**\n * Return a state map element.\n *\n * @param {*} key the element id\n * @return {Object}\n */\n get(key) {\n return super.get(this.normalizeKey(key));\n }\n\n /**\n * Check whether an element with the specified key exists or not.\n *\n * @param {*} key the key to find\n * @return {boolean}\n */\n has(key) {\n return super.has(this.normalizeKey(key));\n }\n\n /**\n * Delete an element from the map.\n *\n * @param {*} key\n * @returns {boolean}\n */\n delete(key) {\n // State maps uses only string keys to avoid strict comparisons.\n key = this.normalizeKey(key);\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${key} value in ${this.name}.`);\n }\n\n const previous = super.get(key);\n\n const result = super.delete(key);\n if (!result) {\n return result;\n }\n\n this.stateManager.registerStateAction(this.name, null, 'deleted', previous);\n\n return result;\n }\n\n /**\n * Return a suitable structure for JSON conversion.\n *\n * This function is needed because new values are compared in JSON. StateMap has Private\n * attributes which cannot be stringified (like this.stateManager which will produce an\n * infinite recursivity).\n *\n * @returns {array}\n */\n toJSON() {\n let result = [];\n this.forEach((value) => {\n result.push(value);\n });\n return result;\n }\n\n /**\n * Insert a full list of values using the id attributes as keys.\n *\n * This method is used mainly to initialize the list. Note each element is indexed by its \"id\" attribute.\n * This is a basic restriction of StateMap. All elements need an id attribute, otherwise it won't be saved.\n *\n * @param {iterable} values the values to load\n * @returns {StateMap} return the this value\n */\n loadValues(values) {\n values.forEach((data) => {\n this.checkValue(data);\n let key = data.id;\n let newvalue = new Proxy(data, new Handler(this.name, this.stateManager));\n this.set(key, newvalue);\n });\n return this;\n }\n}\n"],"file":"statemanager.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/reactive/statemanager.js"],"names":["StateManager","dispatchEvent","target","document","readonly","eventsToPublish","updateTypes","defaultCreate","bind","defaultUpdate","defaultDelete","defaultPut","defaultOverride","defaultRemove","defaultPrepareFields","initialPromise","Promise","resolve","addEventListener","initialStateDone","event","detail","state","initialState","Error","Proxy","Handler","Object","entries","prop","propValue","action","mode","_publishEvents","element","newFunctions","updateType","updateFunction","updates","Array","isArray","setReadOnly","forEach","update","name","processUpdate","fields","updateName","method","prepareFields","stateManager","StateMap","add","current","get","id","delete","fieldName","fieldValue","field","data","parentAction","push","eventName","eventData","fieldChanges","changes","sort","a","b","weights","created","updated","deleted","aweight","bweight","length","publishedEvents","Set","eventkey","has","proxyValues","obj","value","receiver","JSON","stringify","loadValues","registerStateAction","iterable","key","normalizeKey","checkValue","result","valueOf","set","previous","values","newvalue","Map"],"mappings":"gxHAuEqBA,CAAAA,C,YAYjB,WAAYC,CAAZ,CAA2BC,CAA3B,CAAmC,sBAI/B,KAAKD,aAAL,CAAqBA,CAArB,CAIA,KAAKC,MAAL,QAAcA,CAAd,WAAcA,CAAd,CAAcA,CAAd,CAAwBC,QAAxB,CAIA,KAAKC,QAAL,IAIA,KAAKC,eAAL,CAAuB,EAAvB,CAIA,KAAKC,WAAL,CAAmB,CACf,OAAU,KAAKC,aAAL,CAAmBC,IAAnB,CAAwB,IAAxB,CADK,CAEf,OAAU,KAAKC,aAAL,CAAmBD,IAAnB,CAAwB,IAAxB,CAFK,CAGf,OAAU,KAAKE,aAAL,CAAmBF,IAAnB,CAAwB,IAAxB,CAHK,CAIf,IAAO,KAAKG,UAAL,CAAgBH,IAAhB,CAAqB,IAArB,CAJQ,CAKf,SAAY,KAAKI,eAAL,CAAqBJ,IAArB,CAA0B,IAA1B,CALG,CAMf,OAAU,KAAKK,aAAL,CAAmBL,IAAnB,CAAwB,IAAxB,CANK,CAOf,cAAiB,KAAKM,oBAAL,CAA0BN,IAA1B,CAA+B,IAA/B,CAPF,CAAnB,CAaA,KAAKO,cAAL,CAAsB,GAAIC,CAAAA,OAAJ,CAAY,SAACC,CAAD,CAAa,CAI3C,CAAI,CAACf,MAAL,CAAYgB,gBAAZ,CAA6B,cAA7B,CAHyB,QAAnBC,CAAAA,gBAAmB,CAACC,CAAD,CAAW,CAChCH,CAAO,CAACG,CAAK,CAACC,MAAN,CAAaC,KAAd,CACV,CACD,CACH,CALqB,CAMzB,C,2DAWeC,C,CAAc,CAE1B,GAAI,KAAKD,KAAL,SAAJ,CAA8B,CAC1B,KAAME,CAAAA,KAAK,CAAC,4CAAD,CACd,CAID,OADMF,CAAAA,CAAK,CAAG,GAAIG,CAAAA,KAAJ,CAAU,EAAV,CAAc,GAAIC,CAAAA,CAAJ,CAAY,OAAZ,CAAqB,IAArB,IAAd,CACd,OAAgCC,MAAM,CAACC,OAAP,CAAeL,CAAf,CAAhC,gBAA8D,iBAAlDM,CAAkD,MAA5CC,CAA4C,MAC1DR,CAAK,CAACO,CAAD,CAAL,CAAcC,CACjB,CACD,KAAKR,KAAL,CAAaA,CAAb,CAGA,KAAKlB,QAAL,IAEA,KAAKH,aAAL,CAAmB,CACf8B,MAAM,CAAE,cADO,CAEfT,KAAK,CAAE,KAAKA,KAFG,CAAnB,CAGG,KAAKpB,MAHR,CAIH,C,6DAUmB,CAChB,MAAO,MAAKa,cACf,C,gDAcWX,C,CAAU,CAElB,KAAKA,QAAL,CAAgBA,CAAhB,CAEA,GAAI4B,CAAAA,CAAI,CAAG,KAAX,CAGA,GAAI,KAAK5B,QAAT,CAAmB,CACf4B,CAAI,CAAG,IAAP,CACA,KAAKC,cAAL,EACH,CAGD,KAAKhC,aAAL,CAAmB,CACf8B,MAAM,oBAAcC,CAAd,CADS,CAEfV,KAAK,CAAE,KAAKA,KAFG,CAGfY,OAAO,CAAE,IAHM,CAAnB,CAIG,KAAKhC,MAJR,CAKH,C,sDAWciC,C,CAAc,CACzB,cAA2CR,MAAM,CAACC,OAAP,CAAeO,CAAf,CAA3C,gBAAyE,iBAA7DC,CAA6D,MAAjDC,CAAiD,MACrE,GAA8B,UAA1B,QAAOA,CAAAA,CAAX,CAA0C,CACtC,KAAK/B,WAAL,CAAiB8B,CAAjB,EAA+BC,CAAc,CAAC7B,IAAf,CAAoB2B,CAApB,CAClC,CACJ,CACJ,C,sDAWcG,C,CAAShC,C,CAAa,YACjC,GAAI,CAACiC,KAAK,CAACC,OAAN,CAAcF,CAAd,CAAL,CAA6B,CACzB,KAAMd,CAAAA,KAAK,CAAC,gCAAD,CACd,CACD,KAAKiB,WAAL,KACAH,CAAO,CAACI,OAAR,CAAgB,SAACC,CAAD,CAAY,CACxB,GAAIA,CAAM,CAACC,IAAP,SAAJ,CAA+B,CAC3B,KAAMpB,CAAAA,KAAK,CAAC,2BAAD,CACd,CACD,CAAI,CAACqB,aAAL,CACIF,CAAM,CAACC,IADX,CAEID,CAAM,CAACZ,MAFX,CAGIY,CAAM,CAACG,MAHX,CAIIxC,CAJJ,CAMH,CAVD,EAWA,KAAKmC,WAAL,IACH,C,oDAYaM,C,CAAYhB,C,CAAQe,C,CAAQxC,C,CAAa,WAEnD,GAAI,CAACwC,CAAL,CAAa,CACT,KAAMtB,CAAAA,KAAK,CAAC,6BAAD,CACd,CAED,GAAIlB,CAAW,SAAf,CAA+B,CAC3BA,CAAW,CAAG,EACjB,CAEDyB,CAAM,WAAGA,CAAH,gBAAa,QAAnB,CAEA,GAAMiB,CAAAA,CAAM,WAAG1C,CAAW,CAACyB,CAAD,CAAd,gBAA0B,KAAKzB,WAAL,CAAiByB,CAAjB,CAAtC,CAEA,GAAIiB,CAAM,SAAV,CAA0B,CACtB,KAAMxB,CAAAA,KAAK,gCAAyBO,CAAzB,EACd,CAKD,GAAMkB,CAAAA,CAAa,WAAG3C,CAAW,CAAC2C,aAAf,gBAAgC,KAAK3C,WAAL,CAAiB2C,aAApE,CAEAD,CAAM,CAAC,IAAD,CAAOD,CAAP,CAAmBE,CAAa,CAAC,IAAD,CAAOF,CAAP,CAAmBD,CAAnB,CAAhC,CACT,C,kEAYoBI,C,CAAcH,C,CAAYD,C,CAAQ,CACnD,MAAOA,CAAAA,CACV,C,oDAUaI,C,CAAcH,C,CAAYD,C,CAAQ,CAE5C,GAAIxB,CAAAA,CAAK,CAAG4B,CAAY,CAAC5B,KAAzB,CAGA,GAAIA,CAAK,CAACyB,CAAD,CAAL,UAA6BI,CAAAA,CAAjC,CAA2C,CACvC7B,CAAK,CAACyB,CAAD,CAAL,CAAkBK,GAAlB,CAAsBN,CAAtB,EACA,MACH,CACDxB,CAAK,CAACyB,CAAD,CAAL,CAAoBD,CACvB,C,oDASaI,C,CAAcH,C,CAAYD,C,CAAQ,CAG5C,GAAIO,CAAAA,CAAO,CAAGH,CAAY,CAACI,GAAb,CAAiBP,CAAjB,CAA6BD,CAAM,CAACS,EAApC,CAAd,CACA,GAAI,CAACF,CAAL,CAAc,CACV,KAAM7B,CAAAA,KAAK,sBAAeuB,CAAf,aAA6BD,CAAM,CAACS,EAApC,EACd,CAGD,GAAIjC,CAAAA,CAAK,CAAG4B,CAAY,CAAC5B,KAAzB,CAEA,GAAIA,CAAK,CAACyB,CAAD,CAAL,UAA6BI,CAAAA,CAAjC,CAA2C,CACvC7B,CAAK,CAACyB,CAAD,CAAL,CAAkBS,MAAlB,CAAyBV,CAAM,CAACS,EAAhC,EACA,MACH,CACD,MAAOjC,CAAAA,CAAK,CAACyB,CAAD,CACf,C,oDASaG,C,CAAcH,C,CAAYD,C,CAAQ,CAG5C,GAAIO,CAAAA,CAAO,CAAGH,CAAY,CAACI,GAAb,CAAiBP,CAAjB,CAA6BD,CAAM,CAACS,EAApC,CAAd,CACA,GAAI,CAACF,CAAL,CAAc,CACV,MACH,CAGD,GAAI/B,CAAAA,CAAK,CAAG4B,CAAY,CAAC5B,KAAzB,CAEA,GAAIA,CAAK,CAACyB,CAAD,CAAL,UAA6BI,CAAAA,CAAjC,CAA2C,CACvC7B,CAAK,CAACyB,CAAD,CAAL,CAAkBS,MAAlB,CAAyBV,CAAM,CAACS,EAAhC,EACA,MACH,CACD,MAAOjC,CAAAA,CAAK,CAACyB,CAAD,CACf,C,oDASaG,C,CAAcH,C,CAAYD,C,CAAQ,CAG5C,GAAIO,CAAAA,CAAO,CAAGH,CAAY,CAACI,GAAb,CAAiBP,CAAjB,CAA6BD,CAAM,CAACS,EAApC,CAAd,CACA,GAAI,CAACF,CAAL,CAAc,CACV,KAAM7B,CAAAA,KAAK,sBAAeuB,CAAf,aAA6BD,CAAM,CAACS,EAApC,EACd,CAGD,cAAsC5B,MAAM,CAACC,OAAP,CAAekB,CAAf,CAAtC,gBAA8D,iBAAlDW,CAAkD,MAAvCC,CAAuC,MAC1DL,CAAO,CAACI,CAAD,CAAP,CAAqBC,CACxB,CACJ,C,8CASUR,C,CAAcH,C,CAAYD,C,CAAQ,CAGzC,GAAIO,CAAAA,CAAO,CAAGH,CAAY,CAACI,GAAb,CAAiBP,CAAjB,CAA6BD,CAAM,CAACS,EAApC,CAAd,CACA,GAAIF,CAAJ,CAAa,CAET,cAAsC1B,MAAM,CAACC,OAAP,CAAekB,CAAf,CAAtC,gBAA8D,iBAAlDW,CAAkD,MAAvCC,CAAuC,MAC1DL,CAAO,CAACI,CAAD,CAAP,CAAqBC,CACxB,CACJ,CALD,IAKO,CAEH,GAAIpC,CAAAA,CAAK,CAAG4B,CAAY,CAAC5B,KAAzB,CACA,GAAIA,CAAK,CAACyB,CAAD,CAAL,UAA6BI,CAAAA,CAAjC,CAA2C,CACvC7B,CAAK,CAACyB,CAAD,CAAL,CAAkBK,GAAlB,CAAsBN,CAAtB,EACA,MACH,CACDxB,CAAK,CAACyB,CAAD,CAAL,CAAoBD,CACvB,CACJ,C,wDASeI,C,CAAcH,C,CAAYD,C,CAAQ,CAG9C,GAAIO,CAAAA,CAAO,CAAGH,CAAY,CAACI,GAAb,CAAiBP,CAAjB,CAA6BD,CAAM,CAACS,EAApC,CAAd,CACA,GAAIF,CAAJ,CAAa,CAET,cAA0B1B,MAAM,CAACC,OAAP,CAAeyB,CAAf,CAA1B,gBAAmD,iBAAvCI,CAAuC,MAC/C,GAAIX,CAAM,CAACW,CAAD,CAAN,SAAJ,CAAqC,CACjC,MAAOJ,CAAAA,CAAO,CAACI,CAAD,CACjB,CACJ,CAED,cAAsC9B,MAAM,CAACC,OAAP,CAAekB,CAAf,CAAtC,gBAA8D,iBAAlDW,CAAkD,MAAvCC,CAAuC,MAC1DL,CAAO,CAACI,CAAD,CAAP,CAAqBC,CACxB,CACJ,CAXD,IAWO,CAEH,GAAIpC,CAAAA,CAAK,CAAG4B,CAAY,CAAC5B,KAAzB,CACA,GAAIA,CAAK,CAACyB,CAAD,CAAL,UAA6BI,CAAAA,CAAjC,CAA2C,CACvC7B,CAAK,CAACyB,CAAD,CAAL,CAAkBK,GAAlB,CAAsBN,CAAtB,EACA,MACH,CACDxB,CAAK,CAACyB,CAAD,CAAL,CAAoBD,CACvB,CACJ,C,gCAYGF,C,CAAMW,C,CAAI,IACJjC,CAAAA,CAAK,CAAG,KAAKA,KADT,CAGN+B,CAAO,CAAG/B,CAAK,CAACsB,CAAD,CAHT,CAIV,GAAIS,CAAO,WAAYF,CAAAA,CAAvB,CAAiC,CAC7B,GAAII,CAAE,SAAN,CAAsB,CAClB,KAAM/B,CAAAA,KAAK,0BAAmBoB,CAAnB,kBACd,CACDS,CAAO,CAAG/B,CAAK,CAACsB,CAAD,CAAL,CAAYU,GAAZ,CAAgBC,CAAhB,CACb,CAED,MAAOF,CAAAA,CACV,C,gEAqBmBM,C,CAAO9B,C,CAAME,C,CAAQ6B,C,CAAM,CAE3C,GAAIC,CAAAA,CAAY,CAAG,SAAnB,CAEA,GAAa,IAAT,GAAAhC,CAAJ,CAAmB,CACf,KAAKxB,eAAL,CAAqByD,IAArB,CAA0B,CACtBC,SAAS,WAAKJ,CAAL,aAAc9B,CAAd,aAAsBE,CAAtB,CADa,CAEtBiC,SAAS,CAAEJ,CAFW,CAGtB7B,MAAM,CAANA,CAHsB,CAA1B,CAKH,CAND,IAMO,CACH8B,CAAY,CAAG9B,CAClB,CAGD,GAAI6B,CAAI,CAACL,EAAL,SAAJ,CAA2B,CACvB,GAAa,IAAT,GAAA1B,CAAJ,CAAmB,CACf,KAAKxB,eAAL,CAAqByD,IAArB,CAA0B,CACtBC,SAAS,WAAKJ,CAAL,aAAcC,CAAI,CAACL,EAAnB,cAA0B1B,CAA1B,aAAkCE,CAAlC,CADa,CAEtBiC,SAAS,CAAEJ,CAFW,CAGtB7B,MAAM,CAANA,CAHsB,CAA1B,CAKH,CACD,KAAK1B,eAAL,CAAqByD,IAArB,CAA0B,CACtBC,SAAS,WAAKJ,CAAL,aAAcC,CAAI,CAACL,EAAnB,cAA0BM,CAA1B,CADa,CAEtBG,SAAS,CAAEJ,CAFW,CAGtB7B,MAAM,CAAE8B,CAHc,CAA1B,CAKH,CAGD,KAAKxD,eAAL,CAAqByD,IAArB,CAA0B,CACtBC,SAAS,WAAKJ,CAAL,aAAcE,CAAd,CADa,CAEtBG,SAAS,CAAEJ,CAFW,CAGtB7B,MAAM,CAAE8B,CAHc,CAA1B,EAOA,KAAKxD,eAAL,CAAqByD,IAArB,CAA0B,CACtBC,SAAS,gBADa,CAEtBC,SAAS,CAAEJ,CAFW,CAGtB7B,MAAM,CAAE,SAHc,CAA1B,CAKH,C,uDAOgB,YACPkC,CAAY,CAAG,KAAK5D,eADb,CAEb,KAAKA,eAAL,CAAuB,EAAvB,CAGA,KAAKJ,aAAL,CAAmB,CACf8B,MAAM,CAAE,mBADO,CAEfT,KAAK,CAAE,KAAKA,KAFG,CAGfY,OAAO,CAAE,IAHM,CAIfgC,OAAO,CAAED,CAJM,CAAnB,CAKG,KAAK/D,MALR,EAUA+D,CAAY,CAACE,IAAb,CAAkB,SAACC,CAAD,CAAIC,CAAJ,CAAU,SAClBC,CAAO,CAAG,CACZC,OAAO,CAAE,CADG,CAEZC,OAAO,CAAE,CAFG,CAGZC,OAAO,CAAE,CAHG,CADQ,CAMlBC,CAAO,WAAGJ,CAAO,CAACF,CAAC,CAACrC,MAAH,CAAV,gBAAwB,CANb,CAOlB4C,CAAO,WAAGL,CAAO,CAACD,CAAC,CAACtC,MAAH,CAAV,gBAAwB,CAPb,CASxB,GAAI2C,CAAO,GAAKC,CAAhB,CAAyB,CACrB,MAAOP,CAAAA,CAAC,CAACL,SAAF,CAAYa,MAAZ,CAAqBP,CAAC,CAACN,SAAF,CAAYa,MAC3C,CACD,MAAOF,CAAAA,CAAO,CAAGC,CACpB,CAbD,EAgBA,GAAIE,CAAAA,CAAe,CAAG,GAAIC,CAAAA,GAA1B,CAEAb,CAAY,CAACvB,OAAb,CAAqB,SAACtB,CAAD,CAAW,OAEtB2D,CAAQ,WAAM3D,CAAK,CAAC2C,SAAZ,uBAAyB3C,CAAK,CAAC4C,SAAN,CAAgBT,EAAzC,gBAA+C,CAA/C,CAFc,CAI5B,GAAI,CAACsB,CAAe,CAACG,GAAhB,CAAoBD,CAApB,CAAL,CAAoC,CAChC,CAAI,CAAC9E,aAAL,CAAmB,CACf8B,MAAM,CAAEX,CAAK,CAAC2C,SADC,CAEfzC,KAAK,CAAE,CAAI,CAACA,KAFG,CAGfY,OAAO,CAAEd,CAAK,CAAC4C,SAHA,CAAnB,CAIG,CAAI,CAAC9D,MAJR,EAMA2E,CAAe,CAACzB,GAAhB,CAAoB2B,CAApB,CACH,CACJ,CAbD,EAgBA,KAAK9E,aAAL,CAAmB,CACf8B,MAAM,CAAE,iBADO,CAEfT,KAAK,CAAE,KAAKA,KAFG,CAGfY,OAAO,CAAE,IAHM,CAAnB,CAIG,KAAKhC,MAJR,CAKH,C,+BAaCwB,CAAAA,C,YASF,WAAYkB,CAAZ,CAAkBM,CAAlB,CAAgC+B,CAAhC,CAA6C,WACzC,KAAKrC,IAAL,CAAYA,CAAZ,CACA,KAAKM,YAAL,CAAoBA,CAApB,CACA,KAAK+B,WAAL,QAAmBA,CAAnB,WAAmBA,CAAnB,CAAmBA,CAAnB,GACH,C,mCAWGC,C,CAAKrD,C,CAAMsD,C,CAAOC,C,CAAU,CAG5B,GAAI,KAAKlC,YAAL,CAAkB9C,QAAtB,CAAgC,CAC5B,KAAM,IAAIoB,CAAAA,KAAJ,iDAAmDK,CAAnD,sBAAoE,KAAKe,IAAzE,MACT,CAGD,GAAIyC,IAAI,CAACC,SAAL,CAAeJ,CAAG,CAACrD,CAAD,CAAlB,IAA8BwD,IAAI,CAACC,SAAL,CAAeH,CAAf,CAAlC,CAAyD,CACrD,QACH,CAED,GAAMpD,CAAAA,CAAM,CAAImD,CAAG,CAACrD,CAAD,CAAH,SAAD,CAA4B,SAA5B,CAAwC,SAAvD,CAGA,GAAI,KAAKoD,WAAT,CAAsB,CAClB,GAAI1C,KAAK,CAACC,OAAN,CAAc2C,CAAd,CAAJ,CAA0B,CACtBD,CAAG,CAACrD,CAAD,CAAH,CAAY,GAAIsB,CAAAA,CAAJ,CAAatB,CAAb,CAAmB,KAAKqB,YAAxB,EAAsCqC,UAAtC,CAAiDJ,CAAjD,CACf,CAFD,IAEO,CACHD,CAAG,CAACrD,CAAD,CAAH,CAAY,GAAIJ,CAAAA,KAAJ,CAAU0D,CAAV,CAAiB,GAAIzD,CAAAA,CAAJ,CAAYG,CAAZ,CAAkB,KAAKqB,YAAvB,CAAjB,CACf,CACJ,CAND,IAMO,CACHgC,CAAG,CAACrD,CAAD,CAAH,CAAYsD,CACf,CAGD,GAAI,KAAKjC,YAAL,CAAkB5B,KAAlB,SAAJ,CAA2C,CACvC,QACH,CAED,KAAK4B,YAAL,CAAkBsC,mBAAlB,CAAsC,KAAK5C,IAA3C,CAAiDf,CAAjD,CAAuDE,CAAvD,CAA+DqD,CAA/D,EAEA,QACH,C,sDAScF,C,CAAKrD,C,CAAM,CAEtB,GAAI,KAAKqB,YAAL,CAAkB9C,QAAtB,CAAgC,CAC5B,KAAM,IAAIoB,CAAAA,KAAJ,iDAAmDK,CAAnD,gBAA8D,KAAKe,IAAnE,MACT,CACD,GAAIf,CAAI,GAAIqD,CAAAA,CAAZ,CAAiB,CAEb,MAAOA,CAAAA,CAAG,CAACrD,CAAD,CAAV,CAEA,KAAKqB,YAAL,CAAkBsC,mBAAlB,CAAsC,KAAK5C,IAA3C,CAAiDf,CAAjD,CAAuD,SAAvD,CAAkEqD,CAAlE,CACH,CACD,QACH,C,gBAeC/B,C,+BASF,WAAYP,CAAZ,CAAkBM,CAAlB,CAAgCuC,CAAhC,CAA0C,iBAEtC,cAAMA,CAAN,EACA,EAAK7C,IAAL,CAAYA,CAAZ,CACA,EAAKM,YAAL,CAAoBA,CAApB,CAJsC,QAKzC,C,mCAYGwC,C,CAAKP,C,CAAO,CAGZ,GAAI,KAAKjC,YAAL,CAAkB9C,QAAtB,CAAgC,CAC5B,KAAM,IAAIoB,CAAAA,KAAJ,iDAAmDkE,CAAnD,sBAAmE,KAAK9C,IAAxE,MACT,CAGD8C,CAAG,CAAG,KAAKC,YAAL,CAAkBD,CAAlB,CAAN,CAEA,KAAKE,UAAL,CAAgBT,CAAhB,EAEA,GAAIO,CAAG,SAAH,EAA6B,IAAR,GAAAA,CAAzB,CAAuC,CACnC,KAAMlE,CAAAA,KAAK,CAAC,8CAAD,CACd,CAGD,GAAI,KAAKmE,YAAL,CAAkBR,CAAK,CAAC5B,EAAxB,IAAgCmC,CAApC,CAAyC,CACrC,KAAM,IAAIlE,CAAAA,KAAJ,wBAA0B,KAAKoB,IAA/B,8BAAwDuC,CAAK,CAAC5B,EAA9D,uBAA8EmC,CAA9E,eACT,CAnBW,GAqBN3D,CAAAA,CAAM,CAAG,uCAAW2D,CAAX,EAAmB,SAAnB,CAA+B,SArBlC,CAwBNG,CAAM,wCAAaH,CAAb,CAAkB,GAAIjE,CAAAA,KAAJ,CAAU0D,CAAV,CAAiB,GAAIzD,CAAAA,CAAJ,CAAY,KAAKkB,IAAjB,CAAuB,KAAKM,YAA5B,CAAjB,CAAlB,CAxBA,CA2BZ,GAAI,KAAKA,YAAL,CAAkB5B,KAAlB,SAAJ,CAA2C,CACvC,MAAOuE,CAAAA,CACV,CAED,KAAK3C,YAAL,CAAkBsC,mBAAlB,CAAsC,KAAK5C,IAA3C,CAAiD,IAAjD,CAAuDb,CAAvD,wCAAyE2D,CAAzE,GAEA,MAAOG,CAAAA,CACV,C,8CAWUV,C,CAAO,CACd,GAAsB,QAAlB,MAAQA,CAAR,GAAwC,IAAV,GAAAA,CAAlC,CAAkD,CAC9C,KAAM3D,CAAAA,KAAK,CAAC,sCAAD,CACd,CAED,GAAI2D,CAAK,CAAC5B,EAAN,SAAJ,CAA4B,CACxB,KAAM/B,CAAAA,KAAK,CAAC,4DAAD,CACd,CACJ,C,kDAWYkE,C,CAAK,CACd,MAAO,CAAOA,CAAP,KAAYI,OAAZ,EACV,C,gCAUGX,C,CAAO,CACP,KAAKS,UAAL,CAAgBT,CAAhB,EACA,MAAO,MAAKY,GAAL,CAASZ,CAAK,CAAC5B,EAAf,CAAmB4B,CAAnB,CACV,C,gCAQGO,C,CAAK,CACL,8CAAiB,KAAKC,YAAL,CAAkBD,CAAlB,CAAjB,CACH,C,gCAQGA,C,CAAK,CACL,8CAAiB,KAAKC,YAAL,CAAkBD,CAAlB,CAAjB,CACH,C,uCAQMA,C,CAAK,CAERA,CAAG,CAAG,KAAKC,YAAL,CAAkBD,CAAlB,CAAN,CAGA,GAAI,KAAKxC,YAAL,CAAkB9C,QAAtB,CAAgC,CAC5B,KAAM,IAAIoB,CAAAA,KAAJ,iDAAmDkE,CAAnD,sBAAmE,KAAK9C,IAAxE,MACT,CAPO,GASFoD,CAAAA,CAAQ,wCAAaN,CAAb,CATN,CAWFG,CAAM,2CAAgBH,CAAhB,CAXJ,CAYR,GAAI,CAACG,CAAL,CAAa,CACT,MAAOA,CAAAA,CACV,CAED,KAAK3C,YAAL,CAAkBsC,mBAAlB,CAAsC,KAAK5C,IAA3C,CAAiD,IAAjD,CAAuD,SAAvD,CAAkEoD,CAAlE,EAEA,MAAOH,CAAAA,CACV,C,uCAWQ,CACL,GAAIA,CAAAA,CAAM,CAAG,EAAb,CACA,KAAKnD,OAAL,CAAa,SAACyC,CAAD,CAAW,CACpBU,CAAM,CAAC/B,IAAP,CAAYqB,CAAZ,CACH,CAFD,EAGA,MAAOU,CAAAA,CACV,C,8CAWUI,C,CAAQ,YACfA,CAAM,CAACvD,OAAP,CAAe,SAACkB,CAAD,CAAU,CACrB,CAAI,CAACgC,UAAL,CAAgBhC,CAAhB,EADqB,GAEjB8B,CAAAA,CAAG,CAAG9B,CAAI,CAACL,EAFM,CAGjB2C,CAAQ,CAAG,GAAIzE,CAAAA,KAAJ,CAAUmC,CAAV,CAAgB,GAAIlC,CAAAA,CAAJ,CAAY,CAAI,CAACkB,IAAjB,CAAuB,CAAI,CAACM,YAA5B,CAAhB,CAHM,CAIrB,CAAI,CAAC6C,GAAL,CAASL,CAAT,CAAcQ,CAAd,CACH,CALD,EAMA,MAAO,KACV,C,gBA5LkBC,G","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Reactive simple state manager.\n *\n * The state manager contains the state data, trigger update events and\n * can lock and unlock the state data.\n *\n * This file contains the three main elements of the state manager:\n * - State manager: the public class to alter the state, dispatch events and process update messages.\n * - Proxy handler: a private class to keep track of the state object changes.\n * - StateMap class: a private class extending Map class that triggers event when a state list is modifed.\n *\n * @module core/local/reactive/stateManager\n * @class core/local/reactive/stateManager\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * State manager class.\n *\n * This class handle the reactive state and ensure only valid mutations can modify the state.\n * It also provide methods to apply batch state update messages (see processUpdates function doc\n * for more details on update messages).\n *\n * Implementing a deep state manager is complex and will require many frontend resources. To keep\n * the state fast and simple, the state can ONLY store two kind of data:\n * - Object with attributes\n * - Sets of objects with id attributes.\n *\n * This is an example of a valid state:\n *\n * {\n * course: {\n * name: 'course name',\n * shortname: 'courseshort',\n * sectionlist: [21, 34]\n * },\n * sections: [\n * {id: 21, name: 'Topic 1', visible: true},\n * {id: 34, name: 'Topic 2', visible: false,\n * ],\n * }\n *\n * The following cases are NOT allowed at a state ROOT level (throws an exception if they are assigned):\n * - Simple values (strings, boolean...).\n * - Arrays of simple values.\n * - Array of objects without ID attribute (all arrays will be converted to maps and requires an ID).\n *\n * Thanks to those limitations it can simplify the state update messages and the event names. If You\n * need to store simple data, just group them in an object.\n *\n * To grant any state change triggers the proper events, the class uses two private structures:\n * - proxy handler: any object stored in the state is proxied using this class.\n * - StateMap class: any object set in the state will be converted to StateMap using the\n * objects id attribute.\n */\nexport default class StateManager {\n\n /**\n * Create a basic reactive state store.\n *\n * The state manager is meant to work with native JS events. To ensure each reactive module can use\n * it in its own way, the parent element must provide a valid event dispatcher function and an optional\n * DOM element to anchor the event.\n *\n * @param {function} dispatchEvent the function to dispatch the custom event when the state changes.\n * @param {element} target the state changed custom event target (document if none provided)\n */\n constructor(dispatchEvent, target) {\n\n // The dispatch event function.\n /** @package */\n this.dispatchEvent = dispatchEvent;\n\n // The DOM container to trigger events.\n /** @package */\n this.target = target ?? document;\n\n // State can be altered freely until initial state is set.\n /** @package */\n this.readonly = false;\n\n // List of state changes pending to be published as events.\n /** @package */\n this.eventsToPublish = [];\n\n // The update state types functions.\n /** @package */\n this.updateTypes = {\n \"create\": this.defaultCreate.bind(this),\n \"update\": this.defaultUpdate.bind(this),\n \"delete\": this.defaultDelete.bind(this),\n \"put\": this.defaultPut.bind(this),\n \"override\": this.defaultOverride.bind(this),\n \"remove\": this.defaultRemove.bind(this),\n \"prepareFields\": this.defaultPrepareFields.bind(this),\n };\n\n // The state_loaded event is special because it only happens one but all components\n // may react to that state, even if they are registered after the setIinitialState.\n // For these reason we use a promise for that event.\n this.initialPromise = new Promise((resolve) => {\n const initialStateDone = (event) => {\n resolve(event.detail.state);\n };\n this.target.addEventListener('state:loaded', initialStateDone);\n });\n }\n\n /**\n * Loads the initial state.\n *\n * Note this method will trigger a state changed event with \"state:loaded\" actionname.\n *\n * The state mode will be set to read only when the initial state is loaded.\n *\n * @param {object} initialState\n */\n setInitialState(initialState) {\n\n if (this.state !== undefined) {\n throw Error('Initial state can only be initialized ones');\n }\n\n // Create the state object.\n const state = new Proxy({}, new Handler('state', this, true));\n for (const [prop, propValue] of Object.entries(initialState)) {\n state[prop] = propValue;\n }\n this.state = state;\n\n // When the state is loaded we can lock it to prevent illegal changes.\n this.readonly = true;\n\n this.dispatchEvent({\n action: 'state:loaded',\n state: this.state,\n }, this.target);\n }\n\n /**\n * Generate a promise that will be resolved when the initial state is loaded.\n *\n * In most cases the final state will be loaded using an ajax call. This is the reason\n * why states manager are created unlocked and won't be reactive until the initial state is set.\n *\n * @return {Promise} the resulting promise\n */\n getInitialPromise() {\n return this.initialPromise;\n }\n\n /**\n * Locks or unlocks the state to prevent illegal updates.\n *\n * Mutations use this method to modify the state. Once the state is updated, they must\n * block again the state.\n *\n * All changes done while the state is writable will be registered using registerStateAction.\n * When the state is set again to read only the method will trigger _publishEvents to communicate\n * changes to all watchers.\n *\n * @param {bool} readonly if the state is in read only mode enabled\n */\n setReadOnly(readonly) {\n\n this.readonly = readonly;\n\n let mode = 'off';\n\n // When the state is in readonly again is time to publish all pending events.\n if (this.readonly) {\n mode = 'on';\n this._publishEvents();\n }\n\n // Dispatch a read only event.\n this.dispatchEvent({\n action: `readmode:${mode}`,\n state: this.state,\n element: null,\n }, this.target);\n }\n\n /**\n * Add methods to process update state messages.\n *\n * The state manager provide a default update, create and delete methods. However,\n * some applications may require to override the default methods or even add new ones\n * like \"refresh\" or \"error\".\n *\n * @param {Object} newFunctions the new update types functions.\n */\n addUpdateTypes(newFunctions) {\n for (const [updateType, updateFunction] of Object.entries(newFunctions)) {\n if (typeof updateFunction === 'function') {\n this.updateTypes[updateType] = updateFunction.bind(newFunctions);\n }\n }\n }\n\n /**\n * Process a state updates array and do all the necessary changes.\n *\n * Note this method unlocks the state while it is executing and relocks it\n * when finishes.\n *\n * @param {array} updates\n * @param {Object} updateTypes optional functions to override the default update types.\n */\n processUpdates(updates, updateTypes) {\n if (!Array.isArray(updates)) {\n throw Error('State updates must be an array');\n }\n this.setReadOnly(false);\n updates.forEach((update) => {\n if (update.name === undefined) {\n throw Error('Missing state update name');\n }\n this.processUpdate(\n update.name,\n update.action,\n update.fields,\n updateTypes\n );\n });\n this.setReadOnly(true);\n }\n\n /**\n * Process a single state update.\n *\n * Note this method will not lock or unlock the state by itself.\n *\n * @param {string} updateName the state element to update\n * @param {string} action to action to perform\n * @param {object} fields the new data\n * @param {Object} updateTypes optional functions to override the default update types.\n */\n processUpdate(updateName, action, fields, updateTypes) {\n\n if (!fields) {\n throw Error('Missing state update fields');\n }\n\n if (updateTypes === undefined) {\n updateTypes = {};\n }\n\n action = action ?? 'update';\n\n const method = updateTypes[action] ?? this.updateTypes[action];\n\n if (method === undefined) {\n throw Error(`Unkown update action ${action}`);\n }\n\n // Some state data may require some cooking before sending to the\n // state. Reactive instances can overrdide the default fieldDefaults\n // method to add extra logic to all updates.\n const prepareFields = updateTypes.prepareFields ?? this.updateTypes.prepareFields;\n\n method(this, updateName, prepareFields(this, updateName, fields));\n }\n\n /**\n * Prepare fields for processing.\n *\n * This method is used to add default values or calculations from the frontend side.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n defaultPrepareFields(stateManager, updateName, fields) {\n return fields;\n }\n\n\n /**\n * Process a create state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultCreate(stateManager, updateName, fields) {\n\n let state = stateManager.state;\n\n // Create can be applied only to lists, not to objects.\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n\n /**\n * Process a delete state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultDelete(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (!current) {\n throw Error(`Inexistent ${updateName} ${fields.id}`);\n }\n\n // Process deletion.\n let state = stateManager.state;\n\n if (state[updateName] instanceof StateMap) {\n state[updateName].delete(fields.id);\n return;\n }\n delete state[updateName];\n }\n\n /**\n * Process a remove state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultRemove(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (!current) {\n return;\n }\n\n // Process deletion.\n let state = stateManager.state;\n\n if (state[updateName] instanceof StateMap) {\n state[updateName].delete(fields.id);\n return;\n }\n delete state[updateName];\n }\n\n /**\n * Process a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultUpdate(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (!current) {\n throw Error(`Inexistent ${updateName} ${fields.id}`);\n }\n\n // Execute updates.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n }\n\n /**\n * Process a put state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultPut(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (current) {\n // Update attributes.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n } else {\n // Create new object.\n let state = stateManager.state;\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n }\n\n /**\n * Process an override state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultOverride(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (current) {\n // Remove any unnecessary fields.\n for (const [fieldName] of Object.entries(current)) {\n if (fields[fieldName] === undefined) {\n delete current[fieldName];\n }\n }\n // Update field.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n } else {\n // Create the element if not exists.\n let state = stateManager.state;\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n }\n\n /**\n * Get an element from the state or form an alternative state object.\n *\n * The altstate param is used by external update functions that gets the current\n * state as param.\n *\n * @param {String} name the state object name\n * @param {*} id and object id for state maps.\n * @return {Object|undefined} the state object found\n */\n get(name, id) {\n const state = this.state;\n\n let current = state[name];\n if (current instanceof StateMap) {\n if (id === undefined) {\n throw Error(`Missing id for ${name} state update`);\n }\n current = state[name].get(id);\n }\n\n return current;\n }\n\n /**\n * Register a state modification and generate the necessary events.\n *\n * This method is used mainly by proxy helpers to dispatch state change event.\n * However, mutations can use it to inform components about non reactive changes\n * in the state (only the two first levels of the state are reactive).\n *\n * Each action can produce several events:\n * - The specific attribute updated, created or deleter (example: \"cm.visible:updated\")\n * - The general state object updated, created or deleted (example: \"cm:updated\")\n * - If the element has an ID attribute, the specific event with id (example: \"cm[42].visible:updated\")\n * - If the element has an ID attribute, the general event with id (example: \"cm[42]:updated\")\n * - A generic state update event \"state:update\"\n *\n * @param {string} field the affected state field name\n * @param {string|null} prop the affecter field property (null if affect the full object)\n * @param {string} action the action done (created/updated/deleted)\n * @param {*} data the affected data\n */\n registerStateAction(field, prop, action, data) {\n\n let parentAction = 'updated';\n\n if (prop !== null) {\n this.eventsToPublish.push({\n eventName: `${field}.${prop}:${action}`,\n eventData: data,\n action,\n });\n } else {\n parentAction = action;\n }\n\n // Trigger extra events if the element has an ID attribute.\n if (data.id !== undefined) {\n if (prop !== null) {\n this.eventsToPublish.push({\n eventName: `${field}[${data.id}].${prop}:${action}`,\n eventData: data,\n action,\n });\n }\n this.eventsToPublish.push({\n eventName: `${field}[${data.id}]:${parentAction}`,\n eventData: data,\n action: parentAction,\n });\n }\n\n // Register the general change.\n this.eventsToPublish.push({\n eventName: `${field}:${parentAction}`,\n eventData: data,\n action: parentAction,\n });\n\n // Register state updated event.\n this.eventsToPublish.push({\n eventName: `state:updated`,\n eventData: data,\n action: 'updated',\n });\n }\n\n /**\n * Internal method to publish events.\n *\n * This is a private method, it will be invoked when the state is set back to read only mode.\n */\n _publishEvents() {\n const fieldChanges = this.eventsToPublish;\n this.eventsToPublish = [];\n\n // Dispatch a transaction start event.\n this.dispatchEvent({\n action: 'transaction:start',\n state: this.state,\n element: null,\n changes: fieldChanges,\n }, this.target);\n\n // State changes can be registered in any order. However it will avoid many\n // components errors if they are sorted to have creations-updates-deletes in case\n // some component needs to create or destroy DOM elements before updating them.\n fieldChanges.sort((a, b) => {\n const weights = {\n created: 0,\n updated: 1,\n deleted: 2,\n };\n const aweight = weights[a.action] ?? 0;\n const bweight = weights[b.action] ?? 0;\n // In case both have the same weight, the eventName length decide.\n if (aweight === bweight) {\n return a.eventName.length - b.eventName.length;\n }\n return aweight - bweight;\n });\n\n // List of the published events to prevent redundancies.\n let publishedEvents = new Set();\n\n fieldChanges.forEach((event) => {\n\n const eventkey = `${event.eventName}.${event.eventData.id ?? 0}`;\n\n if (!publishedEvents.has(eventkey)) {\n this.dispatchEvent({\n action: event.eventName,\n state: this.state,\n element: event.eventData\n }, this.target);\n\n publishedEvents.add(eventkey);\n }\n });\n\n // Dispatch a transaction end event.\n this.dispatchEvent({\n action: 'transaction:end',\n state: this.state,\n element: null,\n }, this.target);\n }\n}\n\n// Proxy helpers.\n\n/**\n * The proxy handler.\n *\n * This class will inform any value change directly to the state manager.\n *\n * The proxied variable will throw an error if it is altered when the state manager is\n * in read only mode.\n */\nclass Handler {\n\n /**\n * Class constructor.\n *\n * @param {string} name the variable name used for identify triggered actions\n * @param {StateManager} stateManager the state manager object\n * @param {boolean} proxyValues if new values must be proxied (used only at state root level)\n */\n constructor(name, stateManager, proxyValues) {\n this.name = name;\n this.stateManager = stateManager;\n this.proxyValues = proxyValues ?? false;\n }\n\n /**\n * Set trap to trigger events when the state changes.\n *\n * @param {object} obj the source object (not proxied)\n * @param {string} prop the attribute to set\n * @param {*} value the value to save\n * @param {*} receiver the proxied element to be attached to events\n * @returns {boolean} if the value is set\n */\n set(obj, prop, value, receiver) {\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${prop} value in ${this.name}.`);\n }\n\n // Check any data change.\n if (JSON.stringify(obj[prop]) === JSON.stringify(value)) {\n return true;\n }\n\n const action = (obj[prop] !== undefined) ? 'updated' : 'created';\n\n // Proxy value if necessary (used at state root level).\n if (this.proxyValues) {\n if (Array.isArray(value)) {\n obj[prop] = new StateMap(prop, this.stateManager).loadValues(value);\n } else {\n obj[prop] = new Proxy(value, new Handler(prop, this.stateManager));\n }\n } else {\n obj[prop] = value;\n }\n\n // If the state is not ready yet means the initial state is not yet loaded.\n if (this.stateManager.state === undefined) {\n return true;\n }\n\n this.stateManager.registerStateAction(this.name, prop, action, receiver);\n\n return true;\n }\n\n /**\n * Delete property trap to trigger state change events.\n *\n * @param {*} obj the affected object (not proxied)\n * @param {*} prop the prop to delete\n * @returns {boolean} if prop is deleted\n */\n deleteProperty(obj, prop) {\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to delete ${prop} in ${this.name}.`);\n }\n if (prop in obj) {\n\n delete obj[prop];\n\n this.stateManager.registerStateAction(this.name, prop, 'deleted', obj);\n }\n return true;\n }\n}\n\n/**\n * Class to add events dispatching to the JS Map class.\n *\n * When the state has a list of objects (with IDs) it will be converted into a StateMap.\n * StateMap is used almost in the same way as a regular JS map. Because all elements have an\n * id attribute, it has some specific methods:\n * - add: a convenient method to add an element without specifying the key (\"id\" attribute will be used as a key).\n * - loadValues: to add many elements at once wihout specifying keys (\"id\" attribute will be used).\n *\n * Apart, the main difference between regular Map and MapState is that this one will inform any change to the\n * state manager.\n */\nclass StateMap extends Map {\n\n /**\n * Create a reactive Map.\n *\n * @param {string} name the property name\n * @param {StateManager} stateManager the state manager\n * @param {iterable} iterable an iterable object to create the Map\n */\n constructor(name, stateManager, iterable) {\n // We don't have any \"this\" until be call super.\n super(iterable);\n this.name = name;\n this.stateManager = stateManager;\n }\n\n /**\n * Set an element into the map.\n *\n * Each value needs it's own id attribute. Objects without id will be rejected.\n * The function will throw an error if the value id and the key are not the same.\n *\n * @param {*} key the key to store\n * @param {*} value the value to store\n * @returns {Map} the resulting Map object\n */\n set(key, value) {\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${key} value in ${this.name}.`);\n }\n\n // Normalize keys as string to prevent json decoding errors.\n key = this.normalizeKey(key);\n\n this.checkValue(value);\n\n if (key === undefined || key === null) {\n throw Error('State lists keys cannot be null or undefined');\n }\n\n // ID is mandatory and should be the same as the key.\n if (this.normalizeKey(value.id) !== key) {\n throw new Error(`State error: ${this.name} list element ID (${value.id}) and key (${key}) mismatch`);\n }\n\n const action = (super.has(key)) ? 'updated' : 'created';\n\n // Save proxied data into the list.\n const result = super.set(key, new Proxy(value, new Handler(this.name, this.stateManager)));\n\n // If the state is not ready yet means the initial state is not yet loaded.\n if (this.stateManager.state === undefined) {\n return result;\n }\n\n this.stateManager.registerStateAction(this.name, null, action, super.get(key));\n\n return result;\n }\n\n /**\n * Check if a value is valid to be stored in a a State List.\n *\n * Only objects with id attribute can be stored in State lists.\n *\n * This method throws an error if the value is not valid.\n *\n * @param {object} value (with ID)\n */\n checkValue(value) {\n if (!typeof value === 'object' && value !== null) {\n throw Error('State lists can contain objects only');\n }\n\n if (value.id === undefined) {\n throw Error('State lists elements must contain at least an id attribute');\n }\n }\n\n /**\n * Return a normalized key value for state map.\n *\n * Regular maps uses strict key comparissons but state maps are indexed by ID.JSON conversions\n * and webservices sometimes do unexpected types conversions so we convert any integer key to string.\n *\n * @param {*} key the provided key\n * @returns {string}\n */\n normalizeKey(key) {\n return String(key).valueOf();\n }\n\n /**\n * Insert a new element int a list.\n *\n * Each value needs it's own id attribute. Objects withouts id will be rejected.\n *\n * @param {object} value the value to add (needs an id attribute)\n * @returns {Map} the resulting Map object\n */\n add(value) {\n this.checkValue(value);\n return this.set(value.id, value);\n }\n\n /**\n * Return a state map element.\n *\n * @param {*} key the element id\n * @return {Object}\n */\n get(key) {\n return super.get(this.normalizeKey(key));\n }\n\n /**\n * Check whether an element with the specified key exists or not.\n *\n * @param {*} key the key to find\n * @return {boolean}\n */\n has(key) {\n return super.has(this.normalizeKey(key));\n }\n\n /**\n * Delete an element from the map.\n *\n * @param {*} key\n * @returns {boolean}\n */\n delete(key) {\n // State maps uses only string keys to avoid strict comparisons.\n key = this.normalizeKey(key);\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${key} value in ${this.name}.`);\n }\n\n const previous = super.get(key);\n\n const result = super.delete(key);\n if (!result) {\n return result;\n }\n\n this.stateManager.registerStateAction(this.name, null, 'deleted', previous);\n\n return result;\n }\n\n /**\n * Return a suitable structure for JSON conversion.\n *\n * This function is needed because new values are compared in JSON. StateMap has Private\n * attributes which cannot be stringified (like this.stateManager which will produce an\n * infinite recursivity).\n *\n * @returns {array}\n */\n toJSON() {\n let result = [];\n this.forEach((value) => {\n result.push(value);\n });\n return result;\n }\n\n /**\n * Insert a full list of values using the id attributes as keys.\n *\n * This method is used mainly to initialize the list. Note each element is indexed by its \"id\" attribute.\n * This is a basic restriction of StateMap. All elements need an id attribute, otherwise it won't be saved.\n *\n * @param {iterable} values the values to load\n * @returns {StateMap} return the this value\n */\n loadValues(values) {\n values.forEach((data) => {\n this.checkValue(data);\n let key = data.id;\n let newvalue = new Proxy(data, new Handler(this.name, this.stateManager));\n this.set(key, newvalue);\n });\n return this;\n }\n}\n"],"file":"statemanager.min.js"} \ No newline at end of file diff --git a/lib/amd/src/local/reactive/basecomponent.js b/lib/amd/src/local/reactive/basecomponent.js index 0e665bd64b4..3b28e3b6d00 100644 --- a/lib/amd/src/local/reactive/basecomponent.js +++ b/lib/amd/src/local/reactive/basecomponent.js @@ -438,32 +438,52 @@ export default class { * @param {boolean} locked the new locked value */ set locked(locked) { - this.element.dataset.locked = locked ?? false; + this.setElementLocked(this.element, locked); + } + + /** + * Get the current locked value from the element. + * + * @return {boolean} + */ + get locked() { + return this.getElementLocked(this.element); + } + + /** + * Lock/unlock an element. + * + * @param {Element} target the event target + * @param {boolean} locked the new locked value + */ + setElementLocked(target, locked) { + target.dataset.locked = locked ?? false; if (locked) { // Disable interactions. - this.element.style.pointerEvents = 'none'; - this.element.style.userSelect = 'none'; + target.style.pointerEvents = 'none'; + target.style.userSelect = 'none'; // Check if it is draggable. - if (this.element.hasAttribute('draggable')) { - this.element.setAttribute('draggable', false); + if (target.hasAttribute('draggable')) { + target.setAttribute('draggable', false); } } else { - // Reanable interactions. - this.element.style.pointerEvents = null; - this.element.style.userSelect = null; + // Enable interactions. + target.style.pointerEvents = null; + target.style.userSelect = null; // Check if it was draggable. - if (this.element.hasAttribute('draggable')) { - this.element.setAttribute('draggable', true); + if (target.hasAttribute('draggable')) { + target.setAttribute('draggable', true); } } } /** - * Get the current locket value from the element. + * Get the current locked value from the element. * + * @param {Element} target the event target * @return {boolean} */ - get locked() { - return this.element.dataset.locked ?? false; + getElementLocked(target) { + return target.dataset.locked ?? false; } } diff --git a/lib/amd/src/local/reactive/statemanager.js b/lib/amd/src/local/reactive/statemanager.js index b6a66001318..0b4c311582b 100644 --- a/lib/amd/src/local/reactive/statemanager.js +++ b/lib/amd/src/local/reactive/statemanager.js @@ -107,6 +107,7 @@ export default class StateManager { "delete": this.defaultDelete.bind(this), "put": this.defaultPut.bind(this), "override": this.defaultOverride.bind(this), + "remove": this.defaultRemove.bind(this), "prepareFields": this.defaultPrepareFields.bind(this), }; @@ -336,6 +337,31 @@ export default class StateManager { delete state[updateName]; } + /** + * Process a remove state message. + * + * @param {Object} stateManager the state manager + * @param {String} updateName the state element to update + * @param {Object} fields the new data + */ + defaultRemove(stateManager, updateName, fields) { + + // Get the current value. + let current = stateManager.get(updateName, fields.id); + if (!current) { + return; + } + + // Process deletion. + let state = stateManager.state; + + if (state[updateName] instanceof StateMap) { + state[updateName].delete(fields.id); + return; + } + delete state[updateName]; + } + /** * Process a update state message. *