Ajax Action returning javascript data
I created a new QAjaxAction derived class QGetAction (okay the name could be better ;) ).
When experimenting with jstree and QCubed I needed to get some js-data on the server side.
Example:
When draggin a tree-item to a different position.
--> a move_node - event is triggered
--> to make changes on the server side, you need to know the new and the old Position, new and old parent item. All this information is present on the js side (provided by jstree) but it isn't possible to get the data with an
QAjaxAction/QControlAjaxAction.
==> So i decided to write my own Action (QGetAction)
It is derived from QAjaxAction but has an additional parameter $mixReturnParam . Using this parameter you specify one or more javascript objects (by passing in a string or an array) to be returned to the server via the form parameter (url encoded)
usage: (following events are jstree specific and the code is simplified, but it is just an example)
$myTree->AddAction(new QMoveNodeEvent(),new QGetAction("OnMove","this.node_change"));
...
theForm::OnMove(){
$node_change = QGetAction::GetReturnData();
//simplified move (actually the parent item is needed too ...)
$changedItem = $this->GetItem($node_change['oldpos']);
$this->RemoveItem($node_change['oldpos']);
$this->AddItem($changedItem,$node_change['newpos']);
}I think that could be useful for the QSortable class
(sorting lists and getting the new order ... )
What do you think about it?

If I will get the new order from the QSortable list it is exactly what I am looking for! Would you be willing to share your QGetAction code?
Brynjar
Here is the code:
/*
* Like QAjaxAction this action calls a method of the form
* but in addition it returns a javascript object
* The object to return can be specified by $mixReturnParam (i.e.: this.StatusObj)
* get the object with POST['#Qform__FormParameter'][value]
*
* @property mixReturnParam can be a string or an array containing javascript objects/primitives to return
* the returned objects can be accessed by their names
*/
class QGetAction extends QAjaxAction {
protected $mixReturnParam;
public function __construct($strMethodName = null,$mixReturnParam="", $objWaitIconControl = 'default', $mixCausesValidationOverride = null) {
try {
parent::__construct($strMethodName, $objWaitIconControl, $mixCausesValidationOverride);
} catch (QCallerException $objExc) { $objExc->IncrementOffset(); throw $objExc; }
$this->mixReturnParam= $mixReturnParam;
}
public function __get($strName) {
switch ($strName) {
case 'mixReturnParam':
return $this->mixReturnParam;
default:
try {
return parent::__get($strName);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
}
}
public function RenderScript(QControl $objControl) {
$strWaitIconControlId = null;
if ((gettype($this->objWaitIconControl) == 'string') && ($this->objWaitIconControl == 'default')) {
if ($objControl->Form->DefaultWaitIcon)
$strWaitIconControlId = $objControl->Form->DefaultWaitIcon->ControlId;
} else if ($this->objWaitIconControl) {
$strWaitIconControlId = $this->objWaitIconControl->ControlId;
}
if(!empty($this->mixReturnParam)) {
if(is_array($this->mixReturnParam))
$strReturnParam = 'decodeURIComponent($j.param('.implode(')+\'&\'+$j.param(',$this->mixReturnParam).'))';
else
$strReturnParam = 'decodeURIComponent($j.param('. $this->mixReturnParam.'))';
}
else {
$strReturnParam ='\'\'';
}
return sprintf("qc.pA('%s', '%s', '%s', %s, '%s');",
$objControl->Form->FormId, $objControl->ControlId, get_class($this->objEvent),$strReturnParam, $strWaitIconControlId);
}
public static function GetReturnData() {
$arRet=array();
parse_str($_POST['Qform__FormParameter'],$arRet);
if(count($arRet)==1)
return reset($arRet);
return $arRet;
}
}
/*
* Get control action is identical to Get action, except
* the handler for it is defined NOT on the form host, but on a control.
*
* @package Actions
*/
class QGetControlAction extends QGetAction {
public function __construct(QControl $objControl, $strMethodName,$mixReturnParam="", $objWaitIconControl = 'default', $mixCausesValidationOverride = null) {
parent::__construct($objControl->ControlId . ':' . $strMethodName,$mixReturnParam, $objWaitIconControl, $mixCausesValidationOverride);
}
}
This snippet includes QGetAction and QGetControlAction
use it like QAjaxAction / QAjaxControlAction
just the second half of RenderScript() is different from QAjaxAction
for your special problem
try to add the action to QSortable_StopEvent or a QSortable_UpdateEvent
AddAction(new QSortable_StopEvent(),new QGetAction("onSortStop",$strJs))
and now the important thing $strJs:
if the structure is the same as the QCubed example you could do one of the following things:
- $strJs = 'this.find(".sortitem")';
returns al items with id, value +++ (could be large)
- $strJs = '$j.map(this.find(".sortitem"),function(a){return a.attr("id");})';
returns all ids (in the new order)
- $strJs = '$j.map(this.find(".sortitem"),function(a){return a.text();})';
returns an array of content item1,item2,item3 ...
To look at the returned data use QApplication::DisplayAlert(print_r(QGetAction::GetReturnData(),true));
Hope this helps.
I get the javascript error this.find is not a function. Any ideas?
Brynjar
Ok, try $j(this) instead of this.
Ok, now the error is gone, but the data I receive is only "undefined". I'm looking into it now.
Mike,
This is a very interesting idea. I have a question: why do you need the static method to retrieve the value? Doesn't the value come back as the $strParameter argument to your callback method? I mean you define your OnMove method as:
<?phppublic function OnMove($strFormId, $strControlId, $strParameter){
}
?>
then doesn't $strParameter get the same value as your QGetAction::GetReturnData() would return?
Also, for a better name for the action, I was thinking maybe QFetchAction?
-Vartan
@name: Yes, QFetchAction is way better.
@ $strParameter:
the data returned is a string and it needs to be urldecoded
this is done by GetReturnData()
To change this behaviour modifications in qc.postAjax are necessary
I will try to find a better solution
I created a simple solution for myself.
class QSortable extends QSortableBase
{
public function __construct($objParentObject, $strControlId = null) {
parent::__construct($objParentObject, $strControlId);
$this->AddJavascriptFile('jquery/jquery.ui.qsortable.serialize2.js');
}
public function SerializeReturn($options = null) {
$args = array();
$args[] = "serialize2";
if ($options !== null) {
$args[] = $options;
}
$strArgs = JavaScriptHelper::toJsObject($args);
$strJs = sprintf('
var sort%s = jQuery("#%s").sortable(%s);
qc.pA("%s","%s","%s",sort%s,"");',
$this->getJqControlId(),
$this->getJqControlId(),
substr($strArgs, 1, strlen($strArgs)-2),
$this->Form->FormId,
$this->getJqControlId(),
'QSortable_ToSerialized',
$this->getJqControlId()
);
QApplication::ExecuteJavaScript($strJs);
}
}
class QSortable_ToSerialized extends QEvent {
const EventName = 'QSortable_ToSerialized';
}
Also created a new serialize method for jQuery because QCubed reserves the characters jQuery. I make the panels I add to the Sortable Panel have a controlid with the format "myItemz1". The "z" is the delimiter and the number is the id for whatever I'm sorting.
Below is the javascript and would be put in the file denoted at the top of the QSortable class.
var serialize2 = $j.ui.sortable.prototype.serialize2;
$j.ui.sortable.prototype.serialize2 = function(o) {
var items = this._getItemsAsjQuery(o && o.connected);
var str = []; o = o || {};
$j(items).each(function() {
//var res = ($j(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=z](.+)/));
var res = ($j(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=z](.+)[_]/));
if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
});
if(!str.length && o.key) {
str.push(o.key + '=');
}
return str.join('&');
};
I add the following to my class when declaring my new sortable object "mySortable". The $strParameter will have the items in the new sorted order. You'll have to parse the string the comes in based on what you're looking for.
$this->mySortable->AddAction(new QSortable_ToSerialized(), new QAjaxControlAction($this,'mySortable_ToSerialized'));The "mySortable_ToSerialized" function is defined like follows:
public function mySortable_ToSerialized($strFormId, $strControlId, $strParameter) {.....
}
I think I have found a better way to return js objects.
The new concept would improve the whole event / action system and would be fully backward compatible.
necessary changes: view lines in qcubed.js postAjax, minimal addition to QEvent +
minimal changes in QAjaxAction
How it would work: all the returned objects get returned via the Form_parameter
the $_POST["Form_parameter"] holds a map/array/string representing the returned object,array,string or combinations of them
1) An event can specifiy a js return string (form_parameter)
2) You can define a js return-string
example: new QAjaxAction("onHandleMyChange",'$j("#myid").find("a").attr("id")');
3) backward compatibility: possible cases
a) no return string is specified in event and action, take the control's action parameter
this is the same behaviour like the current event system offers
b) event specifies return string --> this string overrides the control's action parameter
c) a js return string is defined for an Action
this overrides the event return string and the control's action parameter.
I think this solution integrates well with the current event system and would add much flexibility.
With this kpirbhai's problem would be a one-liner (i managed to return the new order of id's of a sortable with one line of js with my old QGetAction)
What do you think about that?
I am willing to implement the changes if the concept gets accepted by the devs
I would love to see something like this implemented, especially if it can be made backward compatible. If you provide a patch, I'll review it.
Thanks.
-Vartan
passing js object by an Ajax Action is no problem but as the ServerAction uses the native submit (browser does urlencoding of input elements) the decoding on the server side fails.
Should passing of js objects be possible for Ajax(Control)Actions only,
or does someone know how to pass nested parameters on submit (if it is possible)
See http://www.zulius.com/how-to/send-multidimensional-arrays-php-with-jquer.... My guess is that you have to encode the nested array somehow, then decode it in a ParsePostData call. I don't think there is a way to structure things so that it automatically does this.
thanks for the link, but the ajax part works (without the need to decode on the server side), but there is a problem with $j.submit()in qcubed.js (qcubed.pB(...)).
For the QServerAction I use jquery's param method to urlencode qcubed's form parameter.
Is there a js/jquery way for adding a parameter to submit, because putting an urlencoded object into a hidden input does not work.
Event Action Improvements: passing back js objects/arrays/strings ...
It is done!
I have done small modifications to qcubed.js (qcubed.pA(...)), QEvent and QAjaxAction and now it is possible to return javascript objects, arrays, strings ... by defining the object/array/string as a parameter in QEvent or QAjaxAction/QAjaxControlAction.
I have taken the jquery ui widgets wrapper example and modified it.
With my event/action mods the needed changes where just a few lines per example to get the new item order for QSortable, the width and height for QResizable, and the selected item's id and content for QSelectable
The results:
Sortable
Code to send back the new item order
<?php$strJsParam = '$j("#'.$this->Sortable->ControlId.'").find( "div.sortitem").map(function(){return $j(this).html()}).get()';
$this->Sortable->AddAction(new QSortable_UpdateEvent() ,new QAjaxAction("onSort" ,"default" ,null , $strJsParam));
?>
Code to retrieve the new item order
<?phppublic function onSort($formId,$objId,$objParam) {
$this->SortableResult->Text=print_r($objParam,true);
}
?>
the object,array or whatever you posted back can be accessed by $objParam (the third parameter in the callback functions) or by $_POST["Qform__FormParameter"]
This is the same for all three examples
Selectable
An object with a comma separated string of ids and a content array is posted back.
Resizable
What do you think about it?
I could create a ticket and provide the diffs.
I would sure like to see your changes. That looks great. I have been working on some things in the JqUi stuff, and this may relate.
Just opened ticket 718
Wow. That is really cool.
Very nice. Please see my comments:
http://trac.qcu.be/projects/qcubed/ticket/718#comment:1