/* DX namespace */
if (typeof DX === 'undefined') { DX = {}; }

DX.emptyFunction = function() {};

DX.typeOf = function typeOf(value) {
    var type = typeof value;
	if (type === 'object') {
		if (!value) { return 'null'; }
		if (typeof value.length === 'number' && !(value.propertyIsEnumerable('length')) && typeof value.splice === 'function') { return 'array'; }
	}
	return type;
};

DX.supplant = String.prototype.supplant = function (supplantData) {
    return this.replace(/#{([^{}]*)}/g, function(fullMatch, subMatch) {
		var replacement = supplantData[subMatch];
		return (typeof replacement === 'string' || typeof replacement === 'number') ? replacement : fullMatch;
	});
};

DX.log = function(text) {
	if (typeof console !== 'undefined') { console.log(new Date().toLocaleString() + ' ' + text); }
};


/* DX.Fragment */

DX.Fragment = {
	// Fields
	previousFragments: null,
	pollInterval: 20,
	observers: [],

	// Methods
	updateFragment: function(key, value) {
		var keyAndValue = {};
		keyAndValue[key] = value;
		this.updateFragments(keyAndValue);
	},
	updateFragments: function(keysAndValues) {
		var fragments = this.getFragments();
		for (var key in keysAndValues) {
			var value = keysAndValues[key];
			if (value === null) { delete fragments[key]; }
			else { 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('=');
			var key   = decodeURIComponent(pair[0]);
			var value = decodeURIComponent(pair[1]);
			fragments[key] = value;
		}
		return fragments;
	},
	replaceFragments: function(key, value) {
		this.setFragments({ key: value });
	},
	setFragments: function(fragments) {
		var fragmentString = '';
		var first = true;
		for (var key in fragments) {
			if (fragments.hasOwnProperty(key)) {
				if (!first) { fragmentString += '&'; }
				first = false;
				fragmentString += encodeURIComponent(key) + '=' + encodeURIComponent(fragments[key]);
			}
		}
		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);
	}
};


/* DX.ValueChangeNotifier */

DX.ValueChangeNotifier = function(element, callback, optionsOverrides) {
	this.element   = $(element);
	this.callback  = callback;
	this.lastValue = this.element.value;
	this.timer     = null;

	this.options = {
		delay:     0.3,
		minLength: 2
	};
	Object.extend(this.options, optionsOverrides || {});

	this.element.observe('keyup', this.elementChanged.bindAsEventListener(this));
};

DX.ValueChangeNotifier.prototype.elementChanged = function(e) {
	if (this.element.value == this.lastValue) { return; } // No actual change.
	this.lastValue = this.element.value;

	if (this.element.value.length < this.options.minLength) { return; } // Value too short.

	if (this.timer) { window.clearTimeout(this.timer); }
	this.timer = this.checkLengthAndNotifyCallback.bind(this, e.keyCode).delay(this.options.delay);
};

DX.ValueChangeNotifier.prototype.checkLengthAndNotifyCallback = function(keyCode) {
	if (this.element.value.length < this.options.minLength) { return; }
	this.callback(keyCode);
};


/* DX.Autocomplete */

// Methods related to construction/initialization =>

DX.Autocomplete = function(formFieldElement, suggestionsElement, url, optionsOverrides) {
	this.formFieldElement   = $(formFieldElement);
	this.suggestionsElement = $(suggestionsElement);
	this.url                = url;

	this.options = {
		delay:           0.3,
		minLength:       2,
		parameterName:   'value',
		selectedClass:   'ac_selected',
		oddClass:        'ac_odd',
		evenClass:       'ac_even',
		evalJSON:        true,
		topAdjustment:   0,
		leftAdjustment:  0,
		widthAdjustment: 0,
		debug:           false
	};
	Object.extend(this.options, optionsOverrides || {});

	this.positionRelativeTo = $(this.options.positionRelativeTo)      || this.formFieldElement;
	this.getParameterValue  = this.options.getParameterValueCallback  || this.defaultGetParameterValueCallback;
	this.findSuggestions    = this.options.findSuggestionsCallback    || this.defaultFindSuggestionsCallback;
	this.render             = this.options.renderCallback             || this.defaultRenderCallback;
	this.renderSelection    = this.options.renderSelectionCallback    || this.defaultRenderSelectionCallback;
	this.renderSuggestions  = this.options.renderSuggestionsCallback  || this.defaultRenderSuggestionsCallback;
	this.getSuggestionId    = this.options.getSuggestionIdCallback    || this.defaultGetSuggestionIdCallback;
	this.getSuggestionValue = this.options.getSuggestionValueCallback || this.defaultGetSuggestionValueCallback;
	this.getSuggestionItem  = this.options.getSuggestionItemCallback  || this.defaultGetSuggestionItemCallback;
	this.getSuggestionsHead = this.options.getSuggestionsHeadCallback || this.defaultGetSuggestionsHeadCallback;
	this.getSuggestionsTail = this.options.getSuggestionsTailCallback || this.defaultGetSuggestionsTailCallback;
	this.useSuggestion      = this.options.useSuggestionCallback      || this.defaultUseSuggestionCallback;
	this.unhandledKeyDown   = this.options.unhandledKeyDownCallback   || DX.emptyFunction;

	this.templateItem = this.options.templateItem || "<div id='#{id}' class='dx_autocomplete_item #{oddeven}'>#{content}</div>";
	this.templateHead = this.options.templateHead || "<div class='dx_autocomplete_head'><div class='dx_autocomplete_head_inner'>#{content}</div></div><div class='dx_autocomplete_body'><div class='dx_autocomplete_body_inner'>";
	this.templateTail = this.options.templateTail || "</div></div><div class='dx_autocomplete_tail'><div class='dx_autocomplete_tail_inner'>#{content}</div></div>";

	new DX.ValueChangeNotifier(formFieldElement, this.requestSuggestions.bind(this), { delay: this.options.delay, minLength: this.options.minLength });

	this.reset(); // Initializes/resets suggestions-related data, see below.
	this.formFieldElement.observe('keydown', this.onElementKeyDown.bindAsEventListener(this));
	this.formFieldElement.observe('blur',    this.onElementBlur.bindAsEventListener(this));
};

DX.Autocomplete.prototype.reset = function() {
	this.searchValue   = null;
	this.suggestions   = null;
	this.selectedIndex = -1;
	this.hoveredIndex  = -1;
};

// Methods related to Ajax and rendering the response =>

DX.Autocomplete.prototype.requestSuggestions = function(keyCode) {
	if (keyCode == 13) { return; } // Don't react to selection of suggestions using enter key (see onElementKeyDown function).
	this.hideSuggestions(); // Hide previous suggestions while a new request is in progress.
	var parameters = {}; parameters[this.options.parameterName] = this.getParameterValue(this);
	if (this.options.debug && typeof console !== 'undefined') { console.log('Requesting: ' + this.url + ' with parameter: ' + this.options.parameterName + '=' + parameters[this.options.parameterName]); }
	new Ajax.Request(this.url, {
		method:     'get',
		parameters: parameters,
		onSuccess:  Function.prototype.defer.bind(this.handleSuccess.bind(this, this.formFieldElement.value)), // Defer to make exceptions happen outside the request thread.
		onFailure:  Function.prototype.defer.bind(this.handleFailure.bind(this, this.formFieldElement.value)), // Defer to make exceptions happen outside the request thread.
		evalJSON:   this.options.evalJSON
	});
};

DX.Autocomplete.prototype.defaultGetParameterValueCallback = function(autocomplete) {
	return this.formFieldElement.value;
};

DX.Autocomplete.prototype.handleSuccess = function(searchValue, response) {
	if (this.options.debug && typeof console !== 'undefined') { console.log('Autocompletion for the term "' + searchValue + '" succeeded.'); }
	this.reset();
	this.searchValue = searchValue;
	this.suggestions = this.findSuggestions(response);
	this.render(this, false);
	if (typeof response.transport.Destroy === 'function') { response.transport.Destroy(); } // For when flXHR is used.
};

DX.Autocomplete.prototype.handleFailure = function(searchValue, response) {
	if (this.options.debug && typeof console !== 'undefined') { console.log('Autocompletion for the term "' + searchValue + '" failed.'); }
	this.reset();
	this.searchValue = searchValue;
	this.render(this, false);
	if (typeof response.transport.Destroy === 'function') { response.transport.Destroy(); } // For when flXHR is used.
};

DX.Autocomplete.prototype.defaultRenderCallback = function(autocomplete, onlySelectionChange, previouslySelectedIndex) {
	if (onlySelectionChange) { this.renderSelection(this, previouslySelectedIndex); } else { this.renderSuggestions(this); }
};

DX.Autocomplete.prototype.defaultRenderSuggestionsCallback = function(autocomplete) {
	if (this.suggestions == null && this.options.debug && typeof console !== 'undefined') { return console.log('No suggestions to render.'); }

	var idList = [];
	var markup = this.templateHead.supplant({ content: this.getSuggestionsHead(this) });
	for (var i = 0; i < this.suggestions.length; i++) {
		var suggestion = this.suggestions[i];
		var id = this.getSuggestionId(suggestion, i);
		idList.push(id);
		var oddeven = i % 2 ? this.options.oddClass : this.options.evenClass;
		markup += this.templateItem.supplant({ id: id, content: this.getSuggestionItem(suggestion, this.searchValue), oddeven: oddeven });
	}
	markup += this.templateTail.supplant({ content: this.getSuggestionsTail(this) });
	this.suggestionsElement.update(markup);

	for (var i = 0; i < idList.length; i++) {
		id = idList[i];
		$(id).observe('click',     this.onSuggestionClick.bindAsEventListener(this, i));
		$(id).observe('mouseover', this.onSuggestionMouseOver.bindAsEventListener(this, i));
		$(id).observe('mouseout',  this.onSuggestionMouseOut.bindAsEventListener(this, i));
	}

	// TODO - Refactor the positioning code below into one or more separate DX functions (positionBelow(), etc.).
	var dimensions = this.positionRelativeTo.getDimensions();
	var offsets    = this.positionRelativeTo.positionedOffset();

	this.suggestionsElement.setStyle({
		position: 'absolute',
		top:      (offsets.top + dimensions.height + this.options.topAdjustment) + 'px',
		left:  	  (offsets.left + this.options.leftAdjustment) + 'px',
		width:    (dimensions.width + this.options.widthAdjustment) + 'px'
	});
	this.suggestionsElement.show();
};

DX.Autocomplete.prototype.defaultRenderSelectionCallback = function(autocomplete, previouslySelectedIndex) {
	var suggestion = this.getSelectedSuggestion();
	if (suggestion != null) {
		var suggestionId = this.getSuggestionId(suggestion, this.selectedIndex);
		$(suggestionId).addClassName(this.options.selectedClass);
	}

	suggestion = this.getSuggestion(previouslySelectedIndex);
	if (suggestion != null) {
		suggestionId = this.getSuggestionId(suggestion, previouslySelectedIndex);
		$(suggestionId).removeClassName(this.options.selectedClass);
	}
};

DX.Autocomplete.prototype.defaultGetSuggestionIdCallback = function(suggestion, index) {
	return this.suggestionsElement.id + '_index_' + index;
};

DX.Autocomplete.prototype.defaultGetSuggestionValueCallback = function(suggestion) {
	if (typeof suggestion === 'string') { return suggestion; }
	if (DX.typeOf(suggestion) === 'object') {
		if (suggestion.hasOwnProperty('value')) { return suggestion.value; }
		if (suggestion.hasOwnProperty('name'))  { return suggestion.name; }
		if (suggestion.hasOwnProperty('id'))    { return suggestion.id; }
	}
	return '';
};

DX.Autocomplete.prototype.defaultGetSuggestionItemCallback = function(suggestion, searchValue) {
	var exp = new RegExp('(' + searchValue + ')', 'i');
	return this.getSuggestionValue(suggestion).replace(exp, "<b>$1</b>");
};

DX.Autocomplete.prototype.defaultGetSuggestionsHeadCallback = function(autocomplete) {
	var suggestions = this.getSuggestionsLength();
	return "<b>" + suggestions + "</b> suggestion" + (suggestions == 1 ? '' : 's');
};

DX.Autocomplete.prototype.defaultGetSuggestionsTailCallback = function(autocomplete) {
	return "&nbsp;";
};

DX.Autocomplete.prototype.hideSuggestions = function() {
	this.selectedIndex = -1;
	this.suggestionsElement.hide();
};

// Methods related to the suggestion properties =>

DX.Autocomplete.prototype.defaultFindSuggestionsCallback = function(response) {
	return DX.typeOf(response.responseJSON) === 'array' ? response.responseJSON : null;
};

DX.Autocomplete.prototype.getSuggestionsLength = function() {
	return (this.suggestions == null) ? 0 : this.suggestions.length;
};

DX.Autocomplete.prototype.selectSuggestion = function(index) {
	if (index == this.selectedIndex) { return; }
	var previousIndex  = this.selectedIndex;
	this.selectedIndex = index;
	this.render(this, true, previousIndex);
};

DX.Autocomplete.prototype.getSuggestion = function(index) {
	if (this.suggestions == null || index <= -1 || index >= this.suggestions.length) { return null; }
	return this.suggestions[index];
};

DX.Autocomplete.prototype.getSelectedSuggestion = function() {
	return this.getSuggestion(this.selectedIndex);
};

DX.Autocomplete.prototype.getSelectedSuggestionValue = function() {
	var selectedSuggestion = this.getSelectedSuggestion();
	return (selectedSuggestion == null) ? null : this.getSuggestionValue(selectedSuggestion);
};

DX.Autocomplete.prototype.useSelectedSuggestion = function() {
	var selectedSuggestion = this.getSelectedSuggestion();
	if (selectedSuggestion != null) { this.useSuggestion(selectedSuggestion); }
	this.hideSuggestions();
};

DX.Autocomplete.prototype.defaultUseSuggestionCallback = function(selectedSuggestion) {
	this.formFieldElement.value = this.getSuggestionValue(selectedSuggestion);
};

DX.Autocomplete.prototype.selectNextSuggestion = function() {
	var newIndex = this.selectedIndex + 1;
	if (newIndex >= this.getSuggestionsLength()) { newIndex = -1; }
	this.selectSuggestion(newIndex);
};

DX.Autocomplete.prototype.selectPreviousSuggestion = function() {
	var newIndex = this.selectedIndex - 1;
	if (newIndex < -1) { newIndex = this.getSuggestionsLength() - 1; }
	this.selectSuggestion(newIndex);
};

// Methods related to ui event handling =>

DX.Autocomplete.prototype.onElementKeyDown = function(e) {
	switch (e.keyCode) {
		case 13: // Enter
			if (this.selectedIndex !== -1) { e.stop(); return this.useSelectedSuggestion(); }
			break;
		case 27: e.stop(); return this.hideSuggestions();          // Escape
		case 38: e.stop(); return this.selectPreviousSuggestion(); // Up-arrow
		case 40: e.stop(); return this.selectNextSuggestion();     // Down-arrow
	}
	this.unhandledKeyDown(e, this);
};

DX.Autocomplete.prototype.onElementBlur = function(e) {
	if (this.hoveredIndex == -1) { this.hideSuggestions(); }
};

DX.Autocomplete.prototype.onSuggestionMouseOver = function(e, index) {
	if (index == this.hoveredIndex) { return; }
	this.hoveredIndex = index;
	this.selectSuggestion(index);
};

DX.Autocomplete.prototype.onSuggestionMouseOut = function(e, index) {
	this.hoveredIndex = -1;
};

DX.Autocomplete.prototype.onSuggestionClick = function(e, index) {
	this.useSelectedSuggestion();
	this.focusOnFormField();
};

DX.Autocomplete.prototype.focusOnFormField = function() {
	this.formFieldElement.focus();
};


/* 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.4';
	this.element     = $(element);
	this.kommuneNavn = '';
	this.flashError  = false;
	this.queryData   = null;
	this.breadcrumbs = [];

	this.searchFieldAutocompleter  = null;
	this.kommuneFieldAutocompleter = null;

	this.options = {
		debug:               false,
		beskrivelseLengde:   200,
		sprakform:           'bokmal',
		type:                'privat',
		kommuneNr:           '',
		kommuneListe:        [],
		showKommune:         true,
		showMinimal:         false,
		queryOnEnter:        true,
		startFocus:          false,
		showSearchTip:       true,
		useFlash:            true,
		useRelativePath:     true,

		flashPrefix:         'http://sortere.no/',
		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}',
		suggesterKommuneUrl: 'http://sortere.no/c/portal/json_service?serviceClassName=no.bouvet.loop.sortere.db.service.http.KommuneServiceJSON&serviceMethodName=searchByNavnOrPostStedNr&serviceParameters=value',
		suggesterAvfallUrl:  'http://sortere.no/c/portal/json_service?serviceClassName=no.bouvet.loop.sortere.db.service.http.AvfallServiceJSON&serviceMethodName=searchAvfall&serviceParameters=value,type&type=#{type}',
		searchUrl:           'http://sortere.no/dataservice?p_p_id=sortereexternaldata_WAR_sortereexternaldataportlet&p_p_lifecycle=2&p_p_cacheability=cacheLevelPage&client_version=#{version}&type=#{type}&kommune=#{kommune}&query=#{query}',
		entityUrl:           'http://sortere.no/dataservice?p_p_id=sortereexternaldata_WAR_sortereexternaldataportlet&p_p_lifecycle=2&p_p_cacheability=cacheLevelPage&client_version=#{version}&type=#{type}&kommune=#{kommune}&avfall=#{entity}&id=#{entityId}',
		queryUrl:            'http://sortere.no/guiden/sok_avfall_#{type}?p_p_id=sortere_frontend_guiden_search_avfall_#{type}&p_p_lifecycle=1&p_p_state=normal&_sortere_frontend_guiden_search_avfall_#{type}_cmd=search&_sortere_frontend_guiden_search_avfall_#{type}_avfall=#{query}&_sortere_frontend_guiden_search_avfall_#{type}_struts_action=%2Fsortere%2Ffrontend%2Fguiden%2Fsearch%2Favfall%2Fsearch_avfall_#{type}&_sortere_frontend_guiden_search_avfall_#{type}_kommuneNavn=#{kommuneNavn}',
		miljogiftImgUrl:     'http://lt.sortere.no/images/avfallstype_miljogift.png',
		unknownAtImgUrl:     'http://lt.sortere.no/images/avfallstype_ukjent.png',
		mapUrl:              'http://lt.sortere.no/v/1.4.2/kart.html#lat=#{lat}&lng=#{lng}&zoom=#{zoom}&kommune=#{kommuneNavn}&avfallstypes=#{types}',

		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',
		oddClass:            'ls_odd',
		evenClass:           'ls_even',
		farligClass:         'ls_farlig_avfall',
		loadingId:           'ls_loading',
		blockedNotifierId:   'ls_blocked_notifier'
	};
	Object.extend(this.options, optionsOverrides || {});

	this.templates = {};
	for (var template in Loop.Sortere.PluginTemplates) {
		if (Loop.Sortere.PluginTemplates.hasOwnProperty(template)) {
			this.templates[template] = new Template(Loop.Sortere.PluginTemplates[template]);
		}
	}

	// Overstyring ifm. minimal visning (kommunefelt vises ikke, så evt. kommune-data fjernes)
	if (this.options.showMinimal) {
		this.options.showKommune = false;
		this.options.kommuneListe = [];
	};

	if (this.options.useFlash) {
		Ajax.flXHRproxy.registerOptions(this.options.flashPrefix, {
			autoUpdatePlayer: true,
			xmlResponseText: false,
			binaryResponseBody: false,
			instancePooling: true
		});
	}

	this.render(this.options.startFocus);
	this.initKommune();

	DX.Fragment.observe(this.fragmentsUpdated.bind(this));
	DX.Fragment.start();

	Ajax.Responders.register({
		onCreate:    function()    { $(this.options.loadingId).show(); }.bind(this),
		onComplete:  function()    { $(this.options.loadingId).hide(); }.bind(this),
		onException: Function.prototype.defer.bind(this.onAjaxException.bind(this))
	});
};

/* Handles flXHR errors. We assume that flXHR is loaded if this function is called. */
Loop.Sortere.Plugin.prototype.onAjaxException = 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.options.kommuneInitUrl.supplant({ kommuneNr: encodeURIComponent(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(); } // For when flXHR is used.
};

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(); } // For when flXHR is used.
};

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.getKommuneFieldValue = function() {
	if (this.options.kommuneListe.length > 0) {
		return $(this.options.kommuneFieldId).selectedIndex > 0 ? $(this.options.kommuneFieldId).options[$(this.options.kommuneFieldId).selectedIndex].text : '';
	}
	if (!this.options.showKommune) { return ''; }

	var value = $(this.options.kommuneFieldId).value;
	if (value == Loop.Sortere.PluginTemplates.kommuneTip) { return ''; }
	return 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.getSearchFieldValue = function() {
	var value = $(this.options.searchFieldId).value;
	if (value == Loop.Sortere.PluginTemplates.searchTip) { return ''; }
	return value;
};

Loop.Sortere.Plugin.prototype.setSearchFieldValue = function(value) {
	$(this.options.searchFieldId).value = value;
};


Loop.Sortere.Plugin.prototype.onSearchFormClick = function(e) {
	this.focusOnSearchField();
};

Loop.Sortere.Plugin.prototype.onKommuneClick = function(e) {
	e.stop(); // Prevents click from bubbling up to the search form, triggering a click event there too (which changed the focus to the search field again).
	this.focusOnKommuneField();
};

Loop.Sortere.Plugin.prototype.focusOnSearchField = function() {
	$(this.options.searchFieldId).focus();
};

Loop.Sortere.Plugin.prototype.focusOnKommuneField = function() {
	if (this.options.showKommune) { $(this.options.kommuneFieldId).focus(); }
};

Loop.Sortere.Plugin.prototype.onSearchFieldKeyDown = function(e) {
	if (e.keyCode === 9) { e.stop(); return this.focusOnKommuneField(); } // Tab
};

Loop.Sortere.Plugin.prototype.onKommuneFieldKeyDown = function(e) {
	if (e.keyCode === 9) { e.stop(); return this.focusOnSearchField(); } // Tab
};

Loop.Sortere.Plugin.prototype.unhandledKeyDown = function(e, autocomplete) {
	if (e.keyCode === 13 && this.options.queryOnEnter) { this.registerQuery(); } // Enter
};

Loop.Sortere.Plugin.prototype.registerQuery = function() {
	if (this.options.showMinimal) { return this.doMinimalQuery(); }

	var fragments = { query: this.getSearchFieldValue(), kommune: this.getKommuneNavn() };
	if (!fragments.query)   { delete fragments.query; }
	if (!fragments.kommune) { delete fragments.kommune; }
	DX.Fragment.setFragments(fragments);
};

Loop.Sortere.Plugin.prototype.doMinimalQuery = function() {
	window.top.location.href = this.getMinimalLinkHref();
	/* 2010-04-21: Changed to the simpler behaviour above, by request from LOOP.
	var url = this.setMinimalLinkHref(); // Useful if the popup window is bloked.
	var sortereWindow = window.open(url, 'sortereWindow');
	if (!sortereWindow || !sortereWindow.open) {
		var el = $(this.options.blockedNotifierId);
		el.show();
		el.clonePosition($(this.options.buttonId), {
			setWidth: false,
			setHeight: false,
			offsetTop: $(this.options.buttonId).getHeight(),
			offsetLeft: -154 + $(this.options.buttonId).getWidth()
		});
	}
	*/
};

Loop.Sortere.Plugin.prototype.getMinimalLinkHref = function() {
	return this.options.queryUrl.supplant({ query: this.getSearchFieldValue(), kommuneNavn: this.getKommuneNavn(), type: this.options.type });
};

Loop.Sortere.Plugin.prototype.setMinimalLinkHref = function() {
	return $(this.options.buttonId).href = this.getMinimalLinkHref();
};

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.requestEntity = function(entity, id) {
	var url = this.options.entityUrl.supplant({ entity: encodeURIComponent(entity), entityId: encodeURIComponent(id), type: encodeURIComponent(this.options.type.toLowerCase()), kommune: encodeURIComponent(this.getKommuneNavn()), version: encodeURIComponent(this.version) }) + '&origin=' + encodeURIComponent(window.location.hostname);
	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.sendQuery = function() {
	var url = this.options.searchUrl.supplant({ type: encodeURIComponent(this.options.type.toLowerCase()), query: encodeURIComponent(this.getSearchFieldValue()), kommune: encodeURIComponent(this.getKommuneNavn()), version: encodeURIComponent(this.version) }) + '&origin=' + encodeURIComponent(window.location.hostname);
	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.onQuerySuccess = function(response) {
	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(); } // For when flXHR is used.
};

Loop.Sortere.Plugin.prototype.onQueryFailure = function(response) {
	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(); } // For when flXHR is used.
};


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='" + this.createLink("#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, mapOptions) {
	if (!list) { return ''; }
	mapOptions.atypes = list.pluck('avfallsTypeId').join(',');

	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, mapOptions) : '';
		type.link        = this.createLink('#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; }
		return this.templates.atImage.evaluate(object);
	}
	return '';
};

Loop.Sortere.Plugin.prototype.getOrdningerMarkup = function(omrader, mapOptions) {
	if (!omrader || this.ordningerIsEmpty(omrader)) { return ''; }
	var markup = '';
	for (var i=0; i<omrader.length; i++) {
		markup += this.getOmradeMarkup(omrader[i], mapOptions);
	}
	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, mapOptions) {
	if (!omrade) { return ''; }
	if (omrade.henteOrdningList.length + omrade.kompostOrdningList.length + omrade.gjenvinningStasjonList.length + omrade.punktOrdningList.length <= 0) { return ''; }
	mapOptions.omradeLat = omrade.breddeGradWgs84 || mapOptions.kommuneLat;
	mapOptions.omradeLng = omrade.lengdeGradWgs84 || mapOptions.kommuneLng;
	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],       mapOptions); }
	for (var j=0; j<omrade.kompostOrdningList.length;     j++) { omrade.kompostordninger      += this.getOrdningMarkup(this.templates.kompostordning,      omrade.kompostOrdningList[j],     mapOptions); }
	for (var k=0; k<omrade.gjenvinningStasjonList.length; k++) { omrade.gjenvinningsstasjoner += this.getOrdningMarkup(this.templates.gjenvinningsstasjon, omrade.gjenvinningStasjonList[k], mapOptions); }
	for (var l=0; l<omrade.punktOrdningList.length;       l++) { omrade.punktordninger        += this.getOrdningMarkup(this.templates.punktordning,        omrade.punktOrdningList[l],       mapOptions); }
	return this.templates.omrade.evaluate(omrade);
};

Loop.Sortere.Plugin.prototype.getOrdningMarkup = function(template, ordning, mapOptions) {
	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     = this.getMapLink(ordning, mapOptions);
	return template.evaluate(ordning);
};

Loop.Sortere.Plugin.prototype.getMapLink = function(ordning, mapOptions) {
	if (!mapOptions.kommuneNavn) { return ''; } // Har ikke valgt sted (kommune).
	if (mapOptions.omradeLat && mapOptions.omradeLng) {
		mapOptions.lat = encodeURIComponent(mapOptions.omradeLat);
		mapOptions.lng = encodeURIComponent(mapOptions.omradeLng);
		mapOptions.zoom  = 12;
	}
	if (ordning.antallPunkter >= 1) {
		// Punktordning - la områdets/kommunens koordinater stå
		mapOptions.types = encodeURIComponent(mapOptions.atypes || ordning.types);
	} else if (ordning.breddeGradWgs84 && ordning.breddeGradWgs84 != 0.0 && ordning.lengdeGradWgs84 && ordning.lengdeGradWgs84 != 0.0) {
		// Gjenvinningsstasjon - bruke stasjonens egne koordinater
		if (ordning.breddeGradWgs84 && ordning.lengdeGradWgs84) {
			mapOptions.lat  = encodeURIComponent(ordning.breddeGradWgs84);
			mapOptions.lng  = encodeURIComponent(ordning.lengdeGradWgs84);
			mapOptions.zoom = 14;
		}
		mapOptions.types = encodeURIComponent(mapOptions.atypes || 'default');
	} else {
		return '';
	}
	return this.templates.mapLink.evaluate({ url: this.options.mapUrl.supplant(mapOptions) });
};


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 = '_top'; }
	return "<a href='" + url + "' target='" + target + "'>" + description + "</a>";
};

Loop.Sortere.Plugin.prototype.renderMinimal = function(focusOnSearchField) {
	var data = {};
	Object.extend(data, this.options);

	data.form            = this.templates.minimalForm.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.buttonId).observe('click', $(this.options.blockedNotifierId).hide.bind($(this.options.blockedNotifierId))); // Commented out due to the changes in doMinimalQuery.

	$(this.options.formId).observe('click', this.onSearchFormClick.bindAsEventListener(this));
	$(this.options.searchFieldId).observe('blur', function (e) { this.setMinimalLinkHref(); }.bindAsEventListener(this));
	this.createSearchFieldAutocompleter();

	if (focusOnSearchField) { this.focusOnSearchField.bind(this).delay(0.25); }

	if (this.options.showSearchTip) {
		$(this.options.searchFieldId).value = $(this.options.searchFieldId).value || Loop.Sortere.PluginTemplates.searchTip;
		$(this.options.searchFieldId).observe('focus', function() { if($(this.options.searchFieldId).value == Loop.Sortere.PluginTemplates.searchTip) { $(this.options.searchFieldId).value = ''; } }.bind(this));
		$(this.options.searchFieldId).observe('blur',  function() { if($(this.options.searchFieldId).value == '') { $(this.options.searchFieldId).value = Loop.Sortere.PluginTemplates.searchTip; } }.bind(this));
	}
};

Loop.Sortere.Plugin.prototype.render = function(focusOnSearchField) {
	if (this.options.showMinimal) { return this.renderMinimal(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 mapOptions = { lat: 'default', lng: 'default', zoom: 'default', kommuneNavn: encodeURIComponent(data.kommuneNavn), kommuneLat: this.queryData.kommune.breddeGradWgs84, kommuneLng: this.queryData.kommune.lengdeGradWgs84 };
		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 = data.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         = this.createLink('#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 = data.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        = this.createLink('#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 = data.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        = this.createLink('#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 : ''), mapOptions);
			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);
			mapOptions.atypes   = objData.avfallsTypeId;
			objData.ordninger   = this.getOrdningerMarkup(objData.omradeList, mapOptions);
			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, mapOptions);
			data.results       = this.templates.resultKommune.evaluate(objData);
			breadcrumb.kommune = objData.navn;
			this.addBreadcrumb(breadcrumb, objData.navn, data.hits);
		}
		else if (this.queryData.resultType == 'feil') {
			data.results = this.templates.errorMsg.evaluate({ message: this.queryData.message });
		} 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.options.queryUrl.supplant({ query: encodeURIComponent(data.query), kommuneNavn: encodeURIComponent(data.kommuneNavn), type: encodeURIComponent(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.buttonId).observe('click', this.registerQuery.bind(this));

	$(this.options.formId).observe('click', this.onSearchFormClick.bindAsEventListener(this));
	$(this.options.searchFieldId).observe('keydown', this.onSearchFieldKeyDown.bindAsEventListener(this));
	this.createSearchFieldAutocompleter();

	if (this.options.showKommune) {
		$(this.options.kommuneId).observe('click', this.onKommuneClick.bindAsEventListener(this));
		$(this.options.kommuneFieldId).observe('keydown', this.onKommuneFieldKeyDown.bindAsEventListener(this));
		this.createKommuneFieldAutocompleter();
	}

	if (focusOnSearchField) { this.focusOnSearchField.bind(this).delay(0.25); }

	if (this.options.showSearchTip) {
		$(this.options.searchFieldId).value = $(this.options.searchFieldId).value || Loop.Sortere.PluginTemplates.searchTip;
		$(this.options.searchFieldId).observe('focus', function() { if($(this.options.searchFieldId).value == Loop.Sortere.PluginTemplates.searchTip) { $(this.options.searchFieldId).value = ''; } }.bind(this));
		$(this.options.searchFieldId).observe('blur',  function() { if($(this.options.searchFieldId).value == '') { $(this.options.searchFieldId).value = Loop.Sortere.PluginTemplates.searchTip; } }.bind(this));
	}
	if (this.options.showSearchTip && this.options.showKommune && this.options.kommuneListe.length == 0) {
		$(this.options.kommuneFieldId).value = $(this.options.kommuneFieldId).value || Loop.Sortere.PluginTemplates.kommuneTip;
		$(this.options.kommuneFieldId).observe('focus', function() { if($(this.options.kommuneFieldId).value == Loop.Sortere.PluginTemplates.kommuneTip) { $(this.options.kommuneFieldId).value = ''; } }.bind(this));
		$(this.options.kommuneFieldId).observe('blur',  function() { if($(this.options.kommuneFieldId).value == '') { $(this.options.kommuneFieldId).value = Loop.Sortere.PluginTemplates.kommuneTip; } }.bind(this));
	}

	var closeHandle = $(this.options.closeHandleId);
	if (closeHandle) { closeHandle.observe('click', this.clear.bind(this)); }
};

Loop.Sortere.Plugin.prototype.clear = function() {
	this.queryData   = null;
	this.flashError  = false;
	this.breadcrumbs = [];
	this.render(true);
};

Loop.Sortere.Plugin.prototype.createLink = function(hash) {
	if (this.options.useRelativePath) { return hash; }
	return window.location.protocol + '//' + window.location.hostname + window.location.pathname + window.location.search + hash;
};

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.addBreadcrumb = function(fragments, text, hits) {
	if (fragments.query) { this.breadcrumbs = []; }

	var crumb = {
		link: this.createLink('#' + 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.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.createSearchFieldAutocompleter = function() {
	var url = this.options.suggesterAvfallUrl.supplant({ type: encodeURIComponent(this.options.type.toUpperCase()) });
	this.searchFieldAutocompleter = new DX.Autocomplete(this.options.searchFieldId, this.options.suggesterId, url, {
		positionRelativeTo: this.options.formId,
		getParameterValueCallback: function(autocomplete) { return '%' + autocomplete.formFieldElement.value + '%'; },
		getSuggestionValueCallback: function(suggestion) { return this.getLocalizedValue(suggestion, 'navn'); }.bind(this),
		getSuggestionsHeadCallback: function(autocomplete) { return Loop.Sortere.PluginTemplates.suggestHead.supplant({ hits: autocomplete.getSuggestionsLength() }); },
		unhandledKeyDownCallback: this.unhandledKeyDown.bind(this),

		templateHead: '#{content}',
		templateTail: Loop.Sortere.PluginTemplates.suggestTail,
		templateItem: Loop.Sortere.PluginTemplates.suggestion,
		evalJSON: 'force'
	});
};

Loop.Sortere.Plugin.prototype.createKommuneFieldAutocompleter = function() {
	var url = this.options.suggesterKommuneUrl;
	this.searchFieldAutocompleter = new DX.Autocomplete(this.options.kommuneFieldId, this.options.suggesterId, url, {
		positionRelativeTo: this.options.kommuneId,
		getParameterValueCallback: function(autocomplete) { return '%' + autocomplete.formFieldElement.value + '%'; },
		getSuggestionValueCallback: function(suggestion) { return suggestion.navn; },
		getSuggestionsHeadCallback: function(autocomplete) { return Loop.Sortere.PluginTemplates.suggestHead.supplant({ hits: autocomplete.getSuggestionsLength() }); },
		unhandledKeyDownCallback: this.unhandledKeyDown.bind(this),

		templateHead: '#{content}',
		templateTail: Loop.Sortere.PluginTemplates.suggestTail,
		templateItem: Loop.Sortere.PluginTemplates.suggestion,
		evalJSON: 'force'
	});
};