/**
 Checks if the string starts with the specified sub string.

 @param str the sub string to check for.
 @returns true if the string starts with the sub string, otherwise
  false.
*/
String.prototype.startsWith = function (str) {
  return this.indexOf(str) === 0;
};

// Replaces all instances of the given substring.
String.prototype.replaceAll = function(
    patternRegex, // The substring you want to replace
    replacementString // The string you want to replace in.
){
    var regex = new RegExp(patternRegex, "g");
    return this.replace(regex, replacementString);
};
// Replaces all instances of the given substring.
String.prototype.replaceAllFast = function(
    strTarget, // The substring you want to replace
    strSubString // The string you want to replace in.
){
    var strText = this;
    var intIndexOfMatch = strText.indexOf( strTarget );

    // Keep looping while an instance of the target string
    // still exists in the string.
    while (intIndexOfMatch != -1){
        // Relace out the current instance.
        strText = strText.replace( strTarget, strSubString );

        // Get the index of any next matching substring.
        intIndexOfMatch = strText.indexOf( strTarget );
    }

    // Return the updated string with ALL the target strings
    // replaced out with the new substring.
    return( strText );
};


/**
 * See idea from http://blog.stevenlevithan.com/archives/faster-trim-javascript
 */
String.prototype.trim = function () {
    var str = this;
    str = str.replace(/^\s+/, '');
    for (var i = str.length - 1; i >= 0; i--) {
        if (/\S/.test(str.charAt(i))) {
            str = str.substring(0, i + 1);
            break;
        }
    }
    return str;
};
/**
 Converts this string into a Date instance.
 The date must be in the format "\/Date(2010-10-19T17:50:44.5000000+02:00)\/"
 and conforms to the ISO 8601 profile.

 @returns a new Date instance.
 */
String.prototype.parseJSONDate = function() {
    var dateString = this;
    if (dateString == null)
        return null;
    var year = parseInt(dateString.substr(6, 4), 10);
    var month = parseInt(dateString.substr(11, 2), 10)-1;
    var day = parseInt(dateString.substr(14, 2), 10);
    var hour = parseInt(dateString.substr(17, 2), 10);
    var min = parseInt(dateString.substr(20, 2), 10);
    var sec = parseInt(dateString.substr(23, 2), 10);
    var ms = parseInt(dateString.substr(26, 7), 10) / 10000;
    var date = new Date();
    date.setUTCFullYear(year, month, day);
    date.setUTCHours(hour, min, sec, ms);
    return date;
};
String.prototype.parseShortDateDDMMYYYY = function() {
    var dateString = this;

    dateString = dateString.trim();
    if (dateString.length == 0)
        return null;

    var dateParts = dateString.split(".", 3);
    var day = parseInt(dateParts[0], 10);
    var month = parseInt(dateParts[1], 10)-1;
    var year = parseInt(dateParts[2], 10);
    var date = new Date();
    date.setUTCFullYear(year, month, day);
    date.setUTCHours(0, 0, 0, 0);
    return date;
};
Date.prototype.toShortDate = function() {
    var dateInstance = this;
    if (dateInstance == null)
        return null;
    return dateInstance.getDate() + "." + (dateInstance.getMonth() + 1) + "." + dateInstance.getFullYear();
};

String.prototype.toShortenedWeekday = function() {
    var stringDate = this;
    var newStrDate
    newStrDate = stringDate.replace("Samstag","Sa").replace("Sonntag","So").replace("Montag","Mo").replace("Dienstag","Di").replace("Mittwoch","Mi").replace("Donnerstag","Do").replace("Freitag","Fr");
    newStrDate = newStrDate.replace("Saturday","Sat").replace("Sunday","Sun").replace("Monday","Mon").replace("Tuesday","Tues").replace("Wednesday","Wed").replace("Thursday","Thurs").replace("Friday","Fri");
    return newStrDate;
};
Date.prototype.toJSON = function() {
    var dateInstance = this;
    var month = (dateInstance.getMonth() + 1);
    var year = dateInstance.getFullYear();
    var date = dateInstance.getDate();
    var hours = dateInstance.getHours();
    var minutes = dateInstance.getMinutes();
    var seconds = dateInstance.getSeconds();
    var milliseconds = dateInstance.getMilliseconds();
    return "/Date(" + year + "-" + month.formatWithLeadingZeroPlaceholder(2) + "-" + date.formatWithLeadingZeroPlaceholder(2) + "T" + hours.formatWithLeadingZeroPlaceholder(2) + ":" + minutes.formatWithLeadingZeroPlaceholder(2) + ":" + seconds.formatWithLeadingZeroPlaceholder(2) + "." + milliseconds.formatWithTrailingZeroPlaceholder(7) + "+" + "00:00" + ")/"; // todo utc offset handling needed here!
};
Number.prototype.formatWithLeadingZeroPlaceholder = function(zeroPlaceholderCount) {
    var numberAsString = this.toString();
    while (numberAsString.length < zeroPlaceholderCount) {
        numberAsString = "0" + numberAsString;
    }
    return numberAsString;
};
Number.prototype.formatWithTrailingZeroPlaceholder = function(zeroPlaceholderCount) {
    var numberAsString = this.toString();
    while (numberAsString.length < zeroPlaceholderCount) {
        numberAsString =  numberAsString + "0";
    }
    return numberAsString;
};
function EventBus() {
    this._listeners = new Array();
}
EventBus.prototype.addListener = function(callback) {
    this._listeners.push(callback);
};
EventBus.prototype.publishEvent = function(sender, eventArgs) {
    for (var i = 0; i < this._listeners.length; i++) {
        this._listeners[i](sender, eventArgs);
    }
};

function UrlBuilder() {
}
UrlBuilder.prototype.CreatePageUrl = function(cultureUi, pagePath, part) {
    if (part == urlBuilder.Part.Presentation) {
        // Short URL if default part mode on server side is used.
        return "/" + cultureUi + pagePath;
    }
    return "/" + cultureUi + "/pages" + pagePath + "/" + part;
};
UrlBuilder.prototype.CreateModuleUrl = function(cultureUi, modulePath, part) {
    return "/" + cultureUi + "/modules" + modulePath + "/" + part;
};
UrlBuilder.prototype.CreatePageModuleUrl = function(cultureUi, pagePath, modulePath, part) {
    return "/" + cultureUi + "/pages" + pagePath + "/modules" + modulePath + "/" + part;
};
UrlBuilder.prototype.CreatePageModuleUrlWithParams = function(cultureUi, pagePath, modulePath) {
    return "/" + cultureUi + "/pages" + pagePath + "/modules" + modulePath;
};
UrlBuilder.prototype.Part = {
    "Presentation": "presentation",
    "Behavior": "behavior",
    "Data": "data"
};
// todo als: get rid of this, create a static class.
var urlBuilder = new UrlBuilder();

function ModuleIdentification(instanceName, instanceNameOnPage, templateName) {
    this._instanceName = instanceName;
    this._instanceNameOnPage = instanceNameOnPage;
    this._templateName = templateName;
}
ModuleIdentification.prototype.getInstanceName = function() {
    return this._instanceName;
};
ModuleIdentification.prototype.getInstanceNameOnPage = function() {
    return this._instanceNameOnPage;
};
ModuleIdentification.prototype.getTemplateName = function() {
    return this._templateName;
};

// PageModule
function PageModule(initUrl, cultureName, cultureUiName) {
    this._initUrl = initUrl;
    this._cultureName = cultureName;
    this._cultureUiName = cultureUiName;

    this._modules = new Array();
    this._eventBus = new EventBus();

    var languageSelector = new LanguageSelectorModule($("#pageLanguageSelector"));
    languageSelector.setParent(this);
    languageSelector.reload(true);
}
PageModule.prototype.getPath = function() {
    return this._initUrl;
};
PageModule.prototype.getCulture = function() {
    return this._cultureName;
};
PageModule.prototype.getCultureUi = function() {
    return this._cultureUiName;
};
PageModule.prototype.setCulture = function(cultureName) {
    if ((this._cultureName === cultureName) && (this._cultureUiName == cultureName))
        return;

    this._cultureName = cultureName;
    this._cultureUiName = cultureName;
    //this.reload(); // inplace reload has been disabled as the URL is not updated with the new locale.
    this.reloadRedirect();
};
PageModule.prototype.reload = function() {
    var me = this;
    $.getJSON(
          urlBuilder.CreatePageUrl(this._cultureUiName, this._initUrl, urlBuilder.Part.Behavior),
            // data...
          function(initData) { me.render(initData); }
    );
};
PageModule.prototype.reloadRedirect = function() {
    var redirectUrl = urlBuilder.CreatePageUrl(this._cultureUiName, this._initUrl, urlBuilder.Part.Presentation);
    var hash = this.getUriStateOfModulesAsHash(true);
    if (hash != null)
        redirectUrl += hash;
    window.location = redirectUrl;
};
PageModule.prototype.render = function(initData) {
    var me = this;
    var moduleInstances = initData.moduleInstances;

    var isInitialPageReload = false;
    if (this._modules.length == 0) { // todo als: synchronize list of modules based on module type and instance name...
        isInitialPageReload = true;

        var i;
        for (i = 0; i < moduleInstances.length; i++) {
            var moduleInstanceData = moduleInstances[i];
            var pageInstanceModuleInstanceName = moduleInstanceData.pageInstanceModuleInstanceName;
            var moduleInstanceName = moduleInstanceData.moduleInstanceName;
            var moduleName = moduleInstanceData.moduleName;
            var templateName = moduleInstanceData.moduleTemplateName;

            var moduleDomElement = $("#"+pageInstanceModuleInstanceName);
            var parentDomElement = $("#" + moduleInstanceData.placeholderId);
            if (moduleDomElement.length == 0) {
                // Module not pre-rendered. Create a new container for the module.
                moduleDomElement = $(document.createElement("div"));
                moduleDomElement.attr("id", pageInstanceModuleInstanceName);
                moduleDomElement.addClass(moduleName);
                //moduleDomElement.addClass(moduleInstanceName);
                parentDomElement.append(moduleDomElement);

                // DEBUG START
                /*
                var debugDomElement= $(document.createElement("div"));
                debugDomElement.html(pageInstanceModuleInstanceName);
                debugDomElement.addClass("ritSubModuleInstanceDebugInfo");
                //debugDomElement.css("font-weight", "bold");
                //debugDomElement.css("margin-top", "-20px");
                parentDomElement.append(debugDomElement);
                */
                // DEBUG END
            } else {
                moduleDomElement.detach();
                parentDomElement.append(moduleDomElement);
            }

            var moduleClassName = moduleName + "Module";
            var moduleInstance;
            if (window[moduleClassName] != undefined) {
                moduleInstance = new window[moduleClassName](
                    this._eventBus,
                    new ModuleIdentification(moduleInstanceName, pageInstanceModuleInstanceName, templateName),
                    moduleDomElement);
            }
            else {
                moduleInstance = new UnknownModule(
                    this._eventBus,
                    new ModuleIdentification(moduleInstanceName, pageInstanceModuleInstanceName, templateName),
                    moduleDomElement);
            }
            moduleInstance.setParent(this);
            this._modules.push(moduleInstance);
        }
    }

    var bulkDataLoadingEnabled = true;
    if (bulkDataLoadingEnabled) {
        $.getJSON(
              urlBuilder.CreatePageUrl(this.getCultureUi(), this.getPath(), urlBuilder.Part.Data),
              function(data) { me.onProcessBulkData(data, isInitialPageReload); }
        );
    }
    else {
        for (i = 0; i < this._modules.length; i++) {
            this._modules[i].reload(isInitialPageReload);
        }
    }
};
PageModule.prototype.onProcessBulkData = function(data, isInitialPageReload) {
    var dataOfSubModuleInstances = data.data;
    for (var i = 0; i < dataOfSubModuleInstances.length; i++) {
        var dataOfSubModuleInstance = dataOfSubModuleInstances[i];
        var subModuleInstance = this.getSubModuleInstance(dataOfSubModuleInstance.identificationName);
        if (dataOfSubModuleInstance.data != null) {
            subModuleInstance.render(dataOfSubModuleInstance.data);
            if (subModuleInstance.restoreUriState != undefined)
                subModuleInstance.restoreUriState(this.getModuleInstanceUriState(subModuleInstance));
        }
        else {
            subModuleInstance.reload(isInitialPageReload);
        }
    }

    var hash = this.getUriStateOfModulesAsHash(false);
    if (hash != null) {
        window.location.hash = hash;
    } else if (window.location.hash != "") {
        // delete any existing invalid hash.
        //window.location.hash = "";
        // Disabled as we do not want to override the link that has initially been
        // clicked, e.g. read more on announcement home page.
        // todo als: implement a solution to delete invalid hashes, e.g. lang in combination with invalid identification name...
    }
};
PageModule.prototype.getUriStateOfModulesAsHash = function (localeIndependent) {
    var states = new Array();
    for (var i = 0; i < this._modules.length; i++) {
        var module = this._modules[i];
        if (module.storeUriState == undefined)
            continue;

        var state = module.storeUriState(localeIndependent);
        if (state == null)
            continue;
        for (var property in state) {
            states.push(module._moduleIdentification.getInstanceNameOnPage() + "." + property + "=" + state[property]);
        }
    }
    if (states.length == 0)
        return null;
    return "#!" + states.join("&");
};
PageModule.prototype.getSubModuleInstance = function(identificationName) {
    for (var i = 0; i < this._modules.length; i++) {
        var module = this._modules[i];
        if (identificationName == module._moduleIdentification.getInstanceNameOnPage())
            return module;
    }
    return null;
};
PageModule.prototype.getModuleInstanceUriState = function(subModuleInstance) {
    if (subModuleInstance == null)
        return null;

    var hashFragments = window.location.hash;

    if ((hashFragments == null) || (hashFragments.trim() == ""))
        return null;

    var fragmentsString = hashFragments.trim();
    if (fragmentsString.startsWith("#"))
        fragmentsString = fragmentsString.substr(1, fragmentsString.length - 1);
    if (fragmentsString.startsWith("!"))
        fragmentsString = fragmentsString.substr(1, fragmentsString.length - 1);

    var fragments = fragmentsString.split("&");

    var stateEntries = new Array();
    var i;
    for (i = 0; i < fragments.length; i++) {
        var fragmentString = fragments[i].trim();
        var keyValuePair = fragmentString.split("=");
        if (keyValuePair.length == 2) {
            stateEntries.push({ key: keyValuePair[0], value: keyValuePair[1]});
        }
    }

    var instanceName = subModuleInstance._moduleIdentification.getInstanceNameOnPage();

    var moduleState = {};
    for (i = 0; i < stateEntries.length; i++) {
        var stateEntry = stateEntries[i];
        if (stateEntry.key.startsWith(instanceName)) {
            moduleState[stateEntry.key.substr(instanceName.length+1, stateEntry.key.length - instanceName.length+1)] = stateEntry.value;
        }
    }
    return moduleState;
};

// MenusModule
function MenusModule(eventBus, moduleIdentification, element) {
    this._eventBus = eventBus;
    this._element = element;
    this._moduleIdentification = moduleIdentification;

    this._centerChildCallback = null;
    this._prerenderEnabled = true;
}
MenusModule.prototype.setParent = function(parent) {
    this._parent = parent;
};
MenusModule.prototype.reload = function(isInitialPageReload) {
    var me = this;

    if (me._prerenderEnabled && isInitialPageReload) {
        me.attachEvents();
    }
    else {
        $.getJSON(
              urlBuilder.CreatePageModuleUrl(this._parent.getCultureUi(), this._parent.getPath(), "/ritMenu/" + this._moduleIdentification.getInstanceNameOnPage(), urlBuilder.Part.Data),
              // data...
              function(menuData) { me.render(menuData); }
        );
    }
};
MenusModule.prototype.attachEvents = function() {
    $("#"+this._moduleIdentification.getInstanceNameOnPage()+">ul li").mouseenter(this.getMenuNodeMouseOverAction());
};
MenusModule.prototype.render = function(menuData) {
    this._element.empty();

    this.doRenderMenuRecursive(this._element, menuData, menuData.basePath, 0);

    // Set css class on selected item.
    //$("#" + this._parent.getPath().replace("/", ".")).addClass("selected");
};
MenusModule.prototype.doRenderMenuRecursive = function(domRoot, menuNode, basePath, level) {
    var ul = $(document.createElement("ul"));
    ul.addClass("ritMenuLevel"+level.toString());

    var currentSelected = false;
    var selectedPagePath = this._parent.getPath();

    var nodes = menuNode.nodes;
    var nodeCount = nodes.length;
    for (var i = 0; i < nodeCount; i++) {
        var childNode = nodes[i];
        var childId = basePath + childNode.id;

        var li = $(document.createElement("li"));
        li.attr("id", childId);
        if (i == 0)
            li.addClass("ritMenuFirstChild");
        if (i == nodeCount-1)
            li.addClass("ritMenuLastChild");

        if (level == 0) {
            // todo: read from settings.
            li.addClass("centerChild");
            li.mouseenter(this.getMenuNodeMouseOverAction());
        }

        if ((selectedPagePath == childId) || selectedPagePath.startsWith(childId + "/")) {
            currentSelected = true;
            li.addClass("selected");
        }

        var a = $(document.createElement("a"));
        childId = childId + "/";
        a.attr("href", urlBuilder.CreatePageUrl(this._parent.getCultureUi(), childId.substr(0, childId.length - 1), urlBuilder.Part.Presentation));
        if (childNode.onNavigateScript != undefined && childNode.onNavigateScript != null)
        {
            var callback = function(script) {
                // Required for proper closure handling. Create a new environment
                // for closure variable snapshot.
                return function() {
                    eval(script);
                    return false; // block link from being executed.
                }
            };
            a.click(callback(childNode.onNavigateScript));
        }
        a.html(childNode.caption);

        li.append(a);
        ul.append(li);

        if (nodes[i].nodes != undefined) {
            var isChildInSelectionPath = this.doRenderMenuRecursive(li, nodes[i], childId, level+1);
        }
    }

    domRoot.append(ul);
    return currentSelected;
};
MenusModule.prototype.getMenuNodeMouseOverAction = function() {
    if (this._menuNodeMouseOverAction == null) {
        this._menuNodeMouseOverAction = function(e) {
            var target = e.target;
            if ($(target).parent().get(0) == this)
                target = $(target).parent().get(0);
            if (target != this) return false;

            var liElement = $(target);
            var ulElement = liElement.children("ul");

            if (liElement.hasClass("centerChild")) {
                var marginLeft = liElement.css("margin-left");
                var marginLeftPixel = parseInt(marginLeft.substring(0, marginLeft.length-2));
                var liPosition = liElement.position();

                var liWidth = liElement.outerWidth();
                var ulWidth = ulElement.outerWidth();
                var diff = -ulWidth/2 + liWidth/2;
                liElement.children("ul").css("left", liPosition.left + diff + marginLeftPixel);
            }
        };
    }

    return this._menuNodeMouseOverAction;
};

// TextDetailsModule
function TextDetailsModule(eventBus, moduleIdentification, element) {
    this._eventBus = eventBus;
    this._moduleIdentification = moduleIdentification;
    this._element = element;
}
TextDetailsModule.prototype.setParent = function(parent) {
    this._parent = parent;
};
TextDetailsModule.prototype.reload = function(isInitialPageReload) {
    var me = this;

    // $.post(...)
    $.getJSON(
            urlBuilder.CreatePageModuleUrl(this._parent.getCultureUi(), this._parent.getPath(), "/ritText/" + this._moduleIdentification.getInstanceNameOnPage(), urlBuilder.Part.Data),
            // data...
            function(textData) {
                me.render(textData);
            }
            );
};

TextDetailsModule.prototype.render = function(textData) {
    this._element.empty();
    var templateName = this._moduleIdentification.getTemplateName();

    // Template parse example:
    var unparsedTemplates = document.getElementById("templates");
    var unparsedTemplatesText = unparsedTemplates.text;
    if (unparsedTemplatesText.startsWith("<![CDATA[")) {
        unparsedTemplatesText = unparsedTemplatesText.substring("<![CDATA[".length, unparsedTemplatesText.length - "]]>".length);
    }
    // todo als: not very beautiful. let browser parse on page request and just clone here...
    var parsedTemplates = $(unparsedTemplatesText);

    var mainTemplateId = templateName + "_template";
    mainTemplateId = mainTemplateId.replaceAll("\\.", "\\.");
    var mainTemplate = parsedTemplates.find("#" + mainTemplateId);

    // todo als: does dom id lookup work fast at this point? (=> because not yet part of the dom)
    // so document.getElementById() is not available.
    mainTemplate.find("#title").html(textData.title);
    mainTemplate.find("#htmlFragment").html(textData.htmlFragment);
    var script = textData.scriptFragment;

    // Patch all site links with the proper localized url.
    // todo als: clean this...
    var me = this;
    var siteLinkRegex = new RegExp("^\/.*");
    mainTemplate.find("a").each(function(index, element) {
        var link = $(element).attr("href");
        if ((link != null) && (link.match(siteLinkRegex))) {
            $(element).attr("href", "/"+me._parent.getCultureUi()+link);
        }
    });

    this._element.append(mainTemplate);

    var emailAddresses = this._element.find(".email_address");
    emailAddresses.html(function(index, oldHtml) {
        return oldHtml.replaceAllFast('*', '.').replaceAllFast('$','@');
    });

    if (script != null) {
        eval(script);
    }
};

// AnnouncementsModule
function AnnouncementsModule(eventBus, moduleIdentification, element) {
    this._eventBus = eventBus;
    this._moduleIdentification = moduleIdentification;
    this._element = element;

    this._items = null;
    this._selectedItemDefault = null;
    this._selectedItem = null;
}
AnnouncementsModule.prototype.setParent = function(parent) {
    this._parent = parent;
};
AnnouncementsModule.prototype.reload = function(isInitialPageReload) {
    var me = this;

    // $.post(...)
    $.getJSON(
              urlBuilder.CreatePageModuleUrl(this._parent.getCultureUi(), this._parent.getPath(), "/ritAnnouncement/" + this._moduleIdentification.getInstanceNameOnPage(), urlBuilder.Part.Data),
              // data...
              function(announcementsData) { me.render(announcementsData); me.restoreUriState(me._parent.getModuleInstanceUriState(me)); }
    );
};
AnnouncementsModule.prototype.render = function(announcementsData) {
    this._items = announcementsData.entries;
    if (announcementsData.selectedItemIndex >= 0)
        this._selectedItemDefault = announcementsData.entries[announcementsData.selectedItemIndex];

    this._element.empty();
    var templateName = this._moduleIdentification.getTemplateName();

    // Template parse example:
    var unparsedTemplates = document.getElementById("templates");
    var unparsedTemplatesText = unparsedTemplates.text;
    if (unparsedTemplatesText.startsWith("<![CDATA[")) {
        unparsedTemplatesText = unparsedTemplatesText.substring("<![CDATA[".length, unparsedTemplatesText.length - "]]>".length);
    }

    var ul = $(document.createElement("ul"));
    this._element.append(ul);

    var me = this;

    for (var i = 0; i < this._items.length; i++) {
        var announcementsEntry = this._items[i];

        // todo als: not very beautiful. let browser parse on page request and just clone here...
        var parsedTemplates = $(unparsedTemplatesText);

        var mainAnnouncementsEntryTemplateId = templateName + "_entry.template";
        mainAnnouncementsEntryTemplateId = mainAnnouncementsEntryTemplateId.replaceAll("\\.", "\\.");
        var mainAnnouncementsEntryTemplate = parsedTemplates.find("#" + mainAnnouncementsEntryTemplateId);
        // todo als: does dom id lookup work fast at this point? (=> because not yet part of the dom?)
        // so document.getElementById() is not available.
        var title = mainAnnouncementsEntryTemplate.find("#entry\\.title");
        title.html(announcementsEntry.title);
        title.attr("href", "#!" + this._moduleIdentification.getInstanceNameOnPage() + ".lidn=" + announcementsEntry.lidn);
        mainAnnouncementsEntryTemplate.find("#entry\\.abstract").html(announcementsEntry["abstract"]);

        mainAnnouncementsEntryTemplate.find("#entry\\.publishDate").html(announcementsEntry.publishDate.parseJSONDate().toLocaleDateString());
        mainAnnouncementsEntryTemplate.find("#entry\\.dateFrom").html(announcementsEntry.publishDate.parseJSONDate().toLocaleDateString());
        mainAnnouncementsEntryTemplate.find("#entry\\.publishDate_ShortWeekday").html(announcementsEntry.publishDate.parseJSONDate().toLocaleDateString().toShortenedWeekday());
        mainAnnouncementsEntryTemplate.find("#entry\\.dateFrom_ShortWeekday").html(announcementsEntry.publishDate.parseJSONDate().toLocaleDateString().toShortenedWeekday());
        if (announcementsEntry.expireDate)
        {
            mainAnnouncementsEntryTemplate.find("#entry\\.expireDate").html(announcementsEntry.expireDate.parseJSONDate().toLocaleDateString());
            mainAnnouncementsEntryTemplate.find("#entry\\.dateTo").html(announcementsEntry.expireDate.parseJSONDate().toLocaleDateString());
            mainAnnouncementsEntryTemplate.find("#entry\\.expireDate_ShortWeekday").html(announcementsEntry.expireDate.parseJSONDate().toLocaleDateString().toShortenedWeekday());
            mainAnnouncementsEntryTemplate.find("#entry\\.dateTo_ShortWeekday").html(announcementsEntry.expireDate.parseJSONDate().toLocaleDateString().toShortenedWeekday());
        }
        else
        {
            mainAnnouncementsEntryTemplate.find("#entry\\.dateToConditional").hide();
        }


        // todo als: improve speed.
        var readMoreLink = mainAnnouncementsEntryTemplate.find("#entry\\.link");
        if (readMoreLink.length == 1) {
            var readMore = this._parent.getCultureUi() == "de-CH" ? "Weiterlesen..." : "Read more...";
            readMoreLink.html(readMore);
            var languageRegex = new RegExp("\/[a-z]*-[A-Z]*\/");
            // todo als: fix this once binding support is implemented.
            readMoreLink.attr("href", readMoreLink.attr("href").replace(languageRegex, "/"+this._parent.getCultureUi()+"/") + announcementsEntry.lidn);
        }

        for (var imageIndex = 0; imageIndex < announcementsEntry.images.length; imageIndex++) {
            var image = announcementsEntry.images[imageIndex];
            var imageElement = mainAnnouncementsEntryTemplate.find("#entry\\.image"+imageIndex.toString());
            if (imageElement.length == 0)
                continue;
            imageElement.attr("src", "/globals/resources/img/rit.modules.announcements/"+image.url);
            imageElement.attr("alt", image.description);
            imageElement.attr("title", image.description);
        }

//        var a = $(document.createElement("a"));
//        a.attr("href", "/announcements/todo");
//        a.html(announcementsEntry.title);

        var callback = function(me, announcementEntry) {
            // Required for proper closure handling. Create a new environment
            // for closure variable snapshot.
            return function() {
                me.reloadDetails(announcementEntry, false);
                return true;
            }
        };
        callback = callback(me, announcementsEntry);
        title.click(callback);

        var li = $(document.createElement("li"));
        li.attr("id", announcementsEntry.lidn)
        //li.append(a);
        li.append(mainAnnouncementsEntryTemplate);
        ul.append(li);
    }

    this._element.append(ul);

    // hack: call jScrollPane
    //if (AddScrollPaneFunctionality != undefined)
    if (typeof InitializeScrollPane == "function")
    {
        InitializeScrollPane();
    }
};
AnnouncementsModule.prototype.reloadDetails = function(announcementsEntry, autoScroll) {
    this._eventBus.publishEvent(this, announcementsEntry);
    this._selectedItem = announcementsEntry;

    var id = announcementsEntry.lidn;
    id = id.replaceAllFast('+','\\*').replaceAllFast('*','+');
    var element = $("#"+id);
    element.siblings().removeClass("ritListItemSelected");
    element.addClass("ritListItemSelected");

    if ((autoScroll) && (typeof GetScrollPane == "function"))
    {
        var scrollPane = GetScrollPane();
        var api = scrollPane.data('jsp');
        api.scrollToElement(element[0]);
    }
};
AnnouncementsModule.prototype.storeUriState = function(localeIndependent) {
    if ((this._selectedItem == null) || (this._selectedItem === this.getDefaultSelectedEntry()))
        return null;
    if (localeIndependent) {
        return { "bid": this._selectedItem.bid };
    }
    else {
        return { "lidn": this._selectedItem.lidn };
    }
};
AnnouncementsModule.prototype.restoreUriState = function(state) {
    if (state == null)
        return this.selectDefaultEntry();

    var i;
    if (state.bid != undefined) {
        for (i = 0; i < this._items.length; i++)
        {
            if (this._items[i].bid == state.bid)
            {
                this.reloadDetails(this._items[i], true);
                return true;
            }
        }
    }
    else if (state.lidn != undefined) {
        for (i = 0; i < this._items.length; i++)
        {
            if (this._items[i].lidn == state.lidn)
            {
                this.reloadDetails(this._items[i], true);
                return true;
            }
        }
    }

    return this.selectDefaultEntry();
};
AnnouncementsModule.prototype.getDefaultSelectedEntry = function() {
    return this._selectedItemDefault;
};
AnnouncementsModule.prototype.selectDefaultEntry = function() {
    var entry = this.getDefaultSelectedEntry();
    if (entry == null)
        return false;

    // Implicitly select first entry.
    this.reloadDetails(entry, true);
    return true;
};

// AnnouncementDetailsModule
function AnnouncementDetailsModule(eventBus, moduleIdentification, element) {
    this._eventBus = eventBus;
    this._moduleIdentification = moduleIdentification;
    this._element = element;

    var me = this;
    eventBus.addListener(function(sender, eventArgs) { me.reloadByData(eventArgs); } );
}
AnnouncementDetailsModule.prototype.setParent = function(parent) {
    this._parent = parent;
};
AnnouncementDetailsModule.prototype.reload = function(isInitialPageReload) {
    var me = this;

    // $.post(...)
    //    $.getJSON(
    //          "./staticTestData/mainAnnouncementDetails1.data.html",
    //          // data...
    //          function(menuData) { me.render(menuData); }
    //    );
};
AnnouncementDetailsModule.prototype.reloadById = function(announcementsEntryId) {
    var me = this;

    // $.post(...)
    //    $.getJSON(
    //          "./staticTestData/mainAnnouncementDetails" + announcementsEntryId + ".data.html",
    //          // data...
    //          function(menuData) { me.render(menuData); }
    //    );
};
AnnouncementDetailsModule.prototype.reloadByData = function(announcementEntry) {
    this.render(announcementEntry);
};
AnnouncementDetailsModule.prototype.render = function(announcementEntry) {
    this._element.empty();
    var templateName = this._moduleIdentification.getTemplateName();

    // Template parse example:
    var unparsedTemplates = document.getElementById("templates");
    var unparsedTemplatesText = unparsedTemplates.text;
    if (unparsedTemplatesText.startsWith("<![CDATA[")) {
        unparsedTemplatesText = unparsedTemplatesText.substring("<![CDATA[".length, unparsedTemplatesText.length - "]]>".length);
    }
    // todo als: not very beautiful. let browser parse on page request and just clone here...
    var parsedTemplates = $(unparsedTemplatesText);

    var announcementDetailsTemplateId = templateName + "_template";
    announcementDetailsTemplateId = announcementDetailsTemplateId.replaceAll("\\.", "\\.");
    var announcementDetailsTemplate = parsedTemplates.find("#" + announcementDetailsTemplateId);

    // todo als: does dom id lookup work fast at this point? (=> because not yet part of the dom?)
    // so document.getElementById() is not available.
    announcementDetailsTemplate.find("#title").html(announcementEntry.title);
    announcementDetailsTemplate.find("#body").html(announcementEntry.body);
    announcementDetailsTemplate.find("#publishDate").html(announcementEntry.publishDate.parseJSONDate().toLocaleDateString());
    announcementDetailsTemplate.find("#dateFrom").html(announcementEntry.publishDate.parseJSONDate().toLocaleDateString());
    if (announcementEntry.expireDate)
    {
        announcementDetailsTemplate.find("#expireDate").html(announcementEntry.expireDate.parseJSONDate().toLocaleDateString());
        announcementDetailsTemplate.find("#dateTo").html(announcementEntry.expireDate.parseJSONDate().toLocaleDateString());
    }
    else
    {
        announcementDetailsTemplate.find("#dateToConditional").hide();
    }

    for (var imageIndex = 0; imageIndex < announcementEntry.images.length; imageIndex++) {
        var image = announcementEntry.images[imageIndex];
        var imageElement = announcementDetailsTemplate.find("#image"+imageIndex.toString());
        if (imageElement.length == 0)
            continue;
        imageElement.attr("src", "/globals/resources/img/rit.modules.announcements/"+image.url);
        imageElement.attr("alt", image.description);
        imageElement.attr("title", image.description);
    }

    this._element.append(announcementDetailsTemplate);

};

// AddressesModule
function AddressesModule(eventBus, moduleIdentification, element) {
    this._eventBus = eventBus;
    this._moduleIdentification = moduleIdentification;
    this._element = element;

    var me = this;
    eventBus.addListener(function(sender, eventArgs) { me.reloadById(eventArgs); } );
}
AddressesModule.prototype.setParent = function(parent) {
    this._parent = parent;
};
AddressesModule.prototype.reload = function(isInitialPageReload) {
    var me = this;

//    // $.post(...)
//    $.getJSON(
//              urlBuilder.CreatePageModuleUrl(this._parent.getCultureUi(), this._parent.getPath(), "/ritAddress/" + this._moduleIdentification.getInstanceNameOnPage() + "/Addresses/GetAddresses", urlBuilder.Part.Data),
//              // data...
//              function(addressesData) { me.render(addressesData); }
//    );
};
AddressesModule.prototype.reloadById = function(countriesEntry) {
    var me = this;

    // $.post(...)
    $.getJSON(
              urlBuilder.CreatePageModuleUrlWithParams(
                  this._parent.getCultureUi(),
                  this._parent.getPath(),
                  "/ritAddress/" + this._moduleIdentification.getInstanceNameOnPage() + "/Addresses/GetAddresses",
                  urlBuilder.Part.Data),
              { "CountryKey": countriesEntry.key },
              function(addressesData) { me.render(addressesData); }
    );
};
AddressesModule.prototype.render = function(addressesData) {
    this._element.empty();
    var templateName = this._moduleIdentification.getTemplateName();

    // Template parse example:
    var unparsedTemplates = document.getElementById("templates");
    var unparsedTemplatesText = unparsedTemplates.text;
    if (unparsedTemplatesText.startsWith("<![CDATA[")) {
        unparsedTemplatesText = unparsedTemplatesText.substring("<![CDATA[".length, unparsedTemplatesText.length - "]]>".length);
    }

    var ul = $(document.createElement("ul"));
    this._element.append(ul);

    var me = this;

    for (var i = 0; i < addressesData.entries.length; i++) {
        var addressesEntry = addressesData.entries[i];

        // todo als: not very beautiful. let browser parse on page request and just clone here...
        var parsedTemplates = $(unparsedTemplatesText);

        var mainAddressesEntryTemplateId = templateName + "_entry.template";
        mainAddressesEntryTemplateId = mainAddressesEntryTemplateId.replaceAll("\\.", "\\.");
        var mainAddressesEntryTemplate = parsedTemplates.find("#" + mainAddressesEntryTemplateId);

        // todo als: does dom id lookup work fast at this point? (=> because not yet part of the dom?)
        // so document.getElementById() is not available.
        var nameLine1 = mainAddressesEntryTemplate.find("#entry\\.nameLine1");
        nameLine1.html(addressesEntry.nameLine1);
        mainAddressesEntryTemplate.find("#entry\\.nameLine2").html(addressesEntry["nameLine2"]);

        var addressLine1Template = mainAddressesEntryTemplate.find("#entry\\.addressLine1");
        var addressLine1Data = addressesEntry["addressLine1"];
        if (addressLine1Data != null)
        {
            addressLine1Template.html(addressLine1Template.html() + addressLine1Data);
        }
        else
        {
            //addressLine1Template.addClass("RIT_no_data");
            mainAddressesEntryTemplate.find("#adresslineConditional").hide();
        }

        mainAddressesEntryTemplate.find("#entry\\.addressLine2").html(addressesEntry["addressLine2"]);
        mainAddressesEntryTemplate.find("#entry\\.zipCode").html(addressesEntry["zipCode"]);
        mainAddressesEntryTemplate.find("#entry\\.city").html(addressesEntry["city"]);

        var tel1Template = mainAddressesEntryTemplate.find("#entry\\.tel1");
        var tel1Data = addressesEntry["tel1"];
        if (tel1Data != null)
        {
            tel1Template.html(tel1Template.html() + tel1Data);
        }
        else
        {
            tel1Template.addClass("RIT_no_data");
        }

        /*
        var homepageTemplate = mainAddressesEntryTemplate.find("#entry\\.homepage");
        var homepageData = addressesEntry["homepage"];
        if (homepageData != null) {
            var homepageWithoutProtocol = homepageData.startsWith("http://") ? homepageData.substr(7, homepageData.length-7): homepageData;
            homepageTemplate.html(homepageWithoutProtocol);
            homepageTemplate.attr("href", "http://" + homepageWithoutProtocol);
        }
        else
        {
            homepageTemplate.addClass("RIT_no_data");
        }
        */

        var srcIcon1 = mainAddressesEntryTemplate.find("#entry\\.icon1").attr("src");
        var srcIcon2 = mainAddressesEntryTemplate.find("#entry\\.icon2").attr("src");
        var iconNamePart = "_32.gif";
        var iconBlank = "blank1x1.gif";
        var additionalInfoData = addressesEntry.additionalInfo;
        if (additionalInfoData)
        {
            var sellerOfData = additionalInfoData.sellerOf;
            var sellerOfTypes = sellerOfData.split("|");
            if (sellerOfTypes.length == 1)
            {
                //if (sellerOfTypes[0].localeCompare("ski"))
                if (sellerOfTypes[0] == "ski")
                {
                    mainAddressesEntryTemplate.find("#entry\\.icon1").attr("src", srcIcon1.replace(iconBlank, sellerOfTypes[0] + iconNamePart));
                    mainAddressesEntryTemplate.find("#entry\\.icon1").attr("title", sellerOfTypes[0]);
                }
                else
                {
                    mainAddressesEntryTemplate.find("#entry\\.icon2").attr("src", srcIcon2.replace(iconBlank, sellerOfTypes[0] + iconNamePart));
                    mainAddressesEntryTemplate.find("#entry\\.icon2").attr("title", sellerOfTypes[0]);
                }
            }
            else // at least two categories, assuming proper order ski|snowboard
            {
                mainAddressesEntryTemplate.find("#entry\\.icon1").attr("src", srcIcon1.replace(iconBlank, sellerOfTypes[0] + iconNamePart));
                mainAddressesEntryTemplate.find("#entry\\.icon1").attr("title", sellerOfTypes[0]);
                mainAddressesEntryTemplate.find("#entry\\.icon2").attr("src", srcIcon2.replace(iconBlank, sellerOfTypes[1] + iconNamePart));
                mainAddressesEntryTemplate.find("#entry\\.icon2").attr("title", sellerOfTypes[1]);
            }
        }

        var homepageTemplate = mainAddressesEntryTemplate.find("#entry\\.homepage");
        var homepageData = addressesEntry["homepage"];
        if (homepageData != null) {
            var homepageWithoutProtocol = homepageData.startsWith("http://") ? homepageData.substr(7, homepageData.length-7): homepageData;
            homepageTemplate.html(addressesEntry.nameLine1);
            homepageTemplate.attr("href", "http://" + homepageWithoutProtocol);
        }
        else
        {
            homepageTemplate.html(addressesEntry.nameLine1);
        }


        var emailTemplate = mainAddressesEntryTemplate.find("#entry\\.email");
        var emailData = addressesEntry["email"];
        if (emailData != null)
        {
            var emailWithoutMailto = emailData.startsWith("mailto:") ? emailData.substr(7, emailData.length-7): emailData;
            /*emailTemplate.html(emailWithoutMailto);*/
            var contactText = this._parent.getCultureUi() == "de-CH" ? "Kontakt" : "Contact";
            emailTemplate.html(contactText);
            emailTemplate.attr("href", "mailto:" + emailWithoutMailto);
        }
        else
        {
            emailTemplate.addClass("RIT_no_data");
        }

        var callback = function(me, addressEntry) {
            // Required for proper closure handling. Create a new environment
            // for closure variable snapshot.
            return function() {
                me.reloadDetails(addressEntry);
                return false;
            }
        };
        callback = callback(me, addressesEntry);
        //title.click(callback);

        var li = $(document.createElement("li"));
        li.append(mainAddressesEntryTemplate);
        ul.append(li);
    }

    this._element.append(ul);
};
AddressesModule.prototype.reloadDetails = function(addressesEntryId) {
    this._eventBus.publishEvent(this, addressesEntryId);
};

// AddressDetailsModule
function AddressDetailsModule(eventBus, moduleIdentification, element) {
    this._eventBus = eventBus;
    this._moduleIdentification = moduleIdentification;
    this._element = element;

    var me = this;
    eventBus.addListener(function(sender, eventArgs) { me.reloadByData(eventArgs); } );
}
AddressDetailsModule.prototype.setParent = function(parent) {
    this._parent = parent;
};
AddressDetailsModule.prototype.reload = function(isInitialPageReload) {
    var me = this;

    // $.post(...)
    //    $.getJSON(
    //          "./staticTestData/mainAddressDetails1.data.html",
    //          // data...
    //          function(menuData) { me.render(menuData); }
    //    );
};
AddressDetailsModule.prototype.reloadById = function(addressesEntryId) {
    var me = this;

    // $.post(...)
    //    $.getJSON(
    //          "./staticTestData/mainAddressDetails" + addressesEntryId + ".data.html",
    //          // data...
    //          function(menuData) { me.render(menuData); }
    //    );
};
AddressDetailsModule.prototype.reloadByData = function(addressEntry) {
    this.render(addressEntry);
};
AddressDetailsModule.prototype.render = function(addressEntry) {
    this._element.empty();
    var templateName = this._moduleIdentification.getTemplateName();

    // Template parse example:
    var unparsedTemplates = document.getElementById("templates");
    var unparsedTemplatesText = unparsedTemplates.text;
    if (unparsedTemplatesText.startsWith("<![CDATA[")) {
        unparsedTemplatesText = unparsedTemplatesText.substring("<![CDATA[".length, unparsedTemplatesText.length - "]]>".length);
    }
    // todo als: not very beautiful. let browser parse on page request and just clone here...
    var parsedTemplates = $(unparsedTemplatesText);

    var addressDetailsTemplateId = templateName + "_template";
    addressDetailsTemplateId = addressDetailsTemplateId.replaceAll("\\.", "\\.");
    var addressDetailsTemplate = parsedTemplates.find("#" + addressDetailsTemplateId);

    // todo als: does dom id lookup work fast at this point? (=> because not yet part of the dom?)
    // so document.getElementById() is not available.
    addressDetailsTemplate.find("#title").html(addressEntry.title);
    addressDetailsTemplate.find("#body").html(addressEntry.body);
    addressDetailsTemplate.find("#publishDate").html(addressEntry.publishDate.parseJSONDate().toLocaleDateString());

    if (addressEntry.images.length > 0) {
        var firstImage = addressEntry.images[0];
        var image = addressDetailsTemplate.find("#image");
        image.attr("src", "/globals/resources/img/rit.modules.addresses/"+firstImage.url);
        image.attr("alt", firstImage.description);
    }

    this._element.append(addressDetailsTemplate);
};




// CountriesModule
function CountriesModule(eventBus, moduleIdentification, element) {
    this._eventBus = eventBus;
    this._moduleIdentification = moduleIdentification;
    this._element = element;
}
CountriesModule.prototype.setParent = function(parent) {
    this._parent = parent;
};
CountriesModule.prototype.reload = function(isInitialPageReload) {
    var me = this;

    // $.post(...)
    $.getJSON(
              urlBuilder.CreatePageModuleUrl(this._parent.getCultureUi(), this._parent.getPath(), "/ritAddress/" + "Dealers_KesslerAddresses" + /* todo: this._moduleIdentification.getInstanceNameOnPage() */ "/Addresses/GetCountries", urlBuilder.Part.Data),
              // data...
              function(countriesData) { me.render(countriesData); }
    );
};
CountriesModule.prototype.render = function(countriesData) {
    this._element.empty();
    var templateName = this._moduleIdentification.getTemplateName();

    // Template parse example:
    var unparsedTemplates = document.getElementById("templates");
    var unparsedTemplatesText = unparsedTemplates.text;
    if (unparsedTemplatesText.startsWith("<![CDATA[")) {
        unparsedTemplatesText = unparsedTemplatesText.substring("<![CDATA[".length, unparsedTemplatesText.length - "]]>".length);
    }

    var ul = $(document.createElement("ul"));
    this._element.append(ul);

    var me = this;

    for (var i = 0; i < countriesData.entries.length; i++) {
        var countriesEntry = countriesData.entries[i];

        // todo als: not very beautiful. let browser parse on page request and just clone here...
        var parsedTemplates = $(unparsedTemplatesText);

        var mainCountriesEntryTemplateId = templateName + "_entry.template";
        mainCountriesEntryTemplateId = mainCountriesEntryTemplateId.replaceAll("\\.", "\\.");
        var mainCountriesEntryTemplate = parsedTemplates.find("#" + mainCountriesEntryTemplateId);

        // todo als: does dom id lookup work fast at this point? (=> because not yet part of the dom?)
        // so document.getElementById() is not available.
        var title = mainCountriesEntryTemplate.find("#entry\\.name");
        title.html(countriesEntry.name);

        var callback = function(me, countryEntry) {
            // Required for proper closure handling. Create a new environment
            // for closure variable snapshot.
            return function() {
                me.reloadDetails(countryEntry);
                return false;
            }
        };
        callback = callback(me, countriesEntry);
        title.click(callback);

        var li = $(document.createElement("li"));
        li.append(mainCountriesEntryTemplate);
        ul.append(li);
    }

    this._element.append(ul);

    // todo als: FIX this because it only works if detail module has already been rendered!
    if (countriesData.entries.length > 0) {
        this.reloadDetails(countriesData.entries[0]);
    }
};
CountriesModule.prototype.reloadDetails = function(countriesEntryId) {
    this._eventBus.publishEvent(this, countriesEntryId);
};

// CountryDetailsModule
function CountryDetailsModule(eventBus, moduleIdentification, element) {
    this._eventBus = eventBus;
    this._moduleIdentification = moduleIdentification;
    this._element = element;

    var me = this;
    eventBus.addListener(function(sender, eventArgs) { me.reloadByData(eventArgs); } );
}
CountryDetailsModule.prototype.setParent = function(parent) {
    this._parent = parent;
};
CountryDetailsModule.prototype.reload = function(isInitialPageReload) {
    var me = this;

    // $.post(...)
    //    $.getJSON(
    //          "./staticTestData/mainCountryDetails1.data.html",
    //          // data...
    //          function(menuData) { me.render(menuData); }
    //    );
};
CountryDetailsModule.prototype.reloadById = function(countriesEntryId) {
    var me = this;

    // $.post(...)
    //    $.getJSON(
    //          "./staticTestData/mainCountryDetails" + countriesEntryId + ".data.html",
    //          // data...
    //          function(menuData) { me.render(menuData); }
    //    );
};
CountryDetailsModule.prototype.reloadByData = function(countryEntry) {
    this.render(countryEntry);
};
CountryDetailsModule.prototype.render = function(countryEntry) {
    this._element.empty();
    var templateName = this._moduleIdentification.getTemplateName();

    // Template parse example:
    var unparsedTemplates = document.getElementById("templates");
    var unparsedTemplatesText = unparsedTemplates.text;
    if (unparsedTemplatesText.startsWith("<![CDATA[")) {
        unparsedTemplatesText = unparsedTemplatesText.substring("<![CDATA[".length, unparsedTemplatesText.length - "]]>".length);
    }
    // todo als: not very beautiful. let browser parse on page request and just clone here...
    var parsedTemplates = $(unparsedTemplatesText);

    var countryDetailsTemplateId = templateName + "_template";
    countryDetailsTemplateId = countryDetailsTemplateId.replaceAll("\\.", "\\.");
    var countryDetailsTemplate = parsedTemplates.find("#" + countryDetailsTemplateId);

    // todo als: does dom id lookup work fast at this point? (=> because not yet part of the dom?)
    // so document.getElementById() is not available.
    countryDetailsTemplate.find("#title").html(countryEntry.title);
    countryDetailsTemplate.find("#body").html(countryEntry.body);
    countryDetailsTemplate.find("#publishDate").html(countryEntry.publishDate.parseJSONDate().toLocaleDateString());

    if (countryEntry.images.length > 0) {
        var firstImage = countryEntry.images[0];
        var image = countryDetailsTemplate.find("#image");
        image.attr("src", "/globals/resources/img/rit.modules.countries/"+firstImage.url);
        image.attr("alt", firstImage.description);
    }

    this._element.append(countryDetailsTemplate);
};

// DownloadsModule
function DownloadsModule(eventBus, moduleIdentification, element) {
    this._eventBus = eventBus;
    this._moduleIdentification = moduleIdentification;
    this._element = element;
}
DownloadsModule.prototype.setParent = function(parent) {
    this._parent = parent;
};
DownloadsModule.prototype.reload = function(isInitialPageReload) {
    var me = this;

    // $.post(...)
    $.getJSON(
              urlBuilder.CreatePageModuleUrl(this._parent.getCultureUi(), this._parent.getPath(), "/ritDownload/" + /*"Dealers_KesslerAddresses" +*/ this._moduleIdentification.getInstanceNameOnPage() + "/Downloads/GetDownloads", urlBuilder.Part.Data),
              // data...
              function(downloadsData) { me.render(downloadsData); }
    );
};
DownloadsModule.prototype.render = function(downloadsData) {
    this._element.empty();
    var templateName = this._moduleIdentification.getTemplateName();

    // Template parse example:
    var unparsedTemplates = document.getElementById("templates");
    var unparsedTemplatesText = unparsedTemplates.text;
    if (unparsedTemplatesText.startsWith("<![CDATA[")) {
        unparsedTemplatesText = unparsedTemplatesText.substring("<![CDATA[".length, unparsedTemplatesText.length - "]]>".length);
    }

    var ul = $(document.createElement("ul"));
    this._element.append(ul);

    var me = this;

    for (var i = 0; i < downloadsData.entries.length; i++) {
        var downloadsEntry = downloadsData.entries[i];

        // todo als: not very beautiful. let browser parse on page request and just clone here...
        var parsedTemplates = $(unparsedTemplatesText);

        var mainDownloadsEntryTemplateId = templateName + "_entry.template"
        mainDownloadsEntryTemplateId = mainDownloadsEntryTemplateId.replaceAll("\\.", "\\.");
        var mainDownloadsEntryTemplate = parsedTemplates.find("#" + mainDownloadsEntryTemplateId);

        // todo als: does dom id lookup work fast at this point? (=> because not yet part of the dom?)
        // so document.getElementById() is not available.
        var title = mainDownloadsEntryTemplate.find("#entry\\.title");
        title.html(downloadsEntry.title);

        var description = mainDownloadsEntryTemplate.find("#entry\\.description");
        description.html(downloadsEntry.description);

        var fileLink = mainDownloadsEntryTemplate.find("#entry\\.file");

        var url = urlBuilder.CreatePageModuleUrlWithParams(
            this._parent.getCultureUi(),
            this._parent.getPath(),
            "/ritDownload/" + this._moduleIdentification.getInstanceNameOnPage() + "/Downloads/GetDownload",
            urlBuilder.Part.Data);
        url += "?Id=\"" + downloadsEntry.identificationName + "\"";
        fileLink.attr("href", url);
        fileLink.attr("title", ""); //downloadsEntry.description);
        fileLink.attr("id", fileLink.attr("id") + i); // make unique id
        fileLink.html(downloadsEntry.title);

        var li = $(document.createElement("li"));
        li.append(mainDownloadsEntryTemplate);
        ul.append(li);
    }

    this._element.append(ul);

    // todo als: FIX this because it only works if detail module has already been rendered!
    if (downloadsData.entries.length > 0) {
        this.reloadDetails(downloadsData.entries[0]);
    }
};
DownloadsModule.prototype.reloadDetails = function(downloadsEntryId) {
    this._eventBus.publishEvent(this, downloadsEntryId);
};


// TeamMemberListModule
function TeamMemberListModule(eventBus, moduleIdentification, element) {
    this._eventBus = eventBus;
    this._moduleIdentification = moduleIdentification;
    this._element = element;

    this._items = null;
    this._selectedItem = null;
}
TeamMemberListModule.prototype.setParent = function(parent) {
    this._parent = parent;
};
TeamMemberListModule.prototype.reload = function(isInitialPageReload) {
    var me = this;

    // $.post(...)
    $.getJSON(
              urlBuilder.CreatePageModuleUrl(this._parent.getCultureUi(), this._parent.getPath(), "/ritTeamMember/" + this._moduleIdentification.getInstanceNameOnPage(), urlBuilder.Part.Data),
              // data...
              function(teamMembersData) { me.render(teamMembersData); me.restoreUriState(me._parent.getModuleInstanceUriState(me)); }
    );
};
TeamMemberListModule.prototype.render = function(teamMembersData) {
    this._items = teamMembersData.entries;

    this._element.empty();
    var templateName = this._moduleIdentification.getTemplateName();

    // Template parse example:
    var unparsedTemplates = document.getElementById("templates");
    var unparsedTemplatesText = unparsedTemplates.text;
    if (unparsedTemplatesText.startsWith("<![CDATA[")) {
        unparsedTemplatesText = unparsedTemplatesText.substring("<![CDATA[".length, unparsedTemplatesText.length - "]]>".length);
    }

    var ul = $(document.createElement("ul"));
    this._element.append(ul);

    var me = this;

    for (var i = 0; i < this._items.length; i++) {
        var teamMembersEntry = this._items[i];

        // todo als: not very beautiful. let browser parse on page request and just clone here...
        var parsedTemplates = $(unparsedTemplatesText);

        var mainTeamMemberListEntryTemplateId = templateName + "_entry.template";
        mainTeamMemberListEntryTemplateId = mainTeamMemberListEntryTemplateId.replaceAll("\\.", "\\.");
        var mainTeamMemberListEntryTemplate = parsedTemplates.find("#" + mainTeamMemberListEntryTemplateId);
        // todo als: does dom id lookup work fast at this point? (=> because not yet part of the dom?)
        // so document.getElementById() is not available.
        var title = mainTeamMemberListEntryTemplate.find("#entry\\.link");
        title.attr("href", "#!" + this._moduleIdentification.getInstanceNameOnPage() + ".lidn=" + teamMembersEntry.lidn);
        mainTeamMemberListEntryTemplate.find("#entry\\.firstName").html(teamMembersEntry["firstName"]);
        mainTeamMemberListEntryTemplate.find("#entry\\.lastName").html(teamMembersEntry["lastName"]);

        for (var imageIndex = 0; imageIndex < teamMembersEntry.images.length; imageIndex++) {
            var image = teamMembersEntry.images[imageIndex];
            var imageElement = mainTeamMemberListEntryTemplate.find("#entry\\.image"+imageIndex.toString());
            if (imageElement.length == 0)
                continue;
            imageElement.attr("src", "/globals/resources/img/rit.modules.teamMembers/"+image.url);
            imageElement.attr("alt", image.description);
            imageElement.attr("title", image.description);
        }

//        var a = $(document.createElement("a"));
//        a.attr("href", "/teamMembers/todo");
//        a.html(teamMembersEntry.title);

        var callback = function(me, teamMemberEntry) {
            // Required for proper closure handling. Create a new environment
            // for closure variable snapshot.
            return function() {
                me.reloadDetails(teamMemberEntry);
                return true;
            }
        };
        callback = callback(me, teamMembersEntry);
        title.click(callback);

        var li = $(document.createElement("li"));
        //li.append(a);
        li.append(mainTeamMemberListEntryTemplate);
        ul.append(li);
    }

    this._element.append(ul);
};
TeamMemberListModule.prototype.reloadDetails = function(teamMembersEntry) {
    this._eventBus.publishEvent(this, teamMembersEntry);
    this._selectedItem = teamMembersEntry;
};
TeamMemberListModule.prototype.storeUriState = function(localeIndependent) {
    if ((this._selectedItem == null) || (this._selectedItem === this.getDefaultSelectedEntry()))
        return null;
    if (localeIndependent) {
        return { "bid": this._selectedItem.bid };
    }
    else {
        return { "lidn": this._selectedItem.lidn };
    }
};
TeamMemberListModule.prototype.restoreUriState = function(state) {
    if (state == null)
        return this.selectDefaultEntry();

    var i;
    if (state.bid != undefined) {
        for (i = 0; i < this._items.length; i++)
        {
            if (this._items[i].bid == state.bid)
            {
                this.reloadDetails(this._items[i]);
                return true;
            }
        }
    }
    else if (state.lidn != undefined) {
        for (i = 0; i < this._items.length; i++)
        {
            if (this._items[i].lidn == state.lidn)
            {
                this.reloadDetails(this._items[i]);
                return true;
            }
        }
    }

    return this.selectDefaultEntry();
};
TeamMemberListModule.prototype.getDefaultSelectedEntry = function() {
    if (this._items == null)
        return null;

    if (this._items.length > 0)
        return this._items[0];

    return null;
};
TeamMemberListModule.prototype.selectDefaultEntry = function() {
    var entry = this.getDefaultSelectedEntry();
    if (entry == null)
        return false;

    // Implicitly select first entry.
    this.reloadDetails(entry);
    return true;
};

// TeamMemberDetailsModule
function TeamMemberDetailsModule(eventBus, moduleIdentification, element) {
    this._eventBus = eventBus;
    this._moduleIdentification = moduleIdentification;
    this._element = element;

    var me = this;
    eventBus.addListener(function(sender, eventArgs) { me.reloadByData(eventArgs); } );
}
TeamMemberDetailsModule.prototype.setParent = function(parent) {
    this._parent = parent;
};
TeamMemberDetailsModule.prototype.reload = function(isInitialPageReload) {
    var me = this;

    // $.post(...)
    //    $.getJSON(
    //          "./staticTestData/mainTeamMemberDetails1.data.html",
    //          // data...
    //          function(menuData) { me.render(menuData); }
    //    );
};
TeamMemberDetailsModule.prototype.reloadById = function(teamMembersEntryId) {
    var me = this;

    // $.post(...)
    //    $.getJSON(
    //          "./staticTestData/mainTeamMemberDetails" + teamMembersEntryId + ".data.html",
    //          // data...
    //          function(menuData) { me.render(menuData); }
    //    );
};
TeamMemberDetailsModule.prototype.reloadByData = function(teamMemberEntry) {
    this.render(teamMemberEntry);
};
TeamMemberDetailsModule.prototype.render = function(teamMemberEntry) {
    this._element.empty();
    var templateName = this._moduleIdentification.getTemplateName();

    // Template parse example:
    var unparsedTemplates = document.getElementById("templates");
    var unparsedTemplatesText = unparsedTemplates.text;
    if (unparsedTemplatesText.startsWith("<![CDATA[")) {
        unparsedTemplatesText = unparsedTemplatesText.substring("<![CDATA[".length, unparsedTemplatesText.length - "]]>".length);
    }
    // todo als: not very beautiful. let browser parse on page request and just clone here...
    var parsedTemplates = $(unparsedTemplatesText);

    var teamMemberDetailsTemplateId = templateName + "_template";
    teamMemberDetailsTemplateId = teamMemberDetailsTemplateId.replaceAll("\\.", "\\.");
    var teamMemberDetailsTemplate = parsedTemplates.find("#" + teamMemberDetailsTemplateId);

    // todo als: does dom id lookup work fast at this point? (=> because not yet part of the dom?)
    // so document.getElementById() is not available.
    teamMemberDetailsTemplate.find("#function").html(teamMemberEntry["function"]);
    teamMemberDetailsTemplate.find("#firstName").html(teamMemberEntry.firstName);
    teamMemberDetailsTemplate.find("#lastName").html(teamMemberEntry.lastName);
    teamMemberDetailsTemplate.find("#description").html(teamMemberEntry.description);

    for (var imageIndex = 0; imageIndex < teamMemberEntry.images.length; imageIndex++) {
        var image = teamMemberEntry.images[imageIndex];
        var imageElement = teamMemberDetailsTemplate.find("#image"+imageIndex.toString());
        if (imageElement.length == 0)
            continue;
        imageElement.attr("src", "/globals/resources/img/rit.modules.teamMembers/"+image.url);
        imageElement.attr("alt", image.description);
        imageElement.attr("title", image.description);
    }

    this._element.append(teamMemberDetailsTemplate);
};


// LanguageSelectorModule
function LanguageSelectorModule(element) {
    this._element = element;
}
LanguageSelectorModule.prototype.setParent = function(parent) {
    this._parent = parent;
};
LanguageSelectorModule.prototype.reload = function(isInitialPageReload) {
    var me = this;

    me.render();
    // $.post(...)
    //    $.getJSON(
    //          "./staticTestData/mainAnnouncementDetails1.data.html",
    //          // data...
    //          function(menuData) { me.render(menuData); }
    //    );
};
LanguageSelectorModule.prototype.render = function() {
    this._element.empty();

    var callback = function(page, cultureName) {
            // Required for proper closure handling. Create a new environment
            // for closure variable snapshot.
            return function() {
                page.setCulture(cultureName);
                return false;
            }
    };

    var reloadCallback;
    var de = $("<li><a href=\"#\">DE</a></li>");
    reloadCallback = callback(this._parent, "de-CH");
    de.click(reloadCallback);

    var en = $("<li class=\"lastChild\"><a href=\"#\">EN</a></li>");
    reloadCallback = callback(this._parent, "en-US");
    en.click(reloadCallback);

    var ul = $("<ul />");
    ul.append(de);
    ul.append(en);
    this._element.append(ul);
};

// Authentication module
function AuthenticationModule() {
    // todo als: fix this!
    this._parent = window.pageModule;
}

AuthenticationModule.prototype.signInFromForm = function() {
    var username = $("#tbxUsername").val();
    var password = $("#tbxPassword").val();

    this.signIn(username, password);
};

AuthenticationModule.prototype.signIn = function(username, password) {
    var me = this;

    var authenticationContainer = {
        type: "rit.core.authentication.IAuthenticationRequest",
        username: username,
        password: password
    };

    var serializedData = JSON.stringify(authenticationContainer);
    $.post(
        urlBuilder.CreatePageModuleUrl(
                this._parent.getCultureUi(),
                this._parent.getPath(),
                "/ritAuthentication/" + "LoginInstance" + /*this._moduleIdentification.getInstanceNameOnPage() + */"/Authentication/SignIn",
                urlBuilder.Part.Data),
        { data: serializedData },
        function(data, textStatus, xmlHttpRequest) { me.onAuthentication(data, textStatus, xmlHttpRequest); },
        "json");
};

AuthenticationModule.prototype.onAuthentication = function(data, textStatus, xmlHttpRequest) {
    var statusCode = xmlHttpRequest.status;
    if (data.authenticationResult == "Success") {
        $("#signInFailureMessage").hide();
        window.location = data.redirectUrl;
    }
    else if (data.authenticationResult == "Fail") {
        $("#signInFailureMessage").show();
    }
    /*
    if (statusCode == 403) {
        // 403 Forbidden
    }
    else if (statusCode == 302) {
        // 302 Temporary Redirection
    }
    else {
        // Unknown status code => treat as unknown error.
    }
    */
};

AuthenticationModule.prototype.signOut = function() {
    var me = this;

    var authenticationContainer = {
        type: "rit.core.authentication.IAuthenticationRequest"
    };

    var serializedData = JSON.stringify(authenticationContainer);
    $.post(
        urlBuilder.CreatePageModuleUrl(
                this._parent.getCultureUi(),
                this._parent.getPath(),
                "/ritAuthentication/" + "LoginInstance" + /*this._moduleIdentification.getInstanceNameOnPage() + */"/Authentication/SignOut",
                urlBuilder.Part.Data),
        { data: serializedData },
        function(data, textStatus, xmlHttpRequest) { me.onSignedOut(data, textStatus, xmlHttpRequest); },
        "json");
};

AuthenticationModule.prototype.onSignedOut = function(data, textStatus, xmlHttpRequest) {
    if (xmlHttpRequest.status == 200) {
        // Ok
        window.location = "/";
    }
    else {
        // Unknown status code => treat as unknown error.
    }
};

// UnknownModule
function UnknownModule(eventBus, moduleIdentification, element) {
    this._eventBus = eventBus;
    this._element = element;
    this._moduleIdentification = moduleIdentification;
}
UnknownModule.prototype.setParent = function(parent) {
    this._parent = parent;
};
UnknownModule.prototype.reload = function(isInitialPageReload) {
    this._element.html("Unknown module (instance name: " + this._moduleIdentification.getInstanceName() + ", instance name on page:" + this._moduleIdentification.getInstanceNameOnPage() + ").");
};
