/* 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.regEscape = RegExp.escape = function(str) {
	return str.replace(/[.*+?|(){}\[\]\\]/g, '\\$&');
};

DX.log = function(text) {
	if (typeof console === 'undefined') { return false; }
	var date = new Date();
	console.log(date.toLocaleString() + '.' + date.getMilliseconds() + ' ' + text);
	return true;
};


/* 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);
	},
	hasFragment: function(key) {
		return this.getFragments().hasOwnProperty(key);
	},
	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 = typeof pair[1] === 'undefined' ? undefined : 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) + (typeof fragments[key] === 'undefined' ? '' : '=' + encodeURIComponent(fragments[key]));
			}
		}
		if (fragmentString.length === 0 && Object.keys(this.getFragments()).length === 0) { return; } // Don't attempt to modify the hash if was and is supposed to be empty (this is just a cosmetic fix to avoid having a useless '#' at the end of the url)
		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) { DX.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) { DX.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) { DX.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) { return DX.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(this, suggestion), 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 sLayout  = this.suggestionsElement.getLayout();
	var sPadding = sLayout.get('border-box-width') - sLayout.get('width');
	var rLayout  = this.positionRelativeTo.getLayout();

	this.suggestionsElement.setStyle({
		position: 'absolute',
		top:      (rLayout.get('top') + rLayout.get('border-box-height') + this.options.topAdjustment) + 'px',
		left:  	  (rLayout.get('left') + this.options.leftAdjustment) + 'px',
		width:    (rLayout.get('border-box-width') - sPadding + this.options.widthAdjustment) + 'px'
	});
	this.positionRelativeTo.parentNode.insert(this.suggestionsElement);
	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(autocomplete, suggestion) {
	var exp = new RegExp('(' + RegExp.escape(this.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(this, selectedSuggestion); }
	this.hideSuggestions();
};

DX.Autocomplete.prototype.defaultUseSuggestionCallback = function(autocomplete, 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     = '2.0';
	this.element     = $(element);
	this.kommuneNavn = '';
	this.flashError  = false;
	this.queryData   = null;
	this.breadcrumbs = [];

	this.options = {
		debug:             DX.Fragment.hasFragment('debug'),
		beskrivelseLengde: 200,
		sprakform:         'bokmal',
		type:              'privat',
		kommuneNr:         '',
		kommuneListe:      [],
		initialQuery:      {},
		showKommune:       true,
		showMinimal:       false,
		queryOnEnter:      true,
		redirectOnQuery:   false,
		startFocus:        false,
		startOpenOverlay:  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://kart.sortere.no/#lat=#{lat}&lng=#{lng}&zoom=#{zoom}&kommune=#{kommuneNavn}&avfallstypes=#{types}',

		overlayBgId:       'ls_overlay_background',
		searchFieldId:     'ls_search_field',
		kommuneFieldId:    'ls_kommune_field',
		buttonId:          'ls_search_button',
		suggesterId:       'ls_autosuggester',
		oddClass:          'ls_odd',
		evenClass:         'ls_even',
		farligClass:       'ls_farlig_avfall',
		loadingId:         'ls_loading',
		blockedNotifierId: 'ls_blocked_notifier',
		closeResultId:     'ls_close_result_handle',
		closeOverlayId:    'ls_close_overlay_handle',
		openOverlayIdExt:  null,
		tipsIdExt:         null,
		kontaktIdExt:      null
	};
	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 = [];
		this.options.redirectOnQuery = true;
	};

	if (this.options.useFlash) {
		Ajax.flXHRproxy.registerOptions(this.options.flashPrefix, {
			autoUpdatePlayer: true,
			xmlResponseText: false,
			binaryResponseBody: false,
			instancePooling: true
		});
	}

	this.observeOpenOverlayId();
	this.render(this.options.startFocus);
	this.initFragments();
	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')) {
		DX.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.observeOpenOverlayId = function() {
	if (!this.options.openOverlayIdExt || !$(this.options.openOverlayIdExt)) { return; }
	this.element.setStyle({
		display:  this.options.startOpenOverlay ? '' : 'none',
		position: 'absolute',
		top:      '0px',
		left:     '0px',
		width:    '100%',
		height:   '100%'
	});
	$(this.options.openOverlayIdExt).observe('click', Element.show.bind(null, this.element));
};

Loop.Sortere.Plugin.prototype.initFragments = function() {
	var fragments = DX.Fragment.getFragments();
	if (!fragments.hasOwnProperty('produkttype') && !fragments.hasOwnProperty('avfallstype') && !fragments.hasOwnProperty('miljogift') && !fragments.hasOwnProperty('query') && !fragments.hasOwnProperty('kommune')) {
		DX.Fragment.updateFragments(this.options.initialQuery);
	}
};

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) { DX.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) { DX.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) { DX.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.onSearchClick = 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() {
	if (!this.element.visible()) { return; }
	$(this.options.searchFieldId).focus();
};

Loop.Sortere.Plugin.prototype.focusOnKommuneField = function() {
	if (this.options.showKommune) { $(this.options.kommuneFieldId).focus(); }
};

Loop.Sortere.Plugin.prototype.unhandledKeyDown = function(e, autocomplete) {
	if (e.keyCode === 13 && this.options.queryOnEnter) { e.stop(); this.registerQuery(); } // Enter
};

Loop.Sortere.Plugin.prototype.registerQuery = function() {
	if (this.options.redirectOnQuery) { window.top.location.href = this.getLinkHref(); return; }

	var fragments = { query: this.getSearchFieldValue(), kommune: this.getKommuneNavn() };
	if (!fragments.query)   { delete fragments.query; }
	if (!fragments.kommune) { delete fragments.kommune; }
	DX.Fragment.setFragments(fragments);
};

/* 2010-04-21: Removed in favour of the simpler redirect behaviour in the registerQuery method, by request from LOOP.
Loop.Sortere.Plugin.prototype.doMinimalQuery = function() {
	var url = this.setLinkHref(); // 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.getLinkHref = function() {
	return this.options.queryUrl.supplant({ query: this.getSearchFieldValue(), kommuneNavn: this.getKommuneNavn(), type: this.options.type, sprakform: this.options.sprakform });
};

Loop.Sortere.Plugin.prototype.setLinkHref = function() {
	return $(this.options.buttonId).href = this.getLinkHref();
};

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 : (this.options.showSearchTip ? Loop.Sortere.PluginTemplates.searchTip : ''));

	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) { DX.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) { DX.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) { DX.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) { DX.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.getKommuneKontaktinfoMarkup = function(object) {
	object.hjemmeside = object.hjemmeside || this.makeLinkMarkup(this.getLocalizedValue(object, 'hjemmeside'), null, '_blank');
	var info = {};
	info.overskrift    = object.kontaktInfoOverskrift;
	info.kommuneAdress = this.templates.kommuneAdress.evaluate(object);
	info.kommunePhone  = this.templates.kommunePhone.evaluate(object);
	info.kommuneEmail  = this.templates.kommuneEmail.evaluate(object);
	info.kommuneWeb    = this.templates.kommuneWeb.evaluate(object);
	return this.templates.kontaktinfo.evaluate(info);
};

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({ navn: this.getLocalizedValue(tips[i], 'navn'), 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);
	}
	if (ordning.antallPunkter >= 1) {
		// Punktordning - la områdets/kommunens koordinater stå
		mapOptions.types = encodeURIComponent(mapOptions.atypes || ordning.types);
		mapOptions.zoom  = 12;
	} 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 description; }
	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', Element.hide.bind(null, $(this.options.blockedNotifierId))); // Commented out due to the changes in doMinimalQuery.

	$(this.options.searchFieldId + '_wrapper').observe('click', this.onSearchClick.bindAsEventListener(this));
	$(this.options.searchFieldId).observe('blur', function (e) { this.setLinkHref(); }.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) {
		this.queryData.kommune = this.queryData.kommune || {};

		data.hits        = this.queryData.hits;
		data.query       = this.queryData.query;
		data.kommuneNr   = this.queryData.kommune.kommuneNr;
		data.kommuneNavn = this.queryData.kommune.navn;
		data.results     = '';

		var breadcrumb = data.query ? { query: data.query } : {};
		if (this.options.showKommune && data.kommuneNavn) { breadcrumb.kommune = data.kommuneNavn; }
		var mapOptions = { lat: 'default', lng: 'default', zoom: 'default', kommuneNavn: encodeURIComponent(data.kommuneNavn), kommuneLat: this.queryData.kommune.breddeGradWgs84, kommuneLng: this.queryData.kommune.lengdeGradWgs84 };

		var kontaktMarkup = data.kommuneNr ? this.getKommuneKontaktinfoMarkup(this.queryData.kommune) : '';
		var tipsMarkup    = '';

		var objData = null;
		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);
			tipsMarkup              = this.getTipsBoksMarkup(objData);
			objData.tipsMarkup      = this.options.tipsIdExt ? '' : tipsMarkup;
			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);
			tipsMarkup          = this.getTipsBoksMarkup(objData);
			objData.tipsMarkup  = this.options.tipsIdExt ? '' : tipsMarkup;
			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);
			tipsMarkup          = this.getTipsBoksMarkup(objData);
			objData.tipsMarkup  = this.options.tipsIdExt ? '' : tipsMarkup;
			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);
			objData.kontaktMarkup = this.options.kontaktIdExt ? '' : kontaktMarkup;
			tipsMarkup            = this.getTipsBoksMarkup(objData);
			objData.tipsMarkup    = this.options.tipsIdExt ? '' : tipsMarkup;
			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.kontaktMarkup   = this.options.kontaktIdExt ? '' : kontaktMarkup;
	data.tipsMarkup      = this.options.tipsIdExt    ? '' : tipsMarkup;
	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.overlayBgId).setOpacity(0.5);
	if ($(this.options.tipsIdExt)) { $(this.options.tipsIdExt).update(tipsMarkup); }
	if ($(this.options.kontaktIdExt)) { $(this.options.kontaktIdExt).update(kontaktMarkup); }
	$(this.options.buttonId).observe('click', this.registerQuery.bind(this));

	$(this.options.searchFieldId + '_wrapper').observe('click', this.onSearchClick.bindAsEventListener(this));
	this.createSearchFieldAutocompleter();

	if (this.options.showKommune) {
		$(this.options.kommuneFieldId + '_wrapper').observe('click', this.onKommuneClick.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 closeResultHandle = $(this.options.closeResultId), closeOverlayHandle = $(this.options.closeOverlayId);
	if (closeResultHandle)  { closeResultHandle.observe('click',  this.clearResult.bind(this)); }
	if (closeOverlayHandle) { closeOverlayHandle.observe('click', Element.hide.bind(null, this.element)); }

	// Attempt to fix broken <img> tag 'src' attributes (i.e. those with relative urls intended only to be viewed inside the CMS at sortere.no)
	$$('.ls_plugin img[src]').each(function(img) { if (!img.getAttribute('src').startsWith('http')) { img.src = this.options.urlPrefix + img.getAttribute('src'); } }.bind(this));
};

Loop.Sortere.Plugin.prototype.clearResult = 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()) });
	new DX.Autocomplete(this.options.searchFieldId, this.options.suggesterId, url, {
		positionRelativeTo: this.options.searchFieldId + '_wrapper',
		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',
		debug: this.options.debug
	});
};

Loop.Sortere.Plugin.prototype.createKommuneFieldAutocompleter = function() {
	var url = this.options.suggesterKommuneUrl;
	new DX.Autocomplete(this.options.kommuneFieldId, this.options.suggesterId, url, {
		positionRelativeTo: this.options.kommuneFieldId + '_wrapper',
		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',
		debug: this.options.debug
	});
};
