apotonick / apotomo

MVC Components for Rails.
http://apotomo.de
653 stars 68 forks source link

Invocation of callback function after update or replace #17

Open dre3k opened 13 years ago

dre3k commented 13 years ago

Hi Nick.

What is our opinion on making possible to specify a callback function(or array of functions) that will be invoked after update or replace.

I'll try to explain. I use coffeescript and I don't embed javascript within haml view. I have application.coffee that bind events to ajax requests. One way to call a function after update is to hook a callback function on ajax complete event. For example in Peter's Guide when new tweet is added tweets list updated and it's necessary to make tweets draggble again. The same is necessary when tweet is dumped and tweet list updated. I think instead of specifying one callback for tweet add and one more the same callback for tweet delete, it's reasonable to let update(and replace) method accept something like :callback option, that can specify function that will be invoked when update is finished. Then it's probably will be useful to specify arguments that passed to this function(s), and maybe make conventional case on :callback => true parameter.

I just started going through Peter's Guide and I have quick&dirty solution, but its will be enough to catch the idea:

config/initializers/apotomo.rb

Apotomo::Widget.class_eval do
  def update_with_callback(*args)
    update(*args) + callback_function(*args)
  end

  def replace_with_callback(*args)
    replace(*args) + callback_function(*args)
  end

private
  def callback_function(*args)
    func_name = ((arg = args.last).instance_of?(Hash) && arg[:callback]) ||
      self.class.name.chomp('Widget').underscore

    "((typeof(Rails.Callbacks.#{func_name}) == 'function') && \
      Rails.Callbacks.#{func_name}('#{widget_id}'));"
 end
end
apotonick commented 13 years ago

Do you mean a :callback parameter for render/replace/update that has the same behaviour as in a Rails controller?

dre3k commented 13 years ago

Maybe it's better to name it "after update finalizers". I mean function that will be executed on client side after update/replace completed, like making tweets draggable again after tweets list got updated.

apotonick commented 13 years ago

Isn't that integrated in rails.js already, the before: and after: stuff which invokes a function after the AJAX request returns?!

dre3k commented 13 years ago

Yes, I can specify callback on ajax success when I add new tweet, then I need to specify another callback on ajax success when I delete tweet. And this callbacks will be the same. What I wanted is to add code to respond from server that will call this function automatically on client side. It's maybe a bad idea and it's maybe violates some OOP separation principles, I'm not sure. I just did't want to specify the same callback in many different places.

apotonick commented 13 years ago

If you're really talking about the :callback mechanics from Rails (used in JSON-P) you hit the nail - that's something we definitely need. Do you have a link where that stuff is explained?

dre3k commented 13 years ago

I'm not sure what the right way to implement that kind of functionality. I kind of asked for your opinion and advise.

I just find out about apotomo a couple days ago and I was going through Peter's Guide. But I was doing lessons by lesson in little bit different way. I didn't use :javascript part in haml views. So I have only markup in haml views. And in application.coffee(that gets compiled to application.js) I have all bindings, like bind on submit and bind on drop even. First in application.coffee I just had callbacks defined on ajax success on submit and drop event. Than I realized that this callbacks doing the exactly same thing. They make tweet list items draggable again after tweets list gets updated. So I'v extracted this functionality and put it in separate function named twitter under special namespace(in my case this is Rails.Callbacks). Then I'v made another instance method in Apotomo::Widget called #update_with_callback. This method use standard #update but appends string of javascript code that essentially calls the function of interest on client side. I don't define function itself, it's already defined in application.coffee(application.js). U can see definition of #update_with_callback in my original message.

For example response on submit or delete is something like this: $("#twitter").html("..."); ((typeof(Rails.Callbacks.twitter) == 'function') && Rails.Callbacks.twitter('twitter')); The name of the function to call is value of :callback option or if :callback omitted then by my convention it's derived from widget class name, the argument passed to this function is widget_id. Another example is when I click on heart image response is something like: $("#tweet-76").replaceWith("..."); ((typeof(Rails.Callbacks.tweet) == 'function') && Rails.Callbacks.tweet('tweet-76'));

So main idea is that I don't pass function definition within response, I just append js code necessary to invoke already defined function.

bobanj commented 13 years ago

I was having the same issue...using :callback => params[:callback]. Apotomo's render method is unaware of the :callback param, but including Rack::JSONP does the trick. Apotomo should not care about handling callbacks, it's middlewares job by my opinion. Apotomo's render method is not the same with rails render method.

dre3k commented 13 years ago

Hi Boban,

I got Rack::JSONP hooked up and callback works fine with regular controller #render. But I can't make Rack::JSONP wrap response from Apotomo's #render. I'v read your post http://www.boban.jovanoski.net/posts/2-standalone-widgets and there your are using this: def update text = params[:callback] + '(' + PostLink.all.to_json + ')' render :text => text, :content_type => 'application/json' end But could you please explain more how you managed Apotomo's #render and Rack::JSONP to work together.

bobanj commented 13 years ago

Hello dre3k We have talked about this issue and long story short: For now use the same sucky way as I've done rendering json with apotomo :S and will be fixed in future version.

jasonpvp commented 11 years ago

What about changing wrap_in_javascript_for to:

def wrap_in_javascript_for(mode, *args)
  selector  = args.first.is_a?(String) ? args.shift : false

  if args.first.is_a?(Hash) && args.first[:after_update]
    execute=args.first[:after_update]
    args.first.delete(:after_update)
  end
  content   = render(*args) 

  response = selector ? 
    Apotomo.js_generator.send(mode, selector, content) :    # replace(:twitter)
    Apotomo.js_generator.send("#{mode}_id", name, content)  # replace_id(:twitter)

  return response+execute.to_s

end
jarinudom commented 11 years ago

Hey, so I'm a little confused by this. Is there a way to send a JSONP callback and then also execute whatever Apotomo was going to do right out of the box?

For example, I have an image uploader that uploads and then re-renders the gallery, and it works fine. Now, I want to call a Javascript function (to display an alert or something) and then have it do the same thing as before. Is this where I should be looking, or is there a better way to do it?

apotonick commented 11 years ago

@jarinudom I guess you want something like the following code to be sent back to the browser, after the upload:

* jQuery code to re-render gallery
* jQuery code to display an alert

Does that really require JSONP? Or could that be handled using two widget states getting triggered by the same event?

jarinudom commented 11 years ago

Oh, I can do that with two widget states? Essentially I just need to call function and pass in a message from the Apotomo controller (something like "Photo uploaded").

Sorry if this is super easy, I'm super new to Apotomo.

apotonick commented 11 years ago

Sure, you can even do that in the same state, just concat what you want it to send back.

replace view: :whatever + "alert(..);"
jarinudom commented 11 years ago

Oh excellent, thanks! Looks like I will make my deadline after all ;)

apotonick commented 11 years ago

Feel free to bug us on #cells on IRC (especially if there's a deadline)!

jarinudom commented 11 years ago

Ok just for anyone reading this, it was actually this:

replace(view: :whatever) + "alert('Message!');"