PageSourceSearch

https://www.stonebridgeoldeoaks.com/components/com_rokquickcart/assets/js/simplecart/simpleCart.js

js stonebridgeoldeoaks.com collected 2026-10-04 10:25:23 UTC 58,229 bytes, 1,951 lines download raw bytes

1/*~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
2	Copyright (c) 2012 Brett Wejrowski
3
4	wojodesign.com
5	simplecartjs.org
6	http://github.com/wojodesign/simplecart-js
7
8	VERSION 3.0.5
9
10	Dual licensed under the MIT or GPL licenses.
11~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~*/
12/*jslint browser: true, unparam: true, white: true, nomen: true, regexp: true, maxerr: 50, indent: 4 */
13
14(function (window, document) {
15	/*global HTMLElement */
16
17	var typeof_string			= typeof "",
18		typeof_undefined		= typeof undefined,
19		typeof_function			= typeof function () {},
20		typeof_object			= typeof {},
21		isTypeOf				= function (item, type) { return typeof item === type; },
22		isString				= function (item) { return isTypeOf(item, typeof_string); },
23		isUndefined				= function (item) { return isTypeOf(item, typeof_undefined); },
24		isFunction				= function (item) { return isTypeOf(item, typeof_function); },
25
26		isObject				= function (item) { return isTypeOf(item, typeof_object); },
27		//Returns true if it is a DOM element
28		isElement				= function (o) {
29			return typeof HTMLElement === "object" ? o instanceof HTMLElement : typeof o === "object" && o.nodeType === 1 && typeof o.nodeName === "string";
30		},
31
32
33
34		generateSimpleCart = function (space) {
35
36			// stealing this from selectivizr
37			var selectorEngines = {
38				"MooTools"							: "$$",
39				"Prototype"							: "$$",
40				"jQuery"							: "*"
41			},
42
43
44				// local variables for internal use
45				item_id					= 0,
46				item_id_namespace		= "SCI-",
47				sc_items				= {},
48				namespace				= space || "simpleCart",
49				selectorFunctions		= {},
50				eventFunctions			= {},
51				baseEvents				= {},
52
53				// local references
54				localStorage			= window.localStorage,
55				console					= window.console || { msgs: [], log: function (msg) { console.msgs.push(msg); } },
56
57				// used in views
58				_VALUE_		= 'value',
59				_TEXT_		= 'text',
60				_HTML_		= 'html',
61				_CLICK_		= 'click',
62
63				// Currencies
64				currencies = {
65					"USD": { code: "USD", symbol: "$", name: "US Dollar" },
66					"AUD": { code: "AUD", symbol: "$", name: "Australian Dollar" },
67					"BRL": { code: "BRL", symbol: "R$", name: "Brazilian Real" },
68					"CAD": { code: "CAD", symbol: "$", name: "Canadian Dollar" },
69					"CZK": { code: "CZK", symbol: " Kč", name: "Czech Koruna", after: true },
70					"DKK": { code: "DKK", symbol: "DKK ", name: "Danish Krone" },
71					"EUR": { code: "EUR", symbol: "€", name: "Euro" },
72					"HKD": { code: "HKD", symbol: "$", name: "Hong Kong Dollar" },
73					"HUF": { code: "HUF", symbol: "Ft ", name: "Hungarian Forint", accuracy: 0 },
74					"ILS": { code: "ILS", symbol: "₪", name: "Israeli New Sheqel" },
75					"JPY": { code: "JPY", symbol: "¥", name: "Japanese Yen", accuracy: 0 },
76					"MYR": { code: "MYR", symbol: "RM ", name: "Malaysian Ringgit" },
77					"MXN": { code: "MXN", symbol: "$", name: "Mexican Peso" },
78					"NOK": { code: "NOK", symbol: "NOK ", name: "Norwegian Krone" },
79					"NZD": { code: "NZD", symbol: "$", name: "New Zealand Dollar" },
80					"PHP": { code: "PHP", symbol: "₱", name: "Philippine Pesos" },
81					"PLN": { code: "PLN", symbol: "PLN ", name: "Polish Zloty" },
82					"GBP": { code: "GBP", symbol: "£", name: "Pound Sterling" },
83					"RUB": { code: "RUB", symbol: "₽", name: "Russian Ruble" },
84					"SGD": { code: "SGD", symbol: "$", name: "Singapore Dollar" },
85					"SEK": { code: "SEK", symbol: "SEK ", name: "Swedish Krona" },
86					"CHF": { code: "CHF", symbol: "CHF ", name: "Swiss Franc" },
87					"TWD": { code: "TWD", symbol: "NT$", name: "Taiwan New Dollar", accuracy: 0 },
88					"THB": { code: "THB", symbol: "฿", name: "Thai Baht" },
89					"TRY": { code: "TRY", symbol: "₺", name: "Turkish Lira" },
90					"BTC": { code: "BTC", symbol: " BTC", name: "Bitcoin", accuracy: 4, after: true	}
91				},
92
93				// default options
94				settings = {
95					checkout				: { type: "PayPal", email: "[email protected]" },
96					currency				: "USD",
97					language				: "english-us",
98
99					cartStyle				: "div",
100					cartColumns			: [
101						{ attr: "name", label: "Name" },
102						{ attr: "price", label: "Price", view: 'currency' },
103						{ view: "decrement", label: false },
104						{ attr: "quantity", label: "Qty" },
105						{ view: "increment", label: false },
106						{ attr: "total", label: "SubTotal", view: 'currency' },
107						{ view: "remove", text: "Remove", label: false }
108					],
109
110					excludeFromCheckout	: ['thumb'],
111
112					shippingFlatRate		: 0,
113					shippingQuantityRate	: 0,
114					shippingTotalRate		: 0,
115					shippingCustom		: null,
116
117					taxRate				: 0,
118
119					taxShipping			: false,
120
121					data				: {}
122
123				},
124
125
126				// main simpleCart object, function call is used for setting options
127				simpleCart = function (options) {
128					// shortcut for simpleCart.ready
129					if (isFunction(options)) {
130						return simpleCart.ready(options);
131					}
132
133					// set options
134					if (isObject(options)) {
135						return simpleCart.extend(settings, options);
136					}
137				},
138
139				// selector engine
140				$engine,
141
142				// built in cart views for item cells
143				cartColumnViews;
144
145			// function for extending objects
146			simpleCart.extend = function (target, opts) {
147				var next;
148
149				if (isUndefined(opts)) {
150					opts = target;
151					target = simpleCart;
152				}
153
154				for (next in opts) {
155					if (Object.prototype.hasOwnProperty.call(opts, next)) {
156						target[next] = opts[next];
157					}
158				}
159				return target;
160			};
161
162			// create copy function
163			simpleCart.extend({
164				copy: function (n) {
165					var cp = generateSimpleCart(n);
166					cp.init();
167					return cp;
168				}
169			});
170
171			// add in the core functionality
172			simpleCart.extend({
173
174				isReady: false,
175
176				// this is where the magic happens, the add function
177				add: function (values, opt_quiet) {
178					var info		= values || {},
179						newItem		= new simpleCart.Item(info),
180						addItem 	= true,
181						// optionally supress event triggers
182						quiet 		= opt_quiet === true ? opt_quiet : false,
183						oldItem;
184
185					// trigger before add event
186					if (!quiet) {
187					  	addItem = simpleCart.trigger('beforeAdd', [newItem]);
188
189						if (addItem === false) {
190							return false;
191						}
192					}
193
194					// if the new item already exists, increment the value
195					oldItem = simpleCart.has(newItem);
196					if (oldItem) {
197						oldItem.increment(newItem.quantity());
198						newItem = oldItem;
199
200					// otherwise add the item
201					} else {
202						sc_items[newItem.id()] = newItem;
203					}
204
205					// update the cart
206					simpleCart.update();
207
208					if (!quiet) {
209						// trigger after add event
210						simpleCart.trigger('afterAdd', [newItem, isUndefined(oldItem)]);
211					}
212
213					// return a reference to the added item
214					return newItem;
215				},
216
217
218				// iteration function
219				each: function (array, callback) {
220					var next,
221						x = 0,
222						result,
223						cb,
224						items;
225
226					if (isFunction(array)) {
227						cb = array;
228						items = sc_items;
229					} else if (isFunction(callback)) {
230						cb = callback;
231						items = array;
232					} else {
233						return;
234					}
235
236					for (next in items) {
237						if (Object.prototype.hasOwnProperty.call(items, next)) {
238							result = cb.call(simpleCart, items[next], x, next);
239							if (result === false) {
240								return;
241							}
242							x += 1;
243						}
244					}
245				},
246
247				find: function (id) {
248					var items = [];
249
250					// return object for id if it exists
251					if (isObject(sc_items[id])) {
252						return sc_items[id];
253					}
254					// search through items with the given criteria
255					if (isObject(id)) {
256						simpleCart.each(function (item) {
257							var match = true;
258							simpleCart.each(id, function (val, x, attr) {
259
260								if (isString(val)) {
261									// less than or equal to
262									if (val.match(/<=.*/)) {
263										val = parseFloat(val.replace('<=', ''));
264										if (!(item.get(attr) && parseFloat(item.get(attr)) <= val)) {
265											match = false;
266										}
267
268									// less than
269									} else if (val.match(/</)) {
270										val = parseFloat(val.replace('<', ''));
271										if (!(item.get(attr) && parseFloat(item.get(attr)) < val)) {
272											match = false;
273										}
274
275									// greater than or equal to
276									} else if (val.match(/>=/)) {
277										val = parseFloat(val.replace('>=', ''));
278										if (!(item.get(attr) && parseFloat(item.get(attr)) >= val)) {
279											match = false;
280										}
281
282									// greater than
283									} else if (val.match(/>/)) {
284										val = parseFloat(val.replace('>', ''));
285										if (!(item.get(attr) && parseFloat(item.get(attr)) > val)) {
286											match = false;
287										}
288
289									// equal to
290									} else if (!(item.get(attr) && item.get(attr) === val)) {
291										match = false;
292									}
293
294								// equal to non string
295								} else if (!(item.get(attr) && item.get(attr) === val)) {
296									match = false;
297								}
298
299								return match;
300							});
301
302							// add the item if it matches
303							if (match) {
304								items.push(item);
305							}
306						});
307						return items;
308					}
309
310					// if no criteria is given we return all items
311					if (isUndefined(id)) {
312
313						// use a new array so we don't give a reference to the
314						// cart's item array
315						simpleCart.each(function (item) {
316							items.push(item);
317						});
318						return items;
319					}
320
321					// return empty array as default
322					return items;
323				},
324
325				// return all items
326				items: function () {
327					return this.find();
328				},
329
330				// check to see if item is in the cart already
331				has: function (item) {
332					var match = false;
333
334					simpleCart.each(function (testItem) {
335						if (testItem.equals(item)) {
336							match = testItem;
337						}
338					});
339					return match;
340				},
341
342				// empty the cart
343				empty: function () {
344					// remove each item individually so we see the remove events
345					var newItems = {};
346					simpleCart.each(function (item) {
347						// send a param of true to make sure it doesn't
348						// update after every removal
349						// keep the item if the function returns false,
350						// because we know it has been prevented
351						// from being removed
352						if (item.remove(true) === false) {
353							newItems[item.id()] = item
354						}
355					});
356					sc_items = newItems;
357					simpleCart.update();
358				},
359
360
361				// functions for accessing cart info
362				quantity: function () {
363					var quantity = 0;
364					simpleCart.each(function (item) {
365						quantity += item.quantity();
366					});
367					return quantity;
368				},
369
370				total: function () {
371					var total = 0;
372					simpleCart.each(function (item) {
373						total += item.total();
374					});
375					return total;
376				},
377
378				grandTotal: function () {
379					return simpleCart.total() + simpleCart.tax() + simpleCart.shipping();
380				},
381
382
383				// updating functions
384				update: function () {
385					simpleCart.save();
386					simpleCart.trigger("update");
387				},
388
389				init: function () {
390					simpleCart.load();
391					simpleCart.update();
392					simpleCart.ready();
393				},
394
395				// view management
396				$: function (selector) {
397					return new simpleCart.ELEMENT(selector);
398				},
399
400				$create: function (tag) {
401					var element = simpleCart.$(document.createElement(tag));
402					if (!element.el.length) element.el = new Element(tag);
403					return element;
404				},
405
406				setupViewTool: function () {
407					var members, member, context = window, engine;
408
409					// Determine the "best fit" selector engine
410					for (engine in selectorEngines) {
411						if (Object.prototype.hasOwnProperty.call(selectorEngines, engine) && window[engine]) {
412							members = selectorEngines[engine].replace("*", engine).split(".");
413							member = members.shift();
414							if (member) {
415								context = context[member];
416							}
417							if (typeof context === "function") {
418								// set the selector engine and extend the prototype of our
419								// element wrapper class
420								$engine = context;
421								simpleCart.extend(simpleCart.ELEMENT._, selectorFunctions[engine]);
422								return;
423							}
424						}
425					}
426				},
427
428				// return a list of id's in the cart
429				ids: function () {
430					var ids = [];
431					simpleCart.each(function (item) {
432						ids.push(item.id());
433					});
434					return ids;
435
436				},
437
438
439				// storage
440				save: function () {
441					simpleCart.trigger('beforeSave');
442
443					var items = {};
444
445					// save all the items
446					simpleCart.each(function (item) {
447						items[item.id()] = simpleCart.extend(item.fields(), item.options());
448					});
449
450					localStorage.setItem(namespace + "_items", JSON.stringify(items));
451
452					simpleCart.trigger('afterSave');
453				},
454
455				load: function () {
456
457					// empty without the update
458					sc_items = {};
459
460					var items = localStorage.getItem(namespace + "_items");
461
462					if (!items) {
463						return;
464					}
465
466					// we wrap this in a try statement so we can catch
467					// any json parsing errors. no more stick and we
468					// have a playing card pluckin the spokes now...
469					// soundin like a harley.
470					try {
471						simpleCart.each(JSON.parse(items), function (item) {
472							simpleCart.add(item, true);
473						});
474					} catch (e){
475						simpleCart.error( "Error Loading data: " + e );
476					}
477
478
479					simpleCart.trigger('load');
480				},
481
482				// ready function used as a shortcut for bind('ready',fn)
483				ready: function (fn) {
484
485					if (isFunction(fn)) {
486						// call function if already ready already
487						if (simpleCart.isReady) {
488							fn.call(simpleCart);
489
490						// bind if not ready
491						} else {
492							simpleCart.bind('ready', fn);
493						}
494
495					// trigger ready event
496					} else if (isUndefined(fn) && !simpleCart.isReady) {
497						simpleCart.trigger('ready');
498						simpleCart.isReady = true;
499					}
500
501				},
502
503
504				error: function (message) {
505					var msg = "";
506					if (isString(message)) {
507						msg = message;
508					} else if (isObject(message) && isString(message.message)) {
509						msg = message.message;
510					}
511					try { console.log("simpleCart(js) Error: " + msg); } catch (e) {}
512					simpleCart.trigger('error', [message]);
513				}
514			});
515
516
517			/*******************************************************************
518			 *	TAX AND SHIPPING
519			 *******************************************************************/
520			simpleCart.extend({
521
522				// TODO: tax and shipping
523				tax: function () {
524					var totalToTax = settings.taxShipping ? simpleCart.total() + simpleCart.shipping() : simpleCart.total(),
525						cost = simpleCart.taxRate() * totalToTax;
526
527					simpleCart.each(function (item) {
528						if (item.get('tax')) {
529							cost += item.get('tax');
530						} else if (item.get('taxRate')) {
531							cost += item.get('taxRate') * item.total();
532						}
533					});
534					return parseFloat(cost);
535				},
536
537				taxRate: function () {
538					return settings.taxRate || 0;
539				},
540
541				shipping: function (opt_custom_function) {
542
543					// shortcut to extend options with custom shipping
544					if (isFunction(opt_custom_function)) {
545						simpleCart({
546							shippingCustom: opt_custom_function
547						});
548						return;
549					}
550
551					var cost = settings.shippingQuantityRate * simpleCart.quantity() +
552							settings.shippingTotalRate * simpleCart.total() +
553							settings.shippingFlatRate;
554
555					if (isFunction(settings.shippingCustom)) {
556						cost += settings.shippingCustom.call(simpleCart);
557					}
558
559					simpleCart.each(function (item) {
560						cost += parseFloat(item.get('shipping') || 0);
561					});
562					return parseFloat(cost);
563				}
564
565			});
566
567			/*******************************************************************
568			 *	CART VIEWS
569			 *******************************************************************/
570
571			// built in cart views for item cells
572			cartColumnViews = {
573				attr: function (item, column) {
574					return item.get(column.attr) || "";
575				},
576
577				currency: function (item, column) {
578					return simpleCart.toCurrency(item.get(column.attr) || 0);
579				},
580
581				link: function (item, column) {
582					return "<a href='" + item.get(column.attr) + "'>" + column.text + "</a>";
583				},
584
585				decrement: function (item, column) {
586					return "<a href='javascript:;' class='" + namespace + "_decrement'>" + (column.text || "-") + "</a>";
587				},
588
589				increment: function (item, column) {
590					return "<a href='javascript:;' class='" + namespace + "_increment'>" + (column.text || "+") + "</a>";
591				},
592
593				image: function (item, column) {
594					return "<img src='" + item.get(column.attr) + "'/>";
595				},
596
597				input: function (item, column) {
598					return "<input type='text' value='" + item.get(column.attr) + "' class='" + namespace + "_input'/>";
599				},
600
601				remove: function (item, column) {
602					return "<a href='javascript:;' class='" + namespace + "_remove'>" + (column.text || "X") + "</a>";
603				}
604			};
605
606			// cart column wrapper class and functions
607			function cartColumn(opts) {
608				var options = opts || {};
609				return simpleCart.extend({
610					attr			: "",
611					label			: "",
612					view			: "attr",
613					text			: "",
614					className		: "",
615					hide			: false
616				}, options);
617			}
618
619			function cartCellView(item, column) {
620				var viewFunc = isFunction(column.view) ? column.view : isString(column.view) && isFunction(cartColumnViews[column.view]) ? cartColumnViews[column.view] : cartColumnViews.attr;
621
622				return viewFunc.call(simpleCart, item, column);
623			}
624
625
626			simpleCart.extend({
627
628				// write out cart
629				writeCart: function (selector) {
630					var TABLE = settings.cartStyle.toLowerCase(),
631						isTable = TABLE === 'table',
632						TR = isTable ? "tr" : "div",
633						TH = isTable ? 'th' : 'div',
634						TD = isTable ? 'td' : 'div',
635						THEAD = isTable ? 'thead' : 'div',
636						cart_container = simpleCart.$create(TABLE),
637						thead_container = simpleCart.$create(THEAD),
638						header_container = simpleCart.$create(TR).addClass('headerRow'),
639						container = simpleCart.$(selector),
640						column,
641						klass,
642						label,
643						x,
644						xlen;
645
646					container.html(' ').append(cart_container);
647
648					cart_container.append(thead_container);
649
650					thead_container.append(header_container);
651
652
653					// create header
654					for (x = 0, xlen = settings.cartColumns.length; x < xlen; x += 1) {
655						column	= cartColumn(settings.cartColumns[x]);
656						klass	=  "item-" + (column.attr || column.view || column.label || column.text || "cell") + " " + column.className;
657						label	= column.label || "";
658
659						// append the header cell
660						header_container.append(
661							simpleCart.$create(TH).addClass(klass).html(label)
662						);
663					}
664
665					// cycle through the items
666					simpleCart.each(function (item, y) {
667						simpleCart.createCartRow(item, y, TR, TD, cart_container);
668					});
669
670					return cart_container;
671				},
672
673				// generate a cart row from an item
674				createCartRow: function (item, y, TR, TD, container) {
675					var row = simpleCart.$create(TR)
676										.addClass('itemRow row-' + y + " " + (y % 2 ? "even" : "odd"))
677										.attr('id', "cartItem_" + item.id()),
678						j,
679						jlen,
680						column,
681						klass,
682						content,
683						cell;
684
685					container.append(row);
686
687					// cycle through the columns to create each cell for the item
688					for (j = 0, jlen = settings.cartColumns.length; j < jlen; j += 1) {
689						column	= cartColumn(settings.cartColumns[j]);
690						klass	= "item-" + (column.attr || (isString(column.view) ? column.view : column.label || column.text || "cell")) + " " + column.className;
691						content = cartCellView(item, column);
692						cell	= simpleCart.$create(TD).addClass(klass).html(content);
693
694						row.append(cell);
695					}
696					return row;
697				}
698
699			});
700
701			/*******************************************************************
702			 *	CART ITEM CLASS MANAGEMENT
703			 *******************************************************************/
704
705			simpleCart.Item = function (info) {
706
707				// we use the data object to track values for the item
708				var _data = {},
709					me = this;
710
711				// cycle through given attributes and set them to the data object
712				if (isObject(info)) {
713					simpleCart.extend(_data, info);
714				}
715
716				// set the item id
717				item_id += 1;
718				_data.id = _data.id || item_id_namespace + item_id;
719				while (!isUndefined(sc_items[_data.id])) {
720					item_id += 1;
721					_data.id = item_id_namespace + item_id;
722				}
723
724				function checkQuantityAndPrice() {
725
726					// check to make sure price is valid
727					if (isString(_data.price)) {
728					   // trying to remove all chars that aren't numbers or '.'
729						_data.price = parseFloat(_data.price.replace(simpleCart.currency().decimal, ".").replace(/[^0-9\.]+/ig, ""));
730
731					}
732					if (isNaN(_data.price)) {
733						_data.price = 0;
734					}
735					if (_data.price < 0) {
736						_data.price = 0;
737					}
738
739					// check to make sure quantity is valid
740					if (isString(_data.quantity)) {
741						_data.quantity = parseInt(_data.quantity.replace(simpleCart.currency().delimiter, ""), 10);
742					}
743					if (isNaN(_data.quantity)) {
744						_data.quantity = 1;
745					}
746					if (_data.quantity <= 0) {
747						me.remove();
748					}
749
750				}
751
752				// getter and setter methods to access private variables
753				me.get = function (name, skipPrototypes) {
754
755					var usePrototypes = !skipPrototypes;
756
757					if (isUndefined(name)) {
758						return name;
759					}
760
761					// return the value in order of the data object and then the prototype
762					return isFunction(_data[name])	? _data[name].call(me) :
763							!isUndefined(_data[name]) ? _data[name] :
764
765							isFunction(me[name]) && usePrototypes		? me[name].call(me) :
766							!isUndefined(me[name]) && usePrototypes	? me[name] :
767							_data[name];
768				};
769				me.set = function (name, value) {
770					if (!isUndefined(name)) {
771						_data[name.toLowerCase()] = value;
772						if (name.toLowerCase() === 'price' || name.toLowerCase() === 'quantity') {
773							checkQuantityAndPrice();
774						}
775					}
776					return me;
777				};
778				me.equals = function (item) {
779					for( var label in _data ){
780						if (Object.prototype.hasOwnProperty.call(_data, label)) {
781							if (label !== 'quantity' && label !== 'id') {
782								if (item.get(label) !== _data[label]) {
783									return false;
784								}
785							}
786						}
787					}
788					return true;
789				};
790				me.options = function () {
791					var data = {};
792					simpleCart.each(_data,function (val, x, label) {
793						var add = true;
794						simpleCart.each(me.reservedFields(), function (field) {
795							if (field === label) {
796								add = false;
797							}
798							return add;
799						});
800
801						if (add) {
802							data[label] = me.get(label);
803						}
804					});
805					return data;
806				};
807
808
809				checkQuantityAndPrice();
810			};
811
812			simpleCart.Item._ = simpleCart.Item.prototype = {
813
814				// editing the item quantity
815				increment: function (amount) {
816					var diff = amount || 1;
817					diff = parseInt(diff, 10);
818
819					this.quantity(this.quantity() + diff);
820					if (this.quantity() < 1) {
821						this.remove();
822						return null;
823					}
824					return this;
825
826				},
827				decrement: function (amount) {
828					var diff = amount || 1;
829					return this.increment(-parseInt(diff, 10));
830				},
831				remove: function (skipUpdate) {
832					var removeItemBool = simpleCart.trigger("beforeRemove", [sc_items[this.id()]]);
833					if (removeItemBool === false ) {
834						return false;
835					}
836					delete sc_items[this.id()];
837					if (!skipUpdate) {
838						simpleCart.update();
839					}
840					return null;
841				},
842
843				// special fields for items
844				reservedFields: function () {
845					return ['quantity', 'id', 'item_number', 'price', 'name', 'shipping', 'tax', 'taxRate'];
846				},
847
848				// return values for all reserved fields if they exist
849				fields: function () {
850					var data = {},
851						me = this;
852					simpleCart.each(me.reservedFields(), function (field) {
853						if (me.get(field)) {
854							data[field] = me.get(field);
855						}
856					});
857					return data;
858				},
859
860
861				// shortcuts for getter/setters. can
862				// be overwritten for customization
863				// btw, we are hiring at wojo design, and could
864				// use a great web designer. if thats you, you can
865				// get more info at http://wojodesign.com/now-hiring/
866				// or email me directly: [email protected]
867				quantity: function (val) {
868					return isUndefined(val) ? parseInt(this.get("quantity", true) || 1, 10) : this.set("quantity", val);
869				},
870				price: function (val) {
871					return isUndefined(val) ?
872							parseFloat((this.get("price",true).toString()).replace(simpleCart.currency().symbol,"").replace(simpleCart.currency().delimiter,"") || 1) :
873							this.set("price", parseFloat((val).toString().replace(simpleCart.currency().symbol,"").replace(simpleCart.currency().delimiter,"")));
874				},
875				id: function () {
876					return this.get('id',false);
877				},
878				total:function () {
879					return this.quantity()*this.price();
880				}
881
882			};
883
884
885
886
887			/*******************************************************************
888			 *	CHECKOUT MANAGEMENT
889			 *******************************************************************/
890
891			simpleCart.extend({
892				checkout: function () {
893					if (settings.checkout.type.toLowerCase() === 'custom' && isFunction(settings.checkout.fn)) {
894						settings.checkout.fn.call(simpleCart,settings.checkout);
895					} else if (isFunction(simpleCart.checkout[settings.checkout.type])) {
896						var checkoutData = simpleCart.checkout[settings.checkout.type].call(simpleCart,settings.checkout);
897
898						// if the checkout method returns data, try to send the form
899						if( checkoutData.data && checkoutData.action && checkoutData.method ){
900							// if no one has any objections, send the checkout form
901							if( false !== simpleCart.trigger('beforeCheckout', [checkoutData.data]) ){
902								simpleCart.generateAndSendForm( checkoutData );
903							}
904						}
905
906					} else {
907						simpleCart.error("No Valid Checkout Method Specified");
908					}
909				},
910				extendCheckout: function (methods) {
911					return simpleCart.extend(simpleCart.checkout, methods);
912				},
913				generateAndSendForm: function (opts) {
914					var form = simpleCart.$create("form");
915					form.attr('style', 'display:none;');
916					form.attr('action', opts.action);
917					form.attr('method', opts.method);
918					simpleCart.each(opts.data, function (val, x, name) {
919						form.append(
920							simpleCart.$create("input").attr("type","hidden").attr("name",name).val(val)
921						);
922					});
923					simpleCart.$("body").append(form);
924					if (form.el.submit) form.el.submit();
925					else form.el[0].submit();
926					form.remove();
927				}
928			});
929
930			simpleCart.extendCheckout({
931				PayPal: function (opts) {
932					// account email is required
933					if (!opts.email) {
934						return simpleCart.error("No email provided for PayPal checkout");
935					}
936
937					// build basic form options
938					var data = {
939							  cmd			: "_cart"
940							, upload		: "1"
941							, currency_code : simpleCart.currency().code
942							, business		: opts.email
943							, rm			: opts.method === "GET" ? "0" : "2"
944							, tax_cart		: (simpleCart.tax()*1).toFixed(2)
945							, handling_cart : (simpleCart.shipping()*1).toFixed(2)
946							, charset		: "utf-8"
947						},
948						action = opts.sandbox ? "https://www.sandbox.paypal.com/cgi-bin/webscr" : "https://www.paypal.com/cgi-bin/webscr",
949						method = opts.method === "GET" ? "GET" : "POST";
950
951
952					// check for return and success URLs in the options
953					if (opts.success) {
954						data['return'] = opts.success;
955					}
956					if (opts.cancel) {
957						data.cancel_return = opts.cancel;
958					}
959					if (opts.notify) {
960						data.notify_url = opts.notify;
961					}
962
963
964					// add all the items to the form data
965					simpleCart.each(function (item,x) {
966						var counter = x+1,
967							item_options = item.options(),
968							optionCount = 0,
969							send;
970
971						// basic item data
972						data["item_name_" + counter] = item.get("name");
973						data["quantity_" + counter] = item.quantity();
974						data["amount_" + counter] = (item.price()*1).toFixed(2);
975						data["item_number_" + counter] = item.get("item_number") || counter;
976
977
978						// add the options
979						simpleCart.each(item_options, function (val,k,attr) {
980							// paypal limits us to 10 options
981							if (k < 10) {
982
983								// check to see if we need to exclude this from checkout
984								send = true;
985								simpleCart.each(settings.excludeFromCheckout, function (field_name) {
986									if (field_name === attr) { send = false; }
987								});
988								if (send) {
989										optionCount += 1;
990										data["on" + k + "_" + counter] = attr;
991										data["os" + k + "_" + counter] = val;
992								}
993
994							}
995						});
996
997						// options count
998						data["option_index_"+ x] = Math.min(10, optionCount);
999					});
1000
1001
1002					// return the data for the checkout form
1003					return {
1004						  action	: action
1005						, method	: method
1006						, data		: data
1007					};
1008
1009				},
1010
1011
1012				GoogleCheckout: function (opts) {
1013					// account id is required
1014					if (!opts.merchantID) {
1015						return simpleCart.error("No merchant id provided for GoogleCheckout");
1016					}
1017
1018					// google only accepts USD and GBP
1019					if (simpleCart.currency().code !== "USD" && simpleCart.currency().code !== "GBP") {
1020						return simpleCart.error("Google Checkout only accepts USD and GBP");
1021					}
1022
1023					// build basic form options
1024					var data = {
1025							// TODO: better shipping support for this google
1026							  ship_method_name_1	: "Shipping"
1027							, ship_method_price_1	: simpleCart.shipping()
1028							, ship_method_currency_1: simpleCart.currency().code
1029							, _charset_				: ''
1030						},
1031						action = "https://checkout.google.com/api/checkout/v2/checkoutForm/Merchant/" + opts.merchantID,
1032						method = opts.method === "GET" ? "GET" : "POST";
1033
1034
1035					// add items to data
1036					simpleCart.each(function (item,x) {
1037						var counter = x+1,
1038							options_list = [],
1039							send;
1040						data['item_name_' + counter]		= item.get('name');
1041						data['item_quantity_' + counter]	= item.quantity();
1042						data['item_price_' + counter]		= item.price();
1043						data['item_currency_ ' + counter]	= simpleCart.currency().code;
1044						data['item_tax_rate' + counter]		= item.get('taxRate') || simpleCart.taxRate();
1045
1046						// create array of extra options
1047						simpleCart.each(item.options(), function (val,x,attr) {
1048							// check to see if we need to exclude this from checkout
1049							send = true;
1050							simpleCart.each(settings.excludeFromCheckout, function (field_name) {
1051								if (field_name === attr) { send = false; }
1052							});
1053							if (send) {
1054								options_list.push(attr + ": " + val);
1055							}
1056						});
1057
1058						// add the options to the description
1059						data['item_description_' + counter] = options_list.join(", ");
1060					});
1061
1062					// return the data for the checkout form
1063					return {
1064						  action	: action
1065						, method	: method
1066						, data		: data
1067					};
1068
1069
1070				},
1071
1072
1073				AmazonPayments: function (opts) {
1074					// required options
1075					if (!opts.merchant_signature) {
1076						return simpleCart.error("No merchant signature provided for Amazon Payments");
1077					}
1078					if (!opts.merchant_id) {
1079						return simpleCart.error("No merchant id provided for Amazon Payments");
1080					}
1081					if (!opts.aws_access_key_id) {
1082						return simpleCart.error("No AWS access key id provided for Amazon Payments");
1083					}
1084
1085
1086					// build basic form options
1087					var data = {
1088							  aws_access_key_id:	opts.aws_access_key_id
1089							, merchant_signature:	opts.merchant_signature
1090							, currency_code:		simpleCart.currency().code
1091							, tax_rate:				simpleCart.taxRate()
1092							, weight_unit:			opts.weight_unit || 'lb'
1093						},
1094						action = "https://payments" + (opts.sandbox ? "-sandbox" : "") + ".amazon.com/checkout/" + opts.merchant_id,
1095						method = opts.method === "GET" ? "GET" : "POST";
1096
1097
1098					// add items to data
1099					simpleCart.each(function (item,x) {
1100						var counter = x+1,
1101							options_list = [];
1102						data['item_title_' + counter]			= item.get('name');
1103						data['item_quantity_' + counter]		= item.quantity();
1104						data['item_price_' + counter]			= item.price();
1105						data['item_sku_ ' + counter]			= item.get('sku') || item.id();
1106						data['item_merchant_id_' + counter]	= opts.merchant_id;
1107						if (item.get('weight')) {
1108							data['item_weight_' + counter]		= item.get('weight');
1109						}
1110						if (settings.shippingQuantityRate) {
1111							data['shipping_method_price_per_unit_rate_' + counter] = settings.shippingQuantityRate;
1112						}
1113
1114
1115						// create array of extra options
1116						simpleCart.each(item.options(), function (val,x,attr) {
1117							// check to see if we need to exclude this from checkout
1118							var send = true;
1119							simpleCart.each(settings.excludeFromCheckout, function (field_name) {
1120								if (field_name === attr) { send = false; }
1121							});
1122							if (send && attr !== 'weight' && attr !== 'tax') {
1123								options_list.push(attr + ": " + val);
1124							}
1125						});
1126
1127						// add the options to the description
1128						data['item_description_' + counter] = options_list.join(", ");
1129					});
1130
1131					// return the data for the checkout form
1132					return {
1133						  action	: action
1134						, method	: method
1135						, data		: data
1136					};
1137
1138				},
1139
1140
1141				SendForm: function (opts) {
1142					// url required
1143					if (!opts.url) {
1144						return simpleCart.error('URL required for SendForm Checkout');
1145					}
1146
1147					// build basic form options
1148					var data = {
1149							  currency	: simpleCart.currency().code
1150							, shipping	: simpleCart.shipping()
1151							, tax		: simpleCart.tax()
1152							, taxRate	: simpleCart.taxRate()
1153							, itemCount : simpleCart.find({}).length
1154						},
1155						action = opts.url,
1156						method = opts.method === "GET" ? "GET" : "POST";
1157
1158
1159					// add items to data
1160					simpleCart.each(function (item,x) {
1161						var counter = x+1,
1162							options_list = [],
1163							send;
1164						data['item_name_' + counter]		= item.get('name');
1165						data['item_quantity_' + counter]	= item.quantity();
1166						data['item_price_' + counter]		= item.price();
1167
1168						// create array of extra options
1169						simpleCart.each(item.options(), function (val,x,attr) {
1170							// check to see if we need to exclude this from checkout
1171							send = true;
1172							simpleCart.each(settings.excludeFromCheckout, function (field_name) {
1173								if (field_name === attr) { send = false; }
1174							});
1175							if (send) {
1176								options_list.push(attr + ": " + val);
1177							}
1178						});
1179
1180						// add the options to the description
1181						data['item_options_' + counter] = options_list.join(", ");
1182					});
1183
1184
1185					// check for return and success URLs in the options
1186					if (opts.success) {
1187						data['return'] = opts.success;
1188					}
1189					if (opts.cancel) {
1190						data.cancel_return = opts.cancel;
1191					}
1192
1193					if (opts.extra_data) {
1194						data = simpleCart.extend(data,opts.extra_data);
1195					}
1196
1197					// return the data for the checkout form
1198					return {
1199						  action	: action
1200						, method	: method
1201						, data		: data
1202					};
1203				}
1204
1205
1206			});
1207
1208
1209			/*******************************************************************
1210			 *	EVENT MANAGEMENT
1211			 *******************************************************************/
1212			eventFunctions = {
1213
1214				// bind a callback to an event
1215				bind: function (name, callback) {
1216					if (!isFunction(callback)) {
1217						return this;
1218					}
1219
1220					if (!this._events) {
1221						this._events = {};
1222					}
1223
1224					// split by spaces to allow for multiple event bindings at once
1225					var eventNameList = name.split(/ +/);
1226
1227					// iterate through and bind each event
1228					simpleCart.each( eventNameList , function( eventName ){
1229						if (this._events[eventName] === true) {
1230							callback.apply(this);
1231						} else if (!isUndefined(this._events[eventName])) {
1232							this._events[eventName].push(callback);
1233						} else {
1234							this._events[eventName] = [callback];
1235						}
1236					});
1237
1238
1239					return this;
1240				},
1241
1242				// trigger event
1243				trigger: function (name, options) {
1244					var returnval = true,
1245						x,
1246						xlen;
1247
1248					if (!this._events) {
1249						this._events = {};
1250					}
1251					if (!isUndefined(this._events[name]) && isFunction(this._events[name][0])) {
1252						for (x = 0, xlen = this._events[name].length; x < xlen; x += 1) {
1253							returnval = this._events[name][x].apply(this, (options || []));
1254						}
1255					}
1256					if (returnval === false) {
1257						return false;
1258					}
1259					return true;
1260				}
1261
1262			};
1263			// alias for bind
1264			eventFunctions.on = eventFunctions.bind;
1265			simpleCart.extend(eventFunctions);
1266			simpleCart.extend(simpleCart.Item._, eventFunctions);
1267
1268
1269			// base simpleCart events in options
1270			baseEvents = {
1271				  beforeAdd				: null
1272				, afterAdd				: null
1273				, load					: null
1274				, beforeSave			: null
1275				, afterSave				: null
1276				, update				: null
1277				, ready					: null
1278				, checkoutSuccess		: null
1279				, checkoutFail			: null
1280				, beforeCheckout		: null
1281				, beforeRemove			: null
1282			};
1283
1284			// extend with base events
1285			simpleCart(baseEvents);
1286
1287			// bind settings to events
1288			simpleCart.each(baseEvents, function (val, x, name) {
1289				simpleCart.bind(name, function () {
1290					if (isFunction(settings[name])) {
1291						settings[name].apply(this, arguments);
1292					}
1293				});
1294			});
1295
1296			/*******************************************************************
1297			 *	FORMATTING FUNCTIONS
1298			 *******************************************************************/
1299			simpleCart.extend({
1300				toCurrency: function (number,opts) {
1301					var num = parseFloat(number),
1302						opt_input = opts || {},
1303						_opts = simpleCart.extend(simpleCart.extend({
1304							  symbol:		"$"
1305							, decimal:		"."
1306							, delimiter:	","
1307							, accuracy:		2
1308							, after: false
1309						}, simpleCart.currency()), opt_input),
1310
1311						numParts = num.toFixed(_opts.accuracy).split("."),
1312						dec = numParts[1],
1313						ints = numParts[0];
1314
1315					ints = simpleCart.chunk(ints.reverse(), 3).join(_opts.delimiter.reverse()).reverse();
1316
1317					return	(!_opts.after ? _opts.symbol : "") +
1318							ints +
1319							(dec ? _opts.decimal + dec : "") +
1320							(_opts.after ? _opts.symbol : "");
1321
1322				},
1323
1324
1325				// break a string in blocks of size n
1326				chunk: function (str, n) {
1327					if (typeof n==='undefined') {
1328						n=2;
1329					}
1330					var result = str.match(new RegExp('.{1,' + n + '}','g'));
1331					return result || [];
1332				}
1333
1334			});
1335
1336
1337			// reverse string function
1338			String.prototype.reverse = function () {
1339				return this.split("").reverse().join("");
1340			};
1341
1342
1343			// currency functions
1344			simpleCart.extend({
1345				currency: function (currency) {
1346					if (isString(currency) && !isUndefined(currencies[currency])) {
1347						settings.currency = currency;
1348					} else if (isObject(currency)) {
1349						currencies[currency.code] = currency;
1350						settings.currency = currency.code;
1351					} else {
1352						return currencies[settings.currency];
1353					}
1354				}
1355			});
1356
1357
1358			/*******************************************************************
1359			 *	VIEW MANAGEMENT
1360			 *******************************************************************/
1361
1362			simpleCart.extend({
1363				// bind outlets to function
1364				bindOutlets: function (outlets) {
1365					simpleCart.each(outlets, function (callback, x, selector) {
1366
1367						simpleCart.bind('update', function () {
1368							simpleCart.setOutlet("." + namespace + "_" + selector, callback);
1369						});
1370					});
1371				},
1372
1373				// set function return to outlet
1374				setOutlet: function (selector, func) {
1375					var val = func.call(simpleCart, selector);
1376					if (isObject(val) && val.el) {
1377						simpleCart.$(selector).html(' ').append(val);
1378					} else if (!isUndefined(val)) {
1379						simpleCart.$(selector).html(val);
1380					}
1381				},
1382
1383				// bind click events on inputs
1384				bindInputs: function (inputs) {
1385					simpleCart.each(inputs, function (info) {
1386						simpleCart.setInput("." + namespace + "_" + info.selector, info.event, info.callback);
1387					});
1388				},
1389
1390				// attach events to inputs
1391				setInput: function (selector, event, func) {
1392					simpleCart.$(selector).live(event, func);
1393				}
1394			});
1395
1396
1397			// class for wrapping DOM selector shit
1398			simpleCart.ELEMENT = function (selector) {
1399
1400				this.create(selector);
1401				this.selector = selector || null; // "#" + this.attr('id'); TODO: test length?
1402			};
1403
1404			simpleCart.extend(selectorFunctions, {
1405
1406				"MooTools"		: {
1407					text: function (text) {
1408						return this.attr(_TEXT_, text);
1409					},
1410					html: function (html) {
1411						return this.attr(_HTML_, html);
1412					},
1413					val: function (val) {
1414						return this.attr(_VALUE_, val);
1415					},
1416					attr: function (attr, val) {
1417						if (isUndefined(val)) {
1418							return this.el[0] && this.el[0].get(attr);
1419						}
1420
1421						this.el.set(attr, val);
1422						return this;
1423					},
1424					remove: function () {
1425						this.el.dispose();
1426						return null;
1427					},
1428					addClass: function (klass) {
1429						this.el.addClass(klass);
1430						return this;
1431					},
1432					removeClass: function (klass) {
1433						this.el.removeClass(klass);
1434						return this;
1435					},
1436					append: function (item) {
1437						this.el.adopt(item.el);
1438						return this;
1439					},
1440					each: function (callback) {
1441						if (isFunction(callback)) {
1442							simpleCart.each(this.el, function( e, i, c) {
1443								callback.call( i, i, e, c );
1444							});
1445						}
1446						return this;
1447					},
1448					click: function (callback) {
1449						if (isFunction(callback)) {
1450							this.each(function (e) {
1451								e.addEvent(_CLICK_, function (ev) {
1452									callback.call(e,ev);
1453								});
1454							});
1455						} else if (isUndefined(callback)) {
1456							this.el.fireEvent(_CLICK_);
1457						}
1458
1459						return this;
1460					},
1461					live: function (	event,callback) {
1462						var selector = this.selector;
1463						if (isFunction(callback)) {
1464							simpleCart.$("body").el.addEvent(event + ":relay(" + selector + ")", function (e, el) {
1465								callback.call(el, e);
1466							});
1467						}
1468					},
1469					match: function (selector) {
1470						return this.el.match(selector);
1471					},
1472					parent: function () {
1473						return simpleCart.$(this.el.getParent());
1474					},
1475					find: function (selector) {
1476						return simpleCart.$(this.el.getElements(selector));
1477					},
1478					closest: function (selector) {
1479						return simpleCart.$(this.el.getParent(selector));
1480					},
1481					descendants: function () {
1482						return this.find("*");
1483					},
1484					tag: function () {
1485						return this.el[0].tagName;
1486					},
1487					submit: function (){
1488						this.el[0].submit();
1489						return this;
1490					},
1491					create: function (selector) {
1492						if ((window.typeOf || $type)(selector) === 'array'){
1493							selector = Array.flatten(selector);
1494						}
1495
1496						this.el = $engine(selector);
1497					}
1498
1499
1500				},
1501
1502				"Prototype"		: {
1503					text: function (text) {
1504						if (isUndefined(text)) {
1505							return this.el[0].innerHTML;
1506						}
1507						this.each(function (i,e) {
1508							$(e).update(text);
1509						});
1510						return this;
1511					},
1512					html: function (html) {
1513						return this.text(html);
1514					},
1515					val: function (val) {
1516						return this.attr(_VALUE_, val);
1517					},
1518					attr: function (attr, val) {
1519						if (isUndefined(val)) {
1520							return this.el[0].readAttribute(attr);
1521						}
1522						this.each(function (i,e) {
1523							$(e).writeAttribute(attr, val);
1524						});
1525						return this;
1526					},
1527					append: function (item) {
1528						this.each(function (i,e) {
1529							if (item.el) {
1530								item.each(function (i2,e2) {
1531									$(e).appendChild(e2);
1532								});
1533							} else if (isElement(item)) {
1534								$(e).appendChild(item);
1535							}
1536						});
1537						return this;
1538					},
1539					remove: function () {
1540						this.each(function (i, e) {
1541							$(e).remove();
1542						});
1543						return this;
1544					},
1545					addClass: function (klass) {
1546						this.each(function (i, e) {
1547							$(e).addClassName(klass);
1548						});
1549						return this;
1550					},
1551					removeClass: function (klass) {
1552						this.each(function (i, e) {
1553							$(e).removeClassName(klass);
1554						});
1555						return this;
1556					},
1557					each: function (callback) {
1558						if (isFunction(callback)) {
1559							simpleCart.each(this.el, function( e, i, c) {
1560								callback.call( i, i, e, c );
1561							});
1562						}
1563						return this;
1564					},
1565					click: function (callback) {
1566						if (isFunction(callback)) {
1567							this.each(function (i, e) {
1568								$(e).observe(_CLICK_, function (ev) {
1569									callback.call(e,ev);
1570								});
1571							});
1572						} else if (isUndefined(callback)) {
1573							this.each(function (i, e) {
1574								$(e).fire(_CLICK_);
1575							});
1576						}
1577						return this;
1578					},
1579					live: function (event,callback) {
1580						if (isFunction(callback)) {
1581							var selector = this.selector;
1582							document.observe(event, function (e, el) {
1583								if (el === $engine(e).findElement(selector)) {
1584									callback.call(el, e);
1585								}
1586							});
1587						}
1588					},
1589					parent: function () {
1590						return simpleCart.$(this.el.up());
1591					},
1592					find: function (selector) {
1593						return simpleCart.$(this.el.getElementsBySelector(selector));
1594					},
1595					closest: function (selector) {
1596						return simpleCart.$(this.el.up(selector));
1597					},
1598					descendants: function () {
1599						return simpleCart.$(this.el.descendants());
1600					},
1601					tag: function () {
1602						return this.el.tagName;
1603					},
1604					submit: function() {
1605						this.el[0].submit();
1606					},
1607
1608					create: function (selector) {
1609						if (isString(selector)) {
1610							this.el = $engine(selector);
1611						} else if (isElement(selector)) {
1612							this.el = [selector];
1613						}
1614					}
1615
1616
1617
1618				},
1619
1620				"jQuery": {
1621					passthrough: function (action, val) {
1622						if (isUndefined(val)) {
1623							return this.el[action]();
1624						}
1625
1626						this.el[action](val);
1627						return this;
1628					},
1629					text: function (text) {
1630						return this.passthrough(_TEXT_, text);
1631					},
1632					html: function (html) {
1633						return this.passthrough(_HTML_, html);
1634					},
1635					val: function (val) {
1636						return this.passthrough("val", val);
1637					},
1638					append: function (item) {
1639						var target = item.el || item;
1640						this.el.append(target);
1641						return this;
1642					},
1643					attr: function (attr, val) {
1644						if (isUndefined(val)) {
1645							return this.el.attr(attr);
1646						}
1647						this.el.attr(attr, val);
1648						return this;
1649					},
1650					remove: function () {
1651						this.el.remove();
1652						return this;
1653					},
1654					addClass: function (klass) {
1655						this.el.addClass(klass);
1656						return this;
1657					},
1658					removeClass: function (klass) {
1659						this.el.removeClass(klass);
1660						return this;
1661					},
1662					each: function (callback) {
1663						return this.passthrough('each', callback);
1664					},
1665					click: function (callback) {
1666						return this.passthrough(_CLICK_, callback);
1667					},
1668					live: function (event, callback) {
1669						$engine(document).delegate(this.selector, event, callback);
1670						return this;
1671					},
1672					parent: function () {
1673						return simpleCart.$(this.el.parent());
1674					},
1675					find: function (selector) {
1676						return simpleCart.$(this.el.find(selector));
1677					},
1678					closest: function (selector) {
1679						return simpleCart.$(this.el.closest(selector));
1680					},
1681					tag: function () {
1682						return this.el[0].tagName;
1683					},
1684					descendants: function () {
1685						return simpleCart.$(this.el.find("*"));
1686					},
1687					submit: function() {
1688						return this.el.submit();
1689					},
1690
1691					create: function (selector) {
1692						this.el = $engine(selector);
1693					}
1694				}
1695			});
1696			simpleCart.ELEMENT._ = simpleCart.ELEMENT.prototype;
1697
1698			// bind the DOM setup to the ready event
1699			simpleCart.ready(simpleCart.setupViewTool);
1700
1701			// bind the input and output events
1702			simpleCart.ready(function () {
1703				simpleCart.bindOutlets({
1704					total: function () {
1705						return simpleCart.toCurrency(simpleCart.total());
1706					}
1707					, quantity: function () {
1708						return simpleCart.quantity();
1709					}
1710					, items: function (selector) {
1711						simpleCart.writeCart(selector);
1712					}
1713					, tax: function () {
1714						return simpleCart.toCurrency(simpleCart.tax());
1715					}
1716					, taxRate: function () {
1717						return simpleCart.taxRate().toFixed();
1718					}
1719					, shipping: function () {
1720						return simpleCart.toCurrency(simpleCart.shipping());
1721					}
1722					, grandTotal: function () {
1723						return simpleCart.toCurrency(simpleCart.grandTotal());
1724					}
1725				});
1726				simpleCart.bindInputs([
1727					{	  selector: 'checkout'
1728						, event: 'click'
1729						, callback: function () {
1730							simpleCart.checkout();
1731						}
1732					}
1733					, {	  selector: 'empty'
1734						, event: 'click'
1735						, callback: function () {
1736							simpleCart.empty();
1737						}
1738					}
1739					, {	  selector: 'increment'
1740						, event: 'click'
1741						, callback: function () {
1742							simpleCart.find(simpleCart.$(this).closest('.itemRow').attr('id').split("_")[1]).increment();
1743							simpleCart.update();
1744						}
1745					}
1746					, {	  selector: 'decrement'
1747						, event: 'click'
1748						, callback: function () {
1749							simpleCart.find(simpleCart.$(this).closest('.itemRow').attr('id').split("_")[1]).decrement();
1750							simpleCart.update();
1751						}
1752					}
1753					/* remove from cart */
1754					, {	  selector: 'remove'
1755						, event: 'click'
1756						, callback: function () {
1757							simpleCart.find(simpleCart.$(this).closest('.itemRow').attr('id').split("_")[1]).remove();
1758						}
1759					}
1760
1761					/* cart inputs */
1762					, {	  selector: 'input'
1763						, event: 'change'
1764						, callback: function () {
1765							var $input = simpleCart.$(this),
1766								$parent = $input.parent(),
1767								classList = $parent.attr('class').split(" ");
1768							simpleCart.each(classList, function (klass) {
1769								if (klass.match(/item-.+/i)) {
1770									var field = klass.split("-")[1];
1771									simpleCart.find($parent.closest('.itemRow').attr('id').split("_")[1]).set(field,$input.val());
1772									simpleCart.update();
1773									return;
1774								}
1775							});
1776						}
1777					}
1778
1779					/* here is our shelfItem add to cart button listener */
1780					, { selector: 'shelfItem .item_add'
1781						, event: 'click'
1782						, callback: function () {
1783							var $button = simpleCart.$(this),
1784								fields = {};
1785
1786							$button.closest("." + namespace + "_shelfItem").descendants().each(function (x,item) {
1787								var $item = simpleCart.$(item);
1788
1789								// check to see if the class matches the item_[fieldname] pattern
1790								if (($item.attr("class") &&
1791									$item.attr("class").match(/item_.+/) &&
1792									!$item.attr('class').match(/item_add/)) ||
1793
1794									($item.parent().attr('class') &&
1795									$item.parent().attr("class").match(/item_.+/) &&
1796									!$item.parent().attr('class').match(/item_add/))) {
1797
1798									// find the class name
1799									simpleCart.each(($item.attr('class') || $item.parent().attr('class')).split(' '), function (klass) {
1800										var attr,
1801											val,
1802											type;
1803
1804										// get the value or text depending on the tagName
1805										if (klass.match(/item_.+/)) {
1806											attr = klass.split("_")[1];
1807											val = "";
1808											switch($item.tag().toLowerCase()) {
1809												case "input":
1810												case "textarea":
1811												case "select":
1812													type = $item.attr("type");
1813													if (!type || ((type.toLowerCase() === "checkbox" || type.toLowerCase() === "radio") && $item.attr("checked")) || type.toLowerCase() === "text" || type.toLowerCase() === "number" || type.toLowerCase() == 'text' || type.toLowerCase() == 'hidden') {
1814														val = $item.val();
1815													}
1816													break;
1817												case "option":
1818													if ($item.attr('selected')) val = $item.parent().val();
1819													break;
1820												case "img":
1821													val = $item.attr('src');
1822													break;
1823												default:
1824													val = $item.text();
1825													break;
1826											}
1827
1828											if (val !== null && val !== "") {
1829												fields[attr.toLowerCase()] = fields[attr.toLowerCase() && $item.tag().toLowerCase() != 'option'] ? fields[attr.toLowerCase()] + ", " +  val : val;
1830											}
1831										}
1832									});
1833								}
1834							});
1835
1836
1837							// add the item
1838							simpleCart.add(fields);
1839						}
1840					}
1841				]);
1842			});
1843
1844
1845			/*******************************************************************
1846			 *	DOM READY
1847			 *******************************************************************/
1848			// Cleanup functions for the document ready method
1849			// used from jQuery
1850			/*global DOMContentLoaded */
1851			if (document.addEventListener) {
1852				window.DOMContentLoaded = function () {
1853					document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
1854					simpleCart.init();
1855				};
1856
1857			} else if (document.attachEvent) {
1858				window.DOMContentLoaded = function () {
1859					// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
1860					if (document.readyState === "complete") {
1861						document.detachEvent("onreadystatechange", DOMContentLoaded);
1862						simpleCart.init();
1863					}
1864				};
1865			}
1866			// The DOM ready check for Internet Explorer
1867			// used from jQuery
1868			function doScrollCheck() {
1869				if (simpleCart.isReady) {
1870					return;
1871				}
1872
1873				try {
1874					// If IE is used, use the trick by Diego Perini
1875					// http://javascript.nwbox.com/IEContentLoaded/
1876					document.documentElement.doScroll("left");
1877				} catch (e) {
1878					setTimeout(doScrollCheck, 1);
1879					return;
1880				}
1881
1882				// and execute any waiting functions
1883				simpleCart.init();
1884			}
1885
1886			// bind ready event used from jquery
1887			function sc_BindReady () {
1888
1889				// Catch cases where $(document).ready() is called after the
1890				// browser event has already occurred.
1891				if (document.readyState === "complete") {
1892					// Handle it asynchronously to allow scripts the opportunity to delay ready
1893					return setTimeout(simpleCart.init, 1);
1894				}
1895
1896				// Mozilla, Opera and webkit nightlies currently support this event
1897				if (document.addEventListener) {
1898					// Use the handy event callback
1899					document.addEventListener("DOMContentLoaded", DOMContentLoaded, false);
1900
1901					// A fallback to window.onload, that will always work
1902					window.addEventListener("load", simpleCart.init, false);
1903
1904				// If IE event model is used
1905				} else if (document.attachEvent) {
1906					// ensure firing before onload,
1907					// maybe late but safe also for iframes
1908					document.attachEvent("onreadystatechange", DOMContentLoaded);
1909
1910					// A fallback to window.onload, that will always work
1911					window.attachEvent("onload", simpleCart.init);
1912
1913					// If IE and not a frame
1914					// continually check to see if the document is ready
1915					var toplevel = false;
1916
1917					try {
1918						toplevel = window.frameElement === null;
1919					} catch (e) {}
1920
1921					if (document.documentElement.doScroll && toplevel) {
1922						doScrollCheck();
1923					}
1924				}
1925			}
1926
1927			// bind the ready event
1928			sc_BindReady();
1929
1930			return simpleCart;
1931		};
1932
1933
1934	window.simpleCart = generateSimpleCart();
1935
1936}(window, document));
1937
1938/************ JSON *************/
1939var JSON;JSON||(JSON={});
1940(function () {function k(a) {return a<10?"0"+a:a}function o(a) {p.lastIndex=0;return p.test(a)?'"'+a.replace(p,function (a) {var c=r[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function l(a,j) {var c,d,h,m,g=e,f,b=j[a];b&&typeof b==="object"&&typeof b.toJSON==="function"&&(b=b.toJSON(a));typeof i==="function"&&(b=i.call(j,a,b));switch(typeof b) {case "string":return o(b);case "number":return isFinite(b)?String(b):"null";case "boolean":case "null":return String(b);case "object":if (!b)return"null";
1941e += n;f=[];if (Object.prototype.toString.apply(b)==="[object Array]") {m=b.length;for (c=0;c<m;c += 1)f[c]=l(c,b)||"null";h=f.length===0?"[]":e?"[\n"+e+f.join(",\n"+e)+"\n"+g+"]":"["+f.join(",")+"]";e=g;return h}if (i&&typeof i==="object") {m=i.length;for (c=0;c<m;c += 1)typeof i[c]==="string"&&(d=i[c],(h=l(d,b))&&f.push(o(d)+(e?": ":":")+h))}else for (d in b)Object.prototype.hasOwnProperty.call(b,d)&&(h=l(d,b))&&f.push(o(d)+(e?": ":":")+h);h=f.length===0?"{}":e?"{\n"+e+f.join(",\n"+e)+"\n"+g+"}":"{"+f.join(",")+
1942"}";e=g;return h}}if (typeof Date.prototype.toJSON!=="function")Date.prototype.toJSON=function () {return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+k(this.getUTCMonth()+1)+"-"+k(this.getUTCDate())+"T"+k(this.getUTCHours())+":"+k(this.getUTCMinutes())+":"+k(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function () {return this.valueOf()};var q=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
1943p=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,e,n,r={"\u0008":"\\b","\t":"\\t","\n":"\\n","\u000c":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},i;if (typeof JSON.stringify!=="function")JSON.stringify=function (a,j,c) {var d;n=e="";if (typeof c==="number")for (d=0;d<c;d += 1)n += " ";
1943else typeof c==="string"&&(n=c);if ((i=j)&&typeof j!=="function"&&(typeof j!=="object"||typeof j.length!=="number"))throw Error("JSON.stringify");return l("",
1944{"":a})};if (typeof JSON.parse!=="function")JSON.parse=function (a,e) {function c(a,d) {var g,f,b=a[d];if (b&&typeof b==="object")for (g in b)Object.prototype.hasOwnProperty.call(b,g)&&(f=c(b,g),f!==void 0?b[g]=f:delete b[g]);return e.call(a,d,b)}var d,a=String(a);q.lastIndex=0;q.test(a)&&(a=a.replace(q,function (a) {return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if (/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
1945"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return d=eval("("+a+")"),typeof e==="function"?c({"":d},""):d;throw new SyntaxError("JSON.parse");}})();
1946
1947
1948/************ HTML5 Local Storage Support *************/
1949(function () {if (!this.localStorage)if (this.globalStorage)try {this.localStorage=this.globalStorage}catch(e) {}else{var a=document.createElement("div");a.style.display="none";document.getElementsByTagName("head")[0].appendChild(a);if (a.addBehavior) {a.addBehavior("#default#userdata");var d=this.localStorage={length:0,setItem:function (b,d) {a.load("localStorage");b=c(b);a.getAttribute(b)||this.length++;a.setAttribute(b,d);a.save("localStorage")},getItem:function (b) {a.load("localStorage");b=c(b);return a.getAttribute(b)},
1950removeItem:function (b) {a.load("localStorage");b=c(b);a.removeAttribute(b);a.save("localStorage");this.length=0},clear:function () {a.load("localStorage");for (var b=0;attr=a.XMLDocument.documentElement.attributes[b++];)a.removeAttribute(attr.name);a.save("localStorage");this.length=0},key:function (b) {a.load("localStorage");return a.XMLDocument.documentElement.attributes[b]}},c=function (a) {return a.replace(/[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u37f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/g,
1951"-")};a.load("localStorage");d.length=a.XMLDocument.documentElement.attributes.length}}})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.