cart.js 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678
  1. /*!
  2. * The PayPal Mini Cart
  3. * Visit http://www.minicartjs.com/ for details
  4. * Use subject to license agreement as set forth at the link below
  5. *
  6. * @author Jeff Harrell
  7. * @license https://github.com/jeffharrell/MiniCart/blob/master/LICENSE eBay Open Source License Agreement
  8. */
  9. if (typeof PAYPAL == 'undefined' || !PAYPAL) {
  10. var PAYPAL = {};
  11. }
  12. PAYPAL.apps = PAYPAL.apps || {};
  13. (function () {
  14. /**
  15. * Default configuration
  16. */
  17. var config = {
  18. /**
  19. * The parent element the cart should "pin" to
  20. */
  21. parent: document.body,
  22. /**
  23. * Edge of the window to pin the cart to
  24. */
  25. displayEdge: 'right',
  26. /**
  27. * Distance from the edge of the window
  28. */
  29. edgeDistance: '50px',
  30. /**
  31. * HTML target property for the checkout form
  32. */
  33. formTarget: null,
  34. /**
  35. * The base path of your website to set the cookie to
  36. */
  37. cookiePath: '/',
  38. /**
  39. * Strings used for display text
  40. */
  41. strings: {
  42. button: '',
  43. subtotal: '',
  44. discount: '',
  45. shipping: '',
  46. processing: ''
  47. },
  48. /**
  49. * Unique ID used on the wrapper element
  50. */
  51. name: 'PPMiniCart',
  52. /**
  53. * Boolean to determine if the cart should "peek" when it's hidden with items
  54. */
  55. peekEnabled: true,
  56. /**
  57. * The URL of the PayPal website
  58. */
  59. paypalURL: 'https://www.paypal.com/cgi-bin/webscr',
  60. /**
  61. * The base URL to the visual assets
  62. */
  63. assetURL: 'http://www.minicartjs.com/build/',
  64. events: {
  65. /**
  66. * Custom event fired before the cart is rendered
  67. */
  68. onRender: null,
  69. /**
  70. * Custom event fired after the cart is rendered
  71. */
  72. afterRender: null,
  73. /**
  74. * Custom event fired before the cart is hidden
  75. *
  76. * @param e {event} The triggering event
  77. */
  78. onHide: null,
  79. /**
  80. * Custom event fired after the cart is hidden
  81. *
  82. * @param e {event} The triggering event
  83. */
  84. afterHide: null,
  85. /**
  86. * Custom event fired before the cart is shown
  87. *
  88. * @param e {event} The triggering event
  89. */
  90. onShow: null,
  91. /**
  92. * Custom event fired after the cart is shown
  93. *
  94. * @param e {event} The triggering event
  95. */
  96. afterShow: null,
  97. /**
  98. * Custom event fired before a product is added to the cart
  99. *
  100. * @param data {object} Product object
  101. */
  102. onAddToCart: null,
  103. /**
  104. * Custom event fired after a product is added to the cart
  105. *
  106. * @param data {object} Product object
  107. */
  108. afterAddToCart: null,
  109. /**
  110. * Custom event fired before a product is removed from the cart
  111. *
  112. * @param data {object} Product object
  113. */
  114. onRemoveFromCart: null,
  115. /**
  116. * Custom event fired after a product is removed from the cart
  117. *
  118. * @param data {object} Product object
  119. */
  120. afterRemoveFromCart: null,
  121. /**
  122. * Custom event fired before the checkout action takes place
  123. *
  124. * @param e {event} The triggering event
  125. */
  126. onCheckout: null,
  127. /**
  128. * Custom event fired before the cart is reset
  129. */
  130. onReset: null,
  131. /**
  132. * Custom event fired after the cart is reset
  133. */
  134. afterReset: null
  135. }
  136. };
  137. /**
  138. * Mini Cart application
  139. */
  140. PAYPAL.apps.MiniCart = (function () {
  141. var minicart = {},
  142. isShowing = false,
  143. isRendered = false;
  144. /** PRIVATE **/
  145. /**
  146. * PayPal form cmd values which are supported
  147. */
  148. var SUPPORTED_CMDS = { _cart: true, _xclick: true };
  149. /**
  150. * The form origin that is passed to PayPal
  151. */
  152. var BN_VALUE = 'MiniCart_AddToCart_WPS_US';
  153. /**
  154. * Regex filter for cart settings, which appear only once in a cart
  155. */
  156. var SETTING_FILTER = /^(?:business|currency_code|lc|paymentaction|no_shipping|cn|no_note|invoice|handling_cart|weight_cart|weight_unit|tax_cart|page_style|image_url|cpp_|cs|cbt|return|cancel_return|notify_url|rm|custom|charset)/;
  157. /**
  158. * Adds the cart's CSS to the page in a <style> element.
  159. * The CSS lives in this file so that it can leverage properties from the config
  160. * and doesn't require an additional down. To override the CSS see the FAQ.
  161. */
  162. var _addCSS = function () {
  163. var name = config.name,
  164. css = [],
  165. style, head;
  166. css.push('#' + name + ' form { position:fixed; float:none; top:-250px; ' + config.displayEdge + ':' + config.edgeDistance + '; width:265px; margin:0; padding:10px 10px 0; min-height:170px; background:#fff url(' + config.assetURL + 'images/minicart_sprite.png) no-repeat -225px -60px; border:1px solid #999; border-top:0; font:13px/normal arial, helvetica; color:#333; text-align:left; -moz-border-radius:0 0 8px 8px; -webkit-border-radius:0 0 8px 8px; border-radius:0 0 8px 8px; -moz-box-shadow:1px 1px 1px rgba(0, 0, 0, 0.1); -webkit-box-shadow:1px 1px 1px rgba(0, 0, 0, 0.1); box-shadow:1px 1px 1px rgba(0, 0, 0, 0.1); } ');
  167. css.push('#' + name + ' ul { position:relative; overflow-x:hidden; overflow-y:auto; height:130px; margin:0 0 7px; padding:0; list-style-type:none; border-top:1px solid #ccc; border-bottom:1px solid #ccc; } ');
  168. css.push('#' + name + ' li { position:relative; margin:-1px 0 0; padding:6px 5px 6px 0; border-top:1px solid #f2f2f2; } ');
  169. css.push('#' + name + ' li a { display: block; width: 155px; color:#333; text-decoration:none; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } ');
  170. css.push('#' + name + ' li a span { color:#999; font-size:10px; } ');
  171. css.push('#' + name + ' li .quantity { position:absolute; top:.5em; right:78px; width:22px; padding:1px; border:1px solid #83a8cc; text-align:right; } ');
  172. css.push('#' + name + ' li .price { position:absolute; top:.5em; right:4px; } ');
  173. css.push('#' + name + ' li .remove { position:absolute; top:9px; right:60px; width:14px; height:14px; background:url(' + config.assetURL + 'images/minicart_sprite.png) no-repeat -134px -4px; border:0; cursor:pointer; } ');
  174. css.push('#' + name + ' p { margin:0; padding:0 0 0 20px; background:url(' + config.assetURL + 'images/minicart_sprite.png) no-repeat; font-size:13px; font-weight:bold; } ');
  175. css.push('#' + name + ' p:hover { cursor:pointer; } ');
  176. css.push('#' + name + ' p input { float:right; margin:4px 0 0; padding:1px 4px; text-decoration:none; font-weight:normal; color:#333; background:#ffa822 url(' + config.assetURL + 'images/minicart_sprite.png) repeat-x left center; border:1px solid #d5bd98; border-right-color:#935e0d; border-bottom-color:#935e0d; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; } ');
  177. css.push('#' + name + ' p .shipping { display:block; font-size:10px; font-weight:normal; color:#999; } ');
  178. style = document.createElement('style');
  179. style.type = 'text/css';
  180. if (style.styleSheet) {
  181. style.styleSheet.cssText = css.join('');
  182. } else {
  183. style.appendChild(document.createTextNode(css.join('')));
  184. }
  185. head = document.getElementsByTagName('head')[0];
  186. head.appendChild(style);
  187. };
  188. /**
  189. * Builds the DOM elements required by the cart
  190. */
  191. var _buildDOM = function () {
  192. var UI = minicart.UI,
  193. cmd, type, bn, parent, version;
  194. UI.wrapper = document.createElement('div');
  195. UI.wrapper.id = config.name;
  196. cmd = document.createElement('input');
  197. cmd.type = 'hidden';
  198. cmd.name = 'cmd';
  199. cmd.value = '_cart';
  200. type = cmd.cloneNode(false);
  201. type.name = 'upload';
  202. type.value = '1';
  203. bn = cmd.cloneNode(false);
  204. bn.name = 'bn';
  205. bn.value = BN_VALUE;
  206. UI.cart = document.createElement('form');
  207. UI.cart.method = 'post';
  208. UI.cart.action = config.paypalURL;
  209. if (config.formTarget) {
  210. UI.cart.target = config.formTarget;
  211. }
  212. UI.cart.appendChild(cmd);
  213. UI.cart.appendChild(type);
  214. UI.cart.appendChild(bn);
  215. UI.wrapper.appendChild(UI.cart);
  216. UI.itemList = document.createElement('ul');
  217. UI.cart.appendChild(UI.itemList);
  218. UI.summary = document.createElement('p');
  219. UI.cart.appendChild(UI.summary);
  220. UI.button = document.createElement('input');
  221. UI.button.type = 'submit';
  222. UI.button.value = config.strings.button || 'Checkout';
  223. UI.summary.appendChild(UI.button);
  224. UI.subtotal = document.createElement('span');
  225. UI.subtotal.innerHTML = config.strings.subtotal || 'Subtotal: ';
  226. UI.subtotalAmount = document.createElement('span');
  227. UI.subtotalAmount.innerHTML = '0.00';
  228. UI.subtotal.appendChild(UI.subtotalAmount);
  229. UI.summary.appendChild(UI.subtotal);
  230. UI.shipping = document.createElement('span');
  231. UI.shipping.className = 'shipping';
  232. UI.shipping.innerHTML = config.strings.shipping || '&nbsp;';
  233. UI.summary.appendChild(UI.shipping);
  234. // Workaround: IE 6 and IE 7/8 in quirks mode do not support position:fixed in CSS
  235. if (window.attachEvent && !window.opera) {
  236. version = navigator.userAgent.match(/MSIE\s([^;]*)/);
  237. if (version) {
  238. version = parseFloat(version[1]);
  239. if (version < 7 || (version >= 7 && document.compatMode === 'BackCompat')) {
  240. UI.cart.style.position = 'absolute';
  241. UI.wrapper.style[config.displayEdge] = '0';
  242. UI.wrapper.style.setExpression('top', 'x = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop');
  243. }
  244. }
  245. }
  246. parent = (typeof config.parent === 'string') ? document.getElementById(config.parent) : config.parent;
  247. parent.appendChild(UI.wrapper);
  248. };
  249. /**
  250. * Attaches the cart events to it's DOM elements
  251. */
  252. var _bindEvents =function () {
  253. var ui = minicart.UI,
  254. forms, form, i;
  255. // Look for all "Cart" and "Buy Now" forms on the page and attach events
  256. forms = document.getElementsByTagName('form');
  257. for (i = 0; i < forms.length; i++) {
  258. form = forms[i];
  259. if (form.cmd && SUPPORTED_CMDS[form.cmd.value]) {
  260. minicart.bindForm(form);
  261. }
  262. }
  263. // Hide the Mini Cart for all non-cart related clicks
  264. $.event.add(document, 'click', function (e) {
  265. if (isShowing) {
  266. var target = e.target,
  267. cartEl = ui.cart;
  268. if (!(/input|button|select|option/i.test(target.tagName))) {
  269. while (target.nodeType === 1) {
  270. if (target === cartEl) {
  271. return;
  272. }
  273. target = target.parentNode;
  274. }
  275. minicart.hide(null);
  276. }
  277. }
  278. });
  279. // Run the checkout code when submitting the form
  280. $.event.add(ui.cart, 'submit', function (e) {
  281. _checkout(e);
  282. });
  283. // Show the cart when clicking on the summary
  284. $.event.add(ui.summary, 'click', function (e) {
  285. var target = e.target;
  286. if (target !== ui.button) {
  287. minicart.toggle(e);
  288. }
  289. });
  290. // Update other windows when HTML5 localStorage is updated
  291. if (window.attachEvent && !window.opera) {
  292. $.event.add(document, 'storage', function (e) {
  293. // IE needs a delay in order to properly see the change
  294. setTimeout(_redrawCartItems, 100);
  295. });
  296. } else {
  297. $.event.add(window, 'storage', function (e) {
  298. // Safari, Chrome, and Opera can filter on updated storage key
  299. // Firefox can't so it uses a brute force approach
  300. if ((e.key && e.key == config.name) || !e.key) {
  301. _redrawCartItems();
  302. }
  303. });
  304. }
  305. };
  306. /**
  307. * Parses the userConfig (if applicable) and overwrites the default values
  308. */
  309. var _parseUserConfig = function (userConfig) {
  310. var key;
  311. // TODO: This should recursively merge the config values
  312. for (key in userConfig) {
  313. if (typeof config[key] !== undefined) {
  314. config[key] = userConfig[key];
  315. }
  316. }
  317. };
  318. /**
  319. * Loads the stored data and builds the cart
  320. */
  321. var _parseStorage = function () {
  322. var data, length, i;
  323. if ((data = $.storage.load())) {
  324. length = data.length;
  325. for (i = 0; i < length; i++) {
  326. if (_renderProduct(data[i])) {
  327. isShowing = true;
  328. }
  329. }
  330. }
  331. };
  332. /**
  333. * Data parser used for forms
  334. *
  335. * @param form {HTMLElement} An HTML form
  336. * @return {object}
  337. */
  338. var _parseForm = function (form) {
  339. var raw = form.elements,
  340. data = {},
  341. pair, value, length, i, len;
  342. for (i = 0, len = raw.length; i < len; i++) {
  343. pair = raw[i];
  344. if ((value = $.util.getInputValue(pair))) {
  345. data[pair.name] = value;
  346. }
  347. }
  348. return data;
  349. };
  350. /**
  351. * Massage's a object's data in preparation for adding it to the user's cart
  352. *
  353. * @param data {object} An object of WPS xclick style data to add to the cart. The format is { product: '', settings: '' }.
  354. * @return {object}
  355. */
  356. var _parseData = function (data) {
  357. var product = {},
  358. settings = {},
  359. existing, option_index, key, len, match, i, j;
  360. // Parse the data into a two categories: product and settings
  361. for (key in data) {
  362. if (SETTING_FILTER.test(key)) {
  363. settings[key] = data[key];
  364. } else {
  365. product[key] = data[key];
  366. }
  367. }
  368. // Check the products to see if this variation already exists
  369. // If it does then reuse the same object
  370. for (i = 0, len = minicart.products.length; i < len; i++) {
  371. existing = minicart.products[i].product;
  372. // Do the product name and number match
  373. if (product.item_name === existing.item_name && product.item_number === existing.item_number) {
  374. // Products are a match so far; Now do all of the products options match?
  375. match = true;
  376. j = 0;
  377. while (existing['os' + j]) {
  378. if (product['os' + j] !== existing['os' + j]) {
  379. match = false;
  380. break;
  381. }
  382. j++;
  383. }
  384. if (match) {
  385. product.offset = existing.offset;
  386. break;
  387. }
  388. }
  389. }
  390. // Normalize the values
  391. product.href = product.href || window.location.href;
  392. product.quantity = product.quantity || 1;
  393. product.amount = product.amount || 0;
  394. // Add Mini Cart specific settings
  395. if (settings['return'] && settings['return'].indexOf('#') == -1) {
  396. settings['return'] += '#' + config.name + '=reset';
  397. }
  398. // Add option amounts to the total amount
  399. option_index = (product.option_index) ? product.option_index : 0;
  400. while (product['os' + option_index]) {
  401. i = 0;
  402. while (typeof product['option_select' + i] != 'undefined') {
  403. if (product['option_select' + i] == product['os' + option_index]) {
  404. product.amount = product.amount + parseFloat(product['option_amount' + i]);
  405. break;
  406. }
  407. i++;
  408. }
  409. option_index++;
  410. }
  411. return {
  412. product: product,
  413. settings: settings
  414. };
  415. };
  416. /**
  417. * Resets the card and renders the products
  418. */
  419. var _redrawCartItems = function () {
  420. minicart.products = [];
  421. minicart.UI.itemList.innerHTML = '';
  422. minicart.UI.subtotalAmount.innerHTML = '';
  423. _parseStorage();
  424. minicart.updateSubtotal();
  425. };
  426. /**
  427. * Renders the product in the cart
  428. *
  429. * @param data {object} The data for the product
  430. */
  431. var _renderProduct = function (data) {
  432. var ui = minicart.UI,
  433. cartEl = ui.cart,
  434. product = new ProductNode(data, minicart.UI.itemList.children.length + 1),
  435. offset = data.product.offset,
  436. keyupTimer, hiddenInput, key;
  437. minicart.products[offset] = product;
  438. // Add hidden settings data to parent form
  439. for (key in data.settings) {
  440. if (cartEl.elements[key]) {
  441. if (cartEl.elements[key].value) {
  442. cartEl.elements[key].value = data.settings[key];
  443. } else {
  444. cartEl.elements[key] = data.settings[key];
  445. }
  446. } else {
  447. hiddenInput = document.createElement('input');
  448. hiddenInput.type = 'hidden';
  449. hiddenInput.name = key;
  450. hiddenInput.value = data.settings[key];
  451. cartEl.appendChild(hiddenInput);
  452. }
  453. }
  454. // if the product has no name or number then don't add it
  455. if (product.isPlaceholder) {
  456. return false;
  457. // otherwise, setup the new element
  458. } else {
  459. // Click event for "x"
  460. $.event.add(product.removeInput, 'click', function () {
  461. _removeProduct(product, offset);
  462. });
  463. // Event for changing quantities
  464. var currentValue = product.quantityInput.value;
  465. $.event.add(product.quantityInput, 'keyup', function () {
  466. var that = this;
  467. keyupTimer = setTimeout(function () {
  468. var value = parseInt(that.value, 10);
  469. if (!isNaN(value) && value != currentValue) {
  470. currentValue = value;
  471. product.setQuantity(value);
  472. // Delete the product
  473. if (!product.getQuantity()) {
  474. _removeProduct(product, offset);
  475. }
  476. minicart.updateSubtotal();
  477. $.storage.save(minicart.products);
  478. }
  479. }, 250);
  480. });
  481. // Add the item and fade it in
  482. ui.itemList.insertBefore(product.liNode, ui.itemList.firstChild);
  483. $.util.animate(product.liNode, 'opacity', { from: 0, to: 1 });
  484. return true;
  485. }
  486. };
  487. /**
  488. * Removes a product from the cart
  489. *
  490. * @param product {ProductNode} The product object
  491. * @param offset {Number} The offset for the product in the cart
  492. */
  493. var _removeProduct = function (product, offset) {
  494. var events = config.events,
  495. onRemoveFromCart = events.onRemoveFromCart,
  496. afterRemoveFromCart = events.afterRemoveFromCart;
  497. if (typeof onRemoveFromCart == 'function') {
  498. if (onRemoveFromCart.call(minicart, product) === false) {
  499. return;
  500. }
  501. }
  502. product.setQuantity(0);
  503. product.quantityInput.style.display = 'none';
  504. $.util.animate(product.liNode, 'opacity', { from: 1, to: 0 }, function () {
  505. $.util.animate(product.liNode, 'height', { from: 18, to: 0 }, function () {
  506. try {
  507. product.liNode.parentNode.removeChild(product.liNode);
  508. } catch (e) {
  509. // fail
  510. }
  511. // regenerate the form element indexes
  512. var products = minicart.UI.cart.getElementsByTagName('li'),
  513. products_len = products.length,
  514. inputs,
  515. inputs_len,
  516. input,
  517. matches,
  518. i, j, k = 1;
  519. for (i = 0 ; i < products_len; i++) {
  520. inputs = products[i].getElementsByTagName('input');
  521. inputs_len = inputs.length;
  522. for (j = 0; j < inputs_len; j++) {
  523. input = inputs[j];
  524. matches = /(.+)_[0-9]+$/.exec(input.name);
  525. if (matches && matches[1]) {
  526. input.name = matches[1] + '_' + k;
  527. }
  528. }
  529. k++;
  530. }
  531. if (typeof afterRemoveFromCart == 'function') {
  532. afterRemoveFromCart.call(minicart, product);
  533. }
  534. });
  535. });
  536. minicart.products[offset].product.item_name = '';
  537. minicart.products[offset].product.item_number = '';
  538. minicart.updateSubtotal();
  539. $.storage.save(minicart.products);
  540. };
  541. /**
  542. * Event when the cart form is submitted
  543. *
  544. * @param e {event} The form submission event
  545. */
  546. var _checkout = function (e) {
  547. var onCheckout = config.events.onCheckout;
  548. if (typeof onCheckout == 'function') {
  549. if (onCheckout.call(minicart, e) === false) {
  550. e.preventDefault();
  551. return;
  552. }
  553. }
  554. minicart.UI.button.value = config.strings.processing || 'Processing…';
  555. };
  556. /** PUBLIC **/
  557. /**
  558. * Array of ProductNode
  559. */
  560. minicart.products = [];
  561. /**
  562. * Container for UI elements
  563. */
  564. minicart.UI = {};
  565. /**
  566. * Renders the cart, creates the configuration and loads the data
  567. *
  568. * @param userConfig {object} User settings which override the default configuration
  569. */
  570. minicart.render = function (userConfig) {
  571. var events = config.events,
  572. onRender = events.onRender,
  573. afterRender = events.afterRender,
  574. hash, cmd;
  575. if (typeof onRender == 'function') {
  576. if (onRender.call(minicart) === false) {
  577. return;
  578. }
  579. }
  580. if (!isRendered) {
  581. // Overwrite default configuration with user settings
  582. _parseUserConfig(userConfig);
  583. // Render the cart UI
  584. _addCSS();
  585. _buildDOM();
  586. _bindEvents();
  587. // Check if a transaction was completed
  588. // The "return" form param is modified to contain a hash value
  589. // with "PPMiniCart=reset". If this is seen then it's assumed
  590. // that a transaction was completed and we should reset the cart.
  591. hash = location.hash.substring(1);
  592. if (hash.indexOf(config.name + '=') === 0) {
  593. cmd = hash.split('=')[1];
  594. if (cmd == 'reset') {
  595. minicart.reset();
  596. location.hash = '';
  597. }
  598. }
  599. }
  600. // Process any stored data and render it
  601. // TODO: _parseStorage shouldn't be so tightly coupled here and one
  602. // should be able to redraw without re-parsing the storage
  603. _redrawCartItems();
  604. // Trigger the cart to peek on first load if any products were loaded
  605. if (!isRendered) {
  606. if (isShowing) {
  607. setTimeout(function () {
  608. minicart.hide(null);
  609. }, 500);
  610. } else {
  611. $.storage.remove();
  612. }
  613. }
  614. isRendered = true;
  615. if (typeof afterRender == 'function') {
  616. afterRender.call(minicart);
  617. }
  618. };
  619. /**
  620. * Binds a form to the Mini Cart
  621. *
  622. * @param form {HTMLElement} The form element to bind
  623. */
  624. minicart.bindForm = function (form) {
  625. if (form.add) {
  626. $.event.add(form, 'submit', function (e) {
  627. e.preventDefault(e);
  628. var data = _parseForm(e.target);
  629. minicart.addToCart(data);
  630. });
  631. } else if (form.display) {
  632. $.event.add(form, 'submit', function (e) {
  633. e.preventDefault();
  634. minicart.show(e);
  635. });
  636. } else {
  637. return false;
  638. }
  639. return true;
  640. };
  641. /**
  642. * Adds a product to the cart
  643. *
  644. * @param data {object} Product object. See _parseData for format
  645. * @return {boolean} True if the product was added, false otherwise
  646. */
  647. minicart.addToCart = function (data) {
  648. var events = config.events,
  649. onAddToCart = events.onAddToCart,
  650. afterAddToCart = events.afterAddToCart,
  651. success = false,
  652. productNode, offset;
  653. if (typeof onAddToCart === 'function') {
  654. if (onAddToCart.call(minicart, data) === false) {
  655. return;
  656. }
  657. }
  658. data = _parseData(data);
  659. offset = data.product.offset;
  660. // Check if the product has already been added; update if so
  661. if ((productNode = (typeof offset !== 'undefined' && minicart.products[offset]))) {
  662. productNode.product.quantity += parseInt(data.product.quantity || 1, 10);
  663. productNode.setPrice(data.product.amount * productNode.product.quantity);
  664. productNode.setQuantity(productNode.product.quantity);
  665. success = true;
  666. // Add a new DOM element for the product
  667. } else {
  668. data.product.offset = minicart.products.length;
  669. success = _renderProduct(data);
  670. }
  671. minicart.updateSubtotal();
  672. minicart.show(null);
  673. $.storage.save(minicart.products);
  674. if (typeof afterAddToCart === 'function') {
  675. afterAddToCart.call(minicart, data);
  676. }
  677. return success;
  678. };
  679. /**
  680. * Iterates over each product and calculates the subtotal
  681. *
  682. * @return {number} The subtotal
  683. */
  684. minicart.calculateSubtotal = function () {
  685. var amount = 0,
  686. products = minicart.products,
  687. product, item, price, discount, len, i;
  688. for (i = 0, len = products.length; i < len; i++) {
  689. item = products[i];
  690. if ((product = item.product)) {
  691. if (product.quantity && product.amount) {
  692. price = product.amount;
  693. discount = item.getDiscount();
  694. amount += parseFloat((price * product.quantity) - discount);
  695. }
  696. }
  697. }
  698. return amount.toFixed(2);
  699. };
  700. /**
  701. * Updates the UI with the current subtotal and currency code
  702. */
  703. minicart.updateSubtotal = function () {
  704. var ui = minicart.UI,
  705. cartEl = ui.cart.elements,
  706. subtotalEl = ui.subtotalAmount,
  707. subtotal = minicart.calculateSubtotal(),
  708. level = 1,
  709. currency_code, currency_symbol, hex, len, i;
  710. // Get the currency
  711. currency_code = '';
  712. currency_symbol = '';
  713. if (cartEl.currency_code) {
  714. currency_code = cartEl.currency_code.value || cartEl.currency_code;
  715. } else {
  716. for (i = 0, len = cartEl.length; i < len; i++) {
  717. if (cartEl[i].name == 'currency_code') {
  718. currency_code = cartEl[i].value || cartEl[i];
  719. break;
  720. }
  721. }
  722. }
  723. // Update the UI
  724. subtotalEl.innerHTML = $.util.formatCurrency(subtotal, currency_code);
  725. // Yellow fade on update
  726. (function () {
  727. hex = level.toString(16);
  728. level++;
  729. subtotalEl.style.backgroundColor = '#ff' + hex;
  730. if (level >= 15) {
  731. subtotalEl.style.backgroundColor = 'transparent';
  732. // hide the cart if there's no total
  733. if (subtotal == '0.00') {
  734. minicart.hide(null, true);
  735. }
  736. return;
  737. }
  738. setTimeout(arguments.callee, 30);
  739. })();
  740. };
  741. /**
  742. * Shows the cart
  743. *
  744. * @param e {event} The triggering event
  745. */
  746. minicart.show = function (e) {
  747. var from = parseInt(minicart.UI.cart.offsetTop, 10),
  748. to = 0,
  749. events = config.events,
  750. onShow = events.onShow,
  751. afterShow = events.afterShow;
  752. if (e && e.preventDefault) { e.preventDefault(); }
  753. if (typeof onShow == 'function') {
  754. if (onShow.call(minicart, e) === false) {
  755. return;
  756. }
  757. }
  758. $.util.animate(minicart.UI.cart, 'top', { from: from, to: to }, function () {
  759. if (typeof afterShow == 'function') {
  760. afterShow.call(minicart, e);
  761. }
  762. });
  763. minicart.UI.summary.style.backgroundPosition = '-195px 2px';
  764. isShowing = true;
  765. };
  766. /**
  767. * Hides the cart off the screen
  768. *
  769. * @param e {event} The triggering event
  770. * @param fully {boolean} Should the cart be fully hidden? Optional. Defaults to false.
  771. */
  772. minicart.hide = function (e, fully) {
  773. var ui = minicart.UI,
  774. cartEl = ui.cart,
  775. summaryEl = ui.summary,
  776. cartHeight = (cartEl.offsetHeight) ? cartEl.offsetHeight : document.defaultView.getComputedStyle(cartEl, '').getPropertyValue('height'),
  777. summaryHeight = (summaryEl.offsetHeight) ? summaryEl.offsetHeight : document.defaultView.getComputedStyle(summaryEl, '').getPropertyValue('height'),
  778. from = parseInt(cartEl.offsetTop, 10),
  779. events = config.events,
  780. onHide = events.onHide,
  781. afterHide = events.afterHide,
  782. to;
  783. // make the cart fully hidden
  784. if (fully || minicart.products.length === 0 || !config.peekEnabled) {
  785. to = cartHeight * -1;
  786. // otherwise only show a little teaser portion of it
  787. } else {
  788. to = (cartHeight - summaryHeight - 8) * -1;
  789. }
  790. if (e && e.preventDefault) { e.preventDefault(); }
  791. if (typeof onHide == 'function') {
  792. if (onHide.call(minicart, e) === false) {
  793. return;
  794. }
  795. }
  796. $.util.animate(cartEl, 'top', { from: from, to: to }, function () {
  797. if (typeof afterHide == 'function') {
  798. afterHide.call(minicart, e);
  799. }
  800. });
  801. summaryEl.style.backgroundPosition = '-195px -32px';
  802. isShowing = false;
  803. };
  804. /**
  805. * Toggles the display of the cart
  806. *
  807. * @param e {event} The triggering event
  808. */
  809. minicart.toggle = function (e) {
  810. if (isShowing) {
  811. minicart.hide(e);
  812. } else {
  813. minicart.show(e);
  814. }
  815. };
  816. /**
  817. * Resets the cart to it's initial state
  818. */
  819. minicart.reset = function () {
  820. var ui = minicart.UI,
  821. events = config.events,
  822. onReset = events.onReset,
  823. afterReset = events.afterReset;
  824. if (typeof onReset === 'function') {
  825. if (onReset.call(minicart) === false) {
  826. return;
  827. }
  828. }
  829. minicart.products = [];
  830. if (isShowing) {
  831. ui.itemList.innerHTML = '';
  832. ui.subtotalAmount.innerHTML = '';
  833. minicart.hide(null, true);
  834. }
  835. $.storage.remove();
  836. if (typeof afterReset === 'function') {
  837. afterReset.call(minicart);
  838. }
  839. };
  840. // Expose the object as public methods
  841. return minicart;
  842. })();
  843. /**
  844. * An HTMLElement which displays each product
  845. *
  846. * @param data {object} The data for the product
  847. * @param position {number} The product number
  848. */
  849. var ProductNode = function (data, position) {
  850. this._view(data, position);
  851. };
  852. ProductNode.prototype = {
  853. /**
  854. * Creates the DOM nodes and adds the product content
  855. *
  856. * @param data {object} The data for the product
  857. * @param position {number} The product number
  858. */
  859. _view: function (data, position) {
  860. var name, price, quantity, discount, discountNum, options, hiddenInput, key;
  861. this.product = data.product;
  862. this.settings = data.settings;
  863. this.liNode = document.createElement('li');
  864. this.nameNode = document.createElement('a');
  865. this.metaNode = document.createElement('span');
  866. this.discountNode = document.createElement('span');
  867. this.discountInput = document.createElement('input');
  868. this.priceNode = document.createElement('span');
  869. this.quantityInput = document.createElement('input');
  870. this.removeInput = document.createElement('input');
  871. // Don't add blank products
  872. if (!this.product || (!this.product.item_name && !this.product.item_number)) {
  873. this.isPlaceholder = true;
  874. return;
  875. }
  876. // Name
  877. if (this.product.item_name) {
  878. name = this.product.item_name;
  879. }
  880. this.nameNode.innerHTML = name;
  881. this.nameNode.title = name;
  882. this.nameNode.href = this.product.href;
  883. this.nameNode.appendChild(this.metaNode);
  884. // Meta info
  885. if (this.product.item_number) {
  886. this.metaNode.innerHTML = '<br />#' + this.product.item_number;
  887. }
  888. // Options
  889. options = this.getOptions();
  890. for (key in options) {
  891. this.metaNode.innerHTML += '<br />' + key + ': ' + options[key];
  892. }
  893. // Discount
  894. discount = this.getDiscount();
  895. this.discountInput.type = 'hidden';
  896. this.discountInput.name = 'discount_amount_' + position;
  897. this.discountInput.value = discount;
  898. this.metaNode.appendChild(this.discountNode);
  899. // Price
  900. price = this.getPrice();
  901. this.priceNode.className = 'price';
  902. // Quantity
  903. quantity = this.getQuantity();
  904. this.quantityInput.name = 'quantity_' + position;
  905. this.quantityInput.className = 'quantity';
  906. this.quantityInput.setAttribute('autocomplete', 'off');
  907. this.setQuantity(quantity);
  908. // Remove button
  909. this.removeInput.type = 'button';
  910. this.removeInput.className = 'remove';
  911. // Build out the DOM
  912. this.liNode.appendChild(this.nameNode);
  913. this.liNode.appendChild(this.quantityInput);
  914. this.liNode.appendChild(this.discountInput);
  915. this.liNode.appendChild(this.removeInput);
  916. this.liNode.appendChild(this.priceNode);
  917. // Add in hidden product data
  918. for (key in this.product) {
  919. if (key !== 'quantity' && key.indexOf('discount_') === -1) {
  920. hiddenInput = document.createElement('input');
  921. hiddenInput.type = 'hidden';
  922. hiddenInput.name = key + '_' + position;
  923. hiddenInput.value = this.product[key];
  924. this.liNode.appendChild(hiddenInput);
  925. }
  926. }
  927. },
  928. /**
  929. * Calculates the discount for a product
  930. *
  931. * @return {Object} An object with the discount amount or percentage
  932. */
  933. getDiscount: function () {
  934. var data = {},
  935. discount = 0,
  936. discountNum = this.product.discount_num || -1;
  937. // Discounts: Amount-based
  938. if (this.product.discount_amount) {
  939. // Discount amount for the first item
  940. discount = parseFloat(this.product.discount_amount);
  941. // Discount amount for each additional item
  942. if (this.product.discount_amount2) {
  943. quantity = this.getQuantity();
  944. if (quantity > 1) {
  945. discount += Math.max(quantity - 1, discountNum) * parseFloat(this.product.discount_amount2);
  946. }
  947. }
  948. // Discounts: Percentage-based
  949. } else if (this.product.discount_rate) {
  950. // Discount amount on the first item
  951. discount = this.product.amount * parseFloat(this.product.discount_rate) / 100;
  952. // Discount amount for each additional item
  953. if (this.product.discount_rate2) {
  954. quantity = this.getQuantity();
  955. if (quantity > 1) {
  956. discount += Math.max(quantity - 1, discountNum) * this.product.amount * parseFloat(this.product.discount_amount2) / 100;
  957. }
  958. }
  959. }
  960. return discount && discount.toFixed(2);
  961. },
  962. /**
  963. * Returns an object of options for the product
  964. *
  965. * @return {Object}
  966. */
  967. getOptions: function () {
  968. var options = {},
  969. i = 0;
  970. while (typeof this.product['on' + i] !== 'undefined') {
  971. options[this.product['on' + i]] = this.product['os' + i];
  972. i++;
  973. }
  974. return options;
  975. },
  976. /**
  977. * Utility function to set the quantity of this product
  978. *
  979. * @param value {number} The new value
  980. */
  981. setQuantity: function (value) {
  982. var discount;
  983. value = parseInt(value, 10);
  984. this.product.quantity = value;
  985. if (this.quantityInput.value != value) {
  986. this.quantityInput.value = value;
  987. if ((discount = this.getDiscount())) {
  988. this.discountInput.value = discount;
  989. this.discountNode.innerHTML = '<br />';
  990. this.discountNode.innerHTML += config.strings.discount || 'Discount: ';
  991. this.discountNode.innerHTML += $.util.formatCurrency(discount, this.settings.currency_code);
  992. }
  993. }
  994. this.setPrice(this.product.amount * value);
  995. },
  996. /**
  997. * Utility function to get the quantity of this product
  998. *
  999. * @return {number}
  1000. */
  1001. getQuantity: function () {
  1002. return (typeof this.product.quantity !== undefined) ? this.product.quantity : 1;
  1003. },
  1004. /**
  1005. * Utility function to set the price of this product
  1006. *
  1007. * @param value {number} The new value
  1008. */
  1009. setPrice: function (value) {
  1010. value = parseFloat(value, 10);
  1011. this.priceNode.innerHTML = $.util.formatCurrency(value.toFixed(2), this.settings.currency_code);
  1012. },
  1013. /**
  1014. * Utility function to get the price of this product
  1015. *
  1016. * @return {number}
  1017. */
  1018. getPrice: function () {
  1019. return (this.product.amount * this.getQuantity()).toFixed(2);
  1020. }
  1021. };
  1022. /** UTILITY **/
  1023. var $ = {};
  1024. $.storage = (function () {
  1025. var name = config.name;
  1026. // Use HTML5 client side storage
  1027. if (window.localStorage) {
  1028. return {
  1029. /**
  1030. * Loads the saved data
  1031. *
  1032. * @return {object}
  1033. */
  1034. load: function () {
  1035. var data = localStorage.getItem(name);
  1036. if (data) {
  1037. data = JSON.parse(decodeURIComponent(data));
  1038. }
  1039. return data;
  1040. },
  1041. /**
  1042. * Saves the data
  1043. *
  1044. * @param items {object} The list of items to save
  1045. */
  1046. save: function (items) {
  1047. var data = [],
  1048. item, len, i;
  1049. if (items) {
  1050. for (i = 0, len = items.length; i < len; i++) {
  1051. item = items[i];
  1052. data.push({
  1053. product: item.product,
  1054. settings: item.settings
  1055. });
  1056. }
  1057. data = encodeURIComponent(JSON.stringify(data));
  1058. localStorage.setItem(name, data);
  1059. }
  1060. },
  1061. /**
  1062. * Removes the saved data
  1063. */
  1064. remove: function () {
  1065. localStorage.removeItem(name);
  1066. }
  1067. };
  1068. // Otherwise use cookie based storage
  1069. } else {
  1070. return {
  1071. /**
  1072. * Loads the saved data
  1073. *
  1074. * @return {object}
  1075. */
  1076. load: function () {
  1077. var key = name + '=',
  1078. data, cookies, cookie, value, i;
  1079. try {
  1080. cookies = document.cookie.split(';');
  1081. for (i = 0; i < cookies.length; i++) {
  1082. cookie = cookies[i];
  1083. while (cookie.charAt(0) === ' ') {
  1084. cookie = cookie.substring(1, cookie.length);
  1085. }
  1086. if (cookie.indexOf(key) === 0) {
  1087. value = cookie.substring(key.length, cookie.length);
  1088. data = JSON.parse(decodeURIComponent(value));
  1089. }
  1090. }
  1091. } catch(e) {}
  1092. return data;
  1093. },
  1094. /**
  1095. * Saves the data
  1096. *
  1097. * @param items {object} The list of items to save
  1098. */
  1099. save: function (items, duration) {
  1100. var date = new Date(),
  1101. data = [],
  1102. item, len, i;
  1103. if (items) {
  1104. for (i = 0, len = items.length; i < len; i++) {
  1105. item = items[i];
  1106. data.push({
  1107. product: item.product,
  1108. settings: item.settings
  1109. });
  1110. }
  1111. duration = duration || 30;
  1112. date.setTime(date.getTime() + duration * 24 * 60 * 60 * 1000);
  1113. document.cookie = config.name + '=' + encodeURIComponent(JSON.stringify(data)) + '; expires=' + date.toGMTString() + '; path=' + config.cookiePath;
  1114. }
  1115. },
  1116. /**
  1117. * Removes the saved data
  1118. */
  1119. remove: function () {
  1120. this.save(null, -1);
  1121. }
  1122. };
  1123. }
  1124. })();
  1125. $.event = (function () {
  1126. /**
  1127. * Events are added here for easy reference
  1128. */
  1129. var cache = [];
  1130. // Non-IE events
  1131. if (document.addEventListener) {
  1132. return {
  1133. /**
  1134. * Add an event to an object and optionally adjust it's scope
  1135. *
  1136. * @param obj {HTMLElement} The object to attach the event to
  1137. * @param type {string} The type of event excluding "on"
  1138. * @param fn {function} The function
  1139. * @param scope {object} Object to adjust the scope to (optional)
  1140. */
  1141. add: function (obj, type, fn, scope) {
  1142. scope = scope || obj;
  1143. var wrappedFn = function (e) { fn.call(scope, e); };
  1144. obj.addEventListener(type, wrappedFn, false);
  1145. cache.push([obj, type, fn, wrappedFn]);
  1146. },
  1147. /**
  1148. * Remove an event from an object
  1149. *
  1150. * @param obj {HTMLElement} The object to remove the event from
  1151. * @param type {string} The type of event excluding "on"
  1152. * @param fn {function} The function
  1153. */
  1154. remove: function (obj, type, fn) {
  1155. var wrappedFn, item, len, i;
  1156. for (i = 0; i < cache.length; i++) {
  1157. item = cache[i];
  1158. if (item[0] == obj && item[1] == type && item[2] == fn) {
  1159. wrappedFn = item[3];
  1160. if (wrappedFn) {
  1161. obj.removeEventListener(type, wrappedFn, false);
  1162. delete cache[i];
  1163. }
  1164. }
  1165. }
  1166. }
  1167. };
  1168. // IE events
  1169. } else if (document.attachEvent) {
  1170. return {
  1171. /**
  1172. * Add an event to an object and optionally adjust it's scope (IE)
  1173. *
  1174. * @param obj {HTMLElement} The object to attach the event to
  1175. * @param type {string} The type of event excluding "on"
  1176. * @param fn {function} The function
  1177. * @param scope {object} Object to adjust the scope to (optional)
  1178. */
  1179. add: function (obj, type, fn, scope) {
  1180. scope = scope || obj;
  1181. var wrappedFn = function () {
  1182. var e = window.event;
  1183. e.target = e.target || e.srcElement;
  1184. e.preventDefault = function () {
  1185. e.returnValue = false;
  1186. };
  1187. fn.call(scope, e);
  1188. };
  1189. obj.attachEvent('on' + type, wrappedFn);
  1190. cache.push([obj, type, fn, wrappedFn]);
  1191. },
  1192. /**
  1193. * Remove an event from an object (IE)
  1194. *
  1195. * @param obj {HTMLElement} The object to remove the event from
  1196. * @param type {string} The type of event excluding "on"
  1197. * @param fn {function} The function
  1198. */
  1199. remove: function (obj, type, fn) {
  1200. var wrappedFn, item, len, i;
  1201. for (i = 0; i < cache.length; i++) {
  1202. item = cache[i];
  1203. if (item[0] == obj && item[1] == type && item[2] == fn) {
  1204. wrappedFn = item[3];
  1205. if (wrappedFn) {
  1206. obj.detachEvent('on' + type, wrappedFn);
  1207. delete cache[i];
  1208. }
  1209. }
  1210. }
  1211. }
  1212. };
  1213. }
  1214. })();
  1215. $.util = {
  1216. /**
  1217. * Animation method for elements
  1218. *
  1219. * @param el {HTMLElement} The element to animate
  1220. * @param prop {string} Name of the property to change
  1221. * @param config {object} Properties of the animation
  1222. * @param callback {function} Callback function after the animation is complete
  1223. */
  1224. animate: function (el, prop, config, callback) {
  1225. config = config || {};
  1226. config.from = config.from || 0;
  1227. config.to = config.to || 0;
  1228. config.duration = config.duration || 10;
  1229. config.unit = (/top|bottom|left|right|width|height/.test(prop)) ? 'px' : '';
  1230. var step = (config.to - config.from) / 20,
  1231. current = config.from;
  1232. (function () {
  1233. el.style[prop] = current + config.unit;
  1234. current += step;
  1235. if ((step > 0 && current > config.to) || (step < 0 && current < config.to) || step === 0) {
  1236. el.style[prop] = config.to + config.unit;
  1237. if (typeof callback === 'function') {
  1238. callback();
  1239. }
  1240. return;
  1241. }
  1242. setTimeout(arguments.callee, config.duration);
  1243. })();
  1244. },
  1245. /**
  1246. * Convenience method to return the value of any type of form input
  1247. *
  1248. * @param input {HTMLElement} The element who's value is returned
  1249. */
  1250. getInputValue: function (input) {
  1251. var tag = input.tagName.toLowerCase();
  1252. if (tag == 'select') {
  1253. return input.options[input.selectedIndex].value;
  1254. } else if (tag == 'textarea') {
  1255. return input.innerHTML;
  1256. } else {
  1257. if (input.type == 'radio') {
  1258. return (input.checked) ? input.value : null;
  1259. } else if (input.type == 'checkbox') {
  1260. return (input.checked) ? input.value : null;
  1261. } else {
  1262. return input.value;
  1263. }
  1264. }
  1265. },
  1266. /**
  1267. * Formats a float into a currency
  1268. *
  1269. * @param amount {float} The currency amount
  1270. * @param code {string} The three letter currency code
  1271. */
  1272. formatCurrency: function (amount, code) {
  1273. // TODO: The supported currency patterns need to be refined and
  1274. // should support values for before, after, decimal, and separator.
  1275. var currencies = {
  1276. AED: { before: '\u062c' },
  1277. ANG: { before: '\u0192' },
  1278. ARS: { before: '$' },
  1279. AUD: { before: '$' },
  1280. AWG: { before: '\u0192' },
  1281. BBD: { before: '$' },
  1282. BGN: { before: '\u043b\u0432' },
  1283. BMD: { before: '$' },
  1284. BND: { before: '$' },
  1285. BRL: { before: 'R$' },
  1286. BSD: { before: '$' },
  1287. CAD: { before: '$' },
  1288. CHF: { before: '' },
  1289. CLP: { before: '$' },
  1290. CNY: { before: '\u00A5' },
  1291. COP: { before: '$' },
  1292. CRC: { before: '\u20A1' },
  1293. CZK: { before: 'Kc' },
  1294. DKK: { before: 'kr' },
  1295. DOP: { before: '$' },
  1296. EEK: { before: 'kr' },
  1297. EUR: { before: '\u20AC' },
  1298. GBP: { before: '\u00A3' },
  1299. GTQ: { before: 'Q' },
  1300. HKD: { before: '$' },
  1301. HRK: { before: 'kn' },
  1302. HUF: { before: 'Ft' },
  1303. IDR: { before: 'Rp' },
  1304. ILS: { before: '\u20AA' },
  1305. INR: { before: 'Rs.' },
  1306. ISK: { before: 'kr' },
  1307. JMD: { before: 'J$' },
  1308. JPY: { before: '\u00A5' },
  1309. KRW: { before: '\u20A9' },
  1310. KYD: { before: '$' },
  1311. LTL: { before: 'Lt' },
  1312. LVL: { before: 'Ls' },
  1313. MXN: { before: '$' },
  1314. MYR: { before: 'RM' },
  1315. NOK: { before: 'kr' },
  1316. NZD: { before: '$' },
  1317. PEN: { before: 'S/' },
  1318. PHP: { before: 'Php' },
  1319. PLN: { before: 'z' },
  1320. QAR: { before: '\ufdfc' },
  1321. RON: { before: 'lei' },
  1322. RUB: { before: '\u0440\u0443\u0431' },
  1323. SAR: { before: '\ufdfc' },
  1324. SEK: { before: 'kr' },
  1325. SGD: { before: '$' },
  1326. THB: { before: '\u0E3F' },
  1327. TRY: { before: 'TL' },
  1328. TTD: { before: 'TT$' },
  1329. TWD: { before: 'NT$' },
  1330. UAH: { before: '\u20b4' },
  1331. USD: { before: '$' },
  1332. UYU: { before: '$U' },
  1333. VEF: { before: 'Bs' },
  1334. VND: { before: '\u20ab' },
  1335. XCD: { before: '$' },
  1336. ZAR: { before: 'R' }
  1337. },
  1338. currency = currencies[code] || {},
  1339. before = currency.before || '',
  1340. after = currency.after || '';
  1341. return before + amount + after;
  1342. }
  1343. };
  1344. /**
  1345. * JSON Parser - See http://www.json.org/js.html
  1346. */
  1347. if(!this.JSON){JSON={};}(function(){function f(n){return n<10?"0"+n:n;}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z";};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf();};}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key);}if(typeof rep==="function"){value=rep.call(holder,key,value);}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null";}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||"null";}v=partial.length===0?"[]":gap?"[\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"]":"["+partial.join(",")+"]";gap=mind;return v;}if(rep&&typeof rep==="object"){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==="string"){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v);}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v);}}}}v=partial.length===0?"{}":gap?"{\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"}":"{"+partial.join(",")+"}";gap=mind;return v;}}if(typeof JSON.stringify!=="function"){JSON.stringify=function(value,replacer,space){var i;gap="";indent="";if(typeof space==="number"){for(i=0;i<space;i+=1){indent+=" ";}}else{if(typeof space==="string"){indent=space;}}rep=replacer;if(replacer&&typeof replacer!=="function"&&(typeof replacer!=="object"||typeof replacer.length!=="number")){throw new Error("JSON.stringify");}return str("",{"":value});};}if(typeof JSON.parse!=="function"){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==="object"){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}return reviver.call(holder,key,value);}cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4);});}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver==="function"?walk({"":j},""):j;}throw new SyntaxError("JSON.parse");};}}());
  1348. })();