Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "inplaceselect"

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)