~stephen-stewart/online-services-common-js/update-global-nav

« back to all changes in this revision

Viewing changes to build/musicstore-basket/musicstore-basket-debug.js

  • Committer: Stephen Stewart
  • Date: 2014-02-22 15:05:16 UTC
  • Revision ID: stephen.stewart@canonical.com-20140222150516-rkzti2c43ggwr2ta
import latest js, convert

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
YUI.add('musicstore-basket', function (Y, NAME) {
 
2
 
 
3
"use strict";
 
4
var PROXY_RE = /https?:\/\/cdn\.7static\.com|https?:\/\/www\.7digital\.com/i;
 
5
 
 
6
/* Basket class constructor */
 
7
function Basket() {
 
8
    Basket.superclass.constructor.apply(this, arguments);
 
9
}
 
10
 
 
11
Basket.NAME = "Basket";
 
12
 
 
13
Basket.ATTRS = {
 
14
    addForm: { value: ".add-basket-item" },
 
15
    allTrackButtons: { value: ".track-listing .atb" },
 
16
    allBasketButtons: { value: ".atb" },
 
17
    basketInfoUrl: { value: null },
 
18
    basketData: { value: null },
 
19
    basketItemLimit: { value: 4 },
 
20
    basketItemTotals: { value: ".basket-count"},
 
21
    basketContainerTemplateNode: { value: "#tpl-basket" },
 
22
    basketContainerTemplateString: { value: null },
 
23
    basketItemTemplateNode: { value: "#tpl-basket-item" },
 
24
    basketItemTemplateString: { value: null },
 
25
    buttonA11yTemplateNode: { value: "#tpl-button-added" },
 
26
    buttonA11yTemplateString: { value: null },
 
27
    buttonNode: { value: "button[type=submit]" },
 
28
    delegateNode: { value: "body" },
 
29
    deleteForm: { value: ".del-basket-item" },
 
30
    emptyBasketTemplateNode: { value: null },
 
31
    emptyBasketTemplateString: { value: null },
 
32
    inProgressClass: { value: "progress" },
 
33
    renderOnLoad: { value: true },
 
34
    releaseButtonClass: { value: "buy-all" },
 
35
    releaseButton: { value: ".release-detail .buy-all" },
 
36
    totalItemsTemplateNode: { value: "#tpl-basket-total" },
 
37
    totalItemsTemplateString: { value: null },
 
38
    viewAllItemsTemplateNode: { value: "#tpl-basket-view-all" },
 
39
    viewAllItemsTemplateString: { value: null },
 
40
    oneError: { value: "one:error" }
 
41
 
 
42
};
 
43
 
 
44
Y.extend(Basket, Y.Widget, {
 
45
 
 
46
    addedButtons: null,
 
47
 
 
48
    initializer: function() {
 
49
        var viewAllItemsTemplateNode = Y.one(this.get("viewAllItemsTemplateNode")),
 
50
            emptyBasketTemplateNode = Y.one(this.get("emptyBasketTemplateNode"));
 
51
        if (viewAllItemsTemplateNode) {
 
52
            // This one is optional.
 
53
            this.set("viewAllItemsTemplateString", viewAllItemsTemplateNode.getContent());
 
54
        }
 
55
        if (emptyBasketTemplateNode) {
 
56
            // This one is optional.
 
57
            this.set("emptyBasketTemplateString", emptyBasketTemplateNode.getContent());
 
58
        }
 
59
 
 
60
        this.set("basketItemTemplateString", Y.one(this.get("basketItemTemplateNode")).getContent());
 
61
        this.set("totalItemsTemplateString", Y.one(this.get("totalItemsTemplateNode")).getContent());
 
62
        this.set("basketContainerTemplateString", Y.one(this.get("basketContainerTemplateNode")).getContent());
 
63
        this.set("buttonA11yTemplateString", Y.one(this.get("buttonA11yTemplateNode")).getContent());
 
64
    },
 
65
 
 
66
    destructor: function() {
 
67
        this._add.detach();
 
68
        this._delete.detach();
 
69
        this.set("basketData", null);
 
70
    },
 
71
 
 
72
    renderUI : function() {
 
73
        // Initial rendering needs to fetch data and render the widget.
 
74
        if (this.get("renderOnLoad") === true) {
 
75
            this.getBasket();
 
76
        }
 
77
    },
 
78
 
 
79
    bindUI : function() {
 
80
        var delegate = this.get("delegateNode");
 
81
 
 
82
        // Ah dear old IE < 9 doesn't bubble submits. So for this we have to delegate the click and then
 
83
        // work out the form from the button. There's no visible inputs in these forms so we don't need
 
84
        // to watch for enter on other form elements presently.
 
85
        if (Y.UA.ie && Y.UA.ie < 9){
 
86
            // This is fired with enter too on the focussed button element so keyboard access is not a problem.
 
87
            this._add = Y.delegate("click", this.handleAdd, delegate, this.get("addForm") + " .atb:enabled", this);
 
88
            this._delete = Y.delegate("click", this.handleDeletion, delegate, this.get("deleteForm") + " .remove-basket-item:enabled", this);
 
89
        } else {
 
90
            this._add = Y.delegate("submit", this.handleAdd, delegate, this.get("addForm"), this);
 
91
            this._delete = Y.delegate("submit", this.handleDeletion, delegate, this.get("deleteForm"), this);
 
92
        }
 
93
        this.after("basketDataChange", this.syncUI, this);
 
94
    },
 
95
 
 
96
    syncUI: function() {
 
97
        var basket = this.get("basketData"),
 
98
            basketItems,
 
99
            basketLimit,
 
100
            basketItemsLength = 0,
 
101
            emptyBasketTemplateString,
 
102
            itemTid,
 
103
            itemRid,
 
104
            limit,
 
105
            releasesInBasket = Y.Array([]),
 
106
            renderedBasketItems,
 
107
            i, j, k, l;
 
108
 
 
109
        this.addedButtons = Y.Array([]);
 
110
 
 
111
        if (basket && basket.payload && basket.payload.basket_items) {
 
112
            basketItems = basket.payload.basket_items;
 
113
            basketLimit = this.get("basketItemLimit");
 
114
            basketItemsLength = basketItems.length;
 
115
 
 
116
            renderedBasketItems = [];
 
117
 
 
118
            if (basketLimit === 0) {
 
119
                limit = basketItemsLength;
 
120
            } else {
 
121
                limit = (basketLimit <= basketItemsLength) ? basketLimit : basketItemsLength;
 
122
            }
 
123
 
 
124
            for (i=0, j=limit; i<j; i++) {
 
125
                // TODO: Consider caching these items.
 
126
                renderedBasketItems.push(this.renderBasketItem(basketItems[i]));
 
127
            }
 
128
            if (limit < basketItemsLength) {
 
129
                renderedBasketItems.push(this.renderViewAllItems(basketItemsLength));
 
130
            }
 
131
 
 
132
            if (basketItemsLength > 0) {
 
133
                if (basket.payload.basket_summary && basket.payload.basket_summary.due) {
 
134
                    renderedBasketItems.push(this.renderTotal(basket.payload.basket_summary.due));
 
135
                }
 
136
                Y.all(".checkout-button").removeClass("hidden");
 
137
            } else {
 
138
                emptyBasketTemplateString = this.get("emptyBasketTemplateString");
 
139
                if (emptyBasketTemplateString) {
 
140
                    renderedBasketItems.push(emptyBasketTemplateString);
 
141
                }
 
142
                Y.all(".checkout-button").addClass("hidden");
 
143
            }
 
144
 
 
145
            // Get all the basket items and iterate over them
 
146
            for (k=0, l=basketItemsLength; k<l; k++){
 
147
 
 
148
                itemRid = basketItems[k].release_id;
 
149
                itemTid = basketItems[k].track_id;
 
150
 
 
151
                if (itemRid) {
 
152
                    itemRid = itemRid.toString();
 
153
                }
 
154
                if (itemTid) {
 
155
                    itemTid = itemTid.toString();
 
156
                }
 
157
 
 
158
                // Check for having dealt with this release already.
 
159
                if (Y.Array.indexOf(releasesInBasket, itemRid) > -1) {
 
160
                    continue;
 
161
                }
 
162
 
 
163
                // Set-up all release related buttons.
 
164
                if (itemRid && !itemTid) {
 
165
                    releasesInBasket.push(itemRid);
 
166
                    Y.all(this.get("allBasketButtons") + "[data-u1ms-id^='"+itemRid+"']").each(this.setButtonAdded, this);
 
167
                }
 
168
                // Set-up all track related buttons.
 
169
                if (itemRid && itemTid) {
 
170
                    // If there's only 1 track then set the release button too.
 
171
                    // This is a special case for releases with only one track.
 
172
                    if (Y.all(this.get("allTrackButtons")).size() === 1){
 
173
                        Y.all([this.get("allBasketButtons") + "[data-u1ms-id='"+itemRid+":"+itemTid+"']", this.get("allBasketButtons") + "[data-u1ms-id^='"+itemRid+"']"]).each(this.setButtonAdded, this);
 
174
                    } else {
 
175
                        Y.all(this.get("allBasketButtons") + "[data-u1ms-id='"+itemRid+":"+itemTid+"']").each(this.setButtonAdded, this);
 
176
                    }
 
177
                }
 
178
            }
 
179
 
 
180
            // Remove all the buttons not in this.addedButtons
 
181
            Y.all(this.get("allBasketButtons")).each(function(button){
 
182
                if (Y.Array.indexOf(this.addedButtons, button) === -1) {
 
183
                    this.setButtonRemoved(button);
 
184
                }
 
185
            }, this);
 
186
 
 
187
            this.renderBasket(renderedBasketItems);
 
188
            this.updateBasketTotals(basketItemsLength);
 
189
        }
 
190
    },
 
191
 
 
192
    updateBasketTotals: function(total) {
 
193
        Y.all(this.get("basketItemTotals")).each(function(o) {
 
194
            o.setContent(total);
 
195
        });
 
196
    },
 
197
 
 
198
    renderViewAllItems: function(num) {
 
199
        return Y.substitute(this.get("viewAllItemsTemplateString"), {"num": Y.Escape.html(num) });
 
200
    },
 
201
 
 
202
    renderTotal: function(total) {
 
203
        return Y.substitute(this.get("totalItemsTemplateString"), {"total": Y.Escape.html(total) });
 
204
    },
 
205
 
 
206
    renderBasket: function(items)  {
 
207
        var srcNode = this.get("srcNode"),
 
208
            basketContainer = this.get("basketContainerTemplateString"),
 
209
            basket,
 
210
            tfoot;
 
211
        if (srcNode && items && basketContainer){
 
212
            tfoot = (items.length > 1) ? items.pop() : "";
 
213
 
 
214
            // Y.Escape.html on values in this substitue will break rendering
 
215
            basket = Y.substitute(basketContainer, {"contents": items.join(""), "tfoot": tfoot});
 
216
 
 
217
            srcNode.setContent(basket);
 
218
        }
 
219
    },
 
220
 
 
221
    renderBasketItem: function(data) {
 
222
        var basketItemTemplate,
 
223
        re = PROXY_RE;
 
224
        if (data) {
 
225
 
 
226
            if (data.image && re.test(data.image)) {
 
227
                data.image = data.image.replace(re, U1MS_ASSET_PROXY_PREFIX );
 
228
            }
 
229
 
 
230
            if (data.image.substring(0, 4) !== "http"){
 
231
                data.image = MEDIA_URL + "img/music/default_album350x350.png";
 
232
            }
 
233
 
 
234
            basketItemTemplate = this.get("basketItemTemplateString");
 
235
            return Y.substitute(basketItemTemplate, data, function(k,v){return Y.Escape.html(v);});
 
236
        }
 
237
    },
 
238
 
 
239
    handleDeletion: function(e) {
 
240
        e.preventDefault();
 
241
        var currentTarget = e.currentTarget,
 
242
            uri;
 
243
        if (Y.UA.ie && Y.UA.ie < 9 && currentTarget.get("nodeName").toLowerCase() === "button") {
 
244
            currentTarget = currentTarget.ancestor("form");
 
245
        }
 
246
        if (currentTarget) {
 
247
            uri = currentTarget.getAttribute("data-u1ms-delete-url");
 
248
            if (!uri) {
 
249
                uri = currentTarget.get("action");
 
250
            }
 
251
            this.sendForm(currentTarget, uri);
 
252
        }
 
253
    },
 
254
 
 
255
    handleAdd: function(e) {
 
256
        e.halt();
 
257
        var currentTarget = e.currentTarget, uri;
 
258
        if (Y.UA.ie && Y.UA.ie < 9 && currentTarget.get("nodeName").toLowerCase() === "button") {
 
259
            currentTarget = currentTarget.ancestor("form");
 
260
        }
 
261
        if (currentTarget) {
 
262
            uri = currentTarget.getAttribute("data-u1ms-add-url");
 
263
            this.sendForm(currentTarget, uri);
 
264
        }
 
265
    },
 
266
 
 
267
    getButton: function(formNode) {
 
268
        if (formNode) {
 
269
            return formNode.one(this.get("buttonNode"));
 
270
        }
 
271
    },
 
272
 
 
273
    handleProgress: function(id, o) {
 
274
        var button;
 
275
        if (o && o.formNode) {
 
276
             button = this.getButton(o.formNode);
 
277
             if (button){
 
278
                button.addClass(this.get("inProgressClass"));
 
279
                button.setAttribute("disabled", "disabled");
 
280
             }
 
281
        }
 
282
    },
 
283
 
 
284
    handleBasketError: function(id, o, e) {
 
285
        // o is populated with status and statusText only http://yuilibrary.com/yui/docs/io/#events
 
286
        var button,
 
287
            errorMessage = "Communication with the Ubuntu One servers failed. Please try again in a few moments.";
 
288
 
 
289
        if (o && e.formNode) {
 
290
            button = this.getButton(e.formNode);
 
291
            button.removeClass(this.get("inProgressClass"));
 
292
            button.removeClass(this.get("addClass"));
 
293
            button.removeAttribute("disabled");
 
294
        }
 
295
 
 
296
        Y.Global.fire(this.get("oneError"), { message : errorMessage });
 
297
    },
 
298
 
 
299
    setButtonAdded: function(buttonNode){
 
300
        var inner = buttonNode.one(".inner");
 
301
        buttonNode.addClass("added");
 
302
        buttonNode.removeClass(this.get("inProgressClass"));
 
303
        buttonNode.setAttribute("disabled", "disabled");
 
304
        if (inner) {
 
305
            inner.appendChild(Y.Node.create(this.get("buttonA11yTemplateString")));
 
306
        }
 
307
        this.addedButtons.push(buttonNode);
 
308
    },
 
309
 
 
310
    setButtonRemoved: function(buttonNode) {
 
311
        var innerA11yMsg = buttonNode.one(".inner .acchide");
 
312
        buttonNode.removeClass("added");
 
313
        if (innerA11yMsg) {
 
314
            innerA11yMsg.remove();
 
315
        }
 
316
        buttonNode.removeAttribute("disabled");
 
317
    },
 
318
 
 
319
    handleBasketData: function(id, o){
 
320
        var action,
 
321
            basketChanges,
 
322
            data = o.responseText,
 
323
            itemId;
 
324
 
 
325
        try {
 
326
            data = Y.JSON.parse(data);
 
327
        } catch (e) {
 
328
            Y.log("U1MS: Invalid data received.");
 
329
        }
 
330
 
 
331
        if (data && data.payload && data.payload.basket_changes){
 
332
            basketChanges = data.payload.basket_changes;
 
333
            if (basketChanges) {
 
334
                itemId = basketChanges.item_id;
 
335
                action = basketChanges.change;
 
336
            }
 
337
        }
 
338
 
 
339
        if (data) {
 
340
            this.set("basketData", data);
 
341
        }
 
342
 
 
343
    },
 
344
 
 
345
 
 
346
    getBasket: function(io, complete){
 
347
        // Simply get the basket data.
 
348
        io = io || new Y.IO();
 
349
        complete = complete || this.handleBasketData;
 
350
 
 
351
        var uri = this.get("basketInfoUrl");
 
352
 
 
353
        io.send(uri, {
 
354
            method: 'GET',
 
355
            on: {
 
356
                success: Y.rbind(this.handleBasketData, this, {}),
 
357
                failure: Y.rbind(this.handleBasketError, this, {})
 
358
            }
 
359
        });
 
360
    },
 
361
 
 
362
    sendForm: function(formNode, uri, io, complete) {
 
363
        // Allow dep injection so we can mock out IO
 
364
        // for testing.
 
365
        io = io || new Y.IO();
 
366
        complete = complete || this.handleBasketData;
 
367
        var cfg = {
 
368
                method: 'POST',
 
369
                form: {
 
370
                    id: formNode,
 
371
                    useDisabled: false
 
372
                },
 
373
                on: {
 
374
                    start: Y.rbind(this.handleProgress, this, {"formNode": formNode}),
 
375
                    failure: Y.rbind(this.handleBasketError, this, {"formNode": formNode}),
 
376
                    success: Y.rbind(complete, this, {"formNode": formNode})
 
377
                }
 
378
            },
 
379
            request;
 
380
        request = io.send(uri, cfg);
 
381
    }
 
382
 
 
383
});
 
384
 
 
385
Y.namespace("U1MS").Basket = Basket;
 
386
 
 
387
 
 
388
}, '@VERSION@', {"requires": ["escape", "io-form", "json-parse", "substitute", "widget", "selector-css3"]});