From 24e27ac0c1d85bbf860b6ab74b015c03fcf2cf66 Mon Sep 17 00:00:00 2001 From: Sam Hemelryk Date: Thu, 27 May 2010 01:40:11 +0000 Subject: [PATCH] course MDL-8369 Front page combo list is now restructured and exandable by JavaScript This patch also implements a course renderer. --- course/info.php | 2 + course/lib.php | 61 +++++++++++++ course/renderer.php | 157 ++++++++++++++++++++++++++++++++ index.php | 6 +- lang/en/moodle.php | 4 +- lib/javascript-static.js | 30 ++++++ lib/moodlelib.php | 1 + mod/assignment/index.php | 1 + theme/base/style/course.css | 17 +++- theme/standard/style/course.css | 20 +++- 10 files changed, 292 insertions(+), 7 deletions(-) create mode 100644 course/renderer.php diff --git a/course/info.php b/course/info.php index 2732c4870c3cc..fc3fc4e0bca2a 100644 --- a/course/info.php +++ b/course/info.php @@ -33,6 +33,8 @@ print_error('coursehidden', '', $CFG->wwwroot .'/'); } + $PAGE->set_context($context); + $PAGE->set_pagelayout('course'); $PAGE->set_url('/course/info.php', array('id' => $course->id)); $PAGE->set_title(get_string("summaryof", "", $course->fullname)); $PAGE->set_heading('Course info'); diff --git a/course/lib.php b/course/lib.php index 1b68729541619..39ca44004d479 100644 --- a/course/lib.php +++ b/course/lib.php @@ -1856,6 +1856,67 @@ function make_categories_list(&$list, &$parents, $requiredcapability = '', } } +/** + * This function generates a structured array of courses and categories. + * + * The depth of categories is limited by $CFG->maxcategorydepth however there + * is no limit on the number of courses! + * + * Suitable for use with the course renderers course_category_tree method: + * $renderer = $PAGE->get_renderer('core','course'); + * echo $renderer->course_category_tree(get_course_category_tree()); + * + * @global moodle_database $DB + * @param int $id + * @param int $depth + */ +function get_course_category_tree($id = 0, $depth = 0) { + global $DB; + $viewhiddencats = has_capability('moodle/category:viewhiddencategories', get_context_instance(CONTEXT_SYSTEM)); + $categories = get_child_categories($id); + $categoryids = array(); + foreach ($categories as $key => &$category) { + if (!$category->visible && !$viewhiddencats) { + unset($categories[$key]); + continue; + } + $categoryids[$category->id] = $category; + if (empty($CFG->maxcategorydepth) || $depth <= $CFG->maxcategorydepth) { + list($category->categories, $subcategories) = get_course_category_tree($category->id, $depth+1); + $categoryids = array_merge($categoryids, $subcategories); + $category->courses = array(); + } + } + + if ($depth > 0) { + // This is a recursive call so return the required array + return array($categories, $categoryids); + } + + // The depth is 0 this function has just been called so we can finish it off + + list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx'); + list($catsql, $catparams) = $DB->get_in_or_equal(array_keys($categoryids)); + $sql = "SELECT + c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.password,c.summary,c.guest,c.cost,c.currency,c.category + $ccselect + FROM {course} c + $ccjoin + WHERE c.category $catsql ORDER BY c.sortorder ASC"; + if ($courses = $DB->get_records_sql($sql, $catparams)) { + // loop throught them + foreach ($courses as $course) { + if ($course->id == SITEID) { + continue; + } + context_instance_preload($course); + if (!empty($course->visible) || has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) { + $categoryids[$course->category]->courses[$course->id] = $course; + } + } + } + return $categories; +} /** * Recursive function to print out all the categories in a nice format diff --git a/course/renderer.php b/course/renderer.php new file mode 100644 index 0000000000000..e23a9a65a282a --- /dev/null +++ b/course/renderer.php @@ -0,0 +1,157 @@ +. + +/** + * Renderer for use with the course section and all the goodness that falls + * within it. + * + * This renderer should contain methods useful to courses, and categories. + * + * @package moodlecore + * @copyright 2010 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * The core course renderer + * + * Can be retrieved with the following: + * $renderer = $PAGE->get_renderer('core','course'); + */ +class core_course_renderer extends plugin_renderer_base { + + /** + * A cache of strings + * @var stdClass + */ + protected $strings; + + /** + * Override the constructor so that we can initialise the string cache + * + * @param moodle_page $page + * @param string $target + */ + public function __construct(moodle_page $page, $target) { + $this->strings = new stdClass; + parent::__construct($page, $target); + } + + /** + * Renderers a structured array of courses and categories into a nice + * XHTML tree structure. + * + * This method was designed initially to display the front page course/category + * combo view. The structure can be retrieved by get_course_category_tree() + * + * @param array $structure + * @return string + */ + public function course_category_tree(array $structure) { + $this->strings->allowguests = get_string('allowguests'); + $this->strings->requireskey = get_string('requireskey'); + $this->strings->summary = get_string('summary'); + + // Generate an id and the required JS call to make this a nice widget + $id = html_writer::random_id('course_category_tree'); + $this->page->requires->js_init_call('M.util.init_toggle_class_on_click', array($id, '.category.with_children', 'collapsed')); + + // Start content generation + $content = html_writer::start_tag('div', array('class'=>'course_category_tree', 'id'=>$id)); + foreach ($structure as $category) { + $content .= $this->course_category_tree_category($category); + } + $content .= html_writer::start_tag('div', array('class'=>'controls')); + $content .= html_writer::tag('div', get_string('collapseall'), array('class'=>'addtoall expandall')); + $content .= html_writer::tag('div', get_string('expandall'), array('class'=>'removefromall collapseall')); + $content .= html_writer::end_tag('div'); + $content .= html_writer::end_tag('div'); + + // Return the course category tree HTML + return $content; + } + + /** + * Renderers a category for use with course_category_tree + * + * @param array $category + * @param int $depth + * @return string + */ + protected function course_category_tree_category(stdClass $category, $depth=1) { + $content = ''; + $hassubcategories = (count($category->categories)>0); + $hascourses = (count($category->courses)>0); + $classes = array('category'); + if ($category->parent != 0) { + $classes[] = 'subcategory'; + } + if ($hassubcategories || $hascourses) { + $classes[] = 'with_children'; + if ($depth > 1) { + $classes[] = 'collapsed'; + } + } + $content .= html_writer::start_tag('div', array('class'=>join(' ', $classes))); + $content .= html_writer::start_tag('div', array('class'=>'category_label')); + $content .= html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $category->name, array('class'=>'category_link')); + $content .= html_writer::end_tag('div'); + if ($hassubcategories) { + $content .= html_writer::start_tag('div', array('class'=>'subcategories')); + foreach ($category->categories as $subcategory) { + $content .= $this->course_category_tree_category($subcategory, $depth+1); + } + $content .= html_writer::end_tag('div'); + } + if ($hascourses) { + $content .= html_writer::start_tag('div', array('class'=>'courses')); + $coursecount = 0; + foreach ($category->courses as $course) { + $classes = array('course'); + $coursecount ++; + $classes[] = ($coursecount%2)?'odd':'even'; + $content .= html_writer::start_tag('div', array('class'=>join(' ', $classes))); + $content .= html_writer::link(new moodle_url('/course/view.php', array('id'=>$course->id)), $course->fullname, array('class'=>'course_link')); + $content .= html_writer::start_tag('div', array('class'=>'course_info clearfix')); + + if ($course->guest ) { + $image = html_writer::empty_tag('img', array('src'=>$this->output->pix_url('i/guest'), 'alt'=>$this->strings->allowguests, 'title'=>$this->strings->allowguests)); + $content .= html_writer::tag('div', $image, array('class'=>'course_info_spacer')); + } else { + $content .= html_writer::tag('div', '', array('class'=>'course_info_spacer')); + } + if ($course->password) { + $image = html_writer::empty_tag('img', array('src'=>$this->output->pix_url('i/key'), 'alt'=>$this->strings->requireskey, 'title'=>$this->strings->requireskey)); + $content .= html_writer::tag('div', $image, array('class'=>'course_info_spacer')); + } else { + $content .= html_writer::tag('div', '', array('class'=>'course_info_spacer')); + } + if ($course->summary) { + $image = html_writer::empty_tag('img', array('src'=>$this->output->pix_url('i/info'), 'alt'=>$this->strings->summary)); + $content .= html_writer::link(new moodle_url('/course/info.php', array('id'=>$course->id)), $image, array('title'=>$this->strings->summary)); + } else { + $content .= html_writer::tag('div', '', array('class'=>'course_info_spacer')); + } + $content .= html_writer::end_tag('div'); + $content .= html_writer::end_tag('div'); + } + $content .= html_writer::end_tag('div'); + } + $content .= html_writer::end_tag('div'); + return $content; + } +} \ No newline at end of file diff --git a/index.php b/index.php index 08f889fc67398..5da3ee2a97511 100644 --- a/index.php +++ b/index.php @@ -196,11 +196,9 @@ break; case FRONTPAGECATEGORYCOMBO: - echo $OUTPUT->heading(get_string('categories'), 2, 'headingblock header'); - echo $OUTPUT->box_start('generalbox categorybox'); - print_whole_category_list(NULL, NULL, NULL, -1, true); - echo $OUTPUT->box_end(); + $renderer = $PAGE->get_renderer('core','course'); + echo $renderer->course_category_tree(get_course_category_tree()); print_course_search('', false, 'short'); break; diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 7ecca02e172fe..02902f27dc6f7 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -250,6 +250,7 @@ $string['clicktohideshow'] = 'Click to expand or collapse'; $string['clicktochange'] = 'Click to change'; $string['closewindow'] = 'Close this window'; +$string['collapseall'] = 'Collapse all'; $string['commentincontext'] = 'Find this comment in context'; $string['comments'] = 'Comments'; $string['comparelanguage'] = 'Compare and edit current language'; @@ -675,6 +676,7 @@ $string['existingcreators'] = 'Existing course creators'; $string['existingstudents'] = 'Enrolled students'; $string['existingteachers'] = 'Existing teachers'; +$string['expandall'] = 'Expand all'; $string['expirynotify'] = 'Enrolment expiry notification'; $string['expirynotifyemail'] = 'The following students in this course are expiring after exactly {$a->threshold} days: @@ -1903,4 +1905,4 @@ $string['yourself'] = 'yourself'; $string['yourteacher'] = 'your {$a}'; $string['yourwordforx'] = 'Your word for \'{$a}\''; -$string['zippingbackup'] = 'Zipping backup'; +$string['zippingbackup'] = 'Zipping backup'; \ No newline at end of file diff --git a/lib/javascript-static.js b/lib/javascript-static.js index d7123858e7507..e1faaa423b477 100644 --- a/lib/javascript-static.js +++ b/lib/javascript-static.js @@ -393,6 +393,36 @@ M.util.init_frametop = function(Y) { }); }; +/** + * Finds all nodes that match the given CSS selector and attaches events to them + * so that they toggle a given classname when clicked. + * + * @param {YUI} Y + * @param {string} id An id containing elements to target + * @param {string} cssselector A selector to use to find targets + * @param {string} toggleclassname A classname to toggle + */ +M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname) { + var node = Y.one('#'+id); + node.all(cssselector).each(function(node){ + node.on('click', function(e){ + e.stopPropagation(); + if (e.target.get('nodeName')!='A' && e.target.get('nodeName')!='IMG') { + this.toggleClass(toggleclassname); + } + }, node); + }); + // Attach this click event to the node rather than all selectors... will be much better + // for performance + node.on('click', function(e){ + if (e.target.hasClass('addtoall')) { + node.all(cssselector).addClass(toggleclassname); + } else if (e.target.hasClass('removefromall')) { + node.all(cssselector+'.'+toggleclassname).removeClass(toggleclassname); + } + }, node); +} + //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code === function popupchecker(msg) { diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 4c75e15e9208f..fa366569be073 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -7021,6 +7021,7 @@ function get_core_subsystems() { 'condition' => NULL, 'completion' => NULL, 'countries' => NULL, + 'course' => 'course', 'currencies' => NULL, 'dbtransfer' => NULL, 'debug' => NULL, diff --git a/mod/assignment/index.php b/mod/assignment/index.php index 9d2d771132bb4..e4668637a3f4f 100644 --- a/mod/assignment/index.php +++ b/mod/assignment/index.php @@ -29,6 +29,7 @@ $PAGE->set_url('/mod/assignment/index.php', array('id'=>$course->id)); $PAGE->navbar->add($strassignments); $PAGE->set_title($strassignments); +$PAGE->set_heading($course->fullname); echo $OUTPUT->header(); if (!$cms = get_coursemodules_in_course('assignment', $course->id, 'cm.idnumber, m.assignmenttype, m.timedue')) { diff --git a/theme/base/style/course.css b/theme/base/style/course.css index 766125b27dd1f..fe36edc579643 100644 --- a/theme/base/style/course.css +++ b/theme/base/style/course.css @@ -85,4 +85,19 @@ .weeks-format, /* Window-width: 800 pixels.IE doesn't support, see inline IE conditional comment. */ .topics-format {margin-top: 8px;min-width: 763px;} #page-course-user .section {border-width:1px;border-style:solid;padding:10px;} -.categoryboxcontent {border-width:1px;border-style:solid;} \ No newline at end of file +.categoryboxcontent {border-width:1px;border-style:solid;} + +/* Course and category combo list on front page */ +.course_category_tree .controls {visibility: hidden;} +.course_category_tree .controls div {display:inline;cursor:pointer;} +.course_category_tree .category.with_children .category_label {background-image:url([[pix:moodle|t/expanded]]);background-repeat: no-repeat;} +.course_category_tree .category_label {padding-left:13px;} +.course_category_tree .category .courses .course_link {display:block;background-image:url([[pix:moodle|i/course]]);background-repeat: no-repeat;padding-left:18px;} +.course_category_tree .category .course {position:relative;} +.course_category_tree .category .course_info {position:absolute;right:0;top:0;} +.course_category_tree .category .course_info a, +.course_category_tree .category .course_info div {float:left;width:16px;height:16px;} +.jsenabled .course_category_tree .controls {visibility: visible;} +.jsenabled .course_category_tree .category.with_children.collapsed .category_label {background-image:url([[pix:moodle|t/collapsed]]);} +.jsenabled .course_category_tree .category.with_children.collapsed .subcategories, +.jsenabled .course_category_tree .category.with_children.collapsed .courses {display:none;} \ No newline at end of file diff --git a/theme/standard/style/course.css b/theme/standard/style/course.css index 99327f0b6e2b1..bc9281b505f84 100644 --- a/theme/standard/style/course.css +++ b/theme/standard/style/course.css @@ -84,4 +84,22 @@ .addcoursebutton {text-align:center;} .categorypicker {text-align:center;margin-bottom:10px;} .path-course-report-outline .loginfo {text-align:center;margin: 1em;} -.categorylist {width: 90%;margin:0 auto;text-align: left;} \ No newline at end of file +.categorylist {width: 90%;margin:0 auto;text-align: left;} + +/* Course and category combo list on front page */ +.course_category_tree .controls {margin-bottom:5px;text-align:right;float:right;} +.course_category_tree .controls div {padding-right:2em;font-size:75%;} +.course_category_tree .category {background-color:#FFF;background-image:url([[pix:theme|hgradient]]);background-repeat: repeat-x;border:1px solid #ddd;margin-bottom:10px;} +.course_category_tree .category .category {margin:5px;} +.course_category_tree .category .subcategories {background-color:inherit;padding-left:16px;border:1px solid #FFF;} +.course_category_tree .category.with_children .category_label {background-position:3px 3px;} +.course_category_tree .category_link .category_link {font-size:95%;} +.course_category_tree .category_label {padding-left:13px;} +.course_category_tree .category_link {display:block;margin:5px;font-size:120%;font-weight:bold;} +.course_category_tree .category .courses {background-color:inherit;padding-left:16px;} +.course_category_tree .category .courses .course_link {margin:5px;} +.course_category_tree .category .course {border:1px solid #f9f9f9;border-bottom-color: #eee;border-right-width:0;} +.course_category_tree .category .course:last-child {border-bottom-color:#f6f6f6;} +.course_category_tree .category .course.even {background-color:#f6f6f6;border-color:#eee;border-top-color: #f9f9f9;} +.course_category_tree .category .course_info {right:3px;top:3px;} +.course_category_tree .category .course:hover {background-color:#eee;} \ No newline at end of file