Snippets

Create an account or login to be able to add, comment and rate snippets.

Navigation

Refine Tags

Snippets tagged "ajax" Snippets tagged "ajax"

Generic action to save edit-in-place fields Update

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;
  }
 
by Torsten Zander on 2008-02-03, tagged ajax  propel 

Generic action to save edit-in-place fields

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) );
  }
by Nathan Vonnahme on 2007-05-04, tagged action  ajax  editinplace  form  inputinplaceeditortag  update 

Generic action to save edit-in-place fields

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) );
  }
by whoknows on 2007-05-04, tagged action  ajax  editinplace  form  inputinplaceeditortag  update 

TinyMCE in AJAX call

If you want to use a TinyMCE editor in a form called by ajax, you have to make some additional changes to the code. It's not complicated.

Include TinyMCE

In view.yml include TinyMCE and Prototype:

  javascripts:    [/sf/js/prototype/prototype.js, /js/tiny_mce/tiny_mce.js]

Init TinyMCE

In your main decoration template include the javascript code bellow (best at the end of the file). In most cases this code will be the layout.php.

<?php echo javascript_tag('
var activatedAreas = new Array();   
 
function setTextareaToTinyMCE(sEditorID) 
{
  var oEditor = document.getElementById(sEditorID);
    if(oEditor) 
  {
    if(activatedAreas[sEditorID] == true)
      unsetTextareaToTinyMCE(sEditorID);
    try {
      activatedAreas[sEditorID] = true;
      tinyMCE.execCommand("mceAddControl", true, sEditorID);                    
    }
    catch(e)
    {
      alert("Error activating element " + sEditorID + "\n" + e);
    }
  }
}
 
function unsetTextareaToTinyMCE(sEditorID) 
{
  var oEditor = document.getElementById(sEditorID);
  if(oEditor && (activatedAreas[sEditorID] == true)) 
  {
    try 
    {
      tinyMCE.execCommand("mceRemoveControl", true, sEditorID);
      activatedAreas[sEditorID] = false;
    }
    catch(e)
    {
      alert("Error deactivating element " + sEditorID + "\n" + e);
    }
  }
}
 
function tinymceDeactivate()
{
  try {
    tinyMCE.triggerSave(true,true);         
  }
  catch(e)
  {
    alert("Error saving form\n" + e);
  }
 
  for(var i in activatedAreas)
  {
    if(activatedAreas[i] == true) 
    {
      unsetTextareaToTinyMCE(i);
    }
  }
}
 
function submitForm(formId)
{
 if($(formId).onsubmit())
 {
    $(formId).submit();
 }
}
 
tinyMCE.init({
  theme : "advanced",
  language : "en",
  mode : "exact",
  relative_urls : false,
  debug : false,  
  valid_elements : "*[*]",
  height:"100%",
  width:"100%",
  theme_advanced_resize_horizontal : false,
  theme_advanced_resizing : true,
  auto_reset_designmode : true
});
') ?>

Action AJAX call

And now finally in the action template which is called by ajax, use the code fragment bellow. The link_to_remote call has to have script => true.

Form:

  <form id="form_id">
 
    ......
 
    <?php echo textarea_tag('article','article','size=10x30'); ?>
 
    ......
 
  </form>

At the end of the action template (e. g. editSuccess.php):

  <a class="button save" alt="SAVE" href="#" onclick="tinymceDeactivate();submitForm('form_id'); return false">SAVE</a>  
  <?php echo javascript_tag(' 
    setTextareaToTinyMCE("article");  
  ') ?>

The important thing was to deactivate the tinymce editor on textarea before submitting the form, otherwise there were errors after re-appearing of the form.

by František Tröster on 2006-08-31, tagged ajax  tinymce 
(1 comment)

Form validation in AJAX

The example is for a blog. The page that displays a post also proposes an AJAX form to add a comment. We want that when the validation of this form fails, it displays again in the page with an error message, and when the validation succeeds, the form is replaced byu the comment just posted.

The idea is to take advantage of the way the update option of the form_remote_tag() helper works. It accepts an associative array, where you can specify different zones to update in case of success and failure. The only problem is that for Prototype, a failure is a return code other than 2XX. So when we return the form showing the error message again, we need to set the status code to 404, for instance, for Prototype to choose to update the correct zone.

That, plus the usual use of partials here and there, and you have a working solution:

in modules/post/actions/action.class.php

// Display the form
public function executeShow()
{
  $this->post = PostPeer::retrieveByPk($this->getRequestParameter('post_id'));
}

in modules/post/templates/showSuccess.php

// Display question detail here
...
// Beginning of Comment zone
<div id="added_comment" style="display_none"> </div>
<div id="add_comment">
  <?php include_partial('comment/add_comment', array('post' => $post)) ?>
 </div>

in modules/comment/templates/_add_comment.php

<?php use_helper('Javascript', 'Validation') ?>
<?php echo form_remote_tag(array(
  'url'     => 'comment/add',
  'update'  => array('success' => 'added_comment', 'failure' => 'add_comment'),
  'script'  => true,
  'loading' => "Element.show('indicator')",
  'success' => "Element.hide('indicator');Element.show('added_comment');Element.hide('add_comment');",
  'failure' => "Element.hide('indicator');",
)) ?>
  <?php echo input_hidden_tag('post_id', $post->getId()) ?>
  <?php echo form_error('body') ?>
  <label for="body">Your comment</label>
  <?php echo textarea_tag('body') ?>
  <?php echo submit_tag('Send') ?>
</form>

in modules/comment/validate/add.yml

methods:
  post: [body]
 
fillin:
  activate:        Yes
 
names:
  body:
    required:      Yes
    required_msg:  You must provide a comment
    validators:    spamValidator
 
spamValidator:
   class:          sfRegexValidator
   param:
     match:        No     
     pattern:      /http.*http/
     match_error:  Do not provide more than one URL - It is considered Spam

in modules/comment/actions/action.class.php

public function handleErrorAdd()
{
  $this->post = PostPeer::retrieveByPk($this->getRequestParameter('post_id'));
  $this->getResponse()->setStatusCode(404);
  return sfView::ERROR;
}
 
public function executeAdd()
{
  $post = PostPeer::retrieveByPk($this->getRequestParameter('post_id'));
  $this->forward404Unless($post);
  $comment = new Comment();
  $comment->setPost($post);
  $comment->setAuthor($this->getUser()->getAuthor());
  $comment->setBody($this->getRequestParameter('body'));
  $comment->save();
  $this->comment = $comment;
}

in modules/comment/templates/addError.php

<?php include_partial('comment/add_comment', array('post' => $post)) ?>

in modules/comment/templates/addSuccess.php

Your comment has been added:
<div class="comment">
  <?php echo $comment->getBody() ?>
</div>

As a bonus, the form is still there after a successful submission (but hidden), so with a few more lines of code, you can still provide a Digg-like "edit comment for the next 60 seconds" feature.

by Francois Zaninotto on 2006-10-16, tagged ajax  forms  validation 
(7 comments)

AJAX vote - 5 stars

In the template where the object that users can vote on (here, a snippet) is displayed, add:

  <?php echo use_helper('Javascript') ?>
  <?php $id = $snippet->getId() ?>
  <?php if($sf_user->canVoteFor($snippet)): ?>
    <span id="vote_for_<?php echo $id ?>" style="white-space:nowrap;">
      rate this snippet: <?php for($i = 1 ; $i <= 5 ; $i++): ?><div class="rate <?php if($i <= $snippet->getAverageVote()): ?>rated<?php endif; ?>" 
      id="vote_<?php echo $id ?>_<?php echo $i ?>"><?php echo link_to_remote(image_tag('spacer.gif', 'width=10 height=10'), array(
        'url'         => 'snippet/voteForSnippet?id='.$id.'&vote='.$i,
        'update'      => 'vote_for_'.$id,
        'loading'     => visual_effect('appear', 'indicator'),
        'complete'    => visual_effect('fade', 'indicator').visual_effect('highlight', 'vote_for_'.$id),
      ), array(
        'onMouseOver' => 'highlight_stars('.$id.', '.$i.', true);',
        'onMouseOut'  => 'highlight_stars('.$id.', '.$i.', false);',  
      )) ?></div><?php endfor; ?>
      <span id="indicator" style="display:none"> <?php echo image_tag('indicator.gif') ?></span>
    </span>
  <?php else: ?>
    <?php include_partial('voted', array('id' => $id, 'vote' => $snippet->getAverageVote())) ?>
  <?php endif; ?>

Of course, you have to define the rules about who can vote on what in the -&gt;canVoteFor() method of the User object (in apps/myapp/lib/myUser.php). The template uses a _voted.php partial:

<?php for($i = 1 ; $i <= 5 ; $i++): ?><div class="rate <?php if($i <= $vote): ?>rated<?php endif; ?>" id="vote_<?php echo $id ?>_<?php echo $i ?>"><img src="/images/spacer.gif" width="10" height="10" /></div><?php endfor; ?>

DO NOT add extra spaces to the long lines, or the stars get separated by blank spaces.

The mechanism that changes the aspect of stars relies on classes, so add the following styling to a CSS used in your template:

.rate {
display:inline;
width:10px;
height:10px;
margin:0;
padding:0;
background-image:url(/images/vote_star.gif);
background-position: left 10px;
}
.rated
{
background-position: left 0px;
}
.ratehover {
background-position: left 20px;
}

The vote_star.gif file is a 10x30px image containing three versions of the star: voted, hovered, not voted.

vote_star

The snippet/voteForSnippet action does something like:

  public function executeVoteForSnippet()
  {
    $this->id   = $this->getRequestParameter('id');
    $snippet = SnippetPeer::retrieveByPk($this->id);
    if(!$snippet)
    {
      return sfView::NONE;
    }
 
    $vote = new Vote();
    $vote->setUserId($this->getUser()->getUserId());
    $vote->setSnippetId($snippet->getId());
    $vote->setVote($this->getRequestParameter('vote'));
    $vote->save();
 
    $this->vote = $vote->getSnippet()->getAverageVote();
  }
by Francois Zaninotto on 2006-05-20, tagged ajax  vote 
(6 comments)

Define indicator for all ajax requests

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'); }
});
 
by Halil Köklü on 2007-09-16, tagged ajax  javascript 
(2 comments)

input_in_place_editor_grid_tag

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;
by ahmet ertek on 2007-05-12, tagged admin  ajax  datagrid  grid  grid  inplaceeditor  inplaceeditorgridtag  inplacegrid 
(1 comment)

Select with a onChange remote function option

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')
           )) ?>
by mlier on 2007-03-24, tagged ajax  remote  select 
(1 comment)

One link, multiple ajax div updates, only one remote call

The problem seems to arise quite frequently: 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>
 
<h1>Ajax link</h1>
<?php echo use_helper('Javascript') ?>
<?php echo link_to_remote('click me', array(
  'url'    => 'test/ajax',
  'update' => 'result',
  'script' => true,
)) ?>
<div id="result">
</div>

What would the test/ajax action look like to update both the first_zone and the second_zone?

For the code of the action itself (executeAjax()), we'll ignore it since 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) can be as follows:

<?php echo use_helper('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() ?>
 
<?php echo javascript_tag(
 
  update_element_function('first_zone', array(
    'content' => get_slot('first_update'),
  ))
  .
  update_element_function('second_zone', array(
    'content' => get_slot('second_update'),
  )) 
 
) ?>
 

Once rendered, the HTML code sent to the user will look like this:

<script type="text/javascript">
//<![CDATA[
$('first_zone').innerHTML = '  So you like clicking, uh?\n';
$('second_zone').innerHTML = '  <p>I\'d like to test quotes (like \"). </p>\n  <p>And <strong>tags</strong>, too.</p>\n';
//]]>
</script>

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.

If you happen to do this a lot, maybe you will want to package the multiple updater into a helper. That's quite easy:

function update_elements_function($updates)
{
  $res = "";
  foreach($updates as $zoneName => $slotName)
  {
    $res .= update_element_function($zoneName, array('content' => get_slot($slotName)));
  }
  return javascript_tag($res);
}

Then you could replace the call to the javascript_tag() in the ajaxSuccess.php template by a simpler:

<?php echo update_elements_function(array(
  'first_zone'  => 'first_update',
  'second_zone' => 'second_update',
)) ?>

Performancewise, if the Ajax response is small (below 512 Bytes), you'd better use the JSON approach.

by Francois Zaninotto on 2006-11-22, tagged ajax  helper  multiple 
(3 comments)

Add aditional parameters to Autocomplete

You can pass aditional parameters to input_auto_complete_tag helper. By default, autocompleter pass only object-referer value. For aditional parameter you need use 'with' in $completion_options array. in view layer:

<
<?php echo object_input_tag($model, 'getFilter', array ('size' => 20, 'control_name' => 'filter',));?>
?php echo input_auto_complete_tag('field_to_search','','module/autocomplete', array('autocomplete'=>'off'),array('use_style'=>true,'with'=> " value+'&filter='+$('filter').value"))?>

you must put '" value' (with whitespace) because javascript fail (a little symfony bug). Aditional parameters must go after value.

in control layer:

 public function executeAutocomplete(){
    $search=$this->getRequestParameter('field_to_search');
    $filter=$this->getRequestParameter('filter');
....
}

now you can use field_to_search and filter to construct any query in model layer ans show this in AutocompleteSuccess.php view or another view that you define. this was tested in synfony version > 0.9. Check your version, and review your javascripthelper helper to verify if your input_auto_complete_tag helper supports 'with' option

by Boris Duin on 2006-10-27, tagged ajax  autocomplete  javascript 

Ajax request rejected by session timeout

This snippet is usefull to handle a session timeout in an ajax request.

It is very bad to have the login page filling the update div zone...

The idea came from a post in the forum (thanks a lot RoVeRT !)

The method is :

For that purpose, we will add a little code in the ajax helper and in the login action.

1 - We handle the 401 error code in the ajax helper and enable javascript execution for the popup :

401 => "if ( confirm('Your not logged anymore... Ok to go to the login page.')) {document.location='/';}",

2 - We add this code at the beginning of the login action :

// if the request is an ajax request...
if ($this->getRequest()->isXmlHttpRequest()) {
 
// response to the ajax request : code http 401 (access unauthorized)
$this->getResponse()->setStatusCode(401);
}

Thanks for the comments !

by Vincent Texier on 2006-10-25, tagged ajax  session 
(8 comments)

Recalling AJAX state upon page reload

Remembering AJAX State

If you are updating your interface with ajax actions, typically users have no way to bookmark or return to the page with those updates already in place. This is seen as a fundamental problem with ajax: the URL shown at the top of the browser no longer constitutes a complete specification of the information shown in the window.

This snippet explains how you can track and recreate the current view even when using ajax to update your page. Now if users reload the page or send this link to a friend, the current state will remain intact. (You can view an example of this technique on http://www.ask.com/)

The trick relies on two parts:

  1. Updating the window.location.hash (the part of the url after the "#", also called an anchor) on a page.
  2. Using it to reconstruct the state of the interface. In other words, when users click on your ajax links, you will update the URL with a new hash string, and when the page is loaded you can you look for this hash string and update the interface accordingly.

Part One: Tracking State

First, you must allow the links created using the helpers link_to_remote and link_to_function to NOT have a 'return false;' appended to the end of the onclick attribute of the tag that is generated. There is no way to change this by way of the default helper, but it's easy to override with our own.

A 'return false' statement means the browser won't try to navigate to the url in the 'href' attribute of your link. Usually you don't want your browser window to navigate to the default "href" link--you are just creating the link as means to make the ajax call. Typically href attributes, if any, are present in case the user does not have javascript enabled.

However, we do not want "return false" appended to the onclick attribute. Ideally our helper function would allow us to do something like this:

<?php echo my_link_to_remote('Read this post', array(
    'update' => 'indicator',
    'url'    => 'post/read?id='.$post->getId(),
    'return' => 'true',
), array('href' => '#post:read|id:'.$post->getId()) ?>

which creates this:

<a href="#post:read|id:1234" onclick="new Ajax.Updater(....); return true;">Read this post</a>

When a user clicks the link, not only is the ajax action called but the URL of the page gets updated to something like "http://mysite.com/#post:read|id:1234".

So, let's write our own customer helper. I placed it into the /lib/helper directory in a file called myJavascriptHelper.php.

<?php
/* File: myJavascriptHelper.php */
 
/**
 * Returns a link that will trigger a javascript function using the
 * onclick handler and return TRUE OR FALSE after the fact 
 * depending on the $html_options['return'] parameter. 
 * This attribute is removed before we use the default content_tag helper.
 *
 * Examples:
 *   <?php echo link_to_function('Greeting', "alert('Hello world!')", array('return'=>true)) ?>
 *   <?php echo link_to_function(image_tag('delete'), "if confirm('Really?'){ do_delete(); }") ?>
 */
function my_link_to_function($name, $function, $html_options = array())
{
  $html_options = _parse_attributes($html_options);
 
  $html_options['href'] = isset($html_options['href']) ? $html_options['href'] : '#';
  $html_options['onclick'] = $function.'; return ';
  $html_options['onclick'] .= (isset($html_options['return']) && $html_options['return'] == true) ? 'true;' : 'false;';
  unset($html_options['return']);
 
  return content_tag('a', $name, $html_options);
}
 
/**
 * See docs on link_to_remote helper for more info.
 */
function my_link_to_remote($name, $options = array(), $html_options = array())
{
  return my_link_to_function($name, remote_function($options), $html_options);
}
 
?>

Now for my ajax links I use my new helper and make sure to pass an href attribute as well as the 'return' attributte. This determines if clicking on the link will return true (update the url) or false (don't update it). Here is a real example of how I use it to keep track of what message is currently viewed in a message component within my interface.

<?php echo my_link_to_remote('Load This Message Inline', 
    array(
        'update'    => 'message_viewer',
        'url'=>'message/viewInline?id='.$message->getId().'&view='.$view,
        'loading' => "Element.show('message_indicator')",
        'complete'=> "viewInlineComplete(".$message->getId().", '".$view."')",
        'method'=>'get',
    ), array(
        'href' => '#message:viewInline|id:'.$message->getId().'|view:'.$view,
        'return' => true)) ?>

Notice that the href attribute has a string that mirrors the URL attribute. We could probably take the my_link_to_remote helper a step further and automatically generate the HREF attribute based on the URL, but I decided to keep some flexibility there. Now, if a user clicks the link, the url will get a string like "#message:viewInline|id:3|view:inbox" appended to it.

In my experience, both IE 6 and FF did NOT scroll to the top of the window nor did the page reload. In fact I believe the W3 DOM specs state that links to a hash address never reload the document. Instead, I get a nice new attribute in the URL of the page. Now, if my user wants to bookmark the page, or perhaps reload it, the current ajax state can be recalled using the information in the anchor tag.

Part Two: Using the Hash to Reconstruct State

Although you cannot retrieve the value after the hash from within PHP, you can retrieve it client-side by way of the 'location.hash' function and then set a cookie, update the page, or do whatever you like based on those attributes. I am using it to reload a message from a users inbox into the inline viewer. Let's see how it works.

First, we need a way to load in the hash string and turn it into something we can use. Here is the function I use and included in my main.js file that is used throughout my site.

function readHashVars()
    {
        var hash = window.location.hash;
        hash = hash.substring(1);
        hash = hash.replace(/\|/g, '&');
        hash = hash.replace(/\:/g, '=');
        // lets turn our url hash into a javascript hash (like an associated array)
        // we will use a useful function provided in the prototype library
        hash = hash.parseQuery();
        return hash;
    }

Note: This function requires the prototype library to work!

Next, whenever my page is loaded I should check to see if the hash contains attributes I set within my ajax actions. If there are, I call the same client-side update from remote function as I do when a user clicks on the ajax link in the interface. Here is the top of my indexSuccess.php file for my home page action. I do not include this within the main.js file because I want to use the PHP helpers like remote_function provided by symfony. Compare this to the use of my_link_to_remote in the code above.

<?php use_helper('Javascript') ?>
<?php echo javascript_tag("
function updateDashboard()
{
    var hash = readHashVars();
    // we now have a hash with attributes matching the parameters in the url
    // so if the url has #message:viewInline|id:3, we have a javascript object like
    // hash { message: viewInline, id: 3 }
 
    if(hash.message == 'viewInline') {
        " . remote_function(array(
            'update'    => 'message_viewer',
            'url'           => 'message/viewInline',
            'with'      => "'id='+hash.id+'&view='+hash.view",
            'loading' => "Element.show('message_indicator')",
            'complete'=> 'viewInlineComplete(hash.id, hash.view)',
            'method'    => 'get',
        )) . "
    }
}
 
Event.observe(window, 'load', updateDashboard, false);
") ?>

This time, instead of calling the "remote_function" helper with parameters within PHP, I rely on javascript to send the correct parameters using the "with" parameter. That way, I can include values from javascript that aren't available until the page is loaded.

That's all there is to it (phew!). Now that this is all set up, I can easily convert my inline ajax links to javascript functions that are automatically called when the necessary attributes exist within the URL hash.

by scott meves on 2007-01-29, tagged ajax  linktoremote 

How to perform an action when input_date_tag changes.

What is worth noting in the snippet below is the usage of the 'onchange'=> remote_function( ... you can use the remote_function in many places to call other functions ... like in the "update", "complete" etc...

<div id="js_updating">Stand by..</div>
<?php echo form_tag('/module/action', 'method=get class=simpleForm') ?>
Please select the day:
<?php
echo input_date_tag('day', 'now', array('rich' => true, 'readonly'=>true, 'onchange'=> remote_function( array(
      'update'   => 'Area To Update (DIV TAG)',
      'url'      => 'MODULE/ACTION',
      'loading'  => "Element.show('js_updating')",
      'complete' => "Element.hide('js_updating')"
  ))));
 
?>
 
</form>
by Fuad Arafa on 2006-08-23, tagged ajax  forms  javascript 
(2 comments)

One link, multiple ajax div updates, the ultimate solution

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.

by Francois Zaninotto on 2007-02-23, tagged ajax 
(5 comments)

One link, multiple ajax div updates

The problem

I have:

<?php
 
echo link_to_remote("update news",
                     array(
                           'update'  => 'news_zone',
                           'url'     => 'news/index',
                          )
                   )
?>

The div with the id 'news_zone' will be updated if i click on the link 'update news'. But how can i update another div ?

My solution

You have to add the code in the file symfony/helper/JavascriptHelper.php:

function remote_function($options)
  {
    sfContext::getInstance()->getResponse()->addJavascript('/sf/js/prototype/prototype');
 
    // Multi ajax call
    if (isset($options[0]) && is_array($options[0])) {
        $multi_function = "";
        // First pass (wait)
        for ($i = 0; isset($options[$i]); $i++) {
                if (isset($options[$i]['wait']) && $options[$i]['wait'] != $i && isset($options[$options[$i]['wait']])) {
                        if (isset($options[$options[$i]['wait']]['complete'])) {
                                $options[$options[$i]['wait']]['complete'] .= '; ' . remote_function($options[$i]);
                        } else {
                                $options[$options[$i]['wait']]['complete'] = remote_function($options[$i]);
                        }
                }
        }
 
        // Second pass
        for ($i = 0; isset($options[$i]); $i++) {
                if (!(isset($options[$i]['wait']) && $options[$i]['wait'] != $i && isset($options[$options[$i]['wait']]))) {
                        $multi_function .=  remote_function($options[$i]) . ';';
                }
        }
        return $multi_function;
    }
 
    $javascript_options = _options_for_ajax($options);
...

Now you can use a new syntax (This patch won't affect the "old" syntax. You can use it):

<?php
 
echo link_to_remote('multi ajax', array(
                                         array('update' => 'news_zone',
                                               'url' => '/news/index'
                                              ),
                                         array('update' => 'second_zone',
                                               'url' => '/module/action'
                                               )
                                       )
                   );
 
?>

I provide a special feature: "ajax in order". Requests are asynchronous. Now, you can set an order:

<?php
 
echo link_to_remote('multi ajax', array(
                                         array('update' => 'news_zone',
                                               'url' => '/news/index'
                                              ),
                                         array('update' => 'second_zone',
                                               'url' => '/module/action',
                                               'wait' => 0 /* index table. So update after 'news_zone' */