diff --git a/workbenches/gosling/Gosling.js b/workbenches/gosling/Gosling.js
new file mode 100644
index 00000000..3cc378c4
--- /dev/null
+++ b/workbenches/gosling/Gosling.js
@@ -0,0 +1,164 @@
+////
+//// A little fun driving a view with YAS*.
+////
+
+// Let jshint pass over over our external globals (browserify takes
+// care of it all).
+/* global YASQE */
+/* global YASR */
+/* global jQuery */
+/* global global_barista_token */
+/* global global_barista_location */
+/* global global_sparql_templates_universal */
+
+var us = require('underscore');
+var bbop = require('bbop-core');
+//var bbop = require('bbop').bbop;
+//var bbopx = require('bbopx');
+var amigo_inst = require('amigo2-instance-data');
+var amigo = new amigo_inst();
+var bbop_legacy = require('bbop').bbop;
+
+// Help with strings and colors--configured separately.
+var aid = new bbop_legacy.context(amigo.data.context);
+
+var widgetry = require('noctua-widgetry');
+
+// Aliases
+var each = us.each;
+var uuid = bbop.uuid;
+
+// Code here will be ignored by JSHint, as we are technically
+// "redefining" jQuery (although we are not).
+/* jshint ignore:start */
+var jQuery = require('jquery');
+/* jshint ignore:end */
+
+//
+var json_response = require('bbop-rest-response');
+var jquery_engine = require('bbop-rest-manager').jquery;
+var sparql_manager = require('bbop-manager-sparql');
+
+///
+/// ...
+///
+
+var sparql_select_id = 'spqlsel';
+var yasqe_id = 'yasqe';
+var yasr_id = 'yasr';
+
+// Make the default ours at least.
+YASQE.defaults.sparql.endpoint = "http://rdf.geneontology.org/sparql";
+
+///
+var GoslingInit = function(){
+
+ var logger = new bbop.logger('gosling');
+ logger.DEBUG = true;
+ function ll(str){ logger.kvetch(str); }
+
+ ///
+ /// Add option to dropdown.
+ ///
+
+ // Attach to interface.
+ var app_map = {};
+ var selopts_str = [];
+ us.each(global_sparql_templates_universal, function(tmpl){
+ var uuid = bbop.uuid();
+
+ // Mental.
+ app_map[uuid] = tmpl;
+
+ // Physical.
+ var str = '';
+ selopts_str.push(str);
+ });
+ jQuery('#'+sparql_select_id).append(selopts_str.join(''));
+
+ ///
+ /// Setup and glue YAS*.
+ ///
+
+ var yasqe = YASQE(document.getElementById("yasqe"), {
+ sparql: {
+ showQueryButton: true,
+ endpoint: 'http://rdf.geneontology.org/sparql'
+ }
+ });
+ var yasr = YASR(document.getElementById("yasr"), {
+ //this way, the URLs in the results are prettified using the
+ //defined prefixes in the query
+ getUsedPrefixes: yasqe.getPrefixesFromQuery
+ });
+
+ //link both together
+ yasqe.options.sparql.callbacks.complete = yasr.setResponse;
+
+ ///
+ /// ...
+ ///
+
+ function _insertion(tmpl){
+
+ var prefixes = '';
+ us.each(tmpl['prefixes'], function(prefix){
+ prefixes +=
+ 'PREFIX '+ prefix['prefix'] +':'+ prefix['expansion'] +'\n';
+ });
+ var qstr = prefixes + tmpl['query'];
+
+ yasqe.setValue(qstr);
+ if( tmpl['endpoint'] ){
+ var ep = tmpl['endpoint'];
+ //yasqe.endpoint(tmpl['endpoint']);
+ //YASQE.defaults.sparql.endpoint = ep;
+ //yasqe.defaults.sparql.endpoint = ep;
+ }
+ }
+
+ // Make the interface live.
+ jQuery("#"+sparql_select_id).change(function(){
+ var tmpl_id = jQuery(this).val();
+ console.log(tmpl_id);
+ var tmpl = app_map[tmpl_id];
+ console.log(tmpl);
+ console.log(app_map);
+ _insertion(tmpl);
+ });
+
+ // Insert first item.
+ var stu = global_sparql_templates_universal;
+ if( stu && stu[0] ){
+ _insertion(stu[0]);
+ }
+};
+
+// Start the day the jQuery way.
+jQuery(document).ready(function(){
+ console.log('jQuery ready');
+
+ // Try to define token.
+ var start_token = null;
+ if( global_barista_token ){
+ start_token = global_barista_token;
+ }
+
+ // Next we need a manager to try and pull in the model.
+ if( typeof(global_minerva_definition_name) === 'undefined' ||
+ typeof(global_barista_location) === 'undefined' ){
+ alert('environment not ready');
+ }else{
+ // Only roll if the env is correct.
+ // Will use the above variables internally (sorry).
+ GoslingInit();
+
+ // When all is said and done, let's also fillout the user
+ // name just for niceness. This is also a test of CORS in
+ // express.
+ if( start_token ){
+ widgetry.user_check(global_barista_location,
+ start_token, 'user_name_info', false);
+ }
+ }
+});
diff --git a/workbenches/gosling/README.md b/workbenches/gosling/README.md
new file mode 100644
index 00000000..086adc40
--- /dev/null
+++ b/workbenches/gosling/README.md
@@ -0,0 +1,4 @@
+```
+npm install
+npm run build
+```
diff --git a/workbenches/gosling/config.yaml b/workbenches/gosling/config.yaml
new file mode 100644
index 00000000..a366923d
--- /dev/null
+++ b/workbenches/gosling/config.yaml
@@ -0,0 +1,11 @@
+menu-name: "Gosling (Noctua's little GOOSE)"
+page-name: "Gosling (Noctua's little GOOSE)"
+type: "universal"
+help-link: "http://github.com/geneontology/noctua/issues"
+javascript:
+ - http://cdn.jsdelivr.net/yasqe/2.11.10/yasqe.bundled.min.js
+ - http://cdn.jsdelivr.net/yasr/2.10.8/yasr.bundled.min.js
+ - GoslingBundle.js
+css:
+ - http://cdn.jsdelivr.net/yasqe/2.11.10/yasqe.min.css
+ - http://cdn.jsdelivr.net/yasr/2.10.8/yasr.min.css
diff --git a/workbenches/gosling/package.json b/workbenches/gosling/package.json
new file mode 100644
index 00000000..a75a37a7
--- /dev/null
+++ b/workbenches/gosling/package.json
@@ -0,0 +1,91 @@
+{
+ "name": "workbench-go-gosling",
+ "version": "0.0.1",
+ "license": "BSD-3-Clause",
+ "description": "Experimental GOOOSE-like Noctua interface for the Gene Ontology.",
+ "keywords": [
+ "workbench",
+ "gene ontology",
+ "GO",
+ "bbop",
+ "SPARQL",
+ "GOOSE",
+ "YASGUI",
+ "noctua"
+ ],
+ "author": "SJC (http://berkeleybop.org/)",
+ "homepage": "http://berkeleybop.org/",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/geneontology/noctua.git"
+ },
+ "engines": {
+ "node": ">= 4.4",
+ "npm": ">= 2.15"
+ },
+ "dependencies": {
+ "amigo2-instance-data": "2.5.7",
+ "amigo2": "2.4.3",
+ "bbop": "2.4.3",
+ "bbop-core": "0.0.5",
+ "bbop-graph-noctua": "0.0.32",
+ "bbop-manager-sparql": "0.0.5",
+ "bbop-rest-manager": "0.0.17",
+ "bbop-rest-response": "0.0.4",
+ "gulp": "^3.9.1",
+ "jquery": "^2.2.4",
+ "underscore": "1.8.3"
+ },
+ "devDependencies": {
+ "browserify": "^13.0.1",
+ "browserify-shim": "^3.8.12",
+ "chai": "^3.5.0",
+ "eslint": "^2.13.1",
+ "gulp-bump": "^2.1.0",
+ "gulp-git": "^1.7.2",
+ "gulp-jsdoc": "^0.1.5",
+ "gulp-jshint": "^2.0.1",
+ "gulp-mocha": "^2.2.0",
+ "gulp-streamify": "^1.0.2",
+ "gulp-uglify": "^1.5.3",
+ "jsdoc": "^3.4.0",
+ "jshint": "^2.9.3",
+ "uglifyify": "^3.0.2",
+ "vinyl-source-stream": "1.1.0",
+ "wait-on": "^1.5.2",
+ "watchify": "^3.7.0"
+ },
+ "browserify": {
+ "transform": [
+ "browserify-shim"
+ ]
+ },
+ "browser": {
+ "jquery": "./node_modules/jquery/dist/jquery.min.js",
+ "bootstrap": "./node_modules/bootstrap/dist/js/bootstrap.min.js",
+ "jquery-ui": "./external_js/jquery-ui-1.10.3.custom.min.js",
+ "noctua-widgetry": "../../js/lib/noctua-widgetry/widgetry.js"
+ },
+ "browserify-shim": {
+ "jquery": {
+ "exports": "global:jQuery"
+ },
+ "jquery-ui": {
+ "depends": [
+ "jquery"
+ ]
+ }
+ },
+ "bundleDependencies": [],
+ "private": false,
+ "main": null,
+ "bugs": {
+ "url": "https://github.com/geneontology/noctua/issues"
+ },
+ "directories": {
+ },
+ "scripts": {
+ "build": "./node_modules/.bin/browserify Gosling.js -o public/GoslingBundle.js --exclude ringo/httpclient",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ }
+}
diff --git a/workbenches/gosling/public/GoslingBundle.js b/workbenches/gosling/public/GoslingBundle.js
new file mode 100644
index 00000000..20cbabd0
--- /dev/null
+++ b/workbenches/gosling/public/GoslingBundle.js
@@ -0,0 +1,68780 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o' + 'comment ' +
+ // ann.value() + '';
+ // }
+ var okay_annotations = {
+ 'state' : true,
+ 'date' : true,
+ 'title' : true,
+ 'contributor' : true,
+ 'comment' : true
+ };
+ if( okay_annotations[ann.key()] && ann.value() ){
+ anns += '
' + '' +
+ ann.key() + ' ' +
+ ann.value() + '
';
+ }
+ });
+ if( anns === '' ){
+ anns = '
none
';
+ }
+
+ // Try and get a title out of the model.
+ var mtitle = '??? (title)';
+ var tanns = ecore.get_annotations_by_key('title');
+ if( tanns && tanns.length === 1 ){ mtitle = tanns[0].value(); }
+
+ var str_cache = [
+ '
',
+ // '',
+ // '
',
+ // '
',
+ '
ID
',
+ '
',
+ ecore.get_id(),
+ '
',
+ '
Name
',
+ '
',
+ mtitle,
+ '
',
+ '
Individuals
',
+ '
',
+ nds.length || 0,
+ '
',
+ '
Indv. Rels.
',
+ '
',
+ eds.length || 0,
+ '
',
+ '
Annotations
',
+ anns
+ ];
+
+ // Add to display.
+ jQuery(info_div).empty();
+ jQuery(info_div).append(str_cache.join(' '));
+}
+
+/*
+ * Function: repaint_exp_table
+ *
+ * Add edit model node contents to a descriptive table.
+ */
+function repaint_exp_table(ecore, aid, table_div){
+
+ // First, lets get the headers that we'll need by poking the
+ // model and getting all of the possible categories.
+ var cat_list = [];
+ each(ecore.get_nodes(), function(enode, enode_id){
+ each(enode.types(), function(in_type){
+ cat_list.push(in_type.category());
+ });
+ });
+ // Dedupe list.
+ var tmph = bbop_core.hashify(cat_list);
+ cat_list = us.keys(tmph);
+
+ // If we actually got something, render the table. Otherwise,
+ // a message.
+ if( us.isEmpty(cat_list) ){
+
+ // Add to display.
+ jQuery(table_div).empty();
+ jQuery(table_div).append('
no instances
');
+
+ }else{
+
+ // Sort header list according to known priorities.
+ cat_list = cat_list.sort(function(a, b){
+ return aid.priority(b) - aid.priority(a);
+ });
+
+ // Convert the ids into readable headers.
+ var nav_tbl_headers = [];
+ each(cat_list, function(cat_id){
+ var hdrc = [
+ aid.readable(cat_id),
+ '↑↓'
+ ];
+ nav_tbl_headers.push(hdrc.join(' '));
+ });
+
+ var nav_tbl =
+ new bbop.html.table(nav_tbl_headers, [],
+ {'generate_id': true,
+ 'class': ['table', 'table-bordered',
+ 'table-hover',
+ 'table-condensed'].join(' ')});
+
+ //each(ecore.get_nodes(),
+ each(ecore.edit_node_order(), function(enode_id){
+ var enode = ecore.get_node(enode_id);
+
+ // Now that we have an enode, we want to mimic the order
+ // that we created for the header (cat_list). Start by
+ // binning the types.
+ var bin = {};
+ each(enode.types(), function(in_type){
+ var cat = in_type.category();
+ if( ! bin[cat] ){ bin[cat] = []; }
+ bin[cat].push(in_type);
+ });
+
+ // Now unfold the binned types into the table row
+ // according to the sorted order.
+ var table_row = [];
+ each(cat_list, function(cat_id){
+ var accumulated_types = bin[cat_id];
+ var cell_cache = [];
+ each(accumulated_types, function(atype){
+ var tt = type_to_span(atype, aid);
+ cell_cache.push(tt);
+ });
+ table_row.push(cell_cache.join(' '));
+ });
+ nav_tbl.add_to(table_row);
+ });
+
+ // Add to display.
+ jQuery(table_div).empty();
+ jQuery(table_div).append(nav_tbl.to_string());
+
+ // Make it sortable using the plugin.
+ jQuery('#' + nav_tbl.get_id()).tablesorter();
+ }
+}
+
+/**
+ * Add edit model edge contents to a descriptive table.
+ */
+function repaint_edge_table(ecore, aid, table_div){
+
+ var edge_list = ecore.all_edges();
+
+ // If we actually got something, render the table. Otherwise,
+ // a message.
+ if( us.isEmpty(edge_list) ){
+
+ // Add to display.
+ jQuery(table_div).empty();
+ jQuery(table_div).append('
no relations
');
+
+ }else{
+
+ // Make the (obvjously known) headers pretty.
+ var nav_tbl_headers = [];
+ each(['subject', 'relation', 'object'], function(hdr){
+ var hdrc = [
+ hdr,
+ '↑↓'
+ ];
+ nav_tbl_headers.push(hdrc.join(' '));
+ });
+
+ var nav_tbl =
+ new bbop.html.table(nav_tbl_headers, [],
+ {'generate_id': true,
+ 'class': ['table', 'table-bordered',
+ 'table-hover',
+ 'table-condensed'].join(' ')});
+
+ each(edge_list, function(edge){
+ var s = edge.source();
+ var r = edge.relation();
+ var t = edge.target();
+
+ // according to the sorted order.
+ var table_row = [
+ aid.readable(s),
+ aid.readable(r),
+ aid.readable(t)
+ ];
+
+ nav_tbl.add_to(table_row);
+ });
+
+ // Add to display.
+ jQuery(table_div).empty();
+ jQuery(table_div).append(nav_tbl.to_string());
+
+ // Make it sortable using the plugin.
+ jQuery('#' + nav_tbl.get_id()).tablesorter();
+ }
+}
+
+/**
+ * Wipe out the contents of a jQuery-identified div.
+ */
+function wipe(div){
+ jQuery(div).empty();
+}
+
+/**
+ * Takes a core edit node types as the argument, categorize the, order
+ * them.
+ */
+function enode_types_to_ordered_stack(enode_types, aid){
+
+ // Sort the types within the stack according to the known
+ // type priorities.
+ function _sorter(a, b){
+ // Use aid property priority.
+ var bpri = aid.priority(b.property_id());
+ var apri = aid.priority(a.property_id());
+ return apri - bpri;
+ }
+
+ //
+ var out_stack = enode_types.sort(_sorter);
+ return out_stack;
+}
+
+/**
+ * This is a silly little object that represents a node stack. It can
+ * render the stack as a string (the original non-object purpose of
+ * this little bit of code) and manager to relay the relation between
+ * random DOM IDs and underlying edges (the reason it was turned into
+ * an object).
+ *
+ * This change was made to make it possible to allow the evidence to
+ * be clicked on in the display and the edge annotation dialog (with
+ * the accompanying evidence) to come up for editing.
+ *
+ * This whole bit will change a lot with new evidence coming down the
+ * pipe.
+ */
+function node_stack_object(enode, aid){
+
+ var hook_list = [];
+
+ // Create a colorful label stack into an individual table.
+ var enode_stack_table = new bbop.html.tag('table',
+ {'class':'bbop-mme-stack-table'});
+
+ // General function for adding type information to stack.
+ function _add_table_row(item, color, prefix, suffix){
+ //var rep_color = aid.color(item.category());
+ var out_rep = type_to_span(item, color);
+ if( prefix ){ out_rep = prefix + out_rep; }
+ if( suffix ){ out_rep = out_rep + suffix; }
+ var trstr = null;
+ if( color ){
+ trstr = '
' + out_rep + '
';
+ }else{
+ trstr = '
' +
+ '
' + out_rep + '
';
+ }
+ enode_stack_table.add_to(trstr);
+ }
+
+ // Inferred types first.
+ var inf_types = enode.get_unique_inferred_types();
+ each(inf_types, function(item){ _add_table_row(item, null, '[', ']'); });
+ // Editable types next.
+ var std_types = enode.types();
+ each(std_types, function(item){ _add_table_row(item); });
+
+ // Now we trick our way through to adding the types^H^H^H^H^H
+ // absorbed subgraph nodes of the subgraphs.
+ var subgraph = enode.subgraph();
+ if( subgraph ){
+
+ // Gather the stack to display, abstractly do go up or down
+ // the subgraph.
+ var _folded_stack_gather = function(direction){
+
+ // First, get the parent/child sub-nodes.
+ var x_edges = [];
+ if( direction === 'standard' ){
+ x_edges = subgraph.get_parent_edges(enode.id());
+ }else{
+ x_edges = subgraph.get_child_edges(enode.id());
+ }
+ // Put an order on the edges.
+ x_edges.sort(function(e1, e2){
+ return aid.priority(e1.relation()) - aid.priority(e2.relation());
+ });
+ each(x_edges, function(x_edge){
+ // Edge info.
+ var rel = x_edge.relation() || 'n/a';
+ var rel_color = aid.color(rel);
+ var rel_readable = aid.readable(rel);
+ // If context aid doesn't work, see if it comes with a label.
+ if( rel_readable === rel && typeof(x_edge.label) === 'function'){
+ var label_rn = x_edge.label();
+ if( label_rn !== rel ){
+ rel = label_rn; // use label
+ }
+ }else{
+ rel = rel_readable; // use context
+ }
+
+ // Try and extract proof of evidence.
+ var ev_edge_anns = x_edge.get_annotations_by_key('evidence');
+ // Get node.
+ var x_ent_id = null;
+ if( direction === 'standard' ){
+ x_ent_id = x_edge.object_id();
+ }else{
+ x_ent_id = x_edge.subject_id();
+ }
+ var x_node = subgraph.get_node(x_ent_id);
+ // Try and extract proof of evidence.
+ if( x_node ){
+ var ev_node_anns = x_node.get_annotations_by_key('evidence');
+
+ // Add the edge/node combos to the table.
+ each(x_node.types(), function(x_type){
+
+ //
+ var elt_id = bbop_core.uuid();
+ var edge_id = x_edge.id();
+ hook_list.push([edge_id, elt_id]);
+ if( ev_edge_anns.length > 0 ){
+ // In this case (which should be the only possible
+ // case), we'll capture the ID and pair it with an
+ // ID.
+ _add_table_row(x_type, rel_color, rel + '(',
+ ')E');
+ }else{
+ _add_table_row(x_type, rel_color, rel + '(',
+ ')');
+ }
+ });
+ }
+ });
+ };
+
+ // Do it both ways--upstream and downstream.
+ _folded_stack_gather('standard');
+ _folded_stack_gather('reverse');
+
+ }
+
+ // Inject meta-information if extant.
+ var anns = enode.annotations();
+ if( anns.length !== 0 ){
+
+ // Meta counts.
+ var n_ev = 0;
+ var n_other = 0;
+ each(anns, function(ann){
+ if( ann.key() === 'evidence' ){
+ n_ev++;
+ }else{
+ if( ann.key() !== 'hint-layout-x' &&
+ ann.key() !== 'hint-layout-y' ){
+ n_other++;
+ }
+ }
+ });
+
+ // Add to top. No longer need evidence count on individuals.
+ var trstr = '
'
+ ];
+ }
+ anchor.form_string = form.join('');
+ }
+
+ ///
+ /// Start main running body.
+ ///
+
+ //
+ var mdl = null;
+ if( ! entity ){
+ alert('unknown id:' + entity_id);
+ }else{
+
+ // app_hooks = {
+ // 'app-name': {
+ // : {},
+ // ...
+ // };
+ var app_hooks = {};
+
+
+ // Go through our input list and create a mutable data
+ // structure that we can then use to fill out the editor
+ // slots.
+
+ var ann_classes = {};
+ each(annotation_config, function(ann_class){
+ var aid = ann_class['id'];
+
+ // Clone.
+ ann_classes[aid] = bbop_core.clone(ann_class);
+
+ // Add our additions.
+ ann_classes[aid]['elt2ann'] = {};
+ ann_classes[aid]['list'] = [];
+ ann_classes[aid]['string'] = '???';
+ ann_classes[aid]['widget'] = null;
+ });
+
+ // Going through each of the annotation types, try and collect
+ // them from the model.
+ app_hooks['remote-pmid'] = {};
+ each(us.keys(ann_classes), function(key){
+
+ // Skip adding anything if the policy is
+ // "read-only-optional" and there are no annotations for
+ // it.
+ // var anns_by_key = entity.get_annotations_by_key(key);
+ // if( ann_classes[key]['policy'] === 'read-only-optional' &&
+ // (! anns_by_key || anns_by_key.length === 0 ) ){
+ // // skip
+ // }else{
+
+ each(entity.get_annotations_by_key(key), function(ann){
+
+ // For every one found, assemble the actual display
+ // string while storing the ids for later use.
+ var kval = ann.value();
+ if( kval.split('http://').length === 2 ){ // cheap link
+ kval = '' +
+ kval + '';
+ }
+ // However, evidence annotations are very different
+ // for us now, and we need to dig out the guts from a
+ // subgraph elsewhere.
+ if( ann.key() === 'evidence' && ann.value_type() === 'IRI' ){
+
+ // Setup a dummy in case we fail, like if we're
+ // fully exploded and there is no subgraph.
+ var ref_val = ann.value();
+ var ref_sub = entity.get_referenced_subgraph_by_id(ref_val);
+ kval = '(evidence annotation for: ' + ref_val + ')';
+ if( ref_sub ){ // we found the subgraph
+ kval = '';
+ // Collect class expressions, just using
+ // the default profile extractor for now.
+ var c_cache = [];
+ each(ref_sub.all_nodes(), function(ref_ind){
+ // Collect the classes.
+ each(ref_ind.types(), function(ref_type){
+ c_cache.push(type_to_span(ref_type));
+ });
+ kval += c_cache.join('/');
+ // Collect annotations (almost certainly
+ // had some class first, so no worries
+ // about the dumb tag on the end).
+ var ref_anns = ref_ind.annotations();
+ var sorted_ref_anns = ref_anns.sort(function(a, b){
+ var va = a.key();
+ var vb = b.key();
+ var retval = 0;
+ if( va < vb ){
+ retval = -1;
+ }else if( va > vb ){
+ retval = 1;
+ }
+ return retval;
+ });
+ each(sorted_ref_anns, function(ref_ann){
+ // Skip unnecessary information.
+ //console.log('ref_ann.key():' + ref_ann.key() );
+ if( ref_ann.key() !== 'hint-layout-x' &&
+ ref_ann.key() !== 'hint-layout-y' ){
+ var rav = ref_ann.value();
+ // link pmids silly
+ if( rav.split('PMID:').length === 2 ){
+ var pmid = rav.split('PMID:')[1];
+ var pmid_uuid = bbop_core.uuid();
+ kval += ' ' + ref_ann.key() +': '+ 'PMID:'+ pmid +' 🔗';
+ // As well, capture for later.
+ app_hooks['remote-pmid'][pmid_uuid] =
+ {'pmid': pmid};
+ }else if( rav.split('http://').length === 2 ){
+ kval +=' ' + ref_ann.key() +': '+ rav + ' 🔗';
+ }else{
+ kval +=' '+ ref_ann.key() +': '+ rav;
+ }
+ }
+ });
+ });
+ }
+ }
+
+ // And the annotation id for the key.
+ var kid = bbop_core.uuid();
+
+ // Only add to action set if mutable.
+ if( ann_classes[key]['policy'] === 'mutable' ){
+ ann_classes[key]['elt2ann'][kid] = ann.id();
+ }
+
+ var acache = [];
+ acache.push('
');
+ acache.push(kval);
+
+ // Only add the delete UI bits if the policy says
+ // mutable.
+ if( ann_classes[key]['policy'] === 'mutable' ){
+ acache.push('X');
+ }
+
+ acache.push('
');
+ ann_classes[key]['list'].push(acache.join(''));
+ });
+
+ // Join wahtaver is in the list together to get the display
+ // string.
+ // If we didn't collect anything, it's empty.
+ var str = '';
+ if( ann_classes[key]['list'].length > 0 ){
+ str = ann_classes[key]['list'].join('');
+ str = '
' + str + '
';
+ }
+ ann_classes[key]['string'] = str;
+
+ });
+
+ // TODO: Generate the final code from the created structure.
+ // Use the original ordering of the argument list.
+ var out_cache = [];
+ each(annotation_config, function(list_entry){
+
+ //
+ var eid = list_entry['id'];
+ var entry_info = ann_classes[eid];
+
+ //
+ var elbl = entry_info['label'];
+ var ewid = entry_info['widget_type'];
+ var epol = entry_info['policy'];
+ var ecrd = entry_info['cardinality'];
+ var eplc = entry_info['placeholder'];
+ var eopt = entry_info['options'] || [];
+ // for evidence (ref)
+ var eplc_b = entry_info['placeholder_secondary'] || '';
+ // for evidence (with)
+ var eplc_c = entry_info['placeholder_tertiary'] || '';
+ // Has?
+ var ehas = entry_info['list'].length || 0;
+ // UI output string.
+ var eout = entry_info['string'];
+
+ // Add whatever annotations we have.
+ out_cache.push('
');
+ //out_cache.push('
' + elbl + '
');
+ out_cache.push('
' + elbl + '
');
+ out_cache.push('
');
+ //out_cache.push('
');
+ out_cache.push('
' + eout + '
');
+ //out_cache.push('');
+
+ // And add an input widget if mutable...
+ //console.log('epol: ' + epol);
+ if( epol && epol === 'mutable' ){
+ // ...and cardinality not one or has no items in list.
+ //console.log(' ecrd: ' + ecrd);
+ //console.log(' ehas: ' + ehas);
+ if( ecrd !== 'one' || ehas === 0 ){
+ console.log(' widget for: ' + eid);
+ var form_widget = null;
+ if( ewid === 'source_ref' ){ // evidence is special
+ form_widget = new _abstract_annotation_widget(
+ ewid, eplc, eplc_b, eplc_c, {
+ 'special': 'evidence',
+ 'graph': ecore
+ });
+ }else{
+ form_widget = new _abstract_annotation_widget(
+ ewid, eplc, null, null, eopt);
+ }
+
+ // Add to the literal output.
+ out_cache.push(form_widget.form_string);
+
+ // Add back to the collection for use after
+ // connecting to the DOM.
+ ann_classes[eid]['widget'] = form_widget;
+ }
+ }
+
+ // Close out BS3 panel.
+ out_cache.push('
');
+ out_cache.push('
');
+ });
+
+ // Optionally, collect any annotations not in one of the given
+ // defined categories.
+ var all_undefined_annotations = entity.get_annotations_by_filter(
+ function(in_ann){
+ var retval = false;
+ if( in_ann.key() !== 'http://geneontology.org/lego/json-model' &&
+ in_ann.key() !== 'hint-layout-x' &&
+ in_ann.key() !== 'hint-layout-y' ){
+ if( ! ann_classes[in_ann.key()] ){ // ! defined ann class
+ retval = true;
+ }
+ }
+ return retval;
+ }
+ );
+ // Add them to the display at the bottom if there is anything
+ // worth acting on.
+ if( ! us.isEmpty(all_undefined_annotations) ){
+
+ // As above, but manually add visible annotations.
+ out_cache.push('
');
+ }
+
+ // Setup base modal.
+ mdl = new contained_modal('dialog', 'Annotations for: ' + entity_title);
+ mdl.add_to_body(out_cache.join(''));
+
+ // Okay, still playing from just above, let's arm the
+ // Textpresso and PubAnn buttons and start playing.
+ if( entity_type === 'model' && context === 'go' ){
+
+ // Standard TPC--#316 implementation.
+ jQuery('#' + tpc_btn.get_id()).click( function(evt){
+ evt.stopPropagation();
+
+ // Close out what we had.
+ mdl.destroy();
+
+ var taemdl =
+ new contained_modal('dialog', 'TPC interaction');
+ taemdl.add_to_body('
TPC!
');
+ taemdl.show();
+
+ // Kick people to new link in new window.
+ var btkn = manager.user_token();
+ if( ! btkn || ! us.isString(btkn) ){
+ alert('Need to be logged in to kick out to TPC.');
+ }else{
+
+ //
+ var endpoint_url =
+ encodeURIComponent('http://'+ window.location.hostname +'/tractorbeam');
+
+ var reqs = new minerva_requests.request_set(btkn,
+ ecore.get_id());
+ // Base.
+ reqs.use_groups(manager.use_groups());
+ // Fake.
+ reqs.external_model_id(ecore.get_id());
+ reqs.external_client_id('tpc');
+ //reqs.external_user_id('http://user1'); // not needed yet?
+ var endpoint_arguments =
+ encodeURIComponent(JSON.stringify(reqs.structure()));
+
+ // TODO: This seems to change a lot--maybe push it into
+ // a config, or start the plugin thinking?
+ var txtpr = 'http://tpc.textpresso.org';
+ window.open(txtpr + '/cgi-bin/tc/NoctuaIn?' +
+ 'endpoint_url=' + endpoint_url +
+ '&endpoint_arguments=' + endpoint_arguments,
+ '_blank');
+
+ }
+ taemdl.destroy();
+ });
+
+ // Standard Textpresso--token only.
+ jQuery('#' + textpr_btn.get_id()).click( function(evt){
+ evt.stopPropagation();
+
+ // Close out what we had.
+ mdl.destroy();
+
+ var taemdl =
+ new contained_modal('dialog', 'Textpresso interaction');
+ taemdl.add_to_body('
Textpresso!
');
+ taemdl.show();
+
+ // Kick people to new link in new window.
+ var btkn = manager.user_token();
+ if( ! btkn || ! us.isString(btkn) ){
+ alert('Need to be logged in to kick out to Textpresso.');
+ }else{
+ var txtpr = 'http://tpc.textpresso.org';
+ window.open(txtpr + '/cgi-bin/tc/NoctuaIn?token=' + btkn,
+ '_blank');
+ }
+ taemdl.destroy();
+ });
+
+ // }else if( (entity_type === 'fact' || entity_type === 'individual' ) &&
+ // context === 'go' ){
+ }else if( entity_type === 'fact' && context === 'go' ){
+
+ // PubAnnotation.
+ jQuery('#' + pubann_btn.get_id()).click( function(evt){
+ evt.stopPropagation();
+
+ // Close out what we had.
+ mdl.destroy();
+
+ var taemdl =
+ new contained_modal('dialog', 'PubAnnotation pattern interaction');
+ var tofm = [
+ '
Markup a PubMed document for the comments in this entity.
',
+ '',
+ '
'
+ ];
+ taemdl.add_to_body(tofm.join(''));
+ taemdl.show();
+
+ // TODO: Action on button click.
+ // If input looks okay, kick people to PubAnnotation.
+ jQuery("#pubanninteraction" ).submit(function(event){
+ event.preventDefault();
+
+ var btkn = manager.user_token();
+ if( ! btkn || ! us.isString(btkn) ){
+ alert('Need to be logged in to transfer ' +
+ 'to PubAnnotation.');
+ taemdl.destroy();
+ }else{
+
+ // Try to get the PubMed ID.
+ var finputs = jQuery('#pubanninteraction :input');
+ //console.log(finputs);
+ var fvalues = {};
+ each(finputs, function(finput){
+ fvalues[finput.id] = finput.value;
+ //console.log(finput);
+ });
+ //console.log(fvalues);
+ var inp = fvalues['pubannpubid'];
+
+ // If the PMID is good, build a link out to
+ // PubAnnotation and kick.
+ if( ! inp ){
+ alert('Need to input a PubMed ID.');
+ }else{
+
+ var good_pmid_a = /^[0-9]+$/;
+ var good_pmid_b = /^PMID\:[0-9]+$/;
+ var good_pmid_c = /^http:\/\/.*[0-9]+.*/;
+ if( ! good_pmid_a.test(inp) &&
+ ! good_pmid_b.test(inp) &&
+ ! good_pmid_c.test(inp) ){
+ alert('Not a recognized PubMed ID: ' + inp);
+ }else{
+
+ // Assume PubAnnotation, unless otherwise
+ // specified.
+ // Get only the local if full short form.
+ if( good_pmid_b.test(inp) ){
+ inp = inp.substr(5, inp.length);
+ }
+
+ // Finally, kick out to PubAnnotation.
+ var endp_url = 'http://'+ window.location.origin +'/tractorbeam';
+ var endpoint_url = encodeURIComponent(endp_url);
+ var reqs = new minerva_requests.request_set(
+ btkn, ecore.get_id());
+ // Base.
+ reqs.use_groups(manager.use_groups());
+ // Fake.
+ reqs.external_model_id(ecore.get_id());
+ reqs.external_client_id('pubannotation.org');
+ reqs.external_return_url(window.location.toString());
+ if( entity_type === 'fact' ){
+ var xsource = entity.source();
+ var xtarget = entity.target();
+ var xrelation = entity.relation();
+ reqs.external_fact_source_id(xsource);
+ reqs.external_fact_target_id(xtarget);
+ reqs.external_fact_relation_id(xrelation);
+ }else if( entity_type === 'individual' ){
+ reqs.external_individual_id(entity_id);
+ }
+ var endpoint_arguments =
+ encodeURIComponent(JSON.stringify(reqs.structure()));
+
+ // TODO: This seems to change a lot--maybe
+ // push it into a config, or start the plugin
+ // thinking?
+ var kick_url = 'http://pubannotation.org/docs/sourcedb/PubMed/sourceid/' + inp + '?';
+ if( good_pmid_c.test(inp) ){
+ kick_url = inp + '?';
+ }
+ window.open(kick_url +
+ 'endpoint_url=' + endpoint_url +
+ '&endpoint_arguments=' + endpoint_arguments,
+ '_blank');
+
+ taemdl.destroy();
+ }
+ }
+ }
+ });
+
+ });
+ }
+
+ // Now that they're in the DOM, add the different app classes
+ // we have defined.
+ //console.log('app_hooks', app_hooks);
+ each(us.keys(app_hooks), function(app_hook_set){
+ us.each(us.keys(app_hooks[app_hook_set]), function(app_elt_uuid){
+ var app_elt_data = app_hooks[app_hook_set][app_elt_uuid];
+ if( app_hook_set === 'remote-pmid' ){
+
+ var gptmpl = global_sparql_templates_named['get-pmid'];
+ if( gptmpl ){
+ var sep = gptmpl['endpoint'];
+ //var sqy = gptmpl['query'];
+ console.log('sep', sep);
+ //console.log('query', sqy);
+ console.log('app_elt_data', app_elt_data);
+
+ var engine_to_use = new jquery_engine(response_json);
+ engine_to_use.headers(
+ [['accept', 'application/sparql-results+json']]);
+ var sm = new sparql_manager(sep,
+ [],
+ response_json,
+ engine_to_use,
+ 'async');
+ sm.register('error', function(resp, man){
+ console.log('sparql_manager error', resp);
+ });
+ sm.register('success', function(resp, man){
+ //console.log('sparql_manager success', resp);
+ if( resp.raw()['results'] &&
+ resp.raw()['results']['bindings'] &&
+ resp.raw()['results']['bindings'][0] &&
+ resp.raw()['results']['bindings'][0]['title'] ){
+ var tt = resp.raw()['results']['bindings'][0]['title']['value'];
+ jQuery("#"+app_elt_uuid).html(tt);
+ }else{
+ console.log('wikidata return structure bad');
+ }
+ });
+ sm.template(JSON.stringify(gptmpl), app_elt_data);
+ }
+ }
+ });
+ });
+
+ // Now that they're all in the DOM, add any delete annotation
+ // actions. These are completely generic--all annotations can
+ // be deleted in the same fashion.
+ each(us.keys(ann_classes), function(ann_key){
+ //each(ann_classes[ann_key]['elt2ann'], function(elt_id, ann_id){
+ each(ann_classes[ann_key]['elt2ann'], function(ann_id, elt_id){
+ jQuery('#' + elt_id).click( function(evt){
+ evt.stopPropagation();
+
+ //var annid = elt2ann[elt_id];
+ //alert('blow away: ' + annid);
+ var ann = entity.get_annotation_by_id(ann_id);
+ var akey = ann.key();
+ var aval = ann.value();
+ _ann_dispatch(entity, entity_type, 'remove',
+ ecore.get_id(), akey, aval);
+
+ // Wipe out modal on action.
+ mdl.destroy();
+ });
+ });
+ });
+
+ // Walk through again, this time activating and annotation
+ // "add" buttons that we added.
+ each(us.keys(ann_classes), function(ann_key){
+ var form = ann_classes[ann_key]['widget'];
+ //console.log('ann_key: ' + ann_key, form);
+ if( form ){ // only act if we added/defined it earlier
+
+ // Clone button add.
+ if( form.evidence_profiles.length > 0 ){
+
+ jQuery('#' + form.clone_button.get_id()).click(function(evt){
+
+ // Assemble a little display from each
+ // evidence profile for display.
+ var cln_line_cache = {};
+ var ce_cache = {};
+ us.each(form.evidence_profiles, function(prof){
+
+ // Mine out class expressions..
+ var cln_ce_str = [];
+ var cln_ce = [];
+ us.each(prof.class_expressions, function(ce){
+ cln_ce.push(ce.class_id());
+ cln_ce_str.push(ce.to_string_plus());
+ //console.log(ce);
+ ce_cache[ce.class_id()] = true;
+ });
+
+ // Mine out source and with.
+ var cln_src = [];
+ var cln_with = [];
+ //console.log(prof.annotations.length);
+ us.each(prof.annotations, function(ann){
+ if( ann.key() === 'with' ){
+ cln_with.push(ann.value());
+ }
+ if( ann.key() === 'source' ){
+ cln_src.push(ann.value());
+ }
+ //console.log(ann);
+ });
+
+ // Store and add to display.
+ // BUG/TODO: Locking to single sized for now.
+ var uniq = cln_ce_str.join('/') + '_' +
+ cln_src.join('/') + '_' +
+ cln_with.join(' / ');
+ //console.log('uniq', uniq);
+ if( cln_ce_str.length === 1 &&
+ cln_src.length > 0 ){
+
+ // Create add button.
+ var cln_btn_args = {
+ 'generate_id': true,
+ 'type': 'button',
+ 'class': 'btn btn-success'
+ };
+ var cln_btn = new bbop.html.tag(
+ 'button', cln_btn_args,'Add');
+
+ // Avoid dupes.
+ var line = [
+ cln_ce_str.join(' / '),
+ cln_src.join(' / '),
+ cln_with.join(' / '),
+ cln_btn.to_string(),
+ ];
+ // console.log('line', line);
+ // Uniquify and store thinks we'll
+ // need for the action later.
+ cln_line_cache[uniq] = {
+ 'line': line,
+ 'button': cln_btn,
+ 'class_expressions': cln_ce,
+ 'sources': cln_src,
+ 'withs': cln_with
+ };
+ }
+ });
+
+ var cln_tbl = new bbop.html.table(
+ ['Class expression(s)','Source(s)','With','Action'],
+ us.map(us.values(cln_line_cache), function(store){
+ return store['line'];
+ }),
+ {'generate_id': true,
+ 'class': ['table',
+ 'table-bordered',
+ 'table-hover',
+ 'table-condensed'].join(' ')});
+
+ // Launch widget.
+ var cdl = new contained_modal('dialog',
+ 'Clone evidence to: ' +
+ entity_title);
+ cdl.add_to_body(cln_tbl.to_string());
+ cdl.show();
+
+ ///
+ /// Now that it's in the DOM, add actions to
+ /// the buttons.
+ ///
+
+ // Make buttons from cache.
+ us.each(us.values(cln_line_cache), function(store){
+
+ //
+ var cln_btn = store['button'];
+ var cln_ce = store['class_expressions'];
+ var cln_src = store['sources'];
+ var cln_with = store['withs'] || []; // nil p?
+
+ jQuery('#'+cln_btn.get_id()).click(function(evt){
+
+ // BUG/TODO: class express still
+ // locked to the first.
+ // var line = [
+ // cln_ce.join(' / '),
+ // cln_src.join(' / '),
+ // cln_with.join(' / ')].join('; ');
+ //console.log('pre-action line',line);
+ _ann_dispatch(entity, entity_type, 'add',
+ ecore.get_id(), ann_key,
+ { 'evidence_id': cln_ce[0],
+ 'source_ids': cln_src,
+ 'with_strs': cln_with });
+ cdl.destroy();
+ mdl.destroy();
+ });
+ });
+
+ });
+ }
+
+ // The typical add button.
+ jQuery('#' + form.add_button.get_id()).click(function(evt){
+ evt.stopPropagation();
+
+ if( ann_key === 'evidence' ){
+
+ // In the case of evidence, we need to bring
+ // in the two different text items and make
+ // them into the correct object for
+ // _ann_dispatch(). The "with" field is an
+ // optional add-on.
+ var val_a =
+ jQuery('#'+form.text_input.get_id()).val();
+ var val_b =
+ jQuery('#'+form.text_input_secondary.get_id()).val();
+ var val_c =
+ jQuery('#'+form.text_input_tertiary.get_id()).val();
+
+ // Need ECO and reference, "with" is optional
+ // for now.
+ if( val_a && val_a !== '' && val_b && val_b !== '' ){
+ _ann_dispatch(entity, entity_type, 'add',
+ ecore.get_id(), ann_key,
+ { 'evidence_id': val_a,
+ 'source_ids': val_b,
+ 'with_strs': val_c });
+ }else{
+ alert('need all arguments added for ' + entity_id);
+ }
+
+ }else{
+ var val_d = jQuery('#' + form.text_input.get_id()).val();
+ if( val_d && val_d !== '' ){
+ _ann_dispatch(entity, entity_type, 'add',
+ ecore.get_id(), ann_key, val_d);
+ }else{
+ alert('no ' + ann_key + ' added for ' + entity_id);
+ }
+ }
+
+ // Wipe out modal.
+ mdl.destroy();
+ });
+ }
+ });
+
+ ///
+ /// Special section for special additions (autocomplete, etc.).
+ /// TODO: Eventually, this should also be in the config.
+ ///
+
+ // Add autocomplete box for ECO to evidence box.
+ if( ann_classes['evidence'] && ann_classes['evidence']['widget'] ){
+ var ev_form = ann_classes['evidence']['widget'];
+ var eco_auto_args = {
+ 'label_template':
+ '{{annotation_class_label}} ({{annotation_class}})',
+ 'value_template': '{{annotation_class}}',
+ 'list_select_callback': function(doc){}
+ };
+ var eco_auto =
+ new bbop.widget.search_box(gserv, gconf,
+ ev_form.text_input.get_id(),
+ eco_auto_args);
+ eco_auto.lite(true);
+ eco_auto.add_query_filter('document_category', 'ontology_class');
+ eco_auto.add_query_filter('source', 'eco', ['+']);
+ eco_auto.set_personality('ontology');
+ }
+ }
+
+ // Return our final product.
+ return mdl;
+}
+
+/**
+ * Object.
+ *
+ * Output formatted commentary to element.
+ *
+ * @constructor
+ */
+function reporter(output_id){
+
+ var output_elt = '#' + output_id;
+ var list_elt = null;
+
+ // ...
+ function _date_str(n){
+
+ function _zero_fill(n){
+ var ret = n;
+ if( ret < 10 ){
+ ret = '0' + ret;
+ }
+ return ret;
+ }
+
+ var now = new Date();
+ var dts = now.getFullYear() + '/' +
+ _zero_fill(now.getMonth() +1) + '/' +
+ _zero_fill(now.getDate()) + ' ' +
+ _zero_fill(now.getHours()) + ':' +
+ _zero_fill(now.getMinutes()) + ':' +
+ _zero_fill(now.getSeconds());
+ return dts;
+ }
+
+ this.reset = function(){
+ jQuery(output_elt).empty();
+ var new_list_id = bbop_core.uuid();
+ list_elt = '#' + new_list_id;
+ jQuery(output_elt).append('
');
+ };
+
+ this.comment = function(message){
+
+ // Try and set some defaults.
+ var uid = null;
+ var color = null;
+ if( message ){
+ uid = message['user_name'] ||
+ message['user_email'] ||
+ message['socket_id'];
+ color = message['user_color'];
+ }
+
+ // Start.
+ var out = '
';
+
+ // Add color if defined.
+ out += _date_str() + ': ';
+ if( uid && color ){
+ out += ''+ uid + ': ';
+ }else if( uid ){
+ out += ''+ uid + ': ';
+ }
+
+ // Complicated datagram.
+ var intent = message['intention'] || '??? (intention)';
+ var sig = message['signal'] || '??? (signal)';
+ var mess = message['message'] || '??? (message)';
+ var mess_type = message['message_type'] || '??? (meesage_type)';
+
+ // make a sensible message.
+ if( mess_type === 'error' ){
+ out += mess_type + ': there was a problem: ' + mess;
+ }else{
+ if( sig === 'merge' || sig === 'rebuild' ){
+ if( intent === 'query' ){
+ out += mess_type + ': they likely refreshed';
+ }else{
+ out += 'performed ' +
+ intent + ' (' + mess + '), ' +
+ '' +
+ 'you may wish to refresh' + '';
+ }
+ }else{
+ out += mess_type + ': ' + mess;
+ }
+ }
+
+ // End.
+ out += '
';
+
+ // Actually do it.
+ jQuery(list_elt).prepend(out);
+ };
+
+ // Initialize.
+ this.reset();
+}
+
+/**
+ * Given a token, either report a bad token ot
+ *
+ * Parameters:
+ * barista_loc - barista location
+ * given_token - token
+ * elt_id - element to replace
+ * user_group_fun - [optional] function that returns the current user group id
+ * change_group_announce_fun - [optional] function that returns the current user group id; if false or null (a opposed to undefined), don't use callback and don't draw selector
+ *
+ * Returns: function that returns current group id/state ???
+ */
+function user_check(barista_loc, given_token, elt_id,
+ change_group_announce_fun){
+
+ // Decide whether to render the groups, and if there is a default
+ // callback to use.
+ var render_groups_p = true;
+ if( typeof(change_group_announce_fun) === 'undefined' ){
+ change_group_announce_fun = function(group_id){
+ alert('Ignoring group change to: ' + group_id);
+ };
+ }else if(change_group_announce_fun === false ){
+ render_groups_p = false;
+ }else if(change_group_announce_fun === null ){
+ render_groups_p = false;
+ }
+
+ // Redraw the widget from scratch with the incoming data.
+ var _redraw_widget = function(user_group_id, data){
+
+ // Do a basic check on the data; if bad,
+ // try and recover by clearing the token.
+ if( ! data || ! data['uri'] ){
+
+ alert('You seem to have a bad token; will try to clean...');
+ var to_remove = 'barista_token=' + given_token;
+ var new_url = window.location.toString().replace(to_remove, '');
+ //var new_url = window.location;
+
+ window.location.replace(new_url);
+ console.log('user_check window.location', window.location);
+
+ }else{
+
+ var eid2gid = {};
+
+ // Render a single entry in the groups dropdown.
+ var something_checked_p = false;
+ var _render_entry = function(user_uri, group_id, group_label){
+
+ var chk = '✔ ';
+ var box = '□ ';
+ var ret = '';
+
+ var fresh_id = '_user_group_' + bbop_core.uuid();
+
+ if( user_group_id === group_id ){
+ // Bold if it is our current group.
+ ret = '
';
+ eid2gid[fresh_id] = group_id;
+ }
+
+ return ret;
+ };
+
+ // Try and get the best user name we can.
+ var name = data['nickname'] || data['uri'];
+
+ // If there is group information, create an active widget,
+ // otherwise create a silent one.
+ if( ! us.isArray(data['groups']) ||
+ data['groups'].length === 0 ||
+ render_groups_p === false ){
+
+ // Inactive replacement.
+ var nsel = '' + name + '';
+ jQuery('#' + elt_id).replaceWith(nsel);
+
+ }else{
+
+ // Create the groups list, select the first.
+ // If the first is the uri of the user, select none.
+ var group_list = [];
+ var add_none_p = true;
+ us.each(data['groups'], function(grp){
+
+ // If the user's URI is in there, skip adding
+ // "none" later.
+ if( grp['id'] === data['uri'] ){
+ add_none_p = false;
+ }
+ var ent = _render_entry(data['uri'],
+ grp['id'], grp['label']);
+ group_list.push(ent);
+ });
+
+ // If we did not run into the user's id, add "none" to
+ // the bottom.
+ if( add_none_p ){
+ var nent = null;
+ if( something_checked_p ){
+ nent = _render_entry(data['uri'], null, "(none)");
+ }else{
+ nent = _render_entry(data['uri'], data['uri'], "(none)");
+ }
+ group_list.push(nent);
+ }
+
+ // Create active widget.
+ var gsel = [
+ '',
+ '
',
+ ''+
+ name + ' ',
+ '
',
+ group_list.join(' '),
+ '
',
+ '
'
+ ];
+ jQuery('#' + elt_id).replaceWith(gsel.join(''));
+
+ // User callback on change.
+ us.each(eid2gid, function(gid, eid){
+ jQuery('#' + eid).click(function(evt){
+ evt.stopPropagation();
+
+ // Redraw with new highlight.
+ _redraw_widget(gid, data);
+
+ // Apply user-supplied function.
+ if( typeof(change_group_announce_fun) === 'function' ){
+ change_group_announce_fun(gid);
+ }
+ });
+ });
+ }
+ }
+ };
+
+ var user_info_loc = barista_loc + "/user_info_by_token/" + given_token;
+ jQuery.ajax({
+ 'type': "GET",
+ 'url': user_info_loc,
+ 'dataType': "json",
+ 'error': function(){
+ alert('had an error getting user info for: ' + given_token);
+ },
+ 'success': function(data){
+
+ // Figure out if there is an initial group to handle.
+ var init_user_group = null;
+ if( data && us.isArray(data['groups']) ){
+ if( data['groups'].length > 0 ){
+ var first_group = data['groups'][0];
+ if( first_group && first_group['id'] ){
+ init_user_group = first_group['id'];
+ }
+ }
+ }
+
+ // Initial draw, hopefully with the right group.
+ _redraw_widget(init_user_group, data);
+
+ // Initial use of change group announce fun.
+ if( typeof(change_group_announce_fun) === 'function' ){
+ change_group_announce_fun(init_user_group);
+ }
+ }
+ });
+}
+
+/**
+ * Essentially, minimal rendered as a usable span, with a color
+ * option.
+ */
+function type_to_span(in_type, color){
+
+ var text = null;
+
+ var min = in_type.to_string();
+ var more = in_type.to_string_plus();
+ if( color ){
+ text = '' + min + '';
+ }else{
+ text = '' + min + '';
+ }
+
+ return text;
+}
+
+/**
+ * A recursive writer for when we no longer care--a table that goes on
+ * and on...
+ */
+function type_to_full(in_type, aid){
+ var anchor = this;
+
+ var text = '[???]';
+
+ var t = in_type.type();
+ if( t === 'class' ){ // if simple, the easy way out
+ text = in_type.to_string_plus();
+ }else{
+ // For everything else, we're gunna hafta do a little
+ // lifting...
+ var cache = [];
+ if( t === 'union' || t === 'intersection' ){
+
+ // Some kind of recursion on a frame then.
+ cache = [
+ '
');
+
+ text = cache.join('');
+
+ }else{
+
+ // A little harder: need to a an SVF wrap before I recur.
+ var pid = in_type.property_id();
+ var plabel = in_type.property_label();
+ var svfce = in_type.svf_class_expression();
+ cache = [
+ '