JaquelineBrandao / yii

Automatically exported from code.google.com/p/yii
0 stars 0 forks source link

CHhtml links and buttons don't work with ajax. #38

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
What steps will reproduce the problem?

<div id="target">
<?php echo CHtml::ajaxLink("TestLink",
                array('site/updateTarget'),
                array('replace'=>'#target'),
                array('name'=>'testLink', 'confirm' => 'Are you sure?')
        ); ?>
</div>
Works only ones.

What is the expected output? What do you see instead?
Should work always.

What version of the product are you using? On what operating system?
SVN, tested on Gecko (Iceweasel) and KHTML (Koqueror) browsers.

Suggestion to solve:

in class CClientScript:
+ const POS_TAG=5;

in CHtml::clientChange($event,&$htmlOptions):

+if($htmlOptions['scriptPosition'] === CClientScript::POS_TAG)
+  $htmlOptions['on'.$event] = $handler;
+else 
    $cs->registerScript('Yii.CHtml.#'.$id,"jQuery('#$id').
$event(function(){{$handler}});");
+unset($htmlOptions['scriptPosition']);

Original issue reported on code.google.com by rb.pa...@gmail.com on 4 Dec 2008 at 4:01

GoogleCodeExporter commented 9 years ago
Thanks.

Original comment by qiang.xue on 7 Dec 2008 at 1:11

GoogleCodeExporter commented 9 years ago
Issues are also found in widget classes. We need a better solution.

The cause of this issue is that js in "ready" function is called before the ajax
"update/replace" is performed. Therefore, the expected onclick and other event
handlers are not hooked to the corresponding HTML elements generated by ajax
"update/replace".

A possible solution to this problem is that we wrap those js inside a function 
and
register this function to "ready". For ajax "update/replace", we explicitly 
call this
function. A potential problem is that an onclick handler might be registered 
multiple
times.

Original comment by qiang.xue on 9 Dec 2008 at 1:57

GoogleCodeExporter commented 9 years ago
This is good idea. Registering multiple times is not a problem. There can be 
global
variable which can store in array of boolean whether the function was 
registered. 

Original comment by rb.pa...@gmail.com on 9 Dec 2008 at 4:13

GoogleCodeExporter commented 9 years ago
Please look here:
http://www.yiiframework.com/forum/index.php/topic,249.msg1527.html#msg1527

Original comment by rb.pa...@gmail.com on 22 Dec 2008 at 6:51

GoogleCodeExporter commented 9 years ago
This might be caused by a jquery issue
http://groups.google.com/group/jquery-dev/browse_thread/thread/9f8d25fc9f43b859?
hl=en

Original comment by qiang.xue on 25 Dec 2008 at 1:18

GoogleCodeExporter commented 9 years ago
This issue needs further investigation. Move to 1.0.2.

Original comment by qiang.xue on 3 Jan 2009 at 8:31

GoogleCodeExporter commented 9 years ago
This may also relate to this thread
http://www.yiiframework.com/forum/index.php?topic=90.0
Basically this is an ajax button updating a section with another ajax control. 
The second control is sent with the related javascript to wire the control 
(using
jquery). But is not activated because it is wrapped within a
"jQuery(document).ready(" function which never gets called. 

Original comment by notzi...@gmail.com on 6 Jan 2009 at 4:59

GoogleCodeExporter commented 9 years ago
Not entirely true. Yes, we should not put js code in ready() when they are 
returned
in ajax response. But still, this issue is not solved. The underlying problem 
is that
when the ajax response comes, there are two elements with the same ID: one in 
the
original page, and one in the ajax response. The original element is replaced 
by the
latter. It gets tricky how the replacement is done because it affects how the 
events
are bound.

Original comment by qiang.xue on 6 Jan 2009 at 5:01

GoogleCodeExporter commented 9 years ago
You could collect all the events bound to the element by using a 
jQuery.data(elem,
"events"), and then rebind them afterward. This would ensure all originally 
bound
events to the control were rebound. This would work fine if nothing was changed 
in
the way the control operated. But if the event handlers were required to be 
changed
then you would need to unbind and bind the new event handler manually which 
could be
tricky since you do not know which group of events are being changed.

Just some thoughts..

Original comment by notzi...@gmail.com on 22 Jan 2009 at 6:40

GoogleCodeExporter commented 9 years ago
Need more time on this issue. Moved to 1.0.3.

Original comment by qiang.xue on 30 Jan 2009 at 8:34

GoogleCodeExporter commented 9 years ago

Original comment by qiang.xue on 1 Mar 2009 at 3:48

GoogleCodeExporter commented 9 years ago

Original comment by qiang.xue on 5 Apr 2009 at 8:51

GoogleCodeExporter commented 9 years ago
I was having problems with this

found out about about the 'live' feature of Jquery

http://docs.jquery.com/Events/live

would that be a solution ?

does not work for all events though

any progress on a yii solition ?

thanks

Original comment by thomas.mery@gmail.com on 6 Apr 2009 at 11:10

GoogleCodeExporter commented 9 years ago
I've found in jQuery group following discussion, which may help us to solve this
problem:
http://groups.google.com/group/jquery-en/browse_thread/thread/7c21eb3722361e58/b
a5475801d1c4a1b?lnk=gst&q=ajax+works+once#ba5475801d1c4a1b.
I've found also that lambda function may help instead wrapping ready function:
http://www.learningjquery.com/2006/09/sacrificial-lambda.

There are also some plugins which are helpful when working with dynamic content:
- http://plugins.jquery.com/project/Listen
- http://plugins.jquery.com/project/Intercept
- http://plugins.jquery.com/project/livequery
Maybe we (yii'ers) should decide to use one of them?

Original comment by tomasz.s...@gmail.com on 23 Apr 2009 at 9:08

GoogleCodeExporter commented 9 years ago
Need more time.

Original comment by qiang.xue on 28 Apr 2009 at 1:09

GoogleCodeExporter commented 9 years ago
Is there a way to solve this? It's quite annoying, I can't get my ajaxLink 
working
inside an ajax request...

Original comment by andy.dam...@gmail.com on 4 Aug 2009 at 12:51

GoogleCodeExporter commented 9 years ago

Original comment by qiang.xue on 10 Sep 2009 at 7:52

GoogleCodeExporter commented 9 years ago
I worked with Symfony and similar functionality works fine. They are using 
Prototype
instead.
Maybe someone good with js (I am far from that) could take a look o their code.

Original comment by luciano....@gmail.com on 30 Oct 2009 at 10:44

GoogleCodeExporter commented 9 years ago
It`s almost a year now. What`s the status?

Original comment by mirko.ee...@gmail.com on 20 Nov 2009 at 4:11

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
I've try to fix this, here is my solution for this issue:

in the method CHtml::clientChange I've replaced code:

    $cs->registerScript('Yii.CHtml.#'.$id,"jQuery('#$id').$event(function(){{$handler}});");

with this one

    // check for currently not supported events by jQuery v1.3 `live`
    if(preg_match('/^(blur|focus|mouseenter|mouseleave|change|submit)$/si', $event))
    $cs->registerScript('Yii.CHtml.#'.$id,"jQuery('#$id').$event(function(){{$handler}});");
    else
    $cs->registerScript('Yii.CHtml.#'.$id,"jQuery('#$id').live('$event',function(){{$handler}});");

checked in FF 3.5, IE 8, Opera 10, it works, so I think, this can be used as
temporary solution

Original comment by a.jil...@gmail.com on 30 Nov 2009 at 6:40

GoogleCodeExporter commented 9 years ago
From what I've found the problem is most likely with jQuery. What seems to 
happen is
when you include the main jQuery file in say an ajax call it will reset the 
jQuery
environment. 
As i've noticed with trying to use ajaxLink you've already changed it so that 
the
links are embedded in a script tag if process output is set true, however you
reinclude the jquery file which in my case wipes out my jquery ui objects, so 
while
clicking the link fetches the html i expect my javascript ui window doesn't 
come up. 

CClientScript's behavior for ajax calls might be better served if it didn't 
serve the
registered script files unless they were specifically asked for say you were 
sending
an ajax call with html to insert and need a javascript file that specifies the
behavior of what your sending.

Proposed changes:
1. On ajax requests CClientScript should not send out script files and move all
registered scripts to the body. The reasoning here is that if your doing ajax 
calls
you probably have already everything setup as far as jQuery etc and the script 
tag
will get evaluated in html calls.

2. have a function to tell CClientScript that we do what to include a file such 
as
registerAjaxScriptFile. The reasoning here is your web app may be designed so 
that
certian features of it are loaded via ajax and you don't need the javascript 
that
makes the feature work till then.

3. For non ajax calls allow us to tell CClientScript it should go into ajax 
mode, the
reason here is that you may be faking an XmlHttpRequest with a get or post due 
to
cross site request limitations like i do when i submit user information from 
http ->
https via a jsonp call.

another simpler means might just be to add a function to CClientScript to 
unregister
a file. I believe at least for registering script chunks you could unregister 
it by
passing null or an empty string to the register function.

Original comment by dorml...@gmail.com on 30 Nov 2009 at 6:43

GoogleCodeExporter commented 9 years ago
My apologies I'm using 1.0.10 though it seems that this may be a problem with 
1.1 as
well.

Original comment by dorml...@gmail.com on 30 Nov 2009 at 6:48

GoogleCodeExporter commented 9 years ago
additional for my comment #23

IDs collision possible, if ID autogenerated by Yii
example:
- view contains 2 links (IDs: yt0, yt1), but ajax returns only 1 ajax link (ID: 
yt0)
in response, so returned link will use events of yt0

my solution solves only a half of the problems (rebinding of the events) :(

Original comment by a.jil...@gmail.com on 30 Nov 2009 at 7:02

GoogleCodeExporter commented 9 years ago
also for Ajax links in the Ajax response we nee ability in CClientScript to
flush/return generated handlers instead of put them into HTML (in $content)

Original comment by a.jil...@gmail.com on 30 Nov 2009 at 7:19

GoogleCodeExporter commented 9 years ago
dormlock: The method to block the sending of script files has been in Yii for a
while. It is invoked like Yii::app()->clientScript->scriptMap['*.js'] = false; 
This
will block any javascript file being included in the response.

Original comment by notzi...@gmail.com on 30 Nov 2009 at 2:10

GoogleCodeExporter commented 9 years ago
Thanks notzippy. I knew about the scriptMap as i use it to map my javascript 
files
differently depending on whether the app is running in development mode vs 
production.
I just looked at the guide and api and see i missed this fact.

a.jilkin you can get around the id collision by passing the id in through the 
html
options something like: array('id'=>'myid') this would prevent the name 
collisions,
however since the block of code is evaluated it will rebind any of the ids with 
the
same name, ie both of the yt0's would use the new code not the old code.

I also don't see a reason why you would need to get the generated handlers and 
not
let them be included with the rendered view. The only reason i could see 
wanting to
is if you wanted to send the data back in a formatted data response like json.

Original comment by dorml...@gmail.com on 30 Nov 2009 at 3:21

GoogleCodeExporter commented 9 years ago
dormlock: yes, ID can be specified in the htmlOptions, I've only describe 
possible
problem if ID will be generated by Yii.

Reason to add ability to get all generated handlers is for flexibility of 
CClientScript.

Original comment by a.jil...@gmail.com on 1 Dec 2009 at 10:31

GoogleCodeExporter commented 9 years ago
it seems, my patch (see comment #23) completely solves this problem,
here is a test example:

1. generate webapp
2. add action Update to the default controller

public function actionUpdate()
{
  $this->layout = false;
  Yii::app()->clientScript->scriptMap['*.js'] = false; // thanks, notzippy ;)
  $this->render('update');
}

3. add alaxLink to the default view

<div id="firstDiv">
<?php echo CHtml::ajaxLink('My Link',
    array('/default/update'),
    array('replace' => '#firstDiv'),
    array('id' => 'updateFirstDiv')); ?>
</div>

4. add view for Ajax response

<div id="firstDiv">
<?php echo CHtml::ajaxLink('New Link',
    array('/default/update'),
    array('update' => '#secondDiv'),
    array('id' => 'updateSecondDiv'));
?>
</div>
<div id="secondDiv"></div>

5. open webapp URL
6. click "My Link" --> #firstDiv replaced with new ajaxLink
7. click "New Link" -> second DIV successfully updated by ajaxLink from response

Original comment by a.jil...@gmail.com on 3 Dec 2009 at 4:52

GoogleCodeExporter commented 9 years ago

Original comment by qiang.xue on 9 Jan 2010 at 9:05

GoogleCodeExporter commented 9 years ago
When you call CController::renderPartial($view, $data, false, true), a <script> 
with
the appropriate event handler is sent. The problem is that jQuery evaluates the
<script> *before* the DOM is updated, which means it adds the event listener to 
an
element that immediately gets removed from the page, instead of the new one 
that is
replacing it.

The solution to this is simply to wrap the jQuery.bind call in an anonymous
setTimeout function, so that its execution is delayed until after the DOM has 
been
updated, like so:

--- yii-1.1.0.r1700/framework/web/helpers/CHtml.orig.php      2010-02-11
02:45:46.000000000 -0600
+++ yii-1.1.0.r1700/framework/web/helpers/CHtml.php   2010-02-11 
02:40:44.000000000 -0600
@@ -1779,7 +1779,7 @@
                                $handler="return $confirm;";
                }

-              
$cs->registerScript('Yii.CHtml.#'.$id,"jQuery('#$id').$event(function(){{$handle
r}});");
+              
$cs->registerScript('Yii.CHtml.#'.$id,"setTimeout(function(){jQuery('#$id').$eve
nt(function(){{$handler}});},
1);");

unset($htmlOptions['params'],$htmlOptions['submit'],$htmlOptions['ajax'],$htmlOp
tions['confirm'],$htmlOptions['return'],$htmlOptions['csrf']);
        }

This is tested and works properly in IE7, Firefox 3.5, Safari 4, Chrome 4, and 
Opera
10.10. There should be very low risk of breaking compatibility unless somebody 
has
already tried to manually work around this issue in their code.

One final point to note is that if you rely on Yii to generate unique IDs for 
you,
the ID generated in the partial might end up matching one that was already 
created
and used on part of the page that *isn’t* getting replaced. It would probably 
be wise
if Yii switched to using uniqid() or similar when generating IDs to ensure that 
they
are truly unique, even across AJAX loads.

Regards,

Original comment by goo...@zetafleet.com on 11 Feb 2010 at 8:51

GoogleCodeExporter commented 9 years ago
About the generated ID's: I can't agree more, see 
http://code.google.com/p/yii/issues/detail?id=762

Original comment by maxximus...@gmail.com on 11 Feb 2010 at 9:49

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
This issue was closed by revision r1818.

Original comment by qiang.xue on 18 Feb 2010 at 10:45

GoogleCodeExporter commented 9 years ago
Thank you all for participating in the discussion.

I am closing this issue by using the 'live' event handling approach.

Original comment by qiang.xue on 18 Feb 2010 at 10:46

GoogleCodeExporter commented 9 years ago
live() does not solve this issue.

First, if you are using renderPartial() with $processOutput = true (which would 
be
normal and correct behaviour for a partial rendering that is being returned as 
the
complete content, according to docs) and that partial contains a CHtml AJAX 
element,
you end up binding another anonymous function without removing the old one every
single time the section containing the AJAX element is reloaded.

Second, if you replace a section of the page with new content that contains an
element with the same ID but which performs a different action (say, a 
dynamically
loaded multi-part form), the live() approach *does not* solve the problem. The 
old
event handling function will continue to exist even after the object that it
initially was bound to is gone, leading to incorrect behaviour.

Also, this prevents garbage collection from removing unused anonymous functions,
which means memory leaks for long-running Yii RIAs.

The correct solution is to ensure the DOM is updated before attempting to 
attach the
event listener. Adding live() support is good for other reasons, but it is not 
the
solution.

Regards,

Original comment by goo...@zetafleet.com on 18 Feb 2010 at 11:09

GoogleCodeExporter commented 9 years ago
Perhaps the new delegate() and undelegate() functions in 1.4.2 will help 
keeping the 
DOM sane, instead of using live().

I still like to see a better way how id's are generated by default, the current 
method 
is not suitable for today's environments. Using uniqid() is probably not the 
solution 
when using delegate(), since undelegate() will be difficult. Perhaps generating 
an 
unique ID based on the supplied attributes could be done.

Original comment by maxximus...@gmail.com on 21 Feb 2010 at 12:46

GoogleCodeExporter commented 9 years ago
@Comment #39: There is *no* .live() method that can be used that will properly 
fix
this problem, as per my previous comment. These events should *not* apply to all
future elements that match the selector. All .delegate() does is improve 
performance
by binding the events to a context. .live() is *not* a suitable replacement for 
event
bindings that may change based on the data that loads. One should not have to
remember to .die() an event binding when using setTimeout to delay binding until
after the DOM is updated fixes the problem.

Original comment by goo...@zetafleet.com on 21 Feb 2010 at 7:39

GoogleCodeExporter commented 9 years ago
This one is not fixed. Checked with r2336.

Original comment by alexander.makarow on 25 Aug 2010 at 10:01

GoogleCodeExporter commented 9 years ago
I continue to manually apply the attached patch to resolve this issue, which is 
the same fix mentioned in Comment #33.

Original comment by goo...@zetafleet.com on 25 Aug 2010 at 5:47

Attachments:

GoogleCodeExporter commented 9 years ago
With the new 'delegate' usage, this issue should already be fixed.

Original comment by qiang.xue on 31 Aug 2010 at 3:08

GoogleCodeExporter commented 9 years ago
Hi Qiang,

Could you please explain how delegate does anything to resolve the issues that 
I raised in Comment #38? Seeing as how the only difference between live and 
delegate is that live binds to the document, whereas delegate binds to an 
element lower in the DOM, I don’t see how it addresses this problem.

Original comment by goo...@zetafleet.com on 31 Aug 2010 at 5:34

GoogleCodeExporter commented 9 years ago
Both issues you reported are certainly valid, but I doubt there is a simple 
solution to them. For example, assume in the initial request, you use some 
jquery plugin. And then in ajax response, you include once again jquery.js. You 
may find the jquery plugin stops working because of the re-inclusion of jquery.

I think it is more a responsibility of the developer to properly write the js 
code to do any necessary cleanup. I don't think the timer solution alone can 
fully solve this issue.

In fact, in real practice when dealing with very complex ajax interactions, we 
usually use the approach that we first include all necessary js files, and then 
in ajax responses, we only pass back json data, and on the client side we 
analyze the json response to perform any necessary js actions.

Original comment by qiang.xue on 31 Aug 2010 at 5:50

GoogleCodeExporter commented 9 years ago
Issue 1138 has been merged into this issue.

Original comment by qiang.xue on 1 Sep 2010 at 2:39

GoogleCodeExporter commented 9 years ago
Issue 1315 has been merged into this issue.

Original comment by qiang.xue on 2 Sep 2010 at 2:24

GoogleCodeExporter commented 9 years ago
@Comment 45

You’re absolutely correct on all accounts. I don’t think there will ever be 
a 100% perfect solution to this problem, but I do think we can do better. The 
provided patch works well for my situation, which I imagine is a pretty common 
case (a simple pre-CActiveForm AJAX form that loads over itself), but you’re 
right that it doesn’t solve every potential issue, and the reloading of 
jquery.js is quite icky. So, I would suggest the patch from Comment 42 also 
requires a change to CController::processOutput.

During an AJAX request, it is safe to assume that jquery.js is already loaded 
(since otherwise AJAX would not work), so we can suppress loading that file. We 
would also need to avoid loading any scripts that were mapped to jquery.js. 
Other than that, reloading scripts (such as plugins) should be safe. I can work 
on a patch for this if you would like.

I do just want to mention that once you get to the point where you are 
preloading code to manage your AJAX calls, you’re probably not going to be 
using CHtml’s AJAX stuff anyway. In my experience, it’s really mostly a 
convenience to load simple page fragments, and its utility is greatly 
diminished when those fragments can’t include widgets.

Regards,

Original comment by goo...@zetafleet.com on 2 Sep 2010 at 3:07

GoogleCodeExporter commented 9 years ago
Through my past experience of client side scripting (a lot of ajax involved), I 
think it's better for us to stop here without doing anything further.

Reloading non-jquery scripts may not be safe either because the property 
initialized previously may be overwritten and thrown away.

As you and I both agree, the proper solution to this issue is preloading client 
side code that are needed in later ajax responses. So instead of thinking of 
ways to patch for this issue, why not leave it there and let developers to 
realize it so that they can go on the right track earlier?

Original comment by qiang.xue on 2 Sep 2010 at 3:55

GoogleCodeExporter commented 9 years ago
Hi,
You could add a CClientScript position POS_REHOOK
all scripts registered there may be wrapped in a rehook() JS function
an CHTML::ajaxRehookLink custom function would fire rehook('#html_id') on ajax 
complete, therefore rebinding any events to newly created elements

Original comment by tudorili...@gmail.com on 23 Mar 2011 at 4:18