1

I have a really noob question about ajax widgets. It's really an extension of a question I asked a while ago: ajax widgets in pyramid and chameleon

If you look at this question and the contents of the file account_login_widget.pt, you'll see the 'widget' uses syntax like ${username}. This works again for a login widget of which there is only ever one on a page, but all hell breaks loose trying to use this 'widget pattern' for widgets that can exist multiple times on a page.

Now I want to create an ajax toggle button. The button should be configurable: the text of the button can change depending on the page it's used on, and the callback code should be able to be changed too...again depending on the page. Additionally multiple toggle buttons should be able to exist on the same page.

2 questions:

  1. Let's say I have a list of restaurants on my page and a 'Like' button next to each. What is the correct way to pass the restaurant id to the 'post' server call (ie: how do you pass variables to the javascript widget?).

  2. On the same page, let's say I want to use the same ajax toggle button widget, but in a different context. Let's say, on the same page I also have a list of people and I want to 'Follow' them. What's the correct way to pass the button text variable into the widget and also change the post server call behavior? (ie: In question 1 maybe I want to call restaurant.like(), but in question 2 I want to call person.follow()

Here is some sample code showing the widget...

<script type="text/javascript">
// plugin implementation
(function($) {
$.fn.create_ajax_toggle_button = function(csrf, name, toggle_on, url, on_text, off_text) {
    return this.each(function() {
        var anchor = $('<a></a>');
        anchor.appendTo(this);

        $(anchor).button();

        $(anchor).toggle_ajax_button_text(toggle_on, on_text, off_text);

        $(anchor).click(function(e) {
            var form_data = '_csrf=' + csrf + '&name=' + name + '&toggle_on=' + toggle_on;

            $.post(url, form_data, function(response) {
                $.fn.toggle_ajax_button_text();
            });
        });
    });
};

$.fn.toggle_ajax_button_text = function(toggle_on, on_text, off_text) {
    if (toggle_on == 'True') {
        $(this).toggleClass('ui-state-active');
        $(this).text(on_text);          
    } else {
        $(this).text(off_text);             
    }
};
})(jQuery);

// Define the entry point    
$(document).ready(function() {
    // YUCK!!! I really shouldn't be/can't using ${name} templating
    // syntax like this.  Doing this means the JS code needs to be
    // loaded over and over again for each item in the list.  This
    // just doesn't work.  :-(
    $('.ajax_toggle_button_div_${name}').create_ajax_toggle_button('${csrf}', '${name}',     '${toggle_on}', '${url}', '${on_text}', '${off_text}');
});
</script>

<div class="ajax_toggle_button_div_${name}"></div>

Thanks!

Community
  • 1
  • 1
lostdorje
  • 6,150
  • 9
  • 44
  • 86

1 Answers1

1

I feel like you're doing way too much work just for a like button and I hate javascript so I try to avoid it as much as possible, especially on document loads since it lags the user's browser. I'm going to post what I use for "subscriptions", and hopefully it helps you in some way.

In the controller, I have a template that creates a subscription button. Instead of changing names on a single button, I make two buttons with one hidden (or disabled). This really shouldn't take much more time than what you currently do. Using Mako (not familiar with Chameleon), the template function is:

<%def name="subscribe(post)">
% if request.user:
<% subscribe = Bookmark_C.check_subscribed(request.user.id, post.id) %>
<span class="pstnm">${Bookmark_C.get_num_subscribers(post.id)}</span>
<span class="pstbtn${' hide' if subscribe else ''}" id="sub_${post.base36}" onclick="subscribe(this)">subscribe</span>
<span class="pstbtn${' hide' if not subscribe else ''}" id="unsub_${post.base36}" onclick="unsubscribe(this)">unsubscribe</span>
% endif
</%def>

The current user object is stored in request.user, and subscribe is a boolean telling me whether the current user is subscribed to the current "post". I ID each button with a button name, "sub" or "unsub", postfixed with the ID of the post, which I send/receive in base36 form, and separate these two names with an underscore. I then use javascript to extract the ID of the post out of each button's ID.

function subscribe(obj) {
  var id = $(obj).attr("id").split('_')[1];
  $.post(
    "/api/subscribe/",
    {id: id, _csrf: _csrf},
    function () {
      $('#sub_' + id).hide();
      $('#unsub_' + id).show();
    }
  );
}

I have basically the same function for unsubscribing. Notice how I receive the post's ID from the button's ID attribute. The /api/subscribe/ and /api/unsubscribe/ route takes only one parameter, id, and makes request.user subscribe to the post with that id. These routes won't work if request.user or _csrf doesn't exist.

My version is much shorter and works for sure. Hopefully this helps you.

For likes vs follows, I would write two templates and two sets of javascript functions, assuming they use entirely different controllers. If you're trying to copy Facebook where Like, Subscribe, and Follow are all the same thing, then all you need to do is have the template allow different texts:

<%def name="subscribe(post, subtitle='subscribe', unsubtitle='unsubscribe')">
...
<span ...>${subtitle}</span>
<span ...>${unsubtitle}</span>
</%def>
Jonathan Ong
  • 19,927
  • 17
  • 79
  • 118
  • Thanks for the detailed response. It's a little bit less general than what I'm trying to achieve, but I might end up just going this route. – lostdorje Jan 02 '12 at 02:13