From 00a24d44f7dc3b01501129711ee01224aca14b94 Mon Sep 17 00:00:00 2001 From: tjhunt Date: Thu, 30 Jul 2009 08:22:12 +0000 Subject: [PATCH] blocks: MDL-19893 move blocks on page UI - part 1 This does all the UI. It does not yet update the DB when you click to complete the action. --- blocks/moodleblock.class.php | 2 +- lang/en_utf8/block.php | 2 + lib/blocklib.php | 288 ++++++++++++++++++------------- lib/outputlib.php | 50 +++++- theme/standard/styles_color.css | 14 ++ theme/standard/styles_layout.css | 16 ++ 6 files changed, 247 insertions(+), 125 deletions(-) diff --git a/blocks/moodleblock.class.php b/blocks/moodleblock.class.php index b161989a7a..5d87a7cc7c 100644 --- a/blocks/moodleblock.class.php +++ b/blocks/moodleblock.class.php @@ -381,7 +381,7 @@ class block_base { } if ($this->page->user_is_editing()) { - $bc->controls = block_edit_controls($this, $this->page); + $bc->controls = $this->page->blocks->edit_controls($this); } if ($this->is_empty() && !$bc->controls) { diff --git a/lang/en_utf8/block.php b/lang/en_utf8/block.php index ce33f3a619..70b576f73e 100644 --- a/lang/en_utf8/block.php +++ b/lang/en_utf8/block.php @@ -8,6 +8,8 @@ $string['bracketfirst'] = '$a (first)'; $string['bracketlast'] = '$a (last)'; $string['defaultregion'] = 'Default region'; $string['defaultweight'] = 'Default weight'; +$string['moveblockhere'] = 'Move block here'; +$string['movingthisblockcancel'] = 'Moving this block ($a)'; $string['onthispage'] = 'On this page'; $string['pagetypes'] = 'Page types'; $string['region'] = 'Region'; diff --git a/lib/blocklib.php b/lib/blocklib.php index d16ab6cfd9..6787a37edf 100644 --- a/lib/blocklib.php +++ b/lib/blocklib.php @@ -131,6 +131,17 @@ class block_manager { */ protected $extracontent = array(); + /** + * Used by the block move id, to track whether a block is cuurently being moved. + * + * Whe you click on the move icon of a block, first the page needs to reload with + * extra UI for chooseing a new position for a particular block. In that situation + * this field holds the id of the block being moved. + * + * @var integer|null + */ + protected $movingblock = null; + /// Constructor ================================================================ /** @@ -260,6 +271,18 @@ class block_manager { return $this->visibleblockcontent[$region]; } + /** + * Helper method used by get_content_for_region. + * @param string $region region name + * @param float $weight weight. May be fractional, since you may want to move a block + * between ones with weight 2 and 3, say ($weight would be 2.5). + * @return string URL for moving block $this->movingblock to this position. + */ + protected function get_move_target_url($region, $weight) { + return $this->page->url->out(false, array('bui_moveid' => $this->movingblock, + 'bui_newregion' => $region, 'bui_newweight' => $weight, 'sesskey' => sesskey()), false); + } + /** * Determine whether a region contains anything. (Either any real blocks, or * the add new block UI.) @@ -392,7 +415,6 @@ class block_manager { } } - // The code here needs to be consistent with the code in block_load_for_page. if (is_null($includeinvisible)) { $includeinvisible = $this->page->user_is_editing(); } @@ -683,19 +705,59 @@ class block_manager { } /** - * Return an array of content vars from a set of block instances + * Return an array of content objects from a set of block instances * * @param array $instances An array of block instances - * @return array An array of content vars + * @param moodle_renderer_base The renderer to use. + * @param string $region the region name. + * @return array An array of block_content (and possibly block_move_target) objects. */ - protected function create_block_contents($instances, $output) { + protected function create_block_contents($instances, $output, $region) { $results = array(); + + $lastweight = 0; + $lastblock = 0; + if ($this->movingblock) { + $first = reset($instances); + if ($first) { + $lastweight = $first->instance->weight - 2; + } + + $strmoveblockhere = get_string('moveblockhere', 'block'); + } + foreach ($instances as $instance) { $content = $instance->get_content_for_output($output); - if (!empty($content)) { - $results[] = $content; + if (empty($content)) { + continue; + } + + if ($this->movingblock && $lastweight != $instance->instance->weight && + $content->blockinstanceid != $this->movingblock && $lastblock != $this->movingblock) { + $bmt = new block_move_target(); + $bmt->text = $strmoveblockhere; + $bmt->url = $this->get_move_target_url($region, ($lastweight + $instance->instance->weight)/2); + $results[] = $bmt; + } + + if ($content->blockinstanceid == $this->movingblock) { + $content->add_class('beingmoved'); + $content->annotation .= get_string('movingthisblockcancel', 'block', + $output->link($this->page->url, get_string('cancel'))); } + + $results[] = $content; + $lastweight = $instance->instance->weight; + $lastblock = $instance->instance->id; + } + + if ($this->movingblock && $lastblock != $this->movingblock) { + $bmt = new block_move_target(); + $bmt->text = $strmoveblockhere; + $bmt->url = $this->get_move_target_url($region, $lastweight + 1); + $results[] = $bmt; } + return $results; } @@ -724,7 +786,7 @@ class block_manager { if (array_key_exists($region, $this->extracontent)) { $contents = $this->extracontent[$region]; } - $contents = array_merge($contents, $this->create_block_contents($this->blockinstances[$region], $output)); + $contents = array_merge($contents, $this->create_block_contents($this->blockinstances[$region], $output, $region)); if ($region == $this->defaultregion) { $addblockui = block_add_block_ui($this->page, $output); if ($addblockui) { @@ -737,6 +799,58 @@ class block_manager { /// Process actions from the URL =============================================== + /** + * Get the appropriate list of editing icons for a block. This is used + * to set {@link block_contents::$controls} in {@link block_base::get_contents_for_output()}. + * + * @param $output The core_renderer to use when generating the output. (Need to get icon paths.) + * @return an array in the format for {@link block_contents::$controls} + */ + public function edit_controls($block) { + global $CFG; + + $controls = array(); + $actionurl = $this->page->url->out(false, array('sesskey'=> sesskey()), false); + + // Assign roles icon. + if (has_capability('moodle/role:assign', $block->context)) { + $controls[] = array('url' => $CFG->wwwroot . '/' . $CFG->admin . + '/roles/assign.php?contextid=' . $block->context->id . '&returnurl=' . urlencode($this->page->url->out_returnurl()), + 'icon' => 'i/roles', 'caption' => get_string('assignroles', 'role')); + } + + if ($this->page->user_can_edit_blocks()) { + // Show/hide icon. + if ($block->instance->visible) { + $controls[] = array('url' => $actionurl . '&bui_hideid=' . $block->instance->id, + 'icon' => 't/hide', 'caption' => get_string('hide')); + } else { + $controls[] = array('url' => $actionurl . '&bui_showid=' . $block->instance->id, + 'icon' => 't/show', 'caption' => get_string('show')); + } + } + + if ($this->page->user_can_edit_blocks() || $block->user_can_edit()) { + // Edit config icon - always show - needed for positioning UI. + $controls[] = array('url' => $actionurl . '&bui_editid=' . $block->instance->id, + 'icon' => 't/edit', 'caption' => get_string('configuration')); + } + + if ($this->page->user_can_edit_blocks() && $block->user_can_edit() && $block->user_can_addto($this->page)) { + // Delete icon. + $controls[] = array('url' => $actionurl . '&bui_deleteid=' . $block->instance->id, + 'icon' => 't/delete', 'caption' => get_string('delete')); + } + + if ($this->page->user_can_edit_blocks()) { + // Move icon. + $controls[] = array('url' => $actionurl . '&bui_moveid=' . $block->instance->id, + 'icon' => 't/move', 'caption' => get_string('move')); + } + + return $controls; + } + /** * Process any block actions that were specified in the URL. * @@ -746,8 +860,12 @@ class block_manager { * @return boolean true if anything was done. False if not. */ public function process_url_actions() { + if (!$this->page->user_is_editing()) { + return false; + } return $this->process_url_add() || $this->process_url_delete() || - $this->process_url_show_hide() || $this->process_url_edit(); + $this->process_url_show_hide() || $this->process_url_edit() || + $this->process_url_move(); } /** @@ -762,7 +880,7 @@ class block_manager { confirm_sesskey(); - if (!$this->page->user_is_editing() && !$this->page->user_can_edit_blocks()) { + if ($this->page->user_can_edit_blocks()) { throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('addblock')); } @@ -956,6 +1074,45 @@ class block_manager { exit; } } + + /** + * Handle showing/processing the submission from the block editing form. + * @return boolean true if the form was submitted and the new config saved. Does not + * return if the editing form was displayed. False otherwise. + */ + public function process_url_move() { + global $CFG, $DB, $PAGE; + + $blockid = optional_param('bui_moveid', null, PARAM_INTEGER); + if (!$blockid) { + return false; + } + + confirm_sesskey(); + + $block = $this->find_instance($blockid); + + if (!$this->page->user_can_edit_blocks()) { + throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock')); + } + + $newregion = optional_param('bui_newregion', '', PARAM_ALPHANUMEXT); + $newweight = optional_param('bui_newweight', null, PARAM_FLOAT); + if (!$newregion || is_null($newweight)) { + // Don't have a valid target position yet, must be just starting the move. + $this->movingblock = $blockid; + $this->page->ensure_param_not_in_url('bui_moveid'); + return false; + } + + // Work out if we should be setting defaultposition or just position on this page. + // TODO$block = $this-> + + $this->page->ensure_param_not_in_url('bui_moveid'); + $this->page->ensure_param_not_in_url('bui_newregion'); + $this->page->ensure_param_not_in_url('bui_newweight'); + return true; + } } /// Helper functions for working with block classes ============================ @@ -975,66 +1132,6 @@ function block_method_result($blockname, $method, $param = NULL) { return call_user_func(array('block_'.$blockname, $method), $param); } -/** - * Load a block instance, with position information about where that block appears - * on a given page. - * - * @param integer$blockid the block_instance.id. - * @param moodle_page $page the page the block is appearing on. - * @return block_base the requested block. - */ -function block_load_for_page($blockid, $page) { - global $DB; - - // The code here needs to be consistent with the code in block_manager::load_blocks. - $params = array( - 'blockinstanceid' => $blockid, - 'subpage' => $page->subpage, - 'contextid' => $page->context->id, - 'pagetype' => $page->pagetype, - 'contextblock' => CONTEXT_BLOCK, - ); - $sql = "SELECT - bi.id, - bp.id AS blockpositionid, - bi.blockname, - bi.parentcontextid, - bi.showinsubcontexts, - bi.pagetypepattern, - bi.subpagepattern, - bi.defaultregion, - bi.defaultweight, - COALESCE(bp.visible, 1) AS visible, - COALESCE(bp.region, bi.defaultregion) AS region, - COALESCE(bp.weight, bi.defaultweight) AS weight, - bi.configdata, - ctx.id AS ctxid, - ctx.path AS ctxpath, - ctx.depth AS ctxdepth, - ctx.contextlevel AS ctxlevel - - FROM {block_instances} bi - JOIN {block} b ON bi.blockname = b.name - LEFT JOIN {block_positions} bp ON bp.blockinstanceid = bi.id - AND bp.contextid = :contextid - AND bp.pagetype = :pagetype - AND bp.subpage = :subpage - JOIN {context} ctx ON ctx.contextlevel = :contextblock - AND ctx.instanceid = bi.id - - WHERE - bi.id = :blockinstanceid - AND b.visible = 1 - - ORDER BY - COALESCE(bp.region, bi.defaultregion), - COALESCE(bp.weight, bi.defaultweight), - bi.id"; - $bi = $DB->get_record_sql($sql, $params, MUST_EXIST); - $bi = make_context_subobj($bi); - return block_instance($bi->blockname, $bi, $page); -} - /** * Creates a new object of the specified block class. * @@ -1149,59 +1246,6 @@ function block_add_block_ui($page, $output) { return $bc; } -/** - * Get the appropriate list of editing icons for a block. This is used - * to set {@link block_contents::$controls} in {@link block_base::get_contents_for_output()}. - * - * @param $output The core_renderer to use when generating the output. (Need to get icon paths.) - * @return an array in the format for {@link block_contents::$controls} - * @since Moodle 2.0. - */ -function block_edit_controls($block, $page) { - global $CFG; - - $controls = array(); - $actionurl = $page->url->out(false, array('sesskey'=> sesskey()), false); - - // Assign roles icon. - if (has_capability('moodle/role:assign', $block->context)) { - $controls[] = array('url' => $CFG->wwwroot . '/' . $CFG->admin . - '/roles/assign.php?contextid=' . $block->context->id . '&returnurl=' . urlencode($page->url->out_returnurl()), - 'icon' => 'i/roles', 'caption' => get_string('assignroles', 'role')); - } - - if ($page->user_can_edit_blocks()) { - // Show/hide icon. - if ($block->instance->visible) { - $controls[] = array('url' => $actionurl . '&bui_hideid=' . $block->instance->id, - 'icon' => 't/hide', 'caption' => get_string('hide')); - } else { - $controls[] = array('url' => $actionurl . '&bui_showid=' . $block->instance->id, - 'icon' => 't/show', 'caption' => get_string('show')); - } - } - - if ($page->user_can_edit_blocks() || $block->user_can_edit()) { - // Edit config icon - always show - needed for positioning UI. - $controls[] = array('url' => $actionurl . '&bui_editid=' . $block->instance->id, - 'icon' => 't/edit', 'caption' => get_string('configuration')); - } - - if ($page->user_can_edit_blocks() && $block->user_can_edit() && $block->user_can_addto($page)) { - // Delete icon. - $controls[] = array('url' => $actionurl . '&bui_deleteid=' . $block->instance->id, - 'icon' => 't/delete', 'caption' => get_string('delete')); - } - - if ($page->user_can_edit_blocks()) { - // Move icon. - $controls[] = array('url' => $page->url->out(false, array('moveblockid' => $block->instance->id)), - 'icon' => 't/move', 'caption' => get_string('move')); - } - - return $controls; -} - // Functions that have been deprecated by block_manager ======================= /** diff --git a/lib/outputlib.php b/lib/outputlib.php index a9a441a803..839a0d3142 100644 --- a/lib/outputlib.php +++ b/lib/outputlib.php @@ -2322,6 +2322,7 @@ class moodle_core_renderer extends moodle_renderer_base { $output .= $this->output_end_tag('div'); $output .= $this->output_end_tag('div'); + if ($bc->annotation) { $output .= $this->output_tag('div', array('class' => 'blockannotation'), $bc->annotation); } @@ -2378,13 +2379,30 @@ class moodle_core_renderer extends moodle_renderer_base { */ public function blocks_for_region($region) { $blockcontents = $this->page->blocks->get_content_for_region($region, $this); + $output = ''; foreach ($blockcontents as $bc) { - $output .= $this->block($bc, $region); + if ($bc instanceof block_contents) { + $output .= $this->block($bc, $region); + } else if ($bc instanceof block_move_target) { + $output .= $this->block_move_target($bc); + } else { + throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.'); + } } return $output; } + /** + * Output a place where the block that is currently being moved can be dropped. + * @param block_move_target $target with the necessary details. + * @return string the HTML to be output. + */ + public function block_move_target($target) { + return $this->output_tag('a', array('href' => $target->url, 'class' => 'blockmovetarget'), + $this->output_tag('span', array('class' => 'accesshide'), $target->text)); + } + /** * Given a html_textarea object, outputs an tag that uses the object's attributes. * @@ -2404,7 +2422,7 @@ class moodle_core_renderer extends moodle_renderer_base { * @return string HTML fragment */ public function link($link, $text=null) { - $attributes = array('href' => $link); + $attributes = array(); if (is_a($link, 'html_link')) { $link->prepare(); @@ -2418,6 +2436,9 @@ class moodle_core_renderer extends moodle_renderer_base { } else if (empty($text)) { throw new coding_exception('$OUTPUT->link() must have a string as second parameter if the first param ($link) is a string'); + + } else { + $attributes['href'] = prepare_url($link); } return $this->output_tag('a', $attributes, $text); @@ -3992,6 +4013,31 @@ class block_contents extends moodle_html_component { } +/** + * This class represents a target for where a block can go when it is being moved. + * + * This needs to be rendered as a form with the given hidden from fields, and + * clicking anywhere in the form should submit it. The form action should be + * $PAGE->url. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class block_move_target extends moodle_html_component { + /** + * List of hidden form fields. + * @var array + */ + public $url = array(); + /** + * List of hidden form fields. + * @var array + */ + public $text = ''; +} + + /** * Holds all the information required to render a by * {@see moodle_core_renderer::table()} or by an overridden version of that diff --git a/theme/standard/styles_color.css b/theme/standard/styles_color.css index 6acbb00c74..1d93c2d7f4 100644 --- a/theme/standard/styles_color.css +++ b/theme/standard/styles_color.css @@ -491,7 +491,21 @@ table.flexible .r1 { .block-region .hidden .header { border-bottom-color: #dddddd; } +.blockannotation { + color:#aaa; +} +.blockmovetarget { + background-color: #fcc; + border-color: #f88; +} +.blockmovetarget:hover { + background-color: #f88; + border-color: #c00; +} +.sideblock.beingmoved { + border-color: #f88; +} /*** *** Blogs diff --git a/theme/standard/styles_layout.css b/theme/standard/styles_layout.css index 711b2c57a4..f522f24bd0 100644 --- a/theme/standard/styles_layout.css +++ b/theme/standard/styles_layout.css @@ -1610,6 +1610,22 @@ a.skip:focus, a.skip:active { .sideblock .header .icon.edit { margin-right: 6px; } +.blockannotation { + font-size:0.75em; + margin: -1em 0 1em; +} + +.blockmovetarget { + display: block; + height: 1em; + margin-bottom: 1em; + border-width: 2px; + border-style: dashed; +} +.sideblock.beingmoved { + border-width: 2px; + border-style: dashed; +} .sideblock .content { padding: 4px; -- 2.39.5