Drag 'n Drop Sortable
Thu, 02/25/2010 - 15:56
I need the ability to make a datagrid or unordered list sortable. That is, I need to be able to click on an entry in the list and drag it to a new location, within the list, reordering the list. JQuery UI includes a "sortable" plugin, which does exactly what I want. Has this feature been integrated into QCubed? I haven't found any references here. Has anyone done this in a QCubed application?
Thanks.

I haven't seen this integrated into QCubed. I would imagine that this integration would be WAY easier with QCubed 2.0 that's based on jQuery from the ground up.
If you want to do this in QCubed 1.1, I would recommend a slightly less usable user experience: include a "sort order" textbox onto each of the rows, and let the user modify it. Then, sort the datagrid based on the value of that textbox.
Alex, I doubt "sort order" arrangement would work for the type of user I have. I'll see what I can do with the JQuery sortable plugin. Maybe I can get it to work with 1.1.1. I believe QCubed 1.1.1 uses JQuery 1.4, correct?
However, I would like to know what the status of 2.0 is. Is it in any condition for me to try out? Where can I get my hands on it? If it functions, but just buggy, I can live with that and would be happy to be a guinea pig.
I think you're in for quite a complicated project if you are to proceed on the path you're describing.
Even if your users can't do the "sort order", there are other simpler ways to do it - for example, include a Move Up / Move Down button onto each datagrid row. Really simple to implement, pretty easy to use. Netflix had it that way for the longest time (before they added drag-and-drop).
QCubed 1.1.1 includes jQuery, but it is not BASED on it (unlike QCubed 2.0)
So, are you saying 2.0 isn't in any condition to begin using? Although I'm making good progress with 1.1.1, I've got a very long way to go with this application. I've only written 21 controllers so far and I've got at least another 80 to go.
If 2.0 is usable, it might be a good idea for me to start working with it. Besides, if 2.0 is very different from 1.1.1, it would frustrating to think I might need to start all over again at a point, when I'm almost finished. Also, when this new application is finished, it really needs to be state of the art and include features, like sortable. It needs to look and feel like a very modern application. Anyway, if 2.0 is at all usable, I would love to start with it and help to make it truly ready for prime time, when it's formally available. Considering all that my application requires, I imagine I'll be able to contribute some value as well.
LaCeja, I have used jquery sortable drag and drop functionality within few projects. I have also added some callbacks (if I remember correctly, if drop happens, callback to qcubed is created etc).
If you are interested, I might give you my raw code for you to check out. I believe it works with QCodo 0.3.43 beta. But I guess there is not much which has to be changed.
EDIT.
Well, I checked it out. It is not as clean as I thought. I haven't created a separate control for this, but rather used inline javascript to make it work. Then again, if you are interested, you can try to turn it into a separate plugin or something. If you need assistance, let me know. I guess I could also replace my current implementation with something more neater. But as they say: if it ain't broke, don't fix it :)
Here is my current code:
<?php
class TripMapEditPanel extends QTabPanel {
public $pxyMoveRow;
public function __construct($objParentObject, $strControlId = null) {
...
// proxy
$this->pxyMoveRow = new QControlProxy($this);
$this->pxyMoveRow->AddAction(new QMoveEvent(), new QAjaxControlAction($this, 'pnlEntry_Move'));
}
public function MakeSortable() {
QApplication::ExecuteJavaScript(sprintf("
jQuery(document).ready(function($) {
$('#%s ul').sortable({
placeholder: 'tripMapListPlaceholder',
update: function(event, ui) {
// what moved
var strLiId = ui.item.attr('id');
strLiId = strLiId.substr(0, strLiId.length - 3);
// where moved
var strIndex = \$(this).children('li').index(ui.item);
qc.pA('%s', '%s', 'QMoveEvent', strLiId + '|' + strIndex, '%s');
}
});
});
", $this->strControlId,
$this->objForm->FormId,
$this->pxyMoveRow->ControlId,
$this->objForm->DefaultWaitIcon->ControlId));
}
public function pnlEntry_Move($strFormId, $strControlId, $strParameter) {
// param = what_moved_control_id|where_to_moved_index
//alert($strParameter);
$strParamArray = explode('|', $strParameter);
if (count($strParamArray) != 2) {
return false;
}
$strMovableControlId = $strParamArray[0];
$intMovableIndex = (int) $strParamArray[1]; // new index of control
// here you do magic you want
$pnlEntry = $this->objForm->GetControl($strMovableControlId);
}
}
?>
I have created an array of panels, which I render out as list:
<ul><?php
foreach ($_CONTROL->pnlEntryArray as $pnlEntry) {
echo '<li id="'.$pnlEntry->ControlId.'_li">';
$pnlEntry->Render();
echo '</li>';
}
?>
</ul>
Because I use "_li" suffix, I need to cut the last 3 chars off of the if (finding strLiId in JS in MakeSortable method).
I'm not sure this is the best way to do it. As you have QPanels, which are sortable in the end. May-be it's somewhat overkill, but I have several controls inside li, which are movable. So QPanel sounded like a pretty general/universal way to do it.
So, the plugin might be something like:
1) you have one QPanel (QSortablePanel or something), which has an array of panels inside. also, given wrapper panel holds proxy for events.
2) you have a method "AddPanel" or simply you add panel to that array via parent setting.
3) if you have your array set, you render it out as list (ul, li) as I used
4) then you can use javascript I had
5) and developer can add his own method for move callback.
Thoughts/comments?
Agsel, I'm getting bleary eyed... It's been a long day. However, in the morning, I'll start working on this. I think this is very doable and you may have given me a good start. I'll update you on my progress. I'll publish whatever I come up with.
Thank you very much.
Alex, you never answered my question about QCubed 2.0.
Help! I understand the concept perfectly. However, what I don't understand are the mechanics of creating the sub-panels as children of a main panel. Do I need to create a separate class for the sub-panels to create them as:
$this->subpanel = new QPanel($this->ParentObject)
Thanks.
Hi Alex94040,
refine the question of:
Would you also be an example and move down each line ... This is a good way for beginners to show ...
Good luck!
//Corrected issue//
LaCeja, I'm not sure I understand your question correctly. But you create sub-panels as you would any other control.
$this->pnlSubpanel = new QPanel($this);
$this->pnlSubpanel2 = new QPanel($this);
etc
You create a new QPanel and set current panel ($this) as a parent.
I'm sorry, I wasn't as clear as I should have been. What I'm asking is, how do I make the primary panel a wrapper for the sub-panels? Do, I need to somehow specify that the primary panel is the parent of those sub-panels?
Yes, one panel is parent for others.
Something like this.
You have a form. In your form you add a panel:
<?php$this->pnlWrapper = new QPanel($this);
?>
This will create a new panel and adds it as a child to current form.
Now, let's create 2 new children:
<?
$pnl1 = new QPanel($this->pnlWrapper);
$pnl2 = new QPanel($this->pnlWrapper);
?>
This will create grand children for your form.
form
|
panel
/ \
panel panel
For sortable control, you should extend qpanel and create your own.
Something like that:
<?php
class QSortableControl extends QPanel {
// I've created pnl array, which will contain of child panels. Note, that you basically have already children available (GetChildControls() or something like that). But having references here is somewhat more clear and easier to use. You can have different child controls, but here you only store panels which are movable inside given control
public $pnlArray = array();
public function __construct($objParentObject, $strControlId = null) {
// here the default constructor
}
public function AddPanel(QPanel $pnl) {
// let's add panel to array
$this->pnlArray[$pnl->ControlId] = $pnl;
}
}
?>
Now you do in your form:
<?php
$this->pnl = new QSortableControl($this);
// let's create 2 sortable items
$pnl1 = new QPanel($this->pnl); // let's set wrapper panel as parent
$pnl2 = new QPanel($this->pnl);
$this->pnl->AddPanel($pnl1);
$this->pnl->AddPanel($pnl2);
?>
Later on, you could modify stuff so that you could do:
<?php$pnl1 = new QPanel($this->pnl); // this already adds panel to you wrapper
$pnl2 = new QPanel($this->pnl);
?>
Or well, if you used qcubed default child controls array, you don't need separate $pnlArray in QSortableControl.
I don't know, if I even answered your question. Currently I feel like I went a bit off and my proposal here is not even too good to use. :)
May-be someone gives also some input.
Agsel, no you didn't go too far and your example helps me very much. I think now I can get this done.
When I get this all finished, I'll provide all the details for an example. It seems like this might be a common need, especially in new business applications. In my case, this is a manufacturing process application and the users need a way to rearrange the steps of the process, as well as add new steps.
Thanks again!
Sorry, I have another question. Do I need to have a special control, like a label, to use as a "handle" for the drag 'n drop operation of sortable, or does the entire sub-panel become a handle? What sort of properties do I need to use for the handle?
In my example, I use the whole panel. The thing is, that panel is rendered as just a regular DIV-element in your html. You can render those panels inside LI-element. So, html will be something like:
<ul><li>
<div> here is rendered panel </diV
</li>
<li>
<div> here is another panel etc </div>
</li>
</ul>
Now, you can use a concrete element with id to be a handle (in js you have to add option handle: "my_id"). So, my panels look something like:
<div id="my_id">drag me</div><div> here is content, which can be draged somewhere. but given div itself is not draggable</div>
Now to your question. You can render handle as a control. You can use whatever you like. You can set it when you create "sortable" functionality in js. If you don't set the given option "handle", the whole item itself (currently the panel) is draggable.
I'm working on it to use the "handle" concept, because I'll need a way for the user to also "select" an entry for "editing". It's a complicated program. In this case, the sortable list is a list of manufacturing operations steps. The sequence of the steps may be rearranged, but the user may (is likely) to also need to make changes to data colums, which don't actually appear in the list, but are related to each step.
It's a little complicated, but I'm making progress.
Thanks again Agsel. I appreciate your advice.
I'm having a problem creating the QControlProxy in the constructor of the QSortablePanel. I'm getting the following error msg and I don't understand why.
Fatal error: Call to a member function GenerateControlId() on a non-object in C:\wamp\www\htw\includes\qcubed\_core\base_controls\QControlBase.class.php on line 171The code looks like this:
class QOpsSortablePanel extends QPanel {
public $pxyMoveRow;
public $pnlArray = array();
public function __construct($objParentObject, $strControlId = null) {
// proxy
$this->pxyMoveRow = new QControlProxy($this);
$this->pxyMoveRow->AddAction(new QMoveEvent(), new QAjaxControlAction($this, 'pnlEntry_Move'));
}
...
I'm creating the new control like this:
//Create the wrapper panel$this->pnlMasterOps = new QOpsSortablePanel($this);
Any help is appreciated.
Thank you!
Your sortable panel constructor should look like this:
<?phppublic function __construct($objParentObject, $strControlId = null) {
try {
parent::__construct($objParentObject, $strControlId);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
// now you do your stuff (proxy etc)
}
?>
For each control you create, you always have to have those 6 lines wihtin constructor. That's where qcubed creates relationships between controls and form.
Agsel, I had tried this before without success. However, I think it must have been a typo, because it works when I cut/paste from your example.
I'm sorry, but there is a piece to this puzzle I'm just not getting. Actually, two pieces.
In your example, you created pnlWrapper as a QPanel and then created sub-panels as children of pnlWrapper. From the example, I don't understand where QSortableControl comes in. I proceeded on the assumption that pnlWrapper should have been created as a QSortableControl. So, in my Form I have this, which I think is supposed to be my wrapper:
$this->pnlMasterOps = new QSortableControl($this);Now, because the number of sub-panels I have is based on the number of rows in a table (operations steps for the process being edited), I have created them like this:
$objProcessSteps = Masterprocesssteps::LoadArrayByMasterprocesshdrsId($this->lblId->Text);
if ($objProcessSteps) {
$row = 0;
//Create each panel as a child panel of pnlMasterOps
foreach ($objProcessSteps as $steps) {
$this->pnlOps[$row] = new QPanel($this->pnlMasterOps);
$this->intId = new QIntegerTextBox($this->pnlOps[$row]);
$this->intId->Text = $steps->Id;
$this->txtSequenceNo = new QLabel($this->pnlOps[$row]);
$this->txtSequenceNo->Text = $steps->SequenceNo;
// Add the panel to the wrapper panel
$this->pnlMasterOps->AddPanel($this->pnlOps[$row]);
$row++;
}
}
For now, I've put two controls within each sub-panel, intId and txtSequenceNo, simply for something to display. Later, I'll want to use intId as a handle and txtSequenceNo as a selectable control (doubleclick) to allow the user to "edit" other associated data.
I think the part I may be missing is, possibly I should be creating a separate class for a panel because, it's impossible to render the panels from within the form's template.
Finally, I still don't understand at what point and how to call the MakeSortable method of QSortableControl.
I'm getting really confused. Help!
EDIT:
Never mind, I think I've got it now.
Thanks.
Now I'm really lost!
I thought I had gained an understanding of how the panels work, but now I'm thinking not. Here's what I've got:
In my Form_Create I've created the new parent panel:
//Create the wrapper panel$this->pnlMasterOps = new masterprocessStepsSortablePanel($this);
$this->pnlMasterOps->MakeSortable();
Note that I've also tried to set the "MakeSortable" property.
Further, in my form, I've created sub-panels, based on the rows found in a table:
$objProcessSteps = Masterprocesssteps::LoadArrayByMasterprocesshdrsId($this->lblId->Text); if ($objProcessSteps) {
$row = 0;
//Create each row as a child panel of pnlMasterOps
foreach ($objProcessSteps as $steps) {
$this->pnlOps[$row] = new QPanel($this->pnlMasterOps);
//create controls for the sub-panel
$this->intId = new QIntegerTextBox($this);
$this->intId->Text = $steps->Id;
$this->txtSequenceNo = new QLabel($this);
$this->txtSequenceNo->Text = $steps->SequenceNo;
//add sub-panel to array of panels
$this->pnlMasterOps->AddPanel($this->pnlOps[$row]);
$row++;
}
My masterprocessStepsSortablePanel class looks like this:
<?php
class masterprocessStepsSortablePanel extends QPanel {
public $pxyMoveRow;
public $pnlArray = array();
public function __construct($objParentObject, $strControlId = null) {
try {
parent::__construct($objParentObject, $strControlId);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
$this->pxyMoveRow = new QControlProxy($this);
$this->pxyMoveRow->AddAction(new QMoveEvent(), new QAjaxControlAction($this, 'pnlEntry_Move'));
}
public function MakeSortable() {
QApplication::ExecuteJavaScript(sprintf("
jQuery(document).ready(function($) {
$('#%s ul').sortable({
placeholder: 'sortPlaceholder',
update: function(event, ui) {
// what moved
var strLiId = ui.item.attr('id');
strLiId = strLiId.substr(0, strLiId.length - 3);
// where moved
var strIndex = \$(this).children('li').index(ui.item);
qc.pA('%s', '%s', 'QMoveEvent', strLiId + '|' + strIndex, '%s');
}
});
});
", $this->strControlId,
$this->objForm->FormId,
$this->pxyMoveRow->ControlId,
$this->objForm->DefaultWaitIcon->ControlId));
}
public function AddPanel(QPanel $pnl) {
// let's add panel to array
$this->pnlArray[$pnl->ControlId] = $pnl;
}
public function pnlEntry_Move($strFormId, $strControlId, $strParameter) {
// param = what_moved_control_id|where_to_moved_index
//alert($strParameter);
$strParamArray = explode('|', $strParameter);
if (count($strParamArray) != 2) {
return false;
}
$strMovableControlId = $strParamArray[0];
$intMovableIndex = (int) $strParamArray[1]; // new index of control
// here you do magic you want
$pnlEntry = $this->objForm->GetControl($strMovableControlId);
}
}
?>
I'm getting 2 errors. The first is:
Notice: Trying to get property of non-object, on this statement:
$this->pnlMasterOps->MakeSortable();Using Firebug, there is an error:
jQuery is not defined...
thrown in <b>C:\wamp\www\htw\wla\masterprocesshdrs_edit.tpl.php</b> on line <b>31</b>
<script type="text/javascript">jQuery(document).ready(function($) {
$('#c14 ul').sortable({
placeholder: 'sortPlaceholder',
update: function(event, ui) {
// what moved
var strLiId = ui.item.attr('id');
strLiId = strLiId.substr(0, strLiId.length - 3);
// where moved
var strIndex = $(this).children('li').index(ui.item);
qc.pA('MasterprocesshdrsEditForm', 'c15', 'QMoveEvent', strLiId + '|' + strIndex, '');
}
});
}); </script>
This appears to be a problem with the js. I'm wondering if I have a version conflict. I'm using QCubed 1.1.1, which I believe uses jQuery 1.4 with UI 1.5.3. Agsel, is it possible the script is for an earlier version?
Here's the stack trace from the second error:
Fatal error: Uncaught exception 'QUndefinedPropertyException' with message 'Undefined GET property or variable in 'masterprocessStepsSortablePanel' class: Render' in C:\wamp\www\htw\wla\masterprocesshdrs_edit.tpl.php:31 Stack trace: #0 C:\wamp\www\htw\includes\qcubed\_core\base_controls\QControlBase.class.php(1299): QBaseClass->__get('Render') #1 C:\wamp\www\htw\includes\qcubed\_core\base_controls\QBlockControl.class.php(322): QControlBase->__get('Render') #2 C:\wamp\www\htw\wla\masterprocesshdrs_edit.tpl.php(31): QBlockControl->__get('Render') #3 C:\wamp\www\htw\includes\qcubed\_core\base_controls\QFormBase.class.php(868): require('C:\wamp\www\htw...') #4 C:\wamp\www\htw\includes\qcubed\_core\base_controls\QFormBase.class.php(316): QFormBase->Render() #5 C:\wamp\www\htw\wla\masterprocesshdrs_edit.php(271): QFormBase::Run('Masterprocesshd...') #6 {main} thrown in C:\wamp\www\htw\wla\masterprocesshdrs_edit.tpl.php on line 31I've also noticed the template for the parent panel is not being executed.
I really need some help with this. If not the jQuery part, at least the problem I'm having with the panels.
Thank you.
it seems that jQuery is not found, have you loaded the javascript somewhere?
You're right. I used the wrong constant for the path and also forgot to load the jQuery UI.
But, I'm still getting the error:
Trying to get property of non-object
This one I don't understand, because I have created the control (QPanel), without a problem:
$this->pnlMasterOps = new masterprocessStepsSortablePanel($this);$this->pnlMasterOps->MakeSortable();
But get the error on:
public function MakeSortable() {
QApplication::ExecuteJavaScript(sprintf("
jQuery(document).ready(function($) {
$('#%s ul').sortable({
placeholder: 'sortPlaceholder',
update: function(event, ui) {
// what moved
var strLiId = ui.item.attr('id');
strLiId = strLiId.substr(0, strLiId.length - 3);
// where moved
var strIndex = \$(this).children('li').index(ui.item);
qc.pA('%s', '%s', 'QMoveEvent', strLiId + '|' + strIndex, '%s');
}
});
});
", $this->strControlId,
$this->objForm->FormId,
$this->pxyMoveRow->ControlId,
$this->objForm->DefaultWaitIcon->ControlId));
}
This is the part that I just don't understand. The QPanel is created in the same class as the MakeSortable method. Also, pxyMoveRow was created in the constructor, so it exists.
I'm still lost on this one. Help!
I also notice the following error:
Fatal error: Uncaught exception 'QUndefinedPropertyException' with message 'Undefined GET property or variable in 'masterprocessStepsSortablePanel' class: Render'
You are probably calling ->Render instead of ->Render() in your template.
Thank you. Yes, I caught that and took care of it along with the jQuery problem. Now, the only problem is, "Trying to get property of non-object".
Sorry, my MakeSortable uses DefaultWaitIcon. You probably don't have this defined and that's why you get an error.
Instead of $this->objForm->DefaultWaitIcon->ControlId you could just write ''.
Agsel, you're right. I took it out and that error disappears, although I thought I was using DefaultWaitIcon in my Autocomplete. Anyway, that isn't so important.
However, I'm still having a problem. The template isn't being executed for the panel (masterprocessStepsSortablePanel.tpl.php, so nothing is being rendered, except the empty parent panel. I'm obviously missing something, but I don't know what. Do you have any ideas?
Thanks.
Can you show the code of your template and corresponding class?
Agsel, the class is the masterprocessStepsSortablePanel:
<?php
class masterprocessStepsSortablePanel extends QPanel {
// I've created pnl array, which will contain of child panels. Note, that you basically have already children available (GetChildControls() or something like that). But having references here is somewhat more clear and easier to use. You can have different child controls, but here you only store panels which are movable inside given control
public $pxyMoveRow;
public $pnlArray = array();
public function __construct($objParentObject, $strControlId = null) {
try {
parent::__construct($objParentObject, $strControlId);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
// proxy
$this->pxyMoveRow = new QControlProxy($this);
$this->pxyMoveRow->AddAction(new QMoveEvent(), new QAjaxControlAction($this, 'pnlEntry_Move'));
}
public function MakeSortable() {
QApplication::ExecuteJavaScript(sprintf("
jQuery(document).ready(function($) {
$('#%s ul').sortable({
placeholder: 'sortPlaceholder',
update: function(event, ui) {
// what moved
var strLiId = ui.item.attr('id');
strLiId = strLiId.substr(0, strLiId.length - 3);
// where moved
var strIndex = \$(this).children('li').index(ui.item);
qc.pA('%s', '%s', 'QMoveEvent', strLiId + '|' + strIndex, '%s');
}
});
});
", $this->strControlId,
$this->objForm->FormId,
$this->pxyMoveRow->ControlId,
''));
}
public function AddPanel(QPanel $pnl) {
// let's add panel to array
$this->pnlArray[$pnl->ControlId] = $pnl;
}
public function pnlEntry_Move($strFormId, $strControlId, $strParameter) {
// param = what_moved_control_id|where_to_moved_index
//alert($strParameter);
$strParamArray = explode('|', $strParameter);
if (count($strParamArray) != 2) {
return false;
}
$strMovableControlId = $strParamArray[0];
$intMovableIndex = (int) $strParamArray[1]; // new index of control
// here you do magic you want
$pnlEntry = $this->objForm->GetControl($strMovableControlId);
}
}
?>
This is the template for the form:
<?php
$strPageTitle = QApplication::Translate('Masterprocesshdrs') . ' - ' . $this->mctMasterprocesshdrs->TitleVerb;
require(__CONFIGURATION__ . '/header.inc.php');
?>
<?php $this->RenderBegin() ?>
<div id="titleBar">
<h2><?php _p($this->mctMasterprocesshdrs->TitleVerb); ?></h2>
<h1><?php _t('Masterprocesshdrs')?></h1>
</div>
<div id="formControls">
<?php $this->lblId->RenderWithName(); ?>
<?php $this->txtProcessId->RenderWithName(); ?>
<?php $this->txtCustNo->RenderWithName(); ?>
<?php $this->lstMaterialTypeObject->RenderWithName(); ?>
<?php //$this->txtRecordCharts->RenderWithName(); ?>
<?php //$this->txtCertify->RenderWithName(); ?>
<?php //$this->calLastUsedDate->RenderWithName(); ?>
<?php //$this->txtProdDesc->RenderWithName(); ?>
<?php //$this->txtProcessText->RenderWithName(); ?>
<?php $this->lblUpdTimestamp->RenderWithName(); ?>
</div>
<div id="formActions">
<div id="save"><?php $this->btnSave->Render(); ?></div>
<div id="cancel"><?php $this->btnCancel->Render(); ?></div>
<div id="delete"><?php $this->btnDelete->Render(); ?></div>
</div>
<?php $this->RenderEnd() ?>
<?php require(__CONFIGURATION__ .'/footer.inc.php'); ?>
And, the template for the panel is:'
<ul><?php
foreach ($_CONTROL->pnlArray as $pnlEntry) {
echo '<li id="'.$pnlEntry->ControlId.'_li">';
$pnlEntry->Render();
echo '</li>';
}
?>
</ul>
I also tried rendering the main panel in the form's template, but couldn't get that to work either. I'm going to try rendering the whole thing in the form's template, rendering each sub-panel.
I'm no longer getting any errors, it just doesn't render. If I get it working, before I hear from you, I'll post an edit.
Thanks for all your help Agsel!
I don't see you rendering any panel in your form template. You should probably have something like:
<?php$this->pnlSortablePanel->Render();
?>
Or whatever name your instance of sortable panel is.
Basically, your wrapper panel will never be rendered.
Agsel, yes I did have that in there. This is what was in the form's template, before, but didn't work:
<ul>
<?php
$this->pnlMasterOps->Render();
?>
</ul>
Even with that code, it doesn't run the render code:
<?phpforeach ($_CONTROL->pnlArray as $pnlEntry) {
echo '<li id="'.$pnlEntry->ControlId.'_li">';
$pnlEntry->Render();
echo '</li>';
}
?>
Thanks.
What's is the html (source code) it renders?
For example, if you print some "blah" in your template before foreach part, will it show up in the source?
Thanks for all you help and code Agsel! This wasn't nearly as complicated as I expected, thanks to you. It's going to make a great plugin!
LaCeja
Agsel,
I haven't figured out why yet, but there is a conflict between QAjaxAutocompleteTextbox and sortable. I have other Ajax actions on my form, but when I add a QAjaxAutocompleteTextbox to the form (not included in the sortable ul), I get an error "$("#c35 ul").sortable is not a function" on the update function of the script:
update: function(event, ui) {Do you have any idea where the conflict is?
Thanks,
LaCeja
EDIT:
I just did some additional testing and the problem persists, even if I do not render the QAjaxAutocompleteTextbox.
EDIT:
It appears there is a conflict, because both use a ul and the script for sortable is getting confused. Is it possible to use a different type of control for sortable?
Never mind, I just tried it with an ordered list and that fails as well. Also, tried it, using QJavaScriptAutoCompleteTextBox and get the same results.
I'm completely lost... help!
Help!
I've got the sortable working really nice. However, I've got one HUGE problem! Whatever I add to the sortable list, while in Form_Create, works just fine. But, when I attempt to add to that list later, the new entry simply doesn't render. I have verified, the newly added QPanel does exist in the array being rendered.
For example, in Form_Create:
$this->pnlMainPanel = new QSortablePanel($this);
$this->pnlMainPanel->MakeSortable();
for ($i; $i < 10; $i++){
$this->pnlSortable[$i] = new QPanel($this->pnlMainPanel);
$this->txtSeqNo[$i] = new QTextBox($this->pnlSortable[$i]);
$this->txtSeqNo[$i]->Text = $i;
$this->pnlMasterOps->AddPanel(pnlSoertable[$i]);
}
And, later as a result of pressing a button (QAjaxAction), I add another QPanel to the list:
$i = 10;$this->pnlSortable[$i] = new QPanel($this->pnlMainPanel);
$this->txtSeqNo[$i] = new QTextBox($this->pnlSortable[$i]);
$this->txtSeqNo[$i]->Text = $i;
$this->pnlMasterOps->AddPanel(pnlSoertable[$i]);
In the template, I have a loop to render it, something like this:
<?php echo '<div id="'.$this->pnlMainPanel->ControlId.'" style="width: 100%; height: 140px; border:1px solid #111; overflow: auto; ">'; ?>
<ul>
<?php $this->pnlMainPanel->Render();
foreach ($this->pnlSortable as $pnlSort) {
echo '<li id="'.$pnlSort->ControlId.'_li">';
$pnlSort->Render();
echo '</li>';
}
?>
</ul>
</div>
I have also verified the Render is not performed again, when the new QPanel is added to the list.
Does anyone have any idea why Render is not being performed, after adding the new QPanel?
Thanks,
LaCeja
Have you tried refreshing pnlMasterOps after the AddPanel method?
$this->pnlMasterOps->refresh();
Allegro, I'm sorry I forgot to mention that. Yes, I did. I've been testing more and made another discovery. If I change the action on the button from QAjaxAction to QServerAction, the list is re-Rendered. However, the jQuery script for the sortable disappears and, of course, the list is no longer sortable. Is there a way to re-apply (re-attach) the jQuery sortable script to the control?
EDIT:
Well, I guess I just answered my own question. I just made a test, where I called the "Makesortable" method again (attaches the jQuery sortable) and it seems to be working.
Thanks,
LaCeja
Am I right in assuming this was just a typo in this post "$this->pnlMasterOps->AddPanel(pnlSoertable[$i]);"?
Yes, thanks. I was just trying to present a shortened version of the actual code, because it's a lot of code, with lots of controls in the sortable panels.
It all seems to be working fine now. However, since the screens are so full, it would really be nice, if this could be done with a QAjaxAction instead of a QServerAction. It really slows things down, even though I'm using QFileFormStateHandler to reduce the amount of data flying back and forth. Any ideas? The problem with using QAjaxAction is that the list isn't being re-rendered and I haven't been able to find a way to force it. I've tried both Refresh() and MarkAsModified(). I imagine, if one of those did work, it would result in about the same amount of traffic anyway. I guess I'll just have to be happy with QServerAction.
.tpl files are not re-run on Ajax responses. So any functionality you have there simply won't happen a second time when using Ajax.
You really should have a panel with AutoRenderChildren set, that way calling Refresh() on it will cause it to re-render.
You can use HtmlBefore and HtmlAfter if you need the li's attached.
I haven't been able to make this work. I've added AutoRenderChildren to the parent panel, along with HtmlAfter to render the "
", and then added HtmlBefore to the sub-panels to render the- (with the controls ControlId) and HtmlAfter to render the
, but I am not getting the
and none of the sub-panels render. This idea would really be great because, it would make it much more efficient. I don't quite understand why the child panels (li's) are not rendering as well as why the
doesn't render for the parent panel. Any ideas?
Thanks.
Looking at your HTML, it seems you're reusing the parent panel's control ID. This could seriously confuse QCubed, and isn't valid HTML. Maybe you'll have better luck if you simplify your HTML to just the single render call.
Ok, now I'm really confused. I don't understand where you say I'm reusing the parent panel's control ID...
<?php echo '<div id="'.$this->pnlMainPanel->ControlId.'" style="width: 100%; height: 140px; border:1px solid #111; overflow: auto; ">'; ?>
<ul>
<?php $this->pnlMainPanel->Render();
foreach ($this->pnlSortable as $pnlSort) {
echo '<li id="'.$pnlSort->ControlId.'_li">';
$pnlSort->Render();
echo '</li>';
}
?>
</ul>
</div>
The parent panel is indeed referred to twice, but the first reference is to build an echo'd div, and that's just to create a div id. I'm only rendering the parent once and then the child controls. I think the problem is, when I add AutoRenderChildren to the parent panel, the child panels don't seem to be rendering.
I'll give it another try a little later. Right now I'm struggling with a problem, when I add another child panel to the array. The problem is, if the user has rearranged (sorted) the list and then adds a new entry, the list returns to its original order. Anyway, I'll get back to the AutoRenderChildren, after I get this latest problem sorted out, so to speak.
Thanks!
I'm still unsure why your children wouldn't be rendering. But you really should replace that entire chunk of code with just:
<?php$this->pnlMainPanel->Render();
?>
Specifically though, that render will also create a HTML tag with the Id of $this->pnlMainPanel->ControlId, and as such will conflict with the one you created above it.
Vexed, actually it seems to be rendering just fine with the following template code:
<?php echo '<div id="'.$this->pnlMasterOps->ControlId.'" style="float: left; width: 95%; height: 155px; margin-left: 3px; margin-top: 5px; border:1px solid #111; overflow: auto; background-color: #0B0B3B; color: #ffffff;">'; ?>
<ul>
<?php
$this->pnlMasterOps->Render();
foreach ($this->pnlMasterOps->pnlArray as $pnlOp){
echo '<li id="'.$pnlOp->ControlId.'_li">';
$this->pnlMasterOps->pnlArray[$pnlOp->ControlId]->Render();
echo '</li>';
}
?>
</ul> </div>
</div>
pnlMasterOps is the parent panel. QCubed doesn't seem to be getting at all confused and the form is working without any problems. However, I'm still working on it to be able to render just the parent panel and have the child panels render automatically.
Following is a snippet of the html generated.
<div style="float: left; width: 95%; height: 155px; margin-left: 3px; margin-top: 5px; border: 1px solid rgb(17, 17, 17); overflow: auto; background-color: rgb(11, 11, 59); color: rgb(255, 255, 255);" id="c77">
<ul class="ui-sortable">
<div style="display: inline;" id="c77_ctl"><div id="c77"></div></div><li id="c87_li"><div style="display: inline;" id="c87_ctl"><div id="c87"><span id="c88_ctl"></span><span id="c95_ctl"><span class="sortablehandle" title="Use this control to move the operation within the list. Click on the Sequence and drag it to where you want it to appear in the list." id="c95">1.00</span></span><span id="c96_ctl"><input type="text" readonly="readonly" class="textbox short90sortable" value="WT/CT/INSP" id="c96" name="c96" gtbfieldid="74"></span><span id="c112_ctl"><button class="button withtextfld" title="Special Processing Instructions exist for this Operation Step." id="c112" name="c112" type="button"> Text </button> </span><span id="c113_ctl"><button class="button textfld" id="c113" name="c113" type="button"> Chg/Del </button> </span></div></div></li><li id="c114_li"><div style="display: inline;" id="c114_ctl"><div id="c114"><span id="c115_ctl"></span><span id="c122_ctl"><span class="sortablehandle" title="Use this control to move the operation within the list. Click on the Sequence and drag it to where you want it to appear in the list." id="c122">2.00</span></span><span id="c123_ctl"><input type="text" readonly="readonly" class="textbox short90sortable" value="TEMPER" id="c123" name="c123" gtbfieldid="75"></span><span id="c139_ctl"><button class="button textfld" id="c139" name="c139" type="button"> Text </button> </span><span id="c140_ctl"><button class="button textfld" id="c140" name="c140" type="button"> Chg/Del </button> </span></div></div></li> </ul> </div>
As I warned you, you now have 2 divs with id="c77". This may or may not cause you issues, but isn't valid HTML.
And I'm still convinced you do not need that for loop, though I don't know what AutoRenderChildren is doing wrong in your case. You're sure those child panels have pnlMasterOps as their parent?
Vexed, yes I'm certain the parent panel of the sub-panels is pnlMasterOps. Also, if I remove the pnlMasterSpecs->ControlId.'...">, sortable no longer works. Actually, there are two sortable lists on the form, and they both are working correctly the way I have them coded, with the exception that I have to use QServerAction, instead of QAjaxAction.
This is a huge program, so I'm going to make a small program, with just a sortable list of panels to test with. With such a large program, lots of other things on the form begin to malfunction, when start making changes to make it work with AutoRenderChildren. I'll report back, when I get some results from that test form.
Thank you very much for following up with me. All in all, 2.0 is looking very good. You all have done a great job on it. Thanks again!
Okay, I've got it working without echoing the ControlId of the panel. I've modified my js to apply to '#ul%s' and then changed the
to:
<?php echo '<ul id=ul'.$this->pnlMasterSpecs->ControlId.'>'; ?>However, it is not possible to use AutoRenderChildren because, the master panel (of my QSortable) must be RenderWithName. When I RenderWithName, which disrupts the sortable. Basically, it treats all the sub-panels as a single group and tries to "drag" them as a single unit. Anyway, I have removed the "duplicate" ControlId and all is working very smoothly, except that I'm still stuck with using a QServerAction instead of QAjaxAction.
I still have some additional testing to do on this program (unrelated to sortable), but when it's finished, I'll bundle this into a QSortable plugin and submit it. Maybe someone will be able to sort out the problem, so QAjaxAction may be used.
I'm unsure what you're saying here. You need RenderWithName, or you don't? By default AutoRenderChildren uses plain Render().
You can override this by setting the PreferedRenderMethod on the parent panel (at least in QCubed 2.0 and I think 1.1).
Sorry, I wasn't completely clear. The QSortable panel includes a QProxyControl to handle processing of the movement of the child panels, which must be RenderAsEvents(). You can't do that unless the parent is RenderWithName. That causes the html to have an extra div, which causes sortable to fail. It is extremely complicated, which is why it took so long to determine what was happening.
I'm sorry, I'm still unclear on what is stopping you from rendering the parent using the normal Render() function. To the best of my knowledge, this should work:
<?php$parentPanel->PreferedRenderMethod = 'RenderWithEvents';
$parentPanel->RenderWithChildren = true;
$parentPanel->Render();
?>