1
YUI.add('musicstore-basket', function (Y, NAME) {
4
var PROXY_RE = /https?:\/\/cdn\.7static\.com|https?:\/\/www\.7digital\.com/i;
6
/* Basket class constructor */
8
Basket.superclass.constructor.apply(this, arguments);
11
Basket.NAME = "Basket";
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" }
44
Y.extend(Basket, Y.Widget, {
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());
55
if (emptyBasketTemplateNode) {
56
// This one is optional.
57
this.set("emptyBasketTemplateString", emptyBasketTemplateNode.getContent());
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());
66
destructor: function() {
68
this._delete.detach();
69
this.set("basketData", null);
72
renderUI : function() {
73
// Initial rendering needs to fetch data and render the widget.
74
if (this.get("renderOnLoad") === true) {
80
var delegate = this.get("delegateNode");
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);
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);
93
this.after("basketDataChange", this.syncUI, this);
97
var basket = this.get("basketData"),
100
basketItemsLength = 0,
101
emptyBasketTemplateString,
105
releasesInBasket = Y.Array([]),
109
this.addedButtons = Y.Array([]);
111
if (basket && basket.payload && basket.payload.basket_items) {
112
basketItems = basket.payload.basket_items;
113
basketLimit = this.get("basketItemLimit");
114
basketItemsLength = basketItems.length;
116
renderedBasketItems = [];
118
if (basketLimit === 0) {
119
limit = basketItemsLength;
121
limit = (basketLimit <= basketItemsLength) ? basketLimit : basketItemsLength;
124
for (i=0, j=limit; i<j; i++) {
125
// TODO: Consider caching these items.
126
renderedBasketItems.push(this.renderBasketItem(basketItems[i]));
128
if (limit < basketItemsLength) {
129
renderedBasketItems.push(this.renderViewAllItems(basketItemsLength));
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));
136
Y.all(".checkout-button").removeClass("hidden");
138
emptyBasketTemplateString = this.get("emptyBasketTemplateString");
139
if (emptyBasketTemplateString) {
140
renderedBasketItems.push(emptyBasketTemplateString);
142
Y.all(".checkout-button").addClass("hidden");
145
// Get all the basket items and iterate over them
146
for (k=0, l=basketItemsLength; k<l; k++){
148
itemRid = basketItems[k].release_id;
149
itemTid = basketItems[k].track_id;
152
itemRid = itemRid.toString();
155
itemTid = itemTid.toString();
158
// Check for having dealt with this release already.
159
if (Y.Array.indexOf(releasesInBasket, itemRid) > -1) {
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);
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);
175
Y.all(this.get("allBasketButtons") + "[data-u1ms-id='"+itemRid+":"+itemTid+"']").each(this.setButtonAdded, this);
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);
187
this.renderBasket(renderedBasketItems);
188
this.updateBasketTotals(basketItemsLength);
192
updateBasketTotals: function(total) {
193
Y.all(this.get("basketItemTotals")).each(function(o) {
198
renderViewAllItems: function(num) {
199
return Y.substitute(this.get("viewAllItemsTemplateString"), {"num": Y.Escape.html(num) });
202
renderTotal: function(total) {
203
return Y.substitute(this.get("totalItemsTemplateString"), {"total": Y.Escape.html(total) });
206
renderBasket: function(items) {
207
var srcNode = this.get("srcNode"),
208
basketContainer = this.get("basketContainerTemplateString"),
211
if (srcNode && items && basketContainer){
212
tfoot = (items.length > 1) ? items.pop() : "";
214
// Y.Escape.html on values in this substitue will break rendering
215
basket = Y.substitute(basketContainer, {"contents": items.join(""), "tfoot": tfoot});
217
srcNode.setContent(basket);
221
renderBasketItem: function(data) {
222
var basketItemTemplate,
226
if (data.image && re.test(data.image)) {
227
data.image = data.image.replace(re, U1MS_ASSET_PROXY_PREFIX );
230
if (data.image.substring(0, 4) !== "http"){
231
data.image = MEDIA_URL + "img/music/default_album350x350.png";
234
basketItemTemplate = this.get("basketItemTemplateString");
235
return Y.substitute(basketItemTemplate, data, function(k,v){return Y.Escape.html(v);});
239
handleDeletion: function(e) {
241
var currentTarget = e.currentTarget,
243
if (Y.UA.ie && Y.UA.ie < 9 && currentTarget.get("nodeName").toLowerCase() === "button") {
244
currentTarget = currentTarget.ancestor("form");
247
uri = currentTarget.getAttribute("data-u1ms-delete-url");
249
uri = currentTarget.get("action");
251
this.sendForm(currentTarget, uri);
255
handleAdd: function(e) {
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");
262
uri = currentTarget.getAttribute("data-u1ms-add-url");
263
this.sendForm(currentTarget, uri);
267
getButton: function(formNode) {
269
return formNode.one(this.get("buttonNode"));
273
handleProgress: function(id, o) {
275
if (o && o.formNode) {
276
button = this.getButton(o.formNode);
278
button.addClass(this.get("inProgressClass"));
279
button.setAttribute("disabled", "disabled");
284
handleBasketError: function(id, o, e) {
285
// o is populated with status and statusText only http://yuilibrary.com/yui/docs/io/#events
287
errorMessage = "Communication with the Ubuntu One servers failed. Please try again in a few moments.";
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");
296
Y.Global.fire(this.get("oneError"), { message : errorMessage });
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");
305
inner.appendChild(Y.Node.create(this.get("buttonA11yTemplateString")));
307
this.addedButtons.push(buttonNode);
310
setButtonRemoved: function(buttonNode) {
311
var innerA11yMsg = buttonNode.one(".inner .acchide");
312
buttonNode.removeClass("added");
314
innerA11yMsg.remove();
316
buttonNode.removeAttribute("disabled");
319
handleBasketData: function(id, o){
322
data = o.responseText,
326
data = Y.JSON.parse(data);
328
Y.log("U1MS: Invalid data received.");
331
if (data && data.payload && data.payload.basket_changes){
332
basketChanges = data.payload.basket_changes;
334
itemId = basketChanges.item_id;
335
action = basketChanges.change;
340
this.set("basketData", data);
346
getBasket: function(io, complete){
347
// Simply get the basket data.
348
io = io || new Y.IO();
349
complete = complete || this.handleBasketData;
351
var uri = this.get("basketInfoUrl");
356
success: Y.rbind(this.handleBasketData, this, {}),
357
failure: Y.rbind(this.handleBasketError, this, {})
362
sendForm: function(formNode, uri, io, complete) {
363
// Allow dep injection so we can mock out IO
365
io = io || new Y.IO();
366
complete = complete || this.handleBasketData;
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})
380
request = io.send(uri, cfg);
385
Y.namespace("U1MS").Basket = Basket;
388
}, '@VERSION@', {"requires": ["escape", "io-form", "json-parse", "substitute", "widget", "selector-css3"]});