/*! * 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, tmplDetails, tmplInfoPane, tmplMedia, timerMap = {}, USER_AGENT = "Fancytree Taxonomy Browser/1.0", GBIF_URL = "//api.gbif.org/v1/", TAXONOMY_KEY = "d7dddbf4-2cf0-4f39-9b2a-bb099caae36c", // GBIF backbone taxonomy SEARCH_PAGE_SIZE = 5, CHILD_NODE_PAGE_SIZE = 200, 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.html", function(data) { tmplDetails = Handlebars.compile(data); Handlebars.registerPartial("tmplDetails", tmplDetails); }); $.get("media.tmpl.html", function(data) { tmplMedia = Handlebars.compile(data); Handlebars.registerPartial("tmplMedia", tmplMedia); }); $.get("info-pane.tmpl.html", function(data) { tmplInfoPane = 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 _callWebservice(cmd, data) { return $.ajax({ url: GBIF_URL + cmd, data: $.extend({}, data), cache: true, headers: { "Api-User-Agent": USER_AGENT }, dataType: "jsonp", }); } /** */ function updateItemDetails(key) { $("#tmplDetails").addClass("busy"); $.bbq.pushState({ key: key }); $.when( _callWebservice("species/" + key), _callWebservice("species/" + key + "/speciesProfiles"), _callWebservice("species/" + key + "/synonyms"), _callWebservice("species/" + key + "/descriptions"), _callWebservice("species/" + key + "/media") ).done(function(species, profiles, synonyms, descriptions, media) { // Requests are resolved as: [ data, statusText, jqXHR ] species = species[0]; profiles = profiles[0]; synonyms = synonyms[0]; descriptions = descriptions[0]; media = media[0]; var info = $.extend(species, { profileList: profiles.results, // marine, extinct profile: profiles.results.length === 1 ? profiles.results[0] : null, // marine, extinct synonyms: synonyms.results, descriptions: descriptions.results, descriptionsByLang: {}, media: media.results, now: new Date().toString(), }); $.each(info.descriptions, function(i, o) { if (!info.descriptionsByLang[o.language]) { info.descriptionsByLang[o.language] = []; } info.descriptionsByLang[o.language].push(o); }); console.log("updateItemDetails", info); $("#tmplDetails") // .html(tmplDetails(info)) .removeClass("busy"); $("#tmplMedia") // .html(tmplMedia(info)) .removeClass("busy"); $("#tmplInfoPane") .html(tmplInfoPane(info)) .removeClass("busy"); $("[data-toggle='popover']").popover(); $(".carousel").carousel(); $("#mediaCounter").text("" + (media.results.length || "")); // $("[data-toggle='collapse']").collapse(); updateControls(); }); } /** */ function updateBreadcrumb(key, loadTreeNodes) { var $ol = $("ol.breadcrumb").addClass("busy"), activeNode = taxonTree.getActiveNode(); if (activeNode && activeNode.key !== key) { activeNode.setActive(false); // deactivate, in case the new key is not found } $.when( _callWebservice("species/" + key + "/parents"), _callWebservice("species/" + key) ).done(function(parents, node) { // Both requests resolved (result format: [ data, statusText, jqXHR ]) var nodeList = parents[0], keyList = []; nodeList.push(node[0]); // Display as
    list (for Bootstrap breadcrumbs) $ol.empty().removeClass("busy"); $.each(nodeList, function(i, o) { var name = o.vernacularName || o.canonicalName; keyList.push(o.key); if ("" + o.key === "" + key) { $ol.append( $("
  1. ").append( $("", { text: name, title: o.rank, }) ) ); } else { $ol.append( $("
  2. ").append( $("", { href: "#key=" + o.key, text: name, title: o.rank, }) ) ); } }); if (loadTreeNodes) { // console.log("updateBreadcrumb - loadKeyPath", keyList); taxonTree.loadKeyPath("/" + keyList.join("/"), function( n, status ) { // console.log("... updateBreadcrumb - loadKeyPath " + n.title + ": " + status); switch (status) { case "loaded": n.makeVisible(); break; case "ok": n.setActive(); // n.makeVisible(); break; } }); } }); } /** */ function search(query) { query = $.trim(query); console.log("searching for '" + query + "'..."); // Store the source options for optional paging searchResultTree.lastSourceOpts = { // url: GBIF_URL + "species/match", // Fuzzy matches scientific names against the GBIF Backbone Taxonomy url: GBIF_URL + "species/search", // Full text search of name usages covering the scientific and vernacular name, the species description, distribution and the entire classification across all name usages of all or some checklists data: { q: query, datasetKey: TAXONOMY_KEY, // name: query, // strict: "true", // hl: true, limit: SEARCH_PAGE_SIZE, offset: 0, }, cache: true, // headers: { "Api-User-Agent": USER_AGENT } // dataType: "jsonp" }; $("#searchResultTree").addClass("busy"); searchResultTree .reload(searchResultTree.lastSourceOpts) .done(function(result) { // console.log("search returned", result); if (result.length < 1) { searchResultTree.getRootNode().setStatus("nodata"); } $("#searchResultTree").removeClass("busy"); // https://github.com/tbasse/jquery-truncate // SLOW! // $("div.truncate").truncate({ // multiline: true // }); updateControls(); }); } /******************************************************************************* * Pageload Handler */ $(function() { $("#taxonTree").fancytree({ extensions: ["filter", "glyph", "wide"], filter: { mode: "hide", }, glyph: glyphOpts, autoCollapse: true, activeVisible: true, autoScroll: true, source: { url: GBIF_URL + "species/root/" + TAXONOMY_KEY, data: {}, cache: true, // dataType: "jsonp" }, init: function(event, data) { updateControls(); $(window).trigger("hashchange"); // trigger on initial page load }, lazyLoad: function(event, data) { data.result = { url: GBIF_URL + "species/" + data.node.key + "/children", data: { limit: CHILD_NODE_PAGE_SIZE, }, cache: true, // dataType: "jsonp" }; // store this request options for later paging data.node.lastSourceOpts = data.result; }, postProcess: function(event, data) { var response = data.response; data.node.info("taxonTree postProcess", response); data.result = $.map(response.results, function(o) { return ( o && { title: o.vernacularName || o.canonicalName, key: o.key, nubKey: o.nubKey, folder: true, lazy: true, } ); }); if (response.endOfRecords === false) { // Allow paging data.result.push({ title: "(more)", statusNodeType: "paging", }); } else { // No need to store the extra data delete data.node.lastSourceOpts; } }, activate: function(event, data) { $("#tmplDetails").addClass("busy"); $("ol.breadcrumb").addClass("busy"); updateControls(); _delay("showDetails", 500, function() { updateItemDetails(data.node.key); updateBreadcrumb(data.node.key); }); }, clickPaging: function(event, data) { // Load the next page of results var source = $.extend( true, {}, data.node.parent.lastSourceOpts ); source.data.offset = data.node.parent.countChildren() - 1; data.node.replaceWith(source); }, }); $("#searchResultTree").fancytree({ extensions: ["table", "wide"], source: [{ title: "No Results." }], minExpandLevel: 2, icon: false, table: { nodeColumnIdx: 2, }, postProcess: function(event, data) { var response = data.response; data.node.info("search postProcess", response); data.result = $.map(response.results, function(o) { var res = $.extend( { title: o.scientificName, key: o.key, }, o ); return res; }); // Append paging link if ( response.count != null && response.offset + response.limit < response.count ) { data.result.push({ title: "(" + (response.count - response.offset - response.limit) + " more)", statusNodeType: "paging", }); } data.node.info("search postProcess 2", data.result); }, // loadChildren: function(event, data) { // $("#searchResultTree td div.cell").truncate({ // multiline: true // }); // }, renderColumns: function(event, data) { var i, node = data.node, $tdList = $(node.tr).find(">td"), cnList = node.data.vernacularNames ? $.map(node.data.vernacularNames, function(o) { return o.vernacularName; }) : []; i = 0; function _setCell($cell, text) { $("
    ") .attr("title", text) .text(text) .appendTo($cell); } $tdList.eq(i++).text(node.key); $tdList.eq(i++).text(node.data.rank); i++; // #1: node.title = scientificName // $tdList.eq(i++).text(cnList.join(", ")); _setCell($tdList.eq(i++), cnList.join(", ")); $tdList.eq(i++).text(node.data.canonicalName); // $tdList.eq(i++).text(node.data.accordingTo); _setCell($tdList.eq(i++), node.data.accordingTo); $tdList.eq(i++).text(node.data.taxonomicStatus); $tdList.eq(i++).text(node.data.nameType); $tdList.eq(i++).text(node.data.numOccurrences); $tdList.eq(i++).text(node.data.numDescendants); // $tdList.eq(i++).text(node.data.authorship); _setCell($tdList.eq(i++), node.data.authorship); // $tdList.eq(i++).text(node.data.publishedIn); _setCell($tdList.eq(i++), node.data.publishedIn); }, activate: function(event, data) { if (data.node.isStatusNode()) { return; } _delay("activateNode", 500, function() { updateItemDetails(data.node.key); updateBreadcrumb(data.node.key); }); }, clickPaging: function(event, data) { // Load the next page of results var source = $.extend( true, {}, searchResultTree.lastSourceOpts ); source.data.offset = data.node.parent.countChildren() - 1; data.node.replaceWith(source); }, }); 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 key = $.bbq.getState("key"); console.log("bbq key", key); if (key) { updateBreadcrumb(key, 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()), lastQuery = $(this).data("lastQuery"); if ((e && e.which === $.ui.keyCode.ESCAPE) || query === "") { $("#btnResetSearch").click(); return; } if (e && e.which === $.ui.keyCode.ENTER && query.length >= 2) { $("#btnSearch").click(); return; } if (query === lastQuery || query.length < 2) { console.log("Ignored query '" + query + "'"); return; } $(this).data("lastQuery", query); _delay("search", 1, function() { $("#btnSearch").click(); }); $("#btnResetSearch").attr("disabled", query.length === 0); $("#btnSearch").attr("disabled", query.length < 2); }) .focus(); $("#btnResetSearch").click(function(e) { $("#searchResultPane").collapse("hide"); $("input[name=query]").val(""); searchResultTree.clear(); 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);