PageSourceSearch

https://www.heliks.rs/lib/simpleCart.js

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