jQuery / PHP / Smarty - 非侵入式JS - 设置动态元素ID处理程序(PHP变量)

I'm trying to separate html and jQuery events handlers in my webapp. Here's my context : my webapp works with tabs with ajax loaded content. Each tab content is identified by an unique id. In each tab I got a table. Two or more tabs contents can be the same (like in Chrome for an example, the same website can be loaded in two tabs). In a tab, each table row is editable by clicking a jQuery initialized button.

Here is a simplified example how it currently works :

SMARTY TEMPLATE

<div id="tabContent_{$uniqueId}">
  <table>
    {foreach $operations as $op}
       <tr><td><button id="buttonEditOperation_${op}" /></td></tr>
    {/foreach}
  </table>
</div>

<script>           
     $('#tabContent_{$uniqueId} [id^="buttonEditOperation_"]')
       .click( function() { alert('CLICK !'); })
       .button(blabla);
</script>

When I load a new tab with the same content, only buttons in this new content are initialized, because of #tabContent_{$uniqueId} selector

I would like to create a javascript file to separate JS and my SMARTY TEMPLATE but I don't know how to select only the content of the new loaded tab. I would like something like this :

SMARTY TEMPLATE

<script type="text/javascript" src="./js/buttonInit.js"></script>
<div id="tabContent_{$uniqueId}">
  <table>
     {foreach $operations as $op}
       <tr><td><button id="buttonEditOperation_${op}" /></td></tr>
    {/foreach}
  </table>
</div>

JS FILE

<script> 
     $('#tabContent_?????? [id^="buttonEditOperation_"]')  // HERE IS MY PROBLEM
       .click( function() { alert('CLICK !'); })
       .button(blabla);
</script>

I have some ideas (HTML5 data properties, initialization function, global variable, etc.) but I would like to know the best solution for this problem.

Thanks for your feedback !

See below the best solution I've found ! Do not hesitate to tell me if I'm doing something wrong...

SMARTY TEMPLATE

<script type="text/javascript" src="./js/object.Module.js"></script>
<script type="text/javascript" src="./js/object.Operation.js"></script>
<div id="tabContent_{$uniqueId}">
  <table>
     {foreach $operations as $op}
       <tr><td>
          <button id="buttonEditOperation_${op}" />
          <span class="tooltip" title="Tooltip content">Show tooltip</span>
       </td></tr>
    {/foreach}
  </table>
</div>

<script>
    // Create a new object "Operation" which inherit from "Module"
    var currentModule_{$uniqueId} = new Operation( {$uniqueId} );

    // Use a method from main object "Module"
    currentModule_{$uniqueId}.init_tooltips();

    // Use a method from specific object "Operation"
    currentModule_{$uniqueId}.init_buttonEditOperation('CLICK !');
</script>

MAIN JS FILE : "object.Module.js"

var Module = function(_uniqueId) {
    // Store the value of this in a different variable because jQuery
    // override it when handling events
    // (http://stackoverflow.com/questions/10596727/this-keyword-overriden-in-javascript-class-when-handling-jquery-events)
    var self = this;


    /* ========================================= */
    /* PRIVATE ATTRIBUTE */   
    /* ========================================= */
    // Initialized w/ arguments values when creating "object"
    // (e.g. : var myVar = new Module("35d98q452e");
    self.uniqueId = _uniqueId; // e.g. : "35d98q452e"


    /* ========================================= */
    /* PUBLIC METHODS */   
    /* ========================================= */    
    /*-------------------------------------
    /* ---> Initialize tooltips */
    self.constructor.prototype.init_tooltips = function() {
         $( "#tabContent_" + self.uniqueId + " .tooltip" ).each(function(index) {
            $(this).tooltip();
        }); 
    }

};

MODULE "OPERATION" SPECIFIC JS FILE "object.Operation.js"

var Operation = function(_uniqueId) {
    var self = this;

    /* ========================================= */
    /* INHERIT FROM PARENT OBJECT "Module" */   
    /* ========================================= */
    Module.call(self, _uniqueId);


    /* ========================================= */
    /* PUBLIC METHODS */   
    /* ========================================= */ 
    /*-------------------------------------
    /* ---> Initialize button "Edit operation" */
    self.init_buttonEditOperation = function(_msg) {
       $('#tabContent_' + self.uniqueId + ' [id^="buttonEditOperation_"]')
         .click( function() { alert(_msg); })
         .button(blabla);
    }
};

While separating your HTML and Javascript is a good idea, generating a Javascript file may be overkill for achieving this and also be a lot harder to maintain than it should be.

The best way i've found is to add simple plugin functions to Smarty to allow you to generate all of the HTML and Javascript associated with 'widgets' that appear many times throughout your site.

These plugin functions should:

  • Generate all the HTML for a widget, so that you don't have to repeat HTML between different Smarty templates.
  • Generate all the Javascript that a widget requires, so that you don't have to be writing Javascript in a Smarty template (which is almost always a bad idea - you should just be calling functions).
  • Separates the generating of Javascript from actually outputting in the page, to allow you to control where the Javascript is actually placed.

PHP code to add functions to Smarty

$GLOBALS['pageJavascript'] = array();

$smarty->registerPlugin("function", "addButton", "addButtonSmarty");
$smarty->registerPlugin("function", "displayPageJavascript", "displayPageJavascript");

//Outputs the HTML for a button, generates the associated Javascript and either 
//outputs that Javascript or saves it for later.
function    addButtonSmarty($params, &$smarty){

    if(isset($params['operation']) == FALSE){
        return "operation not set.";
    }

    if(isset($params['tabID']) == FALSE){
        return "tabID not set.";
    }

    $tabID = $params['tabID']
    $operation = $params['operation'];

    $uniqueID = "buttonEditOperation_".$operation;


    addButtonJavascript($tabID, $operation);

    echo "<button id='buttonEditOperation_".$operation."' />";
}

function addButtonJavascript($tabID, $operation){

    $javascript = "
        $('#tabContent_".$tabID." [id^=\"buttonEditOperation_".$operation."\"]')
        .click( function() { alert('CLICK !'); })
        .button(blabla);";

    if(FALSE){
    //You can just add the Javascript directly to the web page, however in this case
    //it may throw an error if the user clicks on something before jQuery is
    //fully loaded.
        echo "<script type="text/javascript">";
        echo $javascript;
        echo "</script>";
    }
    else{
        //Much better is to save the Javascript to be included later.
        $GLOBALS['pageJavascript'][] = $javascript;
    }
}

function    displayPageJavascript(){

    echo "<script type="text/javascript">";
    foreach($GLOBALS['pageJavascript'] as $pageJavascript){
        echo $pageJavascript;
        echo "
";
    }

    echo "</script>";

}

Wherever you add a button, instead of adding it directly, do: {addButton tabID=$uniqueId operation=$op}

So your Smarty template would look like:

Smarty Template

<script type="text/javascript" src="./js/buttonInit.js"></script>
<div id="tabContent_{$uniqueId}">
  <table>
     {foreach $operations as $op}
       <tr><td>{addButton tabID=$uniqueId operation=$op}</td></tr>
    {/foreach}
  </table>
</div>

...
...
...
<!-- bottom of smarty template -->
{displayPageJavascript}

Before anyone jumps in and says that writing HTML and Javascript in PHP is ugly and shouldn't be done - I fully agree!

However in this case the benefits of writing widgets as plugins in Smarty so outweigh the uglyness:

  • It makes the Javascript for manipulating a widget be within a few lines of the HTML for the widget, which makes it a lot easier to write and debug that widget, compared to having them in separate files.
  • Having the implementation of a widget be hidden from the Smart template, means that you can change it's implementation once in one place, and be sure that every use of that widget will be updated.
  • It makes the code be a lot more reliable as you template editors don't have to remember to separately add Javascript for any widget that they add, so there will be a lot fewer mistakes in editing the template.