Форсирование порядка скриптов из метода registerScript в Yii

Я создал виджет, который регистрирует собственный скрипт, как показано ниже

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

и в макете, я называю виджет, как это

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

но в файле представления мне нужна переменная, объявленная этим виджетом.

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

обратите внимание, что в обоих методах registerScript я использую POS_END в качестве позиции скрипта, так как я намерен поместить все скрипты (включая CoreScript, например, jQuery, jQueryUI и т. д.) После <body> тег.

проблема в том, что визуализированный скрипт покажет один из файла представления, а затем один из виджета.

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

как мы видим, приведенный выше код не будет работать, поэтому мне нужно поместить вторую строку выше первой строки.

есть идеи о том, как заставить заказ? Я в порядке с расширением CClientScript (и создание нового registerScript метод), пока все скрипты будут отображаться в конечной позиции, и мне не нужно вытаскивать эти встроенные коды Javascript выше в новый пакет или файл.

5 ответов


так что я, наконец, найти хак, чтобы сделать это. Я расширяю новый ClientScript класс и изменить registerScript метод, поэтому он примет другой param $level.

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

думал о $level как z-index в CSS, за исключением большего числа $level is, тем ниже будет позиция скрипта.

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);

хотя script3 объявлена после script2, в визуализированном скрипте он будет отображаться выше script2 С $level стоимостью script2 больше script3 ' s.

вот код моего решения. Он работает так, как я хочу, хотя я не уверен, что метод организации достаточно оптимизирован.

/**
 * 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;
        }
    }
}

поэтому мне просто нужно поместить класс в качестве компонента clientScript в config

'components' => array(
  'clientScript' => array(
      'class' => 'ClientScript'
  )
)

попробуйте зарегистрироваться в HEAD

Yii::app()->clientScript->registerScript(__CLASS__, <<<JAVASCRIPT
var a = "Hello World!";
JAVASCRIPT
, CClientScript::POS_HEAD);

Я внес патч в Yii, чтобы разрешить заказ (добавить, добавить, номер)

вы можете найти его здесь https://github.com/yiisoft/yii/pull/2263

если он не получил свое место в вас, то вы можете расширить CClientScript на свой собственный класс, чтобы применить мои изменения

но в целом правильно написано JS должен работать независимо от того, например, вместо определения некоторых глобальных областей vars вы можете назначить их в качестве свойств window (доступ к несуществующему свойству не вызывает ошибки в js)


нет необходимости расширять CClienScript. Простым решением этой проблемы было бы создать массив $script в контроллере в контроллере и добавить скрипты представления к этой переменной. В файле макета вы зарегистрируете скрипты, хранящиеся в массиве $scripts, после регистрации тех, которые вы хотите отобразить перед ним. например:

в геймпаде

public $scripts = [];

в вашей макет:

$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);

и в

<?php array_push($this->scripts,"/js/summernote.min.js");?>

переопределите класс CClientScript, как сказал @Petra, вот код, который я использую, после использования кода @Petra и не работал для меня.

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;
}

}

а затем перейдите в файл конфигурации и в разделе компоненты отредактируйте следующее:

'components' => array(
    .
    .
    'clientScript' => array(
        'class' => 'ClientScript'
    ),
)