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.