RegisterScriptFile within Views and Layouts

Hi,

I have several Layout files and View files. If we look at a single Layout and View, I have the following code:

MyLayout.php




Yii::app()->clientScript->registerScriptFile('script_file_1.js', CClientScript::POS_HEAD);



MyView.php




Yii::app()->clientScript->registerScriptFile('script_file_2.js', CClientScript::POS_HEAD);



Now, in the generated XHTML, ‘script_file_2.js’ appears in the HEAD section before ‘script_file_1.js’, where I require (and would expect) 1 to appear before 2.

This is a problem, as ‘script_file_1.js’ may include jQuery, and ‘script_file_2.js’ may use jQuery, but if file 2 appears before file 1, then errors are generated.

I know I can hard-code the ‘<script>’ tags within the Layout, but this is not a viable option for me, as I use various Themes, and libraries, depending on the Layout and View combinations. Also, I re-use Views, so I do not want to use the RegisterScriptFile instruction in the Controller action(s), as this would cause unnecessary duplication.

Does anyone know of a way around this issue? Am I totally misunderstanding how RegisterScriptFile should be used?

I have thought of extending CClientScript and adding extra POS_* properties, so that I can force the sequence in which scripts should be rendered over and above POS_HEAD, etc…, but it would be much nicer if there was some way to decide if a View or a Layout was issuing RegisterScriptFile and re-order accordingly. I’m not even sure if this issue becomes more complicated when considering Widgets, Modules, Extensions, etc…

Any advice greatly received.

Thanks,

Mike

btw… I’m using Yii 1.1.6

I have run into this before as well. For this reason, I do not place registerScriptFile calls into the views. Instead, I place my registerScriptFile calls in controller actions and widgets, along with supplying CClientScript::POS_END.

Then I place the registerScriptFile calls for the base JS files (e.g. Jquery) within my base controller class’s init method. The base controller’s init method is called first, and attaches the base JS files. The view specific JS files are then appended once the widgets and controller actions execute.

Thanks for the advice Simian. I feel like I’ve stepped into a minefield, but I’m not sure if it is one laid by others or by myself :unsure: It looks like I may have to take a similar direction, as I feel that if I extend CClientScript in some way, then I’m probably stacking issues further down the line, especially where Widgets are concerned.

I’m hoping someone can indicate if I’m fighting a mechanism that works as intended due to the relationship between Views and Layouts.

have you tried the POS_END option in your view?




Yii::app()->clientScript->registerScriptFile('script_file_2.js', CClientScript::POS_END);



hope it helps

Yes, the view is rendered to a variable before the layout is rendered.

/Tommy

Thanks for the replies.

scoob.junior - POS_END is not what I really want. I would like both script 1 and script 2 to be in the HEAD section, first 1 then 2. POS_END would move script 2 to the wrong place within the final output XHTML. As there are many views, layouts and scripts, and several inter-dependencies, POS_END would not work in this case. Thanks anyway.

tri - I can understand the view being rendered to a variable before the layout, but I don’t see why this should affect the sequence of the SCRIPT statements within the HEAD section. I suppose I would expect all RegisterScriptFile scripts to be collectively rendered in the correct sequence, within their relevant sections of the XHTML. Thanks for the explanation, but it does not resolve my query with RegisterScriptFile.

Hi all,

I thought I’d let you know I’ve found a temporary workaround for my problem.

I’ve created a class extending CClientScript, and sort the scriptFiles property variable according to the sequence of the items I’ve added to the scriptMap property variable. I initialise the scriptMap property variable in the “init” function of my base controller.

For example,

Controller.php




public function init()

{

...

Yii::app()->clientScript->scriptMap = array('script_1.js' => '/my/js/script_1.js', 'script_2.js' => '/my/js/script_2.js');

...

}



MyClientScript.php




public function renderHead(&$output)

{

...

$jsFiles = $this->scriptFiles[parent::POS_HEAD];

$tempJsFiles = array();

$mapJsFiles = $this->scriptMap;


foreach ($mapJsFiles as $key => $value)

{

  if (isset($jsFiles[$value]))

  {

    $tempJsFiles[$jsFiles[$value]] = $jsFiles[$value];

  }

}


$jsFiles = $tempJsFiles;

$this->scriptFiles[parent::POS_HEAD] = $jsFiles;

...

}



This can be repeated for renderBodyBegin and renderBodyEnd as well, but obviously using POS_BEGIN and POS_END instead of POS_HEAD.

I know this is a hack, but it seems to work for what I’m trying to achieve, and I’m fortunate that I can add my script files to the scriptMap property variable in a definitive sequence.

In the meantime, if anyone has any improvements or suggestions, or knows that I’m fighting a losing battle, then please let us all know :D

Your question was

In my answer to this question I informed you that the view is rendered before the layout. I could have been more clear about the implications on the registerSciptFile() sequence. BTW, the "correct sequence" would be the order in which registerScriptFile() is called.

Glad you found a solution that satisfies your needs.

/Tommy

Hi tri,

Thanks for the further information.

You say that the "correct sequence" would be the order in which registerScriptFile is called. If this was the case, then I believe I would not have a problem, as my views and layouts are calling registerScriptFile in the sequence that I have defined.

However, from what you have said about views being rendered before layouts, I have to assume that the registerScriptFile statements in the views are also processed before those in the layouts; that would explain why the SCRIPT statements in the final XHTML output are in the undesired sequence. Therefore, my understanding / assumption of the intended sequence of SCRIPT statements is completely incorrect. Thanks for setting me straight :)

Now, the real question is … is there a way to ensure the registerScriptFile statements in the layouts are inserted in the "script queue" before the registerScriptFile statements in the views, without some kind of hack?

Alternatively, is there some universal way to gather all of the registerScriptFile requests before any rendering takes place, so that the final SCRIPT sequence can be "corrected", without resorting to my aforementioned hack?

Thanks for your persistence and patience with this matter.