Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Creating mobile course formats: Difference between revisions

From MoodleDocs
(Initial example of a course format plugin.)
 
(2 intermediate revisions by the same user not shown)
Line 50: Line 50:
         ];
         ];
     }
     }
}
</code>
</code>
Then your templates/mobile_course.mustache file will contain the angular template to display your page
Then your templates/mobile_course.mustache file will contain the angular template to display your page
Line 57: Line 58:
     <ng-container *ngFor="let section of sections">
     <ng-container *ngFor="let section of sections">
         <ion-item-divider color="light">
         <ion-item-divider color="light">
        <core-format-text [text]="section.name"></core-format-text>
            <core-format-text [text]="section.name"></core-format-text>
        <!-- Section download. -->
            <!-- Section download. -->
        <div *ngIf="section && downloadEnabled" class="core-button-spinner" float-end>
            <div *ngIf="section && downloadEnabled" class="core-button-spinner" float-end>
            <!-- Download button. -->
                <!-- Download button. -->
            <button *ngIf="section.showDownload && !section.isDownloading && !section.isCalculating" (click)="prefetch($event, section)" ion-button icon-only clear color="dark" [attr.aria-label]="'core.download' | translate">
                <button *ngIf="section.showDownload && !section.isDownloading && !section.isCalculating" (click)="prefetch($event, section)" ion-button icon-only clear color="dark" [attr.aria-label]="'core.download' | translate">
                <ion-icon name="cloud-download"></ion-icon>
                    <ion-icon name="cloud-download"></ion-icon>
            </button>
                </button>
            <!-- Refresh button. -->
                <!-- Refresh button. -->
            <button *ngIf="section.showRefresh && !section.isDownloading && !section.isCalculating" (click)="prefetch($event, section)" ion-button icon-only clear color="dark" [attr.aria-label]="'core.refresh' | translate">
                <button *ngIf="section.showRefresh && !section.isDownloading && !section.isCalculating" (click)="prefetch($event, section)" ion-button icon-only clear color="dark" [attr.aria-label]="'core.refresh' | translate">
                <ion-icon name="refresh"></ion-icon>
                    <ion-icon name="refresh"></ion-icon>
            </button>
                </button>
            <!-- Download progress. -->
                <!-- Download progress. -->
            <ion-badge class="core-course-download-section-progress" *ngIf="section.isDownloading && section.total > 0 && section.count < section.total">{{section.count}} / {{section.total}}</ion-badge>
                <ion-badge class="core-course-download-section-progress" *ngIf="section.isDownloading && section.total > 0 && section.count < section.total">{{section.count}} / {{section.total}}</ion-badge>
            <!-- Spinner (downloading or calculating status). -->
                <!-- Spinner (downloading or calculating status). -->
            <ion-spinner *ngIf="(section.isDownloading && section.total > 0) || section.isCalculating"></ion-spinner>
                <ion-spinner *ngIf="(section.isDownloading && section.total > 0) || section.isCalculating"></ion-spinner>
        </div>
            </div>
    </ion-item-divider>
        </ion-item-divider>


    <ion-item text-wrap *ngIf="section.summary">
        <ion-item text-wrap *ngIf="section.summary">
        <core-format-text [text]="section.summary"></core-format-text>
            <core-format-text [text]="section.summary"></core-format-text>
    </ion-item>
        </ion-item>


    <ng-container *ngFor="let module of section.modules">
        <ng-container *ngFor="let module of section.modules">
        <ng-container *ngIf="module.visibleoncoursepage !== 0">
            <ng-container *ngIf="module.visibleoncoursepage !== 0">
            <core-course-module text-wrap [module]="module" [courseId]="course.id" [downloadEnabled]="downloadEnabled" (completionChanged)="onCompletionChange($event)">
                <core-course-module text-wrap [module]="module" [courseId]="course.id" [downloadEnabled]="downloadEnabled" (completionChanged)="onCompletionChange($event)">
            </core-course-module>
                </core-course-module>
            </ng-container>
         </ng-container>
         </ng-container>
     </ng-container>
     </ng-container>
Line 90: Line 92:


This will get you to a stage where you have something similar to the core format plugin - a list of sections with headers, each containing its list of course modules.  From here, you can make customisations to support your course format's features.
This will get you to a stage where you have something similar to the core format plugin - a list of sections with headers, each containing its list of course modules.  From here, you can make customisations to support your course format's features.
Note that there are a few objects available to your template without you having to do anything:
* sections - this is an array of all the sections on the course, which includes all of the modules within that course.
* course - this contains the basic course data
* downloadEnabled - This is set using the option int he context menu, if "displayenabledownload" is used in your db.php
== Example: only display certain sections ==
When your course page loads, the sections array contains all of the sections on the course.  However, you might not want to display all of these sections on one page.
You can achieve this by returning the list of sections to display along with the template in classes/output/mobile.php:
<code php>
class mobile {
    public static function mobile_course_view($args) {
      ...
      $displaysections = myformat\helper::get_list_of_section_ids($courseid);
        return [
            'templates' => [
                [
                    'id' => 'main',
                    'html' => $html
                ]
            ],
            'otherdata' => [
              'displaysections' => json_encode($displaysections);
            ]
        ];
    }
}
</code>
Then filter the list of sections in your template:
<code html>
    <ng-container *ngFor="let section of sections">
        <ng-container *ngIf="CONTENT_OTHERDATA.displaysections.hasOwnProperty(section.id.toString())">
            <!-- code to display the section goes here -->
        </ng-container>
    </ng-container>
</code>

Revision as of 12:31, 16 April 2019

Course format plugins can be supported in the app using the CoureCourseFormatDelegate.

To begin, add a handler for this delegate to your course format's db/mobile.php file: $addons = [

   'format_myformat' => [
       'handlers' => [ // Different places where the plugin will display content.
           'myformat' => [ // Handler unique name (alphanumeric).
               'delegate' => 'CoreCourseFormatDelegate', // Delegate (where to display the link to the plugin)
               'method' => 'mobile_course_view', // Main function in \format_myformat\output\mobile.
               'styles' => [
                   'url' => $CFG->wwwroot . '/course/format/myformat/mobile.css',
                   'version' => 2019041000
               ],
               'displaysectionselector' => true, // Set to false to disable the default section selector.
               'displayenabledownload' => true, // Set to false to hide the "Enable download" option in the course context menu.
               'init' => 'myformat_init'
           ]
   ]

];

As with other plugins, you then use a function in your plugin's classes/output/mobile.php to return a template: class mobile {

   /**
    * Main course page.
    *
    * @param array $args Standard mobile web service arguments
    * @return array
    */
   public static function mobile_course_view($args) {
       global $OUTPUT, $CFG;
       $course = get_course($args['courseid']);
       require_login($course);
       $html = $OUTPUT->render_from_template('format_oustudyplan/mobile_course');
       return [
           'templates' => [
               [
                   'id' => 'main',
                   'html' => $html
               ]
           ],
           'otherdata' => [
              ...
           ]
       ];
   }

} Then your templates/mobile_course.mustache file will contain the angular template to display your page {{=<% %>=}} <core-dynamic-component [component]="allSectionsComponent" [data]="data" class="format-myformat">

   <ng-container *ngFor="let section of sections">
       <ion-item-divider color="light">
           <core-format-text [text]="section.name"></core-format-text>
               <button *ngIf="section.showDownload && !section.isDownloading && !section.isCalculating" (click)="prefetch($event, section)" ion-button icon-only clear color="dark" [attr.aria-label]="'core.download' | translate">
                   <ion-icon name="cloud-download"></ion-icon>
               </button>
               <button *ngIf="section.showRefresh && !section.isDownloading && !section.isCalculating" (click)="prefetch($event, section)" ion-button icon-only clear color="dark" [attr.aria-label]="'core.refresh' | translate">
                   <ion-icon name="refresh"></ion-icon>
               </button>
               <ion-badge class="core-course-download-section-progress" *ngIf="section.isDownloading && section.total > 0 && section.count < section.total">Template:section.count / Template:section.total</ion-badge>
               <ion-spinner *ngIf="(section.isDownloading && section.total > 0) || section.isCalculating"></ion-spinner>
       </ion-item-divider>
       <ion-item text-wrap *ngIf="section.summary">
           <core-format-text [text]="section.summary"></core-format-text>
       </ion-item>
       <ng-container *ngFor="let module of section.modules">
           <ng-container *ngIf="module.visibleoncoursepage !== 0">
               <core-course-module text-wrap [module]="module" [courseId]="course.id" [downloadEnabled]="downloadEnabled" (completionChanged)="onCompletionChange($event)">
               </core-course-module>
           </ng-container>
       </ng-container>
   </ng-container>

</core-dynamic-component> You don't have to use a mustache template, you can just use a static angular template.

This will get you to a stage where you have something similar to the core format plugin - a list of sections with headers, each containing its list of course modules. From here, you can make customisations to support your course format's features.

Note that there are a few objects available to your template without you having to do anything:

  • sections - this is an array of all the sections on the course, which includes all of the modules within that course.
  • course - this contains the basic course data
  • downloadEnabled - This is set using the option int he context menu, if "displayenabledownload" is used in your db.php

Example: only display certain sections

When your course page loads, the sections array contains all of the sections on the course. However, you might not want to display all of these sections on one page. You can achieve this by returning the list of sections to display along with the template in classes/output/mobile.php: class mobile {

   public static function mobile_course_view($args) {
      ...
      $displaysections = myformat\helper::get_list_of_section_ids($courseid);
       return [
           'templates' => [
               [
                   'id' => 'main',
                   'html' => $html
               ]
           ],
           'otherdata' => [
              'displaysections' => json_encode($displaysections);
           ]
       ];
   }

} Then filter the list of sections in your template:

   <ng-container *ngFor="let section of sections">
       <ng-container *ngIf="CONTENT_OTHERDATA.displaysections.hasOwnProperty(section.id.toString())">
       </ng-container>
   </ng-container>