API Docs for:
Show:

File: app/store/charm.js

'use strict';

/**
 * Provide the CharmStore class.
 *
 * @module store
 * @submodule store.charm
 */

YUI.add('juju-charm-store', function(Y) {
  var ns = Y.namespace('juju'),
      models = Y.namespace('juju.models');

  /**
   * The CharmStore class.
   *
   * @class CharmStore
   */
  var CharmStore = Y.Base.create('charm', Y.Base, [], {

    loadByPath: function(path, options) {
      this.get('datasource').sendRequest({
        request: path,
        callback: {
          success: function(io_request) {
            options.success(
                Y.JSON.parse(io_request.response.results[0].responseText));
          },
          failure: options.failure
        }
      });
    },

    /**
     * @method find
     * @param {string} query Either a string that is passed directly to the
     *   search url, or a hash that is marshalled to the correct format (e.g.,
     *   {series:precise owner:charmers}).
     * @return {Object} CharmId instances grouped by series and ordered
     *   within the groups according to the CharmId compare function.
     */
    find: function(query, options) {
      if (!Y.Lang.isString(query)) {
        var operator = query.op || 'intersection',
            join_string = {union: ' OR ', intersection: ' '}[operator],
            tmp = [];
        delete query.op;
        if (!Y.Lang.isValue(join_string)) {
          throw 'Developer error: unknown operator ' + operator;
        }
        Y.each(query, function(val, key) {
          if (Y.Lang.isString(val)) {
            val = [val];
          }
          Y.each(val, function(v) {
            tmp.push(key + ':' + v);
          });
        });
        query = escape(tmp.join(join_string));
      }
      this.get('datasource').sendRequest({
        request: 'search/json?search_text=' + query,
        callback: {
          'success': Y.bind(function(io_request) {
            // To see an example of what is being obtained, look at
            // http://jujucharms.com/search/json?search_text=mysql .
            var result_set = Y.JSON.parse(
                io_request.response.results[0].responseText);
            options.success(
                this._normalizeCharms(
                result_set.results, options.list, options.defaultSeries));
          }, this),
          'failure': options.failure
        }});
    },

    /**
     * Convert the charm data into Charm instances, using only id and
     * relevance.  Group them into series.  The series are arranged with first
     * the defaultSeries, if any, and then all other available series arranged
     * from newest to oldest. Within each series, official charms come first,
     * sorted by relevance if available and package name otherwise; and then
     * owned charms follow, sorted again by relevance, if available, and
     * package name otherwise.
     *
     * @method _normalizeCharms
     */
    _normalizeCharms: function(results, list, defaultSeries) {
      var hash = {},
          relevances = {};
      Y.each(results, function(result) {
        var charm = list.getById(result.store_url);
        if (!charm) {
          charm = list.add(
              { id: result.store_url, summary: result.summary,
                is_subordinate: result.subordinate});
        }
        var series = charm.get('series');
        if (!Y.Lang.isValue(hash[series])) {
          hash[series] = [];
        }
        hash[series].push(charm);
        relevances[charm.get('id')] = result.relevance;
      });
      var series_names = Y.Object.keys(hash);
      series_names.sort(function(a, b) {
        if (a === defaultSeries && b !== defaultSeries) {
          return -1;
        } else if (a !== defaultSeries && b === defaultSeries) {
          return 1;
        } else {
          return -a.localeCompare(b);
        }
      });
      return Y.Array.map(series_names, function(name) {
        var charms = hash[name];
        charms.sort(function(a, b) {
          return a.compare(
              b, relevances[a.get('id')], relevances[b.get('id')]);
        });
        return {series: name, charms: hash[name]};
      });
    }
  }, {
    ATTRS: {
      datasource: {
        setter: function(val) {
          if (Y.Lang.isString(val)) {
            val = new Y.DataSource.IO({ source: val });
          }
          return val;
        }
      }
    }
  });
  Y.namespace('juju').CharmStore = CharmStore;


  /**
   * Api helper for the updated charmworld api v0.
   *
   * @class Charmworld0
   * @extends {Base}
   *
   */
  ns.Charmworld0 = Y.Base.create('charmworld0', Y.Base, [], {
    _apiRoot: 'api/0/',

    /**
     * Send the actual request and handle response from the api.
     *
     * @method _makeRequest
     * @param {Object} args any query params and arguments required.
     * @private
     *
     */
    _makeRequest: function(apiEndpoint, callbacks, args) {
      // Any query string args need to be put onto the endpoint for calling.
      if (args) {
        apiEndpoint = apiEndpoint + '?' + Y.QueryString.stringify(args);
      }

      this.get('datasource').sendRequest({
        request: apiEndpoint,
        callback: {
          success: function(io_request) {
            var res = Y.JSON.parse(
                io_request.response.results[0].responseText
                );
            callbacks.success(res);
          },

          'failure': function(io_request) {
            var respText = io_request.response.results[0].responseText,
                res;
            if (respText) {
              res = Y.JSON.parse(respText);
            }
            callbacks.failure(res, io_request);
          }
        }
      });
    },

    /**
     * Api call to fetch a charm's details.
     *
     * @method charm
     * @param {String} charmID the charm to fetch.
     * @param {Object} callbacks the success/failure callbacks to use.
     * @param {Object} bindScope the scope of *this* in the callbacks.
     *
     */
    charm: function(charmID, callbacks, bindScope) {
      var endpoint = 'charm/' + charmID;
      if (bindScope) {
        callbacks.success = Y.bind(callbacks.success, bindScope);
        callbacks.failure = Y.bind(callbacks.failure, bindScope);
      }

      this._makeRequest(endpoint, callbacks);
    },

    /**
     * Api call to search charms
     *
     * @method search
     * @param {String} text the search text.
     * @param {Object} callbacks the success/failure callbacks to use.
     * @param {Object} bindScope the scope of *this* in the callbacks.
     */
    search: function(text, callbacks, bindScope) {
      var endpoint = 'charms';
      if (bindScope) {
        callbacks.success = Y.bind(callbacks.success, bindScope);
        callbacks.failure = Y.bind(callbacks.failure, bindScope);
      }
      this._makeRequest(endpoint, callbacks, {text: text});
    },

    /**
     * Fetch the contents of a charm's file.
     *
     * @method file
     * @param {String} charmID the id of the charm's file we want.
     * @param {String} filename the path/name of the file to fetch content.
     * @param {Object} callbacks the success/failure callbacks.
     * @param {Object} bindScope the scope for this in the callbacks.
     *
     */
    file: function(charmID, filename, callbacks, bindScope) {
      var endpoint = 'charm/' + charmID + '/file/' + filename;
      if (bindScope) {
        callbacks.success = Y.bind(callbacks.success, bindScope);
        callbacks.failure = Y.bind(callbacks.failure, bindScope);
      }

      this.get('datasource').sendRequest({
        request: endpoint,
        callback: {
          success: function(io_request) {
            callbacks.success(io_request.response.results[0].responseText);
          },
          'failure': function(io_request) {
            var respText = io_request.response.results[0].responseText,
                res;
            if (respText) {
              res = Y.JSON.parse(respText);
            }
            callbacks.failure(res, io_request);
          }
        }
      });
    },

    /**
     * Load the QA data for a specific charm.
     *
     * @method qa
     * @param {String} charmID the charm to fetch qa data for.
     * @param {Object} callbacks the success/failure callbacks to use.
     * @param {Object} bindScope the scope for 'this' in the callbacks.
     *
     */
    qa: function(charmID, callbacks, bindScope) {
      var endpoint = 'charm/' + charmID + '/qa';
      if (bindScope) {
        callbacks.success = Y.bind(callbacks.success, bindScope);
        callbacks.failure = Y.bind(callbacks.failure, bindScope);
      }
      this._makeRequest(endpoint, callbacks);
    },

    /**
     * Given a result list, turn that into a BrowserCharmList object for the
     * application to use.
     *
     * @method _resultsToCharmlist
     * @param {Object} JSON decoded data from response.
     * @private
     *
     */
    resultsToCharmlist: function(data) {
      return new Y.juju.models.BrowserCharmList({
        items: data
      });
    },

    /**
     * Initialize the API helper. Constructs a reusable datasource for all
     * calls.
     *
     * @method initializer
     * @param {Object} cfg configuration object.
     *
     */
    initializer: function(cfg) {
      // @todo this isn't set on initial load so we have to manually hit the
      // setter to get datasource filled in. Must be a better way.
      this.set('apiHost', cfg.apiHost);
    },

    /**
     * Fetch the interesting landing content from the charmworld api.
     *
     * @method interesting
     * @return {Object} data loaded from the api call.
     *
     */
    interesting: function(callbacks, bindScope) {
      if (bindScope) {
        callbacks.success = Y.bind(callbacks.success, bindScope);
        callbacks.failure = Y.bind(callbacks.failure, bindScope);
      }

      this._makeRequest('charms/interesting', callbacks);
    }
  }, {
    ATTRS: {
      /**
       * Required attribute for the host to talk to for api calls.
       *
       * @attribute apiHost
       * @default undefined
       * @type {String}
       *
       */
      apiHost: {
        required: true,
        setter: function(val) {
          // Make sure we update the datasource if our apiHost changes.
          var source = val + this._apiRoot;
          this.set('datasource', new Y.DataSource.IO({ source: source }));
          return val;
        }
      },

      /**
       * Auto constructed datasource object based on the apiHost attribute.
       * @attribute datasource
       * @type {Datasource}
       *
       */
      datasource: {}
    }
  });

}, '0.1.0', {
  requires: [
    'datasource-io',
    'json-parse',
    'juju-charm-models',
    'querystring-stringify'
  ]
});