All Downloads are FREE. Search and download functionalities are using the official Maven repository.

web.script.plugin_model.js Maven / Gradle / Ivy

function TopicmapsPluginModel() {

    // --------------------------------------------------------------------------------------------------- Private State

    var topicmap                    // Selected topicmap (TopicmapViewmodel object)     \ updated together by
    var topicmap_renderer           // The topicmap renderer of the selected topicmap   / set_selected_topicmap()
    var topicmap_renderers = {}     // Registered topicmap renderers (key: renderer URI, value: TopicmapRenderer object)
                                    // Initialized by register_topicmap_renderers()
    var topicmap_topics = {}        // Loaded topicmap topics, grouped by workspace, an object:
                                    //   {
                                    //     workspaceId: [topicmapTopic]     # real Topic objects
                                    //   }
                                    // Updated by fetch_topicmap_topics()
    var selected_topicmap_ids = {}  // ID of the selected topicmap, per-workspace, an object:
                                    //   {
                                    //     workspaceId: selectedTopicmapId
                                    //   }
    var topicmap_cache = {}         // Loaded topicmaps (key: topicmap ID, value: TopicmapViewmodel object)

    // ------------------------------------------------------------------------------------------------------ Public API

    this.init = init
    this.init_2 = init_2
    this._get_initial_topicmap_id = function() {return _topicmap_id}
    this._drop_initial_state = drop_initial_state

    this.get_topicmap = function() {return topicmap}
    this.set_selected_topicmap = set_selected_topicmap
    this.select_topicmap_for_workspace = select_topicmap_for_workspace
    this.delete_topicmap = delete_topicmap
    this.reload_topicmap = reload_topicmap

    this.get_topicmap_topics = get_topicmap_topics
    this.fetch_topicmap_topics = fetch_topicmap_topics
    this.clear_topicmap_topics = clear_topicmap_topics

    this.get_topicmap_renderer = get_topicmap_renderer
    this.get_current_topicmap_renderer = function() {return topicmap_renderer}
    this.iterate_topicmap_renderers = iterate_topicmap_renderers

    this.iterate_topicmap_cache = iterate_topicmap_cache
    this.clear_topicmap_cache = clear_topicmap_cache

    this.get_selected_workspace_id = get_selected_workspace_id

    // ----------------------------------------------------------------------------------------------- Private Functions

    function get_selected_workspace_id() {
        return dm4c.get_plugin("de.deepamehta.workspaces").get_selected_workspace_id()
    }



    // === Initial State ===

    // Short-term init state
    var _topicmap_id
    var _topic_id

    // Called from plugin at init(1) stage.
    function init() {
        register_topicmap_renderers()
        //
        // Try to obtain a topicmap ID from browser URL, then from cookie
        var match = location.pathname.match(/\/topicmap\/(\d+)(\/topic\/(\d+))?/)
        if (match) {
            _topicmap_id = match[1]
            _topic_id    = match[3]
            console.log("Selecting topicmap " + _topicmap_id + " (ID obtained from browser URL), selected topic: " +
                _topic_id)
        } else {
            _topicmap_id = js.get_cookie("dm4_topicmap_id")
            if (_topicmap_id) {
                console.log("Selecting topicmap " + _topicmap_id + " (ID obtained from cookie)")
            }
        }
        //
        if (_topicmap_id && !is_valid_topicmap_id(_topicmap_id)) {
            drop_initial_state()    // prompt for the fallback at init(3) stage
        }
        // Note: if at this stage a valid topicmap ID is available already the Workspaces plugin will select the
        // corresponding workspace (at init(2) stage). Otherwise it will select the 1st best workspace and leave it to
        // the Topicmaps plugin to choose a valid topicmap ID for that workspace. This fallback takes place at init(3)
        // stage (see init_2() below).
        //
        // So, there are possible 2 situations: in one the workspace selection relies on the topicmap selection. In the
        // other the topicmap selection relies on the workspace selection. That is what makes the initialization of the
        // Topicmaps plugin so intricate.
        //
        // At this point one edge case might apply:
        //
        // A topicmap ID might be regarded as valid although it is not. This happens if a stale Topicmap topic is
        // obtained from the browser cache. This happens if the GET request's condition (If-Modified-Since) is not
        // satisfied and the response is 304 Not Modified. For this to happen it is sufficient if the pretended (stale)
        // topicmap ID refers to *some* existing DB object (topic or association!) whose modification timestamp is
        // "old enough"! This case is not detectable by the Topicmaps plugin at init(1) stage and will only be detected
        // by the Workspaces plugin (at init(2) stage). A valid topicmap ID will be finally obtained through the
        // fallback at init(3) stage (see init_2() below).

        function is_valid_topicmap_id(id) {
            try {
                var topic = dm4c.restc.request("GET", "/core/topic/" + id, undefined, undefined, undefined, undefined,
                    function() {return false}   // suppress error dialog
                )
                if (topic.type_uri != "dm4.topicmaps.topicmap") {
                    throw "it refers to a \"" + topic.type_uri + "\" topic"
                }
                return true
            } catch (e) {
                console.log("WARNING: " + id + " is not a valid topicmap ID (" + e + ")")
            }
        }
    }

    // Called from plugin at init(3) stage.
    //
    // At this stage we know a workspace is selected, and now we're ready to 1) retrieve the topicmap topics for the
    // selected workspace, and 2) perform the fallback to obtain a valid topicmap ID, if not yet achieved, and 3) select
    // the choosen topicmap finally.
    function init_2() {
        fetch_topicmap_topics()
        //
        // fallback to obtain a valid topicmap ID
        if (!_topicmap_id) {
            _topicmap_id = get_first_topicmap_id()
            console.log("Selecting topicmap " + _topicmap_id + " (ID obtained from 1st menu item)")
        }
        //
        set_selected_topicmap(_topicmap_id)
        //
        if (_topic_id) {
            try {
                topicmap.set_topic_selection(_topic_id)
            } catch (e) {
                console.log("WARNING: " + _topic_id + " is not a valid topic ID (" + e + ")")
            }
        }
    }

    // ---

    function drop_initial_state() {
        _topicmap_id = undefined
        _topic_id = undefined
    }



    // === Topicmaps ===

    /**
     * Updates the model to reflect the given topicmap is now selected. That includes setting a cookie
     * and updating 3 model objects ("topicmap", "topicmap_renderer", "selected_topicmap_ids").
     * 

* Prerequisite: the topicmap topic for the specified topicmap is already loaded/up-to-date. */ function set_selected_topicmap(topicmap_id) { // 1) set cookie // Note: the cookie must be set *before* the topicmap is loaded. // Server-side topic loading might depend on the topicmap type. js.set_cookie("dm4_topicmap_id", topicmap_id) // // 2) update "topicmap_renderer" // Note: the renderer must be set *before* the topicmap is loaded. // The renderer is responsible for loading. var renderer_uri = get_topicmap_topic_or_throw(topicmap_id).get("dm4.topicmaps.topicmap_renderer_uri") topicmap_renderer = get_topicmap_renderer(renderer_uri) // // 3) update "topicmap" and "selected_topicmap_ids" topicmap = get_topicmap(topicmap_id) selected_topicmap_ids[get_selected_workspace_id()] = topicmap_id } /** * Updates the model to reflect the given workspace is now selected. */ function select_topicmap_for_workspace(workspace_id) { // Load topicmap topics for that workspace, if not done already if (!get_topicmap_topics()) { fetch_topicmap_topics() } // Note: if the last topicmap of that workspace was deleted from "outside" we create the default // topicmap lazily (see delete_topicmap()). create_default_topicmap() // // Restore recently selected topicmap for that workspace var topicmap_id = selected_topicmap_ids[workspace_id] // Choose an alternate topicmap if either no topicmap was selected in that workspace ever, or if // the formerly selected topicmap is not available anymore. The latter is the case e.g. when the // user logs out while a public/common workspace and a private topicmap is selected. if (!topicmap_id || !get_topicmap_topic(topicmap_id)) { topicmap_id = get_first_topicmap_id() } set_selected_topicmap(topicmap_id) } /** * Updates the model to reflect the given topicmap is now deleted. * * @param workspace_id the ID of the workspace the given topicmap was assigned to. * Note: this is not necessarily the selected workspace. */ function delete_topicmap(topicmap_id, workspace_id) { var is_current_topicmap = topicmap_id == topicmap.get_id() invalidate_topicmap_cache(topicmap_id) remove_topicmap_topic(topicmap_id, workspace_id) // If the topicmap was deleted from "within" (that is the workspace the topicmap was assigned to is currently // SELECTED) we might need to select another topicmap now. This possibly requires to create a default topicmap // first. // In contrast, if the topicmap was deleted from "outside" (that is the workspace the topicmap was assigned to // is NOT currently selected) we do NOT create the default topicmap here. It would be assigned to the wrong // workspace. The default topicmap gets created as soon as the deleted topicmap's workspace is selected (see // select_topicmap_for_workspace()). if (workspace_id == get_selected_workspace_id()) { // Create a default topicmap if the user deleted the workspace's last topicmap. create_default_topicmap() // If the deleted topicmap was the CURRENT topicmap we must select another one from the topicmap menu. if (is_current_topicmap) { console.log("Deleted topicmap " + topicmap_id + " was the CURRENT topicmap of workspace " + workspace_id) set_selected_topicmap(get_first_topicmap_id()) } } return is_current_topicmap } /** * Reloads the current topicmap from DB. */ function reload_topicmap() { // Note: the cookie and the renderer are already up-to-date var topicmap_id = topicmap.get_id() invalidate_topicmap_cache(topicmap_id) topicmap = get_topicmap(topicmap_id) } // === Topicmap Topics === /** * Looks up a topicmap topic for the selected workspace. * * @return the topicmap topic, or undefined if no such topicmap is available in the selected workspace. */ function get_topicmap_topic(topicmap_id) { return js.find(get_topicmap_topics(), function(topic) { return topic.id == topicmap_id }) } /** * Looks up a topicmap topic for the selected workspace. * If no such topicmap is available in the selected workspace an exception is thrown. * * @return the topicmap topic. */ function get_topicmap_topic_or_throw(topicmap_id) { var topicmap_topic = get_topicmap_topic(topicmap_id) if (!topicmap_topic) { throw "TopicmapsPluginError: topicmap " + topicmap_id + " not found in model for workspace " + get_selected_workspace_id() } return topicmap_topic } /** * Returns the ID of the first loaded topicmap topic for the selected workspace */ function get_first_topicmap_id() { var topicmap_topic = get_topicmap_topics()[0] if (!topicmap_topic) { throw "TopicmapsPluginError: workspace " + get_selected_workspace_id() + " has no topicmaps" } return topicmap_topic.id } /** * @param workspace_id the ID of the workspace the given topicmap was assigned to. * Note: this is not necessarily the selected workspace. */ function remove_topicmap_topic(topicmap_id, workspace_id) { var deleted = js.delete(get_topicmap_topics(workspace_id), function(topic) { return topic.id == topicmap_id }) if (deleted != 1) { throw "TopicmapsPluginError: removing topicmap " + topicmap_id + " from model of workspace " + workspace_id + " failed (" + deleted + " entries deleted)" } } // --- /** * Returns the loaded topicmap topics for the given workspace. * * @param workspace_id (Optional) If not given the loaded topicmap topics for the * *selected* workspace are returned. */ function get_topicmap_topics(workspace_id) { return topicmap_topics[workspace_id || get_selected_workspace_id()] } /** * Fetches all Topicmap topics assigned to the selected workspace, and updates the model ("topicmap_topics"). */ function fetch_topicmap_topics() { var workspace_id = get_selected_workspace_id() var topics = dm4c.restc.get_assigned_topics(workspace_id, "dm4.topicmaps.topicmap", true, true) topicmap_topics[workspace_id] = dm4c.build_topics(topics) // include_childs=true, sort=true } function clear_topicmap_topics() { topicmap_topics = {} } // --- /** * Checks the model if for the selected workspace topicmap topics exists, and if not creates a new default * topicmap. *

* Prerequisite: the topicmap topics of the selected workspace are loaded already (which might result in an empty * array though). */ function create_default_topicmap() { if (!get_topicmap_topics().length) { var topicmap_topic = create_topicmap_topic("untitled") // renderer=default, private=false var workspace_id = get_selected_workspace_id() console.log("Creating default topicmap (ID " + topicmap_topic.id + ") for workspace " + workspace_id) topicmap_topics[workspace_id].push(new Topic(topicmap_topic)) } } function create_topicmap_topic(name, topicmap_renderer_uri, private) { return dm4c.restc.create_topicmap(name, topicmap_renderer_uri || "dm4.webclient.default_topicmap_renderer", private) } // === Topicmap Renderers === function register_topicmap_renderers() { // default renderer register(dm4c.topicmap_renderer) // custom renderers var renderers = dm4c.fire_event("topicmap_renderer") renderers.forEach(function(renderer) { register(renderer) }) function register(renderer) { topicmap_renderers[renderer.get_info().uri] = renderer } } function get_topicmap_renderer(renderer_uri) { var renderer = topicmap_renderers[renderer_uri] // error check if (!renderer) { throw "TopicmapsPluginError: \"" + renderer_uri + "\" is an unknown topicmap renderer" } // return renderer } function iterate_topicmap_renderers(visitor_func) { for (var renderer_uri in topicmap_renderers) { visitor_func(topicmap_renderers[renderer_uri]) } } // === Topicmap Cache === /** * Returns a topicmap from the cache. * If not in cache, the topicmap is loaded from DB (and then cached). */ function get_topicmap(topicmap_id) { var topicmap = topicmap_cache[topicmap_id] if (!topicmap) { topicmap = load_topicmap(topicmap_id) topicmap_cache[topicmap_id] = topicmap } return topicmap } /** * Loads a topicmap from DB. *

* Prerequisite: the topicmap renderer responsible for loading is already set. * * @return the loaded topicmap (a TopicmapViewmodel). */ function load_topicmap(topicmap_id) { return topicmap_renderer.load_topicmap(topicmap_id, { is_writable: dm4c.has_write_permission_for_topic(topicmap_id) }) } // --- function iterate_topicmap_cache(visitor_func) { for (var topicmap_id in topicmap_cache) { visitor_func(topicmap_cache[topicmap_id]) } } function invalidate_topicmap_cache(topicmap_id) { delete topicmap_cache[topicmap_id] } function clear_topicmap_cache() { topicmap_cache = {} } } // Enable debugging for dynamically loaded scripts: //# sourceURL=topicmaps_plugin_model.js





© 2015 - 2024 Weber Informatics LLC | Privacy Policy