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