├── images └── favicon.png ├── .gitignore ├── plugins └── google-sheets │ ├── DemoSpreadsheet.xlsx │ └── google-sheets.js ├── css ├── imports.min.css ├── simpleStore.min.css └── simpleStore.css ├── bower.json ├── package.json ├── products.json ├── LICENSE.md ├── Gruntfile.js ├── js ├── config.js ├── simpleStore.min.js ├── simpleStore.js └── simpleCart.min.js ├── README.md └── index.html /images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisdiana/simplestore/HEAD/images/favicon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/* 3 | bower_components/* 4 | release/* 5 | release.zip 6 | .idea/ 7 | dev/* 8 | -------------------------------------------------------------------------------- /plugins/google-sheets/DemoSpreadsheet.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisdiana/simplestore/HEAD/plugins/google-sheets/DemoSpreadsheet.xlsx -------------------------------------------------------------------------------- /css/imports.min.css: -------------------------------------------------------------------------------- 1 | @import '../bower_components/skeleton/css/normalize.css'; 2 | @import '../bower_components/skeleton/css/skeleton.css'; 3 | @import '../bower_components/fontawesome/css/font-awesome.min.css'; -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simpleStore", 3 | "description": "A clean, responsive storefront boilerplate with no database or backend.", 4 | "version": "1.1.1", 5 | "homepage": "http://cdmedia.github.io/simplestore", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/cdmedia/simplestore" 9 | }, 10 | "main": "index.html", 11 | "license": "MIT", 12 | "dependencies": { 13 | "jquery": "latest", 14 | "skeleton": "~2.0.4", 15 | "fontawesome": "latest" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simplestore", 3 | "version": "1.5", 4 | "description": "A clean, responsive storefront boilerplate with no database or backend.", 5 | "main": "index.html", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/cdmedia/simplestore.git" 12 | }, 13 | "author": "Chris Diana", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/cdmedia/simplestore/issues" 17 | }, 18 | "private": true, 19 | "homepage": "https://github.com/cdmedia/simplestore", 20 | "devDependencies": { 21 | "grunt": "~0.4.4", 22 | "grunt-contrib-copy": "latest", 23 | "grunt-contrib-cssmin": "latest", 24 | "grunt-contrib-uglify": "latest", 25 | "grunt-contrib-watch": "latest", 26 | "grunt-file-append": "0.0.6" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /products.json: -------------------------------------------------------------------------------- 1 | { 2 | "products" : [ 3 | { 4 | "name" : "Awesome T-shirt", 5 | "price" : "35.99", 6 | "options" : [ 7 | { "Size" : "Small,Medium,Large" } 8 | ], 9 | "image" : "http://upload.wikimedia.org/wikipedia/commons/c/c6/Grey_Tshirt.jpg", 10 | "description" : "This is a very awesome grey T-shirt" 11 | }, 12 | { 13 | "name" : "Cool T-shirt", 14 | "price" : "15.99", 15 | "options" : [ 16 | { "Size" : "Small,Medium,Large" }, 17 | { "Color" : "Blue,Red" }, 18 | { "OneOfAKind" : true } 19 | ], 20 | "image" : "http://upload.wikimedia.org/wikipedia/commons/2/24/Blue_Tshirt.jpg", 21 | "description" : "This is a really cool blue T-shirt", 22 | "soldOut" : true 23 | }, 24 | { 25 | "name" : "Fun T-shirt", 26 | "price" : "15.99", 27 | "options" : [ 28 | { "Size" : "Small,Medium,Large" } 29 | ], 30 | "image" : "http://upload.wikimedia.org/wikipedia/commons/c/c6/Grey_Tshirt.jpg", 31 | "description" : "This is another fun grey T-shirt" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Chris Diana 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | /* 4 | * To build a release run 'grunt' 5 | * index.html has to be manually copied to 'release/' because of 6 | * reference differences (import.min.css, jquery) 7 | */ 8 | 9 | var minBanner = '/* <%= pkg.name %> | Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author %> (<%= pkg.homepage %>) | <%= pkg.license %> license | v<%= pkg.version %> */'; 10 | var largeBanner = '/*' + '\n' + 11 | '* <%= pkg.name %> v<%= pkg.version %>' + '\n' + 12 | '* Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author %>' + '\n' + 13 | '* <%= pkg.homepage %>' + '\n' + 14 | '* Free to use under the MIT license.' + '\n' + 15 | '* http://www.opensource.org/licenses/mit-license.php' + '\n' + 16 | '*/' + '\n'; 17 | 18 | grunt.initConfig({ 19 | 20 | pkg: grunt.file.readJSON('package.json'), 21 | 22 | uglify: { 23 | options: { 24 | banner: minBanner + '\n' 25 | }, 26 | build: { 27 | files: { 28 | 'js/simpleStore.min.js': 'js/simpleStore.js' 29 | } 30 | } 31 | }, 32 | 33 | cssmin: { 34 | target: { 35 | files: { 36 | 'css/simpleStore.min.css': 'css/simpleStore.css' 37 | } 38 | } 39 | }, 40 | 41 | file_append: { 42 | default_options: { 43 | files: [ 44 | {prepend: minBanner + '\n', input: 'css/simpleStore.min.css'} 45 | ] 46 | } 47 | }, 48 | 49 | copy: { 50 | main: { 51 | files: [ 52 | {src: 'css/simpleStore.min.css', dest: 'release/css/simpleStore.min.css'}, 53 | {src: 'js/simpleStore.min.js', dest: 'release/js/simpleStore.min.js'}, 54 | {src: 'js/simpleCart.min.js', dest: 'release/js/simpleCart.min.js'}, 55 | {src: 'js/config.js', dest: 'release/js/config.js'}, 56 | {src: 'products.json', dest: 'release/products.json'}, 57 | {src: 'images/favicon.png', dest: 'release/images/favicon.png'} 58 | ] 59 | }, 60 | } 61 | 62 | }); 63 | 64 | grunt.loadNpmTasks('grunt-contrib-uglify'); 65 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 66 | grunt.loadNpmTasks('grunt-contrib-copy'); 67 | grunt.loadNpmTasks('grunt-file-append'); 68 | 69 | grunt.registerTask('default', ['uglify', 'cssmin', 'file_append', 'copy']); 70 | }; 71 | -------------------------------------------------------------------------------- /js/config.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | simpleCart({ 3 | 4 | // array representing the format and columns of the cart, see 5 | // the cart columns documentation 6 | cartColumns: [ 7 | { attr: "name" , label: "Name" }, 8 | { attr: "price" , label: "Price", view: 'currency' }, 9 | { view: "decrement" , label: false }, 10 | { attr: "quantity" , label: "Qty" }, 11 | { view: "increment" , label: false }, 12 | { attr: "total" , label: "SubTotal", view: 'currency' }, 13 | { view: "remove" , text: "Remove" , label: false } 14 | ], 15 | 16 | // "div" or "table" - builds the cart as a table or collection of divs 17 | cartStyle: "div", 18 | 19 | // how simpleCart should checkout, see the checkout reference for more info 20 | checkout: { 21 | type: "PayPal" , 22 | email: "you@yours.com" 23 | }, 24 | 25 | // set the currency, see the currency reference for more info 26 | currency: "USD", 27 | 28 | // collection of arbitrary data you may want to store with the cart, 29 | // such as customer info 30 | data: {}, 31 | 32 | // set the cart langauge (may be used for checkout) 33 | language: "english-us", 34 | 35 | // array of item fields that will not be sent to checkout 36 | excludeFromCheckout: [ 37 | 'qty', 38 | 'thumb' 39 | ], 40 | 41 | // custom function to add shipping cost 42 | shippingCustom: null, 43 | 44 | // flat rate shipping option 45 | shippingFlatRate: 0, 46 | 47 | // added shipping based on this value multiplied by the cart quantity 48 | shippingQuantityRate: 0, 49 | 50 | // added shipping based on this value multiplied by the cart subtotal 51 | shippingTotalRate: 0, 52 | 53 | // tax rate applied to cart subtotal 54 | taxRate: 0, 55 | 56 | // true if tax should be applied to shipping 57 | taxShipping: false, 58 | 59 | // event callbacks 60 | beforeAdd : null, 61 | afterAdd : null, 62 | load : null, 63 | beforeSave : null, 64 | afterSave : null, 65 | update : null, 66 | ready : null, 67 | checkoutSuccess : null, 68 | checkoutFail : null, 69 | beforeCheckout : null 70 | 71 | }); 72 | 73 | simpleStore.init({ 74 | 75 | // brand can be text or image URL 76 | brand : "SimpleStore", 77 | 78 | // numder of products per row (accepts 1, 2 or 3) 79 | numColumns : 3, 80 | 81 | // name of JSON file, located in directory root 82 | JSONFile : "products.json" 83 | 84 | }); 85 | 86 | }); 87 | -------------------------------------------------------------------------------- /css/simpleStore.min.css: -------------------------------------------------------------------------------- 1 | /* simplestore | Copyright 2015 Chris Diana (https://github.com/cdmedia/simplestore) | MIT license | v1.5 */ 2 | .brand,select{display:block;float:left}.brand,a.hover,a.simpleCart_empty{text-decoration:none}select{margin-right:10px}.simpleStore{margin-top:20px}.brand{color:#000}.brand:hover{color:#777}.brand img{max-height:50px}.simpleCart_shelfItem{border:1px solid #EFEFEF;padding:25px 18px 10px;margin:15px 0}.headerRow,.itemRow{border-bottom:1px solid #E1E1E1}.simpleCart_items{display:table;border-collapse:collapse;width:100%;min-height:20px;margin-bottom:50px}.simpleCart_items div:first-child{width:100%}.simpleCart_decrement,.simpleCart_increment{font-size:18px;line-height:5px;text-decoration:none}.cart_total,.item_price{font-size:20px}.simpleCart_shelfItem label{display:block;float:left;line-height:40px;margin-right:10px}.detail_thumb,.item_thumb{height:150px;display:block;margin:0 auto;padding-bottom:10px}.item_Quantity{display:inline-block;width:50px}.item_price{font-weight:700;line-height:22px}.item_description{margin-top:1rem;margin-bottom:1.5rem}.headerRow{display:table-row;font-weight:700}.headerRow div{display:table-cell;padding:10px}.itemRow.odd{background:#FAFAFA}.itemRow{display:table-row}.itemRow:last-child{border-bottom:0 solid #fff}.itemRow div{display:table-cell;padding:10px}.itemRow .item-quantity{text-align:center}.cart_info{position:absolute;bottom:0;right:0;margin:22px;text-align:right}.simpleStore_cart,.simpleStore_detailView{position:relative;border:1px solid #EFEFEF}.loader,.notify{text-align:center}.cart_info div{display:inline}.cart_info_item{display:block!important}.simpleStore_getDetail_container{display:block;float:left;width:50%;padding:5px 0}.simpleStore_detailView{margin:15px 0}.simpleStore_detailView.simpleCart_shelfItem{padding:26px 18px 8px}.simpleStore_detailView .item_add{margin:0}.simpleStore_detailView .item_name{margin-bottom:1rem}.simpleStore_detailView .item_price{display:block;margin-bottom:1.5rem}.hidden,.simpleStore_cart_container{display:none}.simpleStore_cart{padding:22px;min-height:230px;margin:15px 0}a.simpleCart_empty{color:#CDCDCD;text-transform:uppercase;font-size:14px}a.simpleCart_empty:hover{color:#777}.simpleCart_checkout{margin:12px 0 0}.loader{display:inline-block;font-size:60px;line-height:50px;color:#ddd;margin:120px auto;width:100%;height:50px;vertical-align:bottom}a.back,a.close{position:absolute;margin:0;line-height:0;padding:0;text-decoration:none}a.back{top:10px;left:18px;font-size:34px;color:#CDCDCD}a.back:hover{color:#777}a.close{top:18px;right:14px;font-size:32px;color:#CDCDCD;font-family:Garamond,"Apple Garamond"}a.close:hover{color:#777}.chevron::before{border-style:solid;border-width:.1em .1em 0 0;content:'';display:inline-block;height:.45em;position:relative;top:.15em;vertical-align:top;width:.45em;left:.25em;transform:rotate(-135deg)}.error{background:#FFE5E5;padding:15px;border-radius:5px;position:relative;margin-bottom:10px}p.error_text{margin-bottom:0}.notify{background:#D9F6FF;background:rgba(81,81,81,.8);color:#fff;position:fixed;padding:14px 0;margin:0 auto;width:100%;font-size:14px;right:0;top:0;z-index:9999}p.notify_text{margin-bottom:0}@media (max-width:920px){.simpleStore_getDetail_container{width:100%}.simpleCart_shelfItem{position:relative}.simpleCart_shelfItem .item_name{font-size:1.8rem}.simpleStore_getDetail{position:inherit;display:block;width:100%;margin:0}}@media (max-width:620px){.simpleStore_detailView .item_thumb{margin-bottom:10px;display:block;width:100%;height:auto}.simpleStore_detailView .item_add{position:inherit;display:block;width:100%}.cart_info{position:inherit;margin:0;padding-top:15px;text-align:left;border-top:1px solid #E1E1E1}.simpleCart_checkout{width:100%}.simpleCart_items{font-size:12px}.item-decrement,.item-increment,.item-total{display:none!important}} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **UPDATES COMING SOON! [Get notified](http://eepurl.com/dHBK11)** 2 | 3 | 4 | [Sign up](http://eepurl.com/gntUvf) to get updates on new features and releases 5 | 6 | # simpleStore 7 | 8 | [simpleStore](http://chrisdiana.github.io/simplestore) is a clean, responsive 9 | storefront boilerplate with no database you can setup in minutes. simpleStore is built on 10 | [simpleCart.js](http://simplecartjs.org) and [Skeleton](http://getskeleton.com) 11 | CSS Framework for a lightweight, fast, simple to use, and completely 12 | customizable experience. 13 | 14 | ![simpleStore Screenshot](https://raw.githubusercontent.com/chrisdiana/simplestore/gh-pages/images/screenshot-v1.1-full.png) 15 | ![simpleStore Cart Screenshot](https://raw.githubusercontent.com/chrisdiana/simplestore/gh-pages/images/screenshot-v1.1-cart.png) 16 | ![simpleStore Detail Screenshot](https://raw.githubusercontent.com/chrisdiana/simplestore/gh-pages/images/screenshot-v1.1-detail.png) 17 | 18 | --- 19 | 20 | # Features 21 | 22 | * No Databases, all client-side (just simple HTML, CSS & Javascript) 23 | * Lightweight & Fast 24 | * Tax Rate Calculations 25 | * Unlimited product attributes 26 | * Shipping 27 | * Multiple Currencies 28 | * Payment Gateways (Paypal, Google Checkout, Amazon Payments) 29 | * For more features check out [simpleCart.js](http://simplecartjs.org) 30 | 31 | # Plugins 32 | 33 | * Google Sheets (Control products from a Google Sheet instead of JSON file) 34 | 35 | # Demo 36 | 37 | You can see a working demo [here](http://chrisdiana.github.io/simplestore/demo/) 38 | 39 | 40 | # Installation 41 | 42 | Install with Bower 43 | 44 | ``` 45 | bower install 46 | ``` 47 | 48 | or manually install using the latest [release](https://github.com/chrisdiana/simplestore/releases/latest) 49 | 50 | 51 | # Setup 52 | 53 | 1.Make sure simpleStore is on a web server (any type will do as long as it can serve static web pages). 54 | 55 | 2.Configure your payment options in `js/config.js`. 56 | 57 | ``` 58 | checkout: { 59 | type: "PayPal" , 60 | email: "you@yours.com" 61 | }, 62 | ``` 63 | 64 | 3.Edit the `js/config.js` to your liking. 65 | 66 | 4.Add additional products in the `products.json` file. 67 | 68 | # Using Plugins 69 | 70 | To use a plugin, add a reference just before your `config.js` file 71 | 72 | ``` 73 | 74 | 75 | ``` 76 | 77 | ## HTML Version 78 | 79 | If you are looking for something more basic, check out the [HTML version on this 80 | branch](https://github.com/chrisdiana/simplestore/tree/simplestore-html). 81 | The HTML version uses plain HTML to build the store instead of a JSON 82 | file. 83 | 84 | Add additional products using the `
` tags. 85 | 86 | ## Credit where credit is due 87 | 88 | For further documentation on expanding/tweaking simpleStore, check out the 89 | framework/plugin pages. 90 | 91 | * [Skeleton](http://getskeleton.com) 92 | * [simpleCart.js](http://simplecartjs.org) 93 | * [Normalize.css](http://necolas.github.io/normalize.css) 94 | * [FontAwesome](http://fortawesome.github.io/Font-Awesome) 95 | * [jQuery](https://jquery.com/) 96 | 97 | ### A note about JavaScript shopping carts 98 | 99 | ALL JavaScript shopping carts are NOT fullproof. Because simpleStore is fully 100 | client-side, some users may attempt to alter prices before checkout. 101 | SimpleStore does the best it can to minimize this 102 | kind of activity. Make sure to monitor your sales. Just like in real life, if someone 103 | walks into your store and changes the price tag, you will certainly not honor 104 | those changes. 105 | 106 | 107 | # Contributing 108 | 109 | All forms of contribution are welcome: bug reports, bug fixes, pull requests and simple suggestions. 110 | If you do wish to contribute, please follow the [Airbnb Javascript Style Guide](https://github.com/airbnb/javascript) Thanks! 111 | 112 | 113 | ## List of contributors 114 | 115 | You can find the list of contributors [here](https://github.com/chrisdiana/simplestore/graphs/contributors). 116 | -------------------------------------------------------------------------------- /plugins/google-sheets/google-sheets.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SimpleStore Google Sheets Plugin 3 | * To use Google spreadsheet as your database, follow the steps below: 4 | * 1. Use the "DemoSpreadsheet.xlsx" as a starting point 5 | * 2. Create a new Google spreadsheet 6 | * 3. Set sharing permissions to either “Public” or set to “Anyone with link can view” 7 | * 4. Publish the sheet (File -> Publish to the web -> Publish) 8 | * 5. Add the spreadsheet ID to your 'config.js' ( spreadsheetID : "XXXXXXXXXXXXXXXXXXXXXXX" ) 9 | */ 10 | 11 | simpleStore.plugins.google = (function() { 12 | 13 | var storeProducts = verifyProducts = []; 14 | 15 | function getSpreadsheetData(s, verify, callback) { 16 | 17 | verify = typeof verify !== 'undefined' ? verify : false; 18 | 19 | var hostname = "https://spreadsheets.google.com"; 20 | var format = "json"; 21 | var spreadsheetURL = hostname + "/feeds/worksheets/" + s.spreadsheetID + "/public/full?alt=" + format; 22 | var mainsheetURL = hostname + "/feeds/list/" + s.spreadsheetID + "/od6/public/values?alt=" + format; 23 | var settingsSheetName = "Settings"; 24 | var productsSheetName = "Products"; 25 | var sheetIDs = {}; 26 | 27 | function getSheetInfo (url, callback) { 28 | // Need to do this because od6 is default Google Sheet ID 29 | $.getJSON(url) 30 | .done(function(data) { 31 | 32 | var sheets = data.feed.entry; 33 | 34 | $(sheets).each(function(i, sheet) { 35 | 36 | var title = sheet.title.$t; 37 | var id = sheet.id.$t; 38 | var sheetID = id.substr(id.lastIndexOf('/') + 1); 39 | 40 | if(title == settingsSheetName) { 41 | sheetIDs.settingsSheetID = sheetID; 42 | } 43 | if(title == productsSheetName) { 44 | sheetIDs.productsSheetID = sheetID; 45 | } 46 | }); 47 | callback(sheetIDs.settingsSheetID); 48 | loadProductData(sheetIDs.productsSheetID); 49 | }); 50 | } 51 | 52 | function loadSiteSettings (id, callback) { 53 | 54 | var settingsSheetURL = hostname + "/feeds/list/" + s.spreadsheetID + "/" + id + "/public/values?alt=" + format; 55 | 56 | $.getJSON(settingsSheetURL) 57 | .done(function(data) { 58 | var data = data.feed.entry; 59 | var s = simpleStore.settings; 60 | 61 | if(data[0]) { 62 | 63 | var siteName = data[0].gsx$sitenametextorimagelink.$t; 64 | var columns = data[0].gsx$columns123.$t; 65 | 66 | if (siteName) { 67 | s.brand = siteName; 68 | } 69 | if (columns) { 70 | s.numColumns = columns; 71 | } 72 | 73 | simpleStore.setLayout(s); 74 | } 75 | }); 76 | } 77 | 78 | function loadProductData (id) { 79 | 80 | var productsSheetURL = hostname + "/feeds/list/" + s.spreadsheetID + "/" + id + "/public/values?alt=" + format; 81 | 82 | // Get Main Sheet Products data 83 | $.getJSON(productsSheetURL) 84 | .done(function(data) { 85 | 86 | var productsData = data.feed.entry; 87 | 88 | // Build products 89 | $(productsData).each(function(i) { 90 | 91 | var options = this.gsx$options.$t; 92 | var setOptions = function(options) { 93 | var productOptions = []; 94 | if(options) { 95 | var opts = options.split(";").filter(function(el) {return el.length != 0}); 96 | $(opts).each(function(i, option) { 97 | var opt = option.trim().split(":"), 98 | key = opt[0], 99 | val = opt[1], 100 | optObj = {}; 101 | 102 | optObj[key] = val; 103 | productOptions.push(optObj); 104 | }); 105 | } 106 | return productOptions; 107 | }; 108 | 109 | // Get product values 110 | var product = { 111 | name : this.gsx$name.$t, 112 | price : this.gsx$price.$t, 113 | description : this.gsx$description.$t, 114 | options : setOptions(options), 115 | image : this.gsx$image.$t 116 | }; 117 | 118 | if (verify) { 119 | verifyProducts.push(product); 120 | } else { 121 | storeProducts.push(product); 122 | } 123 | }); 124 | callback(); 125 | }) 126 | .fail(function(data){ 127 | if (verify) { 128 | var errorMsg = 'There was an error validating your cart.'; 129 | } else { 130 | var errorMsg = 'Error loading spreadsheet data. Make sure the spreadsheet ID is correct.'; 131 | } 132 | setTimeout(function(){ simpleStore.renderError(s, errorMsg); }, 1000); 133 | }); 134 | } 135 | 136 | // Get Sheet data 137 | getSheetInfo(spreadsheetURL, loadSiteSettings); 138 | } 139 | 140 | function validatePrices(s, checkoutData) { 141 | verifyProducts = []; 142 | 143 | getSpreadsheetData(s, true, function() { 144 | if(simpleStore.verifyCheckoutData(checkoutData, verifyProducts, true)) { 145 | simpleStore.checkout(s, checkoutData); 146 | } else { 147 | var errorMsg = 'There was an error validating your cart.'; 148 | simpleStore.renderError(s, errorMsg); 149 | } 150 | }); 151 | } 152 | 153 | return { 154 | init: function(callback) { 155 | var s = simpleStore.settings; 156 | 157 | // Clears out brand to allow for spreadsheet site name 158 | s.brand = ""; 159 | simpleStore.setLayout(s); 160 | 161 | getSpreadsheetData(s, false, function(){ 162 | callback(storeProducts); 163 | }); 164 | }, 165 | validate: function(checkoutData) { 166 | validatePrices(simpleStore.settings, checkoutData); 167 | } 168 | }; 169 | })(); 170 | -------------------------------------------------------------------------------- /js/simpleStore.min.js: -------------------------------------------------------------------------------- 1 | /* simplestore | Copyright 2015 Chris Diana (https://github.com/cdmedia/simplestore) | MIT license | v1.5 */ 2 | var simpleStore={products:[],plugins:{},settings:{numColumns:3,brand:"SimpleStore",mode:"JSON",JSONFile:"products.json",fadeSpeed:200,buttonColor:null,backgroundColor:null,textColor:null,container:$(".simpleStore_container"),cartContainer:$(".simpleStore_cart_container"),rowClass:"simpleStore_row_",columnWidthClasses:{1:"",2:"one-half",3:"one-third"}},productPageOptions:["OneOfAKind"],extend:function(a,b,c){var d;"undefined"==typeof b&&(b=a,a=simpleStore);for(d in b)Object.prototype.hasOwnProperty.call(b,d)&&(a[d]=b[d]);return c(),a},render:function(a,b){var c=a.split("/")[0],d={"":function(){simpleStore.renderProducts(simpleStore.products,b)},"#product":function(){var c=a.split("#product/")[1].trim();simpleStore.renderSingleProduct(c,b)},"#cart":function(){simpleStore.renderCart(b)}};d[c]?d[c]():simpleStore.renderError(b)},insertData:function(a,b){a.find(".item_thumb").attr("src",b.image),a.find(".item_name").text(b.name),a.find(".item_price").text(b.price),a.find(".item_description").text(b.description)},renderProducts:function(a,b){var c,d=1,e=(a.length,Math.ceil(a.length/b.numColumns));b.cartContainer.hide(),b.container.fadeOut(b.fadeSpeed,function(){b.container.html("").fadeIn(b.fadeSpeed);for(var f=0;e>f;f++)b.container.append('
');var g=b.columnWidthClasses;for(var h in g)h==b.numColumns&&(c=g[h]);a.forEach(function(a,e){if(!a.soldOut){var f=$("#products-template").html(),g=$(f);g.first().addClass(c),simpleStore.insertData(g,a);var h=g.find(".simpleStore_getDetail");h.on("click",function(b){b.preventDefault(),window.location.hash="product/"+a.id}),0===e&&(e=1),e%b.numColumns===0&&d++,$("."+b.rowClass+d).append(g)}})})},renderProductOptions:function(a,b){var c="";return a.forEach(function(b){if(simpleStore.productPageOptions in b)simpleStore.renderProductPageOptions(b);else{var d="",e=Object.keys(b)[0].trim(),f=b[e].trim().split(",");$(f).each(function(a,b){d+='"}),a.length&&(c+="")}}),c},renderProductPageOptions:function(a){a.OneOfAKind&&$(".qty").hide()},renderSingleProduct:function(a,b){b.container.fadeOut(b.fadeSpeed,function(){var c=$("#product-detail-template").html(),d=$(c);simpleStore.products.forEach(function(c){if(c.id==a){if(simpleStore.insertData(d,c),b.container.html(d),c.options.length){var e=simpleStore.renderProductOptions(c.options,b);$(".simpleStore_options").append(e)}b.container.fadeIn(b.fadeSpeed)}})})},renderCart:function(a){a.container.fadeOut(a.fadeSpeed,function(){a.cartContainer.fadeIn(a.fadeSpeed)})},renderError:function(a,b){var c=$("#error-template").html(),d=$(c);a.container.html("").fadeIn(a.fadeSpeed),b.length&&d.find(".error_text").text(b),a.container.append(d),a.container.fadeIn(a.fadeSpeed),d.find(".alert_close").on("click",function(b){b.preventDefault(),d.fadeOut(a.fadeSpeed,function(){d.remove()})})},handleFailure:function(a,b){setTimeout(function(){simpleStore.renderError(a,b)},1e3)},notifier:function(a){s=this.settings;var b=$("#notify-template").html(),c=$(b);a.length&&(c.find(".notify_text").text(a),s.container.append(c),c.hide(),c.fadeIn(s.fadeSpeed),setTimeout(function(){c.fadeOut(s.fadeSpeed)},1e3))},initJSON:function(a){var b='There was an error loading the JSON file. Please make sure you have "'+a.JSONFile+'" file in your main directory.';$.get(a.JSONFile).success(function(){$.getJSON(a.JSONFile,function(a){simpleStore.setProducts(a.products)}).fail(function(){simpleStore.handleFailure(a,b)})}).fail(function(){simpleStore.handleFailure(a,b)})},checkMode:function(a){(a.hasOwnProperty("spreadsheetID")||a.hasOwnProperty("spreadsheetId"))&&(a.mode="Google")},checkout:function(a,b){$.isEmptyObject(b)||(simpleCart.checkout(),a.cartContainer.fadeOut(a.fadeSpeed,function(){a.container.html(''),a.container.fadeIn(a.fadeSpeed)}))},verifyCheckoutData:function(a,b,c){for(var d in a)if(a.hasOwnProperty(d))for(var e=a[d],f=e.name,g=e.price,h=0;h0&&a.forEach(function(a,b){a.id=b+1,simpleStore.products.push(a)}),$(window).trigger("hashchange")},setLayout:function(a){a.brand.match("^http://")||a.brand.match("^https://")||a.brand.match("^www.")?$(".brand").html(''):$(".brand").html("
"+a.brand+"
"),$("title").html(a.brand)},generateCart:function(a){var b=$("#cart-template").html(),c=$(b);a.cartContainer.html(c)},generateStore:function(){var a=this.settings;switch(this.checkMode(a),$(window).on("hashchange",function(){simpleStore.render(window.location.hash,a)}),a.mode){case"JSON":this.initJSON(a);break;case"Google":if(simpleStore.plugins.google)simpleStore.plugins.google.init(function(b){simpleStore.setProducts(b,a)});else{var b="There was an error loading the Google plugin. Make sure it is installed properly.";simpleStore.renderError(a,b)}break;default:this.initJSON(a)}this.generateCart(a),this.setLayout(a),$(".simpleStore_checkout").on("click",function(b){b.preventDefault(),simpleStore.validatePrices(a)}),$(".simpleStore_viewCart").on("click",function(a){a.preventDefault(),window.location="#cart"}),$(".view_close").on("click",function(a){a.preventDefault(),window.location.hash=""}),simpleCart({afterAdd:function(){simpleStore.notifier("Item added to cart")}})},init:function(a){return $.isPlainObject(a)?this.extend(this.settings,a,function(){simpleStore.generateStore()}):void 0}}; -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | simpleStore 9 | 10 | 11 | 12 | 14 | 15 | 16 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 34 |
35 |
36 | 37 | 38 | Cart 39 | 40 |
41 |
42 |
43 |
44 | 45 | 47 | 48 | 49 | 66 | 67 | 68 | 92 | 93 | 94 | 127 | 128 | 129 | 136 | 137 | 138 | 143 | 144 | 146 | 147 | 148 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /css/simpleStore.css: -------------------------------------------------------------------------------- 1 | /* 2 | * simplestore 3 | * Copyright 2015 Chris Diana 4 | * https://github.com/cdmedia/simplestore 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | */ 8 | 9 | select { 10 | display: block; 11 | float: left; 12 | margin-right: 10px; 13 | } 14 | 15 | a.hover { 16 | text-decoration: none; 17 | } 18 | 19 | /* Main Layout */ 20 | 21 | .simpleStore { 22 | margin-top: 20px; 23 | } 24 | 25 | .brand { 26 | display: block; 27 | float: left; 28 | text-decoration: none; 29 | color: #000; 30 | } 31 | 32 | .brand:hover { 33 | color: #777; 34 | } 35 | 36 | .brand img { 37 | max-height: 50px; 38 | } 39 | 40 | /* simpleCart */ 41 | 42 | .simpleCart_shelfItem { 43 | border: 1px solid #EFEFEF; 44 | padding: 25px 18px 10px 18px; 45 | margin: 15px 0; 46 | } 47 | 48 | .simpleCart_items { 49 | display: table; 50 | border-collapse: collapse; 51 | width: 100%; 52 | min-height: 20px; 53 | margin-bottom: 50px; 54 | } 55 | 56 | .simpleCart_items div:first-child { 57 | width: 100% 58 | } 59 | 60 | .simpleCart_decrement, .simpleCart_increment { 61 | font-size: 18px; 62 | line-height: 5px; 63 | text-decoration: none; 64 | } 65 | 66 | .simpleCart_shelfItem label { 67 | display: block; 68 | float: left; 69 | line-height: 40px; 70 | margin-right: 10px; 71 | } 72 | 73 | .item_thumb, .detail_thumb { 74 | height: 150px; 75 | display: block; 76 | margin: 0 auto; 77 | padding-bottom: 10px; 78 | } 79 | 80 | .item_Quantity { 81 | display: inline-block; 82 | width: 50px; 83 | } 84 | 85 | .item_price { 86 | font-weight: 700; 87 | font-size: 20px; 88 | line-height: 22px; 89 | } 90 | 91 | .item_description { 92 | margin-top: 1rem; 93 | margin-bottom: 1.5rem; 94 | } 95 | 96 | .headerRow { 97 | display: table-row; 98 | font-weight: 700; 99 | border-bottom: 1px solid #E1E1E1; 100 | } 101 | 102 | .headerRow div { 103 | display: table-cell; 104 | padding: 10px; 105 | } 106 | 107 | .itemRow.odd { 108 | background: #FAFAFA; 109 | } 110 | 111 | .itemRow { 112 | display: table-row; 113 | border-bottom: 1px solid #E1E1E1; 114 | } 115 | 116 | .itemRow:last-child { 117 | border-bottom: 0 solid #fff; 118 | } 119 | 120 | .itemRow div { 121 | display: table-cell; 122 | padding: 10px; 123 | } 124 | 125 | .itemRow .item-quantity { 126 | text-align: center; 127 | } 128 | 129 | /* Cart */ 130 | 131 | .cart_info { 132 | position: absolute; 133 | bottom: 0px; 134 | right: 0px; 135 | margin: 22px; 136 | text-align: right; 137 | } 138 | 139 | .cart_info div { 140 | display: inline; 141 | } 142 | 143 | .cart_info_item { 144 | display: block !important; 145 | } 146 | 147 | .cart_total { 148 | font-size: 20px; 149 | } 150 | 151 | /* Views */ 152 | 153 | .simpleStore_getDetail_container { 154 | display: block; 155 | float: left; 156 | width: 50%; 157 | padding: 5px 0px; 158 | } 159 | 160 | .simpleStore_detailView { 161 | margin: 15px 0px; 162 | border: 1px solid #EFEFEF; 163 | position: relative; 164 | } 165 | 166 | .simpleStore_detailView.simpleCart_shelfItem { 167 | padding: 26px 18px 8px 18px; 168 | } 169 | 170 | .simpleStore_detailView .item_add { 171 | margin: 0px; 172 | margin-bottom: 0px; 173 | } 174 | 175 | .simpleStore_detailView .item_name { 176 | margin-bottom: 1rem; 177 | } 178 | 179 | .simpleStore_detailView .item_price { 180 | display: block; 181 | margin-bottom: 1.5rem; 182 | } 183 | 184 | .simpleStore_cart_container { 185 | display: none; 186 | } 187 | 188 | .simpleStore_cart { 189 | position: relative; 190 | border: 1px solid #EFEFEF; 191 | padding: 22px; 192 | min-height: 230px; 193 | margin: 15px 0px; 194 | } 195 | 196 | a.simpleCart_empty { 197 | color: #CDCDCD; 198 | text-decoration: none; 199 | text-transform: uppercase; 200 | font-size: 14px; 201 | } 202 | 203 | a.simpleCart_empty:hover { 204 | color: #777; 205 | } 206 | 207 | .simpleCart_checkout { 208 | margin: 12px 0px 0px 0px; 209 | } 210 | 211 | 212 | /* Utility */ 213 | 214 | .hidden { 215 | display: none; 216 | } 217 | 218 | .loader { 219 | display: inline-block; 220 | font-size: 60px; 221 | line-height: 50px; 222 | color: #ddd; 223 | margin: 120px auto; 224 | width: 100%; 225 | height: 50px; 226 | text-align: center; 227 | vertical-align: bottom; 228 | } 229 | 230 | a.back { 231 | position: absolute; 232 | top: 10px; 233 | left: 18px; 234 | line-height: 0px; 235 | margin: 0; 236 | padding: 0; 237 | font-size: 34px; 238 | color: #CDCDCD; 239 | text-decoration: none; 240 | } 241 | 242 | a.back:hover { 243 | color: #777; 244 | } 245 | 246 | a.close { 247 | position: absolute; 248 | top: 18px; 249 | right: 14px; 250 | margin: 0; 251 | line-height: 0px; 252 | padding: 0; 253 | font-size: 32px; 254 | color: #CDCDCD; 255 | text-decoration: none; 256 | font-family: Garamond, "Apple Garamond"; 257 | } 258 | 259 | a.close:hover { 260 | color: #777; 261 | } 262 | 263 | .chevron::before { 264 | border-style: solid; 265 | border-width: 0.1em 0.1em 0 0; 266 | content: ''; 267 | display: inline-block; 268 | height: 0.45em; 269 | position: relative; 270 | top: 0.15em; 271 | transform: rotate(-45deg); 272 | vertical-align: top; 273 | width: 0.45em; 274 | left: 0.25em; 275 | transform: rotate(-135deg); 276 | } 277 | 278 | .error { 279 | background: #FFE5E5; 280 | padding: 15px; 281 | border-radius: 5px; 282 | position: relative; 283 | margin-bottom: 10px; 284 | } 285 | p.error_text { 286 | margin-bottom: 0rem; 287 | } 288 | 289 | .notify { 290 | background: #D9F6FF; 291 | background: rgba(81, 81, 81, 0.8); 292 | color: #fff; 293 | position: fixed; 294 | padding: 14px 0; 295 | text-align: center; 296 | margin: 0 auto; 297 | width: 100%; 298 | font-size: 14px; 299 | top: 0; 300 | right: 0; 301 | top: 0; 302 | z-index: 9999; 303 | } 304 | p.notify_text { 305 | margin-bottom: 0rem; 306 | } 307 | 308 | @media (max-width: 920px) { 309 | .simpleStore_getDetail_container { 310 | width: 100%; 311 | } 312 | .simpleCart_shelfItem { 313 | position: relative; 314 | } 315 | .simpleCart_shelfItem .item_name { 316 | font-size: 1.8rem; 317 | } 318 | .simpleStore_getDetail { 319 | position: inherit; 320 | display: block; 321 | width: 100%; 322 | margin: 0px; 323 | margin-bottom: 0px; 324 | } 325 | } 326 | 327 | @media (max-width: 620px) { 328 | 329 | .simpleStore_detailView .item_thumb { 330 | margin-bottom: 10px; 331 | display: block; 332 | width: 100%; 333 | height: auto; 334 | } 335 | 336 | .simpleStore_detailView .item_add { 337 | position: inherit; 338 | display: block; 339 | width: 100%; 340 | } 341 | 342 | .cart_info { 343 | position: inherit; 344 | margin: 0px; 345 | padding-top: 15px; 346 | text-align: left; 347 | border-top: 1px solid #E1E1E1; 348 | } 349 | 350 | .simpleCart_checkout { 351 | width: 100%; 352 | } 353 | 354 | .simpleCart_items { 355 | font-size: 12px; 356 | } 357 | 358 | /* Better mobile support for cart */ 359 | .item-decrement, .item-increment, .item-total { 360 | display: none !important; 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /js/simpleStore.js: -------------------------------------------------------------------------------- 1 | /* 2 | * simplestore 3 | * Copyright 2015 Chris Diana 4 | * https://github.com/cdmedia/simplestore 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | */ 8 | 9 | var simpleStore = { 10 | 11 | products: [], 12 | plugins: {}, 13 | 14 | // Default settings 15 | settings: { 16 | numColumns: 3, 17 | brand: "SimpleStore", 18 | mode: "JSON", 19 | JSONFile: "products.json", 20 | fadeSpeed: 200, 21 | buttonColor: null, 22 | backgroundColor: null, 23 | textColor: null, 24 | container: $('.simpleStore_container'), 25 | cartContainer: $('.simpleStore_cart_container'), 26 | rowClass: 'simpleStore_row_', 27 | columnWidthClasses: { 28 | 1: "", 29 | 2: "one-half", 30 | 3: "one-third" 31 | } 32 | }, 33 | 34 | productPageOptions: [ 35 | 'OneOfAKind' 36 | ], 37 | 38 | extend: function (target, opts, callback) { 39 | var next; 40 | if (typeof opts === "undefined") { 41 | opts = target; 42 | target = simpleStore; 43 | } 44 | for (next in opts) { 45 | if (Object.prototype.hasOwnProperty.call(opts, next)) { 46 | target[next] = opts[next]; 47 | } 48 | } 49 | callback(); // check user config options 50 | return target; 51 | }, 52 | 53 | render: function (url, s) { 54 | var type = url.split('/')[0]; 55 | 56 | var map = { 57 | // Main view 58 | '': function () { 59 | simpleStore.renderProducts(simpleStore.products, s); 60 | }, 61 | // Detail view 62 | '#product': function () { 63 | var id = url.split('#product/')[1].trim(); 64 | simpleStore.renderSingleProduct(id, s); 65 | }, 66 | // Cart view 67 | '#cart': function () { 68 | simpleStore.renderCart(s); 69 | } 70 | }; 71 | 72 | if (map[type]) { 73 | map[type](); 74 | } else { 75 | simpleStore.renderError(s); 76 | } 77 | }, 78 | 79 | insertData: function (tmpl, product) { 80 | tmpl.find('.item_thumb').attr("src", product.image); 81 | tmpl.find('.item_name').text(product.name); 82 | tmpl.find('.item_price').text(product.price); 83 | tmpl.find('.item_description').text(product.description); 84 | }, 85 | 86 | renderProducts: function (products, s) { 87 | 88 | var rowCount = 1, 89 | numProducts = products.length, 90 | numRows = Math.ceil(products.length / s.numColumns), 91 | itemWidth; 92 | 93 | s.cartContainer.hide(); 94 | s.container.fadeOut(s.fadeSpeed, function () { 95 | 96 | // Empty out main container on load 97 | s.container.html('').fadeIn(s.fadeSpeed); 98 | 99 | // Build rows based on number of products 100 | for (var r = 0; r < numRows; r++) { 101 | s.container.append('
'); 102 | } 103 | 104 | // Get item column width 105 | var widthClasses = s.columnWidthClasses; 106 | for (var k in widthClasses) { 107 | if (k == s.numColumns) { 108 | itemWidth = widthClasses[k]; 109 | } 110 | } 111 | 112 | // List layout 113 | products.forEach(function (product, i) { 114 | 115 | if (!product.soldOut) { 116 | var tmpl = $('#products-template').html(), 117 | $tmpl = $(tmpl); 118 | 119 | // Set item width 120 | $tmpl.first().addClass(itemWidth); 121 | 122 | // Insert data into template 123 | simpleStore.insertData($tmpl, product); 124 | 125 | // Render detail view on hash change 126 | var getDetail = $tmpl.find('.simpleStore_getDetail'); 127 | getDetail.on('click', function (e) { 128 | e.preventDefault(); 129 | window.location.hash = 'product/' + product.id; 130 | }); 131 | 132 | // Check where to add new item based on row 133 | if (i === 0) { 134 | i = 1; 135 | } 136 | if (i % (s.numColumns) === 0) { 137 | rowCount++; 138 | } 139 | 140 | // Append to appropriate container 141 | $('.' + s.rowClass + rowCount).append($tmpl); 142 | } 143 | }); 144 | }); 145 | }, 146 | 147 | renderProductOptions: function (options, s) { 148 | 149 | var optionsLayout = ''; 150 | 151 | options.forEach(function (option) { 152 | if (!(simpleStore.productPageOptions in option)) { 153 | var selectItems = ''; 154 | var attributeLabel = Object.keys(option)[0].trim(); 155 | var attributeValues = option[attributeLabel].trim().split(","); 156 | 157 | // Set attribute values 158 | $(attributeValues).each(function (attribute, attributeValue) { 159 | selectItems += ''; 160 | }); 161 | 162 | // Build options layout 163 | if (options.length) { 164 | optionsLayout += ''; 165 | } 166 | } else { 167 | simpleStore.renderProductPageOptions(option); 168 | } 169 | }); 170 | 171 | return optionsLayout; 172 | }, 173 | 174 | renderProductPageOptions: function (option) { 175 | if (option.OneOfAKind) { 176 | $('.qty').hide(); 177 | } 178 | }, 179 | 180 | renderSingleProduct: function (id, s) { 181 | 182 | s.container.fadeOut(s.fadeSpeed, function () { 183 | 184 | var tmpl = $('#product-detail-template').html(), 185 | $tmpl = $(tmpl); 186 | 187 | simpleStore.products.forEach(function (product) { 188 | if (product.id == id) { 189 | 190 | // Insert data into template 191 | simpleStore.insertData($tmpl, product); 192 | 193 | // Load detail view into main container 194 | s.container.html($tmpl); 195 | 196 | // Render product options 197 | if (product.options.length) { 198 | var options = simpleStore.renderProductOptions(product.options, s); 199 | $('.simpleStore_options').append(options); 200 | } 201 | s.container.fadeIn(s.fadeSpeed); 202 | } 203 | }); 204 | }); 205 | }, 206 | 207 | renderCart: function (s) { 208 | s.container.fadeOut(s.fadeSpeed, function () { 209 | s.cartContainer.fadeIn(s.fadeSpeed); 210 | }); 211 | }, 212 | 213 | renderError: function (s, msg) { 214 | var tmpl = $('#error-template').html(), 215 | $tmpl = $(tmpl); 216 | 217 | // Empty out main container on load 218 | s.container.html('').fadeIn(s.fadeSpeed); 219 | 220 | if (msg.length) { 221 | $tmpl.find('.error_text').text(msg); 222 | } 223 | s.container.append($tmpl); 224 | s.container.fadeIn(s.fadeSpeed); 225 | 226 | $tmpl.find('.alert_close').on('click', function (e) { 227 | e.preventDefault(); 228 | $tmpl.fadeOut(s.fadeSpeed, function() { 229 | $tmpl.remove(); 230 | }); 231 | }); 232 | }, 233 | 234 | handleFailure: function(s, errorMsg) { 235 | setTimeout(function () { 236 | simpleStore.renderError(s, errorMsg); 237 | }, 1000); 238 | }, 239 | 240 | notifier: function(msg) { 241 | s = this.settings; 242 | 243 | var tmpl = $('#notify-template').html(), 244 | $tmpl = $(tmpl); 245 | 246 | if (msg.length) { 247 | $tmpl.find('.notify_text').text(msg); 248 | s.container.append($tmpl); 249 | $tmpl.hide(); 250 | $tmpl.fadeIn(s.fadeSpeed); 251 | setTimeout(function () { 252 | $tmpl.fadeOut(s.fadeSpeed); 253 | }, 1000); 254 | } 255 | }, 256 | 257 | initJSON: function (s) { 258 | var errorMsg = 'There was an error loading the JSON file.' + 259 | ' Please make sure you have "' + s.JSONFile + '" file in' + 260 | ' your main directory.'; 261 | 262 | // Checks to make sure file exists 263 | $.get(s.JSONFile) 264 | .success(function () { 265 | // Get product data from JSON file 266 | $.getJSON(s.JSONFile, function (data) { 267 | simpleStore.setProducts(data.products); 268 | }) 269 | .fail(function () { simpleStore.handleFailure(s, errorMsg); }); 270 | }) 271 | .fail(function () { simpleStore.handleFailure(s, errorMsg); }); 272 | }, 273 | 274 | checkMode : function (s) { 275 | if (s.hasOwnProperty("spreadsheetID") || s.hasOwnProperty("spreadsheetId")) { 276 | s.mode = "Google"; 277 | } 278 | }, 279 | 280 | checkout : function (s, checkoutData) { 281 | if (!$.isEmptyObject(checkoutData)) { 282 | simpleCart.checkout(); 283 | s.cartContainer.fadeOut(s.fadeSpeed, function () { 284 | s.container.html(''); 285 | s.container.fadeIn(s.fadeSpeed); 286 | }); 287 | } 288 | }, 289 | 290 | verifyCheckoutData : function (cdata, adata, v) { 291 | for (var d in cdata) { 292 | if (cdata.hasOwnProperty(d)) { 293 | var cp = cdata[d], cn = cp.name, cpp = cp.price; 294 | for (var i = 0; i < adata.length; i++) { 295 | var ap = adata[i], an = ap.name, app = ap.price; 296 | if (cn === an) {if (cpp != app) { v = false; }} 297 | } 298 | } 299 | } 300 | return v; 301 | }, 302 | 303 | validatePrices : function (s) { 304 | var checkoutData = JSON.parse(localStorage.simpleCart_items), 305 | errorMsg = 'There was an error validating your cart.'; 306 | 307 | if (s.mode === "JSON") { 308 | $.get(s.JSONFile) 309 | .success(function () { 310 | $.getJSON(s.JSONFile, function (data) { 311 | var JSONData = data.products; 312 | if (simpleStore.verifyCheckoutData(checkoutData, JSONData, true)) { 313 | simpleStore.checkout(s, checkoutData); 314 | } else { 315 | simpleStore.renderError(s, errorMsg); 316 | } 317 | }) 318 | .fail(function () { simpleStore.handleFailure(s, errorMsg); }); 319 | }) 320 | .fail(function () { simpleStore.handleFailure(s, errorMsg); }); 321 | } else { 322 | var plugin = s.mode.toLowerCase(); 323 | if(simpleStore.plugins[plugin]) { 324 | simpleStore.plugins[plugin].validate(checkoutData); 325 | } 326 | } 327 | }, 328 | 329 | setProducts: function (products, s) { 330 | if (products.length > 0) { 331 | products.forEach(function (product, index) { 332 | product.id = index + 1; 333 | simpleStore.products.push(product); 334 | }); 335 | } 336 | 337 | // Manually trigger on initial load 338 | $(window).trigger('hashchange'); 339 | }, 340 | 341 | setLayout: function (s) { 342 | // Set brand 343 | if (s.brand.match('^http://') || s.brand.match('^https://') || s.brand.match('^www.')) { 344 | $('.brand').html(''); 345 | } else { 346 | $('.brand').html('
' + s.brand + '
'); 347 | } 348 | 349 | // Set title 350 | $('title').html(s.brand); 351 | }, 352 | 353 | generateCart: function (s) { 354 | var tmpl = $('#cart-template').html(), 355 | $tmpl = $(tmpl); 356 | s.cartContainer.html($tmpl); 357 | }, 358 | 359 | generateStore: function () { 360 | 361 | var s = this.settings; 362 | 363 | // Set mode 364 | this.checkMode(s); 365 | 366 | // Check for hash changes 367 | $(window).on('hashchange', function () { 368 | simpleStore.render(window.location.hash, s); 369 | }); 370 | 371 | // Set products based on mode 372 | switch (s.mode) { 373 | case 'JSON': 374 | this.initJSON(s); 375 | break; 376 | case 'Google': 377 | if(simpleStore.plugins.google) { 378 | simpleStore.plugins.google.init(function (products) { 379 | simpleStore.setProducts(products, s); 380 | }); 381 | } else { 382 | var errorMsg = 'There was an error loading the Google plugin. Make sure it is installed properly.'; 383 | simpleStore.renderError(s, errorMsg); 384 | } 385 | break; 386 | default: 387 | this.initJSON(s); 388 | } 389 | 390 | // Because simpleCart items appends to cart, set up only once 391 | this.generateCart(s); 392 | 393 | // Setup layout 394 | this.setLayout(s); 395 | 396 | // Handle Checkout 397 | $('.simpleStore_checkout').on('click', function (e) { 398 | e.preventDefault(); 399 | simpleStore.validatePrices(s); 400 | }); 401 | 402 | // View Cart 403 | $('.simpleStore_viewCart').on('click', function (e) { 404 | e.preventDefault(); 405 | window.location = '#cart'; 406 | }); 407 | 408 | // Go to home on close 409 | $('.view_close').on('click', function (e) { 410 | e.preventDefault(); 411 | window.location.hash = ''; 412 | }); 413 | 414 | // SimpleCart extend 415 | simpleCart({ 416 | afterAdd: function() { 417 | simpleStore.notifier('Item added to cart'); 418 | } 419 | }); 420 | }, 421 | 422 | init: function (options) { 423 | if ($.isPlainObject(options)) { 424 | return this.extend(this.settings, options, function () { 425 | simpleStore.generateStore(); 426 | }); 427 | } 428 | } 429 | }; 430 | -------------------------------------------------------------------------------- /js/simpleCart.min.js: -------------------------------------------------------------------------------- 1 | /* simpleCart(js) | Copyright 2012 Brett Wejrowski Dual (http://simplecartjs.org/) | MIT or GPL licenses | v3.0.5 */ 2 | (function(p,f){var s="string",k=function(e,f){return typeof e===f},e=function(e){return k(e,"undefined")},h=function(e){return k(e,"function")},y=function(e){return"object"===typeof HTMLElement?e instanceof HTMLElement:"object"===typeof e&&1===e.nodeType&&"string"===typeof e.nodeName},C=function(q){function E(a){return b.extend({attr:"",label:"",view:"attr",text:"",className:"",hide:!1},a||{})}function F(){if(!b.isReady){try{f.documentElement.doScroll("left")}catch(a){setTimeout(F,1);return}b.init()}} 3 | var t={MooTools:"$$",Prototype:"$$",jQuery:"*"},n=0,r={},x=q||"simpleCart",z={};q={};q={};var v=p.localStorage,l=p.console||{msgs:[],log:function(a){l.msgs.push(a)}},D={USD:{code:"USD",symbol:"$",name:"US Dollar"},AUD:{code:"AUD",symbol:"$",name:"Australian Dollar"},BRL:{code:"BRL",symbol:"R$",name:"Brazilian Real"},CAD:{code:"CAD",symbol:"$",name:"Canadian Dollar"},CZK:{code:"CZK",symbol:" Kč",name:"Czech Koruna",after:!0},DKK:{code:"DKK",symbol:"DKK ",name:"Danish Krone"}, 4 | EUR:{code:"EUR",symbol:"€",name:"Euro"},HKD:{code:"HKD",symbol:"$",name:"Hong Kong Dollar"},HUF:{code:"HUF",symbol:"Ft",name:"Hungarian Forint"},ILS:{code:"ILS",symbol:"₪",name:"Israeli New Sheqel"},JPY:{code:"JPY",symbol:"¥",name:"Japanese Yen",accuracy:0},MXN:{code:"MXN",symbol:"$",name:"Mexican Peso"},NOK:{code:"NOK",symbol:"NOK ",name:"Norwegian Krone"},NZD:{code:"NZD",symbol:"$",name:"New Zealand Dollar"},PLN:{code:"PLN",symbol:"PLN ",name:"Polish Zloty"}, 5 | GBP:{code:"GBP",symbol:"£",name:"Pound Sterling"},SGD:{code:"SGD",symbol:"$",name:"Singapore Dollar"},SEK:{code:"SEK",symbol:"SEK ",name:"Swedish Krona"},CHF:{code:"CHF",symbol:"CHF ",name:"Swiss Franc"},THB:{code:"THB",symbol:"฿",name:"Thai Baht"},BTC:{code:"BTC",symbol:" BTC",name:"Bitcoin",accuracy:4,after:!0}},m={checkout:{type:"PayPal",email:"you@yours.com"},currency:"USD",language:"english-us",cartStyle:"div",cartColumns:[{attr:"name",label:"Name"},{attr:"price",label:"Price", 6 | view:"currency"},{view:"decrement",label:!1},{attr:"quantity",label:"Qty"},{view:"increment",label:!1},{attr:"total",label:"SubTotal",view:"currency"},{view:"remove",text:"Remove",label:!1}],excludeFromCheckout:["thumb"],shippingFlatRate:0,shippingQuantityRate:0,shippingTotalRate:0,shippingCustom:null,taxRate:0,taxShipping:!1,data:{}},b=function(a){if(h(a))return b.ready(a);if(k(a,"object"))return b.extend(m,a)},A,B;b.extend=function(a,d){var c;e(d)&&(d=a,a=b);for(c in d)Object.prototype.hasOwnProperty.call(d, 7 | c)&&(a[c]=d[c]);return a};b.extend({copy:function(a){a=C(a);a.init();return a}});b.extend({isReady:!1,add:function(a,d){var c=new b.Item(a||{}),g=!0,u=!0===d?d:!1;if(!u&&(g=b.trigger("beforeAdd",[c]),!1===g))return!1;(g=b.has(c))?(g.increment(c.quantity()),c=g):r[c.id()]=c;b.update();u||b.trigger("afterAdd",[c,e(g)]);return c},each:function(a,d){var c,g=0,u,e,w;if(h(a))e=a,w=r;else if(h(d))e=d,w=a;else return;for(c in w)if(Object.prototype.hasOwnProperty.call(w,c)){u=e.call(b,w[c],g,c);if(!1===u)break; 8 | g+=1}},find:function(a){var d=[];if(k(r[a],"object"))return r[a];if(k(a,"object"))return b.each(function(c){var g=!0;b.each(a,function(a,b,d){k(a,s)?a.match(/<=.*/)?(a=parseFloat(a.replace("<=","")),c.get(d)&&parseFloat(c.get(d))<=a||(g=!1)):a.match(/=/)?(a=parseFloat(a.replace(">=","")),c.get(d)&&parseFloat(c.get(d))>=a||(g=!1)):a.match(/>/)?(a=parseFloat(a.replace(">","")),c.get(d)&&parseFloat(c.get(d))>a|| 9 | (g=!1)):c.get(d)&&c.get(d)===a||(g=!1):c.get(d)&&c.get(d)===a||(g=!1);return g});g&&d.push(c)}),d;e(a)&&b.each(function(a){d.push(a)});return d},items:function(){return this.find()},has:function(a){var d=!1;b.each(function(b){b.equals(a)&&(d=b)});return d},empty:function(){var a={};b.each(function(b){!1===b.remove(!0)&&(a[b.id()]=b)});r=a;b.update()},quantity:function(){var a=0;b.each(function(b){a+=b.quantity()});return a},total:function(){var a=0;b.each(function(b){a+=b.total()});return a},grandTotal:function(){return b.total()+ 10 | b.tax()+b.shipping()},update:function(){b.save();b.trigger("update")},init:function(){b.load();b.update();b.ready()},$:function(a){return new b.ELEMENT(a)},$create:function(a){return b.$(f.createElement(a))},setupViewTool:function(){var a,d=p,c;for(c in t)if(Object.prototype.hasOwnProperty.call(t,c)&&p[c]&&(a=t[c].replace("*",c).split("."),(a=a.shift())&&(d=d[a]),"function"===typeof d)){A=d;b.extend(b.ELEMENT._,z[c]);break}},ids:function(){var a=[];b.each(function(b){a.push(b.id())});return a},save:function(){b.trigger("beforeSave"); 11 | var a={};b.each(function(d){a[d.id()]=b.extend(d.fields(),d.options())});v.setItem(x+"_items",JSON.stringify(a));b.trigger("afterSave")},load:function(){r={};var a=v.getItem(x+"_items");if(a){try{b.each(JSON.parse(a),function(a){b.add(a,!0)})}catch(d){b.error("Error Loading data: "+d)}b.trigger("load")}},ready:function(a){h(a)?b.isReady?a.call(b):b.bind("ready",a):e(a)&&!b.isReady&&(b.trigger("ready"),b.isReady=!0)},error:function(a){var d="";k(a,s)?d=a:k(a,"object")&&k(a.message,s)&&(d=a.message); 12 | try{l.log("simpleCart(js) Error: "+d)}catch(c){}b.trigger("error",a)}});b.extend({tax:function(){var a=m.taxShipping?b.total()+b.shipping():b.total(),d=b.taxRate()*a;b.each(function(a){a.get("tax")?d+=a.get("tax"):a.get("taxRate")&&(d+=a.get("taxRate")*a.total())});return parseFloat(d)},taxRate:function(){return m.taxRate||0},shipping:function(a){if(h(a))b({shippingCustom:a});else{var d=m.shippingQuantityRate*b.quantity()+m.shippingTotalRate*b.total()+m.shippingFlatRate;h(m.shippingCustom)&&(d+=m.shippingCustom.call(b)); 13 | b.each(function(a){d+=parseFloat(a.get("shipping")||0)});return parseFloat(d)}}});B={attr:function(a,b){return a.get(b.attr)||""},currency:function(a,d){return b.toCurrency(a.get(d.attr)||0)},link:function(a,b){return""+b.text+""},decrement:function(a,b){return""+(b.text||"-")+""},increment:function(a,b){return""+(b.text||"+")+""},image:function(a,b){return""},input:function(a,b){return""},remove:function(a,b){return""+(b.text||"X")+""}};b.extend({writeCart:function(a){var d=m.cartStyle.toLowerCase(),c="table"===d,g=c?"tr":"div",u=c?"th":"div",e=c?"td":"div",w=b.$create(d),d=b.$create(g).addClass("headerRow"),f,h;b.$(a).html(" ").append(w);w.append(d);c=0;for(h=m.cartColumns.length;cc.price&&(c.price=0);k(c.quantity,s)&&(c.quantity=parseInt(c.quantity.replace(b.currency().delimiter,""),10));isNaN(c.quantity)&&(c.quantity=1);0>=c.quantity&&g.remove()}var c={},g=this;k(a,"object")&&b.extend(c,a);n+=1;for(c.id= 17 | c.id||"SCI-"+n;!e(r[c.id]);)n+=1,c.id="SCI-"+n;g.get=function(a,b){var d=!b;return e(a)?a:h(c[a])?c[a].call(g):e(c[a])?h(g[a])&&d?g[a].call(g):!e(g[a])&&d?g[a]:c[a]:c[a]};g.set=function(a,b){e(a)||(c[a.toLowerCase()]=b,"price"!==a.toLowerCase()&&"quantity"!==a.toLowerCase()||d());return g};g.equals=function(a){for(var b in c)if(Object.prototype.hasOwnProperty.call(c,b)&&"quantity"!==b&&"id"!==b&&a.get(b)!==c[b])return!1;return!0};g.options=function(){var a={};b.each(c,function(d,c,e){var f=!0;b.each(g.reservedFields(), 18 | function(a){a===e&&(f=!1);return f});f&&(a[e]=g.get(e))});return a};d()};b.Item._=b.Item.prototype={increment:function(a){a=parseInt(a||1,10);this.quantity(this.quantity()+a);return 1>this.quantity()?(this.remove(),null):this},decrement:function(a){return this.increment(-parseInt(a||1,10))},remove:function(a){if(!1===b.trigger("beforeRemove",[r[this.id()]]))return!1;delete r[this.id()];a||b.update();return null},reservedFields:function(){return"quantity id item_number price name shipping tax taxRate".split(" ")}, 19 | fields:function(){var a={},d=this;b.each(d.reservedFields(),function(b){d.get(b)&&(a[b]=d.get(b))});return a},quantity:function(a){return e(a)?parseInt(this.get("quantity",!0)||1,10):this.set("quantity",a)},price:function(a){return e(a)?parseFloat(this.get("price",!0).toString().replace(b.currency().symbol,"").replace(b.currency().delimiter,"")||1):this.set("price",parseFloat(a.toString().replace(b.currency().symbol,"").replace(b.currency().delimiter,"")))},id:function(){return this.get("id",!1)}, 20 | total:function(){return this.quantity()*this.price()}};b.extend({checkout:function(){if("custom"===m.checkout.type.toLowerCase()&&h(m.checkout.fn))m.checkout.fn.call(b,m.checkout);else if(h(b.checkout[m.checkout.type])){var a=b.checkout[m.checkout.type].call(b,m.checkout);a.data&&a.action&&a.method&&!1!==b.trigger("beforeCheckout",[a.data])&&b.generateAndSendForm(a)}else b.error("No Valid Checkout Method Specified")},extendCheckout:function(a){return b.extend(b.checkout,a)},generateAndSendForm:function(a){var d= 21 | b.$create("form");d.attr("style","display:none;");d.attr("action",a.action);d.attr("method",a.method);b.each(a.data,function(a,g,e){d.append(b.$create("input").attr("type","hidden").attr("name",e).val(a))});b.$("body").append(d);d.el.submit();d.remove()}});b.extendCheckout({PayPal:function(a){if(!a.email)return b.error("No email provided for PayPal checkout");var d={cmd:"_cart",upload:"1",currency_code:b.currency().code,business:a.email,rm:"GET"===a.method?"0":"2",tax_cart:(1*b.tax()).toFixed(2), 22 | handling_cart:(1*b.shipping()).toFixed(2),charset:"utf-8"},c=a.sandbox?"https://www.sandbox.paypal.com/cgi-bin/webscr":"https://www.paypal.com/cgi-bin/webscr",g="GET"===a.method?"GET":"POST";a.success&&(d["return"]=a.success);a.cancel&&(d.cancel_return=a.cancel);a.notify&&(d.notify_url=a.notify);b.each(function(a,c){var g=c+1,e=a.options(),f=0,h;d["item_name_"+g]=a.get("name");d["quantity_"+g]=a.quantity();d["amount_"+g]=(1*a.price()).toFixed(2);d["item_number_"+g]=a.get("item_number")||g;b.each(e, 23 | function(a,c,e){10>c&&(h=!0,b.each(m.excludeFromCheckout,function(a){a===e&&(h=!1)}),h&&(f+=1,d["on"+c+"_"+g]=e,d["os"+c+"_"+g]=a))});d["option_index_"+c]=Math.min(10,f)});return{action:c,method:g,data:d}},GoogleCheckout:function(a){if(!a.merchantID)return b.error("No merchant id provided for GoogleCheckout");if("USD"!==b.currency().code&&"GBP"!==b.currency().code)return b.error("Google Checkout only accepts USD and GBP");var d={ship_method_name_1:"Shipping",ship_method_price_1:b.shipping(),ship_method_currency_1:b.currency().code, 24 | _charset_:""},c="https://checkout.google.com/api/checkout/v2/checkoutForm/Merchant/"+a.merchantID;a="GET"===a.method?"GET":"POST";b.each(function(a,c){var e=c+1,f=[],h;d["item_name_"+e]=a.get("name");d["item_quantity_"+e]=a.quantity();d["item_price_"+e]=a.price();d["item_currency_ "+e]=b.currency().code;d["item_tax_rate"+e]=a.get("taxRate")||b.taxRate();b.each(a.options(),function(a,d,c){h=!0;b.each(m.excludeFromCheckout,function(a){a===c&&(h=!1)});h&&f.push(c+": "+a)});d["item_description_"+e]=f.join(", ")}); 25 | return{action:c,method:a,data:d}},AmazonPayments:function(a){if(!a.merchant_signature)return b.error("No merchant signature provided for Amazon Payments");if(!a.merchant_id)return b.error("No merchant id provided for Amazon Payments");if(!a.aws_access_key_id)return b.error("No AWS access key id provided for Amazon Payments");var d={aws_access_key_id:a.aws_access_key_id,merchant_signature:a.merchant_signature,currency_code:b.currency().code,tax_rate:b.taxRate(),weight_unit:a.weight_unit||"lb"},c="https://payments"+ 26 | (a.sandbox?"-sandbox":"")+".amazon.com/checkout/"+a.merchant_id,g="GET"===a.method?"GET":"POST";b.each(function(c,g){var e=g+1,f=[];d["item_title_"+e]=c.get("name");d["item_quantity_"+e]=c.quantity();d["item_price_"+e]=c.price();d["item_sku_ "+e]=c.get("sku")||c.id();d["item_merchant_id_"+e]=a.merchant_id;c.get("weight")&&(d["item_weight_"+e]=c.get("weight"));m.shippingQuantityRate&&(d["shipping_method_price_per_unit_rate_"+e]=m.shippingQuantityRate);b.each(c.options(),function(a,d,c){var g=!0;b.each(m.excludeFromCheckout, 27 | function(a){a===c&&(g=!1)});g&&"weight"!==c&&"tax"!==c&&f.push(c+": "+a)});d["item_description_"+e]=f.join(", ")});return{action:c,method:g,data:d}},SendForm:function(a){if(!a.url)return b.error("URL required for SendForm Checkout");var d={currency:b.currency().code,shipping:b.shipping(),tax:b.tax(),taxRate:b.taxRate(),itemCount:b.find({}).length},c=a.url,g="GET"===a.method?"GET":"POST";b.each(function(a,c){var g=c+1,e=[],f;d["item_name_"+g]=a.get("name");d["item_quantity_"+g]=a.quantity();d["item_price_"+ 28 | g]=a.price();b.each(a.options(),function(a,d,c){f=!0;b.each(m.excludeFromCheckout,function(a){a===c&&(f=!1)});f&&e.push(c+": "+a)});d["item_options_"+g]=e.join(", ")});a.success&&(d["return"]=a.success);a.cancel&&(d.cancel_return=a.cancel);a.extra_data&&(d=b.extend(d,a.extra_data));return{action:c,method:g,data:d}}});q={bind:function(a,d){if(!h(d))return this;this._events||(this._events={});var c=a.split(/ +/);b.each(c,function(a){!0===this._events[a]?d.apply(this):e(this._events[a])?this._events[a]= 29 | [d]:this._events[a].push(d)});return this},trigger:function(a,b){var c=!0,g,f;this._events||(this._events={});if(!e(this._events[a])&&h(this._events[a][0]))for(g=0,f=this._events[a].length;ge?"0"+e:e}function f(f){e.lastIndex=0;return e.test(f)?'"'+f.replace(e,function(e){var f=C[e];return"string"===typeof f?f:"\\u"+("0000"+e.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+f+'"'}function s(e,k){var t,n,r,p,z=h,v,l=k[e];l&&"object"===typeof l&&"function"===typeof l.toJSON&&(l=l.toJSON(e));"function"===typeof q&&(l=q.call(k,e,l));switch(typeof l){case "string":return f(l);case "number":return isFinite(l)?String(l):"null";case "boolean":case "null":return String(l); 46 | case "object":if(!l)return"null";h+=y;v=[];if("[object Array]"===Object.prototype.toString.apply(l)){p=l.length;for(t=0;t