可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
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'
)
)
回答2:
Try register to HEAD
Yii::app()->clientScript->registerScript(__CLASS__, <<<JAVASCRIPT
var a = "Hello World!";
JAVASCRIPT
, CClientScript::POS_HEAD);
回答3:
I've contributed a patch to Yii to allow ordering (prepend, append, number)
you can find it here
https://github.com/yiisoft/yii/pull/2263
if it did not get its place into you, then you can extend CClientScript to your own class to apply my changes
but in general properly written JS should work independent of order
for example instead of defining some global scope vars you can assign them as properties to window (accessing a non-existing property does not throw an error in js)
回答4:
There is no need to extend CClienScript. A simple solution to this problem would be to create a $script array in your controller in your controller and add the view scripts to that variable. In your layout file You would register the scripts stored in your $scripts array after you have registered the ones you want to be rendered before it. e.g.:
in your controller
public $scripts = [];
In your layout:
$baseURL = Yii::app()->request->baseUrl;
$clientScript = Yii::app()->clientScript;
$clientScript->registerScriptFile( $baseURL . '/js/jquery.min.js', CClientScript::POS_END );
$clientScript->registerScriptFile( $baseURL . '/js/bootstrap.min.js', CClientScript::POS_END);
and in your view
<?php array_push($this->scripts,"/js/summernote.min.js");?>
回答5:
Override the CClientScript class as @Petra said, here is the code I am using, after using @Petra code and didn't worked out for me.
class ClientScript extends CClientScript {
public $scriptLevels;
public function registerScript($id, $script, $position = null, $level = 1, array $htmlOptions = array()) {
$this->scriptLevels[$id] = $level;
return parent::registerScript($id, $script, $position, $htmlOptions);
}
public function render(&$output) {
foreach ($this->scripts as $key => $value) {
$this->scripts[$key] = $this->reorderScripts($value);
}
parent::render($output);
}
public function reorderScripts($element) {
$tempScripts = array();
$buffer = array();
foreach ($element as $key => $value) {
if (isset($this->scriptLevels[$key])) {
$tempScripts[$this->scriptLevels[$key]][$key] = $value;
}
}
ksort($tempScripts);
foreach ($tempScripts as $value) {
foreach ($value as $k => $v) {
$buffer[$k] = $v;
}
}
return $buffer;
}
}
and then, go to your config file, and in the components section edit the following:
'components' => array(
.
.
'clientScript' => array(
'class' => 'ClientScript'
),
)