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.
This commit is contained in:
tjhunt 2009-07-30 08:22:12 +00:00
parent 52fa87553e
commit 00a24d44f7
6 changed files with 247 additions and 125 deletions

View File

@ -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) {

View File

@ -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';

View File

@ -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 =======================
/**

View File

@ -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 <a> 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 <table> by
* {@see moodle_core_renderer::table()} or by an overridden version of that

View File

@ -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

View File

@ -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;