Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Forcing Script Order from registerScript Method in Yii

Tags:

php

yii

I created a widget that registers its own script like below

class MyWidget extends CWidget {
    public function run(){
        Yii::app()->clientScript->registerScript(__CLASS__, <<<JAVASCRIPT
var a = "Hello World!";
JAVASCRIPT
        , CClientScript::POS_END);
    }
}

And in the layout, I call the widget like this

<?php $this->widget('MyWidget');?>
<?php echo $content;?>

But in a view file, I need the variable declared by that widget.

<?php 
Yii::app()->clientScript->registerScript('script', <<<JAVASCRIPT
    alert(a);
JAVASCRIPT
    , CClientScript::POS_END);
?>

Note that in both registerScript method I use POS_END as the script position since I intend to put all of the scripts (including the CoreScript e.g. jQuery, jQueryUI etc) after the <body> tag.

The problem is that the rendered script will show the one from the view file and after that the one from the widget.

alert(a);
var a = "Hello World!";

As we can see, the above code won't work so I need to put the the second line above the first line.

Any idea on how to force the order? I'm okay with extending the CClientScript (and creating a new registerScript method) as long as all of the scripts will be rendered in the end position and I don't have to pull those inline Javascript codes above to a new package or file.

like image 350
Petra Barus Avatar asked Jul 25 '12 14:07

Petra Barus


2 Answers

So I finally find a hack to do this. I extend a new ClientScript class and modify the registerScript method so it will accept another param $level.

public function registerScript($id, $script, $position = self::POS_END, $level = 1);

Think about $level just like z-index in CSS, except the greater the number of $level is, the lower the position of the script will be.

For example

Yii::app()->clientScript->registerScript('script1', '/** SCRIPT #1 **/', CClientScript::POS_END, 1);
Yii::app()->clientScript->registerScript('script2', '/** SCRIPT #2 **/', CClientScript::POS_END, 2);
Yii::app()->clientScript->registerScript('script3', '/** SCRIPT #3 **/', CClientScript::POS_END, 1);

Even though script3 is declared after script2, in the rendered script, it will show above script2 since the $level value of script2 is greater than script3's.

Here's my solution's code. It's working like what I want although I'm not sure if the arranging method is optimized enough.

/**
 * ClientScript manages Javascript and CSS.
 */
class ClientScript extends CClientScript {
    public $scriptLevels = array();

    /**
     * Registers a piece of javascript code.
     * @param string $id ID that uniquely identifies this piece of JavaScript code
     * @param string $script the javascript code
     * @param integer $position the position of the JavaScript code.
     * @param integer $level the rendering priority of the JavaScript code in a position.
     * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
     */
    public function registerScript($id, $script, $position = self::POS_END, $level = 1) {
        $this->scriptLevels[$id] = $level;
        return parent::registerScript($id, $script, $position);
    }

    /**
     * Renders the registered scripts.
     * Overriding from CClientScript.
     * @param string $output the existing output that needs to be inserted with script tags
     */
    public function render(&$output) {
        if (!$this->hasScripts)
            return;

        $this->renderCoreScripts();

        if (!empty($this->scriptMap))
            $this->remapScripts();

        $this->unifyScripts();

        //===================================
        //Arranging the priority
        $this->rearrangeLevels();
        //===================================

        $this->renderHead($output);
        if ($this->enableJavaScript) {
            $this->renderBodyBegin($output);
            $this->renderBodyEnd($output);
        }
    }


    /**
     * Rearrange the script levels.
     */
    public function rearrangeLevels() {
        $scriptLevels = $this->scriptLevels;
        foreach ($this->scripts as $position => &$scripts) {
            $newscripts = array();
            $tempscript = array();
            foreach ($scripts as $id => $script) {
                $level = isset($scriptLevels[$id]) ? $scriptLevels[$id] : 1;
                $tempscript[$level][$id] = $script;
            }
            foreach ($tempscript as $s) {
                foreach ($s as $id => $script) {
                    $newscripts[$id] = $script;
                }
            }
            $scripts = $newscripts;
        }
    }
}

So I just need to put the class as the clientScript component in the config

'components' => array(
  'clientScript' => array(
      'class' => 'ClientScript'
  )
)
like image 103
Petra Barus Avatar answered Oct 28 '22 23:10

Petra Barus


Try register to HEAD

Yii::app()->clientScript->registerScript(__CLASS__, <<<JAVASCRIPT
var a = "Hello World!";
JAVASCRIPT
, CClientScript::POS_HEAD);
like image 24
Sergey Avatar answered Oct 28 '22 22:10

Sergey