Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "ajax"

Ajax multiple DIV update with Single action and multiple actions.

The idea here is to show different ways of using ajax to update different section of the document.


index.php

<br /><div id="input_tag_section_1" >input_tag_section_1</div>
<br /><div id="input_tag_section_2" >input_tag_section_2</div>
 
 
<br /><div id="update_me_1">update_me_1</div>
<br /><div id="update_me_2">update_me_2</div>
 
<br /><div id="js_holder_for_multi_update"></div>
<?php 
 
echo form_tag('test/index');
 
/**
 * The following input tag updates two different sections using two different actions.
 */
echo '<br />'.input_tag('test_input_tag1', '', array('onkeyup'=>
     remote_function(array(
        'update'=>'input_tag_section_1'
      , 'url'=> 'test/updateInputTagSectionOne'
      , 'with'=>"'value=' + \$F('test_input_tag1')"
      ))
    .remote_function(array(
        'update'=>'input_tag_section_2'
      , 'url'=> 'test/updateInputTagSectionTwo'
      , 'with'=>"'value=' + \$F('test_input_tag1')"
      ))
    ));
 
/**
 * The following input tag updates two different sections (DIV tags) using a single action
 * This is done using a javasctipt that gets created in the called action, and updates several sections 
 */
echo '<br />'.input_tag('test_input_tag2', '', array('onkeyup'=>
     remote_function(array(
        'update'=>'js_holder_for_multi_update'
      , 'url'=> 'test/updateJsHolderForMultiUpdate'
      , 'with'=>"'value=' + \$F('test_input_tag2')"
      , 'script'=>true))
    ));
 
 
echo '</form>';
 
?>
 

action.class.php

Here we have the different actions that are used by the ajax callers above.

<?php
 
class testActions extends sfActions
{
  public function executeIndex()
  {
    //nothing to do here
  }
 
  public function executeUpdateInputTagSectionOne(){
    $this->valueFromAction = 'This is from section ONE action.';
  }
 
  public function executeUpdateInputTagSectionTwo(){
    $this->valueFromAction = 'This is from section TWO action.';
  }
 
  public function executeUpdateJsHolderForMultiUpdate(){
    $this->valueFromAction = 'This is from Multi update action.';
    $this->update_me_1_value = 'Value for first ';
    $this->update_me_2_value = 'Value for second ';
  }
 
 
}
?>
 

updateInputTagSectionOneSuccess.php

This is called by the first input example, updates two DIVs with different content from different actions.

<?php
 
echo $valueFromAction .' Value = '.$sf_params->get('value');
 
?>
 

updateInputTagSectionTwoSuccess.php

This is called by the first input example, updates two DIVs with different content from different actions.

This contains the same code as the previous one template, however the action is giving it a different value.

<?php
 
echo $valueFromAction .' Value = '.$sf_params->get('value');
 
?>
 

updateJsHolderForMultiUpdateSuccess.php

This is called by the second input example, updates two DIVs with different content from a single action.

This template will only contain javascript to update our DIV tags with required data.

Note how the "update_element_function" is concatenated below.

<?php
echo javascript_tag(
     update_element_function('update_me_1', array('content' => $update_me_1_value.' '.$sf_params->get('value')))
    .update_element_function('update_me_2', array('content' => $update_me_2_value.' '.$sf_params->get('value')))
  );
?>
 

Finally .. Enjoy, and I really hope this helps someone out there.

by Fuad Arafa on 2009-09-08, tagged ajax  div  multiple  update 

LightWindow, SwfObject and Flash together

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!

by Diogo Baeder on 2008-03-24, tagged ajax  flash  lightwindow  plugin  swfobject 
(1 comment)

perform an Ajax in Place Rich editor combining inPlaceRichEditor and TinyMCE

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

by Imron Setio Widodo on 2008-02-03, tagged ajax  tinymce 
(1 comment)

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 

Ajax InplaceSelect

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!

by Thomas Eigner on 2007-09-17, tagged ajax  inplaceselect  javascript 
(4 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)

Div to toggle and remote

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>
 
by julien levasseur on 2007-09-14, tagged ajax  div  link  remote  toggle  visualeffect 

Quick Search for admin_double_list

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.

by Myke Hines on 2007-06-29, tagged admin  admindoublelist  admingenerator  ajax  generator 
(3 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)

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 

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, 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)

Tip about input_in_place_editor_tag

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.

by Andrea Giorgini on 2007-02-23, tagged ajax 
(1 comment)

Degradable AJAX for form_remote_tag(), link_to_remote(), button_to_remote(),etc

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/
by Drew Lipsky on 2007-01-30, tagged ajax  buttontoremote  degradable  degradation  formremotetag  graceful  linktoremote 

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 

AJAX Degredation Helper that creates
tags with fragments inside

This helper creates a div tag tag into which links can call other actions and dynamically display its output via AJAX. When JS is not enabled, the links reload the page with GET parameters and allow this helper to load the output.

What do you think of this code? First of all, has it already been done before? And do you think it will actually be useful? How well does it conform to the MVC structure?

Actual code:

<?php
/**
 * This file includes functions that assist in making AJAX degradable
 *
 * These small helper functions add in some of the basic features that allow for degradable
 * JS. The idea is to dynamically load contents when JS is available and let the page reload with
 * appropriate html when JS is not.
 * @author Yining Zhao
 * @package YZ_Helpers
 * @subpackage AJAX_Degredation
 * @since 1/16/2007
 */
 
/**
 * This class creates a pair of <div> tags in which AJAX functions can input dynamic HTML.
 *
 * Usually what happends is that a link is pressed to activate the AJAX command via its onClick method.
 * The HREF attribute is suppressed by a 'return false;' at the end of the onClick method. However
 * when JS is not available, the onClick method is not activated and thus the HREF is called. The 
 * link in the HREF reloads the page and uses GET to pass along the variables to load the designated
 * fragment.
 *
 * If the $trigger_var is 'login_box', here is what the following GET parameters will do:
 *      login_box_display will load the fragment if it is set to 'on', otherwise div style = visibility:none and nothing is loaded
 *          e.g. URL: www.website.com/file.php?login_box_display=on //will load fragment
 *
 *      login_box_make_persistant will let set login_box_display to 'on' in the user session, so as long as
 *          login_box_make_persistant is not set to 'no', the fragment will automatically be loaded on every page 
 *          load regardless of the GET statement
 *          e.g. URL: www.website.com/file.php?login_box_make_persistant=on //will load fragment on every page load
 * 
 * Sample usage of function:
 *      //This function is being called from listSuccess.php template file in my Post module    
 *      div_fragment_creater('login_box','partial','loginPartial',array('referer'=>$referer),array('id'=>'login_div_id'))
 *
 * The source: {@source} 
 *
 * @param string $trigger_var The variable passed via GET that determines if the partial is loaded
 * @param string $fragment_type This tells the function if you are using a partial or a component
 * @param string $fragment_filename The name of the fragment to be called
 * @param array $fragment_values_ary This passes in the values for the fragment
 * @param array $div_attr_ary This contains all the html attributes for the <div> tag
 * @return void Since this function is designed to echo the necessary html, return is void
 */
function div_fragment_creater($trigger_var,$fragment_type, 
                                    $fragment_filename,$fragment_values_ary,$div_attr_ary)
{
    $sf_user=sfContext::getInstance()->getUser()->getAttributeHolder();
    $sf_params=sfContext::getInstance()->getRequest()->getParameterHolder();
    //If in the GET there is a make_persistant command for the given trigger variable, then the display command for the
    //tigger is set for sessions
    if($sf_params->get($trigger_var.'_make_persistant')=='on')
    {
        $sf_user->set($trigger_var.'_display','on');
    } else if($sf_params->get($trigger_var.'_make_persistant')=='off')
    {
        $sf_user->remove($trigger_var.'_display');
        $sf_params->remove($trigger_var.'_display');
    }
    //If the display is set for the trigger in either sf_params or sf_user, display the fragment.
    //Otherwise, don't display the fragment and make the div attribute style = display:none
    $output_fragment=false;
    if(($sf_params->get($trigger_var.'_display')=='on')||($sf_user->get($trigger_var.'_display')=='on'))
    {
        $output_fragment=true;
 
    } else
    {
        $div_attr_arry['style'] = 'display:none';
    }
 
    $div_attr_keys = array_keys($div_attr_ary);
    $div_attr_ary_size = count($div_attr_keys);
    $div_attr='';
    $div_attr_collection='';
    //Cteates the attributes for the <div>
    for($i=0;$i<$div_attr_ary_size;$i++)
    {
        //This creates a string where the div attribute is set to its corresponding value; e.g. id=5
        $div_attr_name = $div_attr_keys[$i];
        $div_attr_collection.=$div_attr_name . ' = "' . $div_attr_ary[$div_attr_name] . '" ';
    }
    echo '<div '.$div_attr_collection.' >';
    if($output_fragment==true)
    {
        switch($fragment_type)
        {
            case 'partial':
                echo include_partial($fragment_filename, $fragment_values_ary);
                break;
            case 'component':
                use_component($fragment_filename, $fragment_values_ary);
                break;
        }
    }
    echo '</div>';
}
?>

Usage Example: In myApp/myModule/list.php:

<body>
Anything is possible...
<?php 
//Creates div with id = myDiv. Usually this div is invisible and empty. But when the get or session variable of triggerVar_display is set to 'on', this div becomes visible and displays the whatsPossiblePartial partial file.
div_fragment_creater('triggerVar','partial','whatsPossiblePartial',array('id'=>$id),array('id'=>'myDiv'))
 
//Provides link to load action into myDiv. If JS is not enabled, link will reload page with triggerVar_display set to 'on'
 
echo link_to_remote('Click To See Whats Possible', array(
            'url'=>'MyModule/WhatPossible?id='.$value,
            'update'=>array('success' => 'myDiv'),
            'loading'=>"Element.show('indicator')",
            'complete'=>visual_effect('blind_down','myDiv',array('duration'=>0.5))';',
            ),          array('href'=>'list?triggerVar_display=on'));
?>
</body>

Inside the whatPossibleSuccess.php:

<html> ...
<?php 
//This file is basically just a wrapper for the whatsPossiblePartial. the reason I have this wrapper is so that I can access it from via link_to_remote() on other pages.
echo include_partial(whatsPossiblePartial,array('id'=>$this->id));
?>
...
</html>
by whoknows on 2007-01-18, tagged ajax  degradation  helper  javascript 

using AJAX to check username duplication

using AJAX to check username duplication. there's another submit button on form, should not use submit whole page to check username availability.

in template:
        <DIV id='usercheck'>&nbsp;</DIV>
        <?php
 
        echo submit_to_remote('ajax_submit', 'Check Username', array(
            'update'   => 'usercheck',
            'url'      => '/user/checkusername',
        ));

in action class, check dupliation:

    public function executeCheckusername(){
        $username=$this->getRequestParameter('obj_username');
        $c = new Criteria();
        $c->add(UserPeer::USERNAME, $username);
        $user = UserPeer::doSelectOne($c);
 
        if ($user) {        // username already exists
            $this->setFlash('username_exists', 'y');
            return sfView::SUCCESS;
        } else {
            $this->setFlash('username_exists', 'n');
            return sfView::SUCCESS;
        }
 
    }

in checkusernameSuccess, display error message:

    <?php
    if($sf_flash->get('username_exists') == 'y'){ ?>
    <FONT COLOR="RED">Username Not Available</FONT>
    <?php } else { ?>
    <FONT COLOR="GREEN">Username Available</FONT>
    <?php } ?>
by William Duan on 2007-01-07, tagged ajax 
(4 comments)

cascading combo 2-level mulit-select list (continued)

~~~The table sql statments can't be edited into previous snippet, so paste here

*** table (the following tables have been splited up into 2 tables to use i18n, but the above code doesn't, so either merge these two tables into 1, or setCulture() in above action.class.php code):

-- 
-- Table structure for table `category`
-- 
 
CREATE TABLE `category` (
  `id` int(11) NOT NULL auto_increment,
  `parent_id` int(11) default NULL,
  PRIMARY KEY  (`id`),
  KEY `category_FI_1` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=43 ;
 
-- 
-- Dumping data for table `category`
-- 
 
INSERT INTO `category` VALUES (1, NULL);
INSERT INTO `category` VALUES (2, NULL);
INSERT INTO `category` VALUES (3, NULL);
INSERT INTO `category` VALUES (4, NULL);
INSERT INTO `category` VALUES (5, NULL);
INSERT INTO `category` VALUES (6, NULL);
INSERT INTO `category` VALUES (7, NULL);
INSERT INTO `category` VALUES (8, 1);
INSERT INTO `category` VALUES (9, 1);
INSERT INTO `category` VALUES (10, 1);
INSERT INTO `category` VALUES (11, 1);
INSERT INTO `category` VALUES (12, 1);
INSERT INTO `category` VALUES (13, 2);
INSERT INTO `category` VALUES (14, 2);
INSERT INTO `category` VALUES (15, 2);
INSERT INTO `category` VALUES (16, 2);
INSERT INTO `category` VALUES (17, 2);
INSERT INTO `category` VALUES (18, 3);
INSERT INTO `category` VALUES (19, 3);
INSERT INTO `category` VALUES (20, 3);
INSERT INTO `category` VALUES (21, 3);
INSERT INTO `category` VALUES (22, 3);
INSERT INTO `category` VALUES (23, 4);
INSERT INTO `category` VALUES (24, 4);
INSERT INTO `category` VALUES (25, 4);
INSERT INTO `category` VALUES (26, 4);
INSERT INTO `category` VALUES (27, 4);
INSERT INTO `category` VALUES (28, 5);
INSERT INTO `category` VALUES (29, 5);
INSERT INTO `category` VALUES (30, 5);
INSERT INTO `category` VALUES (31, 5);
INSERT INTO `category` VALUES (32, 5);
INSERT INTO `category` VALUES (33, 6);
INSERT INTO `category` VALUES (34, 6);
INSERT INTO `category` VALUES (35, 6);
INSERT INTO `category` VALUES (36, 6);
INSERT INTO `category` VALUES (37, 6);
INSERT INTO `category` VALUES (38, 7);
INSERT INTO `category` VALUES (39, 7);
INSERT INTO `category` VALUES (40, 7);
INSERT INTO `category` VALUES (41, 7);
INSERT INTO `category` VALUES (42, 7);
 
-- --------------------------------------------------------
 
-- 
-- Table structure for table `category_i18n`
-- 
 
CREATE TABLE `category_i18n` (
  `id` int(11) NOT NULL,
  `culture` varchar(7) NOT NULL,
  `name` varchar(50) character set utf8 collate utf8_unicode_ci default NULL,
  `description` varchar(500) character set utf8 collate utf8_unicode_ci default NULL,
  PRIMARY KEY  (`id`,`culture`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
 
-- 
-- Dumping data for table `category_i18n`
-- 
 
INSERT INTO `category_i18n` VALUES (1, 'en', 'German', 'description of German');
INSERT INTO `category_i18n` VALUES (1, 'zh', 'DeGuo', 'DeGuo MiaoShu');
INSERT INTO `category_i18n` VALUES (2, 'en', 'France', 'description of France');
INSERT INTO `category_i18n` VALUES (2, 'zh', 'FaGuo', 'FaGuo MiaoShu');
INSERT INTO `category_i18n` VALUES (3, 'en', 'Argentina', 'description of Argentina');
INSERT INTO `category_i18n` VALUES (3, 'zh', 'AGenTing', 'AGenTing MiaoShu');
INSERT INTO `category_i18n` VALUES (4, 'en', 'Australia', 'description of Australia');
INSERT INTO `category_i18n` VALUES (4, 'zh', 'AoDaLiYa', 'AoDaLiYa MiaoShu');
INSERT INTO `category_i18n` VALUES (5, 'en', 'Brazia', 'description of Brazia');
INSERT INTO `category_i18n` VALUES (5, 'zh', 'BaXi', 'BaXi MiaoShu');
INSERT INTO `category_i18n` VALUES (6, 'en', 'Greece', 'description of Greece');
INSERT INTO `category_i18n` VALUES (6, 'zh', 'XiLa', 'XiLa MiaoShu');
INSERT INTO `category_i18n` VALUES (7, 'en', 'England', 'description of England');
INSERT INTO `category_i18n` VALUES (7, 'zh', 'YingGeLan', 'YingGeLan MiaoShu');
INSERT INTO `category_i18n` VALUES (8, 'en', 'Team1.DE', 'description of Team1.DE');
INSERT INTO `category_i18n` VALUES (8, 'zh', 'ZhuQiuDui1.DeGuo', 'MiaoShu ZhuQiuDui1.DeGuo');
INSERT INTO `category_i18n` VALUES (9, 'en', 'Team2.DE', 'description of Team2.DE');
INSERT INTO `category_i18n` VALUES (9, 'zh', 'ZhuQiuDui2.DeGuo', 'MiaoShu ZhuQiuDui2.DeGuo');
INSERT INTO `category_i18n` VALUES (10, 'en', 'Team3.DE', 'description of Team3.DE');
INSERT INTO `category_i18n` VALUES (10, 'zh', 'ZhuQiuDui3.DeGuo', 'MiaoShu ZhuQiuDui3.DeGuo');
INSERT INTO `category_i18n` VALUES (11, 'en', 'Team4.DE', 'description of Team4.DE');
INSERT INTO `category_i18n` VALUES (11, 'zh', 'ZhuQiuDui4.DeGuo', 'MiaoShu ZhuQiuDui4.DeGuo');
INSERT INTO `category_i18n` VALUES (12, 'en', 'Team5.DE', 'description of Team5.DE');
INSERT INTO `category_i18n` VALUES (12, 'zh', 'ZhuQiuDui5.DeGuo', 'MiaoShu ZhuQiuDui5.DeGuo');
INSERT INTO `category_i18n` VALUES (13, 'en', 'Team1.Fr', 'description of Team1.Fr');
INSERT INTO `category_i18n` VALUES (13, 'zh', 'ZhuQiuDui1.FaGuo', 'MiaoShu ZhuQiuDui1.FaGuo');
INSERT INTO `category_i18n` VALUES (14, 'en', 'Team2.Fr', 'description of Team2.Fr');
INSERT INTO `category_i18n` VALUES (14, 'zh', 'ZhuQiuDui2.FaGuo', 'MiaoShu ZhuQiuDui2.FaGuo');
INSERT INTO `category_i18n` VALUES (15, 'en', 'Team3.Fr', 'description of Team3.Fr');
INSERT INTO `category_i18n` VALUES (15, 'zh', 'ZhuQiuDui3.FaGuo', 'MiaoShu ZhuQiuDui3.FaGuo');
INSERT INTO `category_i18n` VALUES (16, 'en', 'Team4.Fr', 'description of Team4.Fr');
INSERT INTO `category_i18n` VALUES (16, 'zh', 'ZhuQiuDui4.FaGuo', 'MiaoShu ZhuQiuDui4.FaGuo');
INSERT INTO `category_i18n` VALUES (17, 'en', 'Team5.Fr', 'description of Team5.Fr');
INSERT INTO `category_i18n` VALUES (17, 'zh', 'ZhuQiuDui5.FaGuo', 'MiaoShu ZhuQiuDui5.FaGuo');
INSERT INTO `category_i18n` VALUES (18, 'en', 'Team1.AR', 'description of Team1.AR');
INSERT INTO `category_i18n` VALUES (18, 'zh', 'ZhuQiuDui1.AGenTing', 'MiaoShu ZhuQiuDui1.AGenTing');
INSERT INTO `category_i18n` VALUES (19, 'en', 'Team2.AR', 'description of Team2.AR');
INSERT INTO `category_i18n` VALUES (19, 'zh', 'ZhuQiuDui2.AGenTing', 'MiaoShu ZhuQiuDui2.AGenTing');
INSERT INTO `category_i18n` VALUES (20, 'en', 'Team3.AR', 'description of Team3.AR');
INSERT INTO `category_i18n` VALUES (20, 'zh', 'ZhuQiuDui3.AGenTing', 'MiaoShu ZhuQiuDui3.AGenTing');
INSERT INTO `category_i18n` VALUES (21, 'en', 'Team4.AR', 'description of Team4.AR');
INSERT INTO `category_i18n` VALUES (21, 'zh', 'ZhuQiuDui4.AGenTing', 'MiaoShu ZhuQiuDui4.AGenTing');
INSERT INTO `category_i18n` VALUES (22, 'en', 'Team5.AR', 'description of Team5.AR');
INSERT INTO `category_i18n` VALUES (22, 'zh', 'ZhuQiuDui5.AGenTing', 'MiaoShu ZhuQiuDui5.AGenTing');
INSERT INTO `category_i18n` VALUES (23, 'en', 'Team1.AU', 'description of Team1.AU');
INSERT INTO `category_i18n` VALUES (23, 'zh', 'ZhuQiuDui1.AoDaLiYa', 'MiaoShu ZhuQiuDui1.AoDaLiYa');
INSERT INTO `category_i18n` VALUES (24, 'en', 'Team2.AU', 'description of Team2.AU');
INSERT INTO `category_i18n` VALUES (24, 'zh', 'ZhuQiuDui2.AoDaLiYa', 'MiaoShu ZhuQiuDui2.AoDaLiYa');
INSERT INTO `category_i18n` VALUES (25, 'en', 'Team3.AU', 'description of Team3.AU');
INSERT INTO `category_i18n` VALUES (25, 'zh', 'ZhuQiuDui3.AoDaLiYa', 'MiaoShu ZhuQiuDui3.AoDaLiYa');
INSERT INTO `category_i18n` VALUES (26, 'en', 'Team4.AU', 'description of Team4.AU');
INSERT INTO `category_i18n` VALUES (26, 'zh', 'ZhuQiuDui4.AoDaLiYa', 'MiaoShu ZhuQiuDui4.AoDaLiYa');
INSERT INTO `category_i18n` VALUES (27, 'en', 'Team5.AU', 'description of Team5.AU');
INSERT INTO `category_i18n` VALUES (27, 'zh', 'ZhuQiuDui5.AoDaLiYa', 'MiaoShu ZhuQiuDui5.AoDaLiYa');
INSERT INTO `category_i18n` VALUES (28, 'en', 'Team1.BR', 'description of Team1.BR');
INSERT INTO `category_i18n` VALUES (28, 'zh', 'ZhuQiuDui1.BaXi', 'MiaoShu ZhuQiuDui1.BaXi');
INSERT INTO `category_i18n` VALUES (29, 'en', 'Team2.BR', 'description of Team2.BR');
INSERT INTO `category_i18n` VALUES (29, 'zh', 'ZhuQiuDui2.BaXi', 'MiaoShu ZhuQiuDui2.BaXi');
INSERT INTO `category_i18n` VALUES (30, 'en', 'Team3.BR', 'description of Team3.BR');
INSERT INTO `category_i18n` VALUES (30, 'zh', 'ZhuQiuDui3.BaXi', 'MiaoShu ZhuQiuDui3.BaXi');
INSERT INTO `category_i18n` VALUES (31, 'en', 'Team4.BR', 'description of Team4.BR');
INSERT INTO `category_i18n` VALUES (31, 'zh', 'ZhuQiuDui4.BaXi', 'MiaoShu ZhuQiuDui4.BaXi');
INSERT INTO `category_i18n` VALUES (32, 'en', 'Team5.BR', 'description of Team5.BR');
INSERT INTO `category_i18n` VALUES (32, 'zh', 'ZhuQiuDui5.BaXi', 'MiaoShu ZhuQiuDui5.BaXi');
INSERT INTO `category_i18n` VALUES (33, 'en', 'Team1.GR', 'description of Team1.GR');
INSERT INTO `category_i18n` VALUES (33, 'zh', 'ZhuQiuDui1.XiLa', 'MiaoShu ZhuQiuDui1.XiLa');
INSERT INTO `category_i18n` VALUES (34, 'en', 'Team2.GR', 'description of Team2.GR');
INSERT INTO `category_i18n` VALUES (34, 'zh', 'ZhuQiuDui2.XiLa', 'MiaoShu ZhuQiuDui2.XiLa');
INSERT INTO `category_i18n` VALUES (35, 'en', 'Team3.GR', 'description of Team3.GR');
INSERT INTO `category_i18n` VALUES (35, 'zh', 'ZhuQiuDui3.XiLa', 'MiaoShu ZhuQiuDui3.XiLa');
INSERT INTO `category_i18n` VALUES (36, 'en', 'Team4.GR', 'description of Team4.GR');
INSERT INTO `category_i18n` VALUES (36, 'zh', 'ZhuQiuDui4.XiLa', 'MiaoShu ZhuQiuDui4.XiLa');
INSERT INTO `category_i18n` VALUES (37, 'en', 'Team5.GR', 'description of Team5.GR');
INSERT INTO `category_i18n` VALUES (37, 'zh', 'ZhuQiuDui5.XiLa', 'MiaoShu ZhuQiuDui5.XiLa');
INSERT INTO `category_i18n` VALUES (38, 'en', 'Team1.EN', 'description of Team1.EN');
INSERT INTO `category_i18n` VALUES (38, 'zh', 'ZhuQiuDui1.YingGeLan', 'MiaoShu ZhuQiuDui1.YingGeLan');
INSERT INTO `category_i18n` VALUES (39, 'en', 'Team2.EN', 'description of Team2.EN');
INSERT INTO `category_i18n` VALUES (39, 'zh', 'ZhuQiuDui2.YingGeLan', 'MiaoShu ZhuQiuDui2.YingGeLan');
INSERT INTO `category_i18n` VALUES (40, 'en', 'Team3.EN', 'description of Team3.EN');
INSERT INTO `category_i18n` VALUES (40, 'zh', 'ZhuQiuDui3.YingGeLan', 'MiaoShu ZhuQiuDui3.YingGeLan');
INSERT INTO `category_i18n` VALUES (41, 'en', 'Team4.EN', 'description of Team4.EN');
INSERT INTO `category_i18n` VALUES (41, 'zh', 'ZhuQiuDui4.YingGeLan', 'MiaoShu ZhuQiuDui4.YingGeLan');
INSERT INTO `category_i18n` VALUES (42, 'en', 'Team5.EN', 'description of Team5.EN');
INSERT INTO `category_i18n` VALUES (42, 'zh', 'ZhuQiuDui5.YingGeLan', 'MiaoShu ZhuQiuDui5.YingGeLan');
by William Duan on 2007-01-04, tagged ajax 

cascading combo 2-level mulit-select list

There are two multi-select box, the first one is parent list menu, the second one is child menu; when clicking item in first list, its submenu will appear in second list, then you can multi-select from 2nd list; and you can multi-select items in 1st list by clicking, double-click, ctrl+click, shift+click, all selected items in 1st list will display their children in 2nd list, all deselected items from 1st list will remove their children from 2nd list. This combo list can be used for select HOBBIES, TYPES, CATEGORIES etc.

*** in multiselSuccess.php:

<script type="text/javascript" src="/sf/js/prototype/prototype.js"></script>
<body onload="new Ajax.Request('/news/select/level/province', {asynchronous:true, evalScripts:false, onComplete:function(request, json){updateJSON(request,json,1)}}); return false;" >

*** in multiselSuccess.php:

<?php
            echo select_tag("province",options_for_select(Array(''=>'-Select-')),'multiple=multiple size=10  onmousedown=GetCurrentListValues(this); onchange=javascript:loadCity(\''.sfConfig::get('app_site_url').'news/select/level/city\',this)');
            echo select_tag('city[]',options_for_select(Array(''=>'-Select-')),'multiple=multiple size=10');
            ?>
            <div id=statusTxt></div>

*** in view.yml:

selectSuccess:
  has_layout: off

*** javascript can be written in multiselSuccess.php, or in another javascript file multiselSuccess.js: if written in a seperate file multiselSuccess.js, put it under PROJECT_NAME\web\js\, and in ...\apps\APPLICATION_NAME\config:

    javascripts:    [multiselSuccess.js ]

<script language="javascript">
<!--//
/**
 * ajax no refresh cascading 2-level menu
 *
 */
function loadCity(url,CONTROL) {                            //load city
 
    var strTmp=provinceChanged(CONTROL);                    //result format: +provinceId or -provinceId
    var provChg=strTmp.split(",");
    url=url+"/id/"+provChg[0];                              //provChg[0]: item value of first menu; url: /news/select/level/city/id/ItemValueOfFirstMenu
    new Ajax.Request(url, {asynchronous:true, evalScripts:false, onComplete:function(request, json){updateJSON(request,json,2,provChg[1])}});
    return false;
}
 
var arrOldValues;
function GetCurrentListValues(CONTROL){
    var strValues = "";
    strValues = GetSelectValues(CONTROL);
    arrOldValues = strValues.split(",")
}
 
function GetSelectValues(CONTROL){
    var strTemp = "";
    for(var i = 0;i < CONTROL.length;i++){
        if(CONTROL.options[i].selected == true){
            strTemp += "1,";
        }
        else{
            strTemp += "0,";
        }
    }
    return strTemp;
}
 
function provinceChanged(CONTROL){
    var strTemp = GetSelectValues(CONTROL);
    arrNewValues = strTemp.split(",");
    var sum=0;
    for(var i=0;i<arrNewValues.length-1;i++){
        sum+=eval(arrNewValues[i]);
        if (sum >= 2){                                      //more than 1 are selected
            break;
        }
        if (arrNewValues[i] == 1){                          //when this condition is met, sum must be 1
            var selectedPos=i;
        }
    }
 
    if (sum==0){                                            //deselect all by ^Click; remove all from 2nd menu
        provChg=""+",--";
    }
    else if (sum==1){                                       //only one selected, click or ctrl+click
        if (arrOldValues[selectedPos] == 0){                //if this selcted one wasn't selected before, just add;
                                                            //at this point,arrNewValues[selectedPos] must be 1
            removeOptionAll("city");
            provChg=CONTROL.options[selectedPos].value+",+";
        }
        else{               //if this selcted one was selected before, remove all others but keep selected of this one
            removeOptionAll("city");
            provChg=CONTROL.options[selectedPos].value+",+";        //???to simplify, just add, previously selected may be lost
        }
    }
    else{       //
        for(var i=0;i<arrNewValues.length-1;i++){
            if (arrNewValues[i] != arrOldValues[i]){
                if (arrNewValues[i]==1){                    //add a new province
 
                    provChg=CONTROL.options[i].value+",+";
                }
                else{                                       //remove a new province
                    provChg=CONTROL.options[i].value+",-";
                }
            }
        }
    }
    return provChg;
}
 
/**
 * ajax no refresh 2-level cascading menu
 *
 */
function addOption(objSelectNow,txt,val)
{
    //using W3C syntax add Options for SELECT
    var objOption = document.createElement("OPTION");
    objOption.text= txt;
    objOption.value=val;
    objSelectNow.options.add(objOption);
}
 
function addOptionGroup(selectId,optGroup)
{
    var objSelect = document.getElementsByTagName("SELECT");
    var objSelectNow = objSelect[selectId];
 
    if (selectId=="province"){
        objSelectNow.length = 1;
    }
        /// add Options group
        for (i=0; i<optGroup.length; i++)
        {
            addOption(objSelectNow, optGroup[i][1], optGroup[i][0]);
        }
 
}
 
 
function removeOptionGroup(selectId,optGroup)                   //optGroup: array of 2nd menu corresponding to deselected item of 1st menu; categoryId=>categoryName
{
    var objSelect = document.getElementsByTagName("SELECT");
    var objSelectNow = objSelect[selectId];
 
    for (i=0;i<objSelectNow.length;i++){
        if (objSelectNow.options[i].value==optGroup[0][0]){     //the first position to be removed
            var bgn=i;
            break;
        }
    }
 
    /// remove Options
    for (i=0;i<optGroup.length;i++){
        objSelectNow.options.remove(bgn);
    }
}
 
function removeOptionAll(selectId)
{
    var objSelect = document.getElementsByTagName("SELECT");
    var objSelectNow = objSelect[selectId];
    for (i=1;i<objSelectNow.options.length;){
        objSelectNow.options.remove(1);
    }
}
 
function updateJSON(request, json,level,operation){
    var responses = json;
    if (!json){
      //if you don't use the json tips then evaluate the renderedText instead
      var responses = eval('(' + request.responseText + ')');
    }
    if (level==1)                                       //for 1st menu
        addOptionGroup("province",responses);
    else if (level==2){                                 //for 2nd menu
        if (operation=="+"){
            addOptionGroup("city",responses);
        }
        else if (operation=="-"){
            removeOptionGroup("city",responses);
        }
        else if (operation=="--"){
            removeOptionAll("city");
        }
    }
}
 
-->
</script>

*** in actions.classs.php:

public function executeSelect(){
    /**
     * ajax no refresh 2-level cascading menu back-end
     *
     */
    //load data:
 
    $c=new Criteria();
    $c->add(CategoryPeer::PARENT_ID,NULL);                  //get contents for 1st menu
    $categories=CategoryPeer::doSelect($c);
    $parent="";
    if ($categories){
        $i=0;
        $parent='[';
        foreach ($categories as $category){
            $name=$category->getName();
            $id=$category->getId();
            $parent=$parent.'["'.$id.'","'.$name.'"],';     //format for 1st menu: [["1","German"],["2","France"],["3","Argentina"]]
 
            //retrive records whose parent_id is..
            $c=new Criteria();
            $c->add(CategoryPeer::PARENT_ID,$category->getId());
            $categories_sub=CategoryPeer::doSelect($c);
            $city[$id]='[';                                  //index of $city[] is category id; format of $city[]:[["48","Team1.Fr"],["49","Team2.Fr"],["50","Team3.Fr"],["51","Team4.Fr"],["52","Team5.Fr"]]
            foreach ($categories_sub as $category_sub){
                $city[$id]=$city[$id].'["'.$category_sub->getId().'","'.$category_sub->getName().'"],';
            }
            $city[$id]=rtrim($city[$id],",");
            $city[$id].=']';
            $i++;
        }
    }
    $parent=rtrim($parent,",");
    $parent.=']';
 
    $level=$this->getRequestParameter('level');
    if($level=="province"||$level==""){     //level=province, output contents for first menu
         $this->output=$parent;
    }
    else if($level=="city"){                //level=city, output contents for second menu
        $id=$this->getRequestParameter('id');
        $this->output=$city[$id];
    }
    else{                                   //false
        $this->output="error when getting data";
    }
    return sfView::SUCCESS;
}

*** in selectSuccess.php:

<?php
echo $output;
?>
by William Duan on 2007-01-04, tagged ajax  javascript 
(3 comments)

initiate new XMLHttpRequest object - browser crossing

function getxmlhttp() {
    //Create a boolean variable to check for a valid IE instance.
    var xmlhttp = false;
 
    //Check if we are using IE.
    try {
        //If the javascript version is greater than 5.
        xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
        //If not, then use the older active x object.
        try {
            //If we are using IE.
            xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        } catch (E) {
            //Else we must be using a non-IE browser.
            xmlhttp = false;
        }
    }
 
    //If we are using a non-IE browser, create a JavaScript instance of the object.
    if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
        xmlhttp = new XMLHttpRequest();
    }
 
    return xmlhttp;
}
by Ken Vu on 2006-12-16, tagged ajax  javascript  new  object  xmlhttprequest 

initiate new XMLHttpRequest object - browser crossing

function getxmlhttp() {
    //Create a boolean variable to check for a valid IE instance.
    var xmlhttp = false;
 
    //Check if we are using IE.
    try {
        //If the javascript version is greater than 5.
        xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
        //If not, then use the older active x object.
        try {
            //If we are using IE.
            xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        } catch (E) {
            //Else we must be using a non-IE browser.
            xmlhttp = false;
        }
    }
 
    //If we are using a non-IE browser, create a JavaScript instance of the object.
    if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
        xmlhttp = new XMLHttpRequest();
    }
 
    return xmlhttp;
}
[/code/
by Ken Vu on 2006-12-16, tagged ajax  javascript  new  object  xmlhttprequest 
(2 comments)

Decode UTF8 ajax requests

Scriptaculous sends the request parameters always in UTF-8 encoding, regardless of what the page encoding has been set to. This can cause problems with special characters like german umlauts if your general characterset is not set to utf8.

The filter below acts on ajax requests only, converting the parameter values to single-byte ISO-8859-1.

You can enable this filter in your filters.yml by adding the following:

xmlhttpUtf8DecodeFilter:
  class: xmlhttpUtf8DecodeFilter

Below is the code of the actual filter. Store this in the lib directory of your project/application (and do not forget to clean your cache: symfony cc).

<?php
/**
 * This filter acts on AJAX requests and decodes the
 * request parameter values using the php utf8_decode()
 * function.
 * 
 * @author jmunz <jochen.munz@virn.de>
 */
class xmlhttpUtf8DecodeFilter extends sfFilter {
 
    /**
     * Run the filter
     */
    public function execute ($filterChain) {
        $request = $this->getContext()->getRequest();
        // Only act if this is an ajax request
        if ( $request->isXmlHttpRequest() && $this->isFirstCall() ){
            $params = $request->getParameterHolder()->getAll();
            $this->decodeParameters($params);
        }
        // execute next filter
        $filterChain->execute();
    }
 
    /**
     * Decode parameters
     */
    private function decodeParameters(&$params){
        foreach (array_keys($params) as $key) {
            if ( is_array($params[$key]) ) {
                $this->decodeParameters($params[$key]);
            } else {
                $params[$key] = utf8_decode($params[$key]);
            }
        }
    }
 
}
by Jochen Munz on 2006-12-13, tagged ajax  iso88591  utf8 
(3 comments)

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)

More on multiple ajax div updates

Quentin Garnier, in this snippet: http://www.symfony-project.com/snippets/snippet/57, suggested a way to have a link call multiple remote functions (with "in order" calls being enforced by a new 'wait' parameter). With a slightly modified version of his code, here are a few more functions to be added to you Javascript.php function in the symfony core:

function periodically_call_remotes($options = array())
{
  $frequency = isset($options[0]['frequency']) ? $options[0]['frequency'] : 10; // every ten seconds by default
  $code = 'new PeriodicalExecuter(function() {'.remote_functions($options).'}, '.$frequency.')';
 
  return javascript_tag($code);
}
 
function link_to_remotes($name, $options = array(), $html_options = array())
{
  return link_to_function($name, remote_functions($options), $html_options);
}
 
function remote_functions($options)
{
  // Multi ajax call
  $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;
}

To use it, you could do something like this:

<?php echo link_to_remotes('multi ajax',
    array(
        array(
            'update' => 'first_zone',
            'url' => '/module/action1',
            'loading' => "Element.show('indicator')"
        ),
        array(
            'update' => 'second_zone',
            'url' => '/module/action2',
            'wait' => 0 /* update in a second server call, after 'news_zone' returns */
        ),
        array(
            'update' => 'third_zone',
            'url' => '/module/action3',
            'wait' => 0
        ),
        array(
            'update' => 'fourth_zone',
            'url' => '/module/action4',
            'complete' => "Element.hide('indicator')",
            'wait' => 2 /* update in a third server call, after 'third_zone' returns */
        )
    )
) ?>

or this:

<?php echo periodically_call_remotes('multi ajax',
    array(
        array(
            'frequency' => 60 /* every 60 seconds, frequency only needs to be specified in first call, not subsequent calls */
            'update' => 'first_zone',
            'url' => '/module/action1',
            'loading' => "Element.show('indicator')"
        ),
        array(
            'update' => 'second_zone',
            'url' => '/module/action2',
            'wait' => 0 /* update in a second server call, after 'news_zone' returns */
        ),
        array(
            'update' => 'third_zone',
            'url' => '/module/action3',
            'wait' => 0
        ),
        array(
            'update' => 'fourth_zone',
            'url' => '/module/action4',
            'complete' => "Element.hide('indicator')",
            'wait' => 2 /* update in a third server call, after 'third_zone' returns */
        )
    )
) ?>

Notes

It should be noted that using the 'wait' parameter will cause all top level ajax remote function calls to be executed in one call to the server, and each second level to be executed in a second call, etc. Basically, multiple calls to the server are generated by the user action.

This will cause huge performance issues if you attempt to use these functions in a high-traffic scalable-sensitive system. It is mainly intended for a link which executes two or three functions at most. To do more, you should consider using another approach, like the JSON approach documented in the wiki.

by Stephen Riesenberg on 2006-11-20, tagged ajax  helper  multiple 
(2 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 

Detect an Ajax request

A simple way to detect an ajax request.

In your action class.

$this->isAjaxCall = $this->getRequest()->isXmlHttpRequest();
 

I put the above line in my preExecute where needed, this way the variable is accessible by action and views.

Inside a template you can use the following as well:

$sf_request->isXmlHttpRequest()
 
by Fuad Arafa on 2006-10-27, tagged ajax  http  request 
(2 comments)

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 
(9 comments)

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 
(10 comments)

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)

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)

Ajax Autocomplete - how to trigger an event after selecting an option

The Problem

The Ajax helper function input_auto_complete_tag() is great stuff. You might have seen something comparable in Google Suggest. However, you might encounter some limitations when using it. Just imagine, you want to use it as a search field. Once you found the desired keyword, you might want to retrieve the whole record related to it. Unfortunately, after selecting an entry, the input field contains a search string, and mostly this is not unique in your database. As a consequence, you want to use the id of the record to perform the database select.

The Solution

First of all we need to have a technique to unambiguously identify all options of the AutoComplete field:

<ul>
<?php foreach ($object as $key => $value): ?>
  <li id="<?php echo $key ?>"><?php echo $value ?></li>
<?php endforeach; ?>
</ul>

It's simple: We just add an id attribute to the list element. Note that this example uses an associative array containing the unique id as key and the name (appearing as option) as value.

The trouble is now: how to access the id attributes of the list elements?

The input_auto_complete_tag() function can take an option called 'after_update_element' as a hook for a user-defined function. This receives 2 parameters:

(1) the autocompletion input field
(2) the selected item

For more Details take a glance at the documentation: http://wiki.script.aculo.us/scriptaculous/show/Ajax.Autocompleter

By including this option in our input_auto_complete_tag() function, we have the right tool for accessing the list element and doing nice things:

<?php echo form_remote_tag(array(
      'url'    => 'yourModule/calledAction',
      'update' => 'resultarea',
      'loading'  => "Element.show('indicator')",
      'complete' => "Element.hide('indicator')",
    )) ?>
    <?php echo input_hidden_tag('unique_id', '') ?>
    <?php echo input_auto_complete_tag('phrase', '', 'yourModule/autoCompleteAction', array('autocomplete' => 'off', 'size' => '30'), array('use_style' => 'true', 'after_update_element' => "function (inputField, selectedItem) { $('unique_id').value = selectedItem.id; }"));   
echo submit_tag('Go') ?>
</form>

In the function definded for 'after_update_element' we set the unique_id attribute of the previously definded hidden field by accessing the 2nd parameter. This hidden field will then provide the unique_id within the target action.

by Sascha Wiener on 2006-07-21, tagged ajax  autocomplete  event 
(3 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' */
                                               )
                                       )
                   );
 
?>
by Quentin Garnier on 2006-07-18, tagged ajax  helper  multiple 
(11 comments)

visual_effect('highlight') transition colors / visual_effect parameters

(note: you can look up the options for the other various visual_effects from the script.aculo.us wiki tags page. Their usage is identical to this example.)

visual_effect('highlight', 'vendor',
array('startcolor' => "'#000000'", 'endcolor' => "'#ffffff'", 'restorecolor' => "'#FF0000'")))

simple, but has a few caveats:

-if you dont enclose the quotes with the color params, the js function color isnt quoted, and the highlight wont occur.

-i found behavior inconsistent without using 6 character RGB values (e.g., #000 didnt work).

-restorecolor can be omitted, but non web-safe colors wont always be restored to the original background color.

passing an options array works for any of the visual_effect() aliases.

by eric williams on 2006-07-07, tagged ajax  highlight  scriptaculous  visualeffect 

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 
(7 comments)