lib/manager.js
function (golr_loc, golr_conf_obj, engine, mode){
    registry.call(this, ['prerun',
			 'reset',
			 'search',
			 'error',
			 'postrun']);
    this._is_a = 'bbop-manager-golr';
    var anchor = this; // get a good self-reference point

    // Per-manager logger.
    this._logger = new bbop.logger(this._is_a);
    //this._logger.DEBUG = true;
    this._logger.DEBUG = false;
    function ll(str){ anchor._logger.kvetch(str); }

    // Setup right handler subsystem--either returning a direct
    // response or a promise, depending.
    anchor._engine = engine || null;
    anchor._mode = mode || null;
    anchor._runner = function(resource, payload){
	var ret = null;
	// if( typeof(anchor._engine) === 'undefined' ){
	//     throw new Error('"engine" not set in new bbop-manager-golr');
	// }
	if( anchor._mode === 'sync' ){
	    ret = anchor._engine.fetch(resource, payload);
	}else if( anchor._mode === 'async' ){
	    ret = anchor._engine.start(resource, payload);
	}else{
	    // throw new Error('"mode" not set in new bbop-manager-golr');
	    return resource;
	}
	return ret;
    };

    // The callback function called after a successful AJAX
    // intialization/reset call. First it runs some template code,
    // then it does all of the callbacks.
    anchor._run_reset_callbacks = function(response){
	ll('run reset callbacks...');
	//var response = new golr_response(json_data);
	anchor.apply_callbacks('reset', [response, anchor]);
	anchor.apply_callbacks('postrun', [response, anchor]);
    };

    // The main callback function called after a successful AJAX call in
    // the update function.
    anchor._run_search_callbacks = function(response){
	ll('run search callbacks...');
	//var response = new golr_response(json_data);
	anchor.apply_callbacks('search', [response, anchor]);
	anchor.apply_callbacks('postrun', [response, anchor]);
    };

    // This set is called when we run into a problem.
    anchor._run_error_callbacks = function(response){
	ll('run error callbacks...');
	//var response = new golr_response(json_data);
	anchor.apply_callbacks('error', [response, anchor]);
	anchor.apply_callbacks('postrun', [response, anchor]);
    };

    // The registries can only be applied if the engine is present.
    if( engine ){

	// Re-contextualize run_promise_functions().
	anchor.run_promise_functions = function(promise_function_stack, 
						accumulator_function,
						final_function,
						error_function){
	    // NOTE: Cannot sensibly use Function.call here as engine
	    // is not really the superclass of this manager.
	    return engine.run_promise_functions(promise_function_stack,
						accumulator_function,
						final_function,
						error_function);
	};

	// Setup the appropriate callbacks in the engine for success.
	anchor._engine.register('success', function(response){
	    
    	    //console.log(anchor._engine);
    	    //console.log(response._raw);
	    
            // 
            if( ! response.success() ){
		throw new Error("Unsuccessful response from golr server!");
            }else{
		var cb_type = response.callback_type();
		//console.log('okay response from server, will probe type...: '+cb_type);
		if( cb_type === 'reset' ){
                    anchor._run_reset_callbacks(response);
		}else if( cb_type === 'search' ){
                    anchor._run_search_callbacks(response);
		}else{
                    throw new Error("Unknown callback type (runner): "+cb_type);
		}
            }
	});
	
	// And, more easily, setup the appropriate callbacks in the
	// engine for failure.
	anchor._engine.register('error', function(response){
    	    anchor._run_error_callbacks(response);
	});
	
    }

    // Just want to compile these once.
    var alphanum = new RegExp(/^[a-zA-Z0-9 ]+$/);

    this.minimal_query_length = 3;

    // To help keep requests from the past haunting us. Actually doing
    // something with this number is up to the UI.
    this.last_sent_packet = 0;
    //this.last_received_packet = 0;

    // Lightly check incoming arguments.
    // There should be a string url argument.
    // There could be a hash of pinned filters argument.
    if( ! golr_loc || ! golr_conf_obj ){
	ll('ERROR: no proper arguments');
    }
    if( typeof golr_loc !== 'string' ){
	ll('ERROR: no proper golr url string argument');
    }
    if(	! golr_conf_obj._is_a || golr_conf_obj._is_a !== 'golr-conf.conf' ){
	    ll('ERROR: no proper golr-conf.conf object argument');
	    throw new Error('boink! ' + bbop.what_is(golr_conf_obj) );
	}
    
    // Whether or not to prevent ajax events from going.
    // This may not be usable, or applicable, to all backends.
    this._safety = false;

    // Our default target url.
    this._solr_url = golr_loc;

    // Settle in the configurations.
    // this._golr_conf = new bbop_golr.conf(golr_conf_var);
    this._golr_conf = golr_conf_obj;

    // The current data batches that we are storing.
    this._batch_urls = [];
    this._batch_accumulator_func = function(){};
    this._batch_final_func = function(){};

    // The current state stack.
    this._excursions = [];

    // The current class/personality that we're using. It may be none.
    this._current_class = null;

    // Our (default) query and the real deal.
    this.fundamental_query = '*:*'; // cannot be changed
    this.default_query = '*:*'; // changable
    this.query = this.default_query; // current

    // Our (default) fl and whatever we have now.
    //this.default_fl = '*%2Cscore';
    this.default_fl = '*,score';
    this.current_fl = this.default_fl;

    // We remember defaults in the case of rows and start since they
    // are the core to any paging mechanisms and may change often.
    //this.default_rows = 25;
    //this.default_rows = 100;
    this.default_rows = 10;
    this.default_start = 0;
    this.current_rows = this.default_rows;
    this.current_start = this.default_start;

    // There is a reason for this...TODO: later (25+)
    this.default_facet_limit = 25;
    this.current_facet_limit = 25;
    // {facet_field_name: value, ...}
    this.current_facet_field_limits = {};
    // TODO: paging for facets;
    this.current_facet_offset = 25;
    this.current_facet_field_offsets = {};

    // Number of snippets to return when hl is enable
    this.default_hl_snippets = 1000;

    // Our default query args, with facet fields plugged in.
    this.query_variants =
	{
	    // Our default standard search type. This means we don't
	    // have to explicitly add fields to the search (although
	    // the query fields ('qf') are still necessary to make
	    // anything real happen).
	    defType: 'edismax',
	    
	    // Things unlikely to be touched.
	    // There are unlikely to be messed with too much.
	    qt: 'standard',
	    indent: 'on',
	    wt: 'json',
	    //version: '2.2',
	    rows: anchor.current_rows,
	    start: anchor.current_start, // Solr is offset indexing
	    //fl: '*%2Cscore',
	    fl: anchor.default_fl,
    
	    // Deprecated: see query_filters
	    //fq: {},
	    
	    // Deprecated: see query_fields
	    //qf: {},
	    
	    // Deprecated: see query
	    //q: '*:*'

	    // Control of facets.
	    facet: 'true',
	    'facet.mincount': 1,
	    'facet.sort': 'count',
	    'json.nl': 'arrarr', // only in facets right now
	    'facet.limit': anchor.default_facet_limit
	    // TODO?: 'f.???.facet.limit': 50,
	    // TODO: 'json.nl': [flat|map|arrarr]

	    // Deprecated: see facet_fields
	    //'facet.field': []
	};

    // This is the 'qf' parameter. Although we keep it, it only needs
    // to be exposed when the query ('q') field is set.
    //this.query_fields = [];
    this.query_fields = {};

    // A richer way to handle the 'fq' query variant.
    // It should look like:
    // {<filter>: {<value>:{'sticky_p':(t|f), 'negative_p':(t|f)}, ...}}
    this.query_filters = {};

    // The engine for the facet.field list.
    this.facet_fields = {};

    /*
     * Function: debug
     * 
     * Turn on or off the verbose messages. Uses <bbop.logger>, so
     * they should come out everywhere.
     * 
     * Parameters: 
     *  p - *[optional]* true or false for debugging
     *
     * Returns: 
     *  boolean; the current state of debugging
     */
    this.debug = function(p){
	if( p === true || p === false ){
	    this._logger.DEBUG = p;
	    // TODO: add debug parameter a la include_highlighting
	}
	return this._logger.DEBUG;
    };

    /*
     * Function: minimal_query
     * 
     * The length of the minimal query to consider. Defaults to 3.
     * 
     * Parameters: 
     *  n - *[optional]* number, or nothing if just want current
     *
     * Returns: 
     *  number
     */
    this.minimal_query = function(n){
	if( us.isNumber(n) ){
	    anchor.minimal_query_length = n;
	}
	return this.minimal_query_length;
    };

    /*
     * Function: lite
     * 
     * Limit the returns fields (the parameter "fl") to the ones
     * defined in the set of fields defined in results, label fields
     * if available (i.e. "_label", "_map" when "_label" is
     * multi=valued), and "score" and "id".
     * 
     * The default is "false".
     * 
     * Parameters: 
     *  use_lite_p - *[optional]* true or false, none just returns current
     *
     * Returns: 
     *  boolean; the current state of lite-ness
     */
    this.lite = function(use_lite_p){

	// Adjust the current state accordingly.
	if( use_lite_p === true || use_lite_p === false ){
	    if( use_lite_p === true ){
		
		// The actual collections and adjustment.
		// First, this only works if we have a personality, so
		// check to see if we have one.
		var per = anchor.get_personality();
		if( per ){
		    // Since we have a personality, collect all of the
		    // mentioned fields.
		    var field_collection = {};
		    var union = bbop.merge;
		    var ccl = anchor._current_class;

		    // Fill field_collection with the fields
		    // in the given category.
		    //loop(['boost', 'result', 'filter'],
		    //loop(['result', 'filter'],
		    each(['result'], function(cat){
			field_collection = 
			    union(field_collection, ccl.get_weights(cat));
		    });
		    
		    // Next, flatten into a list.
		    var flist = us.keys(field_collection);

		    // Now for all the fields in these categories, see
		    // if we can find additional "special" labels to
		    // go with them.
		    each(flist, function(flist_item){
			//loop(['_label', '_label_searchable'],
			each(['_label'], function(field_suffix){
			    var new_field = flist_item + field_suffix;
			    var nf_obj = ccl.get_field(new_field);
			    if( nf_obj ){
				flist.push(new_field);
				
				// There appears to be the
				// thing label. If they are
				// both multi-valued, then
				// there will be a map as
				// well.
				if( nf_obj.is_multi() ){
				    flist.push(flist_item + '_map');
				}
			    }
			});
		    });
		    
		    // Finally, set these fields (plus score) as the
		    // new return fields.
		    flist.push('score');
		    flist.push('id');
		    //anchor.current_fl = flist.join('%2C');
		    anchor.current_fl = flist.join(',');
		    anchor.set('fl', anchor.current_fl);
		}
		
	    }else{ // else false
		// Reset.
		anchor.current_fl = anchor.default_fl;
		anchor.set('fl', anchor.current_fl);
	    }
	}

	// Return the current state.
	var retval = false;
	if( anchor.default_fl !== anchor.current_fl ){
	    retval = true;
	}
	return retval;
    };

    // An internal helper function to munge the name of a field into
    // the name of its corresponding facet field.
    function _field_to_facet_field(field){
	return 'f.' + field + '.facet.limit';
    }
    
    /*
     * Function: get_facet_limit
     * 
     * Get the limit for a specified facet or the global limit.
     * 
     * Parameters: 
     *  field - *[optional]* limit for a specific field; otherwise global value
     *
     * Returns: 
     *  integer or null
     */
    this.get_facet_limit = function(field){
	var retval = null;

	if( ! field ){
	    retval = anchor.current_facet_limit;
	}else{
	    var f = _field_to_facet_field(field);
	    var try_val = anchor.current_facet_field_limits[f];
	    if( typeof(try_val) !== 'undefined' ){
		retval = try_val;
	    }
	}

	return retval;
    };

    /*
     * Function: set_facet_limit
     * 
     * Change the number of facet values returned per call.
     * The default is likely 25.
     * 
     * Just as in Solr, a -1 argument is how to indicate unlimted
     * facet returns.
     * 
     * This setting does not survive things like <resets_facet_limit>.
     * 
     * Parameters: 
     *  arg1 - (integer) set the global limit
     *
     * Parameters: 
     *  arg1 - (string) the name of the field to check
     *  arg2 - (integer) set the limit for this field
     *
     * Returns: 
     *  boolean on whether something was set
     */
    this.set_facet_limit = function(arg1, arg2){
	var retval = false;

	// Decide which form of the function we're using.
	if( typeof(arg2) === 'undefined' &&
	    bbop.what_is(arg1) === 'number' ){ // form one
		
		// Set
		var nlimit = arg1;
		anchor.current_facet_limit = nlimit;
		anchor.set('facet.limit', anchor.current_facet_limit);
		
		retval = true;
	
	}else if( typeof(arg1) !== 'undefined' && 
		  typeof(arg2) !== 'undefined' && 
		  bbop.what_is(arg1) === 'string' &&
		  bbop.what_is(arg2) === 'number' ){
		      
		      var field = _field_to_facet_field(arg1);
		      var limit = arg2;
		      anchor.current_facet_field_limits[field] = limit;
		      
		      retval = true;
	}

	return retval;
    };

    /*
     * Function: set_default_facet_limit
     * 
     * Permanently change the default number of facet values returned
     * per call. The default's default is likely 25.
     * 
     * Just as in Solr, a -1 argument is how to indicate unlimted
     * facet returns.
     * 
     * Parameters: 
     *  lim - (integer) set the global default limit
     *
     * Returns: 
     *  old default
     */
    this.set_default_facet_limit = function(lim){

	// Capture ret.
	var retval = anchor.default_facet_limit;

	// Set
	anchor.default_facet_limit = lim;
	//anchor.set('facet.limit', anchor.default_facet_limit);
		
	return retval;
    };

    /*
     * Function: reset_facet_limit
     * 
     * Either reset the global limit to the original (likely 25)
     * and/or remove the specified filter. Sets everything back to the
     * original values or whatever was set by
     * <set_default_facet_limit>.
     * 
     * Parameters: 
     *  field - *[optional]* remove limit for a field; otherwise all and global
     *
     * Returns: 
     *  boolean on whether something was reset
     */
    this.reset_facet_limit = function(field){
	var retval = false;

	if( typeof(field) === 'undefined' ){
	    // Eliminate all fields by blowing them away.
	    anchor.current_facet_limit = anchor.default_facet_limit;
	    anchor.set('facet.limit', anchor.current_facet_limit);
	    anchor.current_facet_field_limits = {};
	    retval = true;
	}else{ // eliminate just the one field
	    var f = _field_to_facet_field(field);
	    if( typeof(anchor.current_facet_field_limits[f]) !== 'undefined' ){
		delete anchor.current_facet_field_limits[f];
		retval = true;
	    }
	}

	return retval;
    };

    /*
     * Function: get_results_count
     * 
     * Get the current number of results that will be returned.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns: 
     *  integer
     */
    this.get_results_count = function(field){
	return anchor.get('rows');
    };

    /*
     * Function: set_results_count
     * 
     * Change the number of result documents returned per call.
     * The default is likely 10.
     * 
     * Parameters: 
     *  count - (integer) set the global results count
     *
     * Returns:
     *  the count set
     */
    this.set_results_count = function(count){
	anchor.set('rows', count);
	anchor.current_rows = count;
	return anchor.current_rows;
    };

    /*
     * Function: reset_results_count
     * 
     * Reset the number of documents to their original setting, likely
     * 10.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the new count
     */
    this.reset_results_count = function(){
	anchor.set('rows', anchor.default_rows);
	anchor.current_rows = anchor.default_rows;
	return anchor.current_rows;
    };

    /*
     * Function: plist_to_property_hash
     *
     * Turn a plist to a hash containing the different properties that
     * can be defined for a query filter. Possible values are: '+'
     * (positive filter), '-' (negative filter), '*' (sticky filter),
     * '$' (transient). If mutually exclusive properties are defined
     * (e.g. both '+' and '-'), the last one will be used. Or, since
     * that is a call to silliness, let's say the behavior is
     * undefined.
     *
     * Parameters: 
     *  plist - *[optional]* a list of properties to apply to the filter
     *
     * Returns: 
     *  A hash version of the plist; otherwise, the default property hash
     */
    this.plist_to_property_hash = function(plist){

	// Let's start with the default values.
	var phash = {
	    //'positive_p': true,
	    'negative_p': false,
	    //'transient_p': true
	    'sticky_p': false
	};

	// If not defined, just return the default list.
	if( plist ){	    
	    each(plist, function(item){
		if( item === '+' ){
		    phash['negative_p'] = false;
		    //phash['positive_p'] = true;
		}else if( item === '-' ){
		    phash['negative_p'] = true;
		    //phash['positive_p'] = false;
		}else if( item === '*' ){
		    phash['sticky_p'] = true;
		    //phash['transient_p'] = false;
		}else if( item === '$' ){
		    phash['sticky_p'] = false;
		    //phash['transient_p'] = true;
		}
	    });
	}

	return phash;
    };

    /*
     * Function: add_query_filter_as_string
     *
     * Setter for query filters ('fq'). Acts as a 
     *
     * Parameters: 
     *  filter_string - filter (type) string (e.g. "-type:gene")
     *  plist - *[optional]* list of properties of the filter
     *
     * Returns: 
     *  (TODO) The current query filter hash.
     * 
     * See also:
     *  <add_query_filter>
     */
    this.add_query_filter_as_string = function(filter_string, plist){
	
	// Split the incoming filter string into its component parts.
	var f_v = bbop.first_split(':', filter_string);
	var fname = f_v[0];
	var fval = f_v[1];

	// Need to shuck the value from the quotes, as in load_url.
	fval = bbop.dequote(fval);

	var props = null;
	if( us.isArray(plist) ){
	    props = plist;
	}else{
	    props = [];
	}

	// Only continue on sensible inputs.
	var ret = {};
	if( fname !== '' && fval !== '' ){

	    // Similar to the URL loader.
	    var lead_char = fname.charAt(0);
	    if( lead_char === '-' || lead_char === '+' ){
		props.push(lead_char);
		fname = fname.substr(1, fname.length -1);
	    }
	    
	    ret = this.add_query_filter(fname, fval, props);
	}

	return ret;
    };

    /*
     * Function: add_query_filter
     *
     * Setter for query filters ('fq').
     *
     * Parameters: 
     *  filter - filter (type) string
     *  value - filter value string (or TODO: defined logic hash)
     *  plist - *[optional]* list of properties of the filter
     *
     * Returns: 
     *  (TODO) The current query filter hash.
     * 
     * See also:
     *  <plist_to_property_hash>
     */
    this.add_query_filter = function(filter, value, plist){
	
	// Make sure we've defined the group.
	if( typeof(this.query_filters[filter]) === 'undefined' ){
	    this.query_filters[filter] = {};
	}

	this.query_filters[filter][value] = this.plist_to_property_hash(plist);
	
	//ll("Current state: " + bbop.dump(this.query_filters));

	return {}; // TODO
    };

    /*
     * Function: remove_query_filter
     *
     * Remover for query filters ('fq'), is a plist is specified, it
     * will only remove if all of the listed criteria are met.
     *
     * Parameters: 
     *  filter - filter (type) string
     *  value - filter value string (TODO: or defined logic hash)
     *  plist - *[optional]* list of properties of the filter
     *
     * Returns: 
     *  boolean (on success)
     */
    this.remove_query_filter = function(filter, value, plist){

	// Default return value.
	var retval = false;

	// Internal helper to delete a low level key, and then if the
	// top-level is empty, get that one too.
	function _full_delete(hash, key1, key2){
	    if( key1 && key2 && hash &&
		hash[key1] && hash[key1][key2] ){
		    delete hash[key1][key2];
		}
	    if( us.isEmpty(hash[key1]) ){
		delete hash[key1];
	    }
	}

	// If we have a filter, a value, and it's there...
	if( filter && value &&
	    anchor.query_filters[filter] &&
	    anchor.query_filters[filter][value] ){

		// If no real plist hash been defined, just go ahead
		// and get rid of that. Otherwise, make sure that the
		// defined plist and the stored properties are the
		// same before deleting.
		if( ! plist || us.isEmpty(plist) ){
		    _full_delete(anchor.query_filters, filter, value);
		    retval = true;
		}else{
		    
		    var filter_phash = anchor.query_filters[filter][value];
		    var in_phash = anchor.plist_to_property_hash(plist);
		    
		    if( bbop.is_same(filter_phash, in_phash) ){		
			_full_delete(anchor.query_filters, filter, value);
			retval = true;
		    }
		}
	    }

	return retval;
    };

    /*
     * Function: reset_query_filters
     *
     * Reset the query filters ('fq'); but leave sticky filters alone.
     *
     * Parameters: 
     *  n/a
     * 
     * Returns: 
     *  (TODO) The current query filter hash.
     */
    this.reset_query_filters = function(){

	// Drill down and delete all non-stickies.
	each(anchor.query_filters, function(values, filter){
	    //ll('filter: ' + filter);
	    each(values, function(props, value){
		//ll('  value: ' + value);
		var sticky_p = props['sticky_p'];
		if( ! sticky_p ){
		    //ll('hit: ' + filter + ', ' + value);
		    anchor.remove_query_filter(filter, value);
		}
	    });
	});
	
	return {}; // TODO
    };

    /*
     * Function: get_query_filter_properties
     *
     * Get a hash representing a query filter ('fq').
     *
     * Parameters: 
     *  key - filter string (TODO: or defined logic hash)
     *
     * Returns: 
     *  The current query filter hash for key.
     */
    this.get_query_filter_properties = function(filter, value){

	// Default return value.
	var retobj = null;
	
	// If we have a key and it's there...
	var aqf = anchor.query_filters;
	if( filter && value && aqf[filter] && aqf[filter][value] ){
	    retobj =
		{
		    'filter' : filter,
		    'value' : value,
		    //'polarity': aqf[filter][value]['negative_p'],
		    'negative_p': aqf[filter][value]['negative_p'],
		    'sticky_p': aqf[filter][value]['sticky_p']
		};
	}

	return retobj;
    };

    /*
     * Function: get_query_filters
     *
     * Get a list of hashes representing the query filters ('fq'). The
     * return lists look like:
     *
     * : [{'filter': A, 'value': B, 'negative_p': C, 'sticky_p': D}, ...]
     *
     * Where A and B are strings and C and D are booleans.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns: 
     *  A list of the current query filter hashs.
     */
    this.get_query_filters = function(){

	var retlist = [];	
	each(anchor.query_filters, function(values, f){
	    each(values, function(props, v){
		retlist.push(anchor.get_query_filter_properties(f, v));
	    });
	});

	return retlist;
    };

    /*
     * Function: get_sticky_query_filters
     *
     * Get a list of hashes representing the current stucky query
     * filters ('fq'). See <get_query_filters> for a specification of
     * what the return type looks like.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns: 
     *  A list of the current sticky query filter hashs.
     * 
     * See also:
     *  <get_query_filters>
     */
    this.get_sticky_query_filters = function(){

	var retlist = [];	
	each(anchor.query_filters, function(values, f){
	    each(values, function(props, v){
		var qfp = anchor.get_query_filter_properties(f, v);
		if( qfp['sticky_p'] === true ){
		    retlist.push(qfp);			      
		}
	    });
	});

	return retlist;
    };

    // A little extra thing that we might need sometimes.
    this.query_extra = null;

    // Spaces can cause problems in URLs in some environments.
    //final_qurl = final_qurl.replace(/ /g, '%20');
    // Convert the URL into something more usable.
    // Because we internally use %09 as a special case, make sure
    // we don't double-up on it.
    this._query_encode = function(str_to_encode){

	var fs1 = encodeURI(str_to_encode);
	var fs2 = fs1.replace(/\%2509/g, '%09');

	var final_encoding = fs2;
	return final_encoding;
    };

    /*
     * Function: filter_list_to_assemble_hash
     *
     * Get all of our query filter variables and try and make
     * something of them that <get_assemble> can understand.
     *
     * Sticky doesn't matter here, but negativity does. However, we
     * can be pretty naive since the hashing should have already taken
     * out mutually exclusive dupes.
     * 
     * The argument is a list of query filter properties, as returned
     * by <get_query_filters> and <get_sticky_query_filters>.
     *
     * Parameters:
     *  flist - a list of query filter properties (see above)
     *
     * Returns:
     *  hash of filter names to value lists
     * 
     * See also:
     *  <get_query_filters>
     *  <get_sticky_query_filters>
     */
    this.filter_list_to_assemble_hash = function(flist){
	var h = {};
	each(flist, function(filter_property){
	    
	    // Grab only the properties that affect the
	    // URL.
	    var filter = filter_property['filter'];
	    var value = filter_property['value'];
	    var negative_p = filter_property['negative_p'];
	    
	    // We need to alter at the filter level.
	    if( negative_p ){
		filter = '-' + filter;
	    }
	    
	    // Make sure it is defined.
	    if( typeof(h[filter]) === 'undefined' ){
		h[filter] = [];
	    }
	    h[filter].push(value);
	});
	return h;
    };
    
    /*
     * Function: sensible_query_p
     * 
     * Simply ask the manager if a free text query ('q') makes sense
     * at this point.
     * 
     * This currently means that the query text ('q') is one of:
     *  1) the same as the default query
     *  2) the same as the fundamental query
     *  3) three (3) (or whatever minimal_query is set to) characters long
     *  4) the empty string ('')
     * and that query fields ('qf') are defined.
     * 
     * This is an overridable opinion of the manager.
     * 
     * Parameters:
     *  n/a
     *
     * Returns:
     *  boolean
     */
    this.sensible_query_p = function(){
	var retval = false;
	var q = anchor.get_query();
	var qf = anchor.query_field_set();

	//console.log('q', q);
	//console.log('qf', qf);

	if( qf && ! us.isEmpty(qf) ){ // first hurdle

	    if( q === anchor.get_default_query() ){
		retval = true;
	    }else if( q === anchor.get_fundamental_query() ){
		retval = true;
	    }else if( q && q.length >= anchor.minimal_query_length ){
		retval = true;
	    }else if( q === ''){
		retval = true;
	    }
	}
	return retval;
    };

    /*
     * Function: last_packet_sent
     *
     * It is up to the UI to do something interesting with this number.
     * 
     * Also remember that this number only rises through calls to
     * <update> or one of its wrappers. Calls to <get_query_url> and
     * the like will not affect this number.
     * 
     * Parameters:
     *  n/a 
     *
     * Returns:
     *  integer
     * 
     * See also:
     *  <update>
     */
    this.last_packet_sent = function(){
    	return anchor.last_sent_packet;
    };

    /*
     * Function: clear
     *
     * Clear all non-sticky query parameters to get back to a more
     * "original" state.
     * 
     * Not to be confused with <reset>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  n/a
     */
    this.clear = function(){

	// Reset 'q'.
	anchor.query = anchor.default_query;

	// Reset 'fq', all but sticky.
	anchor.reset_query_filters();
    };

    /*
     * Function: reset
     *
     * Manually trigger the "reset" chain of events.
     *
     * This is a curried wrapper for <update> and should be preferred
     * over a direct call to update.
     *
     * Note to be confused with <clear>.
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <update>
     */
    this.reset = function(){
	return anchor.update('reset');
    };

    /*
     * Function: search
     *
     * Trigger the "search" chain of events.
     * Takes a field-keyed hash of bbop.logics as an argument.
     * 
     * This is a curried wrapper for <update> and should be preferred
     * over a direct call to update.
     * 
     * Parameters:
     *  n/a
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <update>
     */
    this.search = function(){
	return anchor.update('search');
    };

    /*
     * Function: page
     *
     * Re-trigger the "search" chain of events, but with the variables
     * set for a different section of the results.
     * 
     * Note that this operates independently of any impossibilites in
     * the results--just how such paging would look and
     * triggering. Ths UI should handle impossibilities and the like.
     * 
     * This is a wrapper for <update> and should be preferred over a
     * direct call to update.
     * 
     * Parameters: 
     *  rows - the number of rows to return
     *  start - the offset of the rows to return
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <update>
     */
    this.page = function(rows, start){
	anchor.set('rows', rows);
	anchor.set('start', start);
	return anchor.update('search', rows, start);
    };

    /*
     * Function: page_first
     *
     * Currently a convenience alias for <search>. Think about it--it
     * makes sense.
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  n/a
     * 
     * See also:
     *  <page>
     */
    this.page_first = anchor.search;
    
    /*
     * Function: page_previous
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <page>
     */
    this.page_previous = function(){
	var do_rows = anchor.get_page_rows();
	var do_offset = anchor.get_page_start() - do_rows;
	return anchor.page(do_rows, do_offset);
    };
    
    /*
     * Function: page_next
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  the query url (with the jQuery callback specific parameters)
     *
     * Returns:
     *  n/a
     * 
     * See also:
     *  <page>
     */
    this.page_next = function(){
	var do_rows = anchor.get_page_rows();
	var do_offset = anchor.get_page_start() + do_rows;
	return anchor.page(do_rows, do_offset);
    };
    
    /*
     * Function: page_last
     * 
     * Trigger search on last page parameters.
     * 
     * Since the manager has no idea about what is actually being
     * returned, the real world number of total documents needs to be
     * added as an argument.
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  total_document_count - integer for the total number of docs found
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <page>
     */
    this.page_last = function(total_document_count){
	var do_rows = anchor.get_page_rows();
	var mod = total_document_count % do_rows;
	var do_offset = total_document_count - mod;
	// ll("page_last: " + total_document_count + " " +
	//    do_rows + " " + mod + " " + do_offset);
	var ret = null;
	if( mod === 0 ){
	    ret = anchor.page(do_rows, do_offset - do_rows);
	}else{
	    ret = anchor.page(do_rows, do_offset);
	}
	return ret;
    };

    /*
     * Function: get_page_rows
     *
     * Return the number of rows the manager is currently set
     * to. Useful as an argument to <page>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  integer; the number of rows the manager is currently set to
     * 
     * See also:
     *  <page>
     */
    this.get_page_rows = function(){
	return anchor.get('rows');
    };

    /*
     * Function: get_page_start
     *
     * Return the rows offset the manager is currently set to. Useful
     * as an argument to <page>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  integer; the offset the manager is currently set to
     * 
     * See also:
     *  <page>
     */
    this.get_page_start = function(){
	return anchor.get('start');
    };

    /*
     * Function: add_query_field
     * 
     * Add a new query field to the query. 
     * 
     * This does not go through and expand into searchable fields, for
     * that see: <query_field_set>.
     *
     * Parameters: 
     *  qf - the query field to add
     *  boost - *[optional]* defaults to 1.0
     *
     * Returns:
     *  true or false on whether or not it is a new field
     * 
     * See also:
     *  <query_field_set>
     */
    this.add_query_field = function(qf, boost){
	
	var retval = false;

	// Make sure that some boost is there.
	if( typeof(boost) === 'undefined' ){
	    boost = 1.0;
	}

	// Check.
	if( typeof(anchor.query_fields[qf]) === 'undefined' ){
	    retval = true;
	}

	// Add.
	anchor.query_fields[qf] = boost;

	return retval;
    };

    /*
     * Function: query_field_set
     *
     * Bulk getter/setter for the query fields--the fields that are
     * searched (and by what weight) when using a query ('q' or
     * set_query(), i.e. the 'qf' field).
     * 
     * This will always use searchable fields if possible,
     * automatically replacing the non-searchable versions (I can't
     * think of any reason to use non-searchable versions unless you
     * want your searches to not work) if a personality is set. If no
     * personality is set, it will just use the arguments as-is.
     * 
     * The argument replaces the current set.
     *
     * The qfs argument should be a hash like:
     * 
     *  {'field01': value01, ...}
     * 
     * Parameters: 
     *  qfs - *[optional]* query fields to set
     *
     * Returns:
     *  the current query_fields array (e.g. ["field01^value01", ...])
     */
    this.query_field_set = function(qfs){

	// Covenience.
	var cclass = anchor._current_class;

	// Only do something if we have a query field set.
	if( qfs ){
	    
	    // Only do the probing if a personality has been set.
	    if( cclass ){

		// Get the current searchable extension string from
		// the personality class.
		//var s_ext = cclass.searchable_extension();
		// Actually, we're going to make this non-variable.
		var s_ext = '_searchable';

		// Probe the input to see if there are any searchable
		// alternatives to try, use those instead.
		var searchable_qfs = {};
		each(qfs, function(value, filter){
		    // If the probe fails, just put in
		    // whatever is there.
		    var cfield = cclass.get_field(filter);
		    if( cfield && cfield.searchable() ){
			//ll('filter/value:');
			var new_f = filter + s_ext;
			searchable_qfs[new_f] = value;
		    }else{
			searchable_qfs[filter] = value;
		    }
	    	});
		qfs = searchable_qfs;
	    }	    
	    
	    // Overwrite the current.
	    anchor.query_fields = qfs;
	}
	
	// Using the original information, convert them to the
	// proper output format.
	var output_format = [];
	each(anchor.query_fields, function(value, filter){
	    output_format.push(filter + '^' + value);
	});
	return output_format;
    };

    /*
     * Function: facets
     *
     * Bulk getter/setter for facets (technically 'facet.field').
     *
     * Parameters: 
     *  key - *[optional]* facet to add to the facet list
     *
     * Parameters: 
     *  list - *[optional]* list to replace the current list with
     *
     * Returns:
     *  the current facets hash.
     */
    this.facets = function(list_or_key){
	if( list_or_key ){
	    if( bbop.what_is(list_or_key) !== 'array' ){
		// Arrayify it.
		list_or_key = [list_or_key];
	    }else{
		// When there is a list, we are replacing the whole
		// thing, so let's just poof it out of existance.
		anchor.facet_fields = {};
	    }
	    each(list_or_key, function(item){
		anchor.facet_fields[item] = true;
	    });
	}
	return us.keys(anchor.facet_fields);
    };

    /*
     * Function: set_default_query
     *
     * Setter for the default query for the query variable ('q').
     * 
     * Call <reset_query> if you want to affect query immediately.
     * 
     * Parameters: 
     *  new_default_query - new default query string (or TODO: <bbop.logic>)
     *
     * Returns:
     *  the current setting of default query for ('q')
     */
    this.set_default_query = function(new_default_query){
	anchor.default_query = new_default_query;
	return anchor.default_query;
    };

    // /*
    //  * Function: set_first_run_query
    //  *
    //  * Setter for a first run query.  Normally, when <reset_query>, or
    //  * related method, is executed, we reset back to the default
    //  * query. This method sets a one time variable so a non empty
    //  * value can be used for the first reset.
    //  * 
    //  * Call <reset_query> if you want to affect query immediately.
    //  * 
    //  * Parameters: 
    //  *  first_run_query - query_string (or TODO: <bbop.logic>)
    //  *
    //  * Returns:
    //  *  the current setting of default query for ('q')
    //  */
    // this.set_first_run_query = function(first_run_query){
    // 	anchor.default_query = new_default_query;
    // 	return anchor.default_query;
    // };

    /*
     * Function: reset_default_query
     *
     * Reset the default query back to "*:*".
     * 
     * Call <reset_query> if you want to affect query immediately.
     * 
     * Parameters:
     *  n/a
     *
     * Returns:
     *  the current setting of default query ('q')
     */
    this.reset_default_query = function(){
	anchor.default_query = anchor.fundamental_query;
	return anchor.default_query;
    };

    /*
     * Function: set_query
     *
     * Setter for the query variable ('q').
     * 
     * Parameters: 
     *  new_query - new value for the query string (or TODO: <bbop.logic>)
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_comfy_query>
     */
    this.set_query = function(new_query){
	anchor.query = new_query;
	return anchor.query;
    };

    /*
     * Function: set_comfy_query
     *
     * A specialized setter for the query variable ('q'), as follows:
     *
     * If the input is all alphanum or space, the input is
     * tokenized. If there is one token, if it is at least three
     * characters, gets a wildcard '*'; if there are more tokens, the
     * last one gets a wild card.
     * 
     * This might be a more comfortable way to search for most naive
     * (non-power user) interfaces.
     * 
     * Parameters: 
     *  new_query - new value for the query string (or TODO: <bbop.logic>)
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_query>
     */
    this.set_comfy_query = function(new_query){

	var comfy_query = new_query;

	// Check that there is something there.
	if( new_query && new_query.length && new_query.length > 0 ){

	    // Check if the last real input has a space after it.
	    var has_cursor_p = true;
	    if( new_query.slice(-1) === ' ' ){
		has_cursor_p = false;
	    }

	    // Now chomp it down again to get rid of whitespace.
	    new_query = bbop.chomp(new_query);

	    // Check (again) that there is something there.
	    if( new_query && new_query.length && new_query.length > 0 ){

		// That it is alphanum+space-ish and that we actually
		// might want to add a wildcard (i.e. has cursor).
		if( alphanum.test(new_query) && has_cursor_p ){
	    
		    // Break it into tokens and get the last.
		    var tokens = new_query.split(new RegExp('\\s+'));
		    var last_token = tokens[tokens.length -1];
		    //ll('last: ' + last_token);
		
		    if( tokens.length === 1 ){
			
			// If it is three or more, add the wildcard.
			if( last_token.length >= anchor.minimal_query_length ){
			    tokens[tokens.length -1] = last_token + '*';
			}
		    }else{
			tokens[tokens.length -1] = last_token + '*';
		    }
		    // And join it all back into our comfy query.
		    comfy_query = tokens.join(' ');
		}
	    }
	}

	// Kick it back to the normal set_query.
	return anchor.set_query(comfy_query);
    };

    /*
     * Function: set_id
     *
     * A limited setter, removing whatever else is on query. This is
     * for when you want to lock into one (unique) document by id
     * (essentially 'q=id:"foo"'). All other query operations behave
     * as they should around it.
     * 
     * Parameters: 
     *  new_id - string id
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_ids>
     */
    this.set_id = function(new_id){
	anchor.query = 'id:' + bbop.ensure(new_id, '"');
	return anchor.query;
    };

    // Function to unwind and lock a list if identifiers onto a field.
    function _lock_map(field, id_list){
	var fixed_list = [];
	each(id_list, function(item){
	    fixed_list.push(bbop.ensure(item, '"'));
	});
	
	var base_id_list = '(' + fixed_list.join(' OR ') + ')';

	var ret_query = field + ':' + base_id_list;
	return ret_query;
	
    }

    /*
     * Function: set_ids
     *
     * Like <set_id>, a limited setter. It removes whatever else is on
     * query and replaces it with something like:
     * 
     * : gm.set_ids(['GO:1', 'GO:2'])
     * 
     * This is for when you want to lock into a set of documents by id. All
     * other query operations behave as they should around it.
     * 
     * Parameters: 
     *  id_list - a list of ids to search for
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_ids>
     */
    this.set_ids = function(id_list){
	anchor.query = _lock_map('id', id_list);
	return anchor.query;
    };

    /*
     * Function: set_targets
     *
     * Like a more generalized version of <set_ids>, a limited. It
     * removes whatever else is on query and replaces it with
     * something like:
     * 
     * : gm.set_targets(['GO:1', 'GO:2'], ['field_1', 'field_2'])
     * 
     * This is for when you want to lock into a set of documents by
     * locking onto identifiers in some set of search fields. All
     * other query operations behave as they should around it.
     * 
     * Parameters: 
     *  id_list - a list of ids to search for
     *  field_list - a list of fields ids to search across
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_ids>
     */
    this.set_targets = function(id_list, field_list){

	var fixed_list = [];
	each(field_list, function(field){
	    fixed_list.push(_lock_map(field, id_list));
	});

	var sum = fixed_list.join(' OR ');

	anchor.query = sum;
	return anchor.query;
    };

    /*
     * Function: get_query
     *
     * Getter for the query variable ('q').
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of extra
     */
    this.get_query = function(){
	return anchor.query;
    };

    /*
     * Function: get_default_query
     *
     * Getter for what the query variable 'q' will be set to on a
     * <reset_query>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of the default query
     */
    this.get_default_query = function(){
	return anchor.default_query;
    };

    /*
     * Function: get_fundamental_query
     *
     * Getter for what the query variable 'q' will be set to on a
     * <reset_default_query>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of the fundamental default query
     */
    this.get_fundamental_query = function(){
	return anchor.fundamental_query;
    };

    /*
     * Function: get_query
     *
     * Getter for the query variable ('q').
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of extra
     */
    this.get_query = function(){
	return anchor.query;
    };

    /*
     * Function: reset_query
     *
     * Remove/reset the query variable ('q'); this set it back to the
     * default query.
     *
     * Parameters:
     *  none
     *
     * Returns:
     *  the current value of query
     * 
     * Also see:
     *  <set_default_query>
     *  <reset_default_query>
     */
    this.reset_query = function(){
	anchor.query = anchor.default_query;
	ll('reset query to default: ' + anchor.query);
	return anchor.query;
    };

    /*
     * Function: set_extra
     *
     * Setter for the internal string variable to be appended to the
     * end of a query. For special use cases only (e.g. extend
     * functionality of the API safely).
     * 
     * Parameters: 
     *  new_extra - *[optional]* new value for the extras string
     *
     * Returns:
     *  the current setting of extra
     */
    this.set_extra = function(new_extra){
	anchor.query_extra = new_extra;
	return anchor.query_extra;
    };

    /*
     * Function: get_extra
     *
     * Getter for the internal string variable to be appended
     * to the end of a query.
     *
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of extra
     */
    this.get_extra = anchor.set_extra;

    /*
     * Function: remove_extra
     *
     * Remove/reset the extra bit.
     *
     * Parameters:
     *  none
     *
     * Returns:
     *  ""
     */
    this.remove_extra = function(){
	anchor.query_extra = "";
	return anchor.query_extra;
    };

    /*
     * Function: set
     *
     * Set an internal variable for the query. The internal variables
     * are typically things like 'qt', 'indent', etc.--things that you
     * might set and forget a while. It does /not/ include highly
     * dynamic variables (like callback and packet) or querying
     * variables like 'q' and 'fq'; for those you need to use the API.
     *
     * Parameters: 
     *  key - the name of the parameter to change
     *  new_val - what you want the new value to be
     *
     * Returns:
     *  n/a
     */
    this.set = function(key, new_val){
	anchor.query_variants[key] = new_val;
    };

    /*
     * Function: get
     *
     * Get an internal variable for the query.
     *
     * See <set> for the kinds of parameters that can be read.
     * 
     * Parameters: 
     *  key - the name of the parameter to get
     *
     * Returns:
     *  The found value of the key.
     */
    this.get = function(key){
	return anchor.query_variants[key];
    };

    /*
     * Function: unset
     *
     * Unset (remove) an internal variable for the query. Only usable on certain types of 
     * 
     * Only use is you really know what you're doing.
     *
     * Parameters: 
     *  key - the name of the parameter to unset/remove
     *
     * Returns:
     *  boolean; true false on whether the key was found
     */
    this.unset = function(key){
	var retval = false;

	if( typeof(anchor.query_variants[key]) !== 'undefined' ){
	    retval = true;
	    delete anchor.query_variants[key];
	}

	return retval;
    };

    /*
     * Function: include_highlighting
     *
     * Turn hilighting on or off (with true or false).
     * 
     * This essentially adds the parameters to the query string to
     * make sure that basic highlighting on the search is returned.
     * 
     * It starts off as false. The optional html_elt_str argument
     * defaults to:
     *  : <em class="hilite">
     *
     * Parameters: 
     *  hilite_p - *[optional]* boolean
     *  html_elt_str - *[serially optional]* the HTML element string to use
     *
     * Returns:
     *  either false or the current string being used for the return element
     */
    this.include_highlighting = function(hilite_p, html_elt_str){
	var retval = false;

	if( typeof(hilite_p) !== 'undefined' &&
	    (hilite_p === true || hilite_p === false) ){
	    if( hilite_p === true ){

		// Set the default string if necessary.
		if( ! html_elt_str ){ html_elt_str = '<em class="hilite">'; }

		// Set the parameters.
		anchor.set('hl', 'true');
		anchor.set('hl.simple.pre', html_elt_str);

        anchor.set('hl.snippets', anchor.default_hl_snippets);

		// And the retval is not longer false.
		retval = html_elt_str;

	    }else{
		
		// Unset the parameters.
		anchor.unset('hl');
		anchor.unset('hl.simple.pre');
		anchor.unset('hl.snippets');
	    }

	}else{
	    // Otherwise, just discover the current state and return
	    // it.
	    var cl_tmp = anchor.get('hl.simple.pre');
	    if( typeof(cl_tmp) !== 'undefined' ){
		retval = cl_tmp;
	    }
	}

	return retval;
    };

    /*
     * Function: set_personality
     *
     * While we are always contacting the same Solr instance, we
     * sometimes want to have different weights, facets, etc. This
     * function allows us to use the pre-set ones defined in the
     * constructor configuration argument.
     * 
     * Currently, this only sets the 'facet.field' internal variable.
     *
     * Parameters: 
     *  personality_id - string
     *
     * Returns:
     *  Will return false if personality doesn't exist
     */
    this.set_personality = function(personality_id){
	var retval = false;

	// This sets the facet.field internal variable.
	var cclass = anchor._golr_conf.get_class(personality_id);
	if( cclass ){

	    // Remember what our personality is.
	    // WARNING: this line must go before the query_field_set
	    // line below, or else we won't get the "smart" search.
	    this._current_class = cclass;

	    // Set the facets for our class.
	    anchor.facets(cclass.field_order_by_weight('filter'));

	    // Set the query field weights ('qf') necessary to make
	    // queries run properly.
	    anchor.query_field_set(cclass.get_weights('boost'));
	    
	    // Show that we did indeed set a personality.
	    retval = true;
	}

	return retval;
    };

    /*
     * Function: get_personality
     *
     * Returns the current personality, null if none.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  Returns the current personality as a string, null if none is set
     */
    this.get_personality = function(){
	var retval = null;

	if( typeof(anchor._current_class) !== 'undefined' &&
	    bbop.what_is(anchor._current_class) === 'golr-conf.conf_class'){
	    retval = anchor._current_class.id();
	}

	return retval;
    };

    /*
     * Function: get_query_url
     *
     * Get the current invariant state of the manager returned as a
     * encoded URL string (using encodeURI()).
     * 
     * This means the URL for the current query to the GOlr store, but
     * without extra information about packets, callbacks, and the
     * like.
     * 
     * This is generally appropriate for getting data, but maybe not
     * for things like high-speed autocomplete where races can
     * occur. For those, you might want to consider <update> or
     * <search>.
     *
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  URL string
     * 
     * Also see:
     *  <update>, <search>
     */
    this.get_query_url = function(){

	// Structure of the necessary invariant parts.	
	var qurl = anchor._solr_url + 'select?';

	// Filters to assemble.
	var assemf = anchor.get_query_filters();
	var fq = anchor.filter_list_to_assemble_hash(assemf);

	// Add all of our different specialized hashes.
	var things_to_add = [
	    //bbop.get_assemble(anchor.query_invariants),
	    //bbop.get_assemble(anchor.query_facets),
	    bbop.get_assemble(anchor.query_variants),
	    bbop.get_assemble(anchor.current_facet_field_limits),
	    //bbop.get_assemble({'fq': anchor.query_sticky_filters}),
	    bbop.get_assemble({'fq': fq}),
	    bbop.get_assemble({'facet.field':
			       us.keys(anchor.facet_fields)}),
	    bbop.get_assemble({'q': anchor.query}),
	    anchor.query_extra
	];
	// Add query_fields ('qf') iff query ('q') is set and it is
	// not length 0.
	if( anchor.query &&
	    anchor.query.length &&
	    anchor.query.length !== 0 &&
	    anchor.query !== anchor.fundamental_query ){
		var in_qf =
		    bbop.get_assemble({'qf': anchor.query_field_set()});
		things_to_add.push(in_qf);
	    }
	
	// Assemble the assemblies into a single URL, throw out
	// everything that seems like it isn't real to keep the URL as
	// clean a possible.
	var filtered_things =
		bbop.pare(things_to_add, function(item, index){
		    var retval = true;
		    if( item && item !== '' ){
			retval = false;
		    }
		    return retval;
		});

	var final_qurl = qurl + filtered_things.join('&');
	final_qurl = anchor._query_encode(final_qurl);
	ll('qurl: ' + final_qurl);
    	return final_qurl;
    };

    /*
     * Function: push_excursion
     *
     * Save the current state of the manager--data and sticky filter
     * information--onto an internal stack. Batch information is not
     * stored.
     * 
     * Useful for gettinginto a state, doing something else, then
     * returning to the original state.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  the number of items on the excursion stack
     * 
     * Also see:
     *  <get_query_url>
     *  <pop_excursion>
     */
    this.push_excursion = function(){
	
	var now = {
	    // Save current state (data).
	    data_url: anchor.get_query_url(),
	    // Save current state (session).
	    session: {
		// Get the sticky filters.
		sticky_filters: anchor.get_sticky_query_filters()
	    }
	};

	// Save.
	anchor._excursions.push(now);

	// ...
    	return anchor._excursions.length;
    };

    /*
     * Function: pop_excursion
     *
     * Return to a previously pushed state. Batch items are not
     * recovered.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  boolean on whether a state was recovered
     * 
     * Also see:
     *  <get_query_url>
     *  <gpush_excursion>
     */
    this.pop_excursion = function(){
	
	var retval = false;

	var then = anchor._excursions.pop();
	if( then ){
	    retval = true;

	    // Recover data state.
	    var then_data_url = then['data_url'];
	    anchor.load_url(then_data_url);

	    // Recover the session state.
	    var then_session_stickies = then['session']['sticky_filters'];
	    // Add the sticky filters.
	    each(then_session_stickies, function(sticky){
		var flt = sticky['filter'];
		var fvl = sticky['value'];
		var fpl = [];
		if( sticky['negative_p'] === true ){
		    fpl.push('-');
		}
		if( sticky['sticky_p'] === true ){
		    fpl.push('*');
		}
		anchor.add_query_filter(flt, fvl, fpl);
	    });	    
	}
	
    	return retval;
    };

    /*
     * Function: get_download_url
     *
     * Get the current invariant state of the manager returned as a
     * URL string.
     * 
     * This differs from <get_query_url> in that the generated string
     * is intended for text-processing uses rather than computerized
     * searching uses. The idea where is to create a TSV file for
     * downloading and consumption.
     * 
     * Instead of downloading all of the results, a limited listed set
     * can be downloaded using entity_list, which identifies documents by id.
     * 
     * The optional argument hash looks like:
     *  rows - the number of rows to return; defaults to: 1000
     *  encapsulator - how to enclose whitespace fields; defaults to: ""
     *  separator - separator between fields; defaults to: "%09" (tab)
     *  header - whether or not to show headers; defaults to: "false"
     *  mv_separator - separator for multi-valued fields; defaults to: "|"
     *  entity_list - list of specific download items in results; default null
     * 
     * With the entity list, keep in mind that null and an empty list
     * are handled in pretty much the same way--they are an indication
     * that we are going after nothing specific, and so all results
     * are game.
     * 
     * Parameters:
     *  field_list - a list of fields to return
     *  in_arg_hash - *[optional]* additional optional arguments
     * 
     * Returns:
     *  URL string
     * 
     * Also see:
     *  <get_query_url>
     */
    this.get_download_url = function(field_list, in_arg_hash){
	
	// Save current state.
	anchor.push_excursion();

	// Deal with getting arguments in properly.
	var default_hash =
	    {
		rows : 1000,
		encapsulator : '',
		separator : "%09",
		header : 'false',
		mv_separator : "|",
		entity_list : [],
		golr_download_url : null
	    };
	var arg_hash = bbop.fold(default_hash, in_arg_hash);

	// Make the changes we want.
	anchor.set('wt', 'csv');
	anchor.set('start', 0);
	anchor.set('fl', field_list.join(','));
	anchor.set('rows', arg_hash['rows']);
	anchor.set('csv.encapsulator', arg_hash['encapsulator']);
	anchor.set('csv.separator', arg_hash['separator']);
	anchor.set('csv.header', arg_hash['header']);
	anchor.set('csv.mv.separator', arg_hash['mv_separator']);

	// A little more tricky, jimmy the entity list into the query
	// if it's viable.
	var entity_list = arg_hash['entity_list'];
	if( typeof(entity_list) !== 'undefined' &&
	    us.isArray(entity_list) &&
	    entity_list.length > 0 ){
		anchor.set_ids(entity_list);
	}

	// Get url.
	var returl = anchor.get_query_url();

	// If defined, switch download URL to something else.
	if( arg_hash.golr_download_url ){
	    returl = returl.replace(anchor._solr_url,
				    arg_hash.golr_download_url);
	}

	// Reset the old state.
	anchor.pop_excursion();

    	return returl;
    };

    /*
     * Function: get_filter_query_string
     *
     * Get the current state of the manager, as defined by the current
     * gross filter set--query, sticky filters, and standard filters--
     * returned as a URL query string (sans the '?').
     * 
     * This differs from <get_query_url> and <get_state_url> in that
     * the generated string is intended for applications that may want
     * just enough information to recover filter state when the
     * personality, and other types of information, are already
     * known. It is intended to be part of a light RESTy bookmarking
     * mechanism in larger application.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  query string for current filters (sans the '?')
     * 
     * Also see:
     *  <get_query_url>
     *  <get_state_url>
     */
    this.get_filter_query_string = function(){
	
	// // Save current state.
	// anchor.push_excursion();

	var q = anchor.get_query();

	// Get the filters and sort them into sticky and "normal"
	// sets.
	var filters = anchor.get_query_filters();
	var std_filters = [];
	var sticky_filters = [];
	each(filters, function(filter){
	    if( filter['sticky_p'] ){
		sticky_filters.push(filter);
	    }else{
		std_filters.push(filter);
	    }
	});
	
	var fq = anchor.filter_list_to_assemble_hash(std_filters);
	var sfq = anchor.filter_list_to_assemble_hash(sticky_filters);

	var things_to_add = [];
	if( q ){
	    things_to_add.push(bbop.get_assemble({'q': q}));
	}
	if( ! us.isEmpty(fq) ){
	    things_to_add.push(bbop.get_assemble({'fq': fq}));
	}
	if( ! us.isEmpty(sfq) ){
	    things_to_add.push(bbop.get_assemble({'sfq': sfq}));
	}
	    
	// // Reset the old state.
	// anchor.pop_excursion();

	var final_qstr = things_to_add.join('&');
	final_qstr = anchor._query_encode(final_qstr);
    	return final_qstr;
    };

    /*
     * Function: get_state_url
     *
     * Get the current invariant state of the manager, plus the
     * current personality as a parameter, returned as a URL string.
     * 
     * This differs from <get_query_url> in that the generated string
     * is intended for applications that may want a little more
     * information and hinting over just what the current search
     * is. This method essentially parameterizes some of the "hidden
     * state" of the manager.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  URL string
     * 
     * Also see:
     *  <get_query_url>
     */
    this.get_state_url = function(){
	
	// Save current state.
	anchor.push_excursion();

	// Make the changes we want. First, physically set the
	// "personality", then set pins for jump-in recovery.
	anchor.set('personality', anchor.get_personality());

	// Explicitly set sticky pins for later recovery.
	// Do this pretty much exactly like we do for get_query_url().
	var sticky_filters = anchor.get_sticky_query_filters();
	var sfq = anchor.filter_list_to_assemble_hash(sticky_filters);
	anchor.set('sfq', sfq);
	
	// Get url.
	var returl = anchor.get_query_url();

	// Reset the old state.
	anchor.pop_excursion();

    	return returl;
    };

    /*
     * Function: load_url
     *
     * Makes a a best attempt to recover the state of a manager from
     * the clues left in a data url. This can also (and probably
     * should) be thought of as a "load bookmark"
     * function. Theoretically, you should even be able to use
     * "bookmarks" from alien installations.
     * 
     * Note that while this recovers enough to get the same data,
     * certain "session"/"preference" type things that are not encoded
     * in the url (e.g. filter stickiness, the contents of batch
     * queues, non-default base queries, etc.) will not be replayed
     * and must be recovered or guessed on an app by app basis..
     * 
     * Warning: this currently only replays a small subset of possible
     * parameters. Currently: personality, q, fq, ???. In the future,
     * this should no all non-session information.
     * 
     * Warning: Because there is more to bookmarks than just the major
     * stuff, variants not supplied in the bookmark will be removed.
     * 
     * This returns true if the parameter portions of the new and
     * bookmark urls match. However, this is often not the case--think
     * shifting personalities, etc.
     * 
     * Parameters:
     *  url - A URL string generated by a manager's get_query_url (or similar)
     * 
     * Returns:
     *  boolean
     */
    this.load_url = function(url){

	// // Some Regexps that would be nice to just compile once.
	// var regexp_url_space = /\%20/g; // '%20' === ' '
	// var regexp_url_quote = /\%22/g; // '%22' === '"'
	// var regexp_url_left_paren = /\%28/g; // '%28' === '('
	// var regexp_url_right_paren = /\%29/g; // '%29' === ')'

	// We are assuming that we are consuming our own URLs from
	// get_query_url(), so we start by attempting to decode it
	// (TODO: need a tab watch here)?
	var decoded_url = decodeURI(url);

	// Break down url.
	var in_params = bbop.url_parameters(decoded_url);

	// First, look for the personality setting and invoke it if
	// it's there--it will dominate unless we take care of it first.
	// Also note the all the keys that we see (for later erasure
	// of excess).
	var seen_params = {};
	each(in_params, function(ip){
	    var key = ip[0];
	    var val = ip[1];
	    if( key === 'personality' && val && val !== '' ){
		anchor.set_personality(val);
	    }
	    seen_params[key] = true;
	});
	
	// Now cycle through the the parameters again and invoke the
	// appropriate functions to bring them in line.
	var sticky_cache = {};
	each(in_params, function(ip){
	    var key = ip[0];
	    var val = ip[1];
	    if( typeof(val) !== 'undefined' && val !== '' ){
		if( key === 'personality' ){
		    // Already did it, skip.
		}else if( key === 'q' ){
		    anchor.set_query(val);
		}else if( key === 'fq' || key === 'sfq' ){
		    // Split the fq (or sfq) parameter.
		    var fnv = bbop.first_split(':', val);
		    var fname = fnv[0];
		    var fval = fnv[1];
		    //ll('HERE: fname: ' + fname);
		    //ll('HERE: fval: ' + fval);
		    if( fname && fval ){
			
			var plist = [];
			
			// Remove leading sign on a filter and
			// add it to the plist.
			var lead_char = fname.charAt(0);
			if( lead_char === '-' || lead_char === '+' ){
			    plist.push(lead_char);
			    fname = fname.substr(1, fname.length -1);
			}
			
			// // TODO: 
			// // If the fval looks like it has not been
			// // decoded (like from a URL-safe
			// // bookmark), go ahead and do so.
			// fval = fval.replace(regexp_url_space, ' ');
			// fval = fval.replace(regexp_url_quote, '"');
			// fval = fval.replace(regexp_url_left_paren, '(');
			// fval = fval.replace(regexp_url_right_paren,')');
			
			// Do not allow quotes in--they will be
			// added by the assembler.
			fval = bbop.dequote(fval);
			
			// Make it sticky it it came in on "sfq".
			// Note if this is the sticky form.
			var skey = fname + '^' + fval;
			if( key === 'sfq' ){
			    sticky_cache[skey] = true;
			    plist.push('*');
			}
			
			// Add the query filter properly, but
			// only if we have not already added the
			// sticky form (prevent clobbering).
			if( ! bbop.is_defined(sticky_cache[skey]) ||
			    key === 'sfq'){
			    anchor.add_query_filter(fname, fval, plist);
			    
			}
		    }
		}else if( key === 'qf' ){
		    // qf is handles a little strangely...
		    var foo = bbop.first_split('^', val);
		    //ll('qf: key: '+ key +', val: '+ val +', foo: '+ foo);
		    anchor.add_query_field(foo[0], foo[1]);
		}else if( key === 'facet.field' ){
		    anchor.facets(val);
		}else if( key === 'start' || key === 'rows' ){
		    // Numbers need to be handled carefully.
		    if( bbop.what_is(val) === 'string' ){
			val = parseFloat(val);
		    }
		    anchor.set(key, val);
		}else{
		    // This one catches all of the non-special
		    // parameters and resets them using .set().
		    anchor.set(key, val);
		    // if( key === 'fq' ){
		    //     throw new Error("OI");			     
		    // }
		}
	    }
	});
	
	// Now go through and remove all of the query variant
	// parameters that were not seen in the bookmark.
	each(anchor.query_variants, function(val, key){
	    if( typeof(seen_params[key]) === 'undefined'){
		anchor.unset(key);
	    }
	});
	
	// Produce our own url from what we've done. If the parameters
	// match with the incoming argument's return true.
	var curr_url = anchor.get_query_url();
	var curr_params = bbop.url_parameters(curr_url);
	var differences = 0;
	if( in_params.length === curr_params.length ){
	    each(in_params, function(in_p, i){
		var curr_p = curr_params[i];
		if( in_p.length === curr_p.length ){
		    if( in_p.length === 1 ){
			if( in_p[0] === curr_p[0] ){
			    // match!
			}else{
			    differences++;
			}
		    }else if( in_p.length === 2 ){
			if( in_p[0] === curr_p[0] && in_p[1] === curr_p[1] ){
			    // match!
			}else{
			    differences++;
			}
		    }
		}else{
		    differences++;
		}
	    });
	}else{
	    differences++;
	}
	
	// Tally the differences and decides if they're the same.
	var retval = false;
	if( differences === 0 ){
	    retval = true;
	}
    	return retval;
    };

    /*
     * Function: add_to_batch
     *
     * "Save" the current manager state to run later in serial batch
     * mode.
     * 
     * The actual job of running these batches is left to the
     * implementation of the sub-managers; probably in "run_batch".
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  state url
     */
    this.add_to_batch = function(){
	var qurl = anchor.get_query_url();
	anchor._batch_urls.push(qurl);
    	return qurl;
    };

    /*
     * Function: batch_urls
     *
     * Return a pointer to the current batch urls.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  array
     */
    this.batch_urls = function(){
    	return anchor._batch_urls;
    };

    /*
     * Function: next_batch_url
     *
     * Return the next data to be processed, removing it from the
     * batch queue in the process.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  state url or null
     */
    this.next_batch_url = function(){
    	return anchor._batch_urls.shift() || null;
    };

    /*
     * Function: reset_batch
     *
     * Clear the currently queued data batch.
     * 
     * The actual job of running these batches is left to the
     * implementation of the sub-managers; probably in "run_batch".
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  the number of items cleared
     */
    this.reset_batch = function(){
	var num = anchor._batch_urls.length;
	anchor._batch_urls = [];
    	return num;
    };
}
function (golr_loc, golr_conf_obj, engine, mode){
    registry.call(this, ['prerun',
			 'reset',
			 'search',
			 'error',
			 'postrun']);
    this._is_a = 'bbop-manager-golr';
    var anchor = this; // get a good self-reference point

    // Per-manager logger.
    this._logger = new bbop.logger(this._is_a);
    //this._logger.DEBUG = true;
    this._logger.DEBUG = false;
    function ll(str){ anchor._logger.kvetch(str); }

    // Setup right handler subsystem--either returning a direct
    // response or a promise, depending.
    anchor._engine = engine || null;
    anchor._mode = mode || null;
    anchor._runner = function(resource, payload){
	var ret = null;
	// if( typeof(anchor._engine) === 'undefined' ){
	//     throw new Error('"engine" not set in new bbop-manager-golr');
	// }
	if( anchor._mode === 'sync' ){
	    ret = anchor._engine.fetch(resource, payload);
	}else if( anchor._mode === 'async' ){
	    ret = anchor._engine.start(resource, payload);
	}else{
	    // throw new Error('"mode" not set in new bbop-manager-golr');
	    return resource;
	}
	return ret;
    };

    // The callback function called after a successful AJAX
    // intialization/reset call. First it runs some template code,
    // then it does all of the callbacks.
    anchor._run_reset_callbacks = function(response){
	ll('run reset callbacks...');
	//var response = new golr_response(json_data);
	anchor.apply_callbacks('reset', [response, anchor]);
	anchor.apply_callbacks('postrun', [response, anchor]);
    };

    // The main callback function called after a successful AJAX call in
    // the update function.
    anchor._run_search_callbacks = function(response){
	ll('run search callbacks...');
	//var response = new golr_response(json_data);
	anchor.apply_callbacks('search', [response, anchor]);
	anchor.apply_callbacks('postrun', [response, anchor]);
    };

    // This set is called when we run into a problem.
    anchor._run_error_callbacks = function(response){
	ll('run error callbacks...');
	//var response = new golr_response(json_data);
	anchor.apply_callbacks('error', [response, anchor]);
	anchor.apply_callbacks('postrun', [response, anchor]);
    };

    // The registries can only be applied if the engine is present.
    if( engine ){

	// Re-contextualize run_promise_functions().
	anchor.run_promise_functions = function(promise_function_stack, 
						accumulator_function,
						final_function,
						error_function){
	    // NOTE: Cannot sensibly use Function.call here as engine
	    // is not really the superclass of this manager.
	    return engine.run_promise_functions(promise_function_stack,
						accumulator_function,
						final_function,
						error_function);
	};

	// Setup the appropriate callbacks in the engine for success.
	anchor._engine.register('success', function(response){
	    
    	    //console.log(anchor._engine);
    	    //console.log(response._raw);
	    
            // 
            if( ! response.success() ){
		throw new Error("Unsuccessful response from golr server!");
            }else{
		var cb_type = response.callback_type();
		//console.log('okay response from server, will probe type...: '+cb_type);
		if( cb_type === 'reset' ){
                    anchor._run_reset_callbacks(response);
		}else if( cb_type === 'search' ){
                    anchor._run_search_callbacks(response);
		}else{
                    throw new Error("Unknown callback type (runner): "+cb_type);
		}
            }
	});
	
	// And, more easily, setup the appropriate callbacks in the
	// engine for failure.
	anchor._engine.register('error', function(response){
    	    anchor._run_error_callbacks(response);
	});
	
    }

    // Just want to compile these once.
    var alphanum = new RegExp(/^[a-zA-Z0-9 ]+$/);

    this.minimal_query_length = 3;

    // To help keep requests from the past haunting us. Actually doing
    // something with this number is up to the UI.
    this.last_sent_packet = 0;
    //this.last_received_packet = 0;

    // Lightly check incoming arguments.
    // There should be a string url argument.
    // There could be a hash of pinned filters argument.
    if( ! golr_loc || ! golr_conf_obj ){
	ll('ERROR: no proper arguments');
    }
    if( typeof golr_loc !== 'string' ){
	ll('ERROR: no proper golr url string argument');
    }
    if(	! golr_conf_obj._is_a || golr_conf_obj._is_a !== 'golr-conf.conf' ){
	    ll('ERROR: no proper golr-conf.conf object argument');
	    throw new Error('boink! ' + bbop.what_is(golr_conf_obj) );
	}
    
    // Whether or not to prevent ajax events from going.
    // This may not be usable, or applicable, to all backends.
    this._safety = false;

    // Our default target url.
    this._solr_url = golr_loc;

    // Settle in the configurations.
    // this._golr_conf = new bbop_golr.conf(golr_conf_var);
    this._golr_conf = golr_conf_obj;

    // The current data batches that we are storing.
    this._batch_urls = [];
    this._batch_accumulator_func = function(){};
    this._batch_final_func = function(){};

    // The current state stack.
    this._excursions = [];

    // The current class/personality that we're using. It may be none.
    this._current_class = null;

    // Our (default) query and the real deal.
    this.fundamental_query = '*:*'; // cannot be changed
    this.default_query = '*:*'; // changable
    this.query = this.default_query; // current

    // Our (default) fl and whatever we have now.
    //this.default_fl = '*%2Cscore';
    this.default_fl = '*,score';
    this.current_fl = this.default_fl;

    // We remember defaults in the case of rows and start since they
    // are the core to any paging mechanisms and may change often.
    //this.default_rows = 25;
    //this.default_rows = 100;
    this.default_rows = 10;
    this.default_start = 0;
    this.current_rows = this.default_rows;
    this.current_start = this.default_start;

    // There is a reason for this...TODO: later (25+)
    this.default_facet_limit = 25;
    this.current_facet_limit = 25;
    // {facet_field_name: value, ...}
    this.current_facet_field_limits = {};
    // TODO: paging for facets;
    this.current_facet_offset = 25;
    this.current_facet_field_offsets = {};

    // Number of snippets to return when hl is enable
    this.default_hl_snippets = 1000;

    // Our default query args, with facet fields plugged in.
    this.query_variants =
	{
	    // Our default standard search type. This means we don't
	    // have to explicitly add fields to the search (although
	    // the query fields ('qf') are still necessary to make
	    // anything real happen).
	    defType: 'edismax',
	    
	    // Things unlikely to be touched.
	    // There are unlikely to be messed with too much.
	    qt: 'standard',
	    indent: 'on',
	    wt: 'json',
	    //version: '2.2',
	    rows: anchor.current_rows,
	    start: anchor.current_start, // Solr is offset indexing
	    //fl: '*%2Cscore',
	    fl: anchor.default_fl,
    
	    // Deprecated: see query_filters
	    //fq: {},
	    
	    // Deprecated: see query_fields
	    //qf: {},
	    
	    // Deprecated: see query
	    //q: '*:*'

	    // Control of facets.
	    facet: 'true',
	    'facet.mincount': 1,
	    'facet.sort': 'count',
	    'json.nl': 'arrarr', // only in facets right now
	    'facet.limit': anchor.default_facet_limit
	    // TODO?: 'f.???.facet.limit': 50,
	    // TODO: 'json.nl': [flat|map|arrarr]

	    // Deprecated: see facet_fields
	    //'facet.field': []
	};

    // This is the 'qf' parameter. Although we keep it, it only needs
    // to be exposed when the query ('q') field is set.
    //this.query_fields = [];
    this.query_fields = {};

    // A richer way to handle the 'fq' query variant.
    // It should look like:
    // {<filter>: {<value>:{'sticky_p':(t|f), 'negative_p':(t|f)}, ...}}
    this.query_filters = {};

    // The engine for the facet.field list.
    this.facet_fields = {};

    /*
     * Function: debug
     * 
     * Turn on or off the verbose messages. Uses <bbop.logger>, so
     * they should come out everywhere.
     * 
     * Parameters: 
     *  p - *[optional]* true or false for debugging
     *
     * Returns: 
     *  boolean; the current state of debugging
     */
    this.debug = function(p){
	if( p === true || p === false ){
	    this._logger.DEBUG = p;
	    // TODO: add debug parameter a la include_highlighting
	}
	return this._logger.DEBUG;
    };

    /*
     * Function: minimal_query
     * 
     * The length of the minimal query to consider. Defaults to 3.
     * 
     * Parameters: 
     *  n - *[optional]* number, or nothing if just want current
     *
     * Returns: 
     *  number
     */
    this.minimal_query = function(n){
	if( us.isNumber(n) ){
	    anchor.minimal_query_length = n;
	}
	return this.minimal_query_length;
    };

    /*
     * Function: lite
     * 
     * Limit the returns fields (the parameter "fl") to the ones
     * defined in the set of fields defined in results, label fields
     * if available (i.e. "_label", "_map" when "_label" is
     * multi=valued), and "score" and "id".
     * 
     * The default is "false".
     * 
     * Parameters: 
     *  use_lite_p - *[optional]* true or false, none just returns current
     *
     * Returns: 
     *  boolean; the current state of lite-ness
     */
    this.lite = function(use_lite_p){

	// Adjust the current state accordingly.
	if( use_lite_p === true || use_lite_p === false ){
	    if( use_lite_p === true ){
		
		// The actual collections and adjustment.
		// First, this only works if we have a personality, so
		// check to see if we have one.
		var per = anchor.get_personality();
		if( per ){
		    // Since we have a personality, collect all of the
		    // mentioned fields.
		    var field_collection = {};
		    var union = bbop.merge;
		    var ccl = anchor._current_class;

		    // Fill field_collection with the fields
		    // in the given category.
		    //loop(['boost', 'result', 'filter'],
		    //loop(['result', 'filter'],
		    each(['result'], function(cat){
			field_collection = 
			    union(field_collection, ccl.get_weights(cat));
		    });
		    
		    // Next, flatten into a list.
		    var flist = us.keys(field_collection);

		    // Now for all the fields in these categories, see
		    // if we can find additional "special" labels to
		    // go with them.
		    each(flist, function(flist_item){
			//loop(['_label', '_label_searchable'],
			each(['_label'], function(field_suffix){
			    var new_field = flist_item + field_suffix;
			    var nf_obj = ccl.get_field(new_field);
			    if( nf_obj ){
				flist.push(new_field);
				
				// There appears to be the
				// thing label. If they are
				// both multi-valued, then
				// there will be a map as
				// well.
				if( nf_obj.is_multi() ){
				    flist.push(flist_item + '_map');
				}
			    }
			});
		    });
		    
		    // Finally, set these fields (plus score) as the
		    // new return fields.
		    flist.push('score');
		    flist.push('id');
		    //anchor.current_fl = flist.join('%2C');
		    anchor.current_fl = flist.join(',');
		    anchor.set('fl', anchor.current_fl);
		}
		
	    }else{ // else false
		// Reset.
		anchor.current_fl = anchor.default_fl;
		anchor.set('fl', anchor.current_fl);
	    }
	}

	// Return the current state.
	var retval = false;
	if( anchor.default_fl !== anchor.current_fl ){
	    retval = true;
	}
	return retval;
    };

    // An internal helper function to munge the name of a field into
    // the name of its corresponding facet field.
    function _field_to_facet_field(field){
	return 'f.' + field + '.facet.limit';
    }
    
    /*
     * Function: get_facet_limit
     * 
     * Get the limit for a specified facet or the global limit.
     * 
     * Parameters: 
     *  field - *[optional]* limit for a specific field; otherwise global value
     *
     * Returns: 
     *  integer or null
     */
    this.get_facet_limit = function(field){
	var retval = null;

	if( ! field ){
	    retval = anchor.current_facet_limit;
	}else{
	    var f = _field_to_facet_field(field);
	    var try_val = anchor.current_facet_field_limits[f];
	    if( typeof(try_val) !== 'undefined' ){
		retval = try_val;
	    }
	}

	return retval;
    };

    /*
     * Function: set_facet_limit
     * 
     * Change the number of facet values returned per call.
     * The default is likely 25.
     * 
     * Just as in Solr, a -1 argument is how to indicate unlimted
     * facet returns.
     * 
     * This setting does not survive things like <resets_facet_limit>.
     * 
     * Parameters: 
     *  arg1 - (integer) set the global limit
     *
     * Parameters: 
     *  arg1 - (string) the name of the field to check
     *  arg2 - (integer) set the limit for this field
     *
     * Returns: 
     *  boolean on whether something was set
     */
    this.set_facet_limit = function(arg1, arg2){
	var retval = false;

	// Decide which form of the function we're using.
	if( typeof(arg2) === 'undefined' &&
	    bbop.what_is(arg1) === 'number' ){ // form one
		
		// Set
		var nlimit = arg1;
		anchor.current_facet_limit = nlimit;
		anchor.set('facet.limit', anchor.current_facet_limit);
		
		retval = true;
	
	}else if( typeof(arg1) !== 'undefined' && 
		  typeof(arg2) !== 'undefined' && 
		  bbop.what_is(arg1) === 'string' &&
		  bbop.what_is(arg2) === 'number' ){
		      
		      var field = _field_to_facet_field(arg1);
		      var limit = arg2;
		      anchor.current_facet_field_limits[field] = limit;
		      
		      retval = true;
	}

	return retval;
    };

    /*
     * Function: set_default_facet_limit
     * 
     * Permanently change the default number of facet values returned
     * per call. The default's default is likely 25.
     * 
     * Just as in Solr, a -1 argument is how to indicate unlimted
     * facet returns.
     * 
     * Parameters: 
     *  lim - (integer) set the global default limit
     *
     * Returns: 
     *  old default
     */
    this.set_default_facet_limit = function(lim){

	// Capture ret.
	var retval = anchor.default_facet_limit;

	// Set
	anchor.default_facet_limit = lim;
	//anchor.set('facet.limit', anchor.default_facet_limit);
		
	return retval;
    };

    /*
     * Function: reset_facet_limit
     * 
     * Either reset the global limit to the original (likely 25)
     * and/or remove the specified filter. Sets everything back to the
     * original values or whatever was set by
     * <set_default_facet_limit>.
     * 
     * Parameters: 
     *  field - *[optional]* remove limit for a field; otherwise all and global
     *
     * Returns: 
     *  boolean on whether something was reset
     */
    this.reset_facet_limit = function(field){
	var retval = false;

	if( typeof(field) === 'undefined' ){
	    // Eliminate all fields by blowing them away.
	    anchor.current_facet_limit = anchor.default_facet_limit;
	    anchor.set('facet.limit', anchor.current_facet_limit);
	    anchor.current_facet_field_limits = {};
	    retval = true;
	}else{ // eliminate just the one field
	    var f = _field_to_facet_field(field);
	    if( typeof(anchor.current_facet_field_limits[f]) !== 'undefined' ){
		delete anchor.current_facet_field_limits[f];
		retval = true;
	    }
	}

	return retval;
    };

    /*
     * Function: get_results_count
     * 
     * Get the current number of results that will be returned.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns: 
     *  integer
     */
    this.get_results_count = function(field){
	return anchor.get('rows');
    };

    /*
     * Function: set_results_count
     * 
     * Change the number of result documents returned per call.
     * The default is likely 10.
     * 
     * Parameters: 
     *  count - (integer) set the global results count
     *
     * Returns:
     *  the count set
     */
    this.set_results_count = function(count){
	anchor.set('rows', count);
	anchor.current_rows = count;
	return anchor.current_rows;
    };

    /*
     * Function: reset_results_count
     * 
     * Reset the number of documents to their original setting, likely
     * 10.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the new count
     */
    this.reset_results_count = function(){
	anchor.set('rows', anchor.default_rows);
	anchor.current_rows = anchor.default_rows;
	return anchor.current_rows;
    };

    /*
     * Function: plist_to_property_hash
     *
     * Turn a plist to a hash containing the different properties that
     * can be defined for a query filter. Possible values are: '+'
     * (positive filter), '-' (negative filter), '*' (sticky filter),
     * '$' (transient). If mutually exclusive properties are defined
     * (e.g. both '+' and '-'), the last one will be used. Or, since
     * that is a call to silliness, let's say the behavior is
     * undefined.
     *
     * Parameters: 
     *  plist - *[optional]* a list of properties to apply to the filter
     *
     * Returns: 
     *  A hash version of the plist; otherwise, the default property hash
     */
    this.plist_to_property_hash = function(plist){

	// Let's start with the default values.
	var phash = {
	    //'positive_p': true,
	    'negative_p': false,
	    //'transient_p': true
	    'sticky_p': false
	};

	// If not defined, just return the default list.
	if( plist ){	    
	    each(plist, function(item){
		if( item === '+' ){
		    phash['negative_p'] = false;
		    //phash['positive_p'] = true;
		}else if( item === '-' ){
		    phash['negative_p'] = true;
		    //phash['positive_p'] = false;
		}else if( item === '*' ){
		    phash['sticky_p'] = true;
		    //phash['transient_p'] = false;
		}else if( item === '$' ){
		    phash['sticky_p'] = false;
		    //phash['transient_p'] = true;
		}
	    });
	}

	return phash;
    };

    /*
     * Function: add_query_filter_as_string
     *
     * Setter for query filters ('fq'). Acts as a 
     *
     * Parameters: 
     *  filter_string - filter (type) string (e.g. "-type:gene")
     *  plist - *[optional]* list of properties of the filter
     *
     * Returns: 
     *  (TODO) The current query filter hash.
     * 
     * See also:
     *  <add_query_filter>
     */
    this.add_query_filter_as_string = function(filter_string, plist){
	
	// Split the incoming filter string into its component parts.
	var f_v = bbop.first_split(':', filter_string);
	var fname = f_v[0];
	var fval = f_v[1];

	// Need to shuck the value from the quotes, as in load_url.
	fval = bbop.dequote(fval);

	var props = null;
	if( us.isArray(plist) ){
	    props = plist;
	}else{
	    props = [];
	}

	// Only continue on sensible inputs.
	var ret = {};
	if( fname !== '' && fval !== '' ){

	    // Similar to the URL loader.
	    var lead_char = fname.charAt(0);
	    if( lead_char === '-' || lead_char === '+' ){
		props.push(lead_char);
		fname = fname.substr(1, fname.length -1);
	    }
	    
	    ret = this.add_query_filter(fname, fval, props);
	}

	return ret;
    };

    /*
     * Function: add_query_filter
     *
     * Setter for query filters ('fq').
     *
     * Parameters: 
     *  filter - filter (type) string
     *  value - filter value string (or TODO: defined logic hash)
     *  plist - *[optional]* list of properties of the filter
     *
     * Returns: 
     *  (TODO) The current query filter hash.
     * 
     * See also:
     *  <plist_to_property_hash>
     */
    this.add_query_filter = function(filter, value, plist){
	
	// Make sure we've defined the group.
	if( typeof(this.query_filters[filter]) === 'undefined' ){
	    this.query_filters[filter] = {};
	}

	this.query_filters[filter][value] = this.plist_to_property_hash(plist);
	
	//ll("Current state: " + bbop.dump(this.query_filters));

	return {}; // TODO
    };

    /*
     * Function: remove_query_filter
     *
     * Remover for query filters ('fq'), is a plist is specified, it
     * will only remove if all of the listed criteria are met.
     *
     * Parameters: 
     *  filter - filter (type) string
     *  value - filter value string (TODO: or defined logic hash)
     *  plist - *[optional]* list of properties of the filter
     *
     * Returns: 
     *  boolean (on success)
     */
    this.remove_query_filter = function(filter, value, plist){

	// Default return value.
	var retval = false;

	// Internal helper to delete a low level key, and then if the
	// top-level is empty, get that one too.
	function _full_delete(hash, key1, key2){
	    if( key1 && key2 && hash &&
		hash[key1] && hash[key1][key2] ){
		    delete hash[key1][key2];
		}
	    if( us.isEmpty(hash[key1]) ){
		delete hash[key1];
	    }
	}

	// If we have a filter, a value, and it's there...
	if( filter && value &&
	    anchor.query_filters[filter] &&
	    anchor.query_filters[filter][value] ){

		// If no real plist hash been defined, just go ahead
		// and get rid of that. Otherwise, make sure that the
		// defined plist and the stored properties are the
		// same before deleting.
		if( ! plist || us.isEmpty(plist) ){
		    _full_delete(anchor.query_filters, filter, value);
		    retval = true;
		}else{
		    
		    var filter_phash = anchor.query_filters[filter][value];
		    var in_phash = anchor.plist_to_property_hash(plist);
		    
		    if( bbop.is_same(filter_phash, in_phash) ){		
			_full_delete(anchor.query_filters, filter, value);
			retval = true;
		    }
		}
	    }

	return retval;
    };

    /*
     * Function: reset_query_filters
     *
     * Reset the query filters ('fq'); but leave sticky filters alone.
     *
     * Parameters: 
     *  n/a
     * 
     * Returns: 
     *  (TODO) The current query filter hash.
     */
    this.reset_query_filters = function(){

	// Drill down and delete all non-stickies.
	each(anchor.query_filters, function(values, filter){
	    //ll('filter: ' + filter);
	    each(values, function(props, value){
		//ll('  value: ' + value);
		var sticky_p = props['sticky_p'];
		if( ! sticky_p ){
		    //ll('hit: ' + filter + ', ' + value);
		    anchor.remove_query_filter(filter, value);
		}
	    });
	});
	
	return {}; // TODO
    };

    /*
     * Function: get_query_filter_properties
     *
     * Get a hash representing a query filter ('fq').
     *
     * Parameters: 
     *  key - filter string (TODO: or defined logic hash)
     *
     * Returns: 
     *  The current query filter hash for key.
     */
    this.get_query_filter_properties = function(filter, value){

	// Default return value.
	var retobj = null;
	
	// If we have a key and it's there...
	var aqf = anchor.query_filters;
	if( filter && value && aqf[filter] && aqf[filter][value] ){
	    retobj =
		{
		    'filter' : filter,
		    'value' : value,
		    //'polarity': aqf[filter][value]['negative_p'],
		    'negative_p': aqf[filter][value]['negative_p'],
		    'sticky_p': aqf[filter][value]['sticky_p']
		};
	}

	return retobj;
    };

    /*
     * Function: get_query_filters
     *
     * Get a list of hashes representing the query filters ('fq'). The
     * return lists look like:
     *
     * : [{'filter': A, 'value': B, 'negative_p': C, 'sticky_p': D}, ...]
     *
     * Where A and B are strings and C and D are booleans.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns: 
     *  A list of the current query filter hashs.
     */
    this.get_query_filters = function(){

	var retlist = [];	
	each(anchor.query_filters, function(values, f){
	    each(values, function(props, v){
		retlist.push(anchor.get_query_filter_properties(f, v));
	    });
	});

	return retlist;
    };

    /*
     * Function: get_sticky_query_filters
     *
     * Get a list of hashes representing the current stucky query
     * filters ('fq'). See <get_query_filters> for a specification of
     * what the return type looks like.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns: 
     *  A list of the current sticky query filter hashs.
     * 
     * See also:
     *  <get_query_filters>
     */
    this.get_sticky_query_filters = function(){

	var retlist = [];	
	each(anchor.query_filters, function(values, f){
	    each(values, function(props, v){
		var qfp = anchor.get_query_filter_properties(f, v);
		if( qfp['sticky_p'] === true ){
		    retlist.push(qfp);			      
		}
	    });
	});

	return retlist;
    };

    // A little extra thing that we might need sometimes.
    this.query_extra = null;

    // Spaces can cause problems in URLs in some environments.
    //final_qurl = final_qurl.replace(/ /g, '%20');
    // Convert the URL into something more usable.
    // Because we internally use %09 as a special case, make sure
    // we don't double-up on it.
    this._query_encode = function(str_to_encode){

	var fs1 = encodeURI(str_to_encode);
	var fs2 = fs1.replace(/\%2509/g, '%09');

	var final_encoding = fs2;
	return final_encoding;
    };

    /*
     * Function: filter_list_to_assemble_hash
     *
     * Get all of our query filter variables and try and make
     * something of them that <get_assemble> can understand.
     *
     * Sticky doesn't matter here, but negativity does. However, we
     * can be pretty naive since the hashing should have already taken
     * out mutually exclusive dupes.
     * 
     * The argument is a list of query filter properties, as returned
     * by <get_query_filters> and <get_sticky_query_filters>.
     *
     * Parameters:
     *  flist - a list of query filter properties (see above)
     *
     * Returns:
     *  hash of filter names to value lists
     * 
     * See also:
     *  <get_query_filters>
     *  <get_sticky_query_filters>
     */
    this.filter_list_to_assemble_hash = function(flist){
	var h = {};
	each(flist, function(filter_property){
	    
	    // Grab only the properties that affect the
	    // URL.
	    var filter = filter_property['filter'];
	    var value = filter_property['value'];
	    var negative_p = filter_property['negative_p'];
	    
	    // We need to alter at the filter level.
	    if( negative_p ){
		filter = '-' + filter;
	    }
	    
	    // Make sure it is defined.
	    if( typeof(h[filter]) === 'undefined' ){
		h[filter] = [];
	    }
	    h[filter].push(value);
	});
	return h;
    };
    
    /*
     * Function: sensible_query_p
     * 
     * Simply ask the manager if a free text query ('q') makes sense
     * at this point.
     * 
     * This currently means that the query text ('q') is one of:
     *  1) the same as the default query
     *  2) the same as the fundamental query
     *  3) three (3) (or whatever minimal_query is set to) characters long
     *  4) the empty string ('')
     * and that query fields ('qf') are defined.
     * 
     * This is an overridable opinion of the manager.
     * 
     * Parameters:
     *  n/a
     *
     * Returns:
     *  boolean
     */
    this.sensible_query_p = function(){
	var retval = false;
	var q = anchor.get_query();
	var qf = anchor.query_field_set();

	//console.log('q', q);
	//console.log('qf', qf);

	if( qf && ! us.isEmpty(qf) ){ // first hurdle

	    if( q === anchor.get_default_query() ){
		retval = true;
	    }else if( q === anchor.get_fundamental_query() ){
		retval = true;
	    }else if( q && q.length >= anchor.minimal_query_length ){
		retval = true;
	    }else if( q === ''){
		retval = true;
	    }
	}
	return retval;
    };

    /*
     * Function: last_packet_sent
     *
     * It is up to the UI to do something interesting with this number.
     * 
     * Also remember that this number only rises through calls to
     * <update> or one of its wrappers. Calls to <get_query_url> and
     * the like will not affect this number.
     * 
     * Parameters:
     *  n/a 
     *
     * Returns:
     *  integer
     * 
     * See also:
     *  <update>
     */
    this.last_packet_sent = function(){
    	return anchor.last_sent_packet;
    };

    /*
     * Function: clear
     *
     * Clear all non-sticky query parameters to get back to a more
     * "original" state.
     * 
     * Not to be confused with <reset>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  n/a
     */
    this.clear = function(){

	// Reset 'q'.
	anchor.query = anchor.default_query;

	// Reset 'fq', all but sticky.
	anchor.reset_query_filters();
    };

    /*
     * Function: reset
     *
     * Manually trigger the "reset" chain of events.
     *
     * This is a curried wrapper for <update> and should be preferred
     * over a direct call to update.
     *
     * Note to be confused with <clear>.
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <update>
     */
    this.reset = function(){
	return anchor.update('reset');
    };

    /*
     * Function: search
     *
     * Trigger the "search" chain of events.
     * Takes a field-keyed hash of bbop.logics as an argument.
     * 
     * This is a curried wrapper for <update> and should be preferred
     * over a direct call to update.
     * 
     * Parameters:
     *  n/a
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <update>
     */
    this.search = function(){
	return anchor.update('search');
    };

    /*
     * Function: page
     *
     * Re-trigger the "search" chain of events, but with the variables
     * set for a different section of the results.
     * 
     * Note that this operates independently of any impossibilites in
     * the results--just how such paging would look and
     * triggering. Ths UI should handle impossibilities and the like.
     * 
     * This is a wrapper for <update> and should be preferred over a
     * direct call to update.
     * 
     * Parameters: 
     *  rows - the number of rows to return
     *  start - the offset of the rows to return
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <update>
     */
    this.page = function(rows, start){
	anchor.set('rows', rows);
	anchor.set('start', start);
	return anchor.update('search', rows, start);
    };

    /*
     * Function: page_first
     *
     * Currently a convenience alias for <search>. Think about it--it
     * makes sense.
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  n/a
     * 
     * See also:
     *  <page>
     */
    this.page_first = anchor.search;
    
    /*
     * Function: page_previous
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <page>
     */
    this.page_previous = function(){
	var do_rows = anchor.get_page_rows();
	var do_offset = anchor.get_page_start() - do_rows;
	return anchor.page(do_rows, do_offset);
    };
    
    /*
     * Function: page_next
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  the query url (with the jQuery callback specific parameters)
     *
     * Returns:
     *  n/a
     * 
     * See also:
     *  <page>
     */
    this.page_next = function(){
	var do_rows = anchor.get_page_rows();
	var do_offset = anchor.get_page_start() + do_rows;
	return anchor.page(do_rows, do_offset);
    };
    
    /*
     * Function: page_last
     * 
     * Trigger search on last page parameters.
     * 
     * Since the manager has no idea about what is actually being
     * returned, the real world number of total documents needs to be
     * added as an argument.
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  total_document_count - integer for the total number of docs found
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <page>
     */
    this.page_last = function(total_document_count){
	var do_rows = anchor.get_page_rows();
	var mod = total_document_count % do_rows;
	var do_offset = total_document_count - mod;
	// ll("page_last: " + total_document_count + " " +
	//    do_rows + " " + mod + " " + do_offset);
	var ret = null;
	if( mod === 0 ){
	    ret = anchor.page(do_rows, do_offset - do_rows);
	}else{
	    ret = anchor.page(do_rows, do_offset);
	}
	return ret;
    };

    /*
     * Function: get_page_rows
     *
     * Return the number of rows the manager is currently set
     * to. Useful as an argument to <page>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  integer; the number of rows the manager is currently set to
     * 
     * See also:
     *  <page>
     */
    this.get_page_rows = function(){
	return anchor.get('rows');
    };

    /*
     * Function: get_page_start
     *
     * Return the rows offset the manager is currently set to. Useful
     * as an argument to <page>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  integer; the offset the manager is currently set to
     * 
     * See also:
     *  <page>
     */
    this.get_page_start = function(){
	return anchor.get('start');
    };

    /*
     * Function: add_query_field
     * 
     * Add a new query field to the query. 
     * 
     * This does not go through and expand into searchable fields, for
     * that see: <query_field_set>.
     *
     * Parameters: 
     *  qf - the query field to add
     *  boost - *[optional]* defaults to 1.0
     *
     * Returns:
     *  true or false on whether or not it is a new field
     * 
     * See also:
     *  <query_field_set>
     */
    this.add_query_field = function(qf, boost){
	
	var retval = false;

	// Make sure that some boost is there.
	if( typeof(boost) === 'undefined' ){
	    boost = 1.0;
	}

	// Check.
	if( typeof(anchor.query_fields[qf]) === 'undefined' ){
	    retval = true;
	}

	// Add.
	anchor.query_fields[qf] = boost;

	return retval;
    };

    /*
     * Function: query_field_set
     *
     * Bulk getter/setter for the query fields--the fields that are
     * searched (and by what weight) when using a query ('q' or
     * set_query(), i.e. the 'qf' field).
     * 
     * This will always use searchable fields if possible,
     * automatically replacing the non-searchable versions (I can't
     * think of any reason to use non-searchable versions unless you
     * want your searches to not work) if a personality is set. If no
     * personality is set, it will just use the arguments as-is.
     * 
     * The argument replaces the current set.
     *
     * The qfs argument should be a hash like:
     * 
     *  {'field01': value01, ...}
     * 
     * Parameters: 
     *  qfs - *[optional]* query fields to set
     *
     * Returns:
     *  the current query_fields array (e.g. ["field01^value01", ...])
     */
    this.query_field_set = function(qfs){

	// Covenience.
	var cclass = anchor._current_class;

	// Only do something if we have a query field set.
	if( qfs ){
	    
	    // Only do the probing if a personality has been set.
	    if( cclass ){

		// Get the current searchable extension string from
		// the personality class.
		//var s_ext = cclass.searchable_extension();
		// Actually, we're going to make this non-variable.
		var s_ext = '_searchable';

		// Probe the input to see if there are any searchable
		// alternatives to try, use those instead.
		var searchable_qfs = {};
		each(qfs, function(value, filter){
		    // If the probe fails, just put in
		    // whatever is there.
		    var cfield = cclass.get_field(filter);
		    if( cfield && cfield.searchable() ){
			//ll('filter/value:');
			var new_f = filter + s_ext;
			searchable_qfs[new_f] = value;
		    }else{
			searchable_qfs[filter] = value;
		    }
	    	});
		qfs = searchable_qfs;
	    }	    
	    
	    // Overwrite the current.
	    anchor.query_fields = qfs;
	}
	
	// Using the original information, convert them to the
	// proper output format.
	var output_format = [];
	each(anchor.query_fields, function(value, filter){
	    output_format.push(filter + '^' + value);
	});
	return output_format;
    };

    /*
     * Function: facets
     *
     * Bulk getter/setter for facets (technically 'facet.field').
     *
     * Parameters: 
     *  key - *[optional]* facet to add to the facet list
     *
     * Parameters: 
     *  list - *[optional]* list to replace the current list with
     *
     * Returns:
     *  the current facets hash.
     */
    this.facets = function(list_or_key){
	if( list_or_key ){
	    if( bbop.what_is(list_or_key) !== 'array' ){
		// Arrayify it.
		list_or_key = [list_or_key];
	    }else{
		// When there is a list, we are replacing the whole
		// thing, so let's just poof it out of existance.
		anchor.facet_fields = {};
	    }
	    each(list_or_key, function(item){
		anchor.facet_fields[item] = true;
	    });
	}
	return us.keys(anchor.facet_fields);
    };

    /*
     * Function: set_default_query
     *
     * Setter for the default query for the query variable ('q').
     * 
     * Call <reset_query> if you want to affect query immediately.
     * 
     * Parameters: 
     *  new_default_query - new default query string (or TODO: <bbop.logic>)
     *
     * Returns:
     *  the current setting of default query for ('q')
     */
    this.set_default_query = function(new_default_query){
	anchor.default_query = new_default_query;
	return anchor.default_query;
    };

    // /*
    //  * Function: set_first_run_query
    //  *
    //  * Setter for a first run query.  Normally, when <reset_query>, or
    //  * related method, is executed, we reset back to the default
    //  * query. This method sets a one time variable so a non empty
    //  * value can be used for the first reset.
    //  * 
    //  * Call <reset_query> if you want to affect query immediately.
    //  * 
    //  * Parameters: 
    //  *  first_run_query - query_string (or TODO: <bbop.logic>)
    //  *
    //  * Returns:
    //  *  the current setting of default query for ('q')
    //  */
    // this.set_first_run_query = function(first_run_query){
    // 	anchor.default_query = new_default_query;
    // 	return anchor.default_query;
    // };

    /*
     * Function: reset_default_query
     *
     * Reset the default query back to "*:*".
     * 
     * Call <reset_query> if you want to affect query immediately.
     * 
     * Parameters:
     *  n/a
     *
     * Returns:
     *  the current setting of default query ('q')
     */
    this.reset_default_query = function(){
	anchor.default_query = anchor.fundamental_query;
	return anchor.default_query;
    };

    /*
     * Function: set_query
     *
     * Setter for the query variable ('q').
     * 
     * Parameters: 
     *  new_query - new value for the query string (or TODO: <bbop.logic>)
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_comfy_query>
     */
    this.set_query = function(new_query){
	anchor.query = new_query;
	return anchor.query;
    };

    /*
     * Function: set_comfy_query
     *
     * A specialized setter for the query variable ('q'), as follows:
     *
     * If the input is all alphanum or space, the input is
     * tokenized. If there is one token, if it is at least three
     * characters, gets a wildcard '*'; if there are more tokens, the
     * last one gets a wild card.
     * 
     * This might be a more comfortable way to search for most naive
     * (non-power user) interfaces.
     * 
     * Parameters: 
     *  new_query - new value for the query string (or TODO: <bbop.logic>)
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_query>
     */
    this.set_comfy_query = function(new_query){

	var comfy_query = new_query;

	// Check that there is something there.
	if( new_query && new_query.length && new_query.length > 0 ){

	    // Check if the last real input has a space after it.
	    var has_cursor_p = true;
	    if( new_query.slice(-1) === ' ' ){
		has_cursor_p = false;
	    }

	    // Now chomp it down again to get rid of whitespace.
	    new_query = bbop.chomp(new_query);

	    // Check (again) that there is something there.
	    if( new_query && new_query.length && new_query.length > 0 ){

		// That it is alphanum+space-ish and that we actually
		// might want to add a wildcard (i.e. has cursor).
		if( alphanum.test(new_query) && has_cursor_p ){
	    
		    // Break it into tokens and get the last.
		    var tokens = new_query.split(new RegExp('\\s+'));
		    var last_token = tokens[tokens.length -1];
		    //ll('last: ' + last_token);
		
		    if( tokens.length === 1 ){
			
			// If it is three or more, add the wildcard.
			if( last_token.length >= anchor.minimal_query_length ){
			    tokens[tokens.length -1] = last_token + '*';
			}
		    }else{
			tokens[tokens.length -1] = last_token + '*';
		    }
		    // And join it all back into our comfy query.
		    comfy_query = tokens.join(' ');
		}
	    }
	}

	// Kick it back to the normal set_query.
	return anchor.set_query(comfy_query);
    };

    /*
     * Function: set_id
     *
     * A limited setter, removing whatever else is on query. This is
     * for when you want to lock into one (unique) document by id
     * (essentially 'q=id:"foo"'). All other query operations behave
     * as they should around it.
     * 
     * Parameters: 
     *  new_id - string id
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_ids>
     */
    this.set_id = function(new_id){
	anchor.query = 'id:' + bbop.ensure(new_id, '"');
	return anchor.query;
    };

    // Function to unwind and lock a list if identifiers onto a field.
    function _lock_map(field, id_list){
	var fixed_list = [];
	each(id_list, function(item){
	    fixed_list.push(bbop.ensure(item, '"'));
	});
	
	var base_id_list = '(' + fixed_list.join(' OR ') + ')';

	var ret_query = field + ':' + base_id_list;
	return ret_query;
	
    }

    /*
     * Function: set_ids
     *
     * Like <set_id>, a limited setter. It removes whatever else is on
     * query and replaces it with something like:
     * 
     * : gm.set_ids(['GO:1', 'GO:2'])
     * 
     * This is for when you want to lock into a set of documents by id. All
     * other query operations behave as they should around it.
     * 
     * Parameters: 
     *  id_list - a list of ids to search for
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_ids>
     */
    this.set_ids = function(id_list){
	anchor.query = _lock_map('id', id_list);
	return anchor.query;
    };

    /*
     * Function: set_targets
     *
     * Like a more generalized version of <set_ids>, a limited. It
     * removes whatever else is on query and replaces it with
     * something like:
     * 
     * : gm.set_targets(['GO:1', 'GO:2'], ['field_1', 'field_2'])
     * 
     * This is for when you want to lock into a set of documents by
     * locking onto identifiers in some set of search fields. All
     * other query operations behave as they should around it.
     * 
     * Parameters: 
     *  id_list - a list of ids to search for
     *  field_list - a list of fields ids to search across
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_ids>
     */
    this.set_targets = function(id_list, field_list){

	var fixed_list = [];
	each(field_list, function(field){
	    fixed_list.push(_lock_map(field, id_list));
	});

	var sum = fixed_list.join(' OR ');

	anchor.query = sum;
	return anchor.query;
    };

    /*
     * Function: get_query
     *
     * Getter for the query variable ('q').
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of extra
     */
    this.get_query = function(){
	return anchor.query;
    };

    /*
     * Function: get_default_query
     *
     * Getter for what the query variable 'q' will be set to on a
     * <reset_query>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of the default query
     */
    this.get_default_query = function(){
	return anchor.default_query;
    };

    /*
     * Function: get_fundamental_query
     *
     * Getter for what the query variable 'q' will be set to on a
     * <reset_default_query>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of the fundamental default query
     */
    this.get_fundamental_query = function(){
	return anchor.fundamental_query;
    };

    /*
     * Function: get_query
     *
     * Getter for the query variable ('q').
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of extra
     */
    this.get_query = function(){
	return anchor.query;
    };

    /*
     * Function: reset_query
     *
     * Remove/reset the query variable ('q'); this set it back to the
     * default query.
     *
     * Parameters:
     *  none
     *
     * Returns:
     *  the current value of query
     * 
     * Also see:
     *  <set_default_query>
     *  <reset_default_query>
     */
    this.reset_query = function(){
	anchor.query = anchor.default_query;
	ll('reset query to default: ' + anchor.query);
	return anchor.query;
    };

    /*
     * Function: set_extra
     *
     * Setter for the internal string variable to be appended to the
     * end of a query. For special use cases only (e.g. extend
     * functionality of the API safely).
     * 
     * Parameters: 
     *  new_extra - *[optional]* new value for the extras string
     *
     * Returns:
     *  the current setting of extra
     */
    this.set_extra = function(new_extra){
	anchor.query_extra = new_extra;
	return anchor.query_extra;
    };

    /*
     * Function: get_extra
     *
     * Getter for the internal string variable to be appended
     * to the end of a query.
     *
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of extra
     */
    this.get_extra = anchor.set_extra;

    /*
     * Function: remove_extra
     *
     * Remove/reset the extra bit.
     *
     * Parameters:
     *  none
     *
     * Returns:
     *  ""
     */
    this.remove_extra = function(){
	anchor.query_extra = "";
	return anchor.query_extra;
    };

    /*
     * Function: set
     *
     * Set an internal variable for the query. The internal variables
     * are typically things like 'qt', 'indent', etc.--things that you
     * might set and forget a while. It does /not/ include highly
     * dynamic variables (like callback and packet) or querying
     * variables like 'q' and 'fq'; for those you need to use the API.
     *
     * Parameters: 
     *  key - the name of the parameter to change
     *  new_val - what you want the new value to be
     *
     * Returns:
     *  n/a
     */
    this.set = function(key, new_val){
	anchor.query_variants[key] = new_val;
    };

    /*
     * Function: get
     *
     * Get an internal variable for the query.
     *
     * See <set> for the kinds of parameters that can be read.
     * 
     * Parameters: 
     *  key - the name of the parameter to get
     *
     * Returns:
     *  The found value of the key.
     */
    this.get = function(key){
	return anchor.query_variants[key];
    };

    /*
     * Function: unset
     *
     * Unset (remove) an internal variable for the query. Only usable on certain types of 
     * 
     * Only use is you really know what you're doing.
     *
     * Parameters: 
     *  key - the name of the parameter to unset/remove
     *
     * Returns:
     *  boolean; true false on whether the key was found
     */
    this.unset = function(key){
	var retval = false;

	if( typeof(anchor.query_variants[key]) !== 'undefined' ){
	    retval = true;
	    delete anchor.query_variants[key];
	}

	return retval;
    };

    /*
     * Function: include_highlighting
     *
     * Turn hilighting on or off (with true or false).
     * 
     * This essentially adds the parameters to the query string to
     * make sure that basic highlighting on the search is returned.
     * 
     * It starts off as false. The optional html_elt_str argument
     * defaults to:
     *  : <em class="hilite">
     *
     * Parameters: 
     *  hilite_p - *[optional]* boolean
     *  html_elt_str - *[serially optional]* the HTML element string to use
     *
     * Returns:
     *  either false or the current string being used for the return element
     */
    this.include_highlighting = function(hilite_p, html_elt_str){
	var retval = false;

	if( typeof(hilite_p) !== 'undefined' &&
	    (hilite_p === true || hilite_p === false) ){
	    if( hilite_p === true ){

		// Set the default string if necessary.
		if( ! html_elt_str ){ html_elt_str = '<em class="hilite">'; }

		// Set the parameters.
		anchor.set('hl', 'true');
		anchor.set('hl.simple.pre', html_elt_str);

        anchor.set('hl.snippets', anchor.default_hl_snippets);

		// And the retval is not longer false.
		retval = html_elt_str;

	    }else{
		
		// Unset the parameters.
		anchor.unset('hl');
		anchor.unset('hl.simple.pre');
		anchor.unset('hl.snippets');
	    }

	}else{
	    // Otherwise, just discover the current state and return
	    // it.
	    var cl_tmp = anchor.get('hl.simple.pre');
	    if( typeof(cl_tmp) !== 'undefined' ){
		retval = cl_tmp;
	    }
	}

	return retval;
    };

    /*
     * Function: set_personality
     *
     * While we are always contacting the same Solr instance, we
     * sometimes want to have different weights, facets, etc. This
     * function allows us to use the pre-set ones defined in the
     * constructor configuration argument.
     * 
     * Currently, this only sets the 'facet.field' internal variable.
     *
     * Parameters: 
     *  personality_id - string
     *
     * Returns:
     *  Will return false if personality doesn't exist
     */
    this.set_personality = function(personality_id){
	var retval = false;

	// This sets the facet.field internal variable.
	var cclass = anchor._golr_conf.get_class(personality_id);
	if( cclass ){

	    // Remember what our personality is.
	    // WARNING: this line must go before the query_field_set
	    // line below, or else we won't get the "smart" search.
	    this._current_class = cclass;

	    // Set the facets for our class.
	    anchor.facets(cclass.field_order_by_weight('filter'));

	    // Set the query field weights ('qf') necessary to make
	    // queries run properly.
	    anchor.query_field_set(cclass.get_weights('boost'));
	    
	    // Show that we did indeed set a personality.
	    retval = true;
	}

	return retval;
    };

    /*
     * Function: get_personality
     *
     * Returns the current personality, null if none.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  Returns the current personality as a string, null if none is set
     */
    this.get_personality = function(){
	var retval = null;

	if( typeof(anchor._current_class) !== 'undefined' &&
	    bbop.what_is(anchor._current_class) === 'golr-conf.conf_class'){
	    retval = anchor._current_class.id();
	}

	return retval;
    };

    /*
     * Function: get_query_url
     *
     * Get the current invariant state of the manager returned as a
     * encoded URL string (using encodeURI()).
     * 
     * This means the URL for the current query to the GOlr store, but
     * without extra information about packets, callbacks, and the
     * like.
     * 
     * This is generally appropriate for getting data, but maybe not
     * for things like high-speed autocomplete where races can
     * occur. For those, you might want to consider <update> or
     * <search>.
     *
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  URL string
     * 
     * Also see:
     *  <update>, <search>
     */
    this.get_query_url = function(){

	// Structure of the necessary invariant parts.	
	var qurl = anchor._solr_url + 'select?';

	// Filters to assemble.
	var assemf = anchor.get_query_filters();
	var fq = anchor.filter_list_to_assemble_hash(assemf);

	// Add all of our different specialized hashes.
	var things_to_add = [
	    //bbop.get_assemble(anchor.query_invariants),
	    //bbop.get_assemble(anchor.query_facets),
	    bbop.get_assemble(anchor.query_variants),
	    bbop.get_assemble(anchor.current_facet_field_limits),
	    //bbop.get_assemble({'fq': anchor.query_sticky_filters}),
	    bbop.get_assemble({'fq': fq}),
	    bbop.get_assemble({'facet.field':
			       us.keys(anchor.facet_fields)}),
	    bbop.get_assemble({'q': anchor.query}),
	    anchor.query_extra
	];
	// Add query_fields ('qf') iff query ('q') is set and it is
	// not length 0.
	if( anchor.query &&
	    anchor.query.length &&
	    anchor.query.length !== 0 &&
	    anchor.query !== anchor.fundamental_query ){
		var in_qf =
		    bbop.get_assemble({'qf': anchor.query_field_set()});
		things_to_add.push(in_qf);
	    }
	
	// Assemble the assemblies into a single URL, throw out
	// everything that seems like it isn't real to keep the URL as
	// clean a possible.
	var filtered_things =
		bbop.pare(things_to_add, function(item, index){
		    var retval = true;
		    if( item && item !== '' ){
			retval = false;
		    }
		    return retval;
		});

	var final_qurl = qurl + filtered_things.join('&');
	final_qurl = anchor._query_encode(final_qurl);
	ll('qurl: ' + final_qurl);
    	return final_qurl;
    };

    /*
     * Function: push_excursion
     *
     * Save the current state of the manager--data and sticky filter
     * information--onto an internal stack. Batch information is not
     * stored.
     * 
     * Useful for gettinginto a state, doing something else, then
     * returning to the original state.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  the number of items on the excursion stack
     * 
     * Also see:
     *  <get_query_url>
     *  <pop_excursion>
     */
    this.push_excursion = function(){
	
	var now = {
	    // Save current state (data).
	    data_url: anchor.get_query_url(),
	    // Save current state (session).
	    session: {
		// Get the sticky filters.
		sticky_filters: anchor.get_sticky_query_filters()
	    }
	};

	// Save.
	anchor._excursions.push(now);

	// ...
    	return anchor._excursions.length;
    };

    /*
     * Function: pop_excursion
     *
     * Return to a previously pushed state. Batch items are not
     * recovered.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  boolean on whether a state was recovered
     * 
     * Also see:
     *  <get_query_url>
     *  <gpush_excursion>
     */
    this.pop_excursion = function(){
	
	var retval = false;

	var then = anchor._excursions.pop();
	if( then ){
	    retval = true;

	    // Recover data state.
	    var then_data_url = then['data_url'];
	    anchor.load_url(then_data_url);

	    // Recover the session state.
	    var then_session_stickies = then['session']['sticky_filters'];
	    // Add the sticky filters.
	    each(then_session_stickies, function(sticky){
		var flt = sticky['filter'];
		var fvl = sticky['value'];
		var fpl = [];
		if( sticky['negative_p'] === true ){
		    fpl.push('-');
		}
		if( sticky['sticky_p'] === true ){
		    fpl.push('*');
		}
		anchor.add_query_filter(flt, fvl, fpl);
	    });	    
	}
	
    	return retval;
    };

    /*
     * Function: get_download_url
     *
     * Get the current invariant state of the manager returned as a
     * URL string.
     * 
     * This differs from <get_query_url> in that the generated string
     * is intended for text-processing uses rather than computerized
     * searching uses. The idea where is to create a TSV file for
     * downloading and consumption.
     * 
     * Instead of downloading all of the results, a limited listed set
     * can be downloaded using entity_list, which identifies documents by id.
     * 
     * The optional argument hash looks like:
     *  rows - the number of rows to return; defaults to: 1000
     *  encapsulator - how to enclose whitespace fields; defaults to: ""
     *  separator - separator between fields; defaults to: "%09" (tab)
     *  header - whether or not to show headers; defaults to: "false"
     *  mv_separator - separator for multi-valued fields; defaults to: "|"
     *  entity_list - list of specific download items in results; default null
     * 
     * With the entity list, keep in mind that null and an empty list
     * are handled in pretty much the same way--they are an indication
     * that we are going after nothing specific, and so all results
     * are game.
     * 
     * Parameters:
     *  field_list - a list of fields to return
     *  in_arg_hash - *[optional]* additional optional arguments
     * 
     * Returns:
     *  URL string
     * 
     * Also see:
     *  <get_query_url>
     */
    this.get_download_url = function(field_list, in_arg_hash){
	
	// Save current state.
	anchor.push_excursion();

	// Deal with getting arguments in properly.
	var default_hash =
	    {
		rows : 1000,
		encapsulator : '',
		separator : "%09",
		header : 'false',
		mv_separator : "|",
		entity_list : [],
		golr_download_url : null
	    };
	var arg_hash = bbop.fold(default_hash, in_arg_hash);

	// Make the changes we want.
	anchor.set('wt', 'csv');
	anchor.set('start', 0);
	anchor.set('fl', field_list.join(','));
	anchor.set('rows', arg_hash['rows']);
	anchor.set('csv.encapsulator', arg_hash['encapsulator']);
	anchor.set('csv.separator', arg_hash['separator']);
	anchor.set('csv.header', arg_hash['header']);
	anchor.set('csv.mv.separator', arg_hash['mv_separator']);

	// A little more tricky, jimmy the entity list into the query
	// if it's viable.
	var entity_list = arg_hash['entity_list'];
	if( typeof(entity_list) !== 'undefined' &&
	    us.isArray(entity_list) &&
	    entity_list.length > 0 ){
		anchor.set_ids(entity_list);
	}

	// Get url.
	var returl = anchor.get_query_url();

	// If defined, switch download URL to something else.
	if( arg_hash.golr_download_url ){
	    returl = returl.replace(anchor._solr_url,
				    arg_hash.golr_download_url);
	}

	// Reset the old state.
	anchor.pop_excursion();

    	return returl;
    };

    /*
     * Function: get_filter_query_string
     *
     * Get the current state of the manager, as defined by the current
     * gross filter set--query, sticky filters, and standard filters--
     * returned as a URL query string (sans the '?').
     * 
     * This differs from <get_query_url> and <get_state_url> in that
     * the generated string is intended for applications that may want
     * just enough information to recover filter state when the
     * personality, and other types of information, are already
     * known. It is intended to be part of a light RESTy bookmarking
     * mechanism in larger application.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  query string for current filters (sans the '?')
     * 
     * Also see:
     *  <get_query_url>
     *  <get_state_url>
     */
    this.get_filter_query_string = function(){
	
	// // Save current state.
	// anchor.push_excursion();

	var q = anchor.get_query();

	// Get the filters and sort them into sticky and "normal"
	// sets.
	var filters = anchor.get_query_filters();
	var std_filters = [];
	var sticky_filters = [];
	each(filters, function(filter){
	    if( filter['sticky_p'] ){
		sticky_filters.push(filter);
	    }else{
		std_filters.push(filter);
	    }
	});
	
	var fq = anchor.filter_list_to_assemble_hash(std_filters);
	var sfq = anchor.filter_list_to_assemble_hash(sticky_filters);

	var things_to_add = [];
	if( q ){
	    things_to_add.push(bbop.get_assemble({'q': q}));
	}
	if( ! us.isEmpty(fq) ){
	    things_to_add.push(bbop.get_assemble({'fq': fq}));
	}
	if( ! us.isEmpty(sfq) ){
	    things_to_add.push(bbop.get_assemble({'sfq': sfq}));
	}
	    
	// // Reset the old state.
	// anchor.pop_excursion();

	var final_qstr = things_to_add.join('&');
	final_qstr = anchor._query_encode(final_qstr);
    	return final_qstr;
    };

    /*
     * Function: get_state_url
     *
     * Get the current invariant state of the manager, plus the
     * current personality as a parameter, returned as a URL string.
     * 
     * This differs from <get_query_url> in that the generated string
     * is intended for applications that may want a little more
     * information and hinting over just what the current search
     * is. This method essentially parameterizes some of the "hidden
     * state" of the manager.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  URL string
     * 
     * Also see:
     *  <get_query_url>
     */
    this.get_state_url = function(){
	
	// Save current state.
	anchor.push_excursion();

	// Make the changes we want. First, physically set the
	// "personality", then set pins for jump-in recovery.
	anchor.set('personality', anchor.get_personality());

	// Explicitly set sticky pins for later recovery.
	// Do this pretty much exactly like we do for get_query_url().
	var sticky_filters = anchor.get_sticky_query_filters();
	var sfq = anchor.filter_list_to_assemble_hash(sticky_filters);
	anchor.set('sfq', sfq);
	
	// Get url.
	var returl = anchor.get_query_url();

	// Reset the old state.
	anchor.pop_excursion();

    	return returl;
    };

    /*
     * Function: load_url
     *
     * Makes a a best attempt to recover the state of a manager from
     * the clues left in a data url. This can also (and probably
     * should) be thought of as a "load bookmark"
     * function. Theoretically, you should even be able to use
     * "bookmarks" from alien installations.
     * 
     * Note that while this recovers enough to get the same data,
     * certain "session"/"preference" type things that are not encoded
     * in the url (e.g. filter stickiness, the contents of batch
     * queues, non-default base queries, etc.) will not be replayed
     * and must be recovered or guessed on an app by app basis..
     * 
     * Warning: this currently only replays a small subset of possible
     * parameters. Currently: personality, q, fq, ???. In the future,
     * this should no all non-session information.
     * 
     * Warning: Because there is more to bookmarks than just the major
     * stuff, variants not supplied in the bookmark will be removed.
     * 
     * This returns true if the parameter portions of the new and
     * bookmark urls match. However, this is often not the case--think
     * shifting personalities, etc.
     * 
     * Parameters:
     *  url - A URL string generated by a manager's get_query_url (or similar)
     * 
     * Returns:
     *  boolean
     */
    this.load_url = function(url){

	// // Some Regexps that would be nice to just compile once.
	// var regexp_url_space = /\%20/g; // '%20' === ' '
	// var regexp_url_quote = /\%22/g; // '%22' === '"'
	// var regexp_url_left_paren = /\%28/g; // '%28' === '('
	// var regexp_url_right_paren = /\%29/g; // '%29' === ')'

	// We are assuming that we are consuming our own URLs from
	// get_query_url(), so we start by attempting to decode it
	// (TODO: need a tab watch here)?
	var decoded_url = decodeURI(url);

	// Break down url.
	var in_params = bbop.url_parameters(decoded_url);

	// First, look for the personality setting and invoke it if
	// it's there--it will dominate unless we take care of it first.
	// Also note the all the keys that we see (for later erasure
	// of excess).
	var seen_params = {};
	each(in_params, function(ip){
	    var key = ip[0];
	    var val = ip[1];
	    if( key === 'personality' && val && val !== '' ){
		anchor.set_personality(val);
	    }
	    seen_params[key] = true;
	});
	
	// Now cycle through the the parameters again and invoke the
	// appropriate functions to bring them in line.
	var sticky_cache = {};
	each(in_params, function(ip){
	    var key = ip[0];
	    var val = ip[1];
	    if( typeof(val) !== 'undefined' && val !== '' ){
		if( key === 'personality' ){
		    // Already did it, skip.
		}else if( key === 'q' ){
		    anchor.set_query(val);
		}else if( key === 'fq' || key === 'sfq' ){
		    // Split the fq (or sfq) parameter.
		    var fnv = bbop.first_split(':', val);
		    var fname = fnv[0];
		    var fval = fnv[1];
		    //ll('HERE: fname: ' + fname);
		    //ll('HERE: fval: ' + fval);
		    if( fname && fval ){
			
			var plist = [];
			
			// Remove leading sign on a filter and
			// add it to the plist.
			var lead_char = fname.charAt(0);
			if( lead_char === '-' || lead_char === '+' ){
			    plist.push(lead_char);
			    fname = fname.substr(1, fname.length -1);
			}
			
			// // TODO: 
			// // If the fval looks like it has not been
			// // decoded (like from a URL-safe
			// // bookmark), go ahead and do so.
			// fval = fval.replace(regexp_url_space, ' ');
			// fval = fval.replace(regexp_url_quote, '"');
			// fval = fval.replace(regexp_url_left_paren, '(');
			// fval = fval.replace(regexp_url_right_paren,')');
			
			// Do not allow quotes in--they will be
			// added by the assembler.
			fval = bbop.dequote(fval);
			
			// Make it sticky it it came in on "sfq".
			// Note if this is the sticky form.
			var skey = fname + '^' + fval;
			if( key === 'sfq' ){
			    sticky_cache[skey] = true;
			    plist.push('*');
			}
			
			// Add the query filter properly, but
			// only if we have not already added the
			// sticky form (prevent clobbering).
			if( ! bbop.is_defined(sticky_cache[skey]) ||
			    key === 'sfq'){
			    anchor.add_query_filter(fname, fval, plist);
			    
			}
		    }
		}else if( key === 'qf' ){
		    // qf is handles a little strangely...
		    var foo = bbop.first_split('^', val);
		    //ll('qf: key: '+ key +', val: '+ val +', foo: '+ foo);
		    anchor.add_query_field(foo[0], foo[1]);
		}else if( key === 'facet.field' ){
		    anchor.facets(val);
		}else if( key === 'start' || key === 'rows' ){
		    // Numbers need to be handled carefully.
		    if( bbop.what_is(val) === 'string' ){
			val = parseFloat(val);
		    }
		    anchor.set(key, val);
		}else{
		    // This one catches all of the non-special
		    // parameters and resets them using .set().
		    anchor.set(key, val);
		    // if( key === 'fq' ){
		    //     throw new Error("OI");			     
		    // }
		}
	    }
	});
	
	// Now go through and remove all of the query variant
	// parameters that were not seen in the bookmark.
	each(anchor.query_variants, function(val, key){
	    if( typeof(seen_params[key]) === 'undefined'){
		anchor.unset(key);
	    }
	});
	
	// Produce our own url from what we've done. If the parameters
	// match with the incoming argument's return true.
	var curr_url = anchor.get_query_url();
	var curr_params = bbop.url_parameters(curr_url);
	var differences = 0;
	if( in_params.length === curr_params.length ){
	    each(in_params, function(in_p, i){
		var curr_p = curr_params[i];
		if( in_p.length === curr_p.length ){
		    if( in_p.length === 1 ){
			if( in_p[0] === curr_p[0] ){
			    // match!
			}else{
			    differences++;
			}
		    }else if( in_p.length === 2 ){
			if( in_p[0] === curr_p[0] && in_p[1] === curr_p[1] ){
			    // match!
			}else{
			    differences++;
			}
		    }
		}else{
		    differences++;
		}
	    });
	}else{
	    differences++;
	}
	
	// Tally the differences and decides if they're the same.
	var retval = false;
	if( differences === 0 ){
	    retval = true;
	}
    	return retval;
    };

    /*
     * Function: add_to_batch
     *
     * "Save" the current manager state to run later in serial batch
     * mode.
     * 
     * The actual job of running these batches is left to the
     * implementation of the sub-managers; probably in "run_batch".
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  state url
     */
    this.add_to_batch = function(){
	var qurl = anchor.get_query_url();
	anchor._batch_urls.push(qurl);
    	return qurl;
    };

    /*
     * Function: batch_urls
     *
     * Return a pointer to the current batch urls.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  array
     */
    this.batch_urls = function(){
    	return anchor._batch_urls;
    };

    /*
     * Function: next_batch_url
     *
     * Return the next data to be processed, removing it from the
     * batch queue in the process.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  state url or null
     */
    this.next_batch_url = function(){
    	return anchor._batch_urls.shift() || null;
    };

    /*
     * Function: reset_batch
     *
     * Clear the currently queued data batch.
     * 
     * The actual job of running these batches is left to the
     * implementation of the sub-managers; probably in "run_batch".
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  the number of items cleared
     */
    this.reset_batch = function(){
	var num = anchor._batch_urls.length;
	anchor._batch_urls = [];
    	return num;
    };
}
function ( golr_loc , golr_conf_obj , engine , mode ) { registry . call ( this , [ 'prerun' , 'reset' , 'search' , 'error' , 'postrun' ] ) ; this . _is_a = 'bbop-manager-golr' ; var anchor = this ; // get a good self-reference point // Per-manager logger. this . _logger = new bbop . logger ( this . _is_a ) ; //this._logger.DEBUG = true; this . _logger . DEBUG = false ; function ll ( str ) { anchor . _logger . kvetch ( str ) ; } // Setup right handler subsystem--either returning a direct // response or a promise, depending. anchor . _engine = engine || null ; anchor . _mode = mode || null ; anchor . _runner = function ( resource , payload ) { var ret = null ; // if( typeof(anchor._engine) === 'undefined' ){ //     throw new Error('"engine" not set in new bbop-manager-golr'); // } if ( anchor . _mode === 'sync' ) { ret = anchor . _engine . fetch ( resource , payload ) ; } else if ( anchor . _mode === 'async' ) { ret = anchor . _engine . start ( resource , payload ) ; } else { // throw new Error('"mode" not set in new bbop-manager-golr'); return resource ; } return ret ; } ; // The callback function called after a successful AJAX // intialization/reset call. First it runs some template code, // then it does all of the callbacks. anchor . _run_reset_callbacks = function ( response ) { ll ( 'run reset callbacks...' ) ; //var response = new golr_response(json_data); anchor . apply_callbacks ( 'reset' , [ response , anchor ] ) ; anchor . apply_callbacks ( 'postrun' , [ response , anchor ] ) ; } ; // The main callback function called after a successful AJAX call in // the update function. anchor . _run_search_callbacks = function ( response ) { ll ( 'run search callbacks...' ) ; //var response = new golr_response(json_data); anchor . apply_callbacks ( 'search' , [ response , anchor ] ) ; anchor . apply_callbacks ( 'postrun' , [ response , anchor ] ) ; } ; // This set is called when we run into a problem. anchor . _run_error_callbacks = function ( response ) { ll ( 'run error callbacks...' ) ; //var response = new golr_response(json_data); anchor . apply_callbacks ( 'error' , [ response , anchor ] ) ; anchor . apply_callbacks ( 'postrun' , [ response , anchor ] ) ; } ; // The registries can only be applied if the engine is present. if ( engine ) { // Re-contextualize run_promise_functions(). anchor . run_promise_functions = function ( promise_function_stack , accumulator_function , final_function , error_function ) { // NOTE: Cannot sensibly use Function.call here as engine // is not really the superclass of this manager. return engine . run_promise_functions ( promise_function_stack , accumulator_function , final_function , error_function ) ; } ; // Setup the appropriate callbacks in the engine for success. anchor . _engine . register ( 'success' , function ( response ) { //console.log(anchor._engine); //console.log(response._raw); //  if ( ! response . success ( ) ) { throw new Error ( "Unsuccessful response from golr server!" ) ; } else { var cb_type = response . callback_type ( ) ; //console.log('okay response from server, will probe type...: '+cb_type); if ( cb_type === 'reset' ) { anchor . _run_reset_callbacks ( response ) ; } else if ( cb_type === 'search' ) { anchor . _run_search_callbacks ( response ) ; } else { throw new Error ( "Unknown callback type (runner): " + cb_type ) ; } } } ) ; // And, more easily, setup the appropriate callbacks in the // engine for failure. anchor . _engine . register ( 'error' , function ( response ) { anchor . _run_error_callbacks ( response ) ; } ) ; } // Just want to compile these once. var alphanum = new RegExp ( / ^[a-zA-Z0-9 ]+$ / ) ; this . minimal_query_length = 3 ; // To help keep requests from the past haunting us. Actually doing // something with this number is up to the UI. this . last_sent_packet = 0 ; //this.last_received_packet = 0; // Lightly check incoming arguments. // There should be a string url argument. // There could be a hash of pinned filters argument. if ( ! golr_loc || ! golr_conf_obj ) { ll ( 'ERROR: no proper arguments' ) ; } if ( typeof golr_loc !== 'string' ) { ll ( 'ERROR: no proper golr url string argument' ) ; } if ( ! golr_conf_obj . _is_a || golr_conf_obj . _is_a !== 'golr-conf.conf' ) { ll ( 'ERROR: no proper golr-conf.conf object argument' ) ; throw new Error ( 'boink! ' + bbop . what_is ( golr_conf_obj ) ) ; } // Whether or not to prevent ajax events from going. // This may not be usable, or applicable, to all backends. this . _safety = false ; // Our default target url. this . _solr_url = golr_loc ; // Settle in the configurations. // this._golr_conf = new bbop_golr.conf(golr_conf_var); this . _golr_conf = golr_conf_obj ; // The current data batches that we are storing. this . _batch_urls = [ ] ; this . _batch_accumulator_func = function ( ) { } ; this . _batch_final_func = function ( ) { } ; // The current state stack. this . _excursions = [ ] ; // The current class/personality that we're using. It may be none. this . _current_class = null ; // Our (default) query and the real deal. this . fundamental_query = '*:*' ; // cannot be changed this . default_query = '*:*' ; // changable this . query = this . default_query ; // current // Our (default) fl and whatever we have now. //this.default_fl = '*%2Cscore'; this . default_fl = '*,score' ; this . current_fl = this . default_fl ; // We remember defaults in the case of rows and start since they // are the core to any paging mechanisms and may change often. //this.default_rows = 25; //this.default_rows = 100; this . default_rows = 10 ; this . default_start = 0 ; this . current_rows = this . default_rows ; this . current_start = this . default_start ; // There is a reason for this...TODO: later (25+) this . default_facet_limit = 25 ; this . current_facet_limit = 25 ; // {facet_field_name: value, ...} this . current_facet_field_limits = { } ; // TODO: paging for facets; this . current_facet_offset = 25 ; this . current_facet_field_offsets = { } ; // Number of snippets to return when hl is enable this . default_hl_snippets = 1000 ; // Our default query args, with facet fields plugged in. this . query_variants = { // Our default standard search type. This means we don't // have to explicitly add fields to the search (although // the query fields ('qf') are still necessary to make // anything real happen). defType : 'edismax' , // Things unlikely to be touched. // There are unlikely to be messed with too much. qt : 'standard' , indent : 'on' , wt : 'json' , //version: '2.2', rows : anchor . current_rows , start : anchor . current_start , // Solr is offset indexing //fl: '*%2Cscore', fl : anchor . default_fl , // Deprecated: see query_filters //fq: {}, // Deprecated: see query_fields //qf: {}, // Deprecated: see query //q: '*:*' // Control of facets. facet : 'true' , 'facet.mincount' : 1 , 'facet.sort' : 'count' , 'json.nl' : 'arrarr' , // only in facets right now 'facet.limit' : anchor . default_facet_limit // TODO?: 'f.???.facet.limit': 50, // TODO: 'json.nl': [flat|map|arrarr] // Deprecated: see facet_fields //'facet.field': [] } ; // This is the 'qf' parameter. Although we keep it, it only needs // to be exposed when the query ('q') field is set. //this.query_fields = []; this . query_fields = { } ; // A richer way to handle the 'fq' query variant. // It should look like: // {<filter>: {<value>:{'sticky_p':(t|f), 'negative_p':(t|f)}, ...}} this . query_filters = { } ; // The engine for the facet.field list. this . facet_fields = { } ; /*
     * Function: debug
     * 
     * Turn on or off the verbose messages. Uses <bbop.logger>, so
     * they should come out everywhere.
     * 
     * Parameters: 
     *  p - *[optional]* true or false for debugging
     *
     * Returns: 
     *  boolean; the current state of debugging
     */ this . debug = function ( p ) { if ( p === true || p === false ) { this . _logger . DEBUG = p ; // TODO: add debug parameter a la include_highlighting } return this . _logger . DEBUG ; } ; /*
     * Function: minimal_query
     * 
     * The length of the minimal query to consider. Defaults to 3.
     * 
     * Parameters: 
     *  n - *[optional]* number, or nothing if just want current
     *
     * Returns: 
     *  number
     */ this . minimal_query = function ( n ) { if ( us . isNumber ( n ) ) { anchor . minimal_query_length = n ; } return this . minimal_query_length ; } ; /*
     * Function: lite
     * 
     * Limit the returns fields (the parameter "fl") to the ones
     * defined in the set of fields defined in results, label fields
     * if available (i.e. "_label", "_map" when "_label" is
     * multi=valued), and "score" and "id".
     * 
     * The default is "false".
     * 
     * Parameters: 
     *  use_lite_p - *[optional]* true or false, none just returns current
     *
     * Returns: 
     *  boolean; the current state of lite-ness
     */ this . lite = function ( use_lite_p ) { // Adjust the current state accordingly. if ( use_lite_p === true || use_lite_p === false ) { if ( use_lite_p === true ) { // The actual collections and adjustment. // First, this only works if we have a personality, so // check to see if we have one. var per = anchor . get_personality ( ) ; if ( per ) { // Since we have a personality, collect all of the // mentioned fields. var field_collection = { } ; var union = bbop . merge ; var ccl = anchor . _current_class ; // Fill field_collection with the fields // in the given category. //loop(['boost', 'result', 'filter'], //loop(['result', 'filter'], each ( [ 'result' ] , function ( cat ) { field_collection = union ( field_collection , ccl . get_weights ( cat ) ) ; } ) ; // Next, flatten into a list. var flist = us . keys ( field_collection ) ; // Now for all the fields in these categories, see // if we can find additional "special" labels to // go with them. each ( flist , function ( flist_item ) { //loop(['_label', '_label_searchable'], each ( [ '_label' ] , function ( field_suffix ) { var new_field = flist_item + field_suffix ; var nf_obj = ccl . get_field ( new_field ) ; if ( nf_obj ) { flist . push ( new_field ) ; // There appears to be the // thing label. If they are // both multi-valued, then // there will be a map as // well. if ( nf_obj . is_multi ( ) ) { flist . push ( flist_item + '_map' ) ; } } } ) ; } ) ; // Finally, set these fields (plus score) as the // new return fields. flist . push ( 'score' ) ; flist . push ( 'id' ) ; //anchor.current_fl = flist.join('%2C'); anchor . current_fl = flist . join ( ',' ) ; anchor . set ( 'fl' , anchor . current_fl ) ; } } else { // else false // Reset. anchor . current_fl = anchor . default_fl ; anchor . set ( 'fl' , anchor . current_fl ) ; } } // Return the current state. var retval = false ; if ( anchor . default_fl !== anchor . current_fl ) { retval = true ; } return retval ; } ; // An internal helper function to munge the name of a field into // the name of its corresponding facet field. function _field_to_facet_field ( field ) { return 'f.' + field + '.facet.limit' ; } /*
     * Function: get_facet_limit
     * 
     * Get the limit for a specified facet or the global limit.
     * 
     * Parameters: 
     *  field - *[optional]* limit for a specific field; otherwise global value
     *
     * Returns: 
     *  integer or null
     */ this . get_facet_limit = function ( field ) { var retval = null ; if ( ! field ) { retval = anchor . current_facet_limit ; } else { var f = _field_to_facet_field ( field ) ; var try_val = anchor . current_facet_field_limits [ f ] ; if ( typeof ( try_val ) !== 'undefined' ) { retval = try_val ; } } return retval ; } ; /*
     * Function: set_facet_limit
     * 
     * Change the number of facet values returned per call.
     * The default is likely 25.
     * 
     * Just as in Solr, a -1 argument is how to indicate unlimted
     * facet returns.
     * 
     * This setting does not survive things like <resets_facet_limit>.
     * 
     * Parameters: 
     *  arg1 - (integer) set the global limit
     *
     * Parameters: 
     *  arg1 - (string) the name of the field to check
     *  arg2 - (integer) set the limit for this field
     *
     * Returns: 
     *  boolean on whether something was set
     */ this . set_facet_limit = function ( arg1 , arg2 ) { var retval = false ; // Decide which form of the function we're using. if ( typeof ( arg2 ) === 'undefined' && bbop . what_is ( arg1 ) === 'number' ) { // form one // Set var nlimit = arg1 ; anchor . current_facet_limit = nlimit ; anchor . set ( 'facet.limit' , anchor . current_facet_limit ) ; retval = true ; } else if ( typeof ( arg1 ) !== 'undefined' && typeof ( arg2 ) !== 'undefined' && bbop . what_is ( arg1 ) === 'string' && bbop . what_is ( arg2 ) === 'number' ) { var field = _field_to_facet_field ( arg1 ) ; var limit = arg2 ; anchor . current_facet_field_limits [ field ] = limit ; retval = true ; } return retval ; } ; /*
     * Function: set_default_facet_limit
     * 
     * Permanently change the default number of facet values returned
     * per call. The default's default is likely 25.
     * 
     * Just as in Solr, a -1 argument is how to indicate unlimted
     * facet returns.
     * 
     * Parameters: 
     *  lim - (integer) set the global default limit
     *
     * Returns: 
     *  old default
     */ this . set_default_facet_limit = function ( lim ) { // Capture ret. var retval = anchor . default_facet_limit ; // Set anchor . default_facet_limit = lim ; //anchor.set('facet.limit', anchor.default_facet_limit); return retval ; } ; /*
     * Function: reset_facet_limit
     * 
     * Either reset the global limit to the original (likely 25)
     * and/or remove the specified filter. Sets everything back to the
     * original values or whatever was set by
     * <set_default_facet_limit>.
     * 
     * Parameters: 
     *  field - *[optional]* remove limit for a field; otherwise all and global
     *
     * Returns: 
     *  boolean on whether something was reset
     */ this . reset_facet_limit = function ( field ) { var retval = false ; if ( typeof ( field ) === 'undefined' ) { // Eliminate all fields by blowing them away. anchor . current_facet_limit = anchor . default_facet_limit ; anchor . set ( 'facet.limit' , anchor . current_facet_limit ) ; anchor . current_facet_field_limits = { } ; retval = true ; } else { // eliminate just the one field var f = _field_to_facet_field ( field ) ; if ( typeof ( anchor . current_facet_field_limits [ f ] ) !== 'undefined' ) { delete anchor . current_facet_field_limits [ f ] ; retval = true ; } } return retval ; } ; /*
     * Function: get_results_count
     * 
     * Get the current number of results that will be returned.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns: 
     *  integer
     */ this . get_results_count = function ( field ) { return anchor . get ( 'rows' ) ; } ; /*
     * Function: set_results_count
     * 
     * Change the number of result documents returned per call.
     * The default is likely 10.
     * 
     * Parameters: 
     *  count - (integer) set the global results count
     *
     * Returns:
     *  the count set
     */ this . set_results_count = function ( count ) { anchor . set ( 'rows' , count ) ; anchor . current_rows = count ; return anchor . current_rows ; } ; /*
     * Function: reset_results_count
     * 
     * Reset the number of documents to their original setting, likely
     * 10.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the new count
     */ this . reset_results_count = function ( ) { anchor . set ( 'rows' , anchor . default_rows ) ; anchor . current_rows = anchor . default_rows ; return anchor . current_rows ; } ; /*
     * Function: plist_to_property_hash
     *
     * Turn a plist to a hash containing the different properties that
     * can be defined for a query filter. Possible values are: '+'
     * (positive filter), '-' (negative filter), '*' (sticky filter),
     * '$' (transient). If mutually exclusive properties are defined
     * (e.g. both '+' and '-'), the last one will be used. Or, since
     * that is a call to silliness, let's say the behavior is
     * undefined.
     *
     * Parameters: 
     *  plist - *[optional]* a list of properties to apply to the filter
     *
     * Returns: 
     *  A hash version of the plist; otherwise, the default property hash
     */ this . plist_to_property_hash = function ( plist ) { // Let's start with the default values. var phash = { //'positive_p': true, 'negative_p' : false , //'transient_p': true 'sticky_p' : false } ; // If not defined, just return the default list. if ( plist ) { each ( plist , function ( item ) { if ( item === '+' ) { phash [ 'negative_p' ] = false ; //phash['positive_p'] = true; } else if ( item === '-' ) { phash [ 'negative_p' ] = true ; //phash['positive_p'] = false; } else if ( item === '*' ) { phash [ 'sticky_p' ] = true ; //phash['transient_p'] = false; } else if ( item === '$' ) { phash [ 'sticky_p' ] = false ; //phash['transient_p'] = true; } } ) ; } return phash ; } ; /*
     * Function: add_query_filter_as_string
     *
     * Setter for query filters ('fq'). Acts as a 
     *
     * Parameters: 
     *  filter_string - filter (type) string (e.g. "-type:gene")
     *  plist - *[optional]* list of properties of the filter
     *
     * Returns: 
     *  (TODO) The current query filter hash.
     * 
     * See also:
     *  <add_query_filter>
     */ this . add_query_filter_as_string = function ( filter_string , plist ) { // Split the incoming filter string into its component parts. var f_v = bbop . first_split ( ':' , filter_string ) ; var fname = f_v [ 0 ] ; var fval = f_v [ 1 ] ; // Need to shuck the value from the quotes, as in load_url. fval = bbop . dequote ( fval ) ; var props = null ; if ( us . isArray ( plist ) ) { props = plist ; } else { props = [ ] ; } // Only continue on sensible inputs. var ret = { } ; if ( fname !== '' && fval !== '' ) { // Similar to the URL loader. var lead_char = fname . charAt ( 0 ) ; if ( lead_char === '-' || lead_char === '+' ) { props . push ( lead_char ) ; fname = fname . substr ( 1 , fname . length - 1 ) ; } ret = this . add_query_filter ( fname , fval , props ) ; } return ret ; } ; /*
     * Function: add_query_filter
     *
     * Setter for query filters ('fq').
     *
     * Parameters: 
     *  filter - filter (type) string
     *  value - filter value string (or TODO: defined logic hash)
     *  plist - *[optional]* list of properties of the filter
     *
     * Returns: 
     *  (TODO) The current query filter hash.
     * 
     * See also:
     *  <plist_to_property_hash>
     */ this . add_query_filter = function ( filter , value , plist ) { // Make sure we've defined the group. if ( typeof ( this . query_filters [ filter ] ) === 'undefined' ) { this . query_filters [ filter ] = { } ; } this . query_filters [ filter ] [ value ] = this . plist_to_property_hash ( plist ) ; //ll("Current state: " + bbop.dump(this.query_filters)); return { } ; // TODO } ; /*
     * Function: remove_query_filter
     *
     * Remover for query filters ('fq'), is a plist is specified, it
     * will only remove if all of the listed criteria are met.
     *
     * Parameters: 
     *  filter - filter (type) string
     *  value - filter value string (TODO: or defined logic hash)
     *  plist - *[optional]* list of properties of the filter
     *
     * Returns: 
     *  boolean (on success)
     */ this . remove_query_filter = function ( filter , value , plist ) { // Default return value. var retval = false ; // Internal helper to delete a low level key, and then if the // top-level is empty, get that one too. function _full_delete ( hash , key1 , key2 ) { if ( key1 && key2 && hash && hash [ key1 ] && hash [ key1 ] [ key2 ] ) { delete hash [ key1 ] [ key2 ] ; } if ( us . isEmpty ( hash [ key1 ] ) ) { delete hash [ key1 ] ; } } // If we have a filter, a value, and it's there... if ( filter && value && anchor . query_filters [ filter ] && anchor . query_filters [ filter ] [ value ] ) { // If no real plist hash been defined, just go ahead // and get rid of that. Otherwise, make sure that the // defined plist and the stored properties are the // same before deleting. if ( ! plist || us . isEmpty ( plist ) ) { _full_delete ( anchor . query_filters , filter , value ) ; retval = true ; } else { var filter_phash = anchor . query_filters [ filter ] [ value ] ; var in_phash = anchor . plist_to_property_hash ( plist ) ; if ( bbop . is_same ( filter_phash , in_phash ) ) { _full_delete ( anchor . query_filters , filter , value ) ; retval = true ; } } } return retval ; } ; /*
     * Function: reset_query_filters
     *
     * Reset the query filters ('fq'); but leave sticky filters alone.
     *
     * Parameters: 
     *  n/a
     * 
     * Returns: 
     *  (TODO) The current query filter hash.
     */ this . reset_query_filters = function ( ) { // Drill down and delete all non-stickies. each ( anchor . query_filters , function ( values , filter ) { //ll('filter: ' + filter); each ( values , function ( props , value ) { //ll('  value: ' + value); var sticky_p = props [ 'sticky_p' ] ; if ( ! sticky_p ) { //ll('hit: ' + filter + ', ' + value); anchor . remove_query_filter ( filter , value ) ; } } ) ; } ) ; return { } ; // TODO } ; /*
     * Function: get_query_filter_properties
     *
     * Get a hash representing a query filter ('fq').
     *
     * Parameters: 
     *  key - filter string (TODO: or defined logic hash)
     *
     * Returns: 
     *  The current query filter hash for key.
     */ this . get_query_filter_properties = function ( filter , value ) { // Default return value. var retobj = null ; // If we have a key and it's there... var aqf = anchor . query_filters ; if ( filter && value && aqf [ filter ] && aqf [ filter ] [ value ] ) { retobj = { 'filter' : filter , 'value' : value , //'polarity': aqf[filter][value]['negative_p'], 'negative_p' : aqf [ filter ] [ value ] [ 'negative_p' ] , 'sticky_p' : aqf [ filter ] [ value ] [ 'sticky_p' ] } ; } return retobj ; } ; /*
     * Function: get_query_filters
     *
     * Get a list of hashes representing the query filters ('fq'). The
     * return lists look like:
     *
     * : [{'filter': A, 'value': B, 'negative_p': C, 'sticky_p': D}, ...]
     *
     * Where A and B are strings and C and D are booleans.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns: 
     *  A list of the current query filter hashs.
     */ this . get_query_filters = function ( ) { var retlist = [ ] ; each ( anchor . query_filters , function ( values , f ) { each ( values , function ( props , v ) { retlist . push ( anchor . get_query_filter_properties ( f , v ) ) ; } ) ; } ) ; return retlist ; } ; /*
     * Function: get_sticky_query_filters
     *
     * Get a list of hashes representing the current stucky query
     * filters ('fq'). See <get_query_filters> for a specification of
     * what the return type looks like.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns: 
     *  A list of the current sticky query filter hashs.
     * 
     * See also:
     *  <get_query_filters>
     */ this . get_sticky_query_filters = function ( ) { var retlist = [ ] ; each ( anchor . query_filters , function ( values , f ) { each ( values , function ( props , v ) { var qfp = anchor . get_query_filter_properties ( f , v ) ; if ( qfp [ 'sticky_p' ] === true ) { retlist . push ( qfp ) ; } } ) ; } ) ; return retlist ; } ; // A little extra thing that we might need sometimes. this . query_extra = null ; // Spaces can cause problems in URLs in some environments. //final_qurl = final_qurl.replace(/ /g, '%20'); // Convert the URL into something more usable. // Because we internally use %09 as a special case, make sure // we don't double-up on it. this . _query_encode = function ( str_to_encode ) { var fs1 = encodeURI ( str_to_encode ) ; var fs2 = fs1 . replace ( / \%2509 / g , '%09' ) ; var final_encoding = fs2 ; return final_encoding ; } ; /*
     * Function: filter_list_to_assemble_hash
     *
     * Get all of our query filter variables and try and make
     * something of them that <get_assemble> can understand.
     *
     * Sticky doesn't matter here, but negativity does. However, we
     * can be pretty naive since the hashing should have already taken
     * out mutually exclusive dupes.
     * 
     * The argument is a list of query filter properties, as returned
     * by <get_query_filters> and <get_sticky_query_filters>.
     *
     * Parameters:
     *  flist - a list of query filter properties (see above)
     *
     * Returns:
     *  hash of filter names to value lists
     * 
     * See also:
     *  <get_query_filters>
     *  <get_sticky_query_filters>
     */ this . filter_list_to_assemble_hash = function ( flist ) { var h = { } ; each ( flist , function ( filter_property ) { // Grab only the properties that affect the // URL. var filter = filter_property [ 'filter' ] ; var value = filter_property [ 'value' ] ; var negative_p = filter_property [ 'negative_p' ] ; // We need to alter at the filter level. if ( negative_p ) { filter = '-' + filter ; } // Make sure it is defined. if ( typeof ( h [ filter ] ) === 'undefined' ) { h [ filter ] = [ ] ; } h [ filter ] . push ( value ) ; } ) ; return h ; } ; /*
     * Function: sensible_query_p
     * 
     * Simply ask the manager if a free text query ('q') makes sense
     * at this point.
     * 
     * This currently means that the query text ('q') is one of:
     *  1) the same as the default query
     *  2) the same as the fundamental query
     *  3) three (3) (or whatever minimal_query is set to) characters long
     *  4) the empty string ('')
     * and that query fields ('qf') are defined.
     * 
     * This is an overridable opinion of the manager.
     * 
     * Parameters:
     *  n/a
     *
     * Returns:
     *  boolean
     */ this . sensible_query_p = function ( ) { var retval = false ; var q = anchor . get_query ( ) ; var qf = anchor . query_field_set ( ) ; //console.log('q', q); //console.log('qf', qf); if ( qf && ! us . isEmpty ( qf ) ) { // first hurdle if ( q === anchor . get_default_query ( ) ) { retval = true ; } else if ( q === anchor . get_fundamental_query ( ) ) { retval = true ; } else if ( q && q . length >= anchor . minimal_query_length ) { retval = true ; } else if ( q === '' ) { retval = true ; } } return retval ; } ; /*
     * Function: last_packet_sent
     *
     * It is up to the UI to do something interesting with this number.
     * 
     * Also remember that this number only rises through calls to
     * <update> or one of its wrappers. Calls to <get_query_url> and
     * the like will not affect this number.
     * 
     * Parameters:
     *  n/a 
     *
     * Returns:
     *  integer
     * 
     * See also:
     *  <update>
     */ this . last_packet_sent = function ( ) { return anchor . last_sent_packet ; } ; /*
     * Function: clear
     *
     * Clear all non-sticky query parameters to get back to a more
     * "original" state.
     * 
     * Not to be confused with <reset>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  n/a
     */ this . clear = function ( ) { // Reset 'q'. anchor . query = anchor . default_query ; // Reset 'fq', all but sticky. anchor . reset_query_filters ( ) ; } ; /*
     * Function: reset
     *
     * Manually trigger the "reset" chain of events.
     *
     * This is a curried wrapper for <update> and should be preferred
     * over a direct call to update.
     *
     * Note to be confused with <clear>.
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <update>
     */ this . reset = function ( ) { return anchor . update ( 'reset' ) ; } ; /*
     * Function: search
     *
     * Trigger the "search" chain of events.
     * Takes a field-keyed hash of bbop.logics as an argument.
     * 
     * This is a curried wrapper for <update> and should be preferred
     * over a direct call to update.
     * 
     * Parameters:
     *  n/a
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <update>
     */ this . search = function ( ) { return anchor . update ( 'search' ) ; } ; /*
     * Function: page
     *
     * Re-trigger the "search" chain of events, but with the variables
     * set for a different section of the results.
     * 
     * Note that this operates independently of any impossibilites in
     * the results--just how such paging would look and
     * triggering. Ths UI should handle impossibilities and the like.
     * 
     * This is a wrapper for <update> and should be preferred over a
     * direct call to update.
     * 
     * Parameters: 
     *  rows - the number of rows to return
     *  start - the offset of the rows to return
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <update>
     */ this . page = function ( rows , start ) { anchor . set ( 'rows' , rows ) ; anchor . set ( 'start' , start ) ; return anchor . update ( 'search' , rows , start ) ; } ; /*
     * Function: page_first
     *
     * Currently a convenience alias for <search>. Think about it--it
     * makes sense.
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  n/a
     * 
     * See also:
     *  <page>
     */ this . page_first = anchor . search ; /*
     * Function: page_previous
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <page>
     */ this . page_previous = function ( ) { var do_rows = anchor . get_page_rows ( ) ; var do_offset = anchor . get_page_start ( ) - do_rows ; return anchor . page ( do_rows , do_offset ) ; } ; /*
     * Function: page_next
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  the query url (with the jQuery callback specific parameters)
     *
     * Returns:
     *  n/a
     * 
     * See also:
     *  <page>
     */ this . page_next = function ( ) { var do_rows = anchor . get_page_rows ( ) ; var do_offset = anchor . get_page_start ( ) + do_rows ; return anchor . page ( do_rows , do_offset ) ; } ; /*
     * Function: page_last
     * 
     * Trigger search on last page parameters.
     * 
     * Since the manager has no idea about what is actually being
     * returned, the real world number of total documents needs to be
     * added as an argument.
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  total_document_count - integer for the total number of docs found
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <page>
     */ this . page_last = function ( total_document_count ) { var do_rows = anchor . get_page_rows ( ) ; var mod = total_document_count % do_rows ; var do_offset = total_document_count - mod ; // ll("page_last: " + total_document_count + " " + //    do_rows + " " + mod + " " + do_offset); var ret = null ; if ( mod === 0 ) { ret = anchor . page ( do_rows , do_offset - do_rows ) ; } else { ret = anchor . page ( do_rows , do_offset ) ; } return ret ; } ; /*
     * Function: get_page_rows
     *
     * Return the number of rows the manager is currently set
     * to. Useful as an argument to <page>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  integer; the number of rows the manager is currently set to
     * 
     * See also:
     *  <page>
     */ this . get_page_rows = function ( ) { return anchor . get ( 'rows' ) ; } ; /*
     * Function: get_page_start
     *
     * Return the rows offset the manager is currently set to. Useful
     * as an argument to <page>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  integer; the offset the manager is currently set to
     * 
     * See also:
     *  <page>
     */ this . get_page_start = function ( ) { return anchor . get ( 'start' ) ; } ; /*
     * Function: add_query_field
     * 
     * Add a new query field to the query. 
     * 
     * This does not go through and expand into searchable fields, for
     * that see: <query_field_set>.
     *
     * Parameters: 
     *  qf - the query field to add
     *  boost - *[optional]* defaults to 1.0
     *
     * Returns:
     *  true or false on whether or not it is a new field
     * 
     * See also:
     *  <query_field_set>
     */ this . add_query_field = function ( qf , boost ) { var retval = false ; // Make sure that some boost is there. if ( typeof ( boost ) === 'undefined' ) { boost = 1.0 ; } // Check. if ( typeof ( anchor . query_fields [ qf ] ) === 'undefined' ) { retval = true ; } // Add. anchor . query_fields [ qf ] = boost ; return retval ; } ; /*
     * Function: query_field_set
     *
     * Bulk getter/setter for the query fields--the fields that are
     * searched (and by what weight) when using a query ('q' or
     * set_query(), i.e. the 'qf' field).
     * 
     * This will always use searchable fields if possible,
     * automatically replacing the non-searchable versions (I can't
     * think of any reason to use non-searchable versions unless you
     * want your searches to not work) if a personality is set. If no
     * personality is set, it will just use the arguments as-is.
     * 
     * The argument replaces the current set.
     *
     * The qfs argument should be a hash like:
     * 
     *  {'field01': value01, ...}
     * 
     * Parameters: 
     *  qfs - *[optional]* query fields to set
     *
     * Returns:
     *  the current query_fields array (e.g. ["field01^value01", ...])
     */ this . query_field_set = function ( qfs ) { // Covenience. var cclass = anchor . _current_class ; // Only do something if we have a query field set. if ( qfs ) { // Only do the probing if a personality has been set. if ( cclass ) { // Get the current searchable extension string from // the personality class. //var s_ext = cclass.searchable_extension(); // Actually, we're going to make this non-variable. var s_ext = '_searchable' ; // Probe the input to see if there are any searchable // alternatives to try, use those instead. var searchable_qfs = { } ; each ( qfs , function ( value , filter ) { // If the probe fails, just put in // whatever is there. var cfield = cclass . get_field ( filter ) ; if ( cfield && cfield . searchable ( ) ) { //ll('filter/value:'); var new_f = filter + s_ext ; searchable_qfs [ new_f ] = value ; } else { searchable_qfs [ filter ] = value ; } } ) ; qfs = searchable_qfs ; } // Overwrite the current. anchor . query_fields = qfs ; } // Using the original information, convert them to the // proper output format. var output_format = [ ] ; each ( anchor . query_fields , function ( value , filter ) { output_format . push ( filter + '^' + value ) ; } ) ; return output_format ; } ; /*
     * Function: facets
     *
     * Bulk getter/setter for facets (technically 'facet.field').
     *
     * Parameters: 
     *  key - *[optional]* facet to add to the facet list
     *
     * Parameters: 
     *  list - *[optional]* list to replace the current list with
     *
     * Returns:
     *  the current facets hash.
     */ this . facets = function ( list_or_key ) { if ( list_or_key ) { if ( bbop . what_is ( list_or_key ) !== 'array' ) { // Arrayify it. list_or_key = [ list_or_key ] ; } else { // When there is a list, we are replacing the whole // thing, so let's just poof it out of existance. anchor . facet_fields = { } ; } each ( list_or_key , function ( item ) { anchor . facet_fields [ item ] = true ; } ) ; } return us . keys ( anchor . facet_fields ) ; } ; /*
     * Function: set_default_query
     *
     * Setter for the default query for the query variable ('q').
     * 
     * Call <reset_query> if you want to affect query immediately.
     * 
     * Parameters: 
     *  new_default_query - new default query string (or TODO: <bbop.logic>)
     *
     * Returns:
     *  the current setting of default query for ('q')
     */ this . set_default_query = function ( new_default_query ) { anchor . default_query = new_default_query ; return anchor . default_query ; } ; // /* //  * Function: set_first_run_query //  * //  * Setter for a first run query.  Normally, when <reset_query>, or //  * related method, is executed, we reset back to the default //  * query. This method sets a one time variable so a non empty //  * value can be used for the first reset. //  *  //  * Call <reset_query> if you want to affect query immediately. //  *  //  * Parameters:  //  *  first_run_query - query_string (or TODO: <bbop.logic>) //  * //  * Returns: //  *  the current setting of default query for ('q') //  */ // this.set_first_run_query = function(first_run_query){ // 	anchor.default_query = new_default_query; // 	return anchor.default_query; // }; /*
     * Function: reset_default_query
     *
     * Reset the default query back to "*:*".
     * 
     * Call <reset_query> if you want to affect query immediately.
     * 
     * Parameters:
     *  n/a
     *
     * Returns:
     *  the current setting of default query ('q')
     */ this . reset_default_query = function ( ) { anchor . default_query = anchor . fundamental_query ; return anchor . default_query ; } ; /*
     * Function: set_query
     *
     * Setter for the query variable ('q').
     * 
     * Parameters: 
     *  new_query - new value for the query string (or TODO: <bbop.logic>)
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_comfy_query>
     */ this . set_query = function ( new_query ) { anchor . query = new_query ; return anchor . query ; } ; /*
     * Function: set_comfy_query
     *
     * A specialized setter for the query variable ('q'), as follows:
     *
     * If the input is all alphanum or space, the input is
     * tokenized. If there is one token, if it is at least three
     * characters, gets a wildcard '*'; if there are more tokens, the
     * last one gets a wild card.
     * 
     * This might be a more comfortable way to search for most naive
     * (non-power user) interfaces.
     * 
     * Parameters: 
     *  new_query - new value for the query string (or TODO: <bbop.logic>)
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_query>
     */ this . set_comfy_query = function ( new_query ) { var comfy_query = new_query ; // Check that there is something there. if ( new_query && new_query . length && new_query . length > 0 ) { // Check if the last real input has a space after it. var has_cursor_p = true ; if ( new_query . slice ( - 1 ) === ' ' ) { has_cursor_p = false ; } // Now chomp it down again to get rid of whitespace. new_query = bbop . chomp ( new_query ) ; // Check (again) that there is something there. if ( new_query && new_query . length && new_query . length > 0 ) { // That it is alphanum+space-ish and that we actually // might want to add a wildcard (i.e. has cursor). if ( alphanum . test ( new_query ) && has_cursor_p ) { // Break it into tokens and get the last. var tokens = new_query . split ( new RegExp ( '\\s+' ) ) ; var last_token = tokens [ tokens . length - 1 ] ; //ll('last: ' + last_token); if ( tokens . length === 1 ) { // If it is three or more, add the wildcard. if ( last_token . length >= anchor . minimal_query_length ) { tokens [ tokens . length - 1 ] = last_token + '*' ; } } else { tokens [ tokens . length - 1 ] = last_token + '*' ; } // And join it all back into our comfy query. comfy_query = tokens . join ( ' ' ) ; } } } // Kick it back to the normal set_query. return anchor . set_query ( comfy_query ) ; } ; /*
     * Function: set_id
     *
     * A limited setter, removing whatever else is on query. This is
     * for when you want to lock into one (unique) document by id
     * (essentially 'q=id:"foo"'). All other query operations behave
     * as they should around it.
     * 
     * Parameters: 
     *  new_id - string id
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_ids>
     */ this . set_id = function ( new_id ) { anchor . query = 'id:' + bbop . ensure ( new_id , '"' ) ; return anchor . query ; } ; // Function to unwind and lock a list if identifiers onto a field. function _lock_map ( field , id_list ) { var fixed_list = [ ] ; each ( id_list , function ( item ) { fixed_list . push ( bbop . ensure ( item , '"' ) ) ; } ) ; var base_id_list = '(' + fixed_list . join ( ' OR ' ) + ')' ; var ret_query = field + ':' + base_id_list ; return ret_query ; } /*
     * Function: set_ids
     *
     * Like <set_id>, a limited setter. It removes whatever else is on
     * query and replaces it with something like:
     * 
     * : gm.set_ids(['GO:1', 'GO:2'])
     * 
     * This is for when you want to lock into a set of documents by id. All
     * other query operations behave as they should around it.
     * 
     * Parameters: 
     *  id_list - a list of ids to search for
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_ids>
     */ this . set_ids = function ( id_list ) { anchor . query = _lock_map ( 'id' , id_list ) ; return anchor . query ; } ; /*
     * Function: set_targets
     *
     * Like a more generalized version of <set_ids>, a limited. It
     * removes whatever else is on query and replaces it with
     * something like:
     * 
     * : gm.set_targets(['GO:1', 'GO:2'], ['field_1', 'field_2'])
     * 
     * This is for when you want to lock into a set of documents by
     * locking onto identifiers in some set of search fields. All
     * other query operations behave as they should around it.
     * 
     * Parameters: 
     *  id_list - a list of ids to search for
     *  field_list - a list of fields ids to search across
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_ids>
     */ this . set_targets = function ( id_list , field_list ) { var fixed_list = [ ] ; each ( field_list , function ( field ) { fixed_list . push ( _lock_map ( field , id_list ) ) ; } ) ; var sum = fixed_list . join ( ' OR ' ) ; anchor . query = sum ; return anchor . query ; } ; /*
     * Function: get_query
     *
     * Getter for the query variable ('q').
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of extra
     */ this . get_query = function ( ) { return anchor . query ; } ; /*
     * Function: get_default_query
     *
     * Getter for what the query variable 'q' will be set to on a
     * <reset_query>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of the default query
     */ this . get_default_query = function ( ) { return anchor . default_query ; } ; /*
     * Function: get_fundamental_query
     *
     * Getter for what the query variable 'q' will be set to on a
     * <reset_default_query>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of the fundamental default query
     */ this . get_fundamental_query = function ( ) { return anchor . fundamental_query ; } ; /*
     * Function: get_query
     *
     * Getter for the query variable ('q').
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of extra
     */ this . get_query = function ( ) { return anchor . query ; } ; /*
     * Function: reset_query
     *
     * Remove/reset the query variable ('q'); this set it back to the
     * default query.
     *
     * Parameters:
     *  none
     *
     * Returns:
     *  the current value of query
     * 
     * Also see:
     *  <set_default_query>
     *  <reset_default_query>
     */ this . reset_query = function ( ) { anchor . query = anchor . default_query ; ll ( 'reset query to default: ' + anchor . query ) ; return anchor . query ; } ; /*
     * Function: set_extra
     *
     * Setter for the internal string variable to be appended to the
     * end of a query. For special use cases only (e.g. extend
     * functionality of the API safely).
     * 
     * Parameters: 
     *  new_extra - *[optional]* new value for the extras string
     *
     * Returns:
     *  the current setting of extra
     */ this . set_extra = function ( new_extra ) { anchor . query_extra = new_extra ; return anchor . query_extra ; } ; /*
     * Function: get_extra
     *
     * Getter for the internal string variable to be appended
     * to the end of a query.
     *
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of extra
     */ this . get_extra = anchor . set_extra ; /*
     * Function: remove_extra
     *
     * Remove/reset the extra bit.
     *
     * Parameters:
     *  none
     *
     * Returns:
     *  ""
     */ this . remove_extra = function ( ) { anchor . query_extra = "" ; return anchor . query_extra ; } ; /*
     * Function: set
     *
     * Set an internal variable for the query. The internal variables
     * are typically things like 'qt', 'indent', etc.--things that you
     * might set and forget a while. It does /not/ include highly
     * dynamic variables (like callback and packet) or querying
     * variables like 'q' and 'fq'; for those you need to use the API.
     *
     * Parameters: 
     *  key - the name of the parameter to change
     *  new_val - what you want the new value to be
     *
     * Returns:
     *  n/a
     */ this . set = function ( key , new_val ) { anchor . query_variants [ key ] = new_val ; } ; /*
     * Function: get
     *
     * Get an internal variable for the query.
     *
     * See <set> for the kinds of parameters that can be read.
     * 
     * Parameters: 
     *  key - the name of the parameter to get
     *
     * Returns:
     *  The found value of the key.
     */ this . get = function ( key ) { return anchor . query_variants [ key ] ; } ; /*
     * Function: unset
     *
     * Unset (remove) an internal variable for the query. Only usable on certain types of 
     * 
     * Only use is you really know what you're doing.
     *
     * Parameters: 
     *  key - the name of the parameter to unset/remove
     *
     * Returns:
     *  boolean; true false on whether the key was found
     */ this . unset = function ( key ) { var retval = false ; if ( typeof ( anchor . query_variants [ key ] ) !== 'undefined' ) { retval = true ; delete anchor . query_variants [ key ] ; } return retval ; } ; /*
     * Function: include_highlighting
     *
     * Turn hilighting on or off (with true or false).
     * 
     * This essentially adds the parameters to the query string to
     * make sure that basic highlighting on the search is returned.
     * 
     * It starts off as false. The optional html_elt_str argument
     * defaults to:
     *  : <em class="hilite">
     *
     * Parameters: 
     *  hilite_p - *[optional]* boolean
     *  html_elt_str - *[serially optional]* the HTML element string to use
     *
     * Returns:
     *  either false or the current string being used for the return element
     */ this . include_highlighting = function ( hilite_p , html_elt_str ) { var retval = false ; if ( typeof ( hilite_p ) !== 'undefined' && ( hilite_p === true || hilite_p === false ) ) { if ( hilite_p === true ) { // Set the default string if necessary. if ( ! html_elt_str ) { html_elt_str = '<em class="hilite">' ; } // Set the parameters. anchor . set ( 'hl' , 'true' ) ; anchor . set ( 'hl.simple.pre' , html_elt_str ) ; anchor . set ( 'hl.snippets' , anchor . default_hl_snippets ) ; // And the retval is not longer false. retval = html_elt_str ; } else { // Unset the parameters. anchor . unset ( 'hl' ) ; anchor . unset ( 'hl.simple.pre' ) ; anchor . unset ( 'hl.snippets' ) ; } } else { // Otherwise, just discover the current state and return // it. var cl_tmp = anchor . get ( 'hl.simple.pre' ) ; if ( typeof ( cl_tmp ) !== 'undefined' ) { retval = cl_tmp ; } } return retval ; } ; /*
     * Function: set_personality
     *
     * While we are always contacting the same Solr instance, we
     * sometimes want to have different weights, facets, etc. This
     * function allows us to use the pre-set ones defined in the
     * constructor configuration argument.
     * 
     * Currently, this only sets the 'facet.field' internal variable.
     *
     * Parameters: 
     *  personality_id - string
     *
     * Returns:
     *  Will return false if personality doesn't exist
     */ this . set_personality = function ( personality_id ) { var retval = false ; // This sets the facet.field internal variable. var cclass = anchor . _golr_conf . get_class ( personality_id ) ; if ( cclass ) { // Remember what our personality is. // WARNING: this line must go before the query_field_set // line below, or else we won't get the "smart" search. this . _current_class = cclass ; // Set the facets for our class. anchor . facets ( cclass . field_order_by_weight ( 'filter' ) ) ; // Set the query field weights ('qf') necessary to make // queries run properly. anchor . query_field_set ( cclass . get_weights ( 'boost' ) ) ; // Show that we did indeed set a personality. retval = true ; } return retval ; } ; /*
     * Function: get_personality
     *
     * Returns the current personality, null if none.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  Returns the current personality as a string, null if none is set
     */ this . get_personality = function ( ) { var retval = null ; if ( typeof ( anchor . _current_class ) !== 'undefined' && bbop . what_is ( anchor . _current_class ) === 'golr-conf.conf_class' ) { retval = anchor . _current_class . id ( ) ; } return retval ; } ; /*
     * Function: get_query_url
     *
     * Get the current invariant state of the manager returned as a
     * encoded URL string (using encodeURI()).
     * 
     * This means the URL for the current query to the GOlr store, but
     * without extra information about packets, callbacks, and the
     * like.
     * 
     * This is generally appropriate for getting data, but maybe not
     * for things like high-speed autocomplete where races can
     * occur. For those, you might want to consider <update> or
     * <search>.
     *
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  URL string
     * 
     * Also see:
     *  <update>, <search>
     */ this . get_query_url = function ( ) { // Structure of the necessary invariant parts.	 var qurl = anchor . _solr_url + 'select?' ; // Filters to assemble. var assemf = anchor . get_query_filters ( ) ; var fq = anchor . filter_list_to_assemble_hash ( assemf ) ; // Add all of our different specialized hashes. var things_to_add = [ //bbop.get_assemble(anchor.query_invariants), //bbop.get_assemble(anchor.query_facets), bbop . get_assemble ( anchor . query_variants ) , bbop . get_assemble ( anchor . current_facet_field_limits ) , //bbop.get_assemble({'fq': anchor.query_sticky_filters}), bbop . get_assemble ( { 'fq' : fq } ) , bbop . get_assemble ( { 'facet.field' : us . keys ( anchor . facet_fields ) } ) , bbop . get_assemble ( { 'q' : anchor . query } ) , anchor . query_extra ] ; // Add query_fields ('qf') iff query ('q') is set and it is // not length 0. if ( anchor . query && anchor . query . length && anchor . query . length !== 0 && anchor . query !== anchor . fundamental_query ) { var in_qf = bbop . get_assemble ( { 'qf' : anchor . query_field_set ( ) } ) ; things_to_add . push ( in_qf ) ; } // Assemble the assemblies into a single URL, throw out // everything that seems like it isn't real to keep the URL as // clean a possible. var filtered_things = bbop . pare ( things_to_add , function ( item , index ) { var retval = true ; if ( item && item !== '' ) { retval = false ; } return retval ; } ) ; var final_qurl = qurl + filtered_things . join ( '&' ) ; final_qurl = anchor . _query_encode ( final_qurl ) ; ll ( 'qurl: ' + final_qurl ) ; return final_qurl ; } ; /*
     * Function: push_excursion
     *
     * Save the current state of the manager--data and sticky filter
     * information--onto an internal stack. Batch information is not
     * stored.
     * 
     * Useful for gettinginto a state, doing something else, then
     * returning to the original state.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  the number of items on the excursion stack
     * 
     * Also see:
     *  <get_query_url>
     *  <pop_excursion>
     */ this . push_excursion = function ( ) { var now = { // Save current state (data). data_url : anchor . get_query_url ( ) , // Save current state (session). session : { // Get the sticky filters. sticky_filters : anchor . get_sticky_query_filters ( ) } } ; // Save. anchor . _excursions . push ( now ) ; // ... return anchor . _excursions . length ; } ; /*
     * Function: pop_excursion
     *
     * Return to a previously pushed state. Batch items are not
     * recovered.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  boolean on whether a state was recovered
     * 
     * Also see:
     *  <get_query_url>
     *  <gpush_excursion>
     */ this . pop_excursion = function ( ) { var retval = false ; var then = anchor . _excursions . pop ( ) ; if ( then ) { retval = true ; // Recover data state. var then_data_url = then [ 'data_url' ] ; anchor . load_url ( then_data_url ) ; // Recover the session state. var then_session_stickies = then [ 'session' ] [ 'sticky_filters' ] ; // Add the sticky filters. each ( then_session_stickies , function ( sticky ) { var flt = sticky [ 'filter' ] ; var fvl = sticky [ 'value' ] ; var fpl = [ ] ; if ( sticky [ 'negative_p' ] === true ) { fpl . push ( '-' ) ; } if ( sticky [ 'sticky_p' ] === true ) { fpl . push ( '*' ) ; } anchor . add_query_filter ( flt , fvl , fpl ) ; } ) ; } return retval ; } ; /*
     * Function: get_download_url
     *
     * Get the current invariant state of the manager returned as a
     * URL string.
     * 
     * This differs from <get_query_url> in that the generated string
     * is intended for text-processing uses rather than computerized
     * searching uses. The idea where is to create a TSV file for
     * downloading and consumption.
     * 
     * Instead of downloading all of the results, a limited listed set
     * can be downloaded using entity_list, which identifies documents by id.
     * 
     * The optional argument hash looks like:
     *  rows - the number of rows to return; defaults to: 1000
     *  encapsulator - how to enclose whitespace fields; defaults to: ""
     *  separator - separator between fields; defaults to: "%09" (tab)
     *  header - whether or not to show headers; defaults to: "false"
     *  mv_separator - separator for multi-valued fields; defaults to: "|"
     *  entity_list - list of specific download items in results; default null
     * 
     * With the entity list, keep in mind that null and an empty list
     * are handled in pretty much the same way--they are an indication
     * that we are going after nothing specific, and so all results
     * are game.
     * 
     * Parameters:
     *  field_list - a list of fields to return
     *  in_arg_hash - *[optional]* additional optional arguments
     * 
     * Returns:
     *  URL string
     * 
     * Also see:
     *  <get_query_url>
     */ this . get_download_url = function ( field_list , in_arg_hash ) { // Save current state. anchor . push_excursion ( ) ; // Deal with getting arguments in properly. var default_hash = { rows : 1000 , encapsulator : '' , separator : "%09" , header : 'false' , mv_separator : "|" , entity_list : [ ] , golr_download_url : null } ; var arg_hash = bbop . fold ( default_hash , in_arg_hash ) ; // Make the changes we want. anchor . set ( 'wt' , 'csv' ) ; anchor . set ( 'start' , 0 ) ; anchor . set ( 'fl' , field_list . join ( ',' ) ) ; anchor . set ( 'rows' , arg_hash [ 'rows' ] ) ; anchor . set ( 'csv.encapsulator' , arg_hash [ 'encapsulator' ] ) ; anchor . set ( 'csv.separator' , arg_hash [ 'separator' ] ) ; anchor . set ( 'csv.header' , arg_hash [ 'header' ] ) ; anchor . set ( 'csv.mv.separator' , arg_hash [ 'mv_separator' ] ) ; // A little more tricky, jimmy the entity list into the query // if it's viable. var entity_list = arg_hash [ 'entity_list' ] ; if ( typeof ( entity_list ) !== 'undefined' && us . isArray ( entity_list ) && entity_list . length > 0 ) { anchor . set_ids ( entity_list ) ; } // Get url. var returl = anchor . get_query_url ( ) ; // If defined, switch download URL to something else. if ( arg_hash . golr_download_url ) { returl = returl . replace ( anchor . _solr_url , arg_hash . golr_download_url ) ; } // Reset the old state. anchor . pop_excursion ( ) ; return returl ; } ; /*
     * Function: get_filter_query_string
     *
     * Get the current state of the manager, as defined by the current
     * gross filter set--query, sticky filters, and standard filters--
     * returned as a URL query string (sans the '?').
     * 
     * This differs from <get_query_url> and <get_state_url> in that
     * the generated string is intended for applications that may want
     * just enough information to recover filter state when the
     * personality, and other types of information, are already
     * known. It is intended to be part of a light RESTy bookmarking
     * mechanism in larger application.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  query string for current filters (sans the '?')
     * 
     * Also see:
     *  <get_query_url>
     *  <get_state_url>
     */ this . get_filter_query_string = function ( ) { // // Save current state. // anchor.push_excursion(); var q = anchor . get_query ( ) ; // Get the filters and sort them into sticky and "normal" // sets. var filters = anchor . get_query_filters ( ) ; var std_filters = [ ] ; var sticky_filters = [ ] ; each ( filters , function ( filter ) { if ( filter [ 'sticky_p' ] ) { sticky_filters . push ( filter ) ; } else { std_filters . push ( filter ) ; } } ) ; var fq = anchor . filter_list_to_assemble_hash ( std_filters ) ; var sfq = anchor . filter_list_to_assemble_hash ( sticky_filters ) ; var things_to_add = [ ] ; if ( q ) { things_to_add . push ( bbop . get_assemble ( { 'q' : q } ) ) ; } if ( ! us . isEmpty ( fq ) ) { things_to_add . push ( bbop . get_assemble ( { 'fq' : fq } ) ) ; } if ( ! us . isEmpty ( sfq ) ) { things_to_add . push ( bbop . get_assemble ( { 'sfq' : sfq } ) ) ; } // // Reset the old state. // anchor.pop_excursion(); var final_qstr = things_to_add . join ( '&' ) ; final_qstr = anchor . _query_encode ( final_qstr ) ; return final_qstr ; } ; /*
     * Function: get_state_url
     *
     * Get the current invariant state of the manager, plus the
     * current personality as a parameter, returned as a URL string.
     * 
     * This differs from <get_query_url> in that the generated string
     * is intended for applications that may want a little more
     * information and hinting over just what the current search
     * is. This method essentially parameterizes some of the "hidden
     * state" of the manager.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  URL string
     * 
     * Also see:
     *  <get_query_url>
     */ this . get_state_url = function ( ) { // Save current state. anchor . push_excursion ( ) ; // Make the changes we want. First, physically set the // "personality", then set pins for jump-in recovery. anchor . set ( 'personality' , anchor . get_personality ( ) ) ; // Explicitly set sticky pins for later recovery. // Do this pretty much exactly like we do for get_query_url(). var sticky_filters = anchor . get_sticky_query_filters ( ) ; var sfq = anchor . filter_list_to_assemble_hash ( sticky_filters ) ; anchor . set ( 'sfq' , sfq ) ; // Get url. var returl = anchor . get_query_url ( ) ; // Reset the old state. anchor . pop_excursion ( ) ; return returl ; } ; /*
     * Function: load_url
     *
     * Makes a a best attempt to recover the state of a manager from
     * the clues left in a data url. This can also (and probably
     * should) be thought of as a "load bookmark"
     * function. Theoretically, you should even be able to use
     * "bookmarks" from alien installations.
     * 
     * Note that while this recovers enough to get the same data,
     * certain "session"/"preference" type things that are not encoded
     * in the url (e.g. filter stickiness, the contents of batch
     * queues, non-default base queries, etc.) will not be replayed
     * and must be recovered or guessed on an app by app basis..
     * 
     * Warning: this currently only replays a small subset of possible
     * parameters. Currently: personality, q, fq, ???. In the future,
     * this should no all non-session information.
     * 
     * Warning: Because there is more to bookmarks than just the major
     * stuff, variants not supplied in the bookmark will be removed.
     * 
     * This returns true if the parameter portions of the new and
     * bookmark urls match. However, this is often not the case--think
     * shifting personalities, etc.
     * 
     * Parameters:
     *  url - A URL string generated by a manager's get_query_url (or similar)
     * 
     * Returns:
     *  boolean
     */ this . load_url = function ( url ) { // // Some Regexps that would be nice to just compile once. // var regexp_url_space = /\%20/g; // '%20' === ' ' // var regexp_url_quote = /\%22/g; // '%22' === '"' // var regexp_url_left_paren = /\%28/g; // '%28' === '(' // var regexp_url_right_paren = /\%29/g; // '%29' === ')' // We are assuming that we are consuming our own URLs from // get_query_url(), so we start by attempting to decode it // (TODO: need a tab watch here)? var decoded_url = decodeURI ( url ) ; // Break down url. var in_params = bbop . url_parameters ( decoded_url ) ; // First, look for the personality setting and invoke it if // it's there--it will dominate unless we take care of it first. // Also note the all the keys that we see (for later erasure // of excess). var seen_params = { } ; each ( in_params , function ( ip ) { var key = ip [ 0 ] ; var val = ip [ 1 ] ; if ( key === 'personality' && val && val !== '' ) { anchor . set_personality ( val ) ; } seen_params [ key ] = true ; } ) ; // Now cycle through the the parameters again and invoke the // appropriate functions to bring them in line. var sticky_cache = { } ; each ( in_params , function ( ip ) { var key = ip [ 0 ] ; var val = ip [ 1 ] ; if ( typeof ( val ) !== 'undefined' && val !== '' ) { if ( key === 'personality' ) { // Already did it, skip. } else if ( key === 'q' ) { anchor . set_query ( val ) ; } else if ( key === 'fq' || key === 'sfq' ) { // Split the fq (or sfq) parameter. var fnv = bbop . first_split ( ':' , val ) ; var fname = fnv [ 0 ] ; var fval = fnv [ 1 ] ; //ll('HERE: fname: ' + fname); //ll('HERE: fval: ' + fval); if ( fname && fval ) { var plist = [ ] ; // Remove leading sign on a filter and // add it to the plist. var lead_char = fname . charAt ( 0 ) ; if ( lead_char === '-' || lead_char === '+' ) { plist . push ( lead_char ) ; fname = fname . substr ( 1 , fname . length - 1 ) ; } // // TODO:  // // If the fval looks like it has not been // // decoded (like from a URL-safe // // bookmark), go ahead and do so. // fval = fval.replace(regexp_url_space, ' '); // fval = fval.replace(regexp_url_quote, '"'); // fval = fval.replace(regexp_url_left_paren, '('); // fval = fval.replace(regexp_url_right_paren,')'); // Do not allow quotes in--they will be // added by the assembler. fval = bbop . dequote ( fval ) ; // Make it sticky it it came in on "sfq". // Note if this is the sticky form. var skey = fname + '^' + fval ; if ( key === 'sfq' ) { sticky_cache [ skey ] = true ; plist . push ( '*' ) ; } // Add the query filter properly, but // only if we have not already added the // sticky form (prevent clobbering). if ( ! bbop . is_defined ( sticky_cache [ skey ] ) || key === 'sfq' ) { anchor . add_query_filter ( fname , fval , plist ) ; } } } else if ( key === 'qf' ) { // qf is handles a little strangely... var foo = bbop . first_split ( '^' , val ) ; //ll('qf: key: '+ key +', val: '+ val +', foo: '+ foo); anchor . add_query_field ( foo [ 0 ] , foo [ 1 ] ) ; } else if ( key === 'facet.field' ) { anchor . facets ( val ) ; } else if ( key === 'start' || key === 'rows' ) { // Numbers need to be handled carefully. if ( bbop . what_is ( val ) === 'string' ) { val = parseFloat ( val ) ; } anchor . set ( key , val ) ; } else { // This one catches all of the non-special // parameters and resets them using .set(). anchor . set ( key , val ) ; // if( key === 'fq' ){ //     throw new Error("OI");			      // } } } } ) ; // Now go through and remove all of the query variant // parameters that were not seen in the bookmark. each ( anchor . query_variants , function ( val , key ) { if ( typeof ( seen_params [ key ] ) === 'undefined' ) { anchor . unset ( key ) ; } } ) ; // Produce our own url from what we've done. If the parameters // match with the incoming argument's return true. var curr_url = anchor . get_query_url ( ) ; var curr_params = bbop . url_parameters ( curr_url ) ; var differences = 0 ; if ( in_params . length === curr_params . length ) { each ( in_params , function ( in_p , i ) { var curr_p = curr_params [ i ] ; if ( in_p . length === curr_p . length ) { if ( in_p . length === 1 ) { if ( in_p [ 0 ] === curr_p [ 0 ] ) { // match! } else { differences ++ ; } } else if ( in_p . length === 2 ) { if ( in_p [ 0 ] === curr_p [ 0 ] && in_p [ 1 ] === curr_p [ 1 ] ) { // match! } else { differences ++ ; } } } else { differences ++ ; } } ) ; } else { differences ++ ; } // Tally the differences and decides if they're the same. var retval = false ; if ( differences === 0 ) { retval = true ; } return retval ; } ; /*
     * Function: add_to_batch
     *
     * "Save" the current manager state to run later in serial batch
     * mode.
     * 
     * The actual job of running these batches is left to the
     * implementation of the sub-managers; probably in "run_batch".
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  state url
     */ this . add_to_batch = function ( ) { var qurl = anchor . get_query_url ( ) ; anchor . _batch_urls . push ( qurl ) ; return qurl ; } ; /*
     * Function: batch_urls
     *
     * Return a pointer to the current batch urls.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  array
     */ this . batch_urls = function ( ) { return anchor . _batch_urls ; } ; /*
     * Function: next_batch_url
     *
     * Return the next data to be processed, removing it from the
     * batch queue in the process.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  state url or null
     */ this . next_batch_url = function ( ) { return anchor . _batch_urls . shift ( ) || null ; } ; /*
     * Function: reset_batch
     *
     * Clear the currently queued data batch.
     * 
     * The actual job of running these batches is left to the
     * implementation of the sub-managers; probably in "run_batch".
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  the number of items cleared
     */ this . reset_batch = function ( ) { var num = anchor . _batch_urls . length ; anchor . _batch_urls = [ ] ; return num ; } ; }
/*
Package: manager.js

Namespace: bbop.golr.manager

Generic BBOP manager for dealing with gross GOlr configuration and
management. Remember, this is actually a "subclass" of
<bbop.registry>. The defined events for this registry are: "reset",
"search", and "error".

reset - functions for initializing and resetting
search - functions for receiving standard search results
error - functions to call when something goes very wrong

Both <bbop.golr.response> (or clean error data) and the manager
itself (this as anchor) should be passed to the callbacks.

TODO/BUG: <set_query> and <set_default_query> should both take
strings or <bbop.logic> as arguments. Those, as well as <get_query>
and <get_query> should only return <bbop.logic>.
 /*
Constructor: manager

Contructor for the GOlr query manager

Arguments:
golr_loc - string url to GOlr server;
golr_conf_obj - a <bbop.golr.conf> object

Returns:
golr manager object

See also:
<bbop.registry>

A manager for handling the AJAX and registry. Initial take from
{module:bbop-rest-manager}.

@constructor
@param {String} barista_location - string for invariant part of API
@param {String} namespace - string for namespace of API to use
@param {String} user_token - identifying string for the user of the manager (Barista token)
@param {Object} engine - Remote resource manager client to use (must be an instantiated {module:bbop-rest-manager} engine)
@param {String} mode - whether or not to have utility methods (most besides fetch_with and start_with be in); options are "sync" and "async", which correspond to internally using fetch and start respectively
@returns {manager} a classic manager
/ * Package : manager . js