Adam
Adam

Reputation: 2031

Dynamically change the URL of Yii ajaxLink on success

How can I change the URL parameter of an ajax link after the ajax has successfully executed? The following code doesn't work, but explains what I want to do.

CHtml::ajaxLink($text, array('/core/bookmark/create'), array(
    'data' => array("id" => $id),
    'type' => 'GET',
    'success'=>'js:function(data){
        if (data.status == "success"){ 
            //attempt to dynamically change the URL
            this.url = "/core/bookmark/delete";
            $("#bookmark-btn").toggleClass("bookmark_on").toggleClass("bookmark_off");
        }
    }',
), array('id'=>'bookmark-btn', 'class' => 'bookmark_off'));  

the line with this.url makes no effect on the ajaxLink's URL, but it doesn't throw any errors in the console either.

Upvotes: 2

Views: 5880

Answers (3)

bool.dev
bool.dev

Reputation: 17478

Why your method does not work

The actual reason this is not working is that you are changing the current jquery ajax object's url property, and not the url for future objects. This becomes obvious when you see the generated html/js:

Link:

<a href="#" class="bookmark_off" id="bookmark-btn">value-of-text</a>

Event listener (the following is found usually in the last <script> before </body> , i have also added a few comments):

$('body').on('click','#bookmark-btn',function(){
    jQuery.ajax({'data':{'id':1},'type':'GET',
        'success':js:function(data){
            if (data.status == "success"){
                //attempt to dynamically change the URL
                this.url = "/core/bookmark/delete";
                $("#bookmark-btn").toggleClass("bookmark_on").toggleClass("bookmark_off");
            }
        },
        'url':'/project-name/index.php/core/bookmark/create', // this url is used repeatedly
        'cache':false
    }); // ajax call ends
    return false; // click handler ends
});

The above listener shows that for each click a new ajax object/call is created/done, therefore everytime only that object's url is changed, but not for the next object, which will be created for the next call. For the next call the same old url will be used, i.e : 'url':'/project-name/index.php/core/bookmark/create', .


Solution

There is a way to "dynamically change the url of an ajax link". Instead of using CHtml::ajaxLink, use a normal CHtml::link, but with added clientChange options. The clientChange options are included within the third parameter to CHtml::link, i.e within htmlOptions. This way we can specify a dynamic url for ajax calls, which will use the href of the <a> tag generated for ajax calls, instead of a static url.

An example (read the comments in code):

echo CHtml::link($text, array('/core/bookmark/create'), array(
    // the htmlOptions array, with clientOptions included
    'id'=>'bookmark-btn',
    'class' => 'bookmark_off',
    'return' => false, // this is already false by default - read documentation
    'ajax' => array( // now specify the ajax options, with a dynamic url
        'url' => 'js:$(this).attr("href")', // this takes the href property of
                // the <a> tag that is generated for this CHtml::link
        'data' => array('id' => $id),
        'custom_data_passed_to_ajax_object' => 'js:this', // this is important,
                // here we pass link, <a> tag, for use within the ajax object
        'success'=>'function(data){
            if (data.status == "success"){ 
                // now change the href of the <a> we passed in custom_data_passed_to_ajax_object
                $(this.custom_data_passed_to_ajax_object).attr("href",
                    "'.$this->createUrl('/core/bookmark/delete').'");
                $("#bookmark-btn").toggleClass("bookmark_on").toggleClass("bookmark_off");
            }
        }'
    )
));

You can actually adapt the above solution for ajaxLink also:

echo CHtml::ajaxLink($text,
    'js:$(this).attr("href")', // this will be generated as 'url':$(this).attr("href")
    array(// ajax options, remember to pass the reference to the <a>,
        // i.e custom_data_passed_to_ajax_object in the above snippet
    ),
    array(
        'href'=>$this->createUrl('/core/bookmark/create'), // your actual url
        // rest of htmlOptions
    )
);

As for your final goal of "implement a bookmark button which toggles between add and delete functionality on each click", everyone else has already given you enough hints to go on.

Upvotes: 1

ernie
ernie

Reputation: 6356

The issue with your current approach is twofold:

  1. the .url property refers to the documents URL, not the link's URL
  2. what you really want to change is the action performed in the event handler in the javascript, not a part of the DOM

I think to get the behavior you want, you'll need to handle this in your controller, as well as modifying your ajaxLink a bit to keep track of state.

CHtml::ajaxLink($text, array('/core/bookmark/create'), array(
    'data' => array("id" => $id, "state"=>$("#bookmark-btn").hasClass("bookmark_on") ),
    'type' => 'GET',
    'success'=>'js:function(data){
        if (data.status == "success"){ 
            var bookmarkState = $("#bookmark-btn").hasClass("bookmark_on");
            if (!bookmarkState) {
                $("#bookmark-btn").addClass("bookmark_on").removeClass("bookmark_off");
                // Probably need some code here to update the link text as well
            } else {
                $("#bookmark-btn").addClass("bookmark_off").removeClass("bookmark_on");
                // Probably need some code here to update the link text as well
            }
        }
    }',
), array('id'=>'bookmark-btn', 'class' => 'bookmark_off'));  

Then, in your controller (which I'd probably rename from create to something more generic like handleBookmark), you'd need to check the value of state, and then call the existing create or delete actions that you've already written.

Alternatively, you could do this on the client side via jQuery, but you'd have to write custom triggers and actions (most likely reading the value of the $("#bookmark-btn"), and going down different javascript paths there). This would mean you'd need to write your javascript directly, rather than using the Yii ajaxLink convenience method.

As has been noted, the easier way of doing this is probably to render both an add and delete link and toggle the visibility as needed.

Upvotes: 1

Uladzimir Pasvistselik
Uladzimir Pasvistselik

Reputation: 3921

CHtml::ajaxLink renders a element, which doesnt have url attribute. You need such code:

if (data.status == "success"){ 
    jQuery(this).attr('href', '/core/bookmark/delete');
}

Update:

Besides, what I'm wanting to do is change the URL in the Ajax function which fires when the link is clicked, not on the link itself.

In that case, better solution is to draw two CHtml::ajaxLink elements and toggle the visibility of them according to ajax-requests result. It will cost you less effort.

Upvotes: 2

Related Questions