![]() |
|
Snippets |
|
Hi, buddies!
It's my first snippet here, and, please, let me know if there's some bad or wrong code in your testings. Also, let me know if I make any mistakes about concepts here, or anything else. :-)
Well, this snippet is about how to get LightWindow, a wonderfull library, and Flash (I mean Adobe's) working together. This can be accomplished by using sfUFOPlugin as unobstrusive Flash content placement and ActionScript 2 inside the Flash animation, but I'll use sfSwfObjectHelperPlugin and ActionScript 3, which are my favorite ones. So, I'm assuming that you have these installed on your Symfony project:
and also knows how to program in ActionScript 3.
Now, let's start with the LightWindow portion of this snippet. If you are already using this plugin's helpers in the view, you don't need to do anything, since the library and CSS will be already loaded. If not, use the helper function, to load the resources, inside the view:
<?php use_helper('LightWindow'); // load the LW helpers _lwAddResources(); // load the LW resources (JS and CSS)
Alright, now the library is available for use by some JavaScript calls that we'll put inside our Flash project. But wait, there's a little catch here: I do not advise one to put a direct call to a LightWindow JS function as a command under an event listener, on Flash, because by the time the ActionScript is read in the compiled SWF the LW library may not be fully loaded; Instead of the direct call, I recommend you to create a simple JS function, right inside the view, that will delegate the call to the LW function, such as this:
<?php use_helper('Javascript'); echo javascript_tag(' function showWindowFromJS() { myLightWindow.activateWindow({href: "path/to/my/image.extension"}); // auxiliary function to be called from inside ActionScript } ');
The path can be assigned to a PHP variable, of course – normally I use $sf_request->getRelativeUrlRoot() to get to the root of the web folders, so that I can access my images easilly -. Also, read the API documentation on LW site to know more about the options to go inside the argument's object.
Now, how about some ActionScript? Let's say:
import flash.events.*; function showWindowFromFlash(event:MouseEvent):void { ExternalInterface.call('showWindowFromJS'); // using EI to communicate to the outside world } mMyLightWindowLauncher.buttonMode = true; // just to make the user sure that it's clickable. mMyLightWindow.addEventListener(MouseEvent.CLICK, showWindowFromFlash); // assigning the function to the button event
We're almost there! Now, just load the Flash movie (SWF file) inside the view using sfSwfObjectHelperPlugin:
<?php use_helper('Flash'); $id = 'flash_container_id'; // div to be replaced by Flash content. $params = array( 'id' => 'flash_object_id', 'movie' => 'path/to/my/movie.swf' 'size' => 'WIDTHxHEIGHT', // replace the dimensions here 'version' => '9', 'background_color' => '#ffffff', 'params' => array( 'allowScriptAccess' => 'sameDomain', 'wmode' => 'transparent', 'quality' => 'high' ), 'variables' => array( // a bunch of variables, can be an array with links to several images, just catch the vars inside the movie and use them passing as arguments in the ExternalInterface code. ), 'create_proxy' => true );
That's it!
See you later, guys!
A few days ago i found a problem about how to edit some term of a contract explicitly or with the WYSIWYG way. So I tried to googling and found this fancy example about how to cover my problem, that was inPlaceRichEditor. The first step, i tried to create A helper that could render the rich editor easily by modified the JavascriptHelper that is included in sf directory. You should notice that this stuff need Prototype 1.6, scriptaculous 1.8 and tinyMCE 2.1.3. put this code on your apps/lib/helper directory;
<?php /* * AjaxEditInPlaceEditor.php * Helper for in place rich editor */ function input_in_place_rich_editor_tag($name, $url, $editor_options = array()) { $response = sfContext::getInstance()->getResponse(); $response->addJavascript('/js/prototype'); $response->addJavascript('/js/scriptaculous'); $response->addJavascript('/js/controls'); $response->addJavascript('/js/effects'); $response->addJavascript('/js/tiny_mce/tiny_mce'); $response->addJavascript('/js/inplacericheditor'); $response->addJavascript('/js/tiny_mce_init'); $editor_options = _convert_options($editor_options); $default_options = array('tag' => 'span', 'id' => '\''.$name.'_in_place_editor', 'class' => 'in_place_editor_field'); return _in_place_rich_editor($name, $url, array_merge($default_options, $editor_options)); } function _in_place_rich_editor($field_id, $url, $options = array()) { $javascript = "new Ajax.InPlaceRichEditor("; $javascript .= "'$field_id', "; $javascript .= "'" . url_for($url) . "'"; $js_options = array(); if (isset($options['tokens'])) $js_options['tokens'] = _array_or_string_for_javascript($options['tokens']); if (isset($options['cancel_text'])) { $js_options['cancelText'] = "'".$options['cancel_text']."'"; } if (isset($options['save_text'])) { $js_options['okText'] = "'".$options['save_text']."'"; } if (isset($options['external_control'])) { $js_options['externalControl'] = "'".$options['external_control']."'"; } if (isset($options['options'])) { $js_options['ajaxOptions'] = $options['options']; } if (isset($options['with'])) { $js_options['callback'] = "function(form, value) { return ".$options['with']." }"; } if (isset($options['highlightcolor'])) { $js_options['highlightcolor'] = "'".$options['highlightcolor']."'"; } if (isset($options['highlightendcolor'])) { $js_options['highlightendcolor'] = "'".$options['highlightendcolor']."'"; } if (isset($options['loadTextURL'])) { $js_options['loadTextURL'] = "'".$options['loadTextURL']."'"; } $javascript .= ', '._options_for_javascript($js_options); $javascript .= ');'; return javascript_tag($javascript); }
As my experience,the inPlaceRichEditor need prototype.js,scriptaculous.js, inplacericheditor.js, controls.js and effects.js exists in the same directory (in /web/js directory). I don't know if you may be could cover this problem if u stay using alias or using sf_prototype_web_dir(/sf/prototype).
and then, here we go.. put this code on your template to perform the ajax in place rich editor
<?php use_helper('AjaxInPlaceRichEditor') ?> <div id="term1"> your text ready to edit </div> <?php echo input_in_place_rich_editor_tag('term1', 'myModule/myAction',array('options' =>"{method: 'post'}")) ?>
that's it, very simple and very nice stuff.
PS:i'm sorry if my english sounds pretty awkward, cause i don't speak english well
I made a small more general modification to the edit_in_place update action, you can use that in any action
class myTools { /** * performs update on any single column for ajax actions * * @param string $peer * @param integer $id * @param string $field * @return object */ public static function updateField($peer, $id, $field, $value) { if (!class_exists($peer)) { throw new InvalidArgumentException($peer.' does not exist'); } $method = new ReflectionMethod($peer, 'retrieveByPk'); $object = $method->invoke(NULL, $id); $object->setByName($field, $value, BasePeer::TYPE_FIELDNAME); $object->save(); return $object; }
creates an InplaceSelect field. A text is as normal text shown, when you click on it, it transforms to a select field. After selecting a value the select field transforms back to normal text. The data shown in the select field is requested from the server.
Save this helper in your_project/lib/helper/AjaxInplaceSelectHelper.php:
<?php /** * AjaxInplaceSelectHelper. * * @package symfony * @subpackage helper * @author Thomas Eigner <webmaster@flyingfinger.de> */ /** * wrapper for InplaceSelect. * @param string $name The id of surrounding span * @param string $value The default text for the span * @param string $url The url to receive the update * @param string $json The url to fetch the list to show in the select field * @param int $selectedId The id of the element to select when select is shown first time * @param array $spanOptions The span tag options. (size, class, etc...) * @param array $inplaceOptions The options for the inplaceSelect (callback, etc...) * * @return string A span with the text, and InplaceSelect javascript tags */ use_helper('JavaScript'); function input_in_place_select_tag($name, $value, $url, $json, $selectedId, $spanOptions = array(), $inplaceOptions = array()) { $context = sfContext::getInstance(); $response = $context->getResponse(); $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/builder'); $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype'); $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/effects'); $response->addJavascript('/js/inplaceselect'); $content = content_tag('span', $value, array('id' => $name)); $js_options = array(); if (isset($inplaceOptions ['callback'])) { $js_options['callback'] = $inplaceOptions['callback']; } else { $js_options['callback'] = "function(value, text) { return '".$name."_id='+value+'&".$name."_value='+text; }"; } if (isset($inplaceOptions ['onFailure'])) { $js_options['onFailure'] = $inplaceOptions['onFailure']; } if (isset($inplaceOptions ['onSuccess'])) { $js_options['onSuccess'] = $inplaceOptions['onSuccess']; } if (isset($inplaceOptions ['highlightcolor'])) { $js_options['highlightcolor'] = "'".addslashes($inplaceOptions['highlightcolor'])."'"; } if (isset($inplaceOptions ['highlightendcolor'])) { $js_options['highlightendcolor'] = "'".addslashes($inplaceOptions['highlightendcolor'])."'"; } if (isset($inplaceOptions ['savingText'])) { $js_options['savingText'] = "'".addslashes($inplaceOptions['savingText'])."'"; } if (isset($inplaceOptions ['cancelText'])) { $js_options['cancelText'] = "'".addslashes($inplaceOptions['cancelText'])."'"; } if (isset($inplaceOptions ['savingClassName'])) { $js_options['savingClassName'] = "'".addslashes($inplaceOptions['savingClassName'])."'"; } if (isset($inplaceOptions ['clickToEditText'])) { $js_options['clickToEditText'] = "'".addslashes($inplaceOptions['clickToEditText'])."'"; } if (isset($inplaceOptions ['cancelLink'])) { $js_options['cancelLink'] = ($inplaceOptions['cancelLink']) ? "true" : "false"; } $javascript = "new Ajax.InPlaceSelect('" . $name . "', '" . url_for($url) . "', '" . url_for($json) . "', " . $selectedId . ", " . _options_for_javascript($js_options) . " );"; return $content . javascript_tag($javascript); }
You also need an additional JS-File in your_project/lib/helper/AjaxInplaceSelectHelper.php:
/*
- inplaceselect.js -
Creates a <select> control in place of the html element with the id
specified. It functions similar to "Ajax.InPlaceEditor" but instead
of an <input> control, it creates a <select> control with a list of
<options> from which to choose. The parameters 'values' and 'labels'
are arrays (of the same length) from which the <options> are defined.
- Syntax -
new Ajax.InPlaceSelect('id', 'url', 'json', 'selected', { options });
- Example -
new Ajax.InPlaceSelect('someId', 'someURL', 'otherURL', 1,
{ callback: function(value, text) { return 'newval='+value+'&newtxt='+text; } } );
- Options('default value') -
- hightlightcolor("#FFFF99"): initial color (mouseover)
- hightlightendcolor("#FFFFFF"): final color (mouseover)
- onFailure(function(transport) {}): Called if failure occurs sending changes
- onSuccess(function(transport) {}): Called on sending changes successfully
- callback(function(value, text) { return 'newval='+value+'&newtxt='+text; }): function to
send additional parameters with the requests
- cancelText("cancel"): Text for the cancel link
- clickToEditText("Click to edit"): Tooltip for the text
- cancelLink(true): Should the cancel link be shown
Original JS-Script is from http://dev.rubyonrails.org/ticket/2667
*/
Ajax.InPlaceSelect = Class.create();
Ajax.InPlaceSelect.prototype =
{
initialize:function(element,url,json,selected,options)
{
this.element = $(element);
this.url = url;
this.json = json;
this.selected = selected;
this.values = new Array();
this.labels = new Array();
this.options = Object.extend(
{
highlightcolor: "#FFFF99",
highlightendcolor: "#FFFFFF",
onFailure: function(transport) {},
onSuccess: function(transport) {},
callback: function(value, text)
{
return 'newval='+value+'&newtxt='+text;
},
cancelText: "cancel",
clickToEditText: "Click to edit",
cancelLink: true,
},
options || {}
);
this.originalBackground = Element.getStyle(this.element, 'background-color');
if (!this.originalBackground)
{
this.originalBackground = "transparent";
}
this.element.title = this.options.clickToEditText;
this.ondblclickListener = this.enterEditMode.bindAsEventListener(this);
this.mouseoverListener = this.enterHover.bindAsEventListener(this);
this.mouseoutListener = this.leaveHoverNormal.bindAsEventListener(this);
Event.observe(this.element, 'click', this.ondblclickListener);
Event.observe(this.element, 'mouseover', this.mouseoverListener);
Event.observe(this.element, 'mouseout', this.mouseoutListener);
},
enterEditMode: function(evt)
{
if (this.saving) return;
if (this.editing) return;
this.editing = true;
new Ajax.Request(
this.json,
{
parameters: this.options.callback('', ''),
onSuccess:this.finishEnterEditMode.bind(this)
}
);
return false;
},
finishEnterEditMode: function(response)
{
var newData = eval(response.responseText);
this.values = new Array();
this.labels = new Array();
var i = 0;
var toSelect = 0;
for (var value in newData)
{
this.values.push(value);
if (value == this.selected) toSelect = i;
this.labels.push(newData[value]);
i++;
}
Element.hide(this.element);
this.createControls();
this.element.parentNode.insertBefore(this.menu, this.element);
this.menu.focus();
if (this.options.cancelLink)
{
this.element.parentNode.insertBefore(this.cancelButton, this.element);
}
this.menu.selectedIndex = toSelect;
return false;
},
createControls: function()
{
var options = new Array();
for (var i=0;i<this.values.length;i++)
options[i] = Builder.node('option', {value:this.values[i]}, this.labels[i]);
this.menu = Builder.node('select', options);
this.menu.onchange = this.onChange.bind(this);
this.menu.onblur = this.onCancel.bind(this);
for (var i=0;i<this.values.length;i++)
if (this.labels[i]==this.element.innerHTML)
{
this.menu.selectedIndex=i;
continue;
}
if (this.options.cancelLink)
{
this.cancelButton = Builder.node('a', this.options.cancelText);
this.cancelButton.onclick = this.onCancel.bind(this);
}
},
onCancel: function()
{
this.cleanUp();
this.leaveEditMode();
return false;
},
onChange: function()
{
var value = this.values[this.menu.selectedIndex];
var text = this.labels[this.menu.selectedIndex];
this.selected = value;
this.onLoading(text);
new Ajax.Request(
this.url, Object.extend(
{
parameters: this.options.callback(value, text),
onComplete: this.onComplete.bind(this),
onFailure: this.onFailure.bind(this)
},
this.options.ajaxOptions
)
);
},
onLoading: function(text)
{
this.saving = true;
this.removeControls();
this.leaveHover();
this.showSaving(text);
},
removeControls:function()
{
if(this.menu)
{
if (this.menu.parentNode) Element.remove(this.menu);
this.menu = null;
}
if (this.cancelButton)
{
if (this.cancelButton.parentNode) Element.remove(this.cancelButton);
this.cancelButton = null;
}
},
showSaving:function(text)
{
this.oldInnerHTML = this.element.innerHTML;
this.element.innerHTML = text;
this.element.style.backgroundColor = this.options.highlightcolor;
Element.show(this.element);
},
onComplete: function(transport)
{
this.options.onSuccess(transport);
this.cleanUp();
},
cleanUp: function()
{
this.leaveEditMode();
new Effect.Highlight(
this.element,
{
startcolor: this.options.highlightcolor,
endcolor: this.options.highlightendcolor,
restorecolor: this.originalBackground
}
);
},
onFailure: function(transport)
{
this.options.onFailure(transport);
if (this.oldInnerHTML)
{
this.element.innerHTML = this.oldInnerHTML;
this.oldInnerHTML = null;
}
return false;
},
enterHover: function()
{
if (this.saving) return;
this.element.style.backgroundColor = this.options.highlightcolor;
if (this.effect) { this.effect.cancel(); }
Element.addClassName(this.element, this.options.hoverClassName)
},
leaveHoverNormal: function()
{
if (this.saving) return;
this.element.style.backgroundColor = this.originalBackground;
},
leaveHover: function()
{
if (this.options.backgroundColor)
{
this.element.style.backgroundColor = this.oldBackground;
}
Element.removeClassName(this.element, this.options.hoverClassName)
if (this.saving) return;
this.effect = new Effect.Highlight(
this.element,
{
startcolor: this.options.highlightcolor,
endcolor: this.options.highlightendcolor,
restorecolor: this.originalBackground
}
);
},
leaveEditMode:function(transport)
{
this.removeControls();
this.leaveHover();
Element.show(this.element);
this.editing = false;
this.saving = false;
this.oldInnerHTML = null;
}
}
Now you need 3 actions
First one to build your complete HTML page:
...
executeShow()
{
$this->article = Article::retrieveByPk(1);
}
...
and the according template showSuccess.php:
<?php use_helper('AjaxInplaceSelect') ?> <h1><?php echo $article->getName() ?></h1> <strong>author:</strong> <?php echo input_in_place_select_tag( 'author_dom_id', $article->getAuthor()->getName(), 'module/updateChanges', 'module/jsonUpdate', $article->getAuthor()->getId(), array(), array('callback' => 'function(value, text) { return \'author_id=\'+value+\'&article='.$article; }')); ?>
Then the second action will generate the data to show in select field when clicking on the author. The data is passed to the InplaceSelect as JSON code:
...
executeJsonUpdate()
{
$authors = Author::doSelect(new Criteria());
$data = array():
foreach($authors as $author)
{
$data[$author->getId()]=$author->getName();
}
$this->renderText('('.json_encode($data).')');
}
...
And finally still the action that is receiving the change and saving it
...
executeUpdateChanges()
{
$newAuthorId = $this->getRequestParameter('author_id');
$articleId = $this->getRequestParameter('article');
$article = Article::retrieveByPk($articleId);
$article->setAuthorId($newAuthorId );
$article->save();
$this->renderText('');
}
...
That's it! Now the InplaceSelect should work fine. After clicking an option of the select field, it transforms back to normal text which show now the selected option. Instead of only pass over the text of the option, of course you can specify something else. For example if you normally show beside the author name also the number of books written by him, you do not need to show all this information also in the select field in order to still have this information for a new selected author. Therefore you can define a onSuccess function when calling the helper in the showSuccess template:
<?php use_helper('AjaxInplaceSelect') ?> <h1><?php echo $article->getName() ?></h1> <strong>author:</strong> <?php echo input_in_place_select_tag( 'author_dom_id', $article->getAuthor()->getName() . ' [' . $article->getAuthor()->getNumberOfBooks() . ' Book(s)]', 'module/updateChanges', 'module/jsonUpdate', $article->getAuthor()->getId(), array(), array( 'onSuccess' => 'onSuccess' => 'function(transporation) { $(\'author_dom_id\').innerHTML = transporation.responseText; }', 'callback' => 'function(value, text) { return \'author_id=\'+value+\'&article='.$article; }') ); ?>
and when saving the changes render the more detailed Author info:
...
executeUpdateChanges()
{
$newAuthorId = $this->getRequestParameter('author_id');
$articleId = $this->getRequestParameter('article');
$article = Article::retrieveByPk($articleId);
$article->setAuthorId($newAuthorId );
$article->save();
$this->renderText($article->getAuthor()->getName() . ' [' . $article->getAuthor()->getNumberOfBooks() . ' Book(s)]');
}
...
There are still some more option which you can use, check helper the source code.
Enjoy the InplaceSelect!
I've found it annoying to set the show and hide commands on each remote request. So I've used this to define it globally:
/* * Defines the indicator globally */ Ajax.Responders.register({ onCreate: function() { Element.show('indicator'); }, onComplete: function() { Element.hide('indicator'); } });
This helper used JavascriptHelper. It create a div which open a another onclick with a visual effect. The content of the div is the template's result of a call to a module/action.
/** * Fonction my_div_to_remote * @author Julien Levasseur * @since - 13 sept. 07 * Extend of my_button_to_remote. return a div. * * Returns an html button to a remote action defined by 'url' (using the * 'url_for()' format) that's called in the background using XMLHttpRequest. * * See link_to_remote() for details. * */ function my_div_to_remote($name, $options = array(),$effect, $html_options = array()) { return my_div_to_function($name, $effect, remote_function($options), $html_options); } function my_div_to_function($name,$effect, $function, $html_options = array()) { $html_options = _parse_attributes($html_options); $html_options['onclick'] = $function.';'.$effect['onclickadd'].';return false;'; return content_tag('div', $name, $html_options); }
Example:
<?php echo my_div_to_remote('div de test',array('url'=>'principal/pret'),array('onclickadd' => visual_effect('toggle_blind', 'rem', array('duration' => 4.0))),array('style'=>'border: solid 1px;width:40px;height:40px;')) ?> <div id="rem" style="display:none;height:300px;border:solid 1px;">youpi</div>
Sometimes in the generator you have a large listing of elements in a admin_double_list.
Well I wanted to write a quick little autocomplete text box that would search for results and when you selected one, it would be added into the associated select box.
In your generator add a new partial: edit: display: [ _quick_search ]
Create a file in your templates directory called _quick_search.php
Copy this code in there:
===CODE===
<?php $response = sfContext::getInstance()->getResponse(); $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype'); $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/effects'); $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/controls'); $response->addStylesheet(sfConfig::get('sf_prototype_web_dir').'/css/input_auto_complete_tag'); ?> <input type="text" id="quick_search" name="quick_search"/>
<div class="auto_complete" id="quick_search_auto_complete" style="position: absolute; left: 445px; top: 421px; width: 113px; opacity: 0.17329; display: none;">
</div>
<script type="text/javascript">
//<![CDATA[
function addSelection (li)
{
$('associated_titles').options[ $('associated_titles').options.length] = new Option (li.childNodes[0].nodeValue, li.id);
}
new Ajax.Autocompleter('quick_search', 'quick_search_auto_complete', '/backend_dev.php/search', { updateElement: addSelection });
//]]>
</script>
===CODE===
Make sure you change your url from /backend_dev.php/search to whatever your action that will search your database and your good to go!
Just FYI: You can use the Symfony helper autocomplete_tag () because it does not allow for passing the updateElement parameter.
creates an inplace editable html table from a database table. save this helper in your_project/lib/helper/input_in_place_editor_gridHelper.php dir
Special thanks to Christian.
There is a problem with saving the snippet here.I will try to update it later.
But for a normal (user interface) following action can be used:
public function executeUpdategrid() { $value=trim(strip_tags($_POST['value']));// new value of the cell being updated $pFieldValue=intval($this->getRequestParameter('pFieldValue'));// value of primary key of the record in the table, ie id value $fieldNo=intval($this->getRequestParameter('fieldNo'));// index of the field coming from input_in_place_grid_tag /* to permit the fields just we want to update here 2,3 and 4 are indexes of these fields in $rs, and this is for security this is used only if secure parameter is set in grid options */ # these are just examples to illustrate secure usage if($fieldNo==2) $fieldName='Account.FIRSTNAME'; elseif($fieldNo==3) $fieldName='Account.LASTNAME'; elseif($fieldNo==4) $fieldName='Account.BIRTHD'; elseif($fieldNo==5) $fieldName='Settings.EMAIL'; else die("Invalid Table Field!");// invalid field number.(just for security.Complete table field names can be used in an admin application) $split=explode(".",$fieldName); $tableName=$split[0]; // to find primary key field name $fields=call_user_func(ucfirst(strtolower($tableName)).'Peer::getFieldNames'); if($fields[0]) { # primary key field name $pFieldName=$tableName.".".strtoupper($fields[0]); # update corresponding field $conn=Propel::getConnection(); $sql="UPDATE $tableName SET $fieldName='$value' WHERE $pFieldName=$pFieldValue"; $conn->executeQuery($sql); $this->value=$value;// set the value to print out in the template "updategridSuccess.php" } else $this->value=null; }
or both can be used in same action as follows:
/** * account actions. * * @package 1insaat * @subpackage account * @author Ahmet Ertek * @version 1.0 * * @desc Implements input_in_place_editor_grid helper's cell update.(this helper is not standart, just a custom helper).This action is not in use now.See /profile/templates/settingsSuccess.php */ public function executeUpdategrid() { $value=trim(strip_tags($_POST['value']));// new value of the cell being updated $pFieldValue=intval($this->getRequestParameter('pFieldValue'));// value of primary key of the record in the table, ie id value $fieldNo=intval($this->getRequestParameter('fieldNo'));// index of the field coming from input_in_place_grid_tag $fieldName=trim($this->getRequestParameter('fieldName'));// name of the field to be updated $fieldName=str_replace("_",".",$fieldName);// replace _ with. This is because no_script_name can be setto "on" in settings.yml of app /* to permit the fields just we want to update here 2,3 and 4 are indexes of these fields in $rs, and this is for security this is used only if secure parameter is set in grid options */ if(!$fieldName) { # these are just examples to illustrate secure usage, change these field names with yours if($fieldNo==2) $fieldName='Account.FIRSTNAME'; elseif($fieldNo==3) $fieldName='Account.LASTNAME'; elseif($fieldNo==4) $fieldName='Account.BIRTHD'; elseif($fieldNo==5) $fieldName='Settings.EMAIL'; else die("Invalid Table Field!");// invalid field number.(just for security.Complete table field names can be used in an admin application) } $split=explode(".",$fieldName); $tableName=$split[0]; // to find primary key field name $fields=call_user_func(ucfirst(strtolower($tableName)).'Peer::getFieldNames'); if($fields[0]) { # primary key field name $pFieldName=$tableName.".".strtoupper($fields[0]); # update corresponding field $conn=Propel::getConnection(); $sql="UPDATE $tableName SET $fieldName='$value' WHERE $pFieldName=$pFieldValue"; $conn->executeQuery($sql); $this->value=$value;// set the value to print out in the template "updategridSuccess.php" } else $this->value=null; } ?>
Note that to use this grid for admin app you may use
$options['secure']=>false;
One stumbler for me was that I didn't realize that the form created by the input_in_place_editor_tag() helper only submits one key/value pair (value=xxx), so to tell the action which PK to use or which object field to update, you have to pass that information on the form URL (field and id).
One way to solve it would be to write a different action for each field you want to update, but I wanted something I could reuse without writing additional actions, so I added two GET parameters:
So, in my showSuccess.php template:
<h1 id="eipTitle"><?php echo $comment->getTitle() ? $comment->getTitle() : "click to edit" ?></h1> <?php echo input_in_place_editor_tag( 'eipTitle', 'comment/ajaxUpdate?field=title&commentid='.$comment->getCommentid(), array( 'cols' => 40, 'rows' => 1, )) ?>
BTW, you can read about the valid options you can pass in the third argument to input_in_place_editor_tag() at the script.aculo.us Wiki
And for the action, I made it as generic as possible (actually it would be nice if Propel generated an action like this next to the regular executeUpdate action):
private function raiseEipError ($message) { $this->logMessage($message, 'err'); return $this->renderText( "ERROR: $message" ); } public function executeAjaxUpdate() { $id = $this->getRequestParameter('commentid'); $field = $this->getRequestParameter('field'); $this->logMessage("commentid:$id and field='$field'", 'debug'); // Check for required params. // Return a nice message to the user and in the log if there's a problem. if (! $field) return $this->raiseEipError( "No field parameter passed in form action" ); $valid_fields = CommentPeer::getFieldNames(BasePeer::TYPE_FIELDNAME); if (! in_array($field, $valid_fields)) return $this->raiseEipError( "Invalid field parameter '$field' passed in form action.\n". " Valid fields: (" . implode(", ", $valid_fields) . ")" ); if (! $id) return $this->raiseEipError( "No ID parameter passed in form action" ); $comment = CommentPeer::retrieveByPk($id); if (! $comment) return $this->raiseEipError( "Object with ID $id not found" ); $comment->setByName($field, $this->getRequestParameter('value'), BasePeer::TYPE_FIELDNAME); $comment->save(); return $this->renderText( $comment->getByName($field, BasePeer::TYPE_FIELDNAME) ); }
One stumbler for me was that I didn't realize that the form created by the input_in_place_editor_tag() helper only submits one key/value pair (value=xxx), so to tell the action which PK to use or which object field to update, you have to pass that information on the form URL (field and id).
One way to solve it would be to write a different action for each field you want to update, but I wanted something I could reuse without writing additional actions, so I added two GET parameters:
So, in my showSuccess.php template:
<h1 id="eipTitle"><?php echo $comment->getTitle() ? $comment->getTitle() : "click to edit" ?></h1> <?php echo input_in_place_editor_tag( 'eipTitle', 'comment/ajaxUpdate?field=title&commentid='.$comment->getCommentid(), array( 'cols' => 40, 'rows' => 1, )) ?>
BTW, you can read about the valid options you can pass in the third argument to input_in_place_editor_tag() at the script.aculo.us Wiki
And for the action, I made it as generic as possible (actually it would be nice if Propel generated an action like this next to the regular executeUpdate action):
private function raiseEipError ($message) { $this->logMessage($message, 'err'); return $this->renderText( "ERROR: $message" ); } public function executeAjaxUpdate() { $id = $this->getRequestParameter('commentid'); $field = $this->getRequestParameter('field'); $this->logMessage("commentid:$id and field='$field'", 'debug'); // Check for required params. // Return a nice message to the user and in the log if there's a problem. if (! $field) return $this->raiseEipError( "No field parameter passed in form action" ); $valid_fields = CommentPeer::getFieldNames(BasePeer::TYPE_FIELDNAME); if (! in_array($field, $valid_fields)) return $this->raiseEipError( "Invalid field parameter '$field' passed in form action.\n". " Valid fields: (" . implode(", ", $valid_fields) . ")" ); if (! $id) return $this->raiseEipError( "No ID parameter passed in form action" ); $comment = CommentPeer::retrieveByPk($id); if (! $comment) return $this->raiseEipError( "Object with ID $id not found" ); $comment->setByName($field, $this->getRequestParameter('value'), BasePeer::TYPE_FIELDNAME); $comment->save(); return $this->renderText( $comment->getByName($field, BasePeer::TYPE_FIELDNAME) ); }
If you need to call a remote function with the parameter of the changed select .
Nota for François : Could be cool to have a better documentation of remote_function.
Here is the editSuccess.php
<tr> <th> <?php echo __('Domains:') ?> </th> <td> <?php echo form_error('domain') ?> <?php echo select_tag('domain', objects_for_select( $domains, 'getIdDomain', 'getName', $list->getDomain(), 'include_custom='.__('Choose a domain')), array( 'onchange' => remote_function(array( 'update' => 'item_domain', 'url' => 'list/subdomain', 'with' => "'id=' + this.options[this.selectedIndex].value" )) ) ) ?> </td> </tr> <tr> <th> <?php echo __('Sub domains:')?> </th> <td> <?php echo form_error('sub_domain') ?> <div id="item_domain"> <?php echo select_tag('sub_domain', objects_for_select( $list_sub_domains, 'getIdDomain', 'getName', $list->getSubDomain(), 'include_custom='.__('Choose a sub domain') )) ?> </div> </td> </tr>
Here is the subdomainSuccess.php
<?php use_helper('Object') ?> <?php echo select_tag('sub_domain', objects_for_select( $list_sub_domains, 'getIdDomain', 'getName', 0, 'include_custom='.__('Choose a sub domain') )) ?>
One link, multiple ajax div updates, the ultimate solution
I already proposed a solution in another snippet, but it seems I missed the simplest solution.
Let's summarize the probkem again: A page contains one Ajax link, but the remote function must update several divs on the page. Consider, for instance, the following template:
<h1>First zone to update</h1> <div id="first_zone"> Hello there </div> <h1>Second zone to update</h1> <div id="second_zone"> <p>How do you do, <strong>mate</strong>? </div>
We want an ajax link to update the two zones. The solution is not to use the Ajax helpers, but rather call the prototype Ajax object directly:
<?php echo use_helper('Javascript') ?> <h1>Ajax link</h1> <?php echo link_to_function('click me', 'new Ajax.Request(\''.url_for('test/ajax').'\');return false')) ?>
The code of the action itself (executeAjax()) does some server stuff to prepare the data used to update the template. It really depends on what logic you put in your Ajax interaction. For this example, it will be empty.
The code of the template (ajaxSuccess.php) just needs to be as follows:
<?php $sf_context->getResponse()->setContentType('text/javascript') ?> <?php slot('first_update') ?> So you like clicking, uh? <?php end_slot() ?> <?php slot('second_update') ?> <p>I\'d like to test quotes (like "). </p> <p>And <strong>tags</strong>, too.</p> <?php end_slot() ?> Element.update('first_zone', '<?php include_slot('first_update') ?>'); Element.update('second_zone', '<?php include_slot('second_update') ?>');
And that's it. Because the response content type is text/javascript, the Ajax object will eval it automatically (no need to mention evalScripts: true anymore).
And this will do exactly what we wanted: update both zones with different content, in a single remote call. The interest of using the slot helpers is that you don't need to worry about escaping the content passed to the JavaScript function, and it looks really nice in your favorite syntax-highlighting text editor.
I was absolutely stuck with this, so I would like to fix it somewhere.
I did implement a simple in place editor field, and I had in my action:
public function executeEditname() { $this->name = trim($this->getRequestParameter('value')); }
and the editnameSuccess.php:
<?php if (isset($name)): ?> <?php echo $name ?> <?php endif; ?>
In the page, when I clicked on the name element the first time, everything was fine. But from the second time on, the text appeared always with a tab space in front, absolutely annoying. Finally I got it: in the template, I did indent the echo with one tab, just because I am used to have clear code. But that tab was the problem. So, after I modified editnameSuccess.php:
<?php if (isset($name)): ?> <?php echo $name ?> <?php endif; ?>
Everything was fine.
GO HERE TO VIEW CODE:
http://www.symfony-project.com/forum/index.php/t/4804/
My Snipeet works on the premise that in the JS helpers such as form_remote_tag, or link_to_remote(), the AJAX request action is called via an OnClick/onSubmit() when JS is enabled. For the sake of our argument, we will call the AJAX request action AMod/Aaction .When JS is not enabled, usually a secondary link given via either the 'href' or 'action' attribute is called. The default secondary link for these actions is '#'. Through my classes, when JS is not enabled, the secondary link brings you to myapp/yz_degradable_ajax_module/Process_nonajax_page action. All of the pertinant information is sent to that action via the GET method. The process_nonajax_page action will also be passed AMod/Aaction and the varables that the original JS helper was trying to pass to it. AMod/Aaction will be called via getPresentation and the values usually passed to it via POST or GET will be passed via $this->getRequest->getAttributes() because that is one of the only ways to pass values from one action to another. The HTML output from getPresentation will be put into a varable and redirects back to the original page. Once back on the original page, the create_div method intercepts that variable and outputs it to the user.
So to the lay user, when JS is not active, the page dynamically loading the new contents via ajax, reloads the whole page with the desired contents.
I created 2 files, 1 class and one module(with 1 action.class.php) that lets you to easily integrate degradable AJAX many of the Javascript remote functions
Place the yzDegradableAjax.class.php class in /app/lib/ . This file contains all of the functions and methods used.
Please let me know if this works for you or if my instructions are too confusing and I'll try to clarify. All criticisms are welcome.
Since the this Symfony Snippet section likes to go crazy whenever I paste in lots of code, you can view the code and course documentation at this form:
http://www.symfony-project.com/forum/index.php/t/4804/