ajax+clientScript

At the moment I have no working extended version of CClientScript because I’m working on some jQuery Plugins. I think I will work on this problem at the end of this month (november).

I don’t think that this will be integrated in the core because most time it will be a special solution for a special problem. To generalize will be very problematic – but I will try to handle this ;)

@zaccaria:

Nice hack you made! Tho I ran into a bug. The core scripts don’t get registered ergo it can cause js errors. For example:

CListView::registerClientScript() has this row:


$cs->registerCoreScript('bbq');

It won’t get registered because you included only the user scripts in ZClientScript::renderOnRequest().

I’m trying to find a workaround now.

The missing of the above script causes this javascript error:


$.param.querystring is not a function

My main problem is, you cannot run ajaxed js code without eval().

I modified CListView’s html code, so rows could be updated, deleted and more rows could be added. (used inputs + ajaxbuttons) After each deletion or addition I updated the content of the list. I managed to give each button an unique id this way:


    <?php echo CHtml::ajaxButton('Save',

        array('attributeValue/update', 'id' => $data->id),

        array(

            'success' => "js: function(data, textStatus, XMLHttpRequest) { $.fn.yiiListView.update('list'); }",

            'type' => 'post',

        ),

        array('name' => 'tl'.CHtml::$count)

    ); ?>

    <?php CHtml::$count++; ?>

When I updated the content it didn’t interfere with the selectors from the main code.

If you include the main script again in the widget, you could get errors, like I did with loading jquery again. (CListWidget’s JS crashed), you have to manually include the files:


Yii::app()->getClientScript()->registerCoreScript('bbq');

I modified zaccaria’s code, so the scripts could be placed where they belong and this way js script can be surrounded by onload & ondomready events. (JQuery undefined error!)


	public function renderOnRequest()

	{

		$html='';

		foreach($this->scriptFiles as $scriptFiles)

		{

			foreach($scriptFiles as $scriptFile)

				$html.=CHtml::scriptFile($scriptFile)."\n";

		}


                $scripts=isset($this->scripts[self::POS_END]) ? $this->scripts[self::POS_END] : array();

                foreach($this->scripts as $k=>$script) {

                    if ($k == self::POS_READY) {

                        $scripts[] = "jQuery(function($) {\n".implode("\n",$script)."\n});";

                    }

                    elseif($k == self::POS_LOAD) {

                        $scripts[] ="jQuery(window).load(function() {\n".implode("\n",$script)."\n});";

                    }                     

                }

                if (!empty($scripts)) $html .= CHtml::script(implode("\n",$scripts))."\n";


		if($html!=='')

			return $html;

	}

The problem was, that when I updated list content the <script> tags were removed via the jquery functions. You cannot add/modifiy <script> tags via JQuery, only hardcoded style. You have to get it as a string and eval() it.

So, after refresh you would have to "reassign" JQuery delegates, because the ids could have been modified, assigned to other objects:


jQuery('body').delegate('#tl0','click',function(){jQuery.ajax({'success': function(data, textStatus, XMLHttpRequest) { $.fn.yiiListView.update('tulajdonsag-lista'); },'type':'post','url':'/yii-gsmhome/admin/attributeValue/update/id/2','cache':false,'data':jQuery(this).parents("form").serialize()});return false;});

jQuery('body').delegate('#tl1','click',function(){jQuery.ajax({'success': function(data, textStatus, XMLHttpRequest) { $.fn.yiiListView.update('tulajdonsag-lista'); },'type':'post','url':'/yii-gsmhome/admin/attributeValue/delete/id/2','cache':false,'data':jQuery(this).parents("form").serialize()});return false;});

I could try to examine the right <script> tag’s content and eval it, so it always becomes updated.

By the way the same kind of thing happens if you have custom buttons in CGridView. When you paginate through ajax, and change page, you have to reassign the events attached to the "new" buttons loaded via ajax. Otherwise it doesnt work.

Any thoughts of this?

I ran into a similar problem and tried to solve it with the extended client script. As others have pointed out though, the solution has its drawbacks:

  • CSS files required by some widgets are not included in the AJAX response

  • reloading core scripts will invalidate previously registered $.fn method calls (and render them unusable)

An alternative solution; just perform a renderPartial for the AJAX request, and filter the resulting HTML on the client side. First, register all statically loaded CSS and JS files:


Yii::app()->clientScript->registerScript('register_static_css_js', "                                                                               

$(function() {                                                                                                                                         

     script_files = $('script[src]').map(function() { return $(this).attr('src'); }).get();                                                                                                                                          

     css_files = $('link[href]').map(function() { return $(this).attr('href'); }).get();                                                                                                                                          

});");

Then, in the AJAX success callback, only include external script and stylesheets that are not yet present in the DOM. Inline Javascript is always included.




success: function(data) {                                                                                                                  

    var reply = $(data);                                                                                                                   

    var target = $('#some_element');

    target.html('');                                                                                                                         

    target.append(reply.filter('script[src]').filter(function() { return $.inArray($(this).attr('src'), script_files) === -1; }));           

    target.append(reply.filter('link[href]').filter(function() { return $.inArray($(this).attr('href'), css_files) === -1; }));              

    target.append(reply.filter('#content'));                                                                                                      

    target.append(reply.filter('script:not([src])'));                                                                                        

}

I’m pretty sure the jQuery code can be optimized, still working on that. Any suggestions?

Promising solution, dude!

Thanks for the feedback :)

Slight improvement to the success callback to prevent redundant requests in case more than one AJAX request is done:




success: function(data) {                                                                                                                  

    var reply = $(data);                                                                                                               	

    var target = $('#some_element');

    target.html('');                                                                                                                     	

    target.append(reply.filter('script[src]').filter(function() {

        if ($.inArray($(this).attr('src'), script_files) === -1) {

            script_files.push($(this).attr('src'));

            return true;

        }

        return false;

    }));       	

    target.append(reply.filter('link[href]').filter(function() {

        if ($.inArray($(this).attr('href'), css_files) === -1) {

            css_files.push($(this).attr('href'));

            return true;

        }

        return false;

    }));              

    target.append(reply.filter('#content'));                                                                                                      

    target.append(reply.filter('script:not([src])'));                                                                                        

}

@blux this is nice!

Here’s a simple solution, intended to help this same situation:

http://code.google.com/p/yii/issues/detail?id=1961

Not necessarily a complete solution, but works for me, and may hopefully rope Qiang into the discussion for his input :wink:

Thanks a lot Zaccaria. Your solution works like a charm.

I too am having this extremely frustrating problem. @intel352’s patch #1961 works if one is generating the script in the ajax response. BUT if the ajax response uses a zii widget which relies on jQuery, it does not work. I applied the CClientScript patch from #1961, then did:




    Yii::app()->getClientScript()->renderScriptFiles = false; // use #1961

    $this->beginWidget('zii.widgets.jui.CJuiDialog', array(

        'id'=>'HistoryDivPopup',

        'options'=>array(

            'title'=>Yii::t('DR','Change History'),

            'autoOpen'=>true,

            'modal'=>true,

            'width'=>600,

            'position'=>array(300,200),

        ),

    ));

    echo $out; // dialog content

    $this->endWidget('zii.widgets.jui.CJuiDialog');



the CJuiDialog does not auto-open, as it does when jQuery is present.

But without renderScriptFiles = false, then after I close the dialog, ajax elements that were already present in the page no longer work. I get "jQuery.yii is undefined". Specifically, I have a "Cancel" button which was rendered in the original view, like this:




            echo CHtml::resetButton(Yii::t('DR','Cancel'),array('submit'=>$this->createUrl($return)));



can anyone help me with this? I am at the very beginning stage of learning javascript and jQuery.

Hi Jeremy, if the problem is that jQuery is missing, then just make sure jQuery is registered in the view that will be pulling in your Dialog window.

If you need more direct help, contact me at jon [at] phpsitesolutions.com

jQuery is already present in the main view, because I’ve used CHtml::SubmitButton() and CHtml::resetButton(). Explicitly registering jquery.js and jquery.yii.js at the top of the _form.php does not help; nor does doing this in my main.php layout.

Maybe I should have mentioned this is all CRUD-generated code (very simple form) using CActiveForm widget.

Also tried @zaccaria’s solution, which works, but as others pointed out it also blocks the css for the CJuiWidget.

{EDIT}: <duh> the solution in my case was much simpler. before rendering the widget in my partial view, I do:




Yii::app()->getClientScript()->scriptMap=array('jquery.js'=>false,'jquery.min.js'=>false); // don't resend jquery.cs



{/EDIT}

I think the answer to my last post is here:

http://www.yiiframework.com/forum/index.php?/topic/12697-registerscriptfile-of-yii-built-in-widgets-isntset-cclientscriptpos/

worked around it by using patch #1961, plus loaded jquery-ui in my main layout. I’m not crazy about this solution, and hoping for an improvement. Maybe patching the widgets per post 12697, and then using scriptMap to exclude jquery.js from loading again ?

I’m trying to load a CJuiDialog from an external view with the ZController render function but it seems that it doesn’t load the requested jquery javascript files and css. How can I do that?? Should I use some specific function inside the view??

Hi!

I can’t extend ‘components/Contoller.php’ to ZController because I’m using the new version of ‘Rights’ module and the components/Contoller.php’ extends to RController :(

I can’t find solution for this problem :unsure:

The solution is to cascade the inheritance, so ZController extends RController, which extends CController; or the other way around if it seems better. I am not familiar with the Rights module so I don’t know which order the extension will work best.

Hi, everyone i’m trying to create ajax call but fail and fail. So does enyone can help me. Can you write simple code how to call action from controller with ajax on mouse over or on mouse move? :)

Hi!

the cascade inheritance is works! but the ajax+clientScript problem is still :(

Hey zaccaria,

Thanks so much for this solutions. With my implementation, the CJuiDialog worked only on the 1st click (after) page rendered, now when I close the dialog and click on the open dialog link again, it gave a $().dialog(‘open’) not defined error.

I got it to work with your solutions plus I also added the CSS files for the widget with this code:


public function renderOnRequest() {


        $html = '';

        foreach ($this->scriptFiles as $scriptFiles) {

            foreach ($scriptFiles as $scriptFile)

                $html.=CHtml::scriptFile($scriptFile) . "\n";

        }


        // Addition to register CSS files

        foreach ($this->cssFiles as $key => $value) {


            $html .= CHtml::cssFile($key) . "\n";

        }


        foreach ($this->scripts as $script)

            $html.=CHtml::script(implode("\n", $script)) . "\n";


        if ($html !== '')

            return $html;

    }

Hi, maybe you’ll find it also useful: http://www.yiiframework.com/extension/nlsclientscript/