Textpattern CMS support forum
You are not logged in. Register | Login | Help
- Topics: Active | Unanswered
Re: New sections tab, multi-edit control block
Gocom wrote:
but that isn’t probably what Robert would prefer.
Have you ever pursued a mind-reading career?
What about… derp. Multidimensional arrays:
Fits perfectly into the existing code base.
- n.selectInput('edit_method', $methods, $method)
+ n.selectInput('edit_method', $methods, $method).
Offline
Re: New sections tab, multi-edit control block
@philippe
I was suggesting the alt key and it’s PC equivalent.
If you all decide to keep the row one click select function then I’d like a preference added to turn that feature on/off too. With a default of off.
Offline
Re: New sections tab, multi-edit control block
Here’s a patch that implements all of the current multi-edit code to the core and updates Sections panel to use it.
Index: include/txp_section.php
===================================================================
--- include/txp_section.php (revision 3847)
+++ include/txp_section.php (working copy)
@@ -163,13 +163,13 @@
if ($rs)
{
echo n.'<div id="'.$event.'_container" class="txp-container">';
- echo n.n.'<form action="index.php" id="section_form" method="post" name="longform" onsubmit="return verify(\''.gTxt('are_you_sure').'\')">'.
+ echo n.n.'<form action="index.php" id="section_form" class="multi_edit_form" method="post" name="longform" onsubmit="return verify(\''.gTxt('are_you_sure').'\')">'.
n.'<div class="txp-listtables">'.n.
n.startTable('', '', 'txp-list').
n.'<thead>'.
n.tr(
- n.hCell(fInput('checkbox', 'selected_toggle', 0, '', '', '', '', '', 'selected_toggle'), '', ' title="'.gTxt('toggle_all_selected').'" class="multi-edit"').
+ n.hCell(fInput('checkbox', 'select_all', 0, '', '', '', '', '', 'select_all'), '', ' title="'.gTxt('toggle_all_selected').'" class="multi-edit"').
n.column_head('name', 'name', 'section', true, $switch_dir, $crit, $search_method, (('name' == $sort) ? "$dir " : '').'name').
n.column_head('title', 'title', 'section', true, $switch_dir, $crit, $search_method, (('title' == $sort) ? "$dir " : '').'name').
n.column_head('page', 'page', 'section', true, $switch_dir, $crit, $search_method, (('page' == $sort) ? "$dir " : '').'page').
@@ -224,10 +224,7 @@
echo '</tbody>'.
n.endTable().
- n.graf(
- select_buttons().n.
- section_multiedit_form($page, $sort, $dir, $crit, $search_method)
- , ' class="multi-edit"').
+ n.section_multiedit_form($page, $sort, $dir, $crit, $search_method).
n.'</div>'.
n.tInput().
@@ -603,16 +600,19 @@
function section_multiedit_form($page, $sort, $dir, $crit, $search_method)
{
+ $pages = safe_column('name', 'txp_page', '1=1');
+ $styles = safe_column('name', 'txp_css', '1=1');
+
$methods = array(
- 'changepage' => gTxt('uses_page'),
- 'changecss' => gTxt('uses_style'),
- 'changeonfrontpage' => gTxt('on_front_page'),
- 'changesyndicate' => gTxt('syndicate'),
- 'changesearchable' => gTxt('include_in_search'),
+ 'changepage' => array('label' => gTxt('uses_page'), 'html' => selectInput('page', $pages, '', false)),
+ 'changecss' => array('label' => gTxt('uses_style'), 'html' => selectInput('css', $styles, '', false)),
+ 'changeonfrontpage' => array('label' => gTxt('on_front_page'), 'html' => yesnoRadio('on_front_page', 1)),
+ 'changesyndicate' => array('label' => gTxt('syndicate'), 'html' => yesnoRadio('in_rss', 1)),
+ 'changesearchable' => array('label' => gTxt('include_in_search'), 'html' => yesnoRadio('searchable', 1)),
'delete' => gTxt('delete'),
);
- return event_multiedit_form('section', $methods, $page, $sort, $dir, $crit, $search_method);
+ return multi_edit($methods, 'section', 'section_multi_edit', $page, $sort, $dir, $crit, $search_method);
}
// -------------------------------------------------------------
Index: textpattern.js
===================================================================
--- textpattern.js (revision 3847)
+++ textpattern.js (working copy)
@@ -43,9 +43,11 @@
popped.focus();
}
-// -------------------------------------------------------------
-// basic confirmation for potentially powerful choice
-// (like deletion, for example)
+/**
+ * Basic confirmation for potentially powerful choice (like deletion, for example)
+ * @param string msg
+ * @return bool
+ */
function verify(msg)
{
@@ -54,6 +56,7 @@
/**
* Selects all multi-edit checkboxes
+ * @deprecated
*/
function selectall()
@@ -63,6 +66,7 @@
/**
* De-selects all multi-edit checkboxes
+ * @deprecated
*/
function deselectall()
@@ -72,6 +76,7 @@
/**
* Selects a range of multi-edit checkboxes
+ * @deprecated
*/
function selectrange()
@@ -95,6 +100,7 @@
/**
* Toggles the current selection of multi-edit checkboxes
+ * @deprecated
*/
function selecttoggle()
@@ -106,6 +112,7 @@
/**
* Toggles the entire column of multi-edit checkboxes
+ * @deprecated
*/
function toggleAll()
@@ -118,8 +125,10 @@
}
}
-// -------------------------------------------------------------
-// ?
+/**
+ * ?
+ * @deprecated
+ */
function cleanSelects()
{
@@ -131,6 +140,351 @@
}
}
+/**
+ * Multi-edit functions
+ * @param string|obj method
+ * @param obj options
+ * @since 4.5.0
+ */
+
+jQuery.fn.txpMultiEditForm = function(method, options)
+{
+ var args = {}, opt;
+
+ var defaults = {
+ 'checkbox' : 'input[name="selected[]"][type=checkbox]',
+ 'rows' : 'tbody td',
+ 'row' : 'tr, p, div',
+ 'selectedClass' : 'selected',
+ 'actions' : 'select[name=edit_method]',
+ 'selectAll' : 'input[name="select_all"][type=checkbox]',
+ 'rowClick' : true,
+ 'altClick' : true
+ };
+
+ if ($.type(method) !== 'string')
+ {
+ options = method;
+ method = null;
+ }
+ else
+ {
+ args = options;
+ }
+
+ opt = options;
+
+ this.closest('form').each(function() {
+
+ var $this = $(this), form = {}, public = {}, private = {};
+
+ if ($this.data('_txpMultiEdit'))
+ {
+ form = $this.data('_txpMultiEdit');
+ opt = $.extend(form.opt, opt);
+ }
+
+ else
+ {
+ opt = $.extend(defaults, opt);
+ form.pattern = opt.checkbox;
+ form.editMethod = $this.find(opt.actions);
+ form.lastCheck = null;
+ form.opt = opt;
+ }
+
+ /**
+ * Registers multi-edit options
+ * @param string label
+ * @param string value HTML Option's value
+ * @param obj|string html Object or HTML markup used as for the action's second step. NULL to skip 2nd step.
+ * @return obj this
+ */
+
+ public.addOption = function(options)
+ {
+ var settings = $.extend({
+ 'label' : null,
+ 'value' : null,
+ 'html' : null
+ }, options);
+
+ var option = form.editMethod.find('option').filter(function() {
+ return $(this).attr('value') === settings.value;
+ });
+
+ var exists = (option.length > 0);
+ form.editMethod.val('');
+
+ if (!exists)
+ {
+ option = $('<option />');
+ }
+
+ if (!option.data('method'))
+ {
+ if (!option.attr('value'))
+ {
+ option.attr('value', settings.value);
+ }
+
+ if (!option.text() && settings.label)
+ {
+ option.text(settings.label);
+ }
+
+ option.data('method', settings.html);
+ }
+
+ if (!exists)
+ {
+ form.editMethod.append(option);
+ }
+
+ return public;
+ };
+
+ /**
+ * Selects rows based on supplied arguments. Only one of the filters applies at time.
+ * @param array index Select based on row's index.
+ * @param array range [min, max] Select based on index range.
+ * @param array value [value1, value2, value3, ...]
+ * @param bool checked Set matched checked or unchecked. FALSE to uncheck.
+ */
+
+ public.select = function(options)
+ {
+ var settings = $.extend({
+ 'index' : null,
+ 'range' : null,
+ 'value' : null,
+ 'checked' : true
+ }, options);
+
+ var obj = $this.find(form.pattern);
+
+ if (settings.value !== null)
+ {
+ obj = obj.filter(function() {
+ return $.inArray($(this).attr('value'), settings.value) !== -1;
+ });
+ }
+
+ else if (settings.index !== null)
+ {
+ obj = obj.filter(function(index) {
+ return $.inArray(index, settings.index) !== -1;
+ });
+ }
+
+ else if (settings.range !== null)
+ {
+ obj = obj.slice(settings.range[0], settings.range[1]);
+ }
+
+ obj.prop('checked', settings.checked).change();
+ return public;
+ };
+
+ /**
+ * Binds checkboxes
+ */
+
+ private.bindRows = function()
+ {
+ form.rows = $this.find(opt.rows);
+ form.boxes = $this.find(form.pattern);
+ return private;
+ };
+
+ /**
+ * Highlights selected rows
+ */
+
+ private.highlight = function()
+ {
+ form.boxes.filter(':checked').closest(opt.row).addClass(opt.selectedClass);
+ form.boxes.filter(':not(:checked)').closest(opt.row).removeClass(opt.selectedClass);
+ return private;
+ };
+
+ /**
+ * Extends click region to whole row
+ */
+
+ private.extendedClick = function()
+ {
+ if (opt.rowClick)
+ {
+ var obj = form.rows;
+ }
+ else
+ {
+ var obj = form.boxes;
+ }
+
+ obj.live('click', function(e) {
+
+ var self = ($(e.target).is(form.pattern) || $(this).is(form.pattern));
+
+ if (!self && (e.target != this || $(this).is('a, :input') || $(e.target).is('a, :input')))
+ {
+ return;
+ }
+
+ if (!self && opt.altClick && !e.altKey && !e.ctrlKey)
+ {
+ return;
+ }
+
+ var box = $(this).parents('tr').find(form.pattern);
+
+ if (box.length < 1)
+ {
+ return;
+ }
+
+ private.bindRows();
+
+ var checked = box.prop('checked');
+
+ if (self)
+ {
+ checked = !checked;
+ }
+
+ if (form.lastCheck)
+ {
+ var end = form.boxes.index(form.lastCheck);
+ }
+
+ if (checked === false)
+ {
+ if (e.shiftKey && form.lastCheck)
+ {
+ var start = form.boxes.index(box);
+
+ public.select({
+ 'range' : [Math.min(start, end), Math.max(start, end)+1]
+ });
+ }
+
+ else if (!self)
+ {
+ box.prop('checked', true).change();
+ }
+
+ form.lastCheck = box;
+ }
+
+ else
+ {
+ if (e.shiftKey && form.lastCheck)
+ {
+ var start = form.boxes.index(box);
+
+ public.select({
+ 'range' : [Math.min(start, end), Math.max(start, end)+1],
+ 'checked' : false
+ });
+ }
+ else if (!self)
+ {
+ box.prop('checked', false).change();
+ }
+
+ form.lastCheck = null;
+ }
+ });
+
+ return private;
+ };
+
+ /**
+ * Tracks row checks
+ */
+
+ private.checked = function()
+ {
+ form.boxes.live('change', function(e) {
+ var box = $(this);
+
+ if (box.prop('checked'))
+ {
+ $(this).parents('tr').addClass(opt.selectedClass);
+ }
+ else
+ {
+ $(this).parents('tr').removeClass(opt.selectedClass);
+ }
+ });
+
+ return private;
+ };
+
+ /**
+ * Handles edit method selecting
+ */
+
+ private.changeMethod = function()
+ {
+ var button = $('.multi-edit input[type="submit"], .multi-edit button[type="submit"]').hide();
+
+ form.editMethod.change(function(e) {
+ var selected = $(this).find('option:selected');
+ $this.find('.multi-step').remove();
+
+ if (selected.length < 1 || selected.val() === '')
+ {
+ button.hide();
+ return private;
+ }
+
+ button.show();
+
+ if (selected.data('method'))
+ {
+ $(this).after($('<div />').attr('class', 'multi-step multi-option').html(selected.data('method')));
+ }
+ else
+ {
+ $(this).parents('form').submit();
+ }
+ });
+
+ return private;
+ };
+
+ if(!$this.data('_txpMultiEdit'))
+ {
+ private.bindRows().highlight().extendedClick().checked().changeMethod();
+
+ $this.find('.multi-option:not(.multi-step)').each(function() {
+ public.addOption({
+ 'label' : null,
+ 'html' : $(this).contents(),
+ 'value' : $(this).attr('id').substring(13)
+ });
+ }).remove();
+
+ $this.find(opt.selectAll).live('change', function(e) {
+ public.select({
+ 'checked' : $(this).prop('checked')
+ });
+ });
+ }
+
+ if (method && public[method])
+ {
+ public[method].call($this, args);
+ }
+
+ $this.data('_txpMultiEdit', form);
+ });
+
+ return this;
+};
+
// -------------------------------------------------------------
// event handling
// By S.Andrew -- http://www.scottandrew.com/
@@ -522,7 +876,7 @@
if(c && "spellcheck" in c) {$(textpattern.do_spellcheck).prop("spellcheck", true);}
// attach toggle behaviours
$('.lever a[class!=pophelp]').click(toggleDisplayHref);
- $('#selected_toggle').click(toggleAll);
+ $('.multi_edit_form').txpMultiEditForm();
// establish AJAX timeout from prefs
if($.ajaxSetup().timeout === undefined) {
$.ajaxSetup( {timeout : textpattern.ajax_timeout} );
Index: lib/txplib_head.php
===================================================================
--- lib/txplib_head.php (revision 3847)
+++ lib/txplib_head.php (working copy)
@@ -135,19 +135,6 @@
$edit_assign_assets = $rs ? selectInput('assign_assets', $rs, '', true) : '';
}
- if ($event == 'section')
- {
- $rs = safe_column('name', 'txp_page', "1=1");
- $edit['page'] = $rs ? selectInput('page', $rs, '', false) : '';
-
- $rs = safe_column('name', 'txp_css', "1=1");
- $edit['css'] = $rs ? selectInput('css', $rs, '', false) : '';
-
- $edit['onfrontpage'] = yesnoRadio('on_front_page', 1);
- $edit['syndicate'] = yesnoRadio('in_rss', 1);
- $edit['searchable'] = yesnoRadio('searchable', 1);
- }
-
// output JavaScript
?>
function poweredit(elm)
Index: lib/txplib_html.php
===================================================================
--- lib/txplib_html.php (revision 3847)
+++ lib/txplib_html.php (working copy)
@@ -866,6 +866,66 @@
}
/**
+ * Render a multi-edit form listing editing methods
+ *
+ * @param array $options array('value' => array( 'label' => '', 'html' => '' ),...)
+ * @param string $event Event
+ * @param string $step Step
+ * @param integer $page Page number
+ * @param string $sort Column sorted by
+ * @param string $dir Sorting direction
+ * @param string $crit Search criterion
+ * @parma string $search_method Search method
+ * @return string HTML
+ */
+
+ function multi_edit($options, $event=null, $step=null, $page='', $sort='', $dir='', $crit='', $search_method='')
+ {
+ $method = ps('edit_method');
+ $html = $methods = array();
+ $methods[''] = gTxt('with_selected_option');
+
+ foreach($options as $value => $option)
+ {
+ if (is_array($option))
+ {
+ $methods[$value] = $option['label'];
+
+ if (isset($option['html']))
+ {
+ $html[$value] = '<div class="multi-option" id="multi-option-'.
+ txpspecialchars($value).'">'.$option['html'].'</div>';
+ }
+ }
+ else
+ {
+ $methods[$value] = $option;
+ }
+ }
+
+ if ($event === null)
+ {
+ global $event;
+ }
+
+ if ($step === null)
+ {
+ $step = $event.'_multi_edit';
+ }
+
+ return '<div class="multi-edit">'.
+ n.selectInput('edit_method', $methods, $method).
+ n.eInput($event).
+ n.sInput($step).
+ n.hInput('page', $page).
+ ($sort ? n.hInput('sort', $sort).n.hInput('dir', $dir) : '' ).
+ ($crit !== '' ? n.hInput('crit', $crit).n.hInput('search_method', $search_method) : '').
+ n.implode('', $html).
+ n.fInput('submit', '', gTxt('go')).
+ n.'</div>';
+ }
+
+/**
* Render a form to select various amounts to page lists by.
*
* @param string $event Event
Edit. updated patch with new additions to the JS (alt+click, option to turn clickable rows off).
Last edited by Gocom (2012-06-27 16:29:08)
Offline
Re: New sections tab, multi-edit control block
I’ve added couple new features to the jQuery plugin since I originally posted that patch. I’ve updated the above post’s diff to include these. Changes are also present in the git repo. These include:
- Alt+Click option. By default turned on.
- Option to strict clickable region to the checkbox. This could have done previously too by changing the selector in options object, but now there is a simple boolean toggle for disabling clickable rows (option is mainly targeted for plugins).
Offline
Re: New sections tab, multi-edit control block
I’m away from computer for next couple of days but that sounds like it clears up any worries I had with the row selection. Personally, as long as the class names are correct I’d be more than happy with this being rolled into an svn patch. I can iron out any CSS issues at the weekend.
Offline
Re: New sections tab, multi-edit control block
Gocom
That’s just too awesome. Code ninja!
In the interests of pluginisation of the multi-edit toolset, is this:
$argv = func_get_args();
return pluggable_ui($event.'_ui', 'multi_edit_form',
'<div class="multi-edit">'.
n.selectInput('edit_method', $methods, $method).
n.eInput($event).
n.sInput($step).
n.hInput('page', $page).
($sort ? n.hInput('sort', $sort).n.hInput('dir', $dir) : '' ).
($crit !== '' ? n.hInput('crit', $crit).n.hInput('search_method', $search_method) : '').
n.implode('', $html).
n.fInput('submit', '', gTxt('go')).
n.'</div>',
$argv);
better / more desirable than this:
callback_event_ref($event.'_ui', 'multi_edit_options', 0, $options, $methods);
return '<div class="multi-edit">'.
n.selectInput('edit_method', $methods, $method).
...
i.e. allow plugins to completely alter the multi-edit area, or only allow them to augment/edit the existing options, leaving the surrounding markup intact?
The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.
Txp Builders – finely-crafted code, design and Txp
Offline
Re: New sections tab, multi-edit control block
To me the second option sounds way better. By ten folds and more. While the surrounding callback is neat, it has very little to no use compared to a option that simply allows adding options. Referenced augmentations all the way.
Hmm, thinking about it, the callback should probably modify the $options
array rather than the $methods
array that contains the HTML markup. I.e.
callback_event_ref($event.'_ui', 'multi_edit_options', 0, &$options);
foreach($options as $value => $option) [...]
Last edited by Gocom (2012-06-27 22:16:52)
Offline
Re: New sections tab, multi-edit control block
Gocom wrote:
the callback should probably modify the
$options
array rather than the$methods
Ah yes, much better up there. Well spotted. Consider it done.
The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.
Txp Builders – finely-crafted code, design and Txp
Offline
Re: New sections tab, multi-edit control block
Rollout will commence very soon across the other table-based panels, once the dust has settled and others have had a chance to weigh in on this one.
The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.
Txp Builders – finely-crafted code, design and Txp
Offline
Re: New sections tab, multi-edit control block
Gocom wrote:
I’ve added couple new features to the jQuery plugin
May I suggest one more thing? The “Toggle all” checkbox could set resp. unset its “checked” attribute when all resp. zero individual checkboxes are checked.
Offline
Re: New sections tab, multi-edit control block
wet wrote:
May I suggest one more thing? The “Toggle all” checkbox could set resp. unset its “checked” attribute when all resp. zero individual checkboxes are checked.
Good call. Here you go:
Index: textpattern.js
===================================================================
--- textpattern.js (revision 3856)
+++ textpattern.js (working copy)
@@ -157,7 +157,8 @@
'row' : 'tr, p, div',
'selectedClass' : 'selected',
'actions' : 'select[name=edit_method]',
- 'selectAll' : 'input[name="select_all"][type=checkbox]',
+ 'submitButton' : '.multi-edit input[type=submit]',
+ 'selectAll' : 'input[name=select_all][type=checkbox]',
'rowClick' : true,
'altClick' : true
};
@@ -191,6 +192,8 @@
form.editMethod = $this.find(opt.actions);
form.lastCheck = null;
form.opt = opt;
+ form.selectAll = $this.find(opt.selectAll);
+ form.button = $this.find(opt.submitButton);
}
/**
@@ -412,10 +415,12 @@
if (box.prop('checked'))
{
$(this).parents('tr').addClass(opt.selectedClass);
+ form.selectAll.prop('checked', form.boxes.filter(':checked').length === form.boxes.length);
}
else
{
$(this).parents('tr').removeClass(opt.selectedClass);
+ form.selectAll.prop('checked', false);
}
});
@@ -428,7 +433,7 @@
private.changeMethod = function()
{
- var button = $('.multi-edit input[type="submit"], .multi-edit button[type="submit"]').hide();
+ form.button.hide();
form.editMethod.change(function(e) {
var selected = $(this).find('option:selected');
@@ -436,11 +441,11 @@
if (selected.length < 1 || selected.val() === '')
{
- button.hide();
+ form.button.hide();
return private;
}
- button.show();
+ form.button.show();
if (selected.data('method'))
{
@@ -467,7 +472,7 @@
});
}).remove();
- $this.find(opt.selectAll).live('change', function(e) {
+ form.selectAll.live('change', function(e) {
public.select({
'checked' : $(this).prop('checked')
});
The patch fixes the select_all’s behavior so that the checkbox is checked when, and only when, all individual checkboxes are checked. In other words, it’s aware of changes and resets its state accordingly.
The patch also includes some minor fixes and performance improvements. For example it fixes an issue where submits buttons of sibling form’s where targeted and hidden too (could have potentially affect plugins w/ multiple list form instances on same page).
Last edited by Gocom (2012-06-28 17:10:14)
Offline
Re: New sections tab, multi-edit control block
One very minor thing. After performing a multi-edit action, the multi-edit select list reverts to the first ‘empty’ item (“with_selected_option”) even though the ‘selected’ attribute is set to the last used action. No idea why. The only exception so far is on the txp_discuss page I’m just modding now, which does actually ‘remember’ the multi-edit action just used and pre-selects it. Which means if you want to perform it again on a different selection, you need to select another action, cancel the auto-submit, then re-select the thing that was pre-selected (because the ‘Go’ button is initially missing until the onchange fires).
It may be to do with the fact that txp_discuss doesn’t have any secondary actions. Can’t figure why the selected
is honoured here and not on the other pages.
But anyway, I’m inclined to make the multi-edit always revert to the ‘with_selected_option’ empty item, because then the act of performing a multi-edit is definite (and the Go button behaves more predictably). Anyone see any problem with modding as follows:
Index: lib/txplib_html.php
===================================================================
--- lib/txplib_html.php (revision 3851)
+++ lib/txplib_html.php (working copy)
@@ -881,7 +881,6 @@
function multi_edit($options, $event=null, $step=null, $page='', $sort='', $dir='', $crit='', $search_method='')
{
- $method = ps('edit_method');
$html = $methods = array();
$methods[''] = gTxt('with_selected_option');
@@ -916,7 +915,7 @@
}
return '<div class="multi-edit">'.
- n.selectInput('edit_method', $methods, $method).
+ n.selectInput('edit_method', $methods, '').
n.eInput($event).
n.sInput($step).
n.hInput('page', $page).
Last edited by Bloke (2012-06-28 21:10:06)
The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.
Txp Builders – finely-crafted code, design and Txp
Offline