Card de curso (Mis cursos / Área personal)
- Impacto — Tema: Override
core_course/coursecard.mustache; AMDcoursecard_favourite.js,coursecard_teachers.js,repository.js - Impacto — Plugin: WS
external\get_course_teachers(db/services.php) - Requisito: Diseño (Figma) · Figma:
19658-13142· Estado: Implementado
Propósito
Rediseño completo de la card de curso que renderiza block_myoverview en "Mis
cursos" (/my/courses.php) y en cualquier región de bloques donde se añada el
bloque nativo myoverview (por ejemplo, el Panel de
Actividades del Área personal). Siguiendo el
diseño Figma "Card_curso", incluye:
La sección Mis Cursos del Área personal (
/my/) usa un diseño de card visualmente equivalente pero no pasa por este override: se genera con una plantilla propia (miscursos_section.mustache) renderizada server-side directamente enlayout/mydashboard.php.
- Imagen de portada, categoría y título del curso (estructura core preservada).
- Botón de favorito (marcador/bookmark) sobre la imagen, funcional como toggle (no solo desde el menú ⋮).
- Campo "Profesor:" con los nombres de los docentes del curso.
- Altura uniforme entre cards de una misma fila, con la barra de progreso y el botón "Ver curso" siempre anclados al final de la card.
Por qué es un override de template y no un bloque/renderer nuevo
/my/courses.php y /my/ renderizan las cards 100% client-side:
blocks/myoverview/amd/src/view.js llama al WS
core_course_get_enrolled_courses_by_timeline_classification
(course_summary_exporter, ambos core) y luego
Templates.render('core_course/coursecards' → 'core_course/coursecard', ...).
No existe un hook ni método de renderer que el tema pueda sobreescribir para inyectar datos adicionales (como el profesor) en ese JSON sin modificar core. Por eso:
- El template (
theme_cdigital/templates/core_course/coursecard.mustache) sobreescribecore_course/coursecardy añade los elementos visuales (marcador, "Profesor:") con marcado/data-*siempre presente. - Los datos adicionales (favorito ya viaja en el contexto del template;
profesor no) se completan después vía un módulo AMD que llama a una
función externa nueva del plugin
local_pccntr8203403_dashboard.
1. Botón de favorito (marcador)
Problema con el comportamiento core
Core solo permite marcar/desmarcar un curso como favorito desde el menú de
acciones (⋮) de la card (core_course_set_favourite_courses). El ícono de
marcador del diseño Figma debía ser un toggle directo sobre la imagen.
Solución
- Iconos nuevos
pix/Icon/bookmark.svgypix/Icon/bookmark-heart.svg(24×24,theme_cdigital). coursecard.mustacheañade un botón.cdigital-coursecard__bookmarksobre.cdigital-coursecard__media, con dos<span>(is-favourite/not-favourite) que alternan visibilidad según{{isfavourite}}.amd/src/coursecard_favourite.js:- Listener delegado global (
document.addEventListener('click', ...)) para que funcione también en cards re-renderizadas (filtros, búsqueda, paginación). - Al hacer click, llama a
setFavouriteCourses(repository → WS corecore_course_set_favourite_courses). - Sincroniza el ícono y los items del menú ⋮ (
add-favourite/remove-favourite). - Publica
CourseEvents.favourited/unfavoritedporcore/pubsub, igual que el toggle nativo del menú, para que otros componentes (block_myoverview) se mantengan sincronizados.
- Listener delegado global (
2. Campo "Profesor:"
Requisito
La etiqueta "Profesor:" (azul cobalto #0943b5, negrita) debe mostrarse
siempre, aunque el curso no tenga ningún docente configurado (en ese caso
queda vacía después del label).
Función externa — local_pccntr8203403_dashboard_get_course_teachers
Nueva función de Web Services del plugin local_pccntr8203403_dashboard
(classes/external/get_course_teachers.php, registrada en db/services.php,
ajax => true, type => read).
Reutiliza la misma API que usa el core para los "course contacts" (la línea "Teacher: X" en los listados de categorías de curso):
core_course_category::preload_course_contacts($courses); // batch, cacheada por core
(new core_course_list_element($record))->get_course_contacts();
Para cada courseid recibido:
get_course()+can_access_course()— descarta cursos inexistentes o sin acceso del usuario (silenciosamente).preload_course_contacts()en batch sobre todos los cursos válidos (respeta$CFG->coursecontacty usa la cachécore/coursecontacts).- Devuelve
[{courseid, teachers}], conteachers= nombres de docentes unidos por,(cadena vacía si el curso no tiene contactos configurados).
Template
coursecard.mustache añade, después del título, un párrafo siempre presente:
<p class="cdigital-coursecard__teachers">
<span class="cdigital-coursecard__teachers-label">{{#str}} coursecard_teacherlabel, theme_cdigital {{/str}}</span>
<span class="cdigital-coursecard__teachers-names" data-region="teacher-names" data-course-id="{{id}}"></span>
</p>
El <span> de nombres se rellena por JS; si el curso no tiene profesor, queda
vacío pero la etiqueta "Profesor:" permanece visible.
Módulo AMD — coursecard_teachers.js (batching)
Cada card llama init(courseId) desde su propio {{#js}}. El módulo:
- Acumula los
courseIden unSeta nivel de módulo. - Agenda un único
setTimeout(flush, 0)(debounce al siguiente tick). - En el flush, hace una sola llamada AJAX (
getCourseTeachers) con todos loscourseIdpendientes de la página, y rellena cada.cdigital-coursecard__teachers-names[data-course-id="X"].
Esto evita N llamadas WS para N cards visibles (típicamente 6-12 en /my/).
3. Altura fija y anclaje inferior
.cdigital-coursecard__bodyya usabadisplay: flex; flex-direction: column; justify-content: space-between;, que ancla.cdigital-coursecard__actions(progreso + "Ver curso" + menú ⋮) al final de la card.- Para que todas las cards de una fila midan igual independientemente de
cuántos docentes tenga cada curso,
.cdigital-coursecard__teachersreserva siempre el espacio de 2 líneas con-webkit-line-clamp: 2+min-height: calc(14px * 1.5 * 2)(~46px, según Figma). - Se eliminó
overflow: hiddende.cdigital-coursecard(regresión de una iteración anterior) para que el menú ⋮ desplegable no quede recortado. Los bordes redondeados se conservan porque.cdigital-coursecard__imageya tiene su propioborder-radius: 16px 16px 0 0.
Archivos clave
| Archivo | Descripción |
|---|---|
theme/cdigital/templates/core_course/coursecard.mustache | Override del template core: marcador de favorito + campo "Profesor:" |
theme/cdigital/amd/src/coursecard_favourite.js | Toggle de favorito (marcador), sincroniza ícono + menú ⋮ + pubsub |
theme/cdigital/amd/src/coursecard_teachers.js | Resuelve nombres de docentes por curso, en una llamada AJAX batcheada |
theme/cdigital/amd/src/repository.js | setFavouriteCourses, getCourseTeachers (wrappers de core/ajax) |
theme/cdigital/pix/Icon/bookmark.svg, bookmark-heart.svg | Iconos del marcador (24×24) |
theme/cdigital/scss/preset/cdigital.scss | .cdigital-coursecard*, .cdigital-coursecard__teachers*, .cdigital-coursecard__bookmark* |
theme/cdigital/lang/{en,es}/theme_cdigital.php | Strings coursecard_viewcourse, coursecard_teacherlabel |
local/pccntr8203403_dashboard/classes/external/get_course_teachers.php | Función externa (WS) — nombres de docentes por curso |
local/pccntr8203403_dashboard/db/services.php | Registro de local_pccntr8203403_dashboard_get_course_teachers |
Build y despliegue
Esta feature incluye módulos AMD que requieren build previo al despliegue. Para el procedimiento de build, ver Build de assets del tema. Para el despliegue completo (copia de código, upgrade, purga de cachés), ver Procedimiento de despliegue.