Source: kepler.js

/**
 * Constructs an instance of Kepler API object.
 * The object is a general class that is used to make API calls to the server
 * side application in all of the modules.
 * Each module is a specific JavaScript code that is loaded by the HTML pages.
 *
 * @constructor
 *
 * @author Joonas Konki
 * @author Anu Koskela
 * @author Mikko Kuhno
 * @author Henrik Paananen
 * @author Atte Räty
 * @license BSD 3-clause, see LICENSE for more details.
 * @copyright 2015 Kepler project authors
 */
function Kepler() {
    var kepler = this;

    var host = window.location.origin;

    /**
     * Adds done and fail callbacks to the specified AJAX call.
     */
    function addCallbacksToCall(call, callback) {
        call.done(function (data, status) {
            if (data.status === 'ok') {
                console.log(data);
                callback(data);
            } else if (data.status === 'error') {
                //alert("Error: " + data.reason + ": " + data.message);
                //console.log("Error: " + data.reason + ": " + data.message);
                kepler.showError(data.reason, data.message);
            } else {
                console.error("Server error: " + status);
            }
        });
        call.fail(function () {
            console.error("Could not connect to Kepler");
        });
        return call;
    }

    /**
     * The primary utility function that makes Kepler AJAX calls
     * to the server side application.
     *
     * @param func - The name of the API function that will be called.
     * @param data - The parameters of the function passed as
     *             the body of POST request.
     * @returns Returns the created jQuery AJAX call.
     */
    function makeCall(func, callback, params) {
        var url = host + '/call/' + func;

        console.log('Kepler call: ' + url);
        var data = JSON.stringify(params);
        //if (data) console.log('params: ' + data);

        var call = $.ajax({
            type: 'POST',
            url: url,
            data: data,
            contentType: "application/json",
            dataType: 'json'
        });
        return addCallbacksToCall(call, callback);
    }

    /**
     * Shows an error message in the element specified by the ID.
     * If no ID is given, the element with ID "#error" is used.
     *
     * @param {string} reason The reason of the error.
     * @param {string} message The error message.
     * @param {string} elem_id The element ID where the error should be shown.
     */
    this.showError = function(reason, message, elem_id) {
        if (elem_id === undefined)
            elem_id = '#error';

        if (!message)
            $(elem_id).text(reason);
        else
            $(elem_id).text(reason + ": " + message);
    };

    // Message fade out delay in milliseconds.
    var messageDelayMs = 5000;

    /**
     * Shows the result message in the element specified by the ID.
     *
     * @param {object} result The Kepler API result object.
     * @param {string} elem_id The element ID where the message should be shown.
     */
    this.showResult = function(result, elem_id) {
        var elem = $(elem_id);

        var msg = result.message;
        if (!msg) {
            if (result.success) {
                msg = kepler.translate('msg.success');
            } else {
                msg = kepler.translate('msg.failure');
            }
        }

        if (result.reason) {
            msg += " " + result.reason;
        }

        if (result.success) {
            elem.addClass("bg-success");
            elem.removeClass("bg-danger");
        } else {
            elem.addClass("bg-danger");
            elem.removeClass("bg-success");
        }

        elem.text(msg);
        elem.show();
        elem.delay(messageDelayMs).fadeOut();
    };

    /**
     * Gets all notice board notes.
     *
     * @param callback The handler that is called after the request returns.
     */
    this.getNoticeBoard = function(callback) {
        return makeCall('get_notice_board', callback);
    };

    /**
     * Gets all notice board notes with additional information.
     *
     * @param callback The handler that is called after the request returns.
     */
    this.getNoticeBoardNotes = function(callback) {
        return makeCall('get_notice_board_notes', callback);
    };

    /**
     * Adds a new notice board note.
     *
     * @param callback  - The handler that is called after the request returns.
     */
    this.addNoticeBoardNote = function(params, callback) {
        return makeCall('add_notice_board_note', callback, params);
    };

    /**
     * Edits an existing notice board note.
     *
     * @param callback  - The handler that is called after the request returns.
     */
    this.editNoticeBoardNote = function(params, callback) {
        return makeCall('edit_notice_board_note', callback, params);
    };

    /**
     * Hides the notice board note.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.hideNoticeBoardNote = function(params, callback) {
        return makeCall('hide_notice_board_note', callback, params);
    };

    /**
     * Shows the notice board note.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.showNoticeBoardNote = function(params, callback) {
        return makeCall('show_notice_board_note', callback, params);
    };

    /**
     * Gets the information related to the current user.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.getUserInfo = function(callback) {
        return makeCall('get_user_info', callback);
    };

    /**
     * Gets all the groups where the current user belongs to.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.getUserGroups = function(callback) {
        return makeCall('get_user_groups', callback);
    };

    /**
     * Creates a new group for the current user.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.addUserGroup = function(params, callback) {
        return makeCall('add_user_group', callback, params);
    };

    /**
     * Deletes (hides) an existing group of the current user from the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.deleteUserGroup = function(params, callback){
        return makeCall('delete_user_group', callback, params);
    };

    /**
     * Gets the reservations of the current user.
     * @param callback - The handler that is called after the request returns.
     * @param err_elem_id - The ID of the element in which possible
     *                    error messages are shown.
     */
    this.getReservationsUser = function(callback) {
        return makeCall('get_reservations_user', callback);
    };

    /**
     * Gets the list of all units from the database.
     * Additionally, returns the list of all unit types in the system.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.getUnits = function(callback) {
        return makeCall('get_units', callback);
    };

    /**
     * Gets the list of all units from the database with additional information
     * needed for editing the units.
     *
     * @param callback - The handler that is called after the request returns.'
     */
    this.getUnitsEditable = function(callback) {
        return makeCall('get_units_editable', callback);
    };

    /**
     * Edits an existing unit in the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.editUnit = function(params, callback) {
        return makeCall('edit_unit', callback, params);
    };

    /**
     * Adds a new unit to the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.addUnit = function(params, callback) {
        return makeCall('add_unit', callback, params);
    };

    /**
     * Deletes (hides) an existing unit from the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.deleteUnit = function(params, callback) {
        return makeCall('delete_unit', callback, params);
    };

    /**
     * Gets the list of unit groups from the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.getUnitGroups = function(callback) {
        return makeCall('get_unit_groups', callback);
    };

    /**
     * Edits an existing unit group in the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.editUnitGroup = function(params, callback) {
        return makeCall('edit_unit_group', callback, params);
    };

    /**
     * Adds a new unit group to the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.addUnitGroup = function(params, callback) {
        return makeCall('add_unit_group', callback, params);
    };

    /**
     * Deletes (hides) an existing unit group from the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.deleteUnitGroup = function(params, callback) {
        return makeCall('delete_unit_group', callback, params);
    };

    /**
     * Gets the list of all resources and their statuses from the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.getResources = function(callback) {
        return makeCall('get_resources', callback);
    };

    /**
     * Edits an existing resource in the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.editResource = function(params, callback) {
        return makeCall('edit_resource', callback, params);
    };

    /**
     * Adds a new resource to the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.addResource = function(params, callback) {
        return makeCall('add_resource', callback, params);
    };

    /**
     * Deletes (hides) an existing resource from the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.deleteResource = function(params, callback) {
        return makeCall('delete_resource', callback, params);
    };

    /**
     * Gets the list of time slots optionally filtered by a time span
     * and the study level.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.getTimeSlots = function(params, callback) {
        return makeCall('get_time_slots', callback, params);
    };

    /**
     * Adds a new reservable time slot to the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.addTimeSlot = function(params, callback){
        return makeCall('add_time_slot', callback, params);
    };

    /**
     * Deletes (hides) an existing reservable time slot to the database.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.deleteTimeSlot = function(params, callback){
        return makeCall('delete_time_slot', callback, params);
    };

    /** Gets the information needed in the time slot adding and editing view.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.getTimeSlotEditInfo = function(callback) {
        return makeCall('get_time_slot_edit_info', callback);
    };


    /** Gets the calendar events of the current user.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.getCalendarEvents = function(params, callback) {
        return makeCall('get_calendar_events', callback, params);
    };

    /** Gets the list of time slot events optionally filtered by
     *  a time span
     *  and the specified unit.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.getTimeSlotEvents = function(params, callback){
        return makeCall('get_time_slot_events', callback, params);
    };

    /** Adds a new reservation for the current user.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.addReservation = function(params, callback){
        return makeCall('add_reservation', callback, params);
    };

    /** Deletes (hides) an existing reservation from the current user.
     *
     * @param callback - The handler that is called after the request returns.
     */
    this.deleteReservation = function(params, callback){
        return makeCall('delete_reservation', callback, params);
    };

    /** Searches for the user that matches the search string.
     *  The call returns the user that matches the search string.
     *  If no user or many users are found the call returns null.
     *
     * @param params - The email or account name used for searching a user.
     * @param callback - The handler that is called after the request returns.
     */
    this.searchUser = function(params, callback){
        return makeCall('search_user', callback, params);
    };

    /** Sets the user default language based on the specified locale name.
     *
     * @param locale_name - The locale name of the language to be set.
     * @param callback - The handler that is called after the request returns.
     */
    this.setUserLanguage = function(locale_name, callback) {
        var params = {'locale_name': locale_name};
        return makeCall('set_user_language', callback, params);
    };

    /** Sets the session language based on the given locale name.
     *
     * @param locale_name - The locale name of the language to be set.
     */
    this.setLanguage = function(locale_name) {
        var params = {'locale_name': locale_name};
        function callback(data) {
            location.reload(true);
        }
        return makeCall('set_session_language', callback, params);
    };


    /**
     * Returns the parameter with the specified name from the URL
     * query parameters.
     *
     * @param name - The URL query parameter's name.
     */
    this.getQueryParam = function(name) {
        var queryString = window.location.search.substring(1);
        var variables = queryString.split('&');
        for (var i = 0; i < variables.length; i++) {
            var param = variables[i].split('=');
            if (param[0] == name) {
                return param[1];
            }
        }
    };

    /**
     * Returns the dynamic translation matching the given translation ID.
     *
     * @param translation_id - The translation ID of the translation text.
     */
    this.translate = function(translation_id) {
        if (_translations[translation_id] === undefined)
            return translation_id;
        return _translations[translation_id];
    };

    /**
     * Formats a Date object to common Finnish time string representation of
     * hours and minutes (HH:mm).
     *
     * @param date - The Date object to be formatted.
     */
    this.datetimeString = function(date) {
        // TODO (Anu) Add a separator between date and time.
        return date.toLocaleDateString('fi', {hour:'2-digit',minute:'2-digit'});
        // return date.toLocaleDateString(navigator.language,
        //    {hour:'2-digit', minute:'2-digit'});
    };

    /**
     * Formats a Date object to common Finnish date string representation.
     *
     * @param date - The Date object to be formatted.
     */
    this.dateString = function(date) {
        return date.toLocaleDateString('fi');
    };

    /**
     * Formats the given Date object to common Finnish time string
     * representation.
     *
     * @param date - The Date object to be formatted.
     */
    this.timeString = function(date) {
        return date.toLocaleTimeString('fi', {hour:'2-digit',minute:'2-digit'});
    };

    /**
     * Converts a date string to a Date object.
     *
     * @param date - The date string to be converted.
     * @returns The Date object corresponding to given string.
     */
    this.stringToDate = function(dateStr) {
        return moment(dateStr, moment.ISO_8601).toDate();
    };

    /**
     * The method is used to format ISO8601 date strings to UI strings.
     *
     * @param date - Either a date string or a Date object.
     * @returns The locale formatted time string.
     */
    this.formatDate = function(date) {
        if (typeof(date) === "string") {
            date = kepler.stringToDate(date);
        }
        return kepler.dateString(date);
    };

    /**
     * The method is used to format ISO8601 date strings to UI strings.
     *
     * @param date - Either a date string or a Date object.
     * @returns The locale formatted time string.
     */
    this.formatDatetime = function(date) {
        if (typeof(date) === "string") {
            date = kepler.stringToDate(date);
        }
        return kepler.datetimeString(date);
    };

    /**
     * Formats two Date objects to a string representation of a time interval.
     *
     * @param start - A Date object for the start of the interval.
     * @param end - A Date object for the end of the interval.
     * @returns A formatted string representing the interval.
     */
    this.datetimeIntervalString = function(start, end) {
        var start_date = kepler.stringToDate(start);
        var end_date = kepler.stringToDate(end);
        return kepler.dateString(start_date) + ' ' +
            kepler.timeString(start_date) + ' - ' + kepler.timeString(end_date);
    };

    /**
    * Replaces the given element in the HTML page temporarily with a new div
    * that contains a gif animation while the data is being fetched from
    * the database.
    *
    * @param elemId - The id of the HTML element where the animation is placed.
    * @param loadFunc - The handler that is called after the request returns.
    * @returns The original area when a response from the database has
    *          been received.
    */
    this.createLoadElement = function(elemId, loadFunc) {
        var parent = $('<div>');
        var element = $(elemId);
        var loadIndicator = $('<img>');

        loadIndicator.attr('src', '/static/ajax-loader.gif');
        loadIndicator.css('position', 'absolute');
        loadIndicator.hide();

        element.replaceWith(parent);

        parent.append(element);
        parent.append(loadIndicator);


        function make_indicatorOffset(loadIndicator, parent) {
            return function () {
                var par = parent.offset();

                var left = par.left+(parent.width()-loadIndicator.width())/2.0;
                var top = par.top+(parent.height()-loadIndicator.height())/2.0;

                loadIndicator.offset({ left: left, top: top });
            };
        }

        $(window).load(make_indicatorOffset(loadIndicator, parent));
        $(window).resize(make_indicatorOffset(loadIndicator, parent));

        var par = parent.position();
        var left = par.left+(parent.width()-loadIndicator.width())/2.0;
        var top = par.top+(parent.height()-loadIndicator.height())/2.0;
        loadIndicator.offset({ left: left, top: top });

        // make_indicatorOffset(loadIndicator, parent)();

        //loadIndicator.load(make_indicatorOffset(loadIndicator, parent));
        //loadIndicator.load();

        return {
            element: element,
            loadIndicator: loadIndicator,
            load: function () {
                loadIndicator.show();
                var call = loadFunc(element);
                call.always(function () {
                    //loadIndicator.hide();
                    loadIndicator.fadeOut();
                });
            }
        };
    };

    /**
    * Creates a clone of an object.
    * @param obj - The object that has a tree-like structure.
    */
    this.clone = function (obj) {
        var copy;

        // Handle the 3 simple types, and null or undefined
        if (null === obj || "object" != typeof obj) return obj;

        // Handle Date
        if (obj instanceof Date) {
            copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        }

        // Handle Array
        if (obj instanceof Array) {
            copy = [];
            for (var i = 0, len = obj.length; i < len; i++) {
                copy[i] = kepler.clone(obj[i]);
            }
            return copy;
        }

        // Handle Object
        if (obj instanceof Object) {
            copy = {};
            for (var attr in obj) {
                if (obj.hasOwnProperty(attr))copy[attr]=kepler.clone(obj[attr]);
            }
            return copy;
        }

        throw new Error("Unable to copy obj! Its type isn't supported.");
    };


    // Set handler to initialise kepler functionality after document is loaded.
    $(document).ready(function() {
        // Set handlers for click event to the "change language" links.
        $('#lang_fi').click(function() { kepler.setLanguage('fi'); });
        $('#lang_en').click(function() { kepler.setLanguage('en'); });

        // Set the title of the document based on the current page header.
        var pageHeader = $('#pageHeader');
        $('title').text("Kepler - " + pageHeader.text());
    });
}

// Initialise the instance of Kepler.
var kepler = new Kepler();


// TODO(henrik): Remove alerts when not needed (inline JS in template files).
//==================
function notImplementedMsg() {
    alert(kepler.translate('msg.not_implemented'));
}