/* TODO - Move ValueChangeNotifier to a different namespace? */
var ValueChangeNotifier = function(element, callback, options) {
	this.element  = $(element);
	this.callback = callback;
	this.value    = this.element.value;
	this.timer    = null;

	options        = options || {};
	this.delay     = options.delay || 0.3;
	this.minLength = options.minLength || 2;

	this.element.observe('keyup', this.elementChanged.bindAsEventListener(this));
};

ValueChangeNotifier.prototype.elementChanged = function(e) {
	if (this.element.value == this.value) { return; } // No actual change.
	this.value = this.element.value;

	if (this.value.length < this.minLength) { return; } // Value too short (so that too short values aren't autocompleted).

	if (this.timer) { window.clearTimeout(this.timer); }
	this.timer = this.callback.bind(this, e.keyCode).delay(this.delay);
};

/* TODO - Move Fragment to a different namespace? */
var Fragment = {
	// Fields
	previousFragments: null,
	pollInterval: 20,
	observers: [],

	// Methods
	updateFragment: function(key, value) {
		var fragments = this.getFragments();
		fragments[key] = value;
		this.setFragments(fragments);
	},
	getFragment: function(key) {
		return this.getFragments()[key];
	},
	removeFragment: function(key) {
		var fragments = this.getFragments();
		delete fragments[key];
		this.setFragments(fragments);
	},
	getFragments: function() {
		if (window.location.hash == '' || window.location.hash == '#') { return {}; }
		var fragments = {};
		var pairs = window.location.hash.substring(1).split('&');
		for (var i=0; i<pairs.length; i++) {
			var pair = pairs[i].split('=');
			fragments[pair[0]] = pair[1];
		}
		return fragments;
	},
	replaceFragments: function(key, value) {
		this.setFragments({ key: value });
	},
	setFragments: function(fragments) {
		var fragmentString = '';
		var first = true;
		for (var fragment in fragments) {
			if (fragments.hasOwnProperty(fragment)) {
				if (!first) { fragmentString += '&'; }
				first = false;
				fragmentString += fragment + '=' + fragments[fragment];
			}
		}
		window.location.hash = fragmentString;
	},
	pollFragmentChanges: function() {
		if (window.location.hash != '' && this.previousFragments != window.location.hash) {
			this.notifyObservers(this.getFragments());
			this.previousFragments = window.location.hash;
		}
	},
	notifyObservers: function(fragments) {
		for (var i=0; i<this.observers.length; i++) {
			this.observers[i](fragments);
		}
	},
	observe: function(callBackFn) {
		this.observers[this.observers.length] = callBackFn;
	},
	start: function() {
		setInterval(this.pollFragmentChanges.bind(this), this.pollInterval);
	}
};


/* Loop/Sortere Namespaces */
if (typeof Loop == 'undefined') { Loop = {}; }
if (typeof Loop.Sortere == 'undefined') { Loop.Sortere = {}; }
if (typeof Loop.Sortere.PluginTemplates == 'undefined') { Loop.Sortere.PluginTemplates = {}; }

Loop.Sortere.Plugin = function(element, optionsOverrides) {
	this.version             = '1.1';
	this.element             = element;
	this.querying            = false;
	this.queryData           = null;
	this.suggestAvfallData   = null;
	this.suggestionAvfallId  = null;
	this.hoveredSuggestionAvfall = -1;
	this.suggestKommuneData  = null;
	this.suggestionKommuneId = null;
	this.hoveredSuggestionKommune = -1;
	this.kommuneNavn         = '';
	this.breadcrumbs         = [];
	this.flashError          = false;

	this.options = {
		debug:               false,
		beskrivelseLengde:   200,
		sprakform:           'bokmal',
		type:                'privat',
		kommuneNr:           '',
		kommuneListe:        [],
		showKommune:         true,
		showMinimal:         false,
		startFocus:          true,
		useFlash:            true,
		urlPrefix:           'http://sortere.no/',
		kommuneInitUrl:      'http://sortere.no/c/portal/json_service?serviceClassName=no.bouvet.loop.sortere.db.service.http.KommuneServiceJSON&serviceMethodName=getKommuneByKommuneNr&serviceParameters=nr&nr=#{kommuneNr}',
		//kommuneInitUrl:      '/sortere/proxy/lookup.php?serviceClassName=no.bouvet.loop.sortere.db.service.http.KommuneServiceJSON&serviceMethodName=getKommuneByKommuneNr&serviceParameters=nr&nr=#{kommuneNr}',
		suggesterKommuneUrl: 'http://sortere.no/c/portal/json_service?serviceClassName=no.bouvet.loop.sortere.db.service.http.KommuneServiceJSON&serviceMethodName=searchByNavnOrPostStedNr&serviceParameters=query&query=#{query}',
		//suggesterKommuneUrl: '/sortere/proxy/lookup.php?serviceClassName=no.bouvet.loop.sortere.db.service.http.KommuneServiceJSON&serviceMethodName=searchByNavnOrPostStedNr&serviceParameters=query&query=#{query}',
		suggesterAvfallUrl:  'http://sortere.no/c/portal/json_service?serviceClassName=no.bouvet.loop.sortere.db.service.http.AvfallServiceJSON&serviceMethodName=searchAvfall&serviceParameters=query,type&query=#{query}&type=#{type}',
		//suggesterAvfallUrl:  '/sortere/proxy/lookup.php?serviceClassName=no.bouvet.loop.sortere.db.service.http.AvfallServiceJSON&serviceMethodName=searchAvfall&serviceParameters=query,type&query=#{query}&type=#{type}',
		searchUrl:           'http://sortere.no/dataservice?p_p_id=sortereexternaldata_WAR_sortereexternaldataportlet&p_p_lifecycle=2&p_p_cacheability=cacheLevelPage&_sortereexternaldata_WAR_sortereexternaldataportlet_client_version=#{version}&_sortereexternaldata_WAR_sortereexternaldataportlet_type=#{type}&_sortereexternaldata_WAR_sortereexternaldataportlet_kommune=#{kommune}&_sortereexternaldata_WAR_sortereexternaldataportlet_query=#{query}',
		//searchUrl:           '/sortere/proxy/query.php?p_p_id=sortereexternaldata_WAR_sortereexternaldataportlet&p_p_lifecycle=2&p_p_cacheability=cacheLevelPage&_sortereexternaldata_WAR_sortereexternaldataportlet_client_version=#{version}&_sortereexternaldata_WAR_sortereexternaldataportlet_type=#{type}&_sortereexternaldata_WAR_sortereexternaldataportlet_kommune=#{kommune}&_sortereexternaldata_WAR_sortereexternaldataportlet_query=#{query}',
		entityUrl:           'http://sortere.no/dataservice?p_p_id=sortereexternaldata_WAR_sortereexternaldataportlet&p_p_lifecycle=2&p_p_cacheability=cacheLevelPage&_sortereexternaldata_WAR_sortereexternaldataportlet_client_version=#{version}&_sortereexternaldata_WAR_sortereexternaldataportlet_type=#{type}&_sortereexternaldata_WAR_sortereexternaldataportlet_kommune=#{kommune}&_sortereexternaldata_WAR_sortereexternaldataportlet_avfall=#{entity}&_sortereexternaldata_WAR_sortereexternaldataportlet_id=#{entityId}',
		//entityUrl:           '/sortere/proxy/query.php?p_p_id=sortereexternaldata_WAR_sortereexternaldataportlet&p_p_lifecycle=2&p_p_cacheability=cacheLevelPage&_sortereexternaldata_WAR_sortereexternaldataportlet_client_version=#{version}&_sortereexternaldata_WAR_sortereexternaldataportlet_type=#{type}&_sortereexternaldata_WAR_sortereexternaldataportlet_kommune=#{kommune}&_sortereexternaldata_WAR_sortereexternaldataportlet_avfall=#{entity}&_sortereexternaldata_WAR_sortereexternaldataportlet_id=#{entityId}',
		queryUrl:            'http://sortere.no/guiden/sok_avfall_#{type}?p_p_id=sortere_frontend_guiden_search_avfall_#{type}&amp;p_p_lifecycle=1&amp;p_p_state=normal&amp;_sortere_frontend_guiden_search_avfall_#{type}_cmd=search&amp;_sortere_frontend_guiden_search_avfall_#{type}_avfall=#{query}&amp;_sortere_frontend_guiden_search_avfall_#{type}_struts_action=%2Fsortere%2Ffrontend%2Fguiden%2Fsearch%2Favfall%2Fsearch_avfall_#{type}&amp;_sortere_frontend_guiden_search_avfall_#{type}_kommuneNavn=#{kommuneNavn}',
		mapUrl:              'http://sortere.no/guiden/vis_kommune_avfalls_ordning_kart?p_r_p_-868126208_kommune_nr=#{kommuneNr}',
		miljogiftImgUrl:     'http://sortere.no/loop-sortere-public-theme/images/guiden/icons/miljogift.png',
		avfallstypeImgUrl:   'http://datamagi.no/sortere/type_icons/',
		unknownAtImgUrl:     'http://datamagi.no/sortere/type_icons/avfallstype_ukjent.png',
		formId:              'ls_search_form',
		kommuneId:           'ls_kommune_field_wrapper',
		searchFieldId:       'ls_search_field',
		kommuneFieldId:      'ls_kommune_field',
		buttonId:            'ls_search_button',
		suggesterId:         'ls_autosuggester',
		closeHandleId:       'ls_result_closehandle',
		selectableClassAT:   'ls_selectable_avfall',
		selectableClassKT:   'ls_selectable_kommune',
		selectedClass:       'ls_selected',
		oddClass:            'ls_odd',
		evenClass:           'ls_even',
		farligClass:         'ls_farlig_avfall',
		loadingId:           'ls_loading'
	};
	Object.extend(this.options, optionsOverrides || {});

	this.templates = {
		kommuneInitUrl:      new Template(this.options.kommuneInitUrl),
		suggesterAvfallUrl:  new Template(this.options.suggesterAvfallUrl),
		suggesterKommuneUrl: new Template(this.options.suggesterKommuneUrl),
		searchUrl:           new Template(this.options.searchUrl),
		entityUrl:           new Template(this.options.entityUrl),
		queryUrl:            new Template(this.options.queryUrl),
		mapUrl:              new Template(this.options.mapUrl)
	};
	for (var template in Loop.Sortere.PluginTemplates) {
		if (Loop.Sortere.PluginTemplates.hasOwnProperty(template)) {
			this.templates[template] = new Template(Loop.Sortere.PluginTemplates[template]);
		}
	}

	if (this.options.useFlash) {
		Ajax.flXHRproxy.registerOptions(this.options.urlPrefix, {
			autoUpdatePlayer: true,
			xmlResponseText: false,
			binaryResponseBody: false,
			instancePooling: true
		});
	}

	this.render(this.options.startFocus);
	this.initKommune();

	Fragment.observe(this.fragmentsUpdated.bind(this));
	Fragment.start();

	var _this = this;
	Ajax.Responders.register({
		onCreate:    function()    { $(_this.options.loadingId).show(); },
		onComplete:  function()    { $(_this.options.loadingId).hide(); },
		onException: function(r,e) { (function() { _this.onTransportException(r,e); }).defer(); }
	});
};

/* Handles flXHR errors. We assume that flXHR is loaded if this function is called. */
Loop.Sortere.Plugin.prototype.onTransportException = function(request, exception) {
	var t = request.transport;
	if (t && t.instanceId && t.instanceId.startsWith('flXHR')) {
		if (this.options.debug && typeof console != 'undefined') { console.log('An error happened in flXHR instance "' + t.instanceId + '": ' + exception.number + '/"' + exception.message + '"'); }
		if (exception.number == flensed.flXHR.PLAYER_VERSION_ERROR) {
			this.flashError = true;
			this.render(true);
			return;
		}
	}

	(function() { throw exception; }).defer(); // Default handling of exceptions is to throw them in another thread, so they'll be visible outside Prototype's XHR thread.
};

Loop.Sortere.Plugin.prototype.initKommune = function() {
	if (!this.options.kommuneNr) { return; }

	var url = this.templates.kommuneInitUrl.evaluate({ kommuneNr: encodeURI(this.options.kommuneNr) });
	if (this.options.debug && typeof console != 'undefined') { console.log('Requesting: ' + url); }
	new Ajax.Request(url, {
		method:    'get',
		onSuccess: Function.prototype.defer.bind(this.onInitKommuneSuccess.bind(this)),
		onFailure: Function.prototype.defer.bind(this.onInitKommuneFailure.bind(this)),
		evalJSON:  'force'
	});
};

Loop.Sortere.Plugin.prototype.onInitKommuneSuccess = function(response) {
	if (this.options.debug && typeof console != 'undefined') { console.log('Kommune-init succeeded.'); }
	if (response.responseJSON == null || response.responseJSON.length != 1) { return this.onInitKommuneFailure(response); }
	this.setKommuneNavn(response.responseJSON[0].navn);
	if (typeof response.transport.Destroy == 'function') { response.transport.Destroy(); }
};

Loop.Sortere.Plugin.prototype.onInitKommuneFailure = function(response) {
	if (this.options.debug && typeof console != 'undefined') { console.log('Kommune-init failed.'); }
	if (typeof response.transport.Destroy == 'function') { response.transport.Destroy(); }
};

Loop.Sortere.Plugin.prototype.getKommuneNavn = function() {
	return this.options.showKommune ? this.getKommuneFieldValue() : this.kommuneNavn;
};

Loop.Sortere.Plugin.prototype.setKommuneNavn = function(kommuneNavn) {
	this.kommuneNavn = kommuneNavn;
	if (this.options.showKommune) { this.setKommuneFieldValue(kommuneNavn); }
};

Loop.Sortere.Plugin.prototype.getSuggestAvfallDataLength = function() {
	if (this.suggestAvfallData == null) { return 0; }
	return this.suggestAvfallData.length;
};

Loop.Sortere.Plugin.prototype.getSuggestKommuneDataLength = function() {
	if (this.suggestKommuneData == null) { return 0; }
	return this.suggestKommuneData.length;
};

Loop.Sortere.Plugin.prototype.getSelectedSuggestionAvfall = function() {
	if (this.suggestAvfallData == null || this.suggestionAvfallId == null) { return null; }
	return this.suggestAvfallData[this.suggestionAvfallId];
};

Loop.Sortere.Plugin.prototype.getSelectedSuggestionKommune = function() {
	if (this.suggestKommuneData == null || this.suggestionKommuneId == null) { return null; }
	return this.suggestKommuneData[this.suggestionKommuneId];
};

Loop.Sortere.Plugin.prototype.useSelectedSuggestionAvfall = function() {
	var suggestion = this.getSelectedSuggestionAvfall();
	if (suggestion == null) { return; }
	this.setSearchFieldValue(this.getLocalizedValue(suggestion, 'navn'));
	this.focusOnSearchField(); // Might not be needed after tab, but is definitely needed after mouse clicks.
};

Loop.Sortere.Plugin.prototype.useSelectedSuggestionKommune = function() {
	var suggestion = this.getSelectedSuggestionKommune();
	if (suggestion == null) { return; }
	this.setKommuneNavn(suggestion.navn);
	this.focusOnKommuneField(); // Might not be needed after tab, but is definitely needed after mouse clicks.
};

Loop.Sortere.Plugin.prototype.selectSuggestionAvfall = function(suggestionId) {
	var previousId = this.suggestionAvfallId;
	this.suggestionAvfallId = suggestionId;
	this.renderSuggestionChangeAvfall(previousId);
};

Loop.Sortere.Plugin.prototype.selectSuggestionKommune = function(suggestionId) {
	var previousId = this.suggestionKommuneId;
	this.suggestionKommuneId = suggestionId;
	this.renderSuggestionChangeKommune(previousId);
};

Loop.Sortere.Plugin.prototype.selectNextSuggestionAvfall = function() {
	var previousId = this.suggestionAvfallId;
	this.suggestionAvfallId = (this.suggestionAvfallId == null) ? 0 : this.suggestionAvfallId+1;
	if (this.suggestionAvfallId >= this.getSuggestAvfallDataLength()) { this.suggestionAvfallId = null; }
	this.renderSuggestionChangeAvfall(previousId);
};

Loop.Sortere.Plugin.prototype.selectNextSuggestionKommune = function() {
	var previousId = this.suggestionKommuneId;
	this.suggestionKommuneId = (this.suggestionKommuneId == null) ? 0 : this.suggestionKommuneId+1;
	if (this.suggestionKommuneId >= this.getSuggestKommuneDataLength()) { this.suggestionKommuneId = null; }
	this.renderSuggestionChangeKommune(previousId);
};

Loop.Sortere.Plugin.prototype.selectPreviousSuggestionAvfall = function() {
	var previousId = this.suggestionAvfallId;
	this.suggestionAvfallId = (this.suggestionAvfallId == null) ? this.getSuggestAvfallDataLength()-1 : this.suggestionAvfallId-1;
	if (this.suggestionAvfallId < 0 || this.suggestionAvfallId >= this.getSuggestAvfallDataLength()) { this.suggestionAvfallId = null; }
	this.renderSuggestionChangeAvfall(previousId);
};

Loop.Sortere.Plugin.prototype.selectPreviousSuggestionKommune = function() {
	var previousId = this.suggestionKommuneId;
	this.suggestionKommuneId = (this.suggestionKommuneId == null) ? this.getSuggestKommuneDataLength()-1 : this.suggestionKommuneId-1;
	if (this.suggestionKommuneId < 0 || this.suggestionKommuneId >= this.getSuggestKommuneDataLength()) { this.suggestionKommuneId = null; }
	this.renderSuggestionChangeKommune(previousId);
};

Loop.Sortere.Plugin.prototype.hideSuggestions = function() {
	this.suggestionAvfallId  = null;
	this.suggestionKommuneId = null;
	$(this.options.suggesterId).hide();
};

Loop.Sortere.Plugin.prototype.focusOnSearchField = function() {
	$(this.options.searchFieldId).focus();
};

Loop.Sortere.Plugin.prototype.focusOnKommuneField = function() {
	$(this.options.kommuneFieldId).focus();
};

Loop.Sortere.Plugin.prototype.getSearchFieldValue = function() {
	return $(this.options.searchFieldId).value;
};

Loop.Sortere.Plugin.prototype.getKommuneFieldValue = function() {
	if (this.options.kommuneListe.length > 0) {
		return $(this.options.kommuneFieldId).selectedIndex > 0 ? $(this.options.kommuneFieldId).options[$(this.options.kommuneFieldId).selectedIndex].text : '';
	}
	return $(this.options.kommuneFieldId).value;
};

Loop.Sortere.Plugin.prototype.setSearchFieldValue = function(value) {
	$(this.options.searchFieldId).value = value;
};

Loop.Sortere.Plugin.prototype.setKommuneFieldValue = function(value) {
	if (this.options.kommuneListe.length > 0) {
		for (var i=0; i<$(this.options.kommuneFieldId).options.length; i++) {
			if ($(this.options.kommuneFieldId).options[i].text == value) {
				$(this.options.kommuneFieldId).selectedIndex = i;
				break;
			}
		}
		return;
	}
	$(this.options.kommuneFieldId).value = value;
};

Loop.Sortere.Plugin.prototype.onSearchFormClick = function(e) {
	this.focusOnSearchField();
};

Loop.Sortere.Plugin.prototype.onKommuneClick = function(e) {
	if (this.options.kommuneListe.length <= 0) { this.focusOnKommuneField(); }
	e.stop();
};

Loop.Sortere.Plugin.prototype.onSearchFieldBlur = function(e) {
	if (this.hoveredSuggestionAvfall == -1) { this.hideSuggestions(); }
};

Loop.Sortere.Plugin.prototype.onKommuneFieldBlur = function(e) {
	if (this.hoveredSuggestionKommune == -1) { this.hideSuggestions(); }
};

Loop.Sortere.Plugin.prototype.onSearchFieldChange = function(keyCode) {
	if ([9,13].indexOf(keyCode) > -1) { return; } // Don't react to selection of suggestions using tab/enter key (see onSearchFieldKeyDown function).
	if (!this.querying) {
		this.hideSuggestions();
		this.sendSuggestSearchAvfall();
	}
};

Loop.Sortere.Plugin.prototype.onKommuneFieldChange = function(keyCode) {
	if ([9,13].indexOf(keyCode) > -1) { return; } // Don't react to selection of suggestions using tab/enter key (see onKommuneFieldKeyDown function).
	if (!this.querying) {
		this.hideSuggestions();
		this.sendSuggestSearchKommune();
	}
};

Loop.Sortere.Plugin.prototype.onSearchFieldKeyDown = function(e) {
	switch (e.keyCode) {
	case 9:  // Tab
		e.stop();
		if (this.getSelectedSuggestionAvfall() != null) { this.useSelectedSuggestionAvfall(); } else { this.focusOnKommuneField(); }
		return this.hideSuggestions();
	case 13: // Enter
		e.stop();
		if (this.getSelectedSuggestionAvfall() != null) { this.useSelectedSuggestionAvfall(); } else { this.registerQuery(); }
		return this.hideSuggestions();
		case 27: e.stop(); return this.hideSuggestions();                // Escape
		case 38: e.stop(); return this.selectPreviousSuggestionAvfall(); // Up-arrow
		case 40: e.stop(); return this.selectNextSuggestionAvfall();     // Down-arrow
	}
};

Loop.Sortere.Plugin.prototype.onKommuneFieldKeyDown = function(e) {
	switch (e.keyCode) {
		case 9:  // Tab
			e.stop();
			if (this.getSelectedSuggestionKommune() != null) { this.useSelectedSuggestionKommune(); } else { this.focusOnSearchField(); }
			return this.hideSuggestions();
		case 13: // Enter
			e.stop();
			if (this.getSelectedSuggestionKommune() != null) { this.useSelectedSuggestionKommune(); } else { this.registerQuery(); }
			return this.hideSuggestions();
		case 27: e.stop(); return this.hideSuggestions();                 // Escape
		case 38: e.stop(); return this.selectPreviousSuggestionKommune(); // Up-arrow
		case 40: e.stop(); return this.selectNextSuggestionKommune();     // Down-arrow
	}
};

Loop.Sortere.Plugin.prototype.onSuggestAvfallMouseOver = function(e, index) {
	if (index == this.hoveredSuggestionAvfall) { return; }
	this.selectSuggestionAvfall(index);
	this.hoveredSuggestionAvfall = index;
};

Loop.Sortere.Plugin.prototype.onSuggestKommuneMouseOver = function(e, index) {
	if (index == this.hoveredSuggestionKommune) { return; }
	this.selectSuggestionKommune(index);
	this.hoveredSuggestionKommune = index;
};

Loop.Sortere.Plugin.prototype.onSuggestAvfallMouseOut = function(e, index) {
	this.hoveredSuggestionAvfall = -1;
};

Loop.Sortere.Plugin.prototype.onSuggestKommuneMouseOut = function(e, index) {
	this.hoveredSuggestionKommune = -1;
};

Loop.Sortere.Plugin.prototype.fragmentsUpdated = function(fragments) {
	if (this.options.showKommune) { this.setKommuneNavn(fragments.kommune ? fragments.kommune : ''); } // Pay attention to the kommune fragment only if the user is supposed to be able to change this.
	this.setSearchFieldValue(fragments.query ? fragments.query : '');

	if (fragments.produkttype) { // We are showing a specific produkttype
		this.requestEntity('produktType', fragments.produkttype);
	} else if (fragments.avfallstype) { // We are showing a specific avfallstype
		this.requestEntity('avfallsType', fragments.avfallstype);
	} else if (fragments.miljogift) { // We are showing a specific miljogift
		this.requestEntity('miljoGift', fragments.miljogift);
	} else if (fragments.query || fragments.kommune) { // We are doing a search
		this.sendQuery();
	}
};


Loop.Sortere.Plugin.prototype.encodeFragments = function(fragments) {
	var parts = [];
	for (var key in fragments) {
		if (fragments.hasOwnProperty(key)) {
			parts.push(key + '=' + fragments[key]);
		}
	}
	return parts.join('&amp;');
};

Loop.Sortere.Plugin.prototype.addBreadcrumb = function(fragments, text, hits) {
	if (fragments.query) { this.breadcrumbs = []; }

	var crumb = {
		link: '#' + this.encodeFragments(fragments),
		crumbText: text,
		hits: hits
	};
	
	var newCrumbs = [];
	for (var i=0; i<this.breadcrumbs.length; i++) {
		newCrumbs[i] = this.breadcrumbs[i];
		if (this.breadcrumbs[i].link == crumb.link) {
			this.breadcrumbs = newCrumbs;
			return;
		}
	}
	this.breadcrumbs[this.breadcrumbs.length] = crumb;
};

Loop.Sortere.Plugin.prototype.requestEntity = function(entity, id) {
	var url = this.templates.entityUrl.evaluate({ entity: encodeURI(entity), entityId: encodeURI(id), type: encodeURI(this.options.type.toLowerCase()), kommune: encodeURI(this.getKommuneNavn()), version: this.version });
	if (this.options.debug && typeof console != 'undefined') { console.log('Requesting: ' + url); }
	new Ajax.Request(url, {
		method:    'get',
		onSuccess: Function.prototype.defer.bind(this.onQuerySuccess.bind(this)),
		onFailure: Function.prototype.defer.bind(this.onQueryFailure.bind(this)),
		evalJSON:  'force'
	});
};

Loop.Sortere.Plugin.prototype.registerQuery = function() {
	var fragments = { query: this.getSearchFieldValue(), kommune: this.getKommuneNavn() };
	if (!fragments.query)   { delete fragments.query; }
	if (!fragments.kommune) { delete fragments.kommune; }
	Fragment.setFragments(fragments);
};

Loop.Sortere.Plugin.prototype.sendQuery = function() {
	var url = this.templates.searchUrl.evaluate({ type: encodeURI(this.options.type.toLowerCase()), query: encodeURI(this.getSearchFieldValue()), kommune: encodeURI(this.getKommuneNavn()), version: this.version });
	if (this.options.debug && typeof console != 'undefined') { console.log('Requesting: ' + url); }
	new Ajax.Request(url, {
		method:    'get',
		onSuccess: Function.prototype.defer.bind(this.onQuerySuccess.bind(this)),
		onFailure: Function.prototype.defer.bind(this.onQueryFailure.bind(this)),
		evalJSON:  'force'
	});
	this.querying = true;
};

Loop.Sortere.Plugin.prototype.onQuerySuccess = function(response) {
	this.querying = false;
	if (this.options.debug && typeof console != 'undefined') { console.log('Query succeeded.'); }
	if (response.responseJSON == null) { return this.onQueryFailure(response); }
	this.queryData = response.responseJSON;
	this.render(true);
	if (typeof response.transport.Destroy == 'function') { response.transport.Destroy(); }
};

Loop.Sortere.Plugin.prototype.onQueryFailure = function(response) {
	this.querying = false;
	if (this.options.debug && typeof console != 'undefined') { console.log('Query failed.'); }
	this.queryData = null;
	this.render(true);
	if (typeof response.transport.Destroy == 'function') { response.transport.Destroy(); }
};

Loop.Sortere.Plugin.prototype.sendSuggestSearchAvfall = function() {
	var url = this.templates.suggesterAvfallUrl.evaluate({ query: encodeURI('%' + this.getSearchFieldValue() + '%'), type: encodeURI(this.options.type.toUpperCase()) });
	if (this.options.debug && typeof console != 'undefined') { console.log('Requesting: ' + url); }
	new Ajax.Request(url, {
		method:    'get',
		onSuccess: Function.prototype.defer.bind(this.onSuggestAvfallSuccess.bind(this)),
		onFailure: Function.prototype.defer.bind(this.onSuggestAvfallFailure.bind(this)),
		evalJSON:  'force'
	});
};

Loop.Sortere.Plugin.prototype.onSuggestAvfallSuccess = function(response) {
	if (this.options.debug && typeof console != 'undefined') { console.log('Autosuggestion for avfall succeeded.'); }
	if (response.responseJSON == null) { return this.onSuggestAvfallFailure(response); }
	this.suggestAvfallData = response.responseJSON;
	this.renderSuggestionsAvfall();
	if (typeof response.transport.Destroy == 'function') { response.transport.Destroy(); }
};

Loop.Sortere.Plugin.prototype.onSuggestAvfallFailure = function(response) {
	if (this.options.debug && typeof console != 'undefined') { console.log('Autosuggestion for avfall failed.'); }
	this.suggestAvfallData = null;
	this.renderSuggestionsAvfall();
	if (typeof response.transport.Destroy == 'function') { response.transport.Destroy(); }
};

Loop.Sortere.Plugin.prototype.sendSuggestSearchKommune = function() {
	var url = this.templates.suggesterKommuneUrl.evaluate({ query: encodeURI('%' + this.getKommuneFieldValue() + '%') });
	if (this.options.debug && typeof console != 'undefined') { console.log('Requesting: ' + url); }
	new Ajax.Request(url, {
		method:    'get',
		onSuccess: Function.prototype.defer.bind(this.onSuggestKommuneSuccess.bind(this)),
		onFailure: Function.prototype.defer.bind(this.onSuggestKommuneFailure.bind(this)),
		evalJSON:  'force'
	});
};

Loop.Sortere.Plugin.prototype.onSuggestKommuneSuccess = function(response) {
	if (this.options.debug && typeof console != 'undefined') { console.log('Autosuggestion for kommune succeeded.'); }
	if (response.responseJSON == null) { return this.onSuggestKommuneFailure(response); }
	this.suggestKommuneData = response.responseJSON;
	this.renderSuggestionsKommune();
	if (typeof response.transport.Destroy == 'function') { response.transport.Destroy(); }
};

Loop.Sortere.Plugin.prototype.onSuggestKommuneFailure = function(response) {
	if (this.options.debug && typeof console != 'undefined') { console.log('Autosuggestion for kommune failed.'); }
	this.suggestKommuneData = null;
	this.renderSuggestionsKommune();
	if (typeof response.transport.Destroy == 'function') { response.transport.Destroy(); }
};

Loop.Sortere.Plugin.prototype.getLocalizedValue = function(object, property) {
	var valueNb = object[property + 'NoNb'];
	var valueNy = object[property + 'NoNy'];
	var value = (valueNb == null || valueNb == '' || (this.options.sprakform == 'nynorsk' && valueNy != null && valueNy != '')) ? valueNy : valueNb;
	return value == null ? '' : value;
};

Loop.Sortere.Plugin.prototype.getTipsBoksMarkup = function(object) {
	var t = object.tips;
	if (t.obs.length + t.visste.length + t.hva.length + t.lenke.length === 0) { return ''; }
	var tips = {};
	tips.obsTips         = this.getTipsMarkup(t.obs,    this.templates.obsTipsHeader);
	tips.vissteTips      = this.getTipsMarkup(t.visste, this.templates.vissteTipsHeader);
	tips.hvaTips         = this.getTipsMarkup(t.hva,    this.templates.hvaTipsHeader);
	tips.lenkeTips       = this.getTipsMarkup(t.lenke,  this.templates.lenkeTipsHeader);
	return this.templates.tipsBoks.evaluate(tips);
};

Loop.Sortere.Plugin.prototype.getTipsMarkup = function(tips, header) {
	return this.templates.tips.evaluate({ tips: this.getTipsListMarkup(tips, header) });
};

Loop.Sortere.Plugin.prototype.getTipsListMarkup = function(tips, header) {
	if (tips.length <= 0) { return ''; }
	var num = tips.length <= 3 ? tips.length : 3;
	var resultMarkup = header.evaluate({teller: num < tips.length ? this.templates.tipsTeller.evaluate({num: num, maks: tips.length}) : ''});
	if (tips.length > 3) {
		tips = this.randomizeArray(tips);
	}
	for (var i = 0; i < num; i++) {
		resultMarkup += this.templates.tip.evaluate({ body: this.getLocalizedValue(tips[i], 'body') });
	}
	return resultMarkup;
};

Loop.Sortere.Plugin.prototype.randomizeArray = function(array) {
	for (var i=0; i<array.length; i++) {
		var swapIndex = Math.floor(((array.length - i)* Math.random())) + i;
		var temp = array[i];
		array[i] = array[swapIndex];
		array[swapIndex] = temp;
	}
	return array;
};

Loop.Sortere.Plugin.prototype.getLocalizedAvfallsTypes = function(list, kommuneNavn) {
	if (!list) { return '???'; }

	var valueList = [];
	for (var i = 0; i < list.length; i++) {
		var type = list[i];
		if (type.type != (this.options.type == 'privat' ? 1 : 2)) { continue; }
		var name = this.getLocalizedValue(type, 'navn');
		type.farligClass = (type.kategori == 1 ? this.options.farligClass : '');
		var markup = "<a class='" + type.farligClass + "' href='#avfallstype=" + type.avfallsTypeId + (kommuneNavn ? "&amp;kommune=" + kommuneNavn : '') + "'>" + name + "</a>";
		valueList.push(markup);
	}
	return valueList.join(' eller ');
};

Loop.Sortere.Plugin.prototype.getLocalizedAvfallsTypeList = function(list, kommuneNavn, avfallstypeOverridenBy, mapLink) {
	if (!list) { return ''; }

	var resultMarkup = '';
	for (var i = 0; i < list.length; i++) {
		var type = list[i];
		if (type.type != (this.options.type == 'privat' ? 1 : 2)) { continue; }
		type.bilde       = this.getImageMarkup(type);
		type.navn        = this.getLocalizedValue(type, 'navn');
		type.overstyring = avfallstypeOverridenBy ? avfallstypeOverridenBy : kommuneNavn; // Hvis avfallstypen er overstyrt, hva er den overstyrt av?
		type.beskrivelse = avfallstypeOverridenBy ? this.templates.atOverriddenByPt.evaluate(type) :
			(this.isAvfallstypeOverridden(type.omradeList) ? this.templates.atOverriddenLocally.evaluate(type) : this.getLocalizedValue(type, 'beskrivelse'));
		type.ordninger   = kommuneNavn ? this.getOrdningerMarkup(type.omradeList, mapLink) : '';
		type.link        = '#avfallstype=' + type.avfallsTypeId + (kommuneNavn ? '&amp;kommune=' + kommuneNavn : '');
		resultMarkup += this.templates.atInProduktype.evaluate(type);
	}
	return resultMarkup;
};

Loop.Sortere.Plugin.prototype.getImageMarkup = function(object) {
	if (object.produktTypeId) {
		object.farlig = '';
		for (var i = 0; object.avfallsTypeList && i < object.avfallsTypeList.length; i++) {
			if (object.avfallsTypeList[i].kategori == 1) { object.farlig = ' ' + this.options.farligClass; }
		}
		if (object.bildeUrl == null) { object.bildeUrl = ''; }
		else if (!object.bildeUrl.startsWith('http')) { object.bildeUrl = this.options.urlPrefix + object.bildeUrl; }
		return this.templates.ptImage.evaluate(object);
	}
	if (object.miljoGiftId) {
		object.bildeUrl = this.options.miljogiftImgUrl;
		return this.templates.mgImage.evaluate(object);
	}
	if (object.avfallsTypeId) {
		if (object.bildeUrl == null || object.bildeUrl === '') { object.bildeUrl = this.options.unknownAtImgUrl; }
		else if (!object.bildeUrl.startsWith('http')) { object.bildeUrl = this.options.urlPrefix + object.bildeUrl; } // Use EITHER this OR the next line. Comment out the other. This line uses sortere.no's database images.
		//else { object.bildeUrl = this.options.avfallstypeImgUrl + 'avfallstype_' + object.avfallsTypeId + '.png'; } // This line uses our own ("fixed") images.
		return this.templates.atImage.evaluate(object);
	}
	return '';
};

Loop.Sortere.Plugin.prototype.getOrdningerMarkup = function(omrader, mapLink) {
	if (!omrader || this.ordningerIsEmpty(omrader)) { return ''; }
	var markup = '';
	for (var i=0; i<omrader.length; i++) {
		markup += this.getOmradeMarkup(omrader[i], mapLink);
	}
	return this.templates.ordninger.evaluate({ omrader: markup });
};

Loop.Sortere.Plugin.prototype.ordningerIsEmpty = function(omrader) {
	for (var i=0; i<omrader.length; i++) {
		if (omrader[i].henteOrdningList.length >= 1 || omrader[i].kompostOrdningList.length >= 1 || omrader[i].gjenvinningStasjonList.length >= 1 || omrader[i].punktOrdningList.length >= 1) { return false; }
	}
	return true;
};

Loop.Sortere.Plugin.prototype.getOmradeMarkup = function(omrade, mapLink) {
	if (!omrade) { return ''; }
	if (omrade.henteOrdningList.length + omrade.kompostOrdningList.length + omrade.gjenvinningStasjonList.length + omrade.punktOrdningList.length <= 0) { return ''; }
	omrade.navn = this.getLocalizedValue(omrade, 'navn');
	omrade.henteordninger = omrade.kompostordninger = omrade.gjenvinningsstasjoner = omrade.punktordninger = '';
	for (var i=0; i<omrade.henteOrdningList.length;       i++) { omrade.henteordninger        += this.getOrdningMarkup(this.templates.henteordning,        omrade.henteOrdningList[i],       mapLink); }
	for (var j=0; j<omrade.kompostOrdningList.length;     j++) { omrade.kompostordninger      += this.getOrdningMarkup(this.templates.kompostordning,      omrade.kompostOrdningList[j],     mapLink); }
	for (var k=0; k<omrade.gjenvinningStasjonList.length; k++) { omrade.gjenvinningsstasjoner += this.getOrdningMarkup(this.templates.gjenvinningsstasjon, omrade.gjenvinningStasjonList[k], mapLink); }
	for (var l=0; l<omrade.punktOrdningList.length;       l++) { omrade.punktordninger        += this.getOrdningMarkup(this.templates.punktordning,        omrade.punktOrdningList[l],       mapLink); }
	return this.templates.omrade.evaluate(omrade);
};

Loop.Sortere.Plugin.prototype.getOrdningMarkup = function(template, ordning, mapLink) {
	ordning.navn        = this.getLocalizedValue(ordning, 'navn');
	ordning.beskrivelse = this.getLocalizedValue(ordning, 'beskrivelse');
	ordning.handtering  = this.getLocalizedValue(ordning, 'handtering');
	ordning.hjemmeside  = this.makeLinkMarkup(this.getLocalizedValue(ordning, 'hjemmeside'));
	ordning.mapLink     = mapLink;
	return template.evaluate(ordning);
};

Loop.Sortere.Plugin.prototype.isAvfallstypeOverridden = function(omrader) {
	if (!omrader) { return false; }
	for (var i=0; i<omrader.length; i++) {
		var omrade = omrader[i];
		for (var j=0; j<omrade.henteOrdningList.length; j++) {
			if (omrade.henteOrdningList[j].handteringOverstyrerAlt) { return true; }
		}
		for (var k=0; k<omrade.kompostOrdningList.length; k++) {
			if (omrade.kompostOrdningList[k].handteringOverstyrerAlt) { return true; }
		}
		for (var l=0; l<omrade.gjenvinningStasjonList.length; l++) {
			if (omrade.gjenvinningStasjonList[l].handteringOverstyrerAlt) { return true; }
		}
		for (var m=0; m<omrade.punktOrdningList.length; m++) {
			if (omrade.punktOrdningList[m].handteringOverstyrerAlt) { return true; }
		}
	}
	return false;
};

Loop.Sortere.Plugin.prototype.makeLinkMarkup = function(url, description, target) {
	if (!url) { return ''; }
	if (!url.startsWith('http')) { url = 'http://' + url; }
	if (!description) { description = url; }
	if (!target) { target = '_blank'; }
	return "<a href='" + url + "' target='" + target + "'>" + description + "</a>";
};

Loop.Sortere.Plugin.prototype.clear = function(focusOnSearchField) {
	this.queryData   = null;
	this.flashError  = false;
	this.breadcrumbs = [];
	this.render(true);
};

Loop.Sortere.Plugin.prototype.render = function(focusOnSearchField) {
	var data = {};
	Object.extend(data, this.options);

	if (this.queryData) {
		var objData = null;

		data.hits        = this.queryData.hits;
		data.query       = this.queryData.query;
		data.kommuneNr   = this.queryData.kommune ? this.queryData.kommune.kommuneNr : '';
		data.kommuneNavn = this.queryData.kommune ? this.queryData.kommune.navn      : '';
		data.results     = '';

		var mapUrl     = data.kommuneNr ? this.templates.mapUrl.evaluate({ kommuneNr: encodeURI(data.kommuneNr) }) : '';
		data.mapLink   = data.kommuneNr ? this.templates.mapLink.evaluate({ url: mapUrl }) : '';

		var breadcrumb = data.query ? { query: data.query } : {};
		if (this.options.showKommune && data.kommuneNavn) { breadcrumb.kommune = data.kommuneNavn; }

		if (this.queryData.resultType == 'resultList') {
			var produkttypeMarkup = '', miljogiftMarkup = '', avfallstypeMarkup = '';
			var ptList = this.queryData.hits > 0 ? this.queryData.data.produktTypeList : [];
			if (ptList.length > 0) {
				produkttypeMarkup = this.templates.preProdukttype.evaluate({ hits: ptList.length, totalHits: data.hits });
				for (var i = 0; i < ptList.length; i++) {
					objData              = ptList[i];
					objData.navn         = this.getLocalizedValue(objData, 'navn');
					objData.beskrivelse  = this.getLocalizedValue(objData, 'beskrivelse').stripTags().truncate(this.options.beskrivelseLengde);
					objData.avfallstyper = this.getLocalizedAvfallsTypes(objData.avfallsTypeList, data.kommuneNavn);
					objData.bilde        = this.getImageMarkup(objData);
					objData.link         = '#produkttype=' + objData.produktTypeId + (data.kommuneNavn ? '&amp;kommune=' + data.kommuneNavn : '');
					objData.oddeven      = i % 2 ? this.options.evenClass : this.options.oddClass;
					produkttypeMarkup    += this.templates.produkttype.evaluate(objData);
				}
				produkttypeMarkup += this.templates.postProdukttype.evaluate({ hits: ptList.length, totalHits: data.hits });
			}

			var mgList = this.queryData.hits > 0 ? this.queryData.data.miljoGiftList : [];
			if (mgList.length > 0) {
				miljogiftMarkup = this.templates.preMiljogift.evaluate({ hits: mgList.length, totalHits: data.hits });
				for (var j = 0; j < mgList.length; j++) {
					objData             = mgList[j];
					objData.navn        = this.getLocalizedValue(objData, 'navn');
					objData.beskrivelse = this.getLocalizedValue(objData, 'beskrivelse').stripTags().truncate(this.options.beskrivelseLengde);
					objData.bilde       = this.getImageMarkup(objData);
					objData.link        = '#miljogift=' + objData.miljoGiftId + (data.kommuneNavn ? '&amp;kommune=' + data.kommuneNavn : '');
					objData.oddeven     = j % 2 ? this.options.evenClass : this.options.oddClass;
					miljogiftMarkup     += this.templates.miljogift.evaluate(objData);
				}
				miljogiftMarkup += this.templates.postMiljogift.evaluate({ hits: mgList.length, totalHits: data.hits });
			}

			var atList = this.queryData.hits > 0 ? this.queryData.data.avfallsTypeList : [];
			if (atList.length > 0) {
				avfallstypeMarkup = this.templates.preAvfallstype.evaluate({ hits: atList.length, totalHits: data.hits, atClass: this.options.atClass });
				for (var k = 0; k < atList.length; k++) {
					objData             = atList[k];
					objData.navn        = this.getLocalizedValue(objData, 'navn');
					objData.beskrivelse = this.getLocalizedValue(objData, 'beskrivelse').stripTags().truncate(this.options.beskrivelseLengde);
					objData.bilde       = this.getImageMarkup(objData);
					objData.link        = '#avfallstype=' + objData.avfallsTypeId + (data.kommuneNavn ? '&amp;kommune=' + data.kommuneNavn : '');
					objData.oddeven     = k % 2 ? this.options.evenClass : this.options.oddClass;
					avfallstypeMarkup   += this.templates.avfallstype.evaluate(objData);
				}
				avfallstypeMarkup += this.templates.postAvfallstype.evaluate({ hits: atList.length, totalHits: data.hits });
			}

			data.results = this.templates.resultList.evaluate({ 'produkttypeMarkup': produkttypeMarkup, 'miljogiftMarkup': miljogiftMarkup, 'avfallstypeMarkup': avfallstypeMarkup });
			this.addBreadcrumb(breadcrumb, data.query, data.hits);
		}

		else if (this.queryData.resultType == 'produktType') {
			objData                 = this.queryData.data;
			objData.navn            = this.getLocalizedValue(objData, 'navn');
			objData.beskrivelse     = this.getLocalizedValue(objData, 'beskrivelse');
			objData.avfallstyper    = this.getLocalizedAvfallsTypes(objData.avfallsTypeList, data.kommuneNavn);
			objData.avfallstypelist = this.getLocalizedAvfallsTypeList(objData.avfallsTypeList, data.kommuneNavn, (objData.beskrivelseOverstyrerAvfallsType ? objData.navn : ''), data.mapLink);
			objData.bilde           = this.getImageMarkup(objData);
			objData.tipsBoks        = this.getTipsBoksMarkup(objData);
			data.results            = this.templates.resultProdukttype.evaluate(objData);
			if (!breadcrumb.query) {
				data.query = objData.navn;
				breadcrumb.produkttype = objData.produktTypeId;
			}
			this.addBreadcrumb(breadcrumb, objData.navn, data.hits);
		}
		else if (this.queryData.resultType == 'miljoGift') {
			objData             = this.queryData.data;
			objData.navn        = this.getLocalizedValue(objData, 'navn');
			objData.beskrivelse = this.getLocalizedValue(objData, 'beskrivelse');
			objData.bilde       = this.getImageMarkup(objData);
			objData.tipsBoks    = this.getTipsBoksMarkup(objData);
			data.results        = this.templates.resultMiljogift.evaluate(objData);
			if (!breadcrumb.query) {
				data.query = objData.navn;
				breadcrumb.miljogift = objData.miljoGiftId;
			}
			this.addBreadcrumb(breadcrumb, objData.navn, data.hits);
		}
		else if (this.queryData.resultType == 'avfallsType') {
			objData             = this.queryData.data;
			objData.navn        = this.getLocalizedValue(objData, 'navn');
			objData.overstyring = data.kommuneNavn;
			objData.beskrivelse = this.isAvfallstypeOverridden(objData.omradeList) ? this.templates.atOverriddenLocally.evaluate(objData) : this.getLocalizedValue(objData, 'beskrivelse');
			objData.bilde       = this.getImageMarkup(objData);
			objData.tipsBoks    = this.getTipsBoksMarkup(objData);
			objData.ordninger   = this.getOrdningerMarkup(objData.omradeList, data.mapLink);
			data.results        = this.templates.resultAvfallstype.evaluate(objData);
			if (!breadcrumb.query) {
				data.query = objData.navn;
				breadcrumb.avfallstype = objData.avfallsTypeId;
			}
			this.addBreadcrumb(breadcrumb, objData.navn, data.hits);
		}
		else if (this.queryData.resultType == 'kommune') {
			objData            = this.queryData.kommune;
			objData.ordninger  = this.getOrdningerMarkup(objData.omradeList, data.mapLink);
			data.results       = this.templates.resultKommune.evaluate(objData);
			breadcrumb.kommune = objData.navn;
			this.addBreadcrumb(breadcrumb, objData.navn, data.hits);
		}
		else {
			data.results = this.templates.failure.evaluate(data);
		}

		if (this.breadcrumbs.length > 0) {
			var breadcrumbsMarkup = '';
			for (var l=0; l<this.breadcrumbs.length; l++) {
				breadcrumbsMarkup += (l === 0 ? this.templates.breadcrumbFirst : this.templates.breadcrumb).evaluate(this.breadcrumbs[l]);
			}
			data.breadcrumbsMarkup = this.templates.breadcrumbs.evaluate({ breadcrumbs: breadcrumbsMarkup });
		}

		var queryUrl   = data.query ? this.templates.queryUrl.evaluate({ query: encodeURI(data.query), kommuneNavn: encodeURI(data.kommuneNavn), type: encodeURI(this.options.type.toLowerCase()) }) : '';
		data.queryLink = data.query ? this.templates.queryLink.evaluate({ url: queryUrl }) : '';
	}

	if (this.flashError) {
		data.results = this.templates.flashError.evaluate(data);
	}

	if (this.queryData || this.flashError) {
		data.result  = this.templates.resultHead.evaluate(data);
		data.result += this.templates.resultBody.evaluate(data);
		data.result += this.templates.resultTail.evaluate(data);
	}

	data.kommuneOptions  = this.options.kommuneListe.length > 0 ? this.generateKommuneOptions() : '';
	data.kommuneField    = this.options.showKommune ? (this.options.kommuneListe.length > 0 ? this.templates.kommuneDropDown.evaluate(data) : this.templates.kommuneTextField.evaluate(data)) : '';
	data.form            = this.templates.form.evaluate(data);
	data.mainHeadMarkup  = this.templates.mainHead.evaluate(data);
	data.mainBodyMarkup  = this.templates.mainBody.evaluate(data);
	data.mainTailMarkup  = this.templates.mainTail.evaluate(data);
	data.suggesterMarkup = this.templates.suggester.evaluate(data);

	$(this.element).innerHTML = this.templates.master.evaluate(data);
	$(this.options.formId).observe('click', this.onSearchFormClick.bindAsEventListener(this));
	$(this.options.searchFieldId).observe('blur', this.onSearchFieldBlur.bindAsEventListener(this));
	$(this.options.searchFieldId).observe('keydown', this.onSearchFieldKeyDown.bindAsEventListener(this));
	$(this.options.buttonId).observe('click', this.registerQuery.bind(this));

	var closeHandle = $(this.options.closeHandleId);
	if (closeHandle) { closeHandle.observe('click', this.clear.bind(this)); }

	new ValueChangeNotifier(this.options.searchFieldId, this.onSearchFieldChange.bind(this));
	if (focusOnSearchField) { this.focusOnSearchField.bind(this).delay(0.25); }

	if (this.options.showKommune) {
		$(this.options.kommuneId).observe('click', this.onKommuneClick.bindAsEventListener(this));
		$(this.options.kommuneFieldId).observe('blur', this.onKommuneFieldBlur.bindAsEventListener(this));
		$(this.options.kommuneFieldId).observe('keydown', this.onKommuneFieldKeyDown.bindAsEventListener(this));
		new ValueChangeNotifier(this.options.kommuneFieldId, this.onKommuneFieldChange.bind(this));
	}

	// Clear suggestions.
	this.suggestAvfallData  = null;
	this.suggestionAvfallId = null;
	this.hoveredSuggestionAvfall = -1;
	this.suggestKommuneData  = null;
	this.suggestionKommuneId = null;
	this.hoveredSuggestionKommune = -1;
};

Loop.Sortere.Plugin.prototype.generateKommuneOptions = function() {
	var result = '';
	for (var i=0; i<this.options.kommuneListe.length; i++) {
		var kommune = this.options.kommuneListe[i];
		result += this.templates.htmlOption.evaluate({ text: kommune, selected: (this.kommuneNavn == kommune) ? " selected='selected'" : '' });
	}
	return result;
};

Loop.Sortere.Plugin.prototype.renderSuggestionsAvfall = function() {
	if (this.suggestAvfallData == null) { return; } // Fail silently.

	var data = {};
	Object.extend(data, this.options);
	data.hits = this.suggestAvfallData.length;
	data.suggestions = '';

	for (var i = 0; i < data.hits; i++) {
		var typeData = this.suggestAvfallData[i];
		typeData.navn = this.getLocalizedValue(typeData, 'navn');
		typeData.oddeven = i % 2 ? this.options.evenClass : this.options.oddClass;
		typeData.selectable = this.options.selectableClassAT + (i == this.suggestionAvfallId ? ' ' + this.options.selectedClass : '');
		data.suggestions += this.templates.suggestion.evaluate(typeData);
	}

	var form       = $(this.options.formId);
	var dimensions = form.getDimensions();
	var offsets    = form.positionedOffset();

	var suggester = $(this.options.suggesterId);
	suggester.innerHTML  = this.templates.suggestHead.evaluate(data);
	suggester.innerHTML += this.templates.suggestBody.evaluate(data);
	suggester.innerHTML += this.templates.suggestTail.evaluate(data);

	suggester.show();
	suggester.setStyle({
		top:   (offsets.top + dimensions.height) + 'px',
		left:  offsets.left + 'px',
		width: dimensions.width + 'px'
	});

	$$('.' + this.options.selectableClassAT).each(function(element, index) {
		element.id = this.options.selectableClassAT + '_' + index;
		element.observe('mouseover', this.onSuggestAvfallMouseOver.bindAsEventListener(this, index));
		element.observe('mouseout', this.onSuggestAvfallMouseOut.bindAsEventListener(this, index));
		element.observe('click', this.useSelectedSuggestionAvfall.bind(this));
	}.bind(this));
};

Loop.Sortere.Plugin.prototype.renderSuggestionsKommune = function() {
	if (this.suggestKommuneData == null) { return; } // Fail silently.

	var data = {};
	Object.extend(data, this.options);
	data.hits = this.suggestKommuneData.length;
	data.suggestions = '';

	for (var i = 0; i < data.hits; i++) {
		var typeData = this.suggestKommuneData[i];
		typeData.oddeven = i % 2 ? this.options.evenClass : this.options.oddClass;
		typeData.selectable = this.options.selectableClassKT + (i == this.suggestionKommuneId ? ' ' + this.options.selectedClass : '');
		data.suggestions += this.templates.suggestion.evaluate(typeData);
	}

	var kommune    = $(this.options.kommuneId);
	var dimensions = kommune.getDimensions();
	var offsets    = kommune.positionedOffset();

	var suggester = $(this.options.suggesterId);
	suggester.innerHTML  = this.templates.suggestHead.evaluate(data);
	suggester.innerHTML += this.templates.suggestBody.evaluate(data);
	suggester.innerHTML += this.templates.suggestTail.evaluate(data);

	suggester.show();
	suggester.setStyle({
		top:   (offsets.top + dimensions.height) + 'px',
		left:  offsets.left + 'px',
		width: dimensions.width + 'px'
	});

	$$('.' + this.options.selectableClassKT).each(function(element, index) {
		element.id = this.options.selectableClassKT + '_' + index;
		element.observe('mouseover', this.onSuggestKommuneMouseOver.bindAsEventListener(this, index));
		element.observe('mouseout', this.onSuggestKommuneMouseOut.bindAsEventListener(this, index));
		element.observe('click', this.useSelectedSuggestionKommune.bind(this));
	}.bind(this));
};

Loop.Sortere.Plugin.prototype.renderSuggestionChangeAvfall = function(previousId) {
	if (previousId != null)              { $(this.options.selectableClassAT + '_' + previousId).removeClassName(this.options.selectedClass); }
	if (this.suggestionAvfallId != null) { $(this.options.selectableClassAT + '_' + this.suggestionAvfallId).addClassName(this.options.selectedClass); }
};

Loop.Sortere.Plugin.prototype.renderSuggestionChangeKommune = function(previousId) {
	if (previousId != null)               { $(this.options.selectableClassKT + '_' + previousId).removeClassName(this.options.selectedClass); }
	if (this.suggestionKommuneId != null) { $(this.options.selectableClassKT + '_' + this.suggestionKommuneId).addClassName(this.options.selectedClass); }
};
