picky.extensions.js���������������������������������������������������������������������������������000644 �000765 �000024 �00000000466 11461010672 015170� 0����������������������������������������������������������������������������������������������������ustar�00admin���������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������Array.prototype.index = function(val) {
  for(var i = 0, l = this.length; i < l; i++) {
    if(this[i] == val) return i;
  }
  return null;
};

Array.prototype.include = function(val) {
  return this.index(val) !== null;
};

Array.prototype.remove = function(index) {
  this.splice(index, 1);
  return this;
};����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./._picky.translations.js���������������������������������������������������������������������������000644 �000765 �000024 �00000000270 11462131244 016055� 0����������������������������������������������������������������������������������������������������ustar�00admin���������������������������staff���������������������������000000 �000000 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������Mac OS X        ����	���2�������������������������������������������������ATTR�=>������������ ���������������������� ��com.macromates.caret�x���R�������<[sk0?'3/«��3+����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������picky.translations.js�������������������������������������������������������������������������������000644 �000765 �000024 �00000002267 11462131244 015513� 0����������������������������������������������������������������������������������������������������ustar�00admin���������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Translations
//
var PickyI18n = { };

// Set the correct locale for all js code.
//
$(function() {
  PickyI18n.locale = $('html').attr('lang').split('-')[0] || 'en';
});

var dictionary = {
  common:{
    join:   { de: 'und', fr: 'et',  it: 'e',    en: 'and',  ch: 'und' },
    'with': { de: 'mit', fr: 'avec',it: 'con',  en: 'with', ch: 'mit' },
    of:     { de: 'von', fr: 'de',  it: 'di',   en: 'of',   ch: 'vo' },
    to:     { de: 'bis', fr: 'à',   it: 'alla', en: 'to',   ch: 'bis' }
  },
  results:{
    addination:{
      more:{
        de: 'Weitere Resultate',
        fr: 'Autres résultats',
        it: 'Altri risultati',
        en: 'More results',
        ch: 'Mee Resultaat'
      }
    },
    header:{
      de: 'Ergebnisse',
      fr: 'Résultats',
      it: 'Risultati',
      en: 'Results',
      ch: 'Ergäbnis'
    }
  }
};
var t = function(key) {
  var locale = PickyI18n.locale || 'en';
  var keys = key.split('.').concat(locale);
  var current = dictionary;

  for (var i = 0, l = keys.length; i < l; i++) {
    current = current[keys[i]];
    if (current == undefined) {
      current = 'Translation missing: ' + key + '.' + locale;
      break;
    }
  };
  return current;
};�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������picky.data.js���������������������������������������������������������������������������������������000644 �000765 �000024 �00000003476 11461010672 013706� 0����������������������������������������������������������������������������������������������������ustar�00admin���������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������// The data is basically the model behind the search.
//

// Container for an allocation.
//
function Allocation(type, weight, count, combination, ids, rendered) {
  var self = this;

  this.type        = type;        // 'books'
  this.weight      = weight;      // 5.14
  this.count       = count;       // 14
  this.combination = combination; // [['title', 'Old', 'old'], ['title', 'Man', 'man']]
  this.ids         = ids || [];
  this.rendered    = rendered || [];
  this.entries     = this.rendered;

  this.isType = function(name) {
    return name == self.type;
  };
};

// Container for the allocations.
//
// allocs (should) come preordered by weight.
//
function Allocations(allocations) {
  var self = this;

  this.allocations = [];

  // Wrap and save the allocations.
  //
  for (var i = 0, l = allocations.length; i < l; i++) {
    var alloc = allocations[i];
    var new_allocation = new Allocation(alloc[0], alloc[1], alloc[2], alloc[3], alloc[4], alloc[5]);
    this.allocations.push(new_allocation);
  }
  this.length = this.allocations.length;
  
  this.each = function(callback) {
    return $.each(this.allocations, callback);
  };
};

// Container for the types.
//
// data:
//   offset: X
//   duration: X
//   total: X
//   allocations:
//     Allocation[] of [weight, count, combination, Entry[] of [id, content]]
//
function PickyData(data) {
  var self = this;

  // Attributes.
  //
  var total       = data.total;
  var duration    = data.duration;
  var offset      = data.offset;
  var allocations = new Allocations(data.allocations || []);

  // Expose some attributes.
  //
  this.total       = total;
  this.duration    = duration;
  this.offset      = offset;
  this.allocations = allocations;
  
  // Are there any results?
  //
  var isEmpty = function() {
    return total == 0;
  };
  this.isEmpty = isEmpty;
  
};
��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������picky.view.js���������������������������������������������������������������������������������������000644 �000765 �000024 �00000012255 11461010672 013742� 0����������������������������������������������������������������������������������������������������ustar�00admin���������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Add PickyResults?
var PickyView = function(picky_controller, config) {
  
  var controller       = picky_controller;
  
  var showResultsLimit = config.showResultsLimit || 10;
  
  var searchField   = $('#picky input.query');
  var clearButton   = $('#picky div.reset');
  var searchButton  = $('#picky input.search_button');
  var resultCounter = $('#picky div.status');
  var dashboard     = $('#picky .dashboard');
  
  var results       = $('#picky .results'); // Push into results.
  var noResults     = $('#picky .no_results');
  
  var addination    = new PickyAddination(this, results); // Push into results.
  
  var allocationsCloud = new PickyAllocationsCloud(this);
  var resultsRenderer  = new PickyResultsRenderer(addination); // TODO Rename results.
  
  // Toggle the clear button visibility.
  //
  var showClearButton = function() {
    clearButton.fadeTo(166, 1.0);
  };
  var hideClearButton = function() {
    clearButton.fadeTo(166, 0.0);
  };
  
  // TODO Move to results
  var clearResults = function() {
    results.empty();
  };
  var hideEmptyResults = function() {
    noResults.hide();
  };
  
  var focus = function() {
    searchField.focus();
  };
  var select = function() {
    searchField.select();
  };
  
  // Cleans the interface of any results or choices presented.
  //
  var clean = function() {
    allocationsCloud.hide();
    clearResults();
    hideEmptyResults();
  };
  
  // Resets the whole view to the inital state.
  //
  var reset = function(to_text) {
    searchField.val(to_text);
    hideClearButton();
    setSearchStatus('empty');
    resultCounter.empty();
    clean();
  };
  
  var bindEventHandlers = function() {
    searchField.keyup(function(event) {
      if (isTextEmpty()) {
        reset();
        controller.searchTextCleared();
      } else {
        controller.searchTextEntered(text(), event);
        showClearButton();
      }
    });
    
    searchButton.click(function(event) {
      if (!isTextEmpty()) {
        controller.searchButtonClicked(text());
      }
    });
    
    clearButton.click(function() {
      reset('');
      controller.clearButtonClicked();
      focus();
    });
  };
  
  var text = function() {
    return searchField.val();
  };
  this.text = text; // TODO Remove.
  var isTextEmpty = function() {
    return text() == '';
  };
  
  var showEmptyResults = function() {
    clean();
    updateResultCounter(0);
    noResults.show();
    showClearButton();
  };
  var showTooManyResults = function(data) {
    clean();
    showClearButton();
    allocationsCloud.show(data);
    updateResultCounter(data.total);
  };
  var showResults = function(data) {
    clean();
    updateResultCounter(data.total);
    resultsRenderer.render(data);
    results.show();
    showClearButton();
  };
  
  var appendResults = function(data) {
    addination.remove(); // TODO Where should this be?
    resultsRenderer.render(data);
    $.scrollTo('#picky .results div.info:last', { duration: 500, offset: -12 });
  };
  
  var updateResultCounter = function(total) {
    resultCounter.text(total); // ((total > 999) ? '999+' : total); // TODO Decide on this.
    flashResultCounter(total);
  };
  
  var alertThreshold = 5;
  var flashResultCounter = function(total) {
    if (total > 0 && total <= alertThreshold) {
      resultCounter.fadeTo('fast', 0.5).fadeTo('fast', 1);
    }
  };
  
  var tooManyResults = function(data) {
    return data.total > showResultsLimit && data.allocations.length > 1;
  };
  var resultStatusFor = function(data) {
    if (data.isEmpty()) { return 'none'; };
    if (tooManyResults(data)) { return 'support'; }
    return 'ok';
  };
  var setSearchStatus = function(statusClass) {
    dashboard.attr('class', 'dashboard ' + statusClass);
  };
  var setSearchStatusFor = function(data) {
    setSearchStatus(resultStatusFor(data));
  };
  
  // Insert a search text into the search field.
  // Field is always selected when doing that.
  //
  var insert = function(text) {
    searchField.val(text);
    select();
  };
  this.insert = insert;
  
  // Callbacks.
  // 
  
  // Full results handling.
  //
  var fullResultsCallback = function(data) {
    setSearchStatusFor(data);
    
    if (data.isEmpty()) {
      showEmptyResults();
    } else if (tooManyResults(data)) {
      showTooManyResults(data);
    } else {
      if (data.offset == 0) {
        showResults(data);
      } else {
        appendResults(data);
      }
    };
    
    focus();
  };
  this.fullResultsCallback = fullResultsCallback;
  
  // Live results handling.
  //
  var liveResultsCallback = function(data) {
    setSearchStatusFor(data);
    
    updateResultCounter(data.total);
  };
  this.liveResultsCallback = liveResultsCallback;
  
  // Callback for when an allocation has been chosen
  // in the allocation cloud.
  //
  var allocationChosen = function(event) {
    var text = event.data.query;
    
    searchField.val(text);
    
    controller.allocationChosen(text);
  };
  this.allocationChosen = allocationChosen;
  
  // Callback for when the addination has been clicked.
  //
  var addinationClicked = function(event) {
    controller.addinationClicked(text(), event);
  };
  this.addinationClicked = addinationClicked;
  
  // 
  //
  bindEventHandlers();
  focus();
};���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������picky.backend.js������������������������������������������������������������������������������������000644 �000765 �000024 �00000005153 11461010672 014356� 0����������������������������������������������������������������������������������������������������ustar�00admin���������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Core search backend.
//
var PickyBackend = function(url) {
  
  // Get returns the data without handling timestamps and whatnot.
  //
  var get = function(query, controllerCallback, offset, specificParams) {
    var params = specificParams || {};
    params = $.extend({ query: query, offset: offset }, specificParams);
    
    // Wrap any data returned in a PickyData object.
    //
    var callback = function(data_hash) {
      if (controllerCallback) { controllerCallback(new PickyData(data_hash)); }
    };
    
    $.ajax({ type: 'GET', url: url, data: params, success: callback, dataType: 'json'});
  };
  
  var search = function(query, controllerCallback, offset, specificParams, specificTimestamps) {
    // Wrap the given callback.
    //
    var callback = function(data) {
      if (controllerCallback) { controllerCallback(specificTimestamps, data); }
    };
    
    get(query, callback, offset, specificParams);
  };
  this.search = search;
};

// Live search backend.
//
var LiveBackend = function(url, callback) {
  var backend = new PickyBackend(url);
  
  var search = function(query, controllerCallback, offset, specificParams, fullTimestamps) {
    var specificTimestamps = fullTimestamps || {};
    
    latestRequestTimestamp = new Date();
    specificTimestamps.live = latestRequestTimestamp;
    
    // Wrap the given callback.
    //
    // Note: Binds the latest request timestamp for later comparison.
    //
    var callback = function(timestamps, data) {
      if (!timestamps.live || timestamps.live == latestRequestTimestamp) {
        if (controllerCallback) { controllerCallback(data); }
      };
    };
    
    // Pass in the timestamp for later comparison.
    //
    backend.search(query, callback, offset, specificParams, specificTimestamps);
  };
  this.search = search;
};

// Full search backend.
//
var FullBackend = function(url) {
  var backend = new PickyBackend(url);
  
  var search = function(query, controllerCallback, offset, specificParams, givenTimestamps) {
    var specificTimestamps = givenTimestamps || {};
    
    latestRequestTimestamp = new Date();
    specificTimestamps.full = latestRequestTimestamp;
    
    // Wrap the given callback.
    //
    // Note: Binds the latest request timestamp for later comparison.
    //
    var callback = function(timestamps, data) {
      if (!timestamps.full || timestamps.full == latestRequestTimestamp) {
        if (controllerCallback) { controllerCallback(data); }
      };
    };
    
    // Pass in the timestamp for later comparison.
    //
    backend.search(query, callback, offset, specificParams, specificTimestamps);
  };
  this.search = search;
};���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������picky.controller.js���������������������������������������������������������������������������������000644 �000765 �000024 �00000006336 11461010672 015156� 0����������������������������������������������������������������������������������������������������ustar�00admin���������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������var PickyController = function(config) {
  
  var view = new PickyView(this, config);
  
  var backends         = config.backends;
  var beforeCallback   = config.before || function(params, query, offset) {  };
  var successCallback  = config.success || function(data, query) {  };
  var afterCallback    = config.after || function(data, query) {  };
  
  // If the given backend cannot be found, ignore the search request.
  //
  var search = function(type, query, callback, offset, specificParams) {
    var currentBackend = backends[type];
    if (currentBackend) { currentBackend.search(query, callback, offset, specificParams); };
  };
  
  var fullSearchCallback = function(data, query) {
    data = successCallback(data, query) || data;
    
    view.fullResultsCallback(data);
    
    afterCallback(data, query);
  };
  var fullSearch = function(query, possibleOffset, possibleParams) {
    var params = possibleParams || {};
    var offset = possibleOffset || 0;
    liveSearchTimer.stop();
    
    params = beforeCallback(params, query, offset) || params;
    
    search('full', query, fullSearchCallback, offset, params);
  };
  var liveSearchCallback = function(data, query) {
    data = successCallback(data, query) || data;
    
    view.liveResultsCallback(data);
    
    afterCallback(data, query);
  };
  var liveSearch = function(query, possibleParams) {
    var params = possibleParams || {};
    
    params = beforeCallback(params) || params;
    
    search('live', query, liveSearchCallback, 0);
  };
  
  // The timer is initially instantly stopped.
  //
  var liveSearchTimer = $.timer(180, function(timer) {
    liveSearch(view.text());
    timer.stop();
  });
  liveSearchTimer.stop();
  
  this.insert = function(query, full) {
    view.insert(query);
    
    if (full) { fullSearch(query); } // TODO
  };
  
  var clearButtonClicked = function() {
    liveSearchTimer.stop();
  };
  this.clearButtonClicked = clearButtonClicked;
  
  var searchTextCleared  = function() {
    liveSearchTimer.stop();
  };
  this.searchTextCleared = searchTextCleared;
  
  var shouldTriggerSearch = function(event) {
    var validTriggerKeys = [
                  0,  // special char (ä ö ü etc...)
                  8,  // backspace
                  13, // enter
                  32, // space
                  46, // delete
                  48, 49, 50, 51, 52, 53, 54, 55, 56, 57, // numbers
                  65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 // a-z
                ];
                
    return $.inArray(event.keyCode, validTriggerKeys) > -1;
  };
  var searchTextEntered = function(text, event) {
    if (shouldTriggerSearch(event)) {
      if (event.keyCode == 13) { fullSearch(text); } else { liveSearchTimer.reset(); }
    }
  };
  this.searchTextEntered = searchTextEntered;
  
  var searchButtonClicked = function(text) {
    fullSearch(text);
  };
  this.searchButtonClicked = searchButtonClicked;
  
  var allocationChosen = function(text) {
    fullSearch(text);
  };
  this.allocationChosen = allocationChosen;
  
  // Move to a view object.
  var addinationClicked = function(text, event) {
    fullSearch(text, event.data.offset);
  };
  this.addinationClicked = addinationClicked;
};��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������picky.client.js�������������������������������������������������������������������������������������000644 �000765 �000024 �00000003603 11461010672 014243� 0����������������������������������������������������������������������������������������������������ustar�00admin���������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������var Localization = {
  // TODO Remove.
  location_delimiters: { de:'in', fr:'à', it:'a', en:'in', ch:'in' },
  
  explanation_delimiters: { de:'und', fr:'et', it:'e', en:'and', ch:'und' }
};

// The client handles parameters and
// offers an insert method.
//
var PickyClient = function(config) {
  
  // Params handling.
  //
  
  // This is used to generate the correct query strings, localized.
  //
  // e.g with locale it:
  // ['title', 'ulysses', 'Ulysses'] => 'titolo:ulysses'
  //
  // This needs to correspond to the parsing in the search engine.
  //
  Localization.qualifiers   = config.qualifiers || {};
  
  // This is used to explain the preceding word in the suggestion text.
  //
  // e.g. with locale it:
  // ['title', 'ulysses', 'Ulysses'] => 'Ulysses (titolo)'
  //
  Localization.explanations = config.explanations || {};
  
  // TODO Explain.
  //
  Localization.explanation_delimiters = { de:'und', fr:'et', it:'e', en:'and', ch:'und' };
  
  // Either you pass it a backends hash with full and live,
  // or you pass it full and live (urls), which will then
  // be wrapped in appropriate backends. 
  //
  var backends = config.backends;
  if (backends) {
    backends.live || alert('Both a full and live backend must be provided.');
    backends.full || alert('Both a full and live backend must be provided.');
  } else {
    config.backends = {
      live: config.live && new LiveBackend(config.live) || alert('A live backend path must be provided.'),
      full: config.full && new FullBackend(config.full) || alert('A live backend path must be provided.')
    };
  }
  
  // The central Picky controller.
  //
  var controller = config.controller && new config.controller(config) || new PickyController(config);
  
  // Insert a query into the client and run it.
  // Default is a full query.
  //
  this.insert = function(query, full) {
    controller.insert(query, full || true);
  };
  
};�����������������������������������������������������������������������������������������������������������������������������picky.addination.js���������������������������������������������������������������������������������000644 �000765 �000024 �00000002162 11461010672 015076� 0����������������������������������������������������������������������������������������������������ustar�00admin���������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������var PickyAddination = function(view, results) {
  
  // Calculate the addination range.
  // 
  var calculateRange = function(data, correction) {
    var correction      = correction || 0;
    var numberOfResults = 20; // Make parametrizable.
    var offset  = data.offset + numberOfResults + correction;
    var end     = offset + numberOfResults;
    var total   = data.total;
    if (total < end) { end = total; }
    return { offset:offset, start:(offset+1), end:end };
  };
  
  // Remove the addination.
  //
  var remove = function() {
    results.find('.addination').remove();
  };
  this.remove = remove;
  
  // Renders the addination;
  //
  var render = function(data) {
    var total = data.total;
    var range = calculateRange(data);
    if (range.offset < total) {
      var result = $("<div class='addination current'>" + t('results.addination.more') + "<div class='tothetop'><a href='javascript:$.scrollTo(0,{ duration: 500});'>&uarr;</a></div></div>");
      result.bind('click', { offset: range.offset }, view.addinationClicked);
      return result;
    } else {
      return '';
    }
  };
  this.render = render;
};��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������picky.results_renderer.js���������������������������������������������������������������������������000644 �000765 �000024 �00000007653 11461010672 016365� 0����������������������������������������������������������������������������������������������������ustar�00admin���������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������var PickyResultsRenderer = function(addination) {
  
  // Adds asterisks to the last token.
  //
  var no_asterisks = ['street_number', 'zipcode']; // TODO Works. Parametrize!
  var asteriskifyLastToken = function(combination) {
    var last_part = combination[combination.length-1];
    var parts = combination.slice(0, combination.length-1);
    if (parts == []) { parts = [parts]; }
    if (!no_asterisks.include(last_part[0])) {
      // Replace with * unless there is already one or a tilde.
      //
      if (last_part[1].match(/[^\*~]$/)) { last_part[1] += '*'; }
    }
    parts.push(last_part);
    return parts;
  };
  
  // Replaces the category with an explanation of the category.
  //
  var explainCategory = function(combination) {
    var explanations = Localization.explanations && Localization.explanations[PickyI18n.locale] || {}; // TODO
    var parts = [];
    var combo;
    
    for (var i = 0, l = combination.length; i < l; i++) {
      combo = combination[i];
      var explanation = combo[0];
      explanation = explanations[explanation] || explanation;
      parts.push([explanation, combo[1]]);
    }
    return parts;
  };
  
  //
  //
  var explain = function(type, combination) {
    var explanation_delimiter = Localization.explanation_delimiters[PickyI18n.locale];
    
    var parts = explainCategory(asteriskifyLastToken(combination));
    var replaced = $.map(parts, function(part) {
      var category = part[0].replace(/([\w\sÄäÖöÜüéèà]+)/, "<strong>$1</strong>");
      var token    = part[1];
      return [category, token].join(' ');
    });
    replaced = replaced.join(' ' + explanation_delimiter + ' ');
    
    return '<div class="explanation">' + type + ' ' + replaced + '</div>';
  };
  
  // TODO Make customizable.
  //
  var renderHeader = function(data, allocation) {
    // TODO Make type definable. (Mapping, i18n)
    //
    var header_html = '<div class="info">';
    header_html += explain(allocation.type, allocation.combination);
    if (data.offset > 0) {
      header_html += '<div class="tothetop"><a href="javascript:$.scrollTo(0,{ duration: 500 });">&uarr;</a></div>'; // searchEngine.focus();
    }
    
    // TODO Parametrize!
    // var names = '';
    // var firstEntryName = $(allocation.entries[0]).find('.name').html();
    // var lastEntryName = $(allocation.entries[allocation.entries.length-1]).find('.name').html();
    // if(firstEntryName == lastEntryName) {
    //   var firstEntryFirstName = $(allocation.entries[0]).find('.first_name').html();
    //   var lastEntryFirstName = $(allocation.entries[allocation.entries.length-1]).find('.first_name').html();
    //   names = '<div class="names">' + firstEntryName + ', ' + firstEntryFirstName + ' ' + t('common.to') + ' ' + lastEntryFirstName + '</div>';
    // }
    // else {
    //   names = '<div class="names">' + firstEntryName + ' ' + t('common.to') + ' ' + lastEntryName + '</div>';
    // }
    
    // var rangeStart = data.offset + 1;
    // var rangeEnd = data.offset + allocation.entries.length;
    // var rangeText = (rangeStart == rangeEnd) ? rangeStart : rangeStart + '-' + rangeEnd;
    // var range = '<div class="range">' + rangeText + ' ' + t('common.of') + ' ' + data.total + '</div>';
    
    // if (data.total > 20) { // TODO Make settable.
    //   // header_html += '<div class="clear"></div>'; // TODO
    //   // header_html += names; // TODO
    //   // header_html += range; // TODO
    // }
    
    // For smooth addination scrolling. Don't ask.
    //
    header_html += '<div class="clear"></div></div>';
    
    return header_html;
  };
  
  // Render results with the data.
  //
  this.render = function(data) {
    var results = $('#picky .results'); // TODO Extract, also from view.
    data.allocations.each(function(i, allocation) {
      results.append(renderHeader(data, allocation)) // TODO header.render(data);
             .append(allocation.entries.join(''))
             .append(addination.render(data));
    });
  };
};�������������������������������������������������������������������������������������./._picky.allocation_renderer.js��������������������������������������������������������������������000644 �000765 �000024 �00000000272 11461010672 017351� 0����������������������������������������������������������������������������������������������������ustar�00admin���������������������������staff���������������������������000000 �000000 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������Mac OS X        ����	���2�������������������������������������������������ATTR�=>������������"����������������������"��com.macromates.caret�{
    column = 87;
    line = 5;
}��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������picky.allocation_renderer.js������������������������������������������������������������������������000644 �000765 �000024 �00000021243 11461010672 017000� 0����������������������������������������������������������������������������������������������������ustar�00admin���������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������function AllocationRenderer(allocationChosenCallback) {
  var self = this;

  var locale                = PickyI18n.locale;
  
  var qualifiers            = Localization.qualifiers && Localization.qualifiers[locale] || {};
  var explanations          = Localization.explanations && Localization.explanations[locale] || {};
  var location_delimiter    = Localization.location_delimiters[locale];
  var explanation_delimiter = Localization.explanation_delimiters[locale];
  
  // Those are of interest to the public.
  //
  this.text = '';
  this.query = '';
  this.explanation = '';
  
  // TODO parametrize.
  //
  var no_ellipses           = ['street_number', 'zipcode'];
  
  // Contracts the originals of the zipped.
  //
  function contract(zipped) {
    var hash = {}; // remembers the values
    var insert = {}; // remembers the insertion locations
    var remove = []; // remembers the remove indexes
    var i;
    for (i = 0, l = zipped.length; i < l; i++) {
      var key = zipped[i][0];
      if (key in hash) {
        hash[key] = hash[key] + ' ' + zipped[i][1];
        remove.push(i);
      } else {
        hash[key] = zipped[i][1];
        insert[i] = key;
      }
    }
    // Insert the ones from the hash.
    for (i in insert) {
      zipped[i][1] = hash[insert[i]];
    }
    // Remove the ones from zipped we don't like. From the end.
    for (i = remove.length-1; i >= 0; i--) {
      zipped.remove(remove[i]);
    }
    return zipped;
  };
  this.contract = contract;
  
  // TODO Parametrize!
  var specialWhoCases = {
    // Use the actual methods, not strings.
    "maiden_name" : { format:"(-%1$s)", method:'capitalize', ignoreSingle:true },
    "name"        : { format:"<strong>%1$s</strong>", method:'toUpperCase', ignoreSingle:true },
    "first_name"  : { format:"%1$s", method:"capitalize" }
  };
  function handleWho(both, singleParam) {
    var single = singleParam || false;
    var allocation = both[0];
    var word       = both[1];

    var formatting = specialWhoCases[allocation];
    if (formatting) {
      if (formatting.method) { word = word[formatting.method](); }
      if (formatting.format) { word = formatting.format.replace(/%1\$s/, word); }
    }
    var explanation = explanations[allocation] || allocation;
    if (single && !(formatting && formatting.ignoreSingle)) { return word + '&nbsp;(' + explanation + ')'; }

    return word;
  }
  // Handles the first (who) part.
  //
  // Rules:
  //  * If there is no thing, do return an empty string.
  //  * If there is only one thing, add an explanation.
  //  * If there are multiple things, handle special cases.
  //    Without special cases, the name is always in front.
  //    The things are separated by commas, and explained.
  //    If there are multiple instances of the same category, they are contracted.
  //
  //  Note: &nbsp; to not disconnect the explanation from the query text.
  //
  function who(zipped) {
    if (zipped.length == 0) {
      return '';
    } else if (zipped.length == 1) {
      return handleWho(zipped[0], true);
    } else {
      // else, sort, special cases etc.
      var result = [];
      var append = [];
      zipped = contract(zipped);
      for (var i = 0, l = zipped.length; i < l; i++) {
        if (zipped[i][0] == 'first_name') {
          result.unshift(handleWho(zipped[i])); // prepend the first name
        } else {
          if (zipped[i][0] == 'maiden_name') {
            append.push(handleWho(zipped[i]));
          } else {
            result.push(handleWho(zipped[i]));
          }
        }
      };
      if (append.length > 0) { result.push(append); };
      return result.join(' ');
    }
  }
  this.who = who;

  function replacerFor(zipped) {
    return function(_, category) {
      for (var i = 0, l = zipped.length; i < l; i++) {
        if (zipped[i][0] == category) { return zipped[i][1]; };
      };
      return '';
    };
  };

  // Handles the second (where) part.
  //
  // Rules:
  //  * If there is no location, do return an empty string.
  //  * If there is only a zipcode, add a [<city explanation>].
  //  * If there is only a city, add nothing.
  //  * If there are both, zipcode needs to be first.
  //  TODO Contraction of multiple "cities" and/or zipcode.
  //
  var locations = {
    'zipcode':(':zipcode [' + explanations.city + ']'),
    'city':':city',
    'city,zipcode':':zipcode :city'
  };
  function where(zipped) {
    if (zipped.length == 0) { return ''; };
    zipped = contract(zipped);
    var key_ary = zipped;
    key_ary.sort(function(zipped1, zipped2) {
      return zipped1[0] < zipped2[0] ? -1 : 1;
    });
    // Now that it's sorted, get the right string.
    var key = [];
    for (var i = 0, l = key_ary.length; i < l; i++) {
      key.push(key_ary[i][0]);
    };
    var loc = locations[key];
    // Replace inside string.
    var result = loc.replace(/:(zipcode|city)/g, replacerFor(zipped));
    return result;
  };
  this.where = where;

  function handleSingleWhat(both) {
    var allocation = both[0];
    var word       = both[1];
    
    var explanation = explanations[allocation] || allocation;
    
    return word + '&nbsp;(' + explanation + ')';
  }
  function what(zipped) {
    if (zipped.length == 0) { return ''; };

    result = [];
    zipped = contract(zipped);
    for (var i = 0, l = zipped.length; i < l; i++) {
      result.push(handleSingleWhat(zipped[i]));
    }

    return result.join(', ');
  };
  this.what = what;

  // Orders the allocation identifiers according to
  // [<who>, <what>, <where>]
  // Returns a reordered array.
  //
  // TODO Rename "group".
  //
  var who_qualifiers = ['first_name', 'name', 'maiden_name'];
  var where_qualifiers = ['zipcode', 'city'];
  function trisect(zipped) {
    var who_parts = [];
    var what_parts = [];
    var where_parts = [];

    for (var i = 0, l = zipped.length; i < l; i++) {
      var combination = zipped[i];
      if (where_qualifiers.include(combination[0])) {
        where_parts.push(combination);
      } else if (who_qualifiers.include(combination[0])) {
        who_parts.push(combination);
      } else {
        what_parts.push(combination);
      }
    }

    // Ellipsisize the last part
    var alloc_part;
    if (where_parts.length > 0) {
      alloc_part = where_parts[where_parts.length-1];
    } else if (what_parts.length > 0) {
      alloc_part = what_parts[what_parts.length-1];
    } else if (who_parts.length > 0) {
      alloc_part = who_parts[who_parts.length-1];
    } // always results in a part
    if (!no_ellipses.include(alloc_part[0])) { alloc_part[1] += '...'; } // TODO *

    var rendered_who   = who(who_parts);
    var rendered_what  = what(what_parts);
    var rendered_where = where(where_parts);
    return [rendered_who, rendered_what, rendered_where];
  };
  this.trisect = trisect;

  // Fuses a possible who part to a possible what part to a possible where part.
  //
  // e.g. <who>, <what> in <where>
  //
  // Note: &nbsp; to not disconnect the location delimiter (e.g. "in") from the location.
  //
  // TODO Parametrize!
  //
  var who_what_join      = ', ';
  var whowhat_where_join = ' ' + location_delimiter + '&nbsp;';
  function fuse(parts) {
    var who = parts[0], what = parts[1], where = parts[2];
    var who_what = '';
    if (who != '') {
      if (what != '') { who_what = [who, what].join(who_what_join); } else { who_what = who; }
    } else {
      who_what = what;
    }
    if (where == '') { return who_what; };
    return [who_what, where].join(whowhat_where_join);
  };
  this.fuse = fuse;

  // Creates a query string from combination and originals.
  //
  function querify(zipped) {
    var query_parts = [];
    var qualifier;
    for (var i in zipped) {
      qualifier = zipped[i][0];
      qualifier = qualifiers[qualifier] || qualifier; // Use the returned qualifier if none is given.
      query_parts[i] = qualifier + ':' + zipped[i][1];
    };
    return query_parts.join(' ');
  };
  this.querify = querify;

  //
  //
  function suggestify(zipped) {
    return fuse(trisect(zipped));
  };


  // Generates the text and the link.
  //
  var generate = function() {
    this.query       = querify(combination);
    this.text        = suggestify(combination);
    
    return self;
  };
  
  //
  //
  var listItem = function(text, count) {
    return $('<li><div class="text">' + text + '</div><div class="count">' + count + '</div></li>');
  };
  
  var render = function(allocation) {
    var combination = allocation.combination;
    var type        = allocation.type;
    var count       = allocation.count;
    
    var query       = querify(combination);
    
    var item = listItem(suggestify(combination), count);
    
    // TODO Move this outwards?
    //
    item.bind('click', { query: query }, allocationChosenCallback);
    return item;
  };
  this.render = render;

};�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������picky.allocations_cloud.js��������������������������������������������������������������������������000644 �000765 �000024 �00000004204 11461010672 016461� 0����������������������������������������������������������������������������������������������������ustar�00admin���������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������var PickyAllocationsCloud = function(view) {
  
  var allocations         = $('#picky .allocations');
  var shownAllocations    = allocations.find('.shown');
  var showMoreAllocations = allocations.find('.more');
  var hiddenAllocations   = allocations.find('.hidden');
  
  // Show the cloud.
  //
  var show = function(data) {
    render(data.allocations);
    allocations.show();
  };
  // Hide the cloud.
  //
  var hide = function() {
    allocations.hide();
  };
  
  var clearAllocationCloud = function() {
    shownAllocations.empty();
    showMoreAllocations.hide();
    hiddenAllocations.empty().hide();
  };
  
  // 
  //
  var allocationChosen = function(event) {
    hide();
    view.allocationChosen(event);
  };
  
  var allocationRenderer = new AllocationRenderer(allocationChosen);
  
  var createAllocationList = function(allocations) {
    var shown = [];
    allocations.each(function(i, allocation) {
      shown.push(allocationRenderer.render(allocation));
      
      // // TODO Combine.
      // allocationRenderer.generate();
      // var listItem = renderListItem(allocationRenderer);
      
      // shown.push(listItem);
    });
    return shown;
  };
  
  var renderList = function(list) {
    if (list.length == 0) {
      return $('#search .allocations').hide();
    }
    var maxSuggestions = 3;
    clearAllocationCloud();
    
    if (list.length > maxSuggestions) {
      $.each(list.slice(0,maxSuggestions-1), function(i, item) {
        shownAllocations.append(item);
      });
      $.each(list.slice(maxSuggestions-1), function(i, item) {
        hiddenAllocations.append(item);
      });
      showMoreAllocations.show();
    }
    else {
      $.each(list, function(i, item) {
        shownAllocations.append(item);
      });
    }
    return $('#search .allocations').show();
  };
  
  // Render the allocation list.
  //
  var render = function(allocations) {
    renderList(createAllocationList(allocations));
  };
  
  // Install handlers.
  //
  showMoreAllocations.click(function() {
    showMoreAllocations.hide();
    hiddenAllocations.show();
  });
  
  // Expose hide and show.
  //
  this.hide = hide;
  this.show = show;
  
};������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������