Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "javascript"

Hide Web Debug Details and show it on errors

This snippet hides the Web Debug details and show it automatically whenever an error has detect by the Debug System.

This code uses a JavaScript file only for development environment, avoiding unnecessary requests and extra codes in production environment and files.

Obs: Requires jQuery JavaScript Library.

1) Copy the code below and save it as a JavaScript file in "/web/js/debug.js"

$(function(){
    //sfWebDebug Bar Toogler
    var JQsfWebDebug = $('#sfWebDebugDetails');
    (JQsfWebDebug.children('li').hasClass('sfWebDebugError')) ? JQsfWebDebug.show() : JQsfWebDebug.hide();
});
 

2) Place the PHP code below in your "/apps/application-name/config/view.yml"

default:
  // your metas, styles, etc... configuration
  javascripts:    [yourScript.js <?php if(sfConfig::get('sf_web_debug')) echo ',debug.js' ?>]
 

Tip: You can use "debug.js" for all JavaScript debug needs.

by Mario Rezende on 2011-01-15, tagged debug  javascript 

Add inline javascript to the response

Allows you to define a script block in a template, and let it be included by the include_javascripts() helper after all js files, so that you can put it right before the body tag , to conform to the Yahoo performance best practice (see http://developer.yahoo.com/performance/rules.html#js_bottom).

First put this class in your /lib/response directory:

<?php
/*
*  LfmWebResponse
* @package    forcemolle
* @subpackage response
* @author     Jules Bernable <julius@laforcemolle.org>
*
*/
class LfmWebResponse extends sfWebResponse
{
  protected
    $inline_scripts = array();
 
  public function initialize(sfEventDispatcher $dispatcher, $options = array())
  {
    parent::initialize($dispatcher, $options);
 
    $this->inline_scripts = array_combine($this->positions, array_fill(0, count($this->positions), array()));
  }
 
  /**
   * Adds a script block to the current web response.
   *
   * @param string $script    The JavaScript code
   * @param string $id        A unique identifier for the script
   * @param string $position  Position
   */
  public function addInlineScript($script='', $position=sfWebResponse::LAST)
  {
    $this->validatePosition($position);
 
    $this->inline_scripts[$position][] = $script;
  }
 
  /**
   * Retrieves inline scripts from the current web response.
   *
   * By default, the position is sfWebResponse::ALL,
   * and the method returns all inline scripts ordered by position.
   *
   * @param  string $position  The position
   *
   * @return array An array of inline scripts
   */
  public function getInlineScripts($position = self::ALL)
  {
    if (self::ALL === $position)
    {
      $inline_scripts = array();
      foreach ($this->getPositions() as $position)
      {
        foreach ($this->inline_scripts[$position] as $script)
        {
          $inline_scripts[] = $script;
        }
      }
      return $inline_scripts;
    }
    else if (self::RAW === $position)
    {
      return $this->inline_scripts;
    }
 
    $this->validatePosition($position);
 
    return $this->inline_scripts[$position];
  }
 
}
 

Update your $sf_app_dir/config/factories.yml accordingly:

all:
  response:
    class: LfmWebResponse
 

Then you need to overwrite the symfony AssetHelper. First copy $sf_symfony_lib_dir/helper/AssetHelper.php to $sf_app_dir/lib/helper/AssetHelper.php (or to $sf_lib_dir/helper/AssetHelper.php if you want it enabled globally). Then replace the get_javascripts() function (line 492) by the following:

function get_javascripts()
{
  $response = sfContext::getInstance()->getResponse();
  sfConfig::set('symfony.asset.javascripts_included', true);
 
  $html = '';
  foreach ($response->getJavascripts() as $file => $options)
  {
    $html .= javascript_include_tag($file, $options);
  }
 
  use_helper('JavascriptBase');
  foreach ($response->getInlineScripts() as $script)
  {
    $html .= content_tag('script', javascript_cdata_section($script), array('type' => 'text/javascript'));
  }
 
  return $html;
}
 

Et voilà ! You can now add a script in a template this way:

<?php $sf_response->addInlineScript(<<<EOF
// Mootools FTW!
window.addEvent('domready', function()
{
  var foo = alert('BAR!');
});
EOF
);
?>
 

Your script block is now added after all js files added by include_javascripts() !

Please note that this is a basic implementation. You can not 'override' a script nor remove it afterwards.

by Jules Bernable on 2010-03-07, tagged javascript  performance  response 
(3 comments)

Javascript loading optimalization

Hi!

I am using a lot of hand written jscript codes due to the required behavior not found in any javascript framework. I've created my Objects in distinct files, although some of them are quite small ( <1kb ). So to make it fast, I've written a filter class that actually parses the files found in the /js directories having *.js suffix. It does not only parse them together but removes whitespaces, comments, indentations so the sum filesize gets a bit compressed. The filter also includes the final file into the response so there is no need to include any *.js ( inside /js ) in a template.

<?php
 
    class JavascriptParser extends sfFilter
    {
      public function execute($filterChain)
      {
        $filterChain->execute();
 
 
        $fp = fopen( "js/compiled.js", "w" );
 
        JavascriptParser::getJSFiles( "js", $fp );
 
        fclose ( $fp );
 
        $response = $this->getContext()->getResponse();
        $content = $response->getContent();
        if (false !== ($pos = strpos($content, '</head>')))
        {
          $html = "<script type='text/javascript' src='/js/compiled.js'></script>";
 
          if ($html)
          {
            $response->setContent(substr($content, 0, $pos).$html.substr($content, $pos));
          }
        }
 
      }
 
      public function getJSFiles( $dir, &$fp )
      {
        $hDir = opendir( $dir );
        while( ( $filename = readdir( $hDir ) ) !== false )
        {
            if ( is_dir( $filename ) )
            {
            }
            else if ( is_dir( $dir."/".$filename ) )
                JavascriptParser::getJSFiles( $dir."/".$filename, $fp );                
            else if ( strpos( $filename, ".js" ) !== false && $filename != "compiled.js" )
            {
                $tmpFile = fopen( $dir."/".$filename, "r" );
                $data = fread( $tmpFile, filesize( $dir."/".$filename ) );
 
                $data = preg_replace( "'\/\*.*?\*\/'si", "", $data  );
                $data = preg_replace( "'//.*?\n'si", "", $data );
                $data = preg_replace( "'[ \t]+'", " ", $data );
 
                fwrite( $fp, " \n".$data );
                fclose( $tmpFile );
            }
        }
        closedir( $hDir );  
      }
    }
 
?>
 

The same should be done with *.css files, since they are even smaller and loading many small files takes much more time then loading one big.

Best Regards

by Kormany Gabor on 2008-04-13, tagged filter  javascript 
(1 comment)

Using a link to submit a form or button to link

For ergonomic and graphism reasons, I sometimes prefer to use link rather than buttons and/or button rather than links...

To do so, I created this small function :

<?php
# File : JsHelper.php
 
// use of "normal" Javascript Helper
use_helper('Javascript');
 
 
/*!
 * Create a link that submit the closest parent form
 *
 * @param $string    string : text to display
 * @param $options mixed : options to be pass to the tag
 * @return string                : HTML code for the link
 */
function link_to_submit( $string, $options = null )
{
  $func = ";var a=this.ancestors();for(var i in a){if(a[i].tagName=='FORM'){try{a[i].onsubmit();} catch(err){a[i].submit();}break;}}";
  return link_to_function( $string, $func, $options );
}
 
 
 
/*!
 * Create a button that act as a link
 *
 * @param $string    string : text to display in the button
 * @param $url         string : URL of the link
 * @param $options mixed : options to be pass to the tag
 * @return string                : HTML code for the link
 */
function button_to_link( $string, $url, $options = null )
{
  $url = url_for($url);
 
  $func = ";document.location = '$url';return false;";
  if ( is_string($options) ) $options.= " onclick=$func";
  else $options['onclick'] = $func;
 
  return tag('button', $options ).htmlspecialchars($string).'</button>';
}
 

Then, you simply use it as a normal link_to function :

<? use_helper('Js') ?>
 
<?=form_tag('module/action')?>
  <?=input_tag('text')?>
  <?=link_to_submit('Validate')?>
  <?=link_to('Cancel','module/index')?>
</form>
 

Or :

<? use_helper('Js') ?>
 
<?=form_tag('module/action')?>
  <?=input_tag('text')?>
  <?=submit_tag('Validate')?>
  <?=button_to_link('Cancel','module/index')?>
</form>
 

In these two examples, links/buttons of the form are graphicly consistents. For a better user experience ;)

PS : works as well with AJAX forms form_remote_tag()

PPS : Don't forget that thanks to CSS, you can make a button looking like a link (et vice-versa).

PPPS : Don't forget that buttons and links doesn't have the same meaning for search engine bots.

PPPPS : Don't forget that this rely on the fact that user is using javascript!!!

by jugjug on 2008-03-26, tagged form  javascript  link  submit 
(2 comments)

jQuery AdminDoubleList replacements

jQuery AdminDouble List

These two js functions do the job while using jQuery with admin double list

function double_list_move(src, dest)
{
    var L = $(src).children();
    $(L).each(function(i){
        if(L[i].selected){
            $(dest).append('<option value="'+$(L[i]).val()+'">'+ $(L[i]).text()+'</option>');
            $(L[i]).remove();
        }
    });
 
}
 
function double_list_submit()
{
    var sel = $("form").find("select.sf_admin_multiple-selected");
    var C = $(sel).children();
    $(C).each(
        function(i){
            if(!C[i].selected)
                C[i].selected=true;
        }
    );
 
}
 

Enjoy it.

by Thomas Schäfer on 2008-03-19, tagged helper  javascript  jquery 

Phone Number Helper

format a phone number with 7, 10, or 11 digits. also can convert phone number letters to numbers

lib/helpers/PhoneHelper.php

<?php 
 
function format_phone($phone = '', $convert = false, $trim = true)
{
    // If we have not entered a phone number just return empty
    if (empty($phone)) {
        return '';
    }
 
    // Strip out any extra characters that we do not need only keep letters and numbers
    $phone = preg_replace("/[^0-9A-Za-z]/", "", $phone);
 
    // Do we want to convert phone numbers with letters to their number equivalent?
    // Samples are: 1-800-TERMINIX, 1-800-FLOWERS, 1-800-Petmeds
    if ($convert == true) {
        $replace = array('2'=>array('a','b','c'),
                 '3'=>array('d','e','f'),
                     '4'=>array('g','h','i'),
                 '5'=>array('j','k','l'),
                                 '6'=>array('m','n','o'),
                 '7'=>array('p','q','r','s'),
                 '8'=>array('t','u','v'),                                '9'=>array('w','x','y','z'));
 
        // Replace each letter with a number
        // Notice this is case insensitive with the str_ireplace instead of str_replace 
        foreach($replace as $digit=>$letters) {
            $phone = str_ireplace($letters, $digit, $phone);
        }
    }
 
    // If we have a number longer than 11 digits cut the string down to only 11
    // This is also only ran if we want to limit only to 11 characters
    if ($trim == true && strlen($phone)>11) {
        $phone = substr($phone, 0, 11);
    }                        
 
    // Perform phone number formatting here
    if (strlen($phone) == 7) {
        return preg_replace("/([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/", "$1-$2", $phone);
    } elseif (strlen($phone) == 10) {
        return preg_replace("/([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/", "($1) $2-$3", $phone);
    } elseif (strlen($phone) == 11) {
        return preg_replace("/([0-9a-zA-Z]{1})([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/", "$1($2) $3-$4", $phone);
    }
 
    // Return original phone if not 7, 10 or 11 digits long
    return $phone;
}
 
by Alex Zogheb on 2008-02-08, tagged formatting  helper  javascript  number  phone  regex 
(1 comment)

Accordion Helper

A symfony helper for the stickman labs accordion http://www.stickmanlabs.com/accordion/

hopefully plugin oneday

<?php
 
use_helper('Tag', 'Javascript');
 
function accordion($container, $options = array()){
 
    $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('accordion', 'last');
 
    $options = _parse_attributes($options);
    $on_load = (isset($options['on_load']) && $options['on_load'] == false) ? false : true;
    if(isset($options['use_stylesheet']) && $options['use_stylesheet']==true) $response->addStylesheet('accordion');
 
    $output= '';
    //onLoad
    $output .= $on_load ? "Event.observe(window, 'load', function() {" : '';
 
    //new accordion
    $output .= "var accordion_$container = new accordion ('$container', ";
 
 
    $accordion_options = array();
 
    //speed
    if (isset($options['resize_speed'])) $accordion_options['resizeSpeed'] = $options['resize_speed'];
 
    //classes
    if(isset($options['toggle']) || isset($options['toggle_active']) || isset($options['content'])){
        if(isset($options['toggle'])) $accordion_options['classNames']['toggle'] = "'{$options['toggle']}'";
        if(isset($options['toggle_active'])) $accordion_options['classNames']['toggleActive'] = "'{$options['toggle_active']}'";
        if(isset($options['content'])) $accordion_options['classNames']['content'] = "'{$options['content']}'";
        $accordion_options['classNames'] = _options_for_javascript_no_sort($accordion_options['classNames']);
    }
 
    //size
    if(isset($options['height']) || isset($options['width'])){
        if(isset($options['height'])) $accordion_options['defaultSize']['height'] = $options['height'];
        if(isset($options['width'])) $accordion_options['defaultSize']['width'] = $options['width'];
        $accordion_options['defaultSize'] = _options_for_javascript_no_sort($accordion_options['defaultSize']);
    }
 
    //direction
    if (isset($options['direction'])) $accordion_options['direction'] = "'{$options['direction']}'";    
 
    //event
    if (isset($options['on_event'])) $accordion_options['onEvent'] = "'{$options['on_event']}'";
 
    $output .= _options_for_javascript_no_sort($accordion_options);
 
    //new accordion end
    $output .= ");";
 
    if(isset($options['activate'])){
        $number = $options['activate'];
        $output .= "accordion_$container.activate($$('#$container .{$options['toggle']}')[$number]);";
    }
 
    //onLoad
    $output .= $on_load ? "});" : '';
 
    return javascript_tag($output);
 
}
 
  function _options_for_javascript_no_sort($options)
  {
    $opts = array();
    foreach ($options as $key => $value)
    {
      $opts[] = "$key:$value";
    }
 
    return '{'.join(', ', $opts).'}';
  }
 
by Alex Zogheb on 2008-02-08, tagged accordion  helper  javascript 

Change detection for admin generator

This JavaScript code registers a change detection mechanism in every form field and notifies the user about unsaved changes. No changes need to be applied to existing modules/actions. Additionaly, the TinyMCE helper can be changed in order to detect changes there as well.

Only requirement: the links for leaving the page need to be in a container with id "header". This can of course be changed.

Add this to the head of the page (or to an external js-file):

var changesDetected = false;
 
/**
 * Registers a change detection mechanism that notifies users about unsaved changes whenever they click on a link.
 */
function registerChangeDetection() {
    /**
     * Notifies user about unsaved changes
     */
    function notifyAboutChanges(e) {
        if(changesDetected){
            //My choice: modal dialog using modalbox (http://www.wildbit.com/labs/modalbox/)
            //Modalbox.show('<div class=\'warning\'><p>Before continuing, you need to save you changes.</p> <input type=\'button\' value=\'Ignore changes\' onclick=\'changesDetected=false;Modalbox.hide()\' style=\'color: #999\' /> <input type=\'button\' value=\'OK\' onclick=\'Modalbox.hide()\' /></div>',  {title: 'Warning', width: 300});
            //Alternative:
            alert('Before continuing, you need to save your changes.');
            return false;
        }
    }
 
    /* Add change detection to every form field */
    if(document.forms.sf_admin_edit_form != null) {
        var elements = Form.getElements(document.forms.sf_admin_edit_form);
        elements.each(function(item) {
            item.onchange = function(e) { changesDetected = true; }
        });
    }
 
    /* Add an onclick handler to every link in the container with id "header" */
    var links = $$('#header a');
    links.each(function(item) {
        if(!(item.onclick instanceof Function)) { //Avoid overwriting existing onclick handlers
            item.onclick = notifyAboutChanges;
        }
    });
}
 

Register the change detection in the body tag:

<body onload="registerChangeDetection()">
 

I also modified the sfRichTextEditorTinyMCE helper in order to use TinyMCE 3 (currently beta). Here is the code relevant to change detection to be put into TinyMCE.init({...}) (Go to http://wiki.moxiecode.com/index.php/TinyMCE:API/tinymce.Editor/onChange for more information):

setup: function(ed) {
    var i = 0;
    ed.onChange.add(function(ed, l) {
        if(i == 0) i++ //Ignore the first change
        else changesDetected = true;
    });
}
 
by Michel Weimerskirch on 2007-12-05, tagged admin  changes  detection  generator  javascript  tinymce 

jsValidator integration helper

A real client-side validation with some nice features, without Ajax tricks provided by sfYzAjaxValidationPlugin may be easily done using jsValidator (http://sourceforge.net/projects/jsformutils/) and little helper that follows.

<?php
/**
 * Generates JavaScript code to validate a form using 
 * jsValidator as client-side validation engine (see http://sourceforge.net/projects/jsformutils/)
 * 
 * @param string $targetForm id attribute of the form to be validated
 * @param mixed $options associative array of options
 * @param string $action action to validated, defaults to current
 * 
 * Options are:
 *  stopOnFirstError: boolean, default: false
 *  labelMessageDelimiter: string, default: ' : ',
 *  messageSeparator: string, default: "\n",
 *  messageHeader: string, default: 'These fields are invalid:\n---\n' + 
 *  highlightErrors: boolean, whether to mark erroneous fields 
 *  errorElementClass: string, CSS class name to be applied to wrong fields
 *  highlightLabels: boolean, whether to mark fields or fields' labels
 */
function generate_validator($targetForm, $options, $action)
{
  $NL = "\n";
  $funcPrefix = 'validate_';
  $labelsKey = 'labels';
  $fieldsKey = 'fields';
  $jsCode = '';
 
  $paramHolder = sfContext::getInstance()->getRequest()->getParameterHolder();  
  $rulesFilePath = sfConfig::get('sf_app_module_dir').'/'.$paramHolder->get('module').'/'.
    sfConfig::get('sf_app_module_validate_dir_name').'/';
 
  // Load rules from YAML file.   
  if (file_exists($rulesFilePath.$paramHolder->get('action').'.yml'))
    $rules = sfYaml::load($rulesFilePath.$paramHolder->get('action').'.yml');
  else
    $rules = sfYaml::load($rulesFilePath.$action.'.yml');
 
  // Generate jsValidator compliant rules.  
  $jsRules = array();
  foreach ($rules['fields'] as $fieldId => $validationRule)
  {
    foreach ($validationRule as $validator=>$rule)
    {
      // Remove server-side sfCallbackValidator.
      if ($validator == 'sfCallbackValidator')
      {
        unset($validationRule[$validator]);
        continue;
      }
      // Map Symfony validators to jsValidator.
      $jsvalidator = preg_replace('/^sf(\w+Validator)$/', 'js\\1', $validator);
      if ($jsvalidator != $validator)
      {
        $validationRule[$jsvalidator] = $validationRule[$validator];
        unset($validationRule[$validator]); 
      }
    }
    $jsRules[] = array_merge ( 
      array ('field' => $fieldId, 'label' => $rules['labels'][$fieldId]),
      $validationRule
    );    
  }
 
  // Generate final JavaScript code. 
  $jsCode .= 'function '.$funcPrefix.$targetForm.'()'.$NL;
  $jsCode .= 
  '{'.$NL.
  ' var options = '.json_encode($options).';'.$NL.
  ' var rules = '.json_encode($jsRules).';'.$NL.
  ' var jsv = new jsValidator();'.$NL.
  ' jsv.SetOptions(options);'.$NL.$NL.
  ' if (!jsv.Validate(rules))'.$NL.
  ' {'.$NL.
  '   alert(jsv.GetErrorMessage());'.$NL.
  '   return false;'.$NL.  
  ' }'.$NL.
  ' return true;'.$NL.  
  '}'.$NL;
 
  return javascript_tag($jsCode);
}
?>
 

However, I need to clear this out. Besides placing this code into jsValidatorHelper.php either in modules' lib directory or symfony's one, we need to call it properly in the corresponding view.

First of all, include the helper (use JavaScript as well, jsValidator depends on it)

<?php use_helper('JavaScript', 'jsValidator') ?>
 

Validation is performed based on the same simple rule mechanism that Symfony provides. The only difference is that JavaScript validator needs these rules to be in JSON format and it needs some more options, that configure it's behavior.

<?php
// Here we set validation options.
// For more information please refer to documentation of jsValidator
$options = array(
  'stopOnFirstError' => false,
  'labelMessageDelimiter' => ' : ',
  'messageSeparator' => "\n",
  'messageHeader' => "These fields are invalid:\n---\n",
  'highlightErrors' => true,
  'errorElementClass' => 'errClass',
  'highlightLabels' => true
  ); 
// Output auto-generated JavaScript code.
echo generate_validator('editComment', $options, 'update'); 
?>
 

We also have to set up form to point to our validator before submitting. Note, that callback in onsubmit is named by concatenating "validate_" and form's id attribute.

<?php echo form_tag('comment/update', array('id'=>'editComment','onsubmit' => 'return validate_editComment()')) ?>
 

Each field must be somehow identified in the resulting error message. We achieve this by adding some extra information to <action>.yml configuration file.

# define labels for erroneous fields
labels:
  <field_id>: <field_label_text>
 

There is a limitation to validation *.yml file structure. The syntax should be something like this:

# define labels for erroneous fields
labels:
  author: Author
  email: E-mail
  body: Body
 
fields:
  author:
    required:
      msg: The name field cannot be left blank
  email:
    sfEmailValidator:
      email_error: The email address is not valid.
  body:
    required:
      msg: The text field cannot be left blank
 
fillin:
  enabled:       on
 

And finally, don't forget to place jsValidator into /web/js folder, and include it in view.yml

<actionTemplate>:
  javascripts: [jsvalidator]
 

Feel free to modify the snippet code and validator to achieve best results!

by Alex Oroshchuk on 2007-12-04, tagged helper  javascript  validation 
(1 comment)

Delayed javascript page redirect

In order to delay a page redirect with several seconds, I wrote this simple helper

<?php
  use_helper('Javascript');
 
  /**
   * Adds javascript code to delay a page redirect
   *
   * @param string 'module/action' or '@rule' of the action (same argument as url_for())
   * @param int time of delay in seconds. Default = 5
   * @return JavaScript tag for delayed page redirect
   */
  function delayed_redirect($internal_uri, $time = 5)
  {
    sfContext::getInstance()->getResponse()->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype');
    $code = 'new PeriodicalExecuter(function() { location.href=\''.url_for($internal_uri).'\';}, '.$time.')';
 
    return javascript_tag($code);
  }
 

See http://www.symfony-project.com/book/1_0/07-Inside-the-View-Layer#Adding%20Your%20Own%20Helpers for information on how to add your own helper

by wtreur on 2007-10-04, tagged helper  javascript  redirect 

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)

Hide Web Debug Details on Launch.

If you want to hide the Web Debug Details/Menus but also want to have quick access when you need it.

Then this could help you. Just copy this in a js file or print it in your layout:

/*
 * HIDE WEB DEBUG DETAILS ON LAUNCH
 */
Event.observe(window, 'load', function(){
    // check if web debug is on
    if ($('sfWebDebugBar'))
        sfWebDebugToggleMenu();
});
by Halil Köklü on 2007-06-25, tagged debug  javascript 

Make Your Dynamic Page States Bookmarkable

I've found this to be a pretty good solution for the dynamic page state v. bookmarking conflict that prevents users from bookmarking a specific state of your dynamic page. Typically, if a general user bookmarks your page after spending some time interacting with its dynamic features, she will be disappointed to find her bookmark doesn't reflect the state of the page when she bookmarked it.

If your Javascript is built in a way that you can hijack the window's onLoad handler to initialize a custom state, based what fragment might be in the URI, you just might find this helper function useful.

JavascriptHelper.php

Save this to your project or application's /lib/helper folder.

<?php
 
require_once(sfConfig::get('sf_symfony_lib_dir') . '/helper/JavascriptHelper.php');
 
/**
 * An alternative to the sf default link_to_function.
 * 
 * Adds logic to onclick's concat'd "; return false;" so it only shows up if 
 * the value of the href option doesn't include a #fragment. If no fragment is
 * embedded in the href it is set to "javascript:void(0)" a la Google. 
 * 
 * With the "; return false;" absent, the fragment will show up in the 
 * browser's address bar, and will be included if the user copies the link or 
 * bookmarks the current state of your dynamic page. You can then add a bit of 
 * logic to your window initialization Javascript to detect any fragments in 
 * the URL and adjust the onLoad state accordingly.
 * 
 * @author  Kris Wallsmith <kris [dot] wallsmith [at] gmail [dot] com>
 * @version tested on symfony 1.0.3
 * @see     link_to_function()
 * 
 * @param   string $name
 * @param   string $function
 * @param   mixed $options
 * 
 * @return  string
 */
function my_link_to_function($name, $function, $options = array())
{
    $options = _parse_attributes($options);
 
    $has_href = isset($options['href']);
 
    if(!isset($options['href']))
    {
        $options['href'] = 'javascript:void(0)';
    }
    $options['onclick'] = $function;
 
    if(!$has_href || strpos($options['href'], '#') === false)
    {
        $options['onclick'] .= '; return false;';
    }
 
    return content_tag('a', $name, $options);
}
by Kris Wallsmith on 2007-06-08, tagged helper  javascript 
(2 comments)

Automatic Javascript Include

If you're like me and prefer to code your own Unobtusive Javascript, you'll probably find this snippet handy. It will search through your project's /web/js folder for any .js files that match the current Symfony action.

For example, the snippet/new action would look for /web/js/snippet/snippet.js and /web/js/snippet/new.js, and add them to the response if either exist.

You can also define an optional subfolder within /web/js where you want the filter to look (i.e. "backend").

The Code

<?php
 
/**
 * Looks for Javascript files based on current action/module and adds them to
 * the current response object.
 * 
 * Add this filter to the end of your filter chain in filters.yml, after the 
 * execution filter:
 * 
 * <code>
 *     rendering: ~
 *     web_debug: ~
 *     security:  ~
 *     
 *     # generally, you will want to insert your own filters here
 *     
 *     cache:     ~
 *     common:    ~
 *     flash:     ~
 *     execution: ~
 *     
 *     auto_javascript_include:
 *       class: myAutoJavascriptIncludeFilter
 * </code>
 *
 * @package     Automatic Javascript Include (AJI)
 * @subpackage  filter
 * @author      Kris Wallsmith <kris [dot] wallsmith [at] gmail [dot] com>
 * @version     SVN: $Id$
 * @copyright   Have at it ...
 */
class myAutoJavascriptIncludeFilter extends sfFilter
{
    /**
     * Include external Javascript files based on last action called.
     * 
     * This kicks in on the way back down the filter chain, so we're sure to
     * catch the last action. Looks through the web/js folder for files that
     * match the naming syntax and adds them to the response. 
     * 
     * You can specify a subfolder of the web/js folder for the filter to 
     * search in app.yml.
     * 
     * @author  Kris Wallsmith <kris [dot] wallsmith [at] gmail [dot] com>
     * @see     sfConfig::get('app_aji_subfolder')
     * @param   sfFilterChain $filterChain
     */
    public function execute($filterChain)
    {
        $filterChain->execute();
 
        $sf_context  = sfContext::getInstance();
        $sf_response = $sf_context->getResponse();
        $sf_web_dir  = sfConfig::get('sf_web_dir');
 
        $module = $sf_context->getModuleName();
        $action = $sf_context->getActionName();
 
        $sub_folder = sfConfig::get('app_aji_subfolder');
        if($sub_folder && $sub_folder{0} != '/') $sub_folder = '/' . $sub_folder;
 
        $fmt = '/js%s/%s/%s.js';
 
        $mod_mod_js = sprintf($fmt, $sub_folder, $module, $module);
        $mod_act_js = sprintf($fmt, $sub_folder, $module, $action);
 
        if(file_exists($sf_web_dir . $mod_mod_js)) $sf_response->addJavascript($mod_mod_js);
        if(file_exists($sf_web_dir . $mod_act_js)) $sf_response->addJavascript($mod_act_js);
    }
}
 
?>
by Kris Wallsmith on 2007-04-24, tagged filter  javascript 
(6 comments)

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 

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)

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 

format_number_choice in JavaScript

If you are dealing with i18n and client side effects, you might need an equivalent for the format_number_choice() helper in Javascript. Here is a simplified version:

function format_number_choice(text_string, replace_hash, number) {
  pattern = new RegExp("\\["+number+"\\]\s?([^\|]*)");
 
  function replace_in_string(text_string, replace_hash) {
    for(var i in replace_hash)
     text_string = text_string.replace(new RegExp(i), replace_hash[i]); 
    return text_string;
  }
 
  matches=text_string.match(pattern);
  if(matches != null)
    return replace_in_string(matches[1], replace_hash);
  else {
    pattern = /\[else\]\s?([^\|]*)/;
    matches=text_string.match(pattern);
    if(matches != null)
      return replace_in_string(matches[1], replace_hash);
    else
      return "not found";
  }
}

Use it in your HTML code as follows:

<script language="JavaScript" type="text/javascript">
  document.write(format_number_choice("[0] No apple |[1] One Apple |[2] Two Apples |[else] Many Apples", {'Apple' : 'fruit'}, 0))
  document.write(format_number_choice("[0] No apple |[1] One Apple |[2] Two Apples |[else] Many Apples", [], 1))
  document.write(format_number_choice("[0] No apple |[1] One Apple |[2] Two Apples |[else] Many Apples", [], 2))
  document.write(format_number_choice("[0] No apple |[1] One Apple |[2] Two Apples |[else] Many Apples", [], 3))
</script>

This will output:

No fruit
One Apple
Two Apples
Many Apples

Now, just use enclose the first argument with the __() helper, and you have an internationalized format_number_choice() on the client side.

by Francois Zaninotto on 2006-08-29, tagged i18n  javascript 

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)

Helper for Javascript Tabbed Panes

I figured I'd post this little helper for using the tabbed pane JS from http://webfx.eae.net/dhtml/tabpane/tabpane.html in your templates.

Simply download the JS package from the site above and place it in your web/js folder. I chose to move the css files from the JS folder to my CSS folder, but you can do whatever you like.

tabsHelper.php

<?php
/**
 * Helper for Javascript Tabbed Panes
 * 
 * Example Usage
 * <code>
 *  <?php use_helper('tabs') ?>
 *  <?php tabMainJS("tp1","tabPane1", "tabPage1", 'General');?>
 *            This is text of tab 1. This is text of tab 1. This is text of tab 1. 
 *      <?php tabPageOpenClose("tp1", "tabPage2", 'Security');?>
 *            This is text of tab 2. This is text of tab 2. This is text of tab 2. 
 *  <?php tabInit();?>
 * </code>
 * 
 * @package    Helpers
 * @author     Jason Ibele
 * @version    SVN: $Id: tabsHelper.php 4 2006-07-19 14:00:47Z jason.ibele $
 */
 
$response = sfContext::getInstance()->getResponse();
$response->addJavascript('tab/tabpane.js');
$response->addStylesheet('tab/tab.webfx.css');
 
/**
 * Opens a new TabPane object and creates first tab
 *
 * @param string $mid         JavaScript variable name to use for webFXTabPane object
 * @param string $id          Main container div ID
 * @param string $page_id     Name for div ID, each needs to be unique
 * @param string $H2_title    The title for the tab
 * @param string $main_class  Optional class name for main Div (note this must match original class definitions)
 * @param string $page_class  Optional class name for page Div (note this must match original class definitions)
 */
function tabMainJS($mid, $id, $page_id, $H2_title, $main_class='tab-pane', $page_class='tab-page')
{
  echo '<div class="'.$main_class.'" id="'.$id.'">'."\n\t";
  echo '<script type="text/javascript">'."\n\t";
  echo $mid.' = new WebFXTabPane( document.getElementById( "'.$id.'" ) );'."\n\t";
  echo '</script>'."\n\t";
  echo '<div class="'.$page_class.'" id="'.$page_id.'">'."\n\t";
  echo '<h2 class="tab">'.$H2_title.'</h2>'."\n\t";
  echo '<script type="text/javascript">'.$mid.'.addTabPage( document.getElementById( "'.$page_id.'" ) );</script>'."\n";
}
 
/**
 * Closes and existing pane div and opens a new one with required JS
 *
 * @param string $mid         JavaScript variable to use for webFXTabPane object
 * @param string $page_id     Name for div ID, each needs to be unique
 * @param string $H2_title    The title for the tab
 * @param string $page_class  Optional class name for page Div (note this must match original class definitions)
 */
function tabPageOpenClose($mid, $page_id, $H2_title, $page_class='tab-page')
{
  echo '</div>'."\n\t";
  echo '<div class="'.$page_class.'" id="'.$page_id.'">'."\n\t";
  echo '<h2 class="tab">'.$H2_title.'</h2>'."\n\t";
  echo '<script type="text/javascript">'.$mid.'.addTabPage( document.getElementById( "'.$page_id.'" ) );</script>'."\n";
}
 
/**
 * Initiates the javascript for tabs and closes the remaining divs
 *
 * @param string $mid    JavaScript variable to use for webFXTabPane object
 * @param string $n      selected index of tab you want to force as set
 */
function tabInit($mid='', $n='')
{
  echo "\t".'</div>'."\n\t";
  echo '<script type="text/javascript">'."\n\t";
  echo 'setupAllTabs();'."\n\t";
 
  if($n){ // n = selected index of tab you want to force as set
    echo $mid.'.setSelectedIndex('.$n.');';
  }
 
  echo '</script>'."\n";
  echo '</div>'."\n";
}
 
?>

template

<?php use_helper('tabs') ?>
 
 
 
 
 
<!-- open the first tab -->
<?php tabMainJS("tp1","tabPane1", "tabPage1", 'General');?><!-- General is the name of the tab -->
 
          This is text of tab 1. This is text of tab 1. This is text of tab 1. 
 
 
<!-- second tab -->     
    <?php tabPageOpenClose("tp1", "tabPage2", 'Security');?> <!-- Security is the name of the tab -->
 
          This is text of tab 2. This is text of tab 2. This is text of tab 2. 
 
 
<!-- third tab -->      
    <?php tabPageOpenClose("tp1", "tabPage3", 'Example');?> <!-- Example is the name of the tab -->
 
          This is text of tab 3. This is text of tab 3. This is text of tab 3.
 
 
<!-- close tabs and initiate the JS -->
<?php tabInit();?>
by Jason Ibele on 2006-07-31, tagged helper  javascript  tabs 
(1 comment)

Popup tooltips

http://www.symfony-project.com/trac/ticket/540

Helper to work with http://tooltip.crtx.org Tooltip.js library (small js library that is using prototype and script.aculo.us, and so is the perfect match for symfony).

You will need to put that file into /helper/ directory, and put Tooltip.js (which you can get at the above-mentioned url) to the /sf/js/prototype/ directory.

Example of usage:

<?php use_helper('Tooltip') ?>
 
<?php echo tooltips_js('autoMoveToCursor=false showEvent=click', 'appear=true blindDown=true', 'blindUp=true', array('style.position' => 'absolute'), array('style.position' => 'absolute')) ?> 
 
<div id="some_id">some text</div>
 
<?php echo tooltip_div('some_id', 'css_class', array('style' => 'visibility: hidden'))?>
  Tooltip text.
</div>

Code is "phpdocumentor ready".

TooltipHelper.php as follows

<?php
 
require_once(sfConfig::get('sf_symfony_lib_dir').'/helper/JavascriptHelper.php');
 
 
/*
 * This file is part of the symfony package.
 * (c) 2006 Dmitry Parnas <parnas@rock.zp.ua>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
 
/**
 * TooltipsHelper.
 *
 * @package    symfony
 * @subpackage helper
 * @author     Dmitry Parnas <parnas@rock.zp.ua>
 * @version    
 */
 
/*
   Helpers to work with Tooltip.js [http://tooltip.crtx.org]
 */
 
 
/**
 * Builds open div tag ready with inforamtion for Tooltip.js
 *
 * Example:
 * <code>
 *   <?php echo tooltip_div('my_element_id', 'css_class_for_it', array('style' => 'visibility: hidden')) ?>
 * </code>
 *
 * @param string HTML id of the element this tooltip is for
 * @param css_class CSS class to skin tooltip in (if not default)
 * @param array Other attributes for the tag. You can also pass string suitable for _parse_attributes()
 *
 * @return string An HTML div string
 *
 */
function tooltip_div($element_id, $css_class = '', $options = array())
{                                                                                                                       
  $response = sfContext::getInstance()->getResponse();
  $response->addJavascript('/sf/js/prototype/prototype');
  $response->addJavascript('/sf/js/prototype/effects');
  $response->addJavascript('/sf/js/prototype/Tooltip');
 
  $options = _parse_attributes($options);
 
  $options['class'] = $css_class.' tooltip for_'.$element_id;
 
  return tag('div', $options, true);
}
 
 
 
/**
 * Builds script that sets optional settings for tooltips on the page.
 *
 * Example:
 * <code>
 *   <?php echo tooltips_js('autoMoveToCursor=false showEvent=click', 'appear=true blindDown=true', 'blindUp=true', array('style.position' => 'absolute'), array('style.position' => 'absolute')) ?>
 * </code>
 *
 * @param array Tooltip.js settings. You can also pass string suitable for _parse_attributes()   
 * @param array Ordered list of script.aculo.us effects you want to apply during tooltip show process. You can also pass string suitable for _parse_attributes()
 * @param array Ordered list of script.aculo.us effects you want to apply during tooltip hide process. You can also pass string suitable for _parse_attributes()
 * @param array Other settings for tooltip show process. You can also pass string suitable for _parse_attributes()
 * @param array Other settings for tooltip hide process. You can also pass string suitable for _parse_attributes()
 *
 * @return string An HTML script string.
 *
 */
function tooltips_js($options = array(), $show_effects = array(), $hide_effects = array(), $show_options = array(), $hide_options = array())
{
  $response = sfContext::getInstance()->getResponse();
  $response->addJavascript('/sf/js/prototype/prototype');
  $response->addJavascript('/sf/js/prototype/effects');
  $response->addJavascript('/sf/js/prototype/Tooltip');
 
 
  $options      = _parse_attributes($options);
  $show_effects = _parse_attributes($show_effects);
  $hide_effects = _parse_attributes($hide_effects);
  $show_options = _parse_attributes($show_options);
  $hide_options = _parse_attributes($hide_options);
 
  $code = '';
 
  foreach($options as $key => $value)
  {
    $value = _method_option_to_s($value);
 
    $value = (is_bool($value)) ? ($value === false) ? 'false' : 'true' : $value;
 
    $code .= '  Tooltip.'.$key.' = '.$value."\n";
  }
 
  $code .= _build_functions('show', $show_effects, $show_options);
  $code .= _build_functions('hide', $hide_effects, $hide_options);
 
  return javascript_tag($code);
}
 
 
function _build_functions($type, $effects = array(), $options = array())
{
  $code = '';
 
  if($effects) // there is no reason to build showMethod if there is no effects defined
  {
    $code .= '  Tooltip.'.$type.'Method = function (tooltip, options)'."\n";
    $code .= '  {'."\n";
 
    foreach($effects as $key => $value)
    {
      $code .= '    Effect.'.ucfirst($key).'(tooltip, options)'."\n";
    }
 
    if($options)
    {
      foreach($options as $key => $value)
      {
        $value = _method_option_to_s($value);
 
        $value = (is_bool($value)) ? ($value === false) ? 'false' : 'true' : $value;
 
        $code .= '    tooltip.'.$key.' = '.$value."\n";
      }
    }
 
    $code .= '  }'."\n";
 
    return $code;
  }
 
}
 
?>
by Adam Clarke on 2006-05-27, tagged helper  javascript  popup  tooltip 
(7 comments)