mar10/fancytree

View on GitHub
demo/taxonomy-browser/taxonomy-browser-itis.js

Summary

Maintainability
D
2 days
Test Coverage
/*!
 * Fancytree Taxonomy Browser
 *
 * Copyright (c) 2015, Martin Wendt (https://wwWendt.de)
 *
 * Released under the MIT license
 * https://github.com/mar10/fancytree/wiki/LicenseInfo
 *
 * @version @VERSION
 * @date @DATE
 */

/* global Handlebars */
/* eslint-disable no-console */

(function ($, window, document) {
    "use strict";

    /*******************************************************************************
     * Private functions and variables
     */

    var taxonTree,
        searchResultTree,
        timerMap = {},
        tmplDetails, // =
        USER_AGENT = "Fancytree Taxonomy Browser/1.0",
        ITIS_URL = "//www.itis.gov/ITISWebService/jsonservice/",
        glyphOpts = {
            preset: "bootstrap3",
            map: {
                expanderClosed: "glyphicon glyphicon-menu-right", // glyphicon-plus-sign
                expanderLazy: "glyphicon glyphicon-menu-right", // glyphicon-plus-sign
                expanderOpen: "glyphicon glyphicon-menu-down", // glyphicon-collapse-down
            },
        };

    // Load and compile handlebar templates

    $.get("details.tmpl", function (data) {
        tmplDetails = Handlebars.compile(data);
    });

    /** Update UI elements according to current status
     */
    function updateControls() {
        var query = $.trim($("input[name=query]").val());

        $("#btnPin").attr("disabled", !taxonTree.getActiveNode());
        $("#btnUnpin")
            .attr("disabled", !taxonTree.isFilterActive())
            .toggleClass("btn-success", taxonTree.isFilterActive());
        $("#btnResetSearch").attr("disabled", query.length === 0);
        $("#btnSearch").attr("disabled", query.length < 2);
    }

    /**
     * Invoke callback after `ms` milliseconds.
     * Any pending action of this type is cancelled before.
     */
    function _delay(tag, ms, callback) {
        /*jshint -W040:true */
        var self = this;

        tag = "" + (tag || "default");
        if (timerMap[tag] != null) {
            clearTimeout(timerMap[tag]);
            delete timerMap[tag];
            // console.log("Cancel timer '" + tag + "'");
        }
        if (ms == null || callback == null) {
            return;
        }
        // console.log("Start timer '" + tag + "'");
        timerMap[tag] = setTimeout(function () {
            // console.log("Execute timer '" + tag + "'");
            callback.call(self);
        }, +ms);
    }

    /**
     */
    function _callItis(cmd, data) {
        return $.ajax({
            url: ITIS_URL + cmd,
            data: $.extend(
                {
                    jsonp: "itis_data",
                },
                data
            ),
            cache: true,
            headers: { "Api-User-Agent": USER_AGENT },
            jsonpCallback: "itis_data",
            dataType: "jsonp",
        });
    }

    /**
     */
    // function countMatches(query) {
    //     $("#tsnDetails").text("Loading TSN " + tsn + "...");
    //     _callItis("getAnyMatchCount", {
    //         srchKey: query
    //     }).done(function(result){
    //         console.log("updateTsnDetails", result);
    //         $("#tsnDetails").html(tmplDetails(result));
    //         updateControls();
    //     });
    // }

    /**
     */
    function updateTsnDetails(tsn) {
        $("#tsnDetails").addClass("busy");
        // $("#tsnDetails").text("Loading TSN " + tsn + "...");
        $.bbq.pushState({ tsn: tsn });

        _callItis("getFullRecordFromTSN", {
            tsn: tsn,
        }).done(function (result) {
            console.log("updateTsnDetails", result);
            $("#tsnDetails").html(tmplDetails(result)).removeClass("busy");

            updateControls();
        });
    }

    /**
     */
    function updateBreadcrumb(tsn, loadTreeNodes) {
        // var $ol = $("ol.breadcrumb").text("...");
        var $ol = $("ol.breadcrumb").addClass("busy");
        _callItis("getFullHierarchyFromTSN", {
            tsn: tsn,
        }).done(function (result) {
            console.log("updateBreadcrumb", result);
            // Convert to simpler format
            var list = [];
            // Display as <OL> list (for Bootstrap breadcrumbs)
            $ol.empty().removeClass("busy");
            $.each(result.hierarchyList, function (i, o) {
                if (o.parentTsn === tsn) {
                    return;
                } // skip direct children
                list.push(o.tsn);
                if (o.tsn === tsn) {
                    $ol.append(
                        $("<li class='active'>").append(
                            $("<span>", {
                                text: o.taxonName,
                                title: o.rankName,
                            })
                        )
                    );
                } else {
                    $ol.append(
                        $("<li>").append(
                            $("<a>", {
                                href: "#tsn=" + o.tsn,
                                text: o.taxonName,
                                title: o.rankName,
                            })
                        )
                    );
                }
            });
            if (loadTreeNodes) {
                console.log("updateBreadcrumb - loadKeyPath", list);
                taxonTree.loadKeyPath(
                    "/" + list.join("/"),
                    function (node, status) {
                        // console.log("... updateBreadcrumb - loadKeyPath", status, node);
                        switch (status) {
                            case "loaded":
                                node.makeVisible();
                                break;
                            case "ok":
                                node.setActive();
                                break;
                        }
                    }
                );
            }
        });
    }

    /**
     */
    function search(query) {
        query = $.trim(query);
        console.log("searching for '" + query + "'...");
        // NOTE:
        // It seems that ITIS searches don't work with jsonp (always return valid
        // but empty result sets).
        // When debugging, make sure cross domain requests are allowed.
        searchResultTree
            .reload({
                url: ITIS_URL + "searchForAnyMatchPaged",
                data: {
                    // jsonp: "itis_data",
                    srchKey: query,
                    pageSize: 10,
                    pageNum: 1,
                    ascend: false,
                },
                cache: true,
                // jsonpCallback: "itis_data",
                // dataType: "jsonp"
            })
            .done(function (result) {
                // console.log("search returned", result);
                // result.anyMatchList
                updateControls();
            });
    }

    /*******************************************************************************
     * Pageload Handler
     */

    $(function () {
        $("#taxonTree").fancytree({
            extensions: ["filter", "glyph", "wide"],
            filter: {
                mode: "hide",
            },
            glyph: glyphOpts,
            activeVisible: true,
            source: {
                // We could use getKingdomNames, but that returns an individual JSON format.
                // getHierarchyDownFromTSN?tsn=0 seems to work as well and allows
                // unified parsing in postProcess.
                // url: ITIS_URL + "getKingdomNames",
                url: ITIS_URL + "getHierarchyDownFromTSN",
                data: {
                    jsonp: "itis_data",
                    tsn: "0",
                },
                cache: true,
                jsonpCallback: "itis_data",
                dataType: "jsonp",
            },
            init: function (event, data) {
                updateControls();
                $(window).trigger("hashchange"); // trigger on initial page load
            },
            lazyLoad: function (event, data) {
                data.result = {
                    url: ITIS_URL + "getHierarchyDownFromTSN",
                    data: {
                        jsonp: "itis_data",
                        tsn: data.node.key,
                    },
                    cache: true,
                    jsonpCallback: "itis_data",
                    dataType: "jsonp",
                };
            },
            postProcess: function (event, data) {
                var response = data.response;

                data.node.info(response);
                data.result = $.map(response.hierarchyList, function (o) {
                    return (
                        o && {
                            title: o.taxonName,
                            key: o.tsn,
                            folder: true,
                            lazy: true,
                        }
                    );
                });
            },
            activate: function (event, data) {
                $("#tsnDetails").addClass("busy"); //text("...");
                updateControls();
                _delay("showDetails", 1000, function () {
                    updateTsnDetails(data.node.key);
                    updateBreadcrumb(data.node.key);
                });
            },
        });

        $("#searchResultTree").fancytree({
            extensions: ["table", "wide"],
            source: [{ title: "No Results." }],
            minExpandLevel: 2,
            icon: false,
            table: {
                nodeColumnIdx: 1,
            },
            postProcess: function (event, data) {
                var response = data.response;

                data.node.info("pp", response);
                data.result = $.map(response.anyMatchList, function (o) {
                    if (!o) {
                        return;
                    }
                    var res = {
                        title: o.sciName,
                        key: o.tsn,
                        author: o.author,
                        matchType: o.matchType,
                    };
                    res.commonNames = $.map(
                        o.commonNameList.commonNames,
                        function (x) {
                            return x && x.commonName
                                ? { name: x.commonName, language: x.language }
                                : undefined;
                        }
                    );
                    return res;
                });
                // console.log("pp2", data.result)
            },
            renderColumns: function (event, data) {
                var node = data.node,
                    $tdList = $(node.tr).find(">td"),
                    cnList = node.data.commonNames
                        ? $.map(node.data.commonNames, function (o) {
                                return o.name;
                          })
                        : [];

                $tdList.eq(0).text(node.key);
                $tdList.eq(2).text(cnList.join(", "));
                $tdList.eq(3).text(node.data.matchType);
                $tdList.eq(4).text(node.data.author);
            },
            activate: function (event, data) {
                _delay("activateNode", 1000, function () {
                    updateTsnDetails(data.node.key);
                    updateBreadcrumb(data.node.key);
                });
            },
        });

        taxonTree = $.ui.fancytree.getTree("#taxonTree");
        searchResultTree = $.ui.fancytree.getTree("#searchResultTree");

        // Bind a callback that executes when document.location.hash changes.
        // (This code uses bbq: https://github.com/cowboy/jquery-bbq)
        $(window).on("hashchange", function (e) {
            var tsn = $.bbq.getState("tsn");
            console.log("bbq tsn", tsn);
            if (tsn) {
                updateBreadcrumb(tsn, true);
            }
        }); // don't trigger now, since we need the the taxonTree root nodes to be loaded first

        $("input[name=query]")
            .on("keyup", function (e) {
                var query = $.trim($(this).val());

                if ((e && e.which === $.ui.keyCode.ESCAPE) || query === "") {
                    $("#btnResetSearch").trigger("click");
                    return;
                }
                if (e && e.which === $.ui.keyCode.ENTER && query.length >= 2) {
                    $("#btnSearch").trigger("click");
                    return;
                }
                $("#btnResetSearch").attr("disabled", query.length === 0);
                $("#btnSearch").attr("disabled", query.length < 2);
            })
            .trigger("focus");

        $("#btnResetSearch").click(function (e) {
            $("#searchResultPane").collapse("hide");
            $("input[name=query]").val("");
            searchResultTree.clear();
            // $("#btnSearch").attr("disabled", true);
            // $(this).attr("disabled", true);
            updateControls();
        });

        $("#btnSearch")
            .click(function (event) {
                $("#searchResultPane").collapse("show");
                search($("input[name=query]").val());
            })
            .attr("disabled", true);

        $("#btnPin").click(function (event) {
            taxonTree.filterBranches(function (n) {
                return n.isActive();
            });
            updateControls();
        });
        $("#btnUnpin").click(function (event) {
            taxonTree.clearFilter();
            updateControls();
        });

        // -----------------------------------------------------------------------------
    }); // end of pageload handler
})(jQuery, window, document);