cowboy / jquery-bbq

jQuery BBQ: Back Button & Query Library
http://benalman.com/projects/jquery-bbq-plugin/
GNU General Public License v2.0
1.18k stars 208 forks source link

Allow subscription to just a *param* in the hash changing #16

Open cowboy opened 14 years ago

cowboy commented 14 years ago

Justin's idea http://twitter.com/justinbmeyer/status/22037575805 and very cool.

jupiterjs commented 14 years ago

Yeah, I am no longer satisfied with JavaScriptMVC's approach to history, so I want to make bbq part of how history is used. I really like sammy's routing:

get('#/by_name/:name', function() { alert(this.params['name']); });

BUT!!!!

I think this would be even better if this was natural as listening to an event. I'd have to check if jQuery setup / init can do this, but:

$(window).bind("hashchange.bbq1", function(ev, params){

})

would be frankly disgustingly sick

cowboy commented 14 years ago

The API should be as jQuery-like as possible (and as BBQ-like as possible), so I'm thinking something like:

$(window).bind( 'hashchange', { param: 'a' }, function(event){
  // Only fires when fragment 'a' param changes

  var param = event.getParam(), // 'a' (NEW)
    param_val = event.getState( param ), // just like BBQ now
    param_val_coerced = event.getState( param, true ); // just like BBQ now
});

And this:

$(window).bind( 'hashchange', { params: [ 'a', 'b', 'c' ] }, function(event){
  // Only fires when any of fragment 'a', 'b' or 'c' param changes

  var param = event.getParam(), // 'a' or 'b' or 'c', depending (NEW)
    param_val = event.getState( param ), // just like BBQ now
    param_val_coerced = event.getState( param, true ); // just like BBQ now
});

In the latter, I think the handler should execute once for each param, so if both 'a' and 'b' change in a single hashchange event, the event handler will fire twice. Make sense?

I'm totally open to ideas. I'd really like to nail this out of the gate, though.

cowboy commented 14 years ago

Another idea that Paul Irish likes that's worth noting:

$(window).bind( 'hashchange', { param: 'a', value: 'x' }, function(event){
  // Only fires when the fragment changes, as long as the 'a' param
  // has a value of 'x'

  var param = event.getParam(), // 'a' (NEW)
    param_val = event.getState( param ); // 'x'

  // Do something with other, related, params.
});

This could be used to simulate routes (also, value could be a RegExp).

jupiterjs commented 14 years ago

Ah, I was thinking about that yesterday too. I was also thinking about 'fancy' routes.

"#/:a/options"

I'm not sure how that would work.

Also, what do you think about the event getting back a second argument which is params?

I suppose you don't always want to calculate it.

cowboy commented 14 years ago

I wouldn't add additional arguments to the callback, but instead add properties or methods to the already-passed event object. The param could be event.getParam() or event.param, whatever makes the most sense.

Enabling full-on routes could be handled by a more route-centric addon on top of BBQ, I don't think it should be in the core (at least, not for now).

jupiterjs commented 14 years ago

I always feel a little dirty adding expandos to events, even if they are jQuery.Event.

josephtate commented 14 years ago

I'm ok if you have an option to do routes style dispatching via an addon, however for large scale projects, object based dispatching works so much better. (I work on a project who's routes based dispatching builds a regexp 16KB long. Yuck.)

cowboy commented 14 years ago

Ok, what about this API idea?

var data;

data = 'a';         // triggered when param 'a' changes.
data = /.*/;        // triggered when hash (sans #) matches regexp.
data = { a: 'x' };  // triggered when param 'a' value == 'x'.
data = { a: /.*/ }; // triggered when param 'a' value matches regexp.

// Only if feasible (Q: handler gets triggered once for each match?)
data = [ 'a', 'b', 'c' ];           // triggered when either param 'a', 'b' or 'c' changes.
data = { a: 'x', b: 'y', c: /.*/ }; // does what you'd expect it to do.

$(window).bind( 'hashchange', data, function(event){

  event.data    // the data value passed in when binding.
  event.param   // the param that matched ( 'a' or 'b' or 'c' ).
  event.matches // if a regexp was used, this contains the result of the match.

  event.getState( event.param )       // the matching param's value.
  event.getState( event.param, true ) // the matching param's value (type coerced).
});
josephtate commented 14 years ago

For the data=['a', 'b', 'c'] and data={ a: 'x', b: 'y', c: /./ } cases, I would want a single event fired on a match of all params, and not once per each parameter that matches. For example, if I had a tab widget inside an accordion, I would want to set var 'a' for the state of the accordion, and var 'b' for the state of the tab. I'd use a data = {a: 'tabpage', b: /./} to subscribe to the hashchange event. Then if the accordion is on a page that the tab widget is not displayed on, then it doesn't get the event, but if if the accordion displays the "tabpage", any change in 'b' will trigger the event, but only once. It'd be a way to filter the events that are relevant.

All of that said, I'm having a hard time figuring out how that would work with JavascriptMVC where hashchange events are currently translated into a OpenAJAX hub event. This, I guess, is why Justin was pushing for a regex based routes system that uses a data type (string) that can be used directly as an OpenAJAX event name. However, I can imagine a scenario where you have thousands of regexes being run on every hash change, and worry about performance. Maybe jQuery/JSMVC event dispatching is somehow smarter than that, I don't know much about it.