~ya-bo-ng/juju-gui/bottom-bar-removed

« back to all changes in this revision

Viewing changes to app/models/charm.js

  • Committer: Nicola Larosa
  • Date: 2013-01-30 17:55:41 UTC
  • mfrom: (357.1.3 more-api-docs)
  • Revision ID: nicola.larosa@canonical.com-20130130175541-8z7a03bqx7tsw3oq
Improve the API docs.

Improve the HTML output of the API docs, and remove most warnings
output by the YUIDoc compiler. There should be no more than two.

Reviewers, you may want to start at the end: look at the changes
for docs/style-guide.rst, where you'll find the raionale for most
of the other changes.

Sorry for the big diff. The indentation changes in
app/models/charm.js are courtesy of the beautifier.

R=bac, gary.poster
CC=
https://codereview.appspot.com/7221072

Show diffs side-by-side

added added

removed removed

Lines of Context:
7
7
 *
8
8
 * Charm instances can represent both environment charms and charm store
9
9
 * charms.  A charm id is reliably and uniquely associated with a given
10
 
 * charm only within a given context--the environment or the charm store.
 
10
 * charm only within a given context: the environment or the charm store.
11
11
 *
12
12
 * Therefore, the database keeps these charms separate in two different
13
 
 * CharmList instances.  One is db.charms, representing the environment
 
13
 * CharmList instances.  One is `db.charms`, representing the environment
14
14
 * charms.  The other, from the charm store, is maintained by and within the
15
 
 * persistent charm panel instance. As you'd expect, environment charms are
16
 
 * what to use when viewing or manipulating the environment.  Charm store
 
15
 * persistent charm panel instance. As you would expect, environment charms
 
16
 * are what to use when viewing or manipulating the environment.  Charm store
17
17
 * charms are what we can browse to select and deploy new charms to the
18
18
 * environment.
19
19
 *
20
20
 * Charms begin their lives with full charm ids, as provided by
21
21
 * services in the environment and the charm store:
22
 
 *
23
 
 * [SCHEME]:(~[OWNER]/)?[SERIES]/[PACKAGE NAME]-[REVISION].
 
22
 
 
23
 *   `[SCHEME]:(~[OWNER]/)?[SERIES]/[PACKAGE NAME]-[REVISION]`
24
24
 *
25
25
 * With an id, we can instantiate a charm: typically we use
26
 
 * "db.charms.add({id: [ID]})".  Finally, we load the charm's data over the
27
 
 * network using the standard YUI Model method "load," providing an object
 
26
 * `db.charms.add({id: [ID]})`.  Finally, we load the charm's data over the
 
27
 * network using the standard YUI Model method `load`, providing an object
28
28
 * with a get_charm callable, and an optional callback (see YUI docs).  Both
29
 
 * the env and the charm store have a get_charm method, so, by design, it
30
 
 * works easily: "charm.load(env, optionalCallback)" or
31
 
 * "charm.load(charm_store, optionalCallback)".  The get_charm method must
 
29
 * the env and the charm store have a `get_charm` method, so, by design, it
 
30
 * works easily: `charm.load(env, optionalCallback)` or
 
31
 * `charm.load(charm_store, optionalCallback)`.  The `get_charm` method must
32
32
 * either callback using the default YUI approach for this code, a boolean
33
33
 * indicating failure, and a result; or it must return what the env version
34
 
 * does: an object with a "result" object containing the charm data, or an
35
 
 * object with an "err" attribute.
 
34
 * does: an object with a `result` object containing the charm data, or an
 
35
 * object with an `err` attribute.
36
36
 *
37
37
 * In both cases, environment charms and charm store charms, a charm's
38
 
 * "loaded" attribute is set to true once it has all the data from its
 
38
 * `loaded` attribute is set to true once it has all the data from its
39
39
 * environment.
40
40
 *
41
41
 * @module charm
44
44
YUI.add('juju-charm-models', function(Y) {
45
45
 
46
46
  var models = Y.namespace('juju.models');
47
 
  var charmIdRe = /^(?:(\w+):)?(?:~(\S+)\/)?(\w+)\/(\S+?)-(\d+)$/,
48
 
      idElements = ['scheme', 'owner', 'series', 'package_name', 'revision'],
49
 
      Charm = Y.Base.create('charm', Y.Model, [], {
50
 
        initializer: function() {
51
 
          var id = this.get('id'),
 
47
  var charmIdRe = /^(?:(\w+):)?(?:~(\S+)\/)?(\w+)\/(\S+?)-(\d+)$/;
 
48
  var idElements = ['scheme', 'owner', 'series', 'package_name', 'revision'];
 
49
 
 
50
  /**
 
51
   * @class Charm
 
52
   */
 
53
  var Charm = Y.Base.create('charm', Y.Model, [], {
 
54
    initializer: function() {
 
55
      var id = this.get('id'),
52
56
              parts = id && charmIdRe.exec(id),
53
57
              self = this;
54
 
          if (!Y.Lang.isValue(id) || !parts) {
55
 
            throw 'Developers must initialize charms with a well-formed id.';
56
 
          }
57
 
          this.loaded = false;
58
 
          this.on('load', function() { this.loaded = true; });
59
 
          parts.shift();
60
 
          Y.each(
61
 
              Y.Array.zip(idElements, parts),
62
 
              function(pair) { self.set(pair[0], pair[1]); });
63
 
          // full_name
64
 
          var tmp = [this.get('series'), this.get('package_name')],
 
58
      if (!Y.Lang.isValue(id) || !parts) {
 
59
        throw 'Developers must initialize charms with a well-formed id.';
 
60
      }
 
61
      this.loaded = false;
 
62
      this.on('load', function() { this.loaded = true; });
 
63
      parts.shift();
 
64
      Y.each(
 
65
          Y.Array.zip(idElements, parts),
 
66
          function(pair) { self.set(pair[0], pair[1]); });
 
67
      // full_name
 
68
      var tmp = [this.get('series'), this.get('package_name')],
65
69
              owner = this.get('owner');
66
 
          if (owner) {
67
 
            tmp.unshift('~' + owner);
68
 
          }
69
 
          this.set('full_name', tmp.join('/'));
70
 
          // charm_store_path
71
 
          this.set(
72
 
              'charm_store_path',
73
 
              [(owner ? '~' + owner : 'charms'),
74
 
               this.get('series'),
75
 
               (this.get('package_name') + '-' + this.get('revision')),
76
 
               'json'
77
 
              ].join('/'));
78
 
        },
79
 
        sync: function(action, options, callback) {
80
 
          if (action !== 'read') {
81
 
            throw (
82
 
                'Only use the "read" action; "' + action + '" not supported.');
83
 
          }
84
 
          if (Y.Lang.isValue(options.get_charm)) {
85
 
            // This is an env.
86
 
            options.get_charm(
87
 
                this.get('id'),
88
 
                function(response) {
89
 
                  if (response.err) {
90
 
                    callback(true, response);
91
 
                  } else if (response.result) {
92
 
                    callback(false, response.result);
93
 
                  } else {
94
 
                    // What's going on?  This does not look like either of our
95
 
                    // expected signatures.  Declare a loading error.
96
 
                    callback(true, response);
97
 
                  }
98
 
                }
99
 
            );
100
 
          } else if (Y.Lang.isValue(options.loadByPath)) {
101
 
            // This is a charm store.
102
 
            options.loadByPath(
103
 
                this.get('charm_store_path'),
104
 
                { success: function(response) {
105
 
                  callback(false, response);
106
 
                },
107
 
                failure: function(response) {
108
 
                  callback(true, response);
109
 
                }
110
 
                });
111
 
          } else {
112
 
            throw 'You must supply a get_charm or loadByPath function.';
113
 
          }
114
 
        },
115
 
        parse: function() {
116
 
          var data = Charm.superclass.parse.apply(this, arguments),
 
70
      if (owner) {
 
71
        tmp.unshift('~' + owner);
 
72
      }
 
73
      this.set('full_name', tmp.join('/'));
 
74
      // charm_store_path
 
75
      this.set(
 
76
          'charm_store_path',
 
77
          [(owner ? '~' + owner : 'charms'),
 
78
           this.get('series'),
 
79
           (this.get('package_name') + '-' + this.get('revision')),
 
80
           'json'
 
81
          ].join('/'));
 
82
    },
 
83
    sync: function(action, options, callback) {
 
84
      if (action !== 'read') {
 
85
        throw (
 
86
            'Only use the "read" action; "' + action + '" not supported.');
 
87
      }
 
88
      if (Y.Lang.isValue(options.get_charm)) {
 
89
        // This is an env.
 
90
        options.get_charm(
 
91
            this.get('id'),
 
92
            function(response) {
 
93
              if (response.err) {
 
94
                callback(true, response);
 
95
              } else if (response.result) {
 
96
                callback(false, response.result);
 
97
              } else {
 
98
                // What's going on?  This does not look like either of our
 
99
                // expected signatures.  Declare a loading error.
 
100
                callback(true, response);
 
101
              }
 
102
            }
 
103
        );
 
104
      } else if (Y.Lang.isValue(options.loadByPath)) {
 
105
        // This is a charm store.
 
106
        options.loadByPath(
 
107
            this.get('charm_store_path'),
 
108
            { success: function(response) {
 
109
              callback(false, response);
 
110
            },
 
111
            failure: function(response) {
 
112
              callback(true, response);
 
113
            }
 
114
            });
 
115
      } else {
 
116
        throw 'You must supply a get_charm or loadByPath function.';
 
117
      }
 
118
    },
 
119
    parse: function() {
 
120
      var data = Charm.superclass.parse.apply(this, arguments),
117
121
              self = this;
118
 
          data.is_subordinate = data.subordinate;
119
 
          Y.each(data, function(value, key) {
120
 
            if (!value ||
 
122
      data.is_subordinate = data.subordinate;
 
123
      Y.each(data, function(value, key) {
 
124
        if (!value ||
121
125
                !self.attrAdded(key) ||
122
126
                Y.Lang.isValue(self.get(key))) {
123
 
              delete data[key];
124
 
            }
125
 
          });
126
 
          if (data.owner === 'charmers') {
127
 
            delete data.owner;
128
 
          }
129
 
          return data;
130
 
        },
131
 
        compare: function(other, relevance, otherRelevance) {
132
 
          // Official charms sort before owned charms.
133
 
          // If !X.owner, that means it is owned by charmers.
134
 
          var owner = this.get('owner'),
 
127
          delete data[key];
 
128
        }
 
129
      });
 
130
      if (data.owner === 'charmers') {
 
131
        delete data.owner;
 
132
      }
 
133
      return data;
 
134
    },
 
135
    compare: function(other, relevance, otherRelevance) {
 
136
      // Official charms sort before owned charms.
 
137
      // If !X.owner, that means it is owned by charmers.
 
138
      var owner = this.get('owner'),
135
139
              otherOwner = other.get('owner');
136
 
          if (!owner && otherOwner) {
137
 
            return -1;
138
 
          } else if (owner && !otherOwner) {
139
 
            return 1;
140
 
          // Relevance is next most important.
141
 
          } else if (relevance && (relevance !== otherRelevance)) {
142
 
            // Higher relevance comes first.
143
 
            return otherRelevance - relevance;
144
 
          // Otherwise sort by package name, then by owner, then by revision.
145
 
          } else {
146
 
            return (
 
140
      if (!owner && otherOwner) {
 
141
        return -1;
 
142
      } else if (owner && !otherOwner) {
 
143
        return 1;
 
144
      // Relevance is next most important.
 
145
      } else if (relevance && (relevance !== otherRelevance)) {
 
146
        // Higher relevance comes first.
 
147
        return otherRelevance - relevance;
 
148
      // Otherwise sort by package name, then by owner, then by revision.
 
149
      } else {
 
150
        return (
147
151
                (this.get('package_name').localeCompare(
148
152
                other.get('package_name'))) ||
149
153
                (owner ? owner.localeCompare(otherOwner) : 0) ||
150
154
                (this.get('revision') - other.get('revision')));
151
 
          }
 
155
      }
 
156
    }
 
157
  }, {
 
158
    ATTRS: {
 
159
      id: {
 
160
        writeOnce: true,
 
161
        validator: function(val) {
 
162
          return Y.Lang.isString(val) && !!charmIdRe.exec(val);
152
163
        }
153
 
      }, {
154
 
        ATTRS: {
155
 
          id: {
156
 
            writeOnce: true,
157
 
            validator: function(val) {
158
 
              return Y.Lang.isString(val) && !!charmIdRe.exec(val);
159
 
            }
160
 
          },
161
 
          bzr_branch: {writeOnce: true},
162
 
          charm_store_path: {writeOnce: true},
163
 
          config: {writeOnce: true},
164
 
          description: {writeOnce: true},
165
 
          full_name: {writeOnce: true},
166
 
          is_subordinate: {writeOnce: true},
167
 
          last_change: {
168
 
            writeOnce: true,
169
 
            /**
 
164
      },
 
165
      bzr_branch: {writeOnce: true},
 
166
      charm_store_path: {writeOnce: true},
 
167
      config: {writeOnce: true},
 
168
      description: {writeOnce: true},
 
169
      full_name: {writeOnce: true},
 
170
      is_subordinate: {writeOnce: true},
 
171
      last_change: {
 
172
        writeOnce: true,
 
173
        /**
170
174
             * Normalize created value from float to date object.
 
175
             *
 
176
             * @method last_change.writeOnce.setter
171
177
             */
172
 
            setter: function(val) {
173
 
              if (val && val.created) {
174
 
                // Mutating in place should be fine since this should only
175
 
                // come from loading over the wire.
176
 
                val.created = new Date(val.created * 1000);
177
 
              }
178
 
              return val;
179
 
            }
180
 
          },
181
 
          maintainer: {writeOnce: true},
182
 
          metadata: {writeOnce: true},
183
 
          package_name: {writeOnce: true},
184
 
          owner: {writeOnce: true},
185
 
          peers: {writeOnce: true},
186
 
          proof: {writeOnce: true},
187
 
          provides: {writeOnce: true},
188
 
          requires: {writeOnce: true},
189
 
          revision: {
190
 
            writeOnce: true,
191
 
            /**
 
178
        setter: function(val) {
 
179
          if (val && val.created) {
 
180
            // Mutating in place should be fine since this should only
 
181
            // come from loading over the wire.
 
182
            val.created = new Date(val.created * 1000);
 
183
          }
 
184
          return val;
 
185
        }
 
186
      },
 
187
      maintainer: {writeOnce: true},
 
188
      metadata: {writeOnce: true},
 
189
      package_name: {writeOnce: true},
 
190
      owner: {writeOnce: true},
 
191
      peers: {writeOnce: true},
 
192
      proof: {writeOnce: true},
 
193
      provides: {writeOnce: true},
 
194
      requires: {writeOnce: true},
 
195
      revision: {
 
196
        writeOnce: true,
 
197
        /**
192
198
             * Parse the revision number out of a string.
 
199
             *
 
200
             * @method revision.writeOnce.setter
193
201
             */
194
 
            setter: function(val) {
195
 
              if (Y.Lang.isValue(val)) {
196
 
                val = parseInt(val, 10);
197
 
              }
198
 
              return val;
199
 
            }
200
 
          },
201
 
          scheme: {
202
 
            value: 'cs',
203
 
            writeOnce: true,
204
 
            /**
 
202
        setter: function(val) {
 
203
          if (Y.Lang.isValue(val)) {
 
204
            val = parseInt(val, 10);
 
205
          }
 
206
          return val;
 
207
        }
 
208
      },
 
209
      scheme: {
 
210
        value: 'cs',
 
211
        writeOnce: true,
 
212
        /**
205
213
             * If no value is given, "cs" is used as the default.
 
214
             *
 
215
             * @method scheme.writeOnce.setter
206
216
             */
207
 
            setter: function(val) {
208
 
              if (!Y.Lang.isValue(val)) {
209
 
                val = 'cs';
210
 
              }
211
 
              return val;
212
 
            }
213
 
          },
214
 
          series: {writeOnce: true},
215
 
          summary: {writeOnce: true},
216
 
          url: {writeOnce: true}
 
217
        setter: function(val) {
 
218
          if (!Y.Lang.isValue(val)) {
 
219
            val = 'cs';
 
220
          }
 
221
          return val;
217
222
        }
218
 
      });
 
223
      },
 
224
      series: {writeOnce: true},
 
225
      summary: {writeOnce: true},
 
226
      url: {writeOnce: true}
 
227
    }
 
228
  });
 
229
 
219
230
  models.Charm = Charm;
220
231
  models.charmIdRe = charmIdRe;
 
232
 
 
233
  /**
 
234
   * @class CharmList
 
235
   */
221
236
  var CharmList = Y.Base.create('charmList', Y.ModelList, [], {
222
237
    model: Charm
223
238
  }, {
224
239
    ATTRS: {}
225
240
  });
226
 
 
227
241
  models.CharmList = CharmList;
228
242
 
229
243
}, '0.1.0', {