Commit 9a0604c4 authored by Richard Mansfield's avatar Richard Mansfield
Browse files

Block configuration ajax submission

parent 2456f8ae
......@@ -280,6 +280,7 @@ class BlockInstance {
private $id;
private $blocktype;
private $artefactplugin;
private $title;
private $configdata;
private $dirty;
......@@ -384,10 +385,22 @@ class BlockInstance {
$this->set('title', $title);
$this->commit();
$SESSION->add_ok_msg(get_string('blockinstanceconfiguredsuccessfully', 'view'));
$new = param_boolean('new');
$category = param_alpha('c', '');
redirect('/view/blocks.php?id=' . $this->get('view') . '&c=' . $category . '&new=' . $new);
$result = array(
'error' => false,
'message' => get_string('blockinstanceconfiguredsuccessfully', 'view'),
'data' => $this->render_editing(false, false, $form->submitted_by_js()),
'blockid' => $this->get('id'),
);
$redirect = '/view/blocks.php?id=' . $this->get('view');
if (param_boolean('new')) {
$redirect .= '&new=1';
}
if ($category = param_alpha('c', '')) {
$redirect .= '&c='. $category;
}
$result['goto'] = $redirect;
$form->reply(PIEFORM_OK, $result);
}
/**
......@@ -399,9 +412,11 @@ class BlockInstance {
* @return array Array with two keys: 'html' for raw html, 'javascript' for
* javascript to run
*/
public function render_editing($configure=false, $new=false) {
public function render_editing($configure=false, $new=false, $jsreply=false) {
safe_require('blocktype', $this->get('blocktype'));
$js = '';
$movecontrols = array();
if ($configure) {
list($content, $js) = array_values($this->build_configure_form($new));
}
......@@ -420,50 +435,50 @@ class BlockInstance {
log_debug($e->getMessage());
$content = '';
}
}
$movecontrols = array();
if (!defined('JSON')) {
if ($this->get('canmoveleft')) {
$movecontrols[] = array(
'column' => $this->get('column') - 1,
'order' => $this->get('order'),
'title' => get_string('moveblockleft', 'view'),
'arrow' => '←',
'dir' => 'left',
);
}
if ($this->get('canmovedown')) {
$movecontrols[] = array(
'column' => $this->get('column'),
'order' => $this->get('order') + 1,
'title' => get_string('moveblockdown', 'view'),
'arrow' => '↓',
'dir' => 'down',
);
}
if ($this->get('canmoveup')) {
$movecontrols[] = array(
'column' => $this->get('column'),
'order' => $this->get('order') - 1,
'title' => get_string('moveblockup', 'view'),
'arrow' => '↑',
'dir' => 'up',
);
}
if ($this->get('canmoveright')) {
$movecontrols[] = array(
'column' => $this->get('column') + 1,
'order' => $this->get('order'),
'title' => get_string('moveblockright', 'view'),
'arrow' => '→',
'dir' => 'right',
);
if (!defined('JSON') && !$jsreply) {
if ($this->get('canmoveleft')) {
$movecontrols[] = array(
'column' => $this->get('column') - 1,
'order' => $this->get('order'),
'title' => get_string('moveblockleft', 'view'),
'arrow' => '←',
'dir' => 'left',
);
}
if ($this->get('canmovedown')) {
$movecontrols[] = array(
'column' => $this->get('column'),
'order' => $this->get('order') + 1,
'title' => get_string('moveblockdown', 'view'),
'arrow' => '↓',
'dir' => 'down',
);
}
if ($this->get('canmoveup')) {
$movecontrols[] = array(
'column' => $this->get('column'),
'order' => $this->get('order') - 1,
'title' => get_string('moveblockup', 'view'),
'arrow' => '↑',
'dir' => 'up',
);
}
if ($this->get('canmoveright')) {
$movecontrols[] = array(
'column' => $this->get('column') + 1,
'order' => $this->get('order'),
'title' => get_string('moveblockright', 'view'),
'arrow' => '→',
'dir' => 'right',
);
}
}
}
$smarty = smarty_core();
$smarty->assign('id', $this->get('id'));
$smarty->assign('viewid', $this->get('view'));
$title = call_static_method(generate_class_name('blocktype', $this->get('blocktype')), 'override_instance_title', $this);
$smarty->assign('title', $title ? $title : $this->get('title'));
$smarty->assign('column', $this->get('column'));
......@@ -560,12 +575,17 @@ class BlockInstance {
'type' => 'hidden',
'value' => $this->get('id'),
),
// This form is never submitted by js, but if it was
// created by a json script, remember that in case the
// block is rendered again after a form error.
'js' => array(
'id' => array(
'type' => 'hidden',
'value' => $this->get('view'),
),
'change' => array(
'type' => 'hidden',
'value' => 1,
),
'new' => array(
'type' => 'hidden',
'value' => (bool) defined('JSON'),
'value' => $new,
),
),
$elements
......@@ -587,14 +607,22 @@ class BlockInstance {
'goto' => View::make_base_url(),
);
$configdirs = array(get_config('libroot') . 'form/');
if ($this->get('artefactplugin')) {
$configdirs[] = get_config('docroot') . 'artefact/' . $this->get('artefactplugin') . '/form/';
}
$form = array(
'name' => 'cb_' . $this->get('id'),
'renderer' => 'maharatable',
'validatecallback' => array(generate_class_name('blocktype', $this->get('blocktype')), 'instance_config_validate'),
'successcallback' => array($this, 'instance_config_store'),
'jsform' => true,
'jssuccesscallback' => 'blockConfigSuccess',
'elements' => $elements,
'viewgroup' => $this->get_view()->get('group'),
'viewinstitution' => $this->get_view()->get('institution'),
'configdirs' => $configdirs,
);
if (param_variable('action_acsearch_id_' . $this->get('id'), false)) {
......@@ -619,13 +647,21 @@ class BlockInstance {
}
}
$html = $pieform->build(false);
$html = $pieform->build();
// We probably need a new version of $pieform->build() that separates out the js
// Temporary evil hack:
if (preg_match('/<script type="text\/javascript">(new Pieform\(.*\);)<\/script>/', $html, $matches)) {
$js = "var pf_{$form['name']} = " . $matches[1] . "pf_{$form['name']}.init();";
}
else {
$js = '';
}
// We need to load any javascript required for the pieform. We do this
// by checking for an api function that has been added especially for
// the purpose, but that is not part of Pieforms. Maybe one day later
// it will be though
$js = '';
// $js = '';
foreach ($elements as $key => $element) {
$element['name'] = $key;
$function = 'pieform_element_' . $element['type'] . '_views_js';
......
......@@ -81,47 +81,6 @@ function ViewManager() {
removeElement('views-loading');
showElement(self.bottomPane);
// If there is a block already in configure mode, make it wider, and get the normal content
var configblockcontent = getFirstElementByTagAndClassName('div', 'configure');
if (configblockcontent) {
var blockinstance = getFirstParentByTagAndClassName(configblockcontent, 'div', 'blockinstance');
var blockinstanceId = blockinstance.id.substr(blockinstance.id.lastIndexOf('_') + 1);
var button = getFirstElementByTagAndClassName('input', 'configurebutton', blockinstance);
setNodeAttribute(button, 'disabled', 'disabled');
var configform = configblockcontent.innerHTML;
// Put a loading message in place while the content downloads
insertSiblingNodesBefore(configblockcontent, DIV({'id':'block-loading'}, IMG({'src': config.theme['images/loading.gif']}), ' ', get_string('loading')));
hideElement(configblockcontent);
sendjsonrequest('blockcontentediting.json.php', {'id':blockinstanceId}, 'POST', function(data) {
showElement(configblockcontent);
self.currentConfigureData = {
'contentdiv': configblockcontent,
'oldcontent': data.data,
'button' : button
};
removeElement($('block-loading'));
self.growBlock(blockinstance);
// Make the cancel button be supersmart
var cancelButton = $('cancel_cb_' + blockinstanceId + '_action_configureblockinstance_id_' + blockinstanceId);
connect(cancelButton, 'onclick', function(e) {
e.stop();
configblockcontent.innerHTML = data.data;
self.currentConfigureData = null;
removeNodeAttribute(button, 'disabled');
self.showMediaPlayers();
self.shrinkBlock(blockinstance);
});
}, function() {
removeElement($('block-loading'));
showElement(configblockcontent);
self.growBlock(blockinstance);
});
}
}
/**
......@@ -305,19 +264,6 @@ function ViewManager() {
this.getConfigureForm = function(blockinstance) {
var button = getFirstElementByTagAndClassName('input', 'configurebutton', blockinstance);
setNodeAttribute(button, 'disabled', 'disabled');
// If there is a configuration form open, close it. This is because
// each one shares the same form tag - the one for the entire form.
// We might be able to support having more than one form open at
// any one time, as long as we can detect precisely which form was
// submitted
if (self.currentConfigureData) {
self.currentConfigureData['contentdiv'].innerHTML = self.currentConfigureData['oldcontent'];
removeNodeAttribute(self.currentConfigureData['button'], 'disabled');
self.shrinkBlock(getFirstParentByTagAndClassName(self.currentConfigureData['contentdiv'], 'div', 'blockinstance'));
self.currentConfigureData = null;
}
var blockinstanceId = blockinstance.id.substr(blockinstance.id.lastIndexOf('_') + 1);
var contentDiv = getFirstElementByTagAndClassName('div', 'blockinstance-content', blockinstance);
......@@ -331,31 +277,17 @@ function ViewManager() {
replaceChildNodes(contentDiv, IMG({'src': config.theme['images/loading.gif']}), ' ', get_string('loading'));
sendjsonrequest('blocks.json.php', pd, 'POST', function(data) {
self.currentConfigureData = {
'contentdiv': contentDiv,
'oldcontent': oldContent,
'button' : button
};
self.growBlock(blockinstance);
contentDiv.innerHTML = data.data['html'];
eval(data.data.javascript);
contentDiv.innerHTML = oldContent;
self.addConfigureBlock(blockinstance, data.data);
$('action-dummy').name = getNodeAttribute(button, 'name');
// Make the cancel button be supersmart
var cancelButton = $('cancel_cb_' + blockinstanceId + '_action_configureblockinstance_id_' + blockinstanceId);
connect(cancelButton, 'onclick', function(e) {
e.stop();
contentDiv.innerHTML = oldContent;
self.currentConfigureData = null;
removeNodeAttribute(button, 'disabled');
self.removeConfigureBlocks();
self.showMediaPlayers();
self.shrinkBlock(blockinstance);
});
}, function() {
removeNodeAttribute(button, 'disabled');
});
}
......@@ -381,49 +313,64 @@ function ViewManager() {
}
this.growBlock = function(blockinstance) {
this.addConfigureBlock = function(oldblock, configblock, removeoncancel) {
self.hideMediaPlayers();
var width = getElementDimensions(blockinstance).w;
var left = getElementPosition(blockinstance).x;
hideElement(blockinstance);
var newwidth = 500;
var blockheader = getFirstElementByTagAndClassName('div', 'blockinstance-header', blockinstance);
var blockcontrols = getFirstElementByTagAndClassName('div', 'blockinstance-controls', blockinstance);
hideElement(blockheader);
insertSiblingNodesAfter(blockheader, DIV({'id':'blockconfig-header'}, scrapeText(blockheader) + ': ' + get_string('Configure')));
addElementClass(blockinstance, 'configure');
setStyle(blockinstance, {
'width': newwidth + 'px',
var temp = DIV();
temp.innerHTML = configblock.html;
var newblock = getFirstElementByTagAndClassName('div', 'blockinstance', temp);
hideElement(newblock);
appendChildNodes(getFirstElementByTagAndClassName('body'), newblock);
var d = getElementDimensions(newblock);
var vpdim = getViewportDimensions();
var newtop = getViewportPosition().y + Math.max((vpdim.h - d.h) / 2, 5);
setStyle(newblock, {
'width': d.w + 'px',
'left': (vpdim.w - d.w) / 2 + 'px',
'top': newtop + 'px',
'position': 'absolute',
'z-index': 1
});
// Move the block to the left to keep it above the old block
var newleft = (width - newwidth) / 2;
if (left + newleft < 0) {
newleft = 0;
} else if (left + newwidth > getViewportDimensions().w) {
newleft = width - newwidth;
var deletebutton = getFirstElementByTagAndClassName('input', 'deletebutton', newblock);
self.rewriteDeleteButton(deletebutton);
if (removeoncancel) {
var oldblockid = newblock.id.substr(0, newblock.id.length - '_configure'.length);
var blockinstanceId = oldblockid.substr(oldblockid.lastIndexOf('_') + 1);
var cancelbutton = $('cancel_cb_' + blockinstanceId + '_action_configureblockinstance_id_' + blockinstanceId);
if (cancelbutton) {
setNodeAttribute(cancelbutton, 'name', getNodeAttribute(deletebutton, 'name'));
disconnectAll(cancelbutton);
self.rewriteCancelButton(cancelbutton, blockinstanceId);
}
}
setStyle(blockinstance, {'left': newleft + 'px'});
showElement(blockinstance);
keepElementInViewport(blockinstance);
showElement(newblock);
eval(configblock.javascript);
}
this.shrinkBlock = function(blockinstance) {
hideElement(blockinstance);
removeElementClass(blockinstance, 'configure');
setStyle(blockinstance, {
'left': 0,
'width': 'auto'
});
var blockheader = getFirstElementByTagAndClassName('div', 'blockinstance-header', blockinstance);
var blockcontrols = getFirstElementByTagAndClassName('div', 'blockinstance-controls', blockinstance);
var configheader = $('blockconfig-header');
if (configheader) {
removeElement(configheader);
this.replaceConfigureBlock = function(data) {
var oldblock = $('blockinstance_' + data.blockid);
if (oldblock) {
var temp = DIV();
temp.innerHTML = data.data.html;
var newblock = getFirstElementByTagAndClassName('div', 'blockinstance', temp)
swapDOM(oldblock, newblock);
eval(data.data.javascript);
self.makeBlockinstanceDraggable(newblock);
self.rewriteConfigureButton(getFirstElementByTagAndClassName('input', 'configurebutton', newblock));
self.rewriteDeleteButton(getFirstElementByTagAndClassName('input', 'deletebutton', newblock));
}
showElement(blockheader);
showElement(blockinstance);
self.removeConfigureBlocks();
self.showMediaPlayers();
}
this.removeConfigureBlocks = function() {
// FF3 hangs unless you delay removal of the iframe inside the old configure block
callLater(0.0001, function () { forEach(getElementsByTagAndClassName('div', 'configure'), removeElement); });
}
/**
......@@ -445,14 +392,11 @@ function ViewManager() {
var pd = {'id': $('viewid').value, 'change': 1};
pd[getNodeAttribute(e.src(), 'name')] = 1;
sendjsonrequest(config['wwwroot'] + 'view/blocks.json.php', pd, 'POST', function(data) {
removeElement(getFirstParentByTagAndClassName(button, 'div', 'blockinstance'));
removeNodeAttribute(button, 'disabled');
if (self.currentConfigureData) {
self.currentConfigureData['contentdiv'].innerHTML = self.currentConfigureData['oldcontent'];
removeNodeAttribute(self.currentConfigureData['button'], 'disabled');
var blockinstanceId = button.name.substr(button.name.lastIndexOf('_') + 1);
removeElement('blockinstance_' + blockinstanceId);
if ($('blockinstance_' + blockinstanceId + '_configure')) {
self.removeConfigureBlocks();
self.showMediaPlayers();
self.shrinkBlock(getFirstParentByTagAndClassName(self.currentConfigureData['contentdiv'], 'div', 'blockinstance'));
self.currentConfigureData = null;
}
}, function() {
removeNodeAttribute(button, 'disabled');
......@@ -465,6 +409,25 @@ function ViewManager() {
});
}
/**
* Rewrites cancel button to remove a block
*/
this.rewriteCancelButton = function(button, blockinstanceId) {
connect(button, 'onclick', function(e) {
var pd = {'id': $('viewid').value, 'change': 1};
pd[getNodeAttribute(e.src(), 'name')] = 1;
sendjsonrequest(config['wwwroot'] + 'view/blocks.json.php', pd, 'POST', function(data) {
removeElement('blockinstance_' + blockinstanceId);
if ($('blockinstance_' + blockinstanceId + '_configure')) {
self.removeConfigureBlocks();
self.showMediaPlayers();
}
});
e.stop();
});
}
/**
* Rewrites the add column buttons to be AJAX
*
......@@ -971,36 +934,22 @@ function ViewManager() {
pd['action_addblocktype_column_' + whereTo['column'] + '_order_' + whereTo['order']] = true;
sendjsonrequest(config['wwwroot'] + 'view/blocks.json.php', pd, 'POST', function(data) {
var div = DIV();
div.innerHTML = data.data.html;
div.innerHTML = data.data.display.html;
var blockinstance = getFirstElementByTagAndClassName('div', 'blockinstance', div);
// Make configure button clickable, but disabled as blocks are rendered in configure mode by default
var configureButton = getFirstElementByTagAndClassName('input', 'configurebutton', blockinstance);
if (configureButton) {
self.rewriteConfigureButton(configureButton);
if (self.currentConfigureData) {
self.currentConfigureData['contentdiv'].innerHTML = self.currentConfigureData['oldcontent'];
removeNodeAttribute(self.currentConfigureData['button'], 'disabled');
self.shrinkBlock(getFirstParentByTagAndClassName(self.currentConfigureData['contentdiv'], 'div', 'blockinstance'));
self.currentConfigureData = null;
}
self.currentConfigureData = {
'contentdiv': getFirstElementByTagAndClassName('div', 'blockinstance-content', blockinstance),
'oldcontent': '',
'button' : configureButton
};
$('action-dummy').name = 'action_addblocktype_column_' + whereTo['column'] + '_order_' + whereTo['order'];
setNodeAttribute(configureButton, 'disabled', 'disabled');
}
self.rewriteDeleteButton(getFirstElementByTagAndClassName('input', 'deletebutton', blockinstance));
self.makeBlockinstanceDraggable(blockinstance);
insertSiblingNodesAfter(self.blockPlaceholder, blockinstance);
removeElement(self.blockPlaceholder);
eval(data.data.javascript);
if (configureButton) {
self.growBlock(blockinstance);
if (data.data.configure) {
self.addConfigureBlock(blockinstance, data.data.configure, true);
}
});
......@@ -1122,10 +1071,6 @@ function ViewManager() {
// it is dropped. Needs a margin the same as the blockinstances
this.blockPlaceholder = DIV({'id': 'block-placeholder'});
// A container for information about the blockinstance current being
// configured
this.currentConfigureData = null;
// The column container - set in self.init
this.columnContainer = null;
......@@ -1143,3 +1088,13 @@ function ViewManager() {
viewManager = new ViewManager();
viewManager.addCSSRules();
function blockConfigSuccess(form, data) {
if (data.formelementsuccess) {
eval(data.formelementsuccess + '(form, data)');
}
if (data.blockid) {
viewManager.replaceConfigureBlock(data);
}
}
......@@ -206,7 +206,8 @@ $string['moveblockdown'] = 'Move this block down';
$string['moveblockup'] = 'Move this block up';
$string['moveblockright'] = 'Move this block right';
$string['Configure'] = 'Configure';
$string['configureblock'] = 'Configure this block';
$string['configureblock'] = 'Configure block';
$string['configurethisblock'] = 'Configure this block';
$string['removeblock'] = 'Remove this block';
$string['blocktitle'] = 'Block Title';
......
......@@ -70,7 +70,9 @@ function pieform_element_tinywysiwyg_get_value(Pieform $form, $element) {
function pieform_element_tinywysiwyg_views_js(Pieform $form, $element) {
global $USER;
if ($USER->get_account_preference('wysiwyg') || defined('PUBLIC')) {
return 'tinyMCE.execCommand("mceAddControl", true, ' . json_encode($form->get_name() . '_' . $element['name']) . ');';
$formname = json_encode($form->get_name());
$editor = json_encode($form->get_name() . '_' . $element['name']);
return "\ntinyMCE.idCounter=0;tinyMCE.execCommand('mceAddControl', true, $editor);PieformManager.connect('onsubmit', $formname, tinyMCE.triggerSave);";
}
return '';
}
......
......@@ -1055,13 +1055,9 @@ class View {
if (!in_array($blockinstance->get('blocktype'), $installed)) {
continue; // this plugin has been disabled
}
$configure = ($blockinstance->get('id') == $this->blockinstance_currently_being_configured);
$result = $blockinstance->$renderfunction($configure, false);
$result = $blockinstance->$renderfunction();
if ($editing) {
$blockcontent .= $result['html'];
if ($configure) {
$blockcontent .= '<script type="text/javascript">' . $result['javascript'] . '</script>';
}
// NOTE: build_column is always called in the context of column
// operations, so the javascript returned, which is currently
// for configuring block instances only, is not necessary
......@@ -1133,8 +1129,14 @@ class View {
$this->dirtycolumns[$values['column']] = 1;
if ($values['returndata']) {
// Make sure it's in configure mode if it has configuration
return $bi->render_editing(call_static_method(generate_class_name('blocktype', $values['blocktype']), 'has_instance_config'), true);
// Return new block rendered in both configure mode and (editing) display mode
$result = array(
'display' => $bi->render_editing(false, true),
);
if (call_static_method(generate_class_name('blocktype', $values['blocktype']), 'has_instance_config')) {
$result['configure'] = $bi->render_editing(true, true);
}
return $result;
}
}
......@@ -1263,7 +1265,7 @@ class View {
public function configureblockinstance($values) {
require_once(get_config('docroot') . 'blocktype/lib.php');
$bi = new BlockInstance($values['id']);
return $bi->build_configure_form();
return $bi->render_editing(true);
}
/**
......
......@@ -76,23 +76,23 @@
}
/* styling the tabs for the artefact chooser */
.maincontent ul.artefactchooser-tabs {
ul.artefactchooser-tabs {
line-height: 24px;