2 // functions for handling the path
4 var PATH_REPLACER = "([^\/]+)",
5 PATH_NAME_MATCHER = /:([\w\d]+)/g,
6 QUERY_STRING_MATCHER = /\?([^#]*)$/,
7 SPLAT_MATCHER = /(\*)/,
8 SPLAT_REPLACER = "(.+)",
13 function hashChanged() {
14 _currentPath = getPath();
15 // if path is actually changed from what we thought it was, then react
16 if (_lastPath != _currentPath) {
17 _lastPath = _currentPath;
18 return triggerOnPath(_currentPath);
25 begin : function(defaultPath) {
26 // this should trigger the defaultPath if there's not a path in the URL
27 // otherwise it should trigger the URL's path
29 var loadPath = getPath();
31 triggerOnPath(loadPath);
34 triggerOnPath(defaultPath);
42 currentPath : function() {
45 onChange : function (fun) {
46 $.pathbinder.changeFuns.push(fun);
50 function pollPath(every) {
51 function hashCheck() {
52 _currentPath = getPath();
53 // path changed if _currentPath != _lastPath
54 if (_lastPath != _currentPath) {
55 setTimeout(function() {
56 $(window).trigger('hashchange');
61 _pathInterval = setInterval(hashCheck, every);
62 $(window).bind('unload', function() {
63 clearInterval(_pathInterval);
67 function triggerOnPath(path) {
68 path = path.replace(/^#/,'');
69 $.pathbinder.changeFuns.forEach(function(fun) {fun(path)});
70 var pathSpec, path_params, params = {}, param_name, param;
71 for (var i=0; i < $.pathbinder.paths.length; i++) {
72 pathSpec = $.pathbinder.paths[i];
73 // $.log("pathSpec", pathSpec);
74 if ((path_params = pathSpec.matcher.exec(path)) !== null) {
75 // $.log("path_params", path_params);
77 for (var j=0; j < path_params.length; j++) {
78 param_name = pathSpec.param_names[j];
79 param = decodeURIComponent(path_params[j]);
81 params[param_name] = param;
83 if (!params.splat) params.splat = [];
84 params.splat.push(param);
87 pathSpec.callback(params);
88 // return true; // removed this to allow for multi match
95 if ('onhashchange' in window) {
96 // we have a native event
100 // setTimeout(hashChanged,50);
101 $(window).bind('hashchange', hashChanged);
104 function registerPath(pathSpec) {
105 $.pathbinder.paths.push(pathSpec);
108 function setPath(pathSpec, params) {
109 var newPath = $.mustache(pathSpec.template, params);
113 function goPath(newPath) {
115 // $.log("goPath", newPath)
116 window.location = '#'+newPath;
118 _lastPath = getPath();
122 var matches = window.location.toString().match(/^[^#]*(#.+)$/);
123 return matches ? matches[1] : '';
126 function makePathSpec(path, callback) {
127 var param_names = [];
130 PATH_NAME_MATCHER.lastIndex = 0;
132 while ((path_match = PATH_NAME_MATCHER.exec(path)) !== null) {
133 param_names.push(path_match[1]);
137 param_names : param_names,
138 matcher : new RegExp("^" + path.replace(
139 PATH_NAME_MATCHER, PATH_REPLACER).replace(
140 SPLAT_MATCHER, SPLAT_REPLACER) + "/?$"),
141 template : path.replace(PATH_NAME_MATCHER, function(a, b) {
143 }).replace(SPLAT_MATCHER, '{{splat}}'),
148 $.fn.pathbinder = function(name, paths, options) {
149 options = options || {};
150 var self = $(this), pathList = paths.split(/\n/);
151 $.each(pathList, function() {
154 // $.log("bind path", path);
155 var pathSpec = makePathSpec(path, function(params) {
156 // $.log("path cb", name, path, self)
157 // $.log("trigger path: "+path+" params: ", params);
158 self.trigger(name, [params]);
160 // set the path when the event triggered through other means
161 if (options.bindPath) {
162 self.bind(name, function(ev, params) {
163 params = params || {};
164 // $.log("set path", name, pathSpec)
165 setPath(pathSpec, params);
168 // trigger when the path matches
169 registerPath(pathSpec);