Skip to content

Bindings uri templates and uri query params

NickLeippe edited this page Feb 10, 2015 · 7 revisions

Use Case

You want to specify the target url in the href of an <a> tag but tie specific query parameters to observables. The alternatives are:

  • specify the href via the attr binding and build the entire url string by hand inline or via helper function
  • use the click binding instead which can be more verbose both inline and with extra routing code, which also precludes client-side history api routing benefits (at least not without extra, IMO superfluous work)

These handlers modify:

  • the href attribute on <a> tags
  • the src attribute on <img> tags
  • the action attribute on <form> tags

Dependencies

  • requires URI.js
  • does not require optional jquery pieces of URI.js
  • urlt requires the template functionality of URI.js--choose accordingly when building your download
  • could be rewritten to work with miuri.js and/or some other URI-template library without trouble

Example

<ul data-bind="foreach: mylist">
<li><a href="/page?foo=bar" data-bind="urlq: { id: id }">edit</a>
 <span data-bind="text: description"></span></li>
</ul>

Produces href="/page?foo=bar&id=123"

<ul data-bind="foreach: mylist">
<li><a href="/page/{foo}{?bar}" data-bind="urlt: { foo: fooValue, bar: barValue }">edit</a>
 <span data-bind="text: description"></span></li>
</ul>

Produces href="/page/abc?bar=xyz"

Code

/*****************************************************************************
 *
 *  helper used by both urlq and urlt binding handlers
 *
 ****************************************************************************/
function urlAttrForTagName(element) {
	//var tagName = ko.utils.tagNameLower(element);
	var tagName = element && element.tagName && element.tagName.toLowerCase();
	if (tagName == "img") {
		return 'src';
	} else if (tagName == "a") {
		return 'href';
	} else if (tagName == "form") {
		return 'action';
	} else {
		return;
	}
}
/*****************************************************************************
 *
 *  urlq KO binding handler
 *
 *  Purpose:
 *  Control a url's query parameters for href attribute of <a> tags,  src
 *  attribute of <img> tags, action attribute of <form> tags
 *
 *  Dependencies:
 *  Requires URI.js from: http://medialize.github.com/URI.js/
 *
 *  Usage:
 *  data-bind="urlq: { foo: fooValue, bar: barValue }"
 *
 *  Will synchronize ?foo=abc&bar=xyz portion of url in src/href attribute
 *  to the respective observables. (one-way binding)
 *
 *  Only replaces query parameters explicitly specified--any others in the
 *  attribute's original value are unchanged.
 *
 *  Interaction with an overlapping attr binding handler is undefined.
 *  I *think* it will always reapply after the attr handler makes changes,
 *  but am not sure.
 *
 *  Getting this to play nicely with attr: { href/src: } may be difficult.
 *
 ****************************************************************************/
ko.bindingHandlers.urlq = {
	update: function(element, valueAccessor) {
		var value;
		var paramName;
		var paramValue;
		var url;
		var attr = urlAttrForTagName(element);
		if (!attr) {
			return;
		}
		url   = URI(element.getAttribute(attr));
		value = ko.utils.unwrapObservable(valueAccessor()) || {};
		for (paramName in value) {
			if (typeof paramName != "string") {
				continue;
			}
			paramValue = ko.utils.unwrapObservable(value[paramName]);
			url.removeSearch(paramName);
			if (paramValue !== null &&
			    paramValue != undefined)
			{
				url.addSearch(paramName, paramValue);
			}
		}
		element.setAttribute(attr, url.toString());
	}
};

/*****************************************************************************
 *
 *  urlt KO binding handler
 *
 *  Purpose:
 *  Control a url's value using uri templates
 *  see: http://medialize.github.com/URI.js/uri-template.html
 *
 *  Dependencies:
 *  requires URI.js from: http://medialize.github.com/URI.js/ with the
 *  optional uri template features enabled
 *
 *  Usage:
 *  data-bind="urlt: { foo: fooValue, bar: barValue }"
 *
 *  Will synchronize src/href="page/{foo}{?bar}" -> page/abc?bar=xyz
 *  via the respective observables. (one-way binding)
 *
 *  Probably incompatible with overlapping attr binding handler (since this
 *  needs to save the original template--it would need to have its init
 *  called again if the original value got replaced, not just update)
 *
 *  Getting this to play nicely with attr: { href/src: } may be difficult, if
 *  possible at all.
 *
 ****************************************************************************/
var url_template_saved = '__ko__urlt_template_saved';
ko.bindingHandlers.urlt = {
	init: function(element) {
		var attr = urlAttrForTagName(element);
		if (!attr) {
			return;
		}
		element[url_template_saved] = element.getAttribute(attr);
	},
	update: function(element, valueAccessor) {
		var value;
		var attr = urlAttrForTagName(element);
		if (!attr) {
			return;
		}
		value = ko.toJS(ko.utils.unwrapObservable(valueAccessor()) || {});
		element.setAttribute(attr,
				     URI.expand(element[url_template_saved], value).toString());
	}
};