Shopping Cart
Shopping Cart
Turn on a screen reader to experience this example in action.
Content updated dynamically
dummy linkThumbnail | Item Name | Unit price | Quantity | Actions | Extras | Total |
---|
Cart Total: $
HTML Source Code
<p>Content updated dynamically</p>
<a href="#">dummy link</a>
<div id="cart">
<table class="list" tabindex="-1">
<caption class="clipped">
<span>Shopping cart, total </span>
<span class="total"></span>
<span> dollars</span>
</caption>
<thead>
<tr>
<th scope="col">Thumbnail</th>
<th scope="col">Item Name</th>
<th scope="col">Unit price</th>
<th id="qty-header" scope="col">Quantity</th>
<th scope="col">Actions</th>
<th scope="col">Extras</th>
<th scope="col">Total</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="summary">
<span id="cart-total">
<span>Cart Total: $</span><span class="total"></span>
</span>
</div>
<div id="cart-log" class="clipped" aria-live="polite" aria-atomic="true"></div>
</div>
JavaScript Source Code
This code was combined from three separate files cart.js, item.js, and index.js
(function outer(modules, cache, entries){
/**
* Global
*/
var global = (function(){ return this; })();
/**
* Require `name`.
*
* @param {String} name
* @param {Boolean} jumped
* @api public
*/
function require(name, jumped){
if (cache[name]) return cache[name].exports;
if (modules[name]) return call(name, require);
throw new Error('cannot find module "' + name + '"');
}
/**
* Call module `id` and cache it.
*
* @param {Number} id
* @param {Function} require
* @return {Function}
* @api private
*/
function call(id, require){
var m = cache[id] = { exports: {} };
var mod = modules[id];
var name = mod[2];
var fn = mod[0];
fn.call(m.exports, function(req){
var dep = modules[id][1][req];
return require(dep ? dep : req);
}, m, m.exports, outer, modules, cache, entries);
// expose as `name`.
if (name) cache[name] = cache[id];
return cache[id].exports;
}
/**
* Require all entries exposing them on global if needed.
*/
for (var id in entries) {
if (entries[id]) {
global[entries[id]] = require(id);
} else {
require(id);
}
}
/**
* Duo flag.
*/
require.duo = true;
/**
* Expose cache.
*/
require.cache = cache;
/**
* Expose modules
*/
require.modules = modules;
/**
* Return newest require.
*/
return require;
})({
1: [function(require, module, exports) {
/**
* Module dependencies.
*/
var data = require('./data.json');
var Item = require('./item');
var Cart = require('./cart');
/**
* Create the cart, and add all the items.
*/
var cart = new Cart();
while (data.items.length) {
var _name = data.items.shift();
addItem(_name);
}
cart.updateTotal();
/**
* Creates a new `Item#` and appends it to `Cart#`.
*
* @param {String} name
* @api private
*/
function addItem(name) {
new Item(name)
.initialize()
.appendTo(cart)
.on('updated', function () {
cart.updateTotal();
})
.on('removed', function () {
cart.updateTotal();
cart.focusWrapper();
})
.on('log', function (data) {
cart.log(data.msg);
});
}
}, {"./data.json":2,"./item":3,"./cart":4}],
2: [function(require, module, exports) {
module.exports = {
"items": [
"Ray Gun",
"Leonidas",
"Beef Jerky",
"Amplifier",
"Cheesecake"
]
};
}, {}],
3: [function(require, module, exports) {
/**
* Module dependencies.
*/
var itemHtml = require('./item.html');
var classes = require('component/classes');
var domify = require('component/domify');
var events = require('component/events');
var Emitter = require('component/emitter');
var closest = require('component/closest');
var Confirmation = require('component/confirmation');
var focusTrap = require('stephenmathieson/focus-trap');
var fmt = require('yields/fmt');
/**
* Extend `fmt` to format currency values.
*/
fmt.c = function (n) {
return Number(n || 0).toFixed(2);
};
/**
* Expose `Item`.
*/
module.exports = Item;
/**
* Create a new `Item`.
*
* @param {String} name
* @api public
*/
function Item(name) {
this.el = domify(itemHtml);
this.name = name;
this.price = 9.99;
this.quantity = 1;
this.warrantyCost = 4.99;
this.giftwrapCost = 2.99;
this.nameEl = this.el.querySelector('.name');
this.thumb = this.el.querySelector('.thumb img');
this.priceEl = this.el.querySelector('.price .dollars');
this.quantityEl = this.el.querySelector('.quantity input');
this.extrasEl = this.el.querySelector('.extras');
this.totalEl = this.el.querySelector('td.total .dollars');
this.events = events(this.el, this);
this.events.bind('change .quantity input', 'updateTotal');
this.events.bind('click button.warranty', 'toggleExtra');
this.events.bind('click button.giftwrap', 'toggleExtra');
this.events.bind('click button.remove', 'remove');
}
/**
* Mixin `Emitter`.
*/
Emitter(Item.prototype);
/**
* Sets up the Item. Invoke prior to `#appendTo`.
*
* @return {Item}
* @api public
*/
Item.prototype.initialize = function () {
var altStr = 'Thumbnail image of ';
this.nameEl.innerHTML = this.name;
this.thumb.setAttribute('alt', altStr + this.name);
this.priceEl.innerHTML = this.price;
this.quantityEl.value = this.quantity;
this.totalPrice = this.totalEl.innerHTML =
Number(this.price * this.quantity).toFixed(2);
return this;
};
/**
* Appends the Item to a given `cart`.
*
* @param {Cart} cart
* @return {Item}
* @api public
*/
Item.prototype.appendTo = function (cart) {
cart.listEl.appendChild(this.el);
cart.items.push(this);
return this;
};
/**
* Updates `Item#totalPrice` and `Item#totalEl.innerHTML`
* using available item info.
*
* @return {Item}
* @api public
*/
Item.prototype.updateTotal = function () {
var qty = this.quantity = this.quantityEl.value;
var warranty = this.extrasEl.querySelector('.warranty.active')
? this.warrantyCost * qty : 0;
var giftwrap = this.extrasEl.querySelector('.giftwrap.active')
? this.giftwrapCost * qty : 0;
this.totalPrice = (this.price * this.quantityEl.value)
+ warranty
+ giftwrap;
this.totalEl.innerHTML = Number(this.totalPrice).toFixed(2);
this.emit('updated');
this.log(fmt('Item total: %c dollars', this.totalPrice));
return this;
};
/**
* Handles clicks on "action" items which toggle "extras".
*
* @param {Event} e
* @return {Item}
* @api public
*/
Item.prototype.toggleExtra = function (e) {
var button = closest(e.target, 'button', true);
var extraName = button.className;
var extra = this.extrasEl.querySelector('.' + extraName);
var action = button.querySelector('.action');
var origState = action.innerHTML;
var extraMsg;
// Toggle "active" class to display/hide the "extra"
classes(extra).toggle('active');
// Toggle the trigger button's text
if (origState == 'Add') {
action.innerHTML = 'Remove';
extraMsg = extraName + ' added. ';
} else {
action.innerHTML = 'Add';
extraMsg = extraName + ' removed. ';
}
this.updateTotal();
this.log(extraMsg + fmt('Item total: %c dollars', this.totalPrice));
return this;
};
/**
* Handles clicks on the "Remove Item" button.
*
* Triggers a confirmation prompt before
* removing the item.
*
* @api public
*/
Item.prototype.remove = function () {
var self = this;
var trigger = self.el.querySelector('button.remove');
new Confirmation('Do you really want to remove ' + self.name + ' from your cart?')
.ok('Yes')
.cancel('No')
.fixed()
.on('show', function () {
focusTrap(this.el.querySelector('.confirmation-actions'));
this.el.setAttribute('role', 'alertdialog');
})
.on('ok', function () {
self.totalPrice = 0;
self.el.parentNode.removeChild(self.el);
self.emit('removed');
self.log(self.name + ' removed from cart');
trigger.focus();
})
.on('cancel', function () {
trigger.focus();
})
.show();
};
Item.prototype.log = function (msg) {
this.emit('log', {
msg: '<span>' + msg + '</span>'
});
};
}, {"./item.html":5,"component/classes":6,"component/domify":7,"component/events":8,"component/emitter":9,"component/closest":10,"component/confirmation":11,"stephenmathieson/focus-trap":12,"yields/fmt":13}],
5: [function(require, module, exports) {
module.exports = '\n<tr class="item">\n\n <td class="thumb">\n <img src="/assets/js/aria/custom-form-controls/shopping-cart/sf/box.png">\n </td>\n\n <th class="name" scope="row"></th>\n\n <td class="price">\n <span class="dollars"></span>\n <span class="clipped"> dollars</span>\n </td>\n\n <td class="quantity">\n <input type="number" min="0" aria-labelledby="qty-header">\n </td>\n\n <td class="actions">\n <ul>\n <li>\n <button aria-controls="cart-total" class="warranty">\n <span class="action">Add</span>\n <span> Warranty</span>\n </button>\n </li>\n <li>\n <button aria-controls="cart-total" class="giftwrap">\n <span class="action">Add</span>\n <span> Gift Wrapping</span>\n </button>\n </li>\n <li>\n <button aria-controls="cart-total" class="remove">Remove Item</button>\n </li>\n </ul>\n </td>\n\n <td class="extras">\n <ul>\n <li class="warranty">Warranty ($4.99)</li>\n <li class="giftwrap">Gift Wrapping ($2.99)</li>\n </ul>\n </td>\n\n <td class="total">\n <span class="dollars"></span>\n <span class="clipped"> dollars</span>\n </td>\n\n</tr>';
}, {}],
6: [function(require, module, exports) {
/**
* Module dependencies.
*/
var index = require('indexof');
/**
* Whitespace regexp.
*/
var re = /\s+/;
/**
* toString reference.
*/
var toString = Object.prototype.toString;
/**
* Wrap `el` in a `ClassList`.
*
* @param {Element} el
* @return {ClassList}
* @api public
*/
module.exports = function(el){
return new ClassList(el);
};
/**
* Initialize a new ClassList for `el`.
*
* @param {Element} el
* @api private
*/
function ClassList(el) {
if (!el || !el.nodeType) {
throw new Error('A DOM element reference is required');
}
this.el = el;
this.list = el.classList;
}
/**
* Add class `name` if not already present.
*
* @param {String} name
* @return {ClassList}
* @api public
*/
ClassList.prototype.add = function(name){
// classList
if (this.list) {
this.list.add(name);
return this;
}
// fallback
var arr = this.array();
var i = index(arr, name);
if (!~i) arr.push(name);
this.el.className = arr.join(' ');
return this;
};
/**
* Remove class `name` when present, or
* pass a regular expression to remove
* any which match.
*
* @param {String|RegExp} name
* @return {ClassList}
* @api public
*/
ClassList.prototype.remove = function(name){
if ('[object RegExp]' == toString.call(name)) {
return this.removeMatching(name);
}
// classList
if (this.list) {
this.list.remove(name);
return this;
}
// fallback
var arr = this.array();
var i = index(arr, name);
if (~i) arr.splice(i, 1);
this.el.className = arr.join(' ');
return this;
};
/**
* Remove all classes matching `re`.
*
* @param {RegExp} re
* @return {ClassList}
* @api private
*/
ClassList.prototype.removeMatching = function(re){
var arr = this.array();
for (var i = 0; i < arr.length; i++) {
if (re.test(arr[i])) {
this.remove(arr[i]);
}
}
return this;
};
/**
* Toggle class `name`, can force state via `force`.
*
* For browsers that support classList, but do not support `force` yet,
* the mistake will be detected and corrected.
*
* @param {String} name
* @param {Boolean} force
* @return {ClassList}
* @api public
*/
ClassList.prototype.toggle = function(name, force){
// classList
if (this.list) {
if ("undefined" !== typeof force) {
if (force !== this.list.toggle(name, force)) {
this.list.toggle(name); // toggle again to correct
}
} else {
this.list.toggle(name);
}
return this;
}
// fallback
if ("undefined" !== typeof force) {
if (!force) {
this.remove(name);
} else {
this.add(name);
}
} else {
if (this.has(name)) {
this.remove(name);
} else {
this.add(name);
}
}
return this;
};
/**
* Return an array of classes.
*
* @return {Array}
* @api public
*/
ClassList.prototype.array = function(){
var str = this.el.className.replace(/^\s+|\s+$/g, '');
var arr = str.split(re);
if ('' === arr[0]) arr.shift();
return arr;
};
/**
* Check if class `name` is present.
*
* @param {String} name
* @return {ClassList}
* @api public
*/
ClassList.prototype.has =
ClassList.prototype.contains = function(name){
return this.list
? this.list.contains(name)
: !! ~index(this.array(), name);
};
}, {"indexof":14}],
14: [function(require, module, exports) {
module.exports = function(arr, obj){
if (arr.indexOf) return arr.indexOf(obj);
for (var i = 0; i < arr.length; ++i) {
if (arr[i] === obj) return i;
}
return -1;
};
}, {}],
7: [function(require, module, exports) {
/**
* Expose `parse`.
*/
module.exports = parse;
/**
* Tests for browser support.
*/
var div = document.createElement('div');
// Setup
div.innerHTML = ' <link/><table></table><a href="/a">a</a><input type="checkbox"/>';
// Make sure that link elements get serialized correctly by innerHTML
// This requires a wrapper element in IE
var innerHTMLBug = !div.getElementsByTagName('link').length;
div = undefined;
/**
* Wrap map from jquery.
*/
var map = {
legend: [1, '<fieldset>', '</fieldset>'],
tr: [2, '<table><tbody>', '</tbody></table>'],
col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
// for script/link/style tags to work in IE6-8, you have to wrap
// in a div with a non-whitespace character in front, ha!
_default: innerHTMLBug ? [1, 'X<div>', '</div>'] : [0, '', '']
};
map.td =
map.th = [3, '<table><tbody><tr>', '</tr></tbody></table>'];
map.option =
map.optgroup = [1, '<select multiple="multiple">', '</select>'];
map.thead =
map.tbody =
map.colgroup =
map.caption =
map.tfoot = [1, '<table>', '</table>'];
map.text =
map.circle =
map.ellipse =
map.line =
map.path =
map.polygon =
map.polyline =
map.rect = [1, '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">','</svg>'];
/**
* Parse `html` and return a DOM Node instance, which could be a TextNode,
* HTML DOM Node of some kind (<div> for example), or a DocumentFragment
* instance, depending on the contents of the `html` string.
*
* @param {String} html - HTML string to "domify"
* @param {Document} doc - The `document` instance to create the Node for
* @return {DOMNode} the TextNode, DOM Node, or DocumentFragment instance
* @api private
*/
function parse(html, doc) {
if ('string' != typeof html) throw new TypeError('String expected');
// default to the global `document` object
if (!doc) doc = document;
// tag name
var m = /<([\w:]+)/.exec(html);
if (!m) return doc.createTextNode(html);
html = html.replace(/^\s+|\s+$/g, ''); // Remove leading/trailing whitespace
var tag = m[1];
// body support
if (tag == 'body') {
var el = doc.createElement('html');
el.innerHTML = html;
return el.removeChild(el.lastChild);
}
// wrap map
var wrap = map[tag] || map._default;
var depth = wrap[0];
var prefix = wrap[1];
var suffix = wrap[2];
var el = doc.createElement('div');
el.innerHTML = prefix + html + suffix;
while (depth--) el = el.lastChild;
// one element
if (el.firstChild == el.lastChild) {
return el.removeChild(el.firstChild);
}
// several elements
var fragment = doc.createDocumentFragment();
while (el.firstChild) {
fragment.appendChild(el.removeChild(el.firstChild));
}
return fragment;
}
}, {}],
8: [function(require, module, exports) {
/**
* Module dependencies.
*/
var events = require('event');
var delegate = require('delegate');
/**
* Expose `Events`.
*/
module.exports = Events;
/**
* Initialize an `Events` with the given
* `el` object which events will be bound to,
* and the `obj` which will receive method calls.
*
* @param {Object} el
* @param {Object} obj
* @api public
*/
function Events(el, obj) {
if (!(this instanceof Events)) return new Events(el, obj);
if (!el) throw new Error('element required');
if (!obj) throw new Error('object required');
this.el = el;
this.obj = obj;
this._events = {};
}
/**
* Subscription helper.
*/
Events.prototype.sub = function(event, method, cb){
this._events[event] = this._events[event] || {};
this._events[event][method] = cb;
};
/**
* Bind to `event` with optional `method` name.
* When `method` is undefined it becomes `event`
* with the "on" prefix.
*
* Examples:
*
* Direct event handling:
*
* events.bind('click') // implies "onclick"
* events.bind('click', 'remove')
* events.bind('click', 'sort', 'asc')
*
* Delegated event handling:
*
* events.bind('click li > a')
* events.bind('click li > a', 'remove')
* events.bind('click a.sort-ascending', 'sort', 'asc')
* events.bind('click a.sort-descending', 'sort', 'desc')
*
* @param {String} event
* @param {String|function} [method]
* @return {Function} callback
* @api public
*/
Events.prototype.bind = function(event, method){
var e = parse(event);
var el = this.el;
var obj = this.obj;
var name = e.name;
var method = method || 'on' + name;
var args = [].slice.call(arguments, 2);
// callback
function cb(){
var a = [].slice.call(arguments).concat(args);
obj[method].apply(obj, a);
}
// bind
if (e.selector) {
cb = delegate.bind(el, e.selector, name, cb);
} else {
events.bind(el, name, cb);
}
// subscription for unbinding
this.sub(name, method, cb);
return cb;
};
/**
* Unbind a single binding, all bindings for `event`,
* or all bindings within the manager.
*
* Examples:
*
* Unbind direct handlers:
*
* events.unbind('click', 'remove')
* events.unbind('click')
* events.unbind()
*
* Unbind delegate handlers:
*
* events.unbind('click', 'remove')
* events.unbind('click')
* events.unbind()
*
* @param {String|Function} [event]
* @param {String|Function} [method]
* @api public
*/
Events.prototype.unbind = function(event, method){
if (0 === arguments.length) return this.unbindAll();
if (1 === arguments.length) return this.unbindAllOf(event);
// no bindings for this event
var bindings = this._events[event];
if (!bindings) return;
// no bindings for this method
var cb = bindings[method];
if (!cb) return;
events.unbind(this.el, event, cb);
};
/**
* Unbind all events.
*
* @api private
*/
Events.prototype.unbindAll = function(){
for (var event in this._events) {
this.unbindAllOf(event);
}
};
/**
* Unbind all events for `event`.
*
* @param {String} event
* @api private
*/
Events.prototype.unbindAllOf = function(event){
var bindings = this._events[event];
if (!bindings) return;
for (var method in bindings) {
this.unbind(event, method);
}
};
/**
* Parse `event`.
*
* @param {String} event
* @return {Object}
* @api private
*/
function parse(event) {
var parts = event.split(/ +/);
return {
name: parts.shift(),
selector: parts.join(' ')
}
}
}, {"event":15,"delegate":16}],
15: [function(require, module, exports) {
var bind = window.addEventListener ? 'addEventListener' : 'attachEvent',
unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent',
prefix = bind !== 'addEventListener' ? 'on' : '';
/**
* Bind `el` event `type` to `fn`.
*
* @param {Element} el
* @param {String} type
* @param {Function} fn
* @param {Boolean} capture
* @return {Function}
* @api public
*/
exports.bind = function(el, type, fn, capture){
el[bind](prefix + type, fn, capture || false);
return fn;
};
/**
* Unbind `el` event `type`'s callback `fn`.
*
* @param {Element} el
* @param {String} type
* @param {Function} fn
* @param {Boolean} capture
* @return {Function}
* @api public
*/
exports.unbind = function(el, type, fn, capture){
el[unbind](prefix + type, fn, capture || false);
return fn;
};
}, {}],
16: [function(require, module, exports) {
/**
* Module dependencies.
*/
var closest = require('closest')
, event = require('event');
/**
* Delegate event `type` to `selector`
* and invoke `fn(e)`. A callback function
* is returned which may be passed to `.unbind()`.
*
* @param {Element} el
* @param {String} selector
* @param {String} type
* @param {Function} fn
* @param {Boolean} capture
* @return {Function}
* @api public
*/
exports.bind = function(el, selector, type, fn, capture){
return event.bind(el, type, function(e){
var target = e.target || e.srcElement;
e.delegateTarget = closest(target, selector, true, el);
if (e.delegateTarget) fn.call(el, e);
}, capture);
};
/**
* Unbind event `type`'s callback `fn`.
*
* @param {Element} el
* @param {String} type
* @param {Function} fn
* @param {Boolean} capture
* @api public
*/
exports.unbind = function(el, type, fn, capture){
event.unbind(el, type, fn, capture);
};
}, {"closest":10,"event":15}],
10: [function(require, module, exports) {
var matches = require('matches-selector')
module.exports = function (element, selector, checkYoSelf, root) {
element = checkYoSelf ? {parentNode: element} : element
root = root || document
// Make sure `element !== document` and `element != null`
// otherwise we get an illegal invocation
while ((element = element.parentNode) && element !== document) {
if (matches(element, selector))
return element
// After `matches` on the edge case that
// the selector matches the root
// (when the root is not the document)
if (element === root)
return
}
}
}, {"matches-selector":17}],
17: [function(require, module, exports) {
/**
* Module dependencies.
*/
var query = require('query');
/**
* Element prototype.
*/
var proto = Element.prototype;
/**
* Vendor function.
*/
var vendor = proto.matches
|| proto.webkitMatchesSelector
|| proto.mozMatchesSelector
|| proto.msMatchesSelector
|| proto.oMatchesSelector;
/**
* Expose `match()`.
*/
module.exports = match;
/**
* Match `el` to `selector`.
*
* @param {Element} el
* @param {String} selector
* @return {Boolean}
* @api public
*/
function match(el, selector) {
if (!el || el.nodeType !== 1) return false;
if (vendor) return vendor.call(el, selector);
var nodes = query.all(selector, el.parentNode);
for (var i = 0; i < nodes.length; ++i) {
if (nodes[i] == el) return true;
}
return false;
}
}, {"query":18}],
18: [function(require, module, exports) {
function one(selector, el) {
return el.querySelector(selector);
}
exports = module.exports = function(selector, el){
el = el || document;
return one(selector, el);
};
exports.all = function(selector, el){
el = el || document;
return el.querySelectorAll(selector);
};
exports.engine = function(obj){
if (!obj.one) throw new Error('.one callback required');
if (!obj.all) throw new Error('.all callback required');
one = obj.one;
exports.all = obj.all;
return exports;
};
}, {}],
9: [function(require, module, exports) {
/**
* Expose `Emitter`.
*/
module.exports = Emitter;
/**
* Initialize a new `Emitter`.
*
* @api public
*/
function Emitter(obj) {
if (obj) return mixin(obj);
};
/**
* Mixin the emitter properties.
*
* @param {Object} obj
* @return {Object}
* @api private
*/
function mixin(obj) {
for (var key in Emitter.prototype) {
obj[key] = Emitter.prototype[key];
}
return obj;
}
/**
* Listen on the given `event` with `fn`.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.on =
Emitter.prototype.addEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
(this._callbacks[event] = this._callbacks[event] || [])
.push(fn);
return this;
};
/**
* Adds an `event` listener that will be invoked a single
* time then automatically removed.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.once = function(event, fn){
var self = this;
this._callbacks = this._callbacks || {};
function on() {
self.off(event, on);
fn.apply(this, arguments);
}
on.fn = fn;
this.on(event, on);
return this;
};
/**
* Remove the given callback for `event` or all
* registered callbacks.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.off =
Emitter.prototype.removeListener =
Emitter.prototype.removeAllListeners =
Emitter.prototype.removeEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
// all
if (0 == arguments.length) {
this._callbacks = {};
return this;
}
// specific event
var callbacks = this._callbacks[event];
if (!callbacks) return this;
// remove all handlers
if (1 == arguments.length) {
delete this._callbacks[event];
return this;
}
// remove specific handler
var cb;
for (var i = 0; i < callbacks.length; i++) {
cb = callbacks[i];
if (cb === fn || cb.fn === fn) {
callbacks.splice(i, 1);
break;
}
}
return this;
};
/**
* Emit `event` with the given args.
*
* @param {String} event
* @param {Mixed} ...
* @return {Emitter}
*/
Emitter.prototype.emit = function(event){
this._callbacks = this._callbacks || {};
var args = [].slice.call(arguments, 1)
, callbacks = this._callbacks[event];
if (callbacks) {
callbacks = callbacks.slice(0);
for (var i = 0, len = callbacks.length; i < len; ++i) {
callbacks[i].apply(this, args);
}
}
return this;
};
/**
* Return array of callbacks for `event`.
*
* @param {String} event
* @return {Array}
* @api public
*/
Emitter.prototype.listeners = function(event){
this._callbacks = this._callbacks || {};
return this._callbacks[event] || [];
};
/**
* Check if this emitter has `event` handlers.
*
* @param {String} event
* @return {Boolean}
* @api public
*/
Emitter.prototype.hasListeners = function(event){
return !! this.listeners(event).length;
};
}, {}],
11: [function(require, module, exports) {
/**
* Module dependencies.
*/
var Dialog = require('dialog').Dialog
, events = require('event')
, q = require('query')
, html = require('./confirmation')
, inherit = require('inherit');
/**
* Expose `confirm()`.
*/
exports = module.exports = confirm;
/**
* Expose `Confirmation`.
*/
exports.Confirmation = Confirmation;
/**
* Return a new `Confirmation` with the given
* (optional) `title` and `msg`.
*
* @param {String} title or msg
* @param {String} msg
* @return {Confirmation}
* @api public
*/
function confirm(title, msg) {
switch (arguments.length) {
case 2:
return new Confirmation({ title: title, message: msg });
case 1:
return new Confirmation({ message: title });
}
}
/**
* Initialize a new `Confirmation` dialog.
*
* @param {Object} options
* @api public
*/
function Confirmation(options) {
Dialog.call(this, options);
this.focus('cancel');
}
/**
* Inherits from `Dialog.prototype`.
*/
inherit(Confirmation, Dialog);
/**
* Focus `type`, either "ok" or "cancel".
*
* @param {String} type
* @return {Confirmation}
* @api public
*/
Confirmation.prototype.focus = function(type){
this._focus = type;
return this;
};
/**
* Change "cancel" button `text`.
*
* @param {String} text
* @return {Confirmation}
* @api public
*/
Confirmation.prototype.cancel = function(text){
q('.cancel', this.el).innerHTML = text;
return this;
};
/**
* Change "ok" button `text`.
*
* @param {String} text
* @return {Confirmation}
* @api public
*/
Confirmation.prototype.ok = function(text){
q('.ok', this.el).innerHTML = text;
return this;
};
/**
* Show the confirmation dialog and invoke `fn(ok)`.
*
* @param {Function} fn
* @return {Confirmation} for chaining
* @api public
*/
Confirmation.prototype.show = function(fn){
Dialog.prototype.show.call(this);
q('.' + this._focus, this.el).focus();
this.callback = fn || function(){};
return this;
};
/**
* Render with the given `options`.
*
* Emits "cancel" event.
* Emits "ok" event.
*
* @param {Object} options
* @api public
*/
Confirmation.prototype.render = function(options){
var self = this;
Dialog.prototype.render.call(this, options);
this._classes.add('confirmation');
this.el.insertAdjacentHTML('beforeend', html);
this.on('close', function(){
self.emit('cancel');
self.callback(false);
});
this.on('escape', function(){
self.emit('cancel');
self.callback(false);
});
events.bind(q('.cancel', this.el), 'click', function(e){
e.preventDefault();
self.emit('cancel');
self.callback(false);
self.hide();
});
events.bind(q('.ok', this.el), 'click', function(e){
e.preventDefault();
self.emit('ok');
self.callback(true);
self.hide();
});
};
}, {"dialog":19,"event":15,"query":18,"./confirmation":20,"inherit":21}],
19: [function(require, module, exports) {
/**
* Module dependencies.
*/
var Emitter = require('emitter')
, overlay = require('overlay')
, domify = require('domify')
, events = require('event')
, classes = require('classes')
, query = require('query');
/**
* Active dialog.
*/
var active;
/**
* Expose `dialog()`.
*/
exports = module.exports = dialog;
/**
* Expose `Dialog`.
*/
exports.Dialog = Dialog;
/**
* Return a new `Dialog` with the given
* (optional) `title` and `msg`.
*
* @param {String} title or msg
* @param {String} msg
* @return {Dialog}
* @api public
*/
function dialog(title, msg){
switch (arguments.length) {
case 2:
return new Dialog({ title: title, message: msg });
case 1:
return new Dialog({ message: title });
}
};
/**
* Initialize a new `Dialog`.
*
* Options:
*
* - `title` dialog title
* - `message` a message to display
*
* Emits:
*
* - `show` when visible
* - `hide` when hidden
*
* @param {Object} options
* @api public
*/
function Dialog(options) {
Emitter.call(this);
options = options || {};
this.template = require('./template.html');
this.el = domify(this.template);
this._classes = classes(this.el);
this.render(options);
if (active && !active.hiding) active.hide();
if (exports.effect) this.effect(exports.effect);
this.on('escape', this.hide.bind(this));
active = this;
};
/**
* Inherit from `Emitter.prototype`.
*/
Dialog.prototype = new Emitter;
/**
* Render with the given `options`.
*
* @param {Object} options
* @api public
*/
Dialog.prototype.render = function(options){
var self = this
, el = self.el
, title = options.title
, titleEl = query('.title', el)
, pEl = query('p', el)
, msg = options.message;
events.bind(query('.close', el), 'click', function (ev) {
ev.preventDefault();
self.emit('close');
self.hide();
});
if (titleEl) {
if (!title) {
titleEl.parentNode.removeChild(titleEl);
} else {
titleEl.textContent = title;
}
}
// message
if ('string' == typeof msg) {
pEl.textContent = msg;
} else if (msg) {
pEl.parentNode.insertBefore(msg.el || msg, pEl);
pEl.parentNode.removeChild(pEl);
}
};
/**
* Enable the dialog close link.
*
* @return {Dialog} for chaining
* @api public
*/
Dialog.prototype.closable = function(){
return this.addClass('closable');
};
/**
* Add class `name`.
*
* @param {String} name
* @return {Dialog}
* @api public
*/
Dialog.prototype.addClass = function(name){
this._classes.add(name);
return this;
};
/**
* Set the effect to `type`.
*
* @param {String} type
* @return {Dialog} for chaining
* @api public
*/
Dialog.prototype.effect = function(type){
this._effect = type;
this.addClass(type);
return this;
};
/**
* Make it modal!
*
* @return {Dialog} for chaining
* @api public
*/
Dialog.prototype.modal = function(){
this._overlay = overlay();
return this;
};
/**
* Add an overlay.
*
* @return {Dialog} for chaining
* @api public
*/
Dialog.prototype.overlay = function(opts){
var self = this;
opts = opts || { closable: true };
var o = overlay(opts);
o.on('hide', function(){
self._overlay = null;
self.hide();
});
this._overlay = o;
return this;
};
/**
* Close the dialog when the escape key is pressed.
*
* @api public
*/
Dialog.prototype.escapable = function(){
var self = this;
// Save reference to remove listener later
self._escKeyCallback = self._escKeyCallback || function (e) {
e.which = e.which || e.keyCode;
if (27 !== e.which) return;
self.emit('escape');
};
events.bind(document, 'keydown', self._escKeyCallback);
return this;
};
/**
* Fixed dialogs position can be manipulated through CSS.
*
* @return {Dialog} for chaining
* @api public
*/
Dialog.prototype.fixed = function(){
this._fixed = true;
return this;
}
/**
* Show the dialog.
*
* Emits "show" event.
*
* @return {Dialog} for chaining
* @api public
*/
Dialog.prototype.show = function(){
var overlay = this._overlay;
var self = this;
// overlay
if (overlay) {
overlay.show();
this._classes.add('modal');
}
// escape
if (!overlay || overlay.closable) this.escapable();
// position
document.body.appendChild(this.el);
if (!this._fixed) {
setTimeout(function() {
self.el.style.marginLeft = -(self.el.offsetWidth / 2) + 'px'
}, 0);
}
this._classes.remove('hide');
this.emit('show');
return this;
};
/**
* Hide the overlay.
*
* @api private
*/
Dialog.prototype.hideOverlay = function(){
if (!this._overlay) return;
this._overlay.remove();
this._overlay = null;
};
/**
* Hide the dialog with optional delay of `ms`,
* otherwise the dialog is removed immediately.
*
* Emits "hide" event.
*
* @return {Number} ms
* @return {Dialog} for chaining
* @api public
*/
Dialog.prototype.hide = function(ms){
var self = this;
if (self._escKeyCallback) {
events.unbind(document, 'keydown', self._escKeyCallback);
}
// prevent thrashing - this isn't used
self.hiding = true;
// duration
if (ms) {
setTimeout(function(){
self.hide();
}, ms);
return self;
}
// hide / remove
self._classes.add('hide');
if (self._effect) {
setTimeout(function(){
self.remove();
}, 500);
} else {
self.remove();
}
// overlay
self.hideOverlay();
return self;
};
/**
* Hide the dialog without potential animation.
*
* @return {Dialog} for chaining
* @api public
*/
Dialog.prototype.remove = function(){
if (this.el.parentNode) {
this.emit('hide');
this.el.parentNode.removeChild(this.el);
}
return this;
};
}, {"emitter":9,"overlay":22,"domify":7,"event":15,"classes":23,"query":18,"./template.html":24}],
22: [function(require, module, exports) {
/**
* Module dependencies.
*/
var Emitter = require('emitter');
var tmpl = require('./template.html');
var domify = require('domify');
var event = require('event');
var classes = require('classes');
/**
* Expose `overlay()`.
*/
exports = module.exports = overlay;
/**
* Expose `Overlay`.
*/
exports.Overlay = Overlay;
/**
* Return a new `Overlay` with the given `options`.
*
* @param {Object|Element} options
* @return {Overlay}
* @api public
*/
function overlay(options){
options = options || {};
// element
if (options.nodeName) {
options = { target: options };
}
return new Overlay(options);
}
/**
* Initialize a new `Overlay`.
*
* @param {Object} options
* @api public
*/
function Overlay(options) {
Emitter.call(this);
options = options || {};
this.target = options.target || document.body;
this.closable = options.closable;
this.el = domify(tmpl);
this.target.appendChild(this.el);
if (this.closable) {
event.bind(this.el, 'click', this.hide.bind(this));
classes(this.el).add('closable');
}
}
/**
* Mixin emitter.
*/
Emitter(Overlay.prototype);
/**
* Show the overlay.
*
* Emits "show" event.
*
* @return {Overlay}
* @api public
*/
Overlay.prototype.show = function(){
this.emit('show');
classes(this.el).remove('hidden');
return this;
};
/**
* Hide the overlay.
*
* Emits "hide" event.
*
* @return {Overlay}
* @api public
*/
Overlay.prototype.hide = function(){
this.emit('hide');
return this.remove();
};
/**
* Hide the overlay without emitting "hide".
*
* Emits "close" event.
*
* @return {Overlay}
* @api public
*/
Overlay.prototype.remove = function(){
var self = this;
this.emit('close');
classes(this.el).add('hidden');
setTimeout(function(){
self.target.removeChild(self.el);
}, 350);
return this;
};
}, {"emitter":25,"./template.html":26,"domify":27,"event":28,"classes":23}],
25: [function(require, module, exports) {
/**
* Expose `Emitter`.
*/
module.exports = Emitter;
/**
* Initialize a new `Emitter`.
*
* @api public
*/
function Emitter(obj) {
if (obj) return mixin(obj);
};
/**
* Mixin the emitter properties.
*
* @param {Object} obj
* @return {Object}
* @api private
*/
function mixin(obj) {
for (var key in Emitter.prototype) {
obj[key] = Emitter.prototype[key];
}
return obj;
}
/**
* Listen on the given `event` with `fn`.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.on =
Emitter.prototype.addEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
(this._callbacks[event] = this._callbacks[event] || [])
.push(fn);
return this;
};
/**
* Adds an `event` listener that will be invoked a single
* time then automatically removed.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.once = function(event, fn){
var self = this;
this._callbacks = this._callbacks || {};
function on() {
self.off(event, on);
fn.apply(this, arguments);
}
on.fn = fn;
this.on(event, on);
return this;
};
/**
* Remove the given callback for `event` or all
* registered callbacks.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.off =
Emitter.prototype.removeListener =
Emitter.prototype.removeAllListeners =
Emitter.prototype.removeEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
// all
if (0 == arguments.length) {
this._callbacks = {};
return this;
}
// specific event
var callbacks = this._callbacks[event];
if (!callbacks) return this;
// remove all handlers
if (1 == arguments.length) {
delete this._callbacks[event];
return this;
}
// remove specific handler
var cb;
for (var i = 0; i < callbacks.length; i++) {
cb = callbacks[i];
if (cb === fn || cb.fn === fn) {
callbacks.splice(i, 1);
break;
}
}
return this;
};
/**
* Emit `event` with the given args.
*
* @param {String} event
* @param {Mixed} ...
* @return {Emitter}
*/
Emitter.prototype.emit = function(event){
this._callbacks = this._callbacks || {};
var args = [].slice.call(arguments, 1)
, callbacks = this._callbacks[event];
if (callbacks) {
callbacks = callbacks.slice(0);
for (var i = 0, len = callbacks.length; i < len; ++i) {
callbacks[i].apply(this, args);
}
}
return this;
};
/**
* Return array of callbacks for `event`.
*
* @param {String} event
* @return {Array}
* @api public
*/
Emitter.prototype.listeners = function(event){
this._callbacks = this._callbacks || {};
return this._callbacks[event] || [];
};
/**
* Check if this emitter has `event` handlers.
*
* @param {String} event
* @return {Boolean}
* @api public
*/
Emitter.prototype.hasListeners = function(event){
return !! this.listeners(event).length;
};
}, {}],
26: [function(require, module, exports) {
module.exports = '<div class="overlay hidden"></div>\n';
}, {}],
27: [function(require, module, exports) {
/**
* Expose `parse`.
*/
module.exports = parse;
/**
* Wrap map from jquery.
*/
var map = {
legend: [1, '<fieldset>', '</fieldset>'],
tr: [2, '<table><tbody>', '</tbody></table>'],
col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
_default: [0, '', '']
};
map.td =
map.th = [3, '<table><tbody><tr>', '</tr></tbody></table>'];
map.option =
map.optgroup = [1, '<select multiple="multiple">', '</select>'];
map.thead =
map.tbody =
map.colgroup =
map.caption =
map.tfoot = [1, '<table>', '</table>'];
map.text =
map.circle =
map.ellipse =
map.line =
map.path =
map.polygon =
map.polyline =
map.rect = [1, '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">','</svg>'];
/**
* Parse `html` and return the children.
*
* @param {String} html
* @return {Array}
* @api private
*/
function parse(html) {
if ('string' != typeof html) throw new TypeError('String expected');
// tag name
var m = /<([\w:]+)/.exec(html);
if (!m) return document.createTextNode(html);
html = html.replace(/^\s+|\s+$/g, ''); // Remove leading/trailing whitespace
var tag = m[1];
// body support
if (tag == 'body') {
var el = document.createElement('html');
el.innerHTML = html;
return el.removeChild(el.lastChild);
}
// wrap map
var wrap = map[tag] || map._default;
var depth = wrap[0];
var prefix = wrap[1];
var suffix = wrap[2];
var el = document.createElement('div');
el.innerHTML = prefix + html + suffix;
while (depth--) el = el.lastChild;
// one element
if (el.firstChild == el.lastChild) {
return el.removeChild(el.firstChild);
}
// several elements
var fragment = document.createDocumentFragment();
while (el.firstChild) {
fragment.appendChild(el.removeChild(el.firstChild));
}
return fragment;
}
}, {}],
28: [function(require, module, exports) {
var bind = window.addEventListener ? 'addEventListener' : 'attachEvent',
unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent',
prefix = bind !== 'addEventListener' ? 'on' : '';
/**
* Bind `el` event `type` to `fn`.
*
* @param {Element} el
* @param {String} type
* @param {Function} fn
* @param {Boolean} capture
* @return {Function}
* @api public
*/
exports.bind = function(el, type, fn, capture){
el[bind](prefix + type, fn, capture || false);
return fn;
};
/**
* Unbind `el` event `type`'s callback `fn`.
*
* @param {Element} el
* @param {String} type
* @param {Function} fn
* @param {Boolean} capture
* @return {Function}
* @api public
*/
exports.unbind = function(el, type, fn, capture){
el[unbind](prefix + type, fn, capture || false);
return fn;
};
}, {}],
23: [function(require, module, exports) {
/**
* Module dependencies.
*/
var index = require('indexof');
/**
* Whitespace regexp.
*/
var re = /\s+/;
/**
* toString reference.
*/
var toString = Object.prototype.toString;
/**
* Wrap `el` in a `ClassList`.
*
* @param {Element} el
* @return {ClassList}
* @api public
*/
module.exports = function(el){
return new ClassList(el);
};
/**
* Initialize a new ClassList for `el`.
*
* @param {Element} el
* @api private
*/
function ClassList(el) {
if (!el) throw new Error('A DOM element reference is required');
this.el = el;
this.list = el.classList;
}
/**
* Add class `name` if not already present.
*
* @param {String} name
* @return {ClassList}
* @api public
*/
ClassList.prototype.add = function(name){
// classList
if (this.list) {
this.list.add(name);
return this;
}
// fallback
var arr = this.array();
var i = index(arr, name);
if (!~i) arr.push(name);
this.el.className = arr.join(' ');
return this;
};
/**
* Remove class `name` when present, or
* pass a regular expression to remove
* any which match.
*
* @param {String|RegExp} name
* @return {ClassList}
* @api public
*/
ClassList.prototype.remove = function(name){
if ('[object RegExp]' == toString.call(name)) {
return this.removeMatching(name);
}
// classList
if (this.list) {
this.list.remove(name);
return this;
}
// fallback
var arr = this.array();
var i = index(arr, name);
if (~i) arr.splice(i, 1);
this.el.className = arr.join(' ');
return this;
};
/**
* Remove all classes matching `re`.
*
* @param {RegExp} re
* @return {ClassList}
* @api private
*/
ClassList.prototype.removeMatching = function(re){
var arr = this.array();
for (var i = 0; i < arr.length; i++) {
if (re.test(arr[i])) {
this.remove(arr[i]);
}
}
return this;
};
/**
* Toggle class `name`, can force state via `force`.
*
* For browsers that support classList, but do not support `force` yet,
* the mistake will be detected and corrected.
*
* @param {String} name
* @param {Boolean} force
* @return {ClassList}
* @api public
*/
ClassList.prototype.toggle = function(name, force){
// classList
if (this.list) {
if ("undefined" !== typeof force) {
if (force !== this.list.toggle(name, force)) {
this.list.toggle(name); // toggle again to correct
}
} else {
this.list.toggle(name);
}
return this;
}
// fallback
if ("undefined" !== typeof force) {
if (!force) {
this.remove(name);
} else {
this.add(name);
}
} else {
if (this.has(name)) {
this.remove(name);
} else {
this.add(name);
}
}
return this;
};
/**
* Return an array of classes.
*
* @return {Array}
* @api public
*/
ClassList.prototype.array = function(){
var str = this.el.className.replace(/^\s+|\s+$/g, '');
var arr = str.split(re);
if ('' === arr[0]) arr.shift();
return arr;
};
/**
* Check if class `name` is present.
*
* @param {String} name
* @return {ClassList}
* @api public
*/
ClassList.prototype.has =
ClassList.prototype.contains = function(name){
return this.list
? this.list.contains(name)
: !! ~index(this.array(), name);
};
}, {"indexof":14}],
24: [function(require, module, exports) {
module.exports = '<div class="dialog hide">\n <div class="content">\n <span class="title">Title</span>\n <a href="#" class="close">×<em>close</em></a>\n <div class="body">\n <p>Message</p>\n </div>\n </div>\n</div>\n';
}, {}],
20: [function(require, module, exports) {
module.exports = '<div class="confirmation-actions">\n <button class="cancel">Cancel</button>\n <button class="ok main">Ok</button>\n</div>';
}, {}],
21: [function(require, module, exports) {
module.exports = function(a, b){
var fn = function(){};
fn.prototype = b.prototype;
a.prototype = new fn;
a.prototype.constructor = a;
};
}, {}],
12: [function(require, module, exports) {
var focusable = require('focusable-elements');
var ev = require('event');
var normalize = require('normalize');
/**
* Expose `trapFocus`
*/
module.exports = focusTrap;
/**
* Trap focus within `container`
*
* @api public
* @param {HTMLElement} container
* @return {Function} keyboard listener
*/
function focusTrap(container) {
var elements = focusable(container);
var first = elements[0];
var last = elements[elements.length - 1];
return ev.bind(container, 'keydown', function (keyboardEvent) {
keyboardEvent = normalize(keyboardEvent);
if (9 !== keyboardEvent.which) return;
if (keyboardEvent.shiftKey) {
if (first === keyboardEvent.target) {
keyboardEvent.preventDefault();
last.focus();
}
} else {
if (last === keyboardEvent.target) {
keyboardEvent.preventDefault();
first.focus();
}
}
});
}
}, {"focusable-elements":29,"event":15,"normalize":30}],
29: [function(require, module, exports) {
var isFocusable = require('is-focusable');
var descendants = require('descendants');
module.exports = elements;
/**
* Get all focusable elements within `container`
*
* @api public
* @param {HTMLElement} container
* @return {Array}
*/
function elements(container) {
var children = descendants(container);
var focusable = [];
for (var i = 0, len = children.length; i < len; i++) {
if (isFocusable(children[i])) focusable.push(children[i]);
}
return focusable;
}
}, {"is-focusable":31,"descendants":32}],
31: [function(require, module, exports) {
var selector = /input|select|textarea|button/i;
module.exports = isFocusable;
/**
* Check if the given `element` can receive focus
*
* @api public
* @param {HTMLElement} element
* @return {Boolean}
*/
function isFocusable(element) {
// tabindex
if (element.hasAttribute('tabindex')) {
var tabindex = element.getAttribute('tabindex');
if (!isNaN(tabindex)) {
return true;
}
}
// natively focusable, but only when enabled
var name = element.nodeName;
if (selector.test(name)) {
return element.type.toLowerCase() !== 'hidden'
&& !element.disabled;
}
// anchors must have an href
if (name === 'A') {
return !!element.href;
}
return false;
}
}, {}],
32: [function(require, module, exports) {
/**
* Convert the given `nodes` to an Array, filtering
* out text and comment nodes
*
* @api private
* @param {NodeList} nodes
* @return {Array}
*/
function array(nodes) {
var arr = [];
for (var i = 0, len = nodes.length; i < len; i++) {
var type = nodes[i].nodeType;
type !== 8 && type !== 3 && arr.push(nodes[i]);
}
return arr;
}
/**
* Get an Array of descendants from `element`
*
* @api public
* @param {HTMLElement} element
* @param {Boolean} [direct]
* @return {Array}
*/
exports = module.exports = function (element, direct) {
var decendants = direct
? element.childNodes
: element.getElementsByTagName('*');
return array(decendants);
};
}, {}],
30: [function(require, module, exports) {
/**
* Normalize the events provided to `fn`
*
* @api public
* @param {Function|Event} fn
* @return {Function|Event}
*/
exports = module.exports = function (fn) {
// handle functions which are passed an event
if (typeof fn === 'function') {
return function (event) {
event = exports.normalize(event);
fn.call(this, event);
};
}
// just normalize the event
return exports.normalize(fn);
};
/**
* Normalize the given `event`
*
* @api private
* @param {Event} event
* @return {Event}
*/
exports.normalize = function (event) {
event = event || window.event;
event.target = event.target || event.srcElement;
event.which = event.which || event.keyCode || event.charCode;
event.preventDefault = event.preventDefault || function () {
this.returnValue = false;
};
event.stopPropagation = event.stopPropagation || function () {
this.cancelBubble = true;
};
return event;
};
}, {}],
13: [function(require, module, exports) {
/**
* toString.
*/
var toString = window.JSON
? JSON.stringify
: function(_){ return String(_); };
/**
* Export `fmt`
*/
module.exports = fmt;
/**
* Formatters
*/
fmt.o = toString;
fmt.s = String;
fmt.d = parseInt;
/**
* Format the given `str`.
*
* @param {String} str
* @param {...} args
* @return {String}
* @api public
*/
function fmt(str){
var args = [].slice.call(arguments, 1);
var j = 0;
return str.replace(/%([a-z])/gi, function(_, f){
return fmt[f]
? fmt[f](args[j++])
: _ + f;
});
}
}, {}],
4: [function(require, module, exports) {
/**
* Module dependencies.
*/
var Emitter = require('component/emitter');
var events = require('component/events');
/**
* Expose `Cart`.
*/
module.exports = Cart;
/**
* Create a new `Cart`.
*
* @api public
*/
function Cart() {
this.el = document.querySelector('#cart');
this.items = [];
this.listEl = this.el.querySelector('.list tbody');
this.totalEl = this.el.querySelector('#cart-total .total');
this.logEl = this.el.querySelector('#cart-log');
this.events = events(this.el, this);
}
/**
* Mixin `Emitter`.
*/
Emitter(Cart.prototype);
/**
* Update the text content of the cart's total.
*
* @return {Cart}
* @api public
*/
Cart.prototype.updateTotal = function () {
var total = 0;
this.items.forEach(function (item) {
total += parseFloat(item.totalPrice);
});
this.totalEl.innerHTML = Number(total).toFixed(2);
// Update the total in the table caption as well
var captionTotal = this.el.querySelector('caption .total');
captionTotal.innerHTML = Number(total).toFixed(2);
return this;
};
/**
* Focuses the outermost wrapper of the shopping cart.
*
* @return {Cart}
* @api public
*/
Cart.prototype.focusWrapper = function () {
var wrapper = this.el.querySelector('.list');
wrapper.focus();
return this;
};
/**
* Assertively announces a given `msg`.
*
* @param {String} msg
* @return {Cart}
* @api public
*/
Cart.prototype.log = function (msg) {
msg = [msg, '<span>Cart Total: ' + this.totalEl.innerHTML + ' dollars</span>'].join(' ');
this.logEl.innerHTML = msg;
return this;
};
}, {"component/emitter":9,"component/events":8}]}, {}, {"1":""})
CSS Source Code
:focus {
outline: 2px solid;
}
.clipped {
clip: rect(1px, 1px, 1px, 1px);
position: absolute;
overflow: hidden;
width: 0;
}
.dollars::before {
content: '$';
}
#cart .list {
width: 100%;
padding: 0;
list-style: none;
border-bottom: 1px dotted #ccc;
}
#cart .item {
padding: 10px 0;
border-top: 1px dotted #ccc;
}
#cart th, #cart td {
padding: 0 10px;
}
#cart table > tbody > tr:nth-child(even) {
background-color: #f6f6f6;
/* background-color: #f9f9f9; */
}
#cart .thumb {
width: 100px;
padding: 10px;
}
#cart .quantity input {
width: 50px;
padding: 0 0 0 5px;
}
#cart .quantity input:focus, #cart .quantity input:hover {
border: 1px #333 solid !important;
outline: 2px solid #467310 !important;
outline-offset: 1.5px;
}
#cart .actions {
padding: 10px;
width: 250px;
}
#cart .actions ul,
#cart .extras ul {
list-style: none;
margin: 0;
padding: 0;
font-size: .9em;
}
#cart .actions button {
background-color: inherit;
border: none;
text-decoration: underline;
border: none!important;
}
#cart .actions button:hover,
#cart .actions button:focus {
color: blue;
}
#cart .extras {
width: 150px;
}
#cart .extras li {
display: none;
}
#cart .extras li.active {
display: block;
}
#cart td.total {
width: 80px;
}
#cart .summary::after {
clear: both;
content: '';
display: table;
}
#cart-total {
float: right;
padding: 20px;
font-size: 1.2em;
font-weight: 600;
}
.confirmation {
box-shadow: 0px 0px 8px #ccc;
background-color: #fff;
border: 1px solid #ccc;
padding: 20px;
height: 150px;
width: 400px;
position: fixed;
top: 20vh;
left: calc(50vw - 200px);
}
.confirmation .close {
display: none;
}
.confirmation-actions {
position: absolute;
right: 20px;
bottom: 20px;
}