├── .gitattributes ├── .gitignore ├── background.js ├── blank.html ├── fonts ├── YanoneKaffeesatz-Regular-webfont.eot ├── YanoneKaffeesatz-Regular-webfont.svg ├── YanoneKaffeesatz-Regular-webfont.ttf ├── YanoneKaffeesatz-Regular-webfont.woff ├── satellite-webfont.eot ├── satellite-webfont.svg ├── satellite-webfont.ttf └── satellite-webfont.woff ├── images ├── background.png ├── hero-images │ ├── 001.png │ ├── 002.png │ ├── 003.png │ ├── 004.png │ ├── 005.png │ ├── 006.png │ ├── 007.png │ ├── 008.png │ ├── 009.png │ ├── 010.png │ ├── 011.png │ └── 012.png ├── icon_128.png ├── icon_16.png ├── icon_48.png ├── ioCinemaIcon.png ├── loading.gif ├── printTicketsIcon.png ├── purchaseTicketsIcon.png └── screenshots │ ├── 1280x800.png │ └── 440x280.png ├── manifest.json ├── scripts ├── data.js ├── data.json ├── lib │ ├── StackBlur.js │ ├── jquery-2.1.1.min.js │ ├── sandbox-scripts.js │ ├── sandbox-stripe.js │ ├── sandbox.js │ ├── slider.js │ ├── spreadsheet.gs │ ├── spreadsheet.js │ ├── stillthere.js │ ├── stripe.js │ └── swiper.js ├── main.js ├── objects │ ├── movie.js │ ├── pricing.js │ ├── receipt.js │ ├── session.js │ ├── showing.js │ ├── theater.js │ ├── ticket.js │ └── tickettype.js ├── rotten.js ├── sandbox-rotten.js └── sandbox-scripts.html ├── styles ├── main.css ├── print.css ├── slider.css └── stillthere.css └── window.html /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | 32 | # Thumbnails 33 | ._* 34 | 35 | # Files that might appear on external disk 36 | .Spotlight-V100 37 | .Trashes 38 | /resources/ 39 | /nbproject/ -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | (function(window, chrome) { 2 | //Background Scope 3 | var background = {}; 4 | window.background = background; 5 | 6 | //Create window and add listeners 7 | chrome.app.runtime.onLaunched.addListener(function() { 8 | chrome.app.window.create('window.html', { 9 | 'state': 'fullscreen', 10 | 'bounds': { 11 | 'width': 1280, 12 | 'height': 1024 13 | } 14 | }, function(window) { 15 | window.onClosed.addListener(function() { 16 | background.setKeyboard(false); 17 | chrome.power.releaseKeepAwake(); 18 | }); 19 | background.setKeyboard(true); 20 | chrome.power.requestKeepAwake('display'); 21 | }); 22 | }); 23 | 24 | /** 25 | * Enables or disables the on-screen keyboard. 26 | *

27 | * If enable is true, the virtual keyboard of the ChromeOS 28 | * accessibility features will be enabled. If false, this function will 29 | * return the setting to its original value. 30 | * @param {boolean} enable true to enable, or false to reset 31 | * @returns {undefined} 32 | */ 33 | background.setKeyboard = function(enable) { 34 | if (chrome.accessibilityFeatures) { 35 | if (enable) { 36 | chrome.accessibilityFeatures.virtualKeyboard.set({ 37 | value: enable 38 | }); 39 | } 40 | else { 41 | chrome.accessibilityFeatures.virtualKeyboard.clear({}); 42 | } 43 | } 44 | }; 45 | })(window, chrome); -------------------------------------------------------------------------------- /blank.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

10 | 11 | 12 | -------------------------------------------------------------------------------- /fonts/YanoneKaffeesatz-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/fonts/YanoneKaffeesatz-Regular-webfont.eot -------------------------------------------------------------------------------- /fonts/YanoneKaffeesatz-Regular-webfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG webfont generated by Font Squirrel. 6 | Copyright : Copyright c 2010 Yanone httpyanonedetypedesign All rights reservedThis Font Software is licensed under the SIL Open Font License Version 11This license is available with a FAQ at httpscriptssilorgOFL 7 | Designer : Yanone 8 | Foundry : Yanone 9 | Foundry URL : httpyanonedetypedesign 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /fonts/YanoneKaffeesatz-Regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/fonts/YanoneKaffeesatz-Regular-webfont.ttf -------------------------------------------------------------------------------- /fonts/YanoneKaffeesatz-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/fonts/YanoneKaffeesatz-Regular-webfont.woff -------------------------------------------------------------------------------- /fonts/satellite-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/fonts/satellite-webfont.eot -------------------------------------------------------------------------------- /fonts/satellite-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/fonts/satellite-webfont.ttf -------------------------------------------------------------------------------- /fonts/satellite-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/fonts/satellite-webfont.woff -------------------------------------------------------------------------------- /images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/background.png -------------------------------------------------------------------------------- /images/hero-images/001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/hero-images/001.png -------------------------------------------------------------------------------- /images/hero-images/002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/hero-images/002.png -------------------------------------------------------------------------------- /images/hero-images/003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/hero-images/003.png -------------------------------------------------------------------------------- /images/hero-images/004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/hero-images/004.png -------------------------------------------------------------------------------- /images/hero-images/005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/hero-images/005.png -------------------------------------------------------------------------------- /images/hero-images/006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/hero-images/006.png -------------------------------------------------------------------------------- /images/hero-images/007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/hero-images/007.png -------------------------------------------------------------------------------- /images/hero-images/008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/hero-images/008.png -------------------------------------------------------------------------------- /images/hero-images/009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/hero-images/009.png -------------------------------------------------------------------------------- /images/hero-images/010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/hero-images/010.png -------------------------------------------------------------------------------- /images/hero-images/011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/hero-images/011.png -------------------------------------------------------------------------------- /images/hero-images/012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/hero-images/012.png -------------------------------------------------------------------------------- /images/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/icon_128.png -------------------------------------------------------------------------------- /images/icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/icon_16.png -------------------------------------------------------------------------------- /images/icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/icon_48.png -------------------------------------------------------------------------------- /images/ioCinemaIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/ioCinemaIcon.png -------------------------------------------------------------------------------- /images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/loading.gif -------------------------------------------------------------------------------- /images/printTicketsIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/printTicketsIcon.png -------------------------------------------------------------------------------- /images/purchaseTicketsIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/purchaseTicketsIcon.png -------------------------------------------------------------------------------- /images/screenshots/1280x800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/screenshots/1280x800.png -------------------------------------------------------------------------------- /images/screenshots/440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KioskApps/QuickTicket/44a36f7e6fe58b12bb83cb1db2b557ea359e4fe9/images/screenshots/440x280.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "background": { 4 | "scripts": ["background.js"] 5 | } 6 | }, 7 | "manifest_version": 2, 8 | "name": "QuickTicket", 9 | "version": "1.5.1", 10 | 11 | "description": "Movie theater kiosk app for Chrome", 12 | "icons": { 13 | "16": "images/icon_16.png", 14 | "48": "images/icon_48.png", 15 | "128": "images/icon_128.png" 16 | }, 17 | 18 | "author": "ChromeKioskApps@gmail.com", 19 | "kiosk_enabled": true, 20 | "minimum_chrome_version": "37", 21 | "offline_enabled": true, 22 | "permissions": [ 23 | "power", 24 | "accessibilityFeatures.read", 25 | "accessibilityFeatures.modify", 26 | "" 27 | ], 28 | "sandbox": { 29 | "pages": [ 30 | "scripts/sandbox-scripts.html" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scripts/data.js: -------------------------------------------------------------------------------- 1 | //Data Scope 2 | var data = {}; 3 | 4 | //Static Global Data Variables 5 | /** 6 | * Name of the theater 7 | * @type String 8 | */ 9 | data.CINEMA_NAME = 'I/0 Cinema 10'; 10 | /** 11 | * Abbreviated month names 12 | * @type Array. 13 | */ 14 | data.MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; 15 | /** 16 | * Spreadsheet to save receipt information to 17 | * @type String 18 | */ 19 | data.SPREADSHEET_ID = '1PSIXV-k_r0r53dVFIIqO72ZCCSXWNxa7-Ijl4Yjm0Fc'; 20 | 21 | /** 22 | * Contains all of the TicketType objects 23 | * @type Array. 24 | */ 25 | data.ticketTypes = []; 26 | /** 27 | * Contains all of the Pricing objects 28 | * @type Array. 29 | */ 30 | data.pricings = []; 31 | /** 32 | * Contains all of the Theater objects 33 | * @type Array. 34 | */ 35 | data.theaters = []; 36 | /** 37 | * Contains all of the Movie objects 38 | * @type Array. 39 | */ 40 | data.movies = []; 41 | /** 42 | * Contains all of the Showing objects 43 | * @type Array. 44 | */ 45 | data.showings = []; 46 | 47 | /** 48 | * Initializes all data and calls the provided callback function upon 49 | * completion. 50 | * @param {function} callback called upon completion of data initialization 51 | * @returns {undefined} 52 | */ 53 | data.initializeData = function(callback) { 54 | /* 55 | * In a real case scenario, all of this data would be pulled in from the 56 | * theater's internal web services and API. For this demonstration, we 57 | * are pulling in data from three sources: a JSON file, a sandboxed 58 | * API (Rotten Tomatoes), and dynamically generated content. 59 | */ 60 | $.getJSON('scripts/data.json', data.initializeDataJson); 61 | rotten.getInTheatersData(function() { 62 | data.getShowtimes(callback); 63 | }); 64 | spreadsheet.defaultSpreadsheetId = data.SPREADSHEET_ID; 65 | }; 66 | 67 | /** 68 | * Retrieves the showtime information for all the Movie objects. 69 | * @param {function} callback called upon completion of the Showtime object 70 | * initialization 71 | * @returns {undefined} 72 | */ 73 | data.getShowtimes = function(callback) { 74 | //We are dynamically generating showtimes, but this is where you would 75 | //call a showtime API service such as Fandango to pull in showtime data 76 | //for each movie 77 | data.showings = []; 78 | var now = new Date(); 79 | var date = data.MONTHS[now.getMonth()] + ' ' + now.getDate(); 80 | 81 | var times = [ 82 | '10:00am', 83 | '11:25am', 84 | '12:00pm', 85 | '12:30pm', 86 | '1:30pm', 87 | '2:00pm', 88 | '4:45pm', 89 | '6:20pm' 90 | ]; 91 | var numShowings = 5; 92 | 93 | for (var i = 0; i < data.movies.length; i++) { 94 | var timeIndex = Math.floor((Math.random() * 3)); 95 | 96 | for (var j = 0; j < numShowings; j++) { 97 | if (timeIndex < times.length) { 98 | var time = times[timeIndex]; 99 | timeIndex += 2; 100 | 101 | var id = Math.floor((Math.random() * 9999) + 1000); 102 | var movie = data.movies[i]; 103 | var theater = data.theaters[Math.floor(Math.random() * data.theaters.length)]; 104 | var showing = new Showing(id, movie, date, time, theater); 105 | data.showings.push(showing); 106 | } 107 | } 108 | } 109 | callback(); 110 | }; 111 | /** 112 | * Initialize data from a JSON file. 113 | * @param {Object} json the JSON file to initialize data from 114 | * @returns {undefined} 115 | */ 116 | data.initializeDataJson = function(json) { 117 | //For ticket information, pricings, and theaters, you would pull this from 118 | //the theater's in house API. For demo purposes, we defined this data in a 119 | //JSON file 120 | for (var i = 0; i < json.ticketTypes.length; i++) { 121 | var tt = json.ticketTypes[i]; 122 | data.ticketTypes.push(new TicketType(tt.id, tt.name)); 123 | } 124 | for (var i = 0; i < json.pricings.length; i++) { 125 | var p = json.pricings[i]; 126 | var pricing = new Pricing(p.id, p.name); 127 | for (var j = 0; j < p.tickets.length; j++) { 128 | pricing.tickets.push(new Ticket(data.getTicketTypeByID(p.tickets[j].id), p.tickets[j].price)); 129 | } 130 | data.pricings.push(pricing); 131 | } 132 | for (var i = 0; i < json.theaters.length; i++) { 133 | var t = json.theaters[i]; 134 | data.theaters.push(new Theater(t.id, t.name, data.getPricingByName(t.pricing))); 135 | } 136 | return; 137 | }; 138 | 139 | //Helper Functions 140 | /** 141 | * Retrieves a TicketType object by its ID. 142 | * @param {string|number} id the ID of the TicketType to retrieve 143 | * @returns {TicketType} the TicketType, or undefined 144 | */ 145 | data.getTicketTypeByID = function(id) { 146 | return data.getObjectByProperty(data.ticketTypes, 'id', id); 147 | }; 148 | /** 149 | * Retrieves a Pricing object by its name. 150 | * @param {string} name the name of the Pricing object to retrieve 151 | * @returns {Pricing} the Pricing object, or undefined 152 | */ 153 | data.getPricingByName = function(name) { 154 | return data.getObjectByProperty(data.pricings, 'name', name); 155 | }; 156 | /** 157 | * Retrieves a Movie object by its ID. 158 | * @param {string|number} id the ID of the Movie to retrieve 159 | * @returns {Movie} the Movie, or undefined 160 | */ 161 | data.getMovieByID = function(id) { 162 | return data.getObjectByProperty(data.movies, 'id', id); 163 | }; 164 | /** 165 | * Retrieves a Movie object by its title. 166 | * @param {string} name the title of the Movie to retrieve 167 | * @returns {Movie} the Movie, or undefined 168 | */ 169 | data.getMovieByTitle = function(name) { 170 | return data.getObjectByProperty(data.movies, 'title', name); 171 | }; 172 | /** 173 | * Retrieves a Theater object by its ID. 174 | * @param {string|number} id the ID of the Theater to retrieve 175 | * @returns {Theater} the Theater, or undefined 176 | */ 177 | data.getTheaterByID = function(id) { 178 | return data.getObjectByProperty(data.theaters, 'id', id); 179 | }; 180 | /** 181 | * Retrieves a Showing object by its ID. 182 | * @param {string|number} id the ID of the Showing to retrieve 183 | * @returns {Showing} the Showing, or undefined 184 | */ 185 | data.getShowingByID = function(id) { 186 | return data.getObjectByProperty(data.showings, 'id', id); 187 | }; 188 | /** 189 | * Retrieves an array of Showing object associated with a give Movie. 190 | * @param {Movie} movie the Movie to retrieve Showings for 191 | * @returns {Array.} Array of Showing objects for the provided Movie 192 | */ 193 | data.getShowingsByMovie = function(movie) { 194 | return data.getArrayByProperty(data.showings, 'movie', movie); 195 | }; 196 | /** 197 | * Retrieves an Object from the provided Array whose property matches the 198 | * given value, or undefined in no Object could be found in the Array. 199 | * @param {Array} array the Array to search 200 | * @param {string} property the property name to look for 201 | * @param {Object} value the property value to match 202 | * @returns {Object} the Object found, or undefined 203 | */ 204 | data.getObjectByProperty = function(array, property, value) { 205 | var results = data.getArrayByProperty(array, property, value); 206 | if (results.length >= 1) { 207 | return results[0]; 208 | } 209 | }; 210 | /** 211 | * Retrieves an Array of Objects from the provided Array whose property 212 | * matches the given value. 213 | *

214 | * The Array will be empty if no Objects were found. 215 | * @param {Array} array the Array to search 216 | * @param {string} property the property name to look for 217 | * @param {Object} value the property value to match 218 | * @returns {Array} an Array of Objects found 219 | */ 220 | data.getArrayByProperty = function(array, property, value) { 221 | var results = []; 222 | for (var i = 0; i < array.length; i++) { 223 | //Loosely compare the value 224 | if (array[i][property] == value) { 225 | results.push(array[i]); 226 | } 227 | } 228 | return results; 229 | }; 230 | 231 | /** 232 | * Saves the given Receipt object to a spreadsheet. 233 | * @param {Receipt|object} receipt the Receipt or receipt storage object to save 234 | * @returns {undefined} 235 | */ 236 | data.saveReceipt = function(receipt) { 237 | if (receipt && receipt.createStorageObject) { 238 | receipt = receipt.createStorageObject(); 239 | } 240 | var row = [ 241 | JSON.stringify(receipt) 242 | ]; 243 | spreadsheet.appendRow(row, function(message, status) { 244 | console.log('Receipt Save Result: (' + status + ') ' + message); 245 | }); 246 | }; 247 | /** 248 | * Retrieves a Receipt object from the spreadsheet by its ID. 249 | * @param {string|number} id the ID of the Receipt to retrieve 250 | * @param {function(Receipt)} callback called upon successfully locating the 251 | * Receipt, the argument may be undefined if a Receipt is not found 252 | * @returns {undefined} 253 | */ 254 | data.getReceiptById = function(id, callback) { 255 | data.getReceiptByProperty('id', id, callback); 256 | }; 257 | /** 258 | * Retrieves a Receipt object from the spreadsheet by the Card object used 259 | * to create the Receipt. 260 | * @param {Card} card the Card used with the Receipt to retrieve 261 | * @param {function(Receipt)} callback called upon successfully locating the 262 | * Receipt, the argument may be undefined if a Receipt is not found 263 | * @returns {undefined} 264 | */ 265 | data.getReceiptByCard = function(card, callback) { 266 | var hash = swiper.generateCardHash(card); 267 | data.getReceiptByProperty('cardHash', hash, callback); 268 | }; 269 | /** 270 | * Retrieves a Receipt from the spreadsheet whose property matches the 271 | * provided value, or undefined if a Receipt could not be found. 272 | * @param {string} property the property name to look for 273 | * @param {Object} value the property value to match 274 | * @param {function(Receipt)} callback called upon successfully locating the 275 | * Receipt, the argument may be undefined if a Receipt is not found 276 | * @returns {undefined} 277 | */ 278 | data.getReceiptByProperty = function(property, value, callback) { 279 | spreadsheet.getAllRows(function(rows) { 280 | rows.reverse(); 281 | for (var i = 0; i < rows.length; i++) { 282 | var receipt = JSON.parse(rows[i]); 283 | //Loosely compare the value 284 | if (receipt[property] == value) { 285 | callback(receipt); 286 | return; 287 | } 288 | } 289 | callback(); 290 | }); 291 | }; -------------------------------------------------------------------------------- /scripts/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "ticketTypes": [ 3 | { 4 | "id": 1, 5 | "name": "Adult" 6 | }, 7 | { 8 | "id": 2, 9 | "name": "Child" 10 | }, 11 | { 12 | "id": 3, 13 | "name": "Senior" 14 | }, 15 | { 16 | "id": 4, 17 | "name": "Student" 18 | } 19 | ], 20 | "pricings": [ 21 | { 22 | "id": 1, 23 | "name": "Standard", 24 | "tickets": [ 25 | { 26 | "id": 1, "price": 10.00 27 | }, 28 | { 29 | "id": 2, "price": 7.00 30 | }, 31 | { 32 | "id": 4, "price": 8.00 33 | }, 34 | { 35 | "id": 3, "price": 6.00 36 | } 37 | ] 38 | }, 39 | { 40 | "id": 2, 41 | "name": "3D", 42 | "tickets": [ 43 | { 44 | "id": 1, "price": 12.00 45 | }, 46 | { 47 | "id": 2, "price": 9.00 48 | }, 49 | { 50 | "id": 4, "price": 10.00 51 | }, 52 | { 53 | "id": 3, "price": 8.00 54 | } 55 | ] 56 | }, 57 | { 58 | "id": 3, 59 | "name": "IMAX", 60 | "tickets": [ 61 | { 62 | "id": 1, "price": 15.00 63 | }, 64 | { 65 | "id": 2, "price": 12.00 66 | }, 67 | { 68 | "id": 4, "price": 13.00 69 | }, 70 | { 71 | "id": 3, "price": 11.00 72 | } 73 | ] 74 | }, 75 | { 76 | "id": 4, 77 | "name": "IMAX 3D", 78 | "tickets": [ 79 | { 80 | "id": 1, "price": 17.00 81 | }, 82 | { 83 | "id": 2, "price": 14.00 84 | }, 85 | { 86 | "id": 4, "price": 15.00 87 | }, 88 | { 89 | "id": 3, "price": 13.00 90 | } 91 | ] 92 | } 93 | ], 94 | "theaters": [ 95 | { 96 | "id": 1, 97 | "name": "Theater 1", 98 | "pricing": "Standard" 99 | }, 100 | { 101 | "id": 2, 102 | "name": "Theater 2", 103 | "pricing": "3D" 104 | }, 105 | { 106 | "id": 3, 107 | "name": "Theater 3", 108 | "pricing": "IMAX" 109 | }, 110 | { 111 | "id": 4, 112 | "name": "Theater 4", 113 | "pricing": "IMAX 3D" 114 | } 115 | ] 116 | } -------------------------------------------------------------------------------- /scripts/lib/StackBlur.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | StackBlur - a fast almost Gaussian Blur For Canvas 4 | 5 | Version: 0.5 6 | Author: Mario Klingemann 7 | Contact: mario@quasimondo.com 8 | Website: http://www.quasimondo.com/StackBlurForCanvas 9 | Twitter: @quasimondo 10 | 11 | In case you find this class useful - especially in commercial projects - 12 | I am not totally unhappy for a small donation to my PayPal account 13 | mario@quasimondo.de 14 | 15 | Or support me on flattr: 16 | https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript 17 | 18 | Copyright (c) 2010 Mario Klingemann 19 | 20 | Permission is hereby granted, free of charge, to any person 21 | obtaining a copy of this software and associated documentation 22 | files (the "Software"), to deal in the Software without 23 | restriction, including without limitation the rights to use, 24 | copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the 26 | Software is furnished to do so, subject to the following 27 | conditions: 28 | 29 | The above copyright notice and this permission notice shall be 30 | included in all copies or substantial portions of the Software. 31 | 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 33 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 34 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 35 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 36 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 37 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 38 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 39 | OTHER DEALINGS IN THE SOFTWARE. 40 | */ 41 | 42 | var mul_table = [ 43 | 512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512, 44 | 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512, 45 | 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456, 46 | 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512, 47 | 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328, 48 | 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456, 49 | 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335, 50 | 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512, 51 | 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405, 52 | 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328, 53 | 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271, 54 | 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456, 55 | 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388, 56 | 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335, 57 | 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292, 58 | 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259]; 59 | 60 | 61 | var shg_table = [ 62 | 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 63 | 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 64 | 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 65 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 66 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 67 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 68 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 69 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 70 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 71 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 72 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 73 | 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 74 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 75 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 76 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 77 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 ]; 78 | 79 | function stackBlurImage( imageID, canvasID, radius, blurAlphaChannel ) 80 | { 81 | 82 | var img = document.getElementById( imageID ); 83 | var w = img.naturalWidth; 84 | var h = img.naturalHeight; 85 | 86 | var canvas = document.getElementById( canvasID ); 87 | 88 | canvas.style.width = w + "px"; 89 | canvas.style.height = h + "px"; 90 | canvas.width = w; 91 | canvas.height = h; 92 | 93 | var context = canvas.getContext("2d"); 94 | context.clearRect( 0, 0, w, h ); 95 | context.drawImage( img, 0, 0 ); 96 | 97 | if ( isNaN(radius) || radius < 1 ) return; 98 | 99 | if ( blurAlphaChannel ) 100 | stackBlurCanvasRGBA( canvasID, 0, 0, w, h, radius ); 101 | else 102 | stackBlurCanvasRGB( canvasID, 0, 0, w, h, radius ); 103 | } 104 | 105 | 106 | function stackBlurCanvasRGBA( id, top_x, top_y, width, height, radius ) 107 | { 108 | if ( isNaN(radius) || radius < 1 ) return; 109 | radius |= 0; 110 | 111 | var canvas = document.getElementById( id ); 112 | var context = canvas.getContext("2d"); 113 | var imageData; 114 | 115 | try { 116 | try { 117 | imageData = context.getImageData( top_x, top_y, width, height ); 118 | } catch(e) { 119 | 120 | // NOTE: this part is supposedly only needed if you want to work with local files 121 | // so it might be okay to remove the whole try/catch block and just use 122 | // imageData = context.getImageData( top_x, top_y, width, height ); 123 | try { 124 | netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); 125 | imageData = context.getImageData( top_x, top_y, width, height ); 126 | } catch(e) { 127 | alert("Cannot access local image"); 128 | throw new Error("unable to access local image data: " + e); 129 | return; 130 | } 131 | } 132 | } catch(e) { 133 | alert("Cannot access image"); 134 | throw new Error("unable to access image data: " + e); 135 | } 136 | 137 | var pixels = imageData.data; 138 | 139 | var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum, 140 | r_out_sum, g_out_sum, b_out_sum, a_out_sum, 141 | r_in_sum, g_in_sum, b_in_sum, a_in_sum, 142 | pr, pg, pb, pa, rbs; 143 | 144 | var div = radius + radius + 1; 145 | var w4 = width << 2; 146 | var widthMinus1 = width - 1; 147 | var heightMinus1 = height - 1; 148 | var radiusPlus1 = radius + 1; 149 | var sumFactor = radiusPlus1 * ( radiusPlus1 + 1 ) / 2; 150 | 151 | var stackStart = new BlurStack(); 152 | var stack = stackStart; 153 | for ( i = 1; i < div; i++ ) 154 | { 155 | stack = stack.next = new BlurStack(); 156 | if ( i == radiusPlus1 ) var stackEnd = stack; 157 | } 158 | stack.next = stackStart; 159 | var stackIn = null; 160 | var stackOut = null; 161 | 162 | yw = yi = 0; 163 | 164 | var mul_sum = mul_table[radius]; 165 | var shg_sum = shg_table[radius]; 166 | 167 | for ( y = 0; y < height; y++ ) 168 | { 169 | r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0; 170 | 171 | r_out_sum = radiusPlus1 * ( pr = pixels[yi] ); 172 | g_out_sum = radiusPlus1 * ( pg = pixels[yi+1] ); 173 | b_out_sum = radiusPlus1 * ( pb = pixels[yi+2] ); 174 | a_out_sum = radiusPlus1 * ( pa = pixels[yi+3] ); 175 | 176 | r_sum += sumFactor * pr; 177 | g_sum += sumFactor * pg; 178 | b_sum += sumFactor * pb; 179 | a_sum += sumFactor * pa; 180 | 181 | stack = stackStart; 182 | 183 | for( i = 0; i < radiusPlus1; i++ ) 184 | { 185 | stack.r = pr; 186 | stack.g = pg; 187 | stack.b = pb; 188 | stack.a = pa; 189 | stack = stack.next; 190 | } 191 | 192 | for( i = 1; i < radiusPlus1; i++ ) 193 | { 194 | p = yi + (( widthMinus1 < i ? widthMinus1 : i ) << 2 ); 195 | r_sum += ( stack.r = ( pr = pixels[p])) * ( rbs = radiusPlus1 - i ); 196 | g_sum += ( stack.g = ( pg = pixels[p+1])) * rbs; 197 | b_sum += ( stack.b = ( pb = pixels[p+2])) * rbs; 198 | a_sum += ( stack.a = ( pa = pixels[p+3])) * rbs; 199 | 200 | r_in_sum += pr; 201 | g_in_sum += pg; 202 | b_in_sum += pb; 203 | a_in_sum += pa; 204 | 205 | stack = stack.next; 206 | } 207 | 208 | 209 | stackIn = stackStart; 210 | stackOut = stackEnd; 211 | for ( x = 0; x < width; x++ ) 212 | { 213 | pixels[yi+3] = pa = (a_sum * mul_sum) >> shg_sum; 214 | if ( pa != 0 ) 215 | { 216 | pa = 255 / pa; 217 | pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa; 218 | pixels[yi+1] = ((g_sum * mul_sum) >> shg_sum) * pa; 219 | pixels[yi+2] = ((b_sum * mul_sum) >> shg_sum) * pa; 220 | } else { 221 | pixels[yi] = pixels[yi+1] = pixels[yi+2] = 0; 222 | } 223 | 224 | r_sum -= r_out_sum; 225 | g_sum -= g_out_sum; 226 | b_sum -= b_out_sum; 227 | a_sum -= a_out_sum; 228 | 229 | r_out_sum -= stackIn.r; 230 | g_out_sum -= stackIn.g; 231 | b_out_sum -= stackIn.b; 232 | a_out_sum -= stackIn.a; 233 | 234 | p = ( yw + ( ( p = x + radius + 1 ) < widthMinus1 ? p : widthMinus1 ) ) << 2; 235 | 236 | r_in_sum += ( stackIn.r = pixels[p]); 237 | g_in_sum += ( stackIn.g = pixels[p+1]); 238 | b_in_sum += ( stackIn.b = pixels[p+2]); 239 | a_in_sum += ( stackIn.a = pixels[p+3]); 240 | 241 | r_sum += r_in_sum; 242 | g_sum += g_in_sum; 243 | b_sum += b_in_sum; 244 | a_sum += a_in_sum; 245 | 246 | stackIn = stackIn.next; 247 | 248 | r_out_sum += ( pr = stackOut.r ); 249 | g_out_sum += ( pg = stackOut.g ); 250 | b_out_sum += ( pb = stackOut.b ); 251 | a_out_sum += ( pa = stackOut.a ); 252 | 253 | r_in_sum -= pr; 254 | g_in_sum -= pg; 255 | b_in_sum -= pb; 256 | a_in_sum -= pa; 257 | 258 | stackOut = stackOut.next; 259 | 260 | yi += 4; 261 | } 262 | yw += width; 263 | } 264 | 265 | 266 | for ( x = 0; x < width; x++ ) 267 | { 268 | g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0; 269 | 270 | yi = x << 2; 271 | r_out_sum = radiusPlus1 * ( pr = pixels[yi]); 272 | g_out_sum = radiusPlus1 * ( pg = pixels[yi+1]); 273 | b_out_sum = radiusPlus1 * ( pb = pixels[yi+2]); 274 | a_out_sum = radiusPlus1 * ( pa = pixels[yi+3]); 275 | 276 | r_sum += sumFactor * pr; 277 | g_sum += sumFactor * pg; 278 | b_sum += sumFactor * pb; 279 | a_sum += sumFactor * pa; 280 | 281 | stack = stackStart; 282 | 283 | for( i = 0; i < radiusPlus1; i++ ) 284 | { 285 | stack.r = pr; 286 | stack.g = pg; 287 | stack.b = pb; 288 | stack.a = pa; 289 | stack = stack.next; 290 | } 291 | 292 | yp = width; 293 | 294 | for( i = 1; i <= radius; i++ ) 295 | { 296 | yi = ( yp + x ) << 2; 297 | 298 | r_sum += ( stack.r = ( pr = pixels[yi])) * ( rbs = radiusPlus1 - i ); 299 | g_sum += ( stack.g = ( pg = pixels[yi+1])) * rbs; 300 | b_sum += ( stack.b = ( pb = pixels[yi+2])) * rbs; 301 | a_sum += ( stack.a = ( pa = pixels[yi+3])) * rbs; 302 | 303 | r_in_sum += pr; 304 | g_in_sum += pg; 305 | b_in_sum += pb; 306 | a_in_sum += pa; 307 | 308 | stack = stack.next; 309 | 310 | if( i < heightMinus1 ) 311 | { 312 | yp += width; 313 | } 314 | } 315 | 316 | yi = x; 317 | stackIn = stackStart; 318 | stackOut = stackEnd; 319 | for ( y = 0; y < height; y++ ) 320 | { 321 | p = yi << 2; 322 | pixels[p+3] = pa = (a_sum * mul_sum) >> shg_sum; 323 | if ( pa > 0 ) 324 | { 325 | pa = 255 / pa; 326 | pixels[p] = ((r_sum * mul_sum) >> shg_sum ) * pa; 327 | pixels[p+1] = ((g_sum * mul_sum) >> shg_sum ) * pa; 328 | pixels[p+2] = ((b_sum * mul_sum) >> shg_sum ) * pa; 329 | } else { 330 | pixels[p] = pixels[p+1] = pixels[p+2] = 0; 331 | } 332 | 333 | r_sum -= r_out_sum; 334 | g_sum -= g_out_sum; 335 | b_sum -= b_out_sum; 336 | a_sum -= a_out_sum; 337 | 338 | r_out_sum -= stackIn.r; 339 | g_out_sum -= stackIn.g; 340 | b_out_sum -= stackIn.b; 341 | a_out_sum -= stackIn.a; 342 | 343 | p = ( x + (( ( p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1 ) * width )) << 2; 344 | 345 | r_sum += ( r_in_sum += ( stackIn.r = pixels[p])); 346 | g_sum += ( g_in_sum += ( stackIn.g = pixels[p+1])); 347 | b_sum += ( b_in_sum += ( stackIn.b = pixels[p+2])); 348 | a_sum += ( a_in_sum += ( stackIn.a = pixels[p+3])); 349 | 350 | stackIn = stackIn.next; 351 | 352 | r_out_sum += ( pr = stackOut.r ); 353 | g_out_sum += ( pg = stackOut.g ); 354 | b_out_sum += ( pb = stackOut.b ); 355 | a_out_sum += ( pa = stackOut.a ); 356 | 357 | r_in_sum -= pr; 358 | g_in_sum -= pg; 359 | b_in_sum -= pb; 360 | a_in_sum -= pa; 361 | 362 | stackOut = stackOut.next; 363 | 364 | yi += width; 365 | } 366 | } 367 | 368 | context.putImageData( imageData, top_x, top_y ); 369 | 370 | } 371 | 372 | 373 | function stackBlurCanvasRGB( id, top_x, top_y, width, height, radius ) 374 | { 375 | if ( isNaN(radius) || radius < 1 ) return; 376 | radius |= 0; 377 | 378 | //var canvas = document.getElementById( id ); 379 | //Edited to simply pass the canvas instead of looking it up by ID 380 | var canvas = id; 381 | var context = canvas.getContext("2d"); 382 | var imageData; 383 | 384 | try { 385 | try { 386 | imageData = context.getImageData( top_x, top_y, width, height ); 387 | } catch(e) { 388 | 389 | // NOTE: this part is supposedly only needed if you want to work with local files 390 | // so it might be okay to remove the whole try/catch block and just use 391 | // imageData = context.getImageData( top_x, top_y, width, height ); 392 | try { 393 | netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); 394 | imageData = context.getImageData( top_x, top_y, width, height ); 395 | } catch(e) { 396 | alert("Cannot access local image"); 397 | throw new Error("unable to access local image data: " + e); 398 | return; 399 | } 400 | } 401 | } catch(e) { 402 | alert("Cannot access image"); 403 | throw new Error("unable to access image data: " + e); 404 | } 405 | 406 | var pixels = imageData.data; 407 | 408 | var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, 409 | r_out_sum, g_out_sum, b_out_sum, 410 | r_in_sum, g_in_sum, b_in_sum, 411 | pr, pg, pb, rbs; 412 | 413 | var div = radius + radius + 1; 414 | var w4 = width << 2; 415 | var widthMinus1 = width - 1; 416 | var heightMinus1 = height - 1; 417 | var radiusPlus1 = radius + 1; 418 | var sumFactor = radiusPlus1 * ( radiusPlus1 + 1 ) / 2; 419 | 420 | var stackStart = new BlurStack(); 421 | var stack = stackStart; 422 | for ( i = 1; i < div; i++ ) 423 | { 424 | stack = stack.next = new BlurStack(); 425 | if ( i == radiusPlus1 ) var stackEnd = stack; 426 | } 427 | stack.next = stackStart; 428 | var stackIn = null; 429 | var stackOut = null; 430 | 431 | yw = yi = 0; 432 | 433 | var mul_sum = mul_table[radius]; 434 | var shg_sum = shg_table[radius]; 435 | 436 | for ( y = 0; y < height; y++ ) 437 | { 438 | r_in_sum = g_in_sum = b_in_sum = r_sum = g_sum = b_sum = 0; 439 | 440 | r_out_sum = radiusPlus1 * ( pr = pixels[yi] ); 441 | g_out_sum = radiusPlus1 * ( pg = pixels[yi+1] ); 442 | b_out_sum = radiusPlus1 * ( pb = pixels[yi+2] ); 443 | 444 | r_sum += sumFactor * pr; 445 | g_sum += sumFactor * pg; 446 | b_sum += sumFactor * pb; 447 | 448 | stack = stackStart; 449 | 450 | for( i = 0; i < radiusPlus1; i++ ) 451 | { 452 | stack.r = pr; 453 | stack.g = pg; 454 | stack.b = pb; 455 | stack = stack.next; 456 | } 457 | 458 | for( i = 1; i < radiusPlus1; i++ ) 459 | { 460 | p = yi + (( widthMinus1 < i ? widthMinus1 : i ) << 2 ); 461 | r_sum += ( stack.r = ( pr = pixels[p])) * ( rbs = radiusPlus1 - i ); 462 | g_sum += ( stack.g = ( pg = pixels[p+1])) * rbs; 463 | b_sum += ( stack.b = ( pb = pixels[p+2])) * rbs; 464 | 465 | r_in_sum += pr; 466 | g_in_sum += pg; 467 | b_in_sum += pb; 468 | 469 | stack = stack.next; 470 | } 471 | 472 | 473 | stackIn = stackStart; 474 | stackOut = stackEnd; 475 | for ( x = 0; x < width; x++ ) 476 | { 477 | pixels[yi] = (r_sum * mul_sum) >> shg_sum; 478 | pixels[yi+1] = (g_sum * mul_sum) >> shg_sum; 479 | pixels[yi+2] = (b_sum * mul_sum) >> shg_sum; 480 | 481 | r_sum -= r_out_sum; 482 | g_sum -= g_out_sum; 483 | b_sum -= b_out_sum; 484 | 485 | r_out_sum -= stackIn.r; 486 | g_out_sum -= stackIn.g; 487 | b_out_sum -= stackIn.b; 488 | 489 | p = ( yw + ( ( p = x + radius + 1 ) < widthMinus1 ? p : widthMinus1 ) ) << 2; 490 | 491 | r_in_sum += ( stackIn.r = pixels[p]); 492 | g_in_sum += ( stackIn.g = pixels[p+1]); 493 | b_in_sum += ( stackIn.b = pixels[p+2]); 494 | 495 | r_sum += r_in_sum; 496 | g_sum += g_in_sum; 497 | b_sum += b_in_sum; 498 | 499 | stackIn = stackIn.next; 500 | 501 | r_out_sum += ( pr = stackOut.r ); 502 | g_out_sum += ( pg = stackOut.g ); 503 | b_out_sum += ( pb = stackOut.b ); 504 | 505 | r_in_sum -= pr; 506 | g_in_sum -= pg; 507 | b_in_sum -= pb; 508 | 509 | stackOut = stackOut.next; 510 | 511 | yi += 4; 512 | } 513 | yw += width; 514 | } 515 | 516 | 517 | for ( x = 0; x < width; x++ ) 518 | { 519 | g_in_sum = b_in_sum = r_in_sum = g_sum = b_sum = r_sum = 0; 520 | 521 | yi = x << 2; 522 | r_out_sum = radiusPlus1 * ( pr = pixels[yi]); 523 | g_out_sum = radiusPlus1 * ( pg = pixels[yi+1]); 524 | b_out_sum = radiusPlus1 * ( pb = pixels[yi+2]); 525 | 526 | r_sum += sumFactor * pr; 527 | g_sum += sumFactor * pg; 528 | b_sum += sumFactor * pb; 529 | 530 | stack = stackStart; 531 | 532 | for( i = 0; i < radiusPlus1; i++ ) 533 | { 534 | stack.r = pr; 535 | stack.g = pg; 536 | stack.b = pb; 537 | stack = stack.next; 538 | } 539 | 540 | yp = width; 541 | 542 | for( i = 1; i <= radius; i++ ) 543 | { 544 | yi = ( yp + x ) << 2; 545 | 546 | r_sum += ( stack.r = ( pr = pixels[yi])) * ( rbs = radiusPlus1 - i ); 547 | g_sum += ( stack.g = ( pg = pixels[yi+1])) * rbs; 548 | b_sum += ( stack.b = ( pb = pixels[yi+2])) * rbs; 549 | 550 | r_in_sum += pr; 551 | g_in_sum += pg; 552 | b_in_sum += pb; 553 | 554 | stack = stack.next; 555 | 556 | if( i < heightMinus1 ) 557 | { 558 | yp += width; 559 | } 560 | } 561 | 562 | yi = x; 563 | stackIn = stackStart; 564 | stackOut = stackEnd; 565 | for ( y = 0; y < height; y++ ) 566 | { 567 | p = yi << 2; 568 | pixels[p] = (r_sum * mul_sum) >> shg_sum; 569 | pixels[p+1] = (g_sum * mul_sum) >> shg_sum; 570 | pixels[p+2] = (b_sum * mul_sum) >> shg_sum; 571 | 572 | r_sum -= r_out_sum; 573 | g_sum -= g_out_sum; 574 | b_sum -= b_out_sum; 575 | 576 | r_out_sum -= stackIn.r; 577 | g_out_sum -= stackIn.g; 578 | b_out_sum -= stackIn.b; 579 | 580 | p = ( x + (( ( p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1 ) * width )) << 2; 581 | 582 | r_sum += ( r_in_sum += ( stackIn.r = pixels[p])); 583 | g_sum += ( g_in_sum += ( stackIn.g = pixels[p+1])); 584 | b_sum += ( b_in_sum += ( stackIn.b = pixels[p+2])); 585 | 586 | stackIn = stackIn.next; 587 | 588 | r_out_sum += ( pr = stackOut.r ); 589 | g_out_sum += ( pg = stackOut.g ); 590 | b_out_sum += ( pb = stackOut.b ); 591 | 592 | r_in_sum -= pr; 593 | g_in_sum -= pg; 594 | b_in_sum -= pb; 595 | 596 | stackOut = stackOut.next; 597 | 598 | yi += width; 599 | } 600 | } 601 | 602 | context.putImageData( imageData, top_x, top_y ); 603 | 604 | } 605 | 606 | function BlurStack() 607 | { 608 | this.r = 0; 609 | this.g = 0; 610 | this.b = 0; 611 | this.a = 0; 612 | this.next = null; 613 | } -------------------------------------------------------------------------------- /scripts/lib/sandbox-scripts.js: -------------------------------------------------------------------------------- 1 | (function(window, document, JSON) { 2 | /* Remember that we are within the sandbox-scripts.html window scope */ 3 | //Sandbox scope 4 | var sandbox = {}; 5 | window.sandbox = sandbox; 6 | 7 | /* 8 | * Returns a message from the provided event. 9 | *

10 | * This is the main and only function that sandboxed script implementations 11 | * should call. Upon receiving an event with their messageHandler() 12 | * function, implementations should process the event and return an 13 | * object message using this function. 14 | * @param {Event} event the original Event a message was recieved from 15 | * @param {Object} message the return message 16 | * @returns {undefined} 17 | */ 18 | sandbox.returnMessage = function(event, message) { 19 | message = sandbox.deepCopySafeMessage(message || {}); 20 | //Assign the source/script to the original event's source/script 21 | message.source = event.data.source; 22 | message.script = event.data.script; 23 | 24 | //Send the message back to sandbox.js 25 | event.source.postMessage(message, event.origin); 26 | }; 27 | /** 28 | * Sandbox messages are not allowed to contain functions. This function 29 | * will quickly remove any function objects by stringifying and parsing 30 | * the message as a JSON object. 31 | * @param {Object} object 32 | * @returns {unresolved} 33 | */ 34 | sandbox.deepCopySafeMessage = function(object) { 35 | return JSON.parse(JSON.stringify(object)); 36 | }; 37 | 38 | /** 39 | * This object keeps track of which sandboxed scripts have been 40 | * initialized. 41 | */ 42 | sandbox.initsCalled = {}; 43 | /** 44 | * This object keeps track of the number of attempts an event has been 45 | * posted to a script after initialization. If too many attempts are made, 46 | * the event is dropped. 47 | */ 48 | var eventAttempts = {}; 49 | /** 50 | * Initializes a script from the provided URL. 51 | *

52 | * This function should only be called by sandbox-scripts.js. Individual 53 | * sandboxed script implementations should implement the checkScript() and 54 | * getScriptUrl() functions and let the sandbox do the work. 55 | *

56 | * Note that this is only used for externally loaded scripts, and is 57 | * different from the sandboxed script implementation. An implementation 58 | * (for instance, Google Maps API), may require additional assets to be 59 | * loaded that are hosted externally. These assets are what should be 60 | * passed in the getScriptUrl() function. 61 | * @param {string} url the URL of the script to load 62 | * @returns {undefined} 63 | */ 64 | sandbox.initializeScript = function(url) { 65 | var script = document.createElement('script'); 66 | script.type = 'text/javascript'; 67 | script.src = url; 68 | document.body.appendChild(script); 69 | }; 70 | 71 | /** 72 | * The sandbox-scripts message handler. This is where most all of the 73 | * processing takes place. 74 | * @param {Event} e 'message' event that may be from a sandboxed script or 75 | * from the main window's sandbox.js 76 | * @returns {undefined} 77 | */ 78 | function messageHandler(e) { 79 | //If this is a load check from sandbox.js, let it know we're loaded 80 | if (e.data.loadCheck) { 81 | e.source.postMessage({ 82 | 'loaded': true 83 | }, e.origin); 84 | } 85 | else if (e.data.script) { 86 | //If 'source' is not specified, generate a random source string 87 | if (!e.data.source) { 88 | e.data.source = 'Unknown' + Math.floor((Math.random() * 899) + 100); 89 | } 90 | 91 | /* 92 | * This is where sandbox-scripts tries to locate the global 93 | * variable for the requested script. 94 | *

95 | * In the case of our example sandboxed script, 'sandbox-myscript.js', 96 | * a global variable 'myscript' should be defined. 97 | *

98 | * Additionally, 'myscript' should implement AT LEAST a function 99 | * defined as messageHandler(Event). It SHOULD implement 100 | * checkScript() and getScriptUrl() as needed. See 101 | * 'sandbox-stripe.js' for an example implementation. 102 | */ 103 | var script = window[e.data.script]; 104 | if (script && script.messageHandler) { 105 | //If the script requires loading via a URL, 106 | if (script.checkScript && script.getScriptUrl) { 107 | if (script.checkScript()) { 108 | //If the script is loaded, pass the event to the message 109 | //handler 110 | script.messageHandler(e); 111 | } 112 | else { 113 | //If it is not loaded, check to see whether or not we 114 | //have already called tried to initialize the script 115 | if (!sandbox.initsCalled[script]) { 116 | //If we have not, attempt to initialize the script 117 | //using the provided URL 118 | sandbox.initsCalled[script] = true; 119 | sandbox.initializeScript(script.getScriptUrl()); 120 | } 121 | 122 | //Resend the event while we wait for the script to load, 123 | //but timeout if it takes too long and the script 124 | //still has not loaded. This is usually due to a 125 | //manifest issue. 126 | var attempt = eventAttempts[e.data.source] || 0; 127 | if (attempt > 10) { 128 | throw new Error('Could not send message after too many failed attempts, make sure manifest.json has specified the correct scripts-sandbox.html sandbox permission.') 129 | } 130 | else { 131 | attempt++; 132 | eventAttempts[e.data.source] = attempt; 133 | setTimeout(function() { 134 | messageHandler(e); 135 | }, 100); 136 | } 137 | 138 | } 139 | } 140 | else { 141 | //If the script does not require initialization, pass 142 | //the event to the handler 143 | script.messageHandler(e); 144 | } 145 | } 146 | } 147 | } 148 | 149 | /** 150 | * Initialize the sandbox-scripts and add the message handler. 151 | * @returns {undefined} 152 | */ 153 | var initialize = function() { 154 | window.addEventListener('message', messageHandler); 155 | }; 156 | initialize(); 157 | }(window, document, JSON)); 158 | -------------------------------------------------------------------------------- /scripts/lib/sandbox-stripe.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | //Check if sandbox-scripts.js exists 3 | if (!window.sandbox) { 4 | throw new Error('sandbox-scripts.js is not loaded in the "sandbox-scripts" iframe.'); 5 | } 6 | 7 | //Stripe Scope 8 | var stripe = {}; 9 | window.stripe = stripe; 10 | 11 | /** 12 | * Stripe.com JavaScript API script URL 13 | */ 14 | stripe.API_URL = 'https://js.stripe.com/v2/'; 15 | /** 16 | * Stripe.com account test publishable key. 17 | *

18 | * Be sure to use the publishable key and not the secret key. For 19 | * deployment, change this to the live publishable key. 20 | */ 21 | stripe.API_KEY = 'pk_test_Mh1oKuE9T1kalP8Stm9bzFee'; 22 | 23 | /** 24 | * Indicates whether or not the sandboxed script has been loaded. 25 | *

26 | * For this script, the global variable 'Stripe' (uppercase S is different 27 | * from the sandboxe global variable 'stripe') will be undefined until 28 | * the Stripe.com script is loaded. This can be used to indicate whether 29 | * or not the sandboxed script is ready. 30 | * @returns {boolean} true if the Stripe.com script is loaded and ready 31 | */ 32 | stripe.checkScript = function() { 33 | return typeof Stripe !== 'undefined'; 34 | }; 35 | /** 36 | * Retrieves the API script URL that needs to be loaded. 37 | *

38 | * This function is called by sandbox-scripts.js, and refers to the 39 | * Stripe.com script for this library. 40 | * @returns {string} the Stripe.com API script URL 41 | */ 42 | stripe.getScriptUrl = function() { 43 | return stripe.API_URL; 44 | }; 45 | /** 46 | * Stripe sandboxed message handler. 47 | * @param {Event} e message event 48 | * @returns {undefined} 49 | */ 50 | stripe.messageHandler = function(e) { 51 | //Note: when using a sandboxed script with sandbox-scripts.js, there 52 | //is no need to check the event's source/script parameters, or even 53 | //check if this sandboxed script is loaded and ready. 54 | //Sandbox-scripts.js takes care of the event source/script checking, 55 | //it can be safely assume that this script will only receive events with 56 | //the 'stripe' source property. 57 | //Since this scripts implements checkScript() and getScriptUrl(), it 58 | //is also safe to assume that the global variable 'Stripe' will always 59 | //be defined and ready since sandbox-scripts.js will automatically 60 | //initialize and load the Stripe.com API script 61 | Stripe.setPublishableKey(stripe.API_KEY); 62 | chargeCard(e); 63 | }; 64 | 65 | /** 66 | * Charges a card from the provided event. 67 | * @param {Event} event 68 | * @returns {undefined} 69 | */ 70 | var chargeCard = function(event) { 71 | //The response to send back over the sandbox 72 | var eventResponse = {}; 73 | 74 | //Card information 75 | var number = event.data.number; 76 | var expMonth = event.data.expMonth.toString(); 77 | var expYear = event.data.expYear.toString(); 78 | //The amount to be charged 79 | var amount = event.data.amount; 80 | 81 | //Use Stripe.com's card validation functions to ensure the card's 82 | //expiration is valid and the number is a valid credit card number 83 | if (Stripe.validateCardNumber(number) && Stripe.validateExpiry(expMonth, expYear)) { 84 | //Create a charge token 85 | Stripe.card.createToken({ 86 | number: number, 87 | exp_month: expMonth, 88 | exp_year: expYear 89 | }, function(status, response) { 90 | if (response.error) { 91 | eventResponse.success = false; 92 | eventResponse.message = response.error; 93 | } 94 | else { 95 | var token = response.id; 96 | //This is where you would send the token and the amount to your 97 | //own server which would charge the Stripe single-use token 98 | /*$.ajax('https://mystripeserver.com/', { 99 | data: { 100 | token: token, 101 | amount: amount 102 | } 103 | }).success(function(data) { 104 | //Charge was successful 105 | }).fail(function(jqXHR) { 106 | //Charge failed 107 | });*/ 108 | //For testing purposes, assume the transaction was successful 109 | eventResponse.success = true; 110 | eventResponse.message = 'Charge successful'; 111 | 112 | //Send the response back over the sandbox 113 | window.sandbox.returnMessage(event, eventResponse); 114 | } 115 | }); 116 | } 117 | else { 118 | eventResponse.success = false; 119 | eventResponse.message = 'Invalid card'; 120 | 121 | //Send the response back over the sandbox 122 | window.sandbox.returnMessage(event, eventResponse); 123 | } 124 | }; 125 | })(window); -------------------------------------------------------------------------------- /scripts/lib/sandbox.js: -------------------------------------------------------------------------------- 1 | (function(window, document) { 2 | //Sandbox Scope 3 | var sandbox = {}; 4 | window.sandbox = sandbox; 5 | 6 | //Indicates whether or not sandbox-scripts.html has loaded 7 | var loaded = false; 8 | //Indicates whether or not an iframe with the ID of '#sandbox-scripts' was found 9 | var iframeNotFound = false; 10 | 11 | /** 12 | * Sends a message to a sandboxed script. 13 | *

14 | * The message should AT LEAST contain a 'source' property, which should 15 | * be any uniquely identifying String, and a 'script' property, which 16 | * indicates the script to load. 17 | *

18 | * The 'script' property should be equal to the scope object declared in 19 | * the sandbox script's file. Usually this is a lowercase name of the 20 | * script. 21 | *

22 | * For the sandbox script 'sandbox-myscript.js', the 'script' parameter 23 | * should be 'myscript'. 24 | *

25 | * Any additional properties can be set as needed for the individual 26 | * sandboxed script. 27 | * @param {Object} message the message to send to the sandboxed script 28 | * @returns {undefined} 29 | */ 30 | sandbox.message = function(message) { 31 | if (loaded) { 32 | postMessage(message); 33 | } 34 | else if (iframeNotFound) { 35 | //If we can't find the sandbox, throw an error, don't try it again 36 | throw new Error('Cannot send a message when iframe "sandbox-scripts" was not found.'); 37 | } 38 | else { 39 | //Keep trying to send the message if the sandbox has not loaded 40 | setTimeout(function() { 41 | sandbox.message(message); 42 | }, 100); 43 | } 44 | }; 45 | 46 | /** 47 | * Sends a message to the sandbox-scripts.html content window. 48 | * @param {Object} message the message to send 49 | * @returns {undefined} 50 | */ 51 | var postMessage = function(message) { 52 | var iframe = document.getElementById('sandbox-scripts'); 53 | if (iframe) { 54 | var win = iframe.contentWindow; 55 | if (win !== null) { 56 | win.postMessage(message, '*'); 57 | } 58 | } 59 | else { 60 | iframeNotFound = true; 61 | throw new Error('Warning: No iframe with "sandbox-scripts" ID was found, include it for sandbox scripts to work.'); 62 | } 63 | }; 64 | 65 | /** 66 | * Initialize the sandbox. 67 | * @returns {undefined} 68 | */ 69 | var initialize = function() { 70 | //Add a listener to determine if the sandbox is loaded 71 | window.addEventListener('message', function(e) { 72 | if (e.data.loaded) { 73 | loaded = true; 74 | } 75 | }); 76 | //Start the load check as soon as the window is done loading 77 | window.addEventListener('load', loadCheck); 78 | }; 79 | /** 80 | * A load check function that will repeatedly message sandbox-scripts.html 81 | * until it receives a response that sandbox-scripts.js is completely 82 | * loaded. 83 | * @returns {undefined} 84 | */ 85 | var loadCheck = function() { 86 | if (!loaded && !iframeNotFound) { 87 | postMessage({ 88 | 'loadCheck': true 89 | }); 90 | setTimeout(loadCheck, 100); 91 | } 92 | }; 93 | initialize(); 94 | })(window, document); -------------------------------------------------------------------------------- /scripts/lib/slider.js: -------------------------------------------------------------------------------- 1 | (function(window, $) { 2 | //Slider Scope 3 | var slider = {}; 4 | window.slider = slider; 5 | 6 | /** 7 | * Defines before/after open/close event types. 8 | *

9 | * For example: 10 | *

 11 |      * $(mypage).on(slider.Event.BEFORE_OPEN, functionToCallBefore);
 12 |      * 
13 | * Note that these events should be used for quick actions. If a 14 | * long-running function needs to be called before a slide is opened, 15 | * use the "beforeOpen" parameter in slider.slide() to display a processing 16 | * page first. 17 | */ 18 | slider.Event = { 19 | BEFORE_OPEN: 'before-open', 20 | AFTER_OPEN: 'after-open', 21 | BEFORE_CLOSE: 'before-close', 22 | AFTER_CLOSE: 'after-close' 23 | }; 24 | /** 25 | * Defines the direction a slide will be opened. 26 | */ 27 | slider.Direction = { 28 | LEFT: 'left', 29 | RIGHT: 'right' 30 | }; 31 | /** 32 | * The duration (ms) of the slide animation 33 | */ 34 | slider.duration = 350; 35 | /** 36 | * The jQuery selector for the processing page. 37 | *

38 | * If specified, when using slider.slide() with a "beforeOpen" parameter, 39 | * the processing page will be opened first, the beforeOpen function run, 40 | * and then the next page displayed. 41 | *

42 | * If the processing page is not specified, using "beforeOpen" on 43 | * slider.slide() would be equivalent to binding the function to the 44 | * "before-open" trigger, and no processing page will be displayed. 45 | */ 46 | slider.processing; 47 | /** 48 | * The jQuery selector for the hidden storage div. 49 | *

50 | * If specified, when removing slides, slider.js will append their content 51 | * to the storage div. This is important if the pages need to be accessed 52 | * again and are not stored in memory. 53 | */ 54 | slider.storage; 55 | 56 | //Processing jQuery selector 57 | var $process; 58 | 59 | /** 60 | * Slides the provided page into the specified slider. 61 | *

62 | * By default, the direction is left. 63 | *

64 | * If a function is provided to execute before opening the page, a 65 | * processing page (if defined in slider.processing) will be opened 66 | * first, the function executed, and then the page displayed. 67 | * @param {string|jQuery} slideContainer the slider to display the page in 68 | * @param {string|jQuery} page the page to display 69 | * @param {string} direction the direction to slide the page from 70 | * @param {function} beforeOpen optional function to execute before opening 71 | * @param {Object} param optional parameters for the optional function 72 | * @returns {undefined} 73 | */ 74 | slider.slide = function(slideContainer, page, direction, beforeOpen, param) { 75 | //If beforeOpen is specified and a processing page is specified, 76 | if (typeof beforeOpen === 'function' && slider.processing) { 77 | //Define the processing page selector if it has not been defined yet 78 | if (!$process) { 79 | $process = $(slider.processing); 80 | } 81 | 82 | //Wrap the page in a slide and open the page after the process 83 | //slide has opened fully 84 | var processSlide = $('

').addClass('slide').append($process); 85 | processSlide.on(slider.Event.AFTER_OPEN, function(e) { 86 | e.stopPropagation(); //Prevents second trigger 87 | var targetSlide = $('
').addClass('slide').append($(page)); 88 | targetSlide.on(slider.Event.BEFORE_OPEN, function() { 89 | beforeOpen(param); 90 | }); 91 | open(slideContainer, targetSlide, direction); 92 | }); 93 | open(slideContainer, processSlide, direction); 94 | } 95 | else { 96 | //If no processing page is specified, execute the beforeOpen 97 | //function immediately and open the page 98 | if (typeof beforeOpen === 'function') { 99 | beforeOpen(param); 100 | } 101 | var targetSlide = $('
').addClass('slide').append($(page)); 102 | open(slideContainer, targetSlide, direction); 103 | } 104 | }; 105 | 106 | /** 107 | * Opens a slide in the specified slider. 108 | * @param {string|jQuery} slideContainer the slider to open the slide into 109 | * @param {string|jQuery} target the target slide to open 110 | * @param {string} direction the direction to slide the target from 111 | * @returns {undefined} 112 | */ 113 | var open = function(slideContainer, target, direction) { 114 | //If no direction is specified, default to left 115 | direction = direction === slider.Direction.LEFT ? slider.Direction.LEFT : slider.Direction.RIGHT; 116 | 117 | var $slider = $(slideContainer); 118 | 119 | //Get the current center slide 120 | var center = $slider.find('.slide.center'); 121 | //Trigger events on the slide's wrapped children 122 | center.children().trigger(slider.Event.BEFORE_CLOSE); 123 | center.animate({ 124 | left: direction === slider.Direction.LEFT ? '100%' : '-100%' 125 | }, slider.duration, function() { 126 | center.children().trigger(slider.Event.AFTER_CLOSE); 127 | if (slider.storage) { 128 | //If storage is specified, add the children to storage 129 | center.children().appendTo(slider.storage); 130 | } 131 | center.detach(); 132 | }); 133 | 134 | //Get the incoming target slide and add it to the slider 135 | target = $(target); 136 | target.addClass(direction).appendTo($slider); 137 | //Trigger events on the slide's wrapped children 138 | target.children().trigger(slider.Event.BEFORE_OPEN); 139 | target.animate({ 140 | left: 0 141 | }, slider.duration, function() { 142 | target.removeClass(direction).addClass('center'); 143 | target.children().trigger(slider.Event.AFTER_OPEN); 144 | }); 145 | }; 146 | })(window, jQuery); -------------------------------------------------------------------------------- /scripts/lib/spreadsheet.gs: -------------------------------------------------------------------------------- 1 | //This script should be registered on a Google account at 2 | //https://script.google.com/ 3 | //After creating a new project and deploying the script as a web app, 4 | //retrieve the web app URL and set it as spreadsheet.MACRO_URL in the 5 | //spreadsheet.js library. 6 | //You will need to exectue the app as "me" and allow "Anyone, even anonymous" 7 | //access to the app. Additionally, you will need to run it once in the browser 8 | //to authorize the app access to your Google Drive spreadsheets. 9 | 10 | function doGet(event) { 11 | var response = {}; 12 | response.message = ''; 13 | response.status = 200; 14 | response.data = {}; 15 | 16 | try { 17 | //Retrieve spreadsheet by ID or return error message 18 | if (!event || !event.parameter) { 19 | response.message = 'No parameters given'; 20 | response.status = 400; 21 | return ContentService.createTextOutput(JSON.stringify(response)); 22 | } 23 | 24 | var id = event.parameter.spreadsheetId; 25 | if (!id) { 26 | response.message = 'No spreadsheet ID provided'; 27 | response.status = 400; 28 | return ContentService.createTextOutput(JSON.stringify(response)); 29 | } 30 | var ss; 31 | try { 32 | ss = SpreadsheetApp.openById(id); 33 | } 34 | catch (e) { 35 | response.message = 'Invalid spreadsheet ID'; 36 | response.status = 400; 37 | return ContentService.createTextOutput(JSON.stringify(response)); 38 | } 39 | if (ss.getSheets().length < 1) { 40 | response.message = 'Empty spreadsheet'; 41 | response.status = 400; 42 | return ContentService.createTextOutput(JSON.stringify(response)); 43 | } 44 | 45 | //Retrieve sheet (default first sheet unless sheetIndex is specified) 46 | sheetIndex = 0; 47 | if (event.parameter.sheetIndex) { 48 | if (event.parameter.sheetIndex < ss.getSheets().length - 1) { 49 | sheetIndex = event.parameter.sheetIndex; 50 | } 51 | } 52 | var sheet = ss.getSheets()[sheetIndex]; 53 | 54 | var action = event.parameter.action; 55 | if (action === 'get') { 56 | var range = sheet.getDataRange(); 57 | var values = range.getValues(); 58 | if (values.length < 1) { 59 | response.message = 'Empty spreadsheet'; 60 | response.status = 400; 61 | return ContentService.createTextOutput(JSON.stringify(response)); 62 | } 63 | 64 | response.message = 'Success'; 65 | response.status = 200; 66 | response.data = values; 67 | return ContentService.createTextOutput(JSON.stringify(response)); 68 | } 69 | else if (action === 'post') { 70 | var row = JSON.parse(event.parameter.row); 71 | if (Object.prototype.toString.call(row) !== '[object Array]') { 72 | response.message = 'Invalid row parameter'; 73 | response.status = 400; 74 | return ContentService.createTextOutput(JSON.stringify(response)); 75 | } 76 | sheet.appendRow(row); 77 | response.message = 'Success'; 78 | response.status = 200; 79 | return ContentService.createTextOutput(JSON.stringify(response)); 80 | } 81 | else { 82 | response.message = 'Invalid action'; 83 | response.status = 400; 84 | return ContentService.createTextOutput(JSON.stringify(response)); 85 | } 86 | } 87 | catch (e) { 88 | response.message = 'An unexpected error occurred: ' + e.message; 89 | response.status = 500; 90 | return ContentService.createTextOutput(JSON.stringify(response)); 91 | } 92 | } -------------------------------------------------------------------------------- /scripts/lib/spreadsheet.js: -------------------------------------------------------------------------------- 1 | (function(window, JSON) { 2 | //Spreadsheet Scope 3 | var spreadsheet = {}; 4 | window.spreadsheet = spreadsheet; 5 | 6 | /** 7 | * The web app URL that spreadsheet.gs is deployed to 8 | */ 9 | spreadsheet.MACRO_URL = 'https://script.google.com/macros/s/AKfycbw4AvoXKYaURCkaCmjwi7zQO54GCP45YaNnGQ0d8slA0ZGxiEw/exec'; 10 | /** 11 | * The default spreadsheetId to use when making requests. 12 | *

13 | * This should be specified if the app is only communicating with one 14 | * spreadsheet. 15 | */ 16 | spreadsheet.defaultSpreadsheetId; 17 | 18 | /** 19 | * Retrieves all rows in the provided spreadsheet and returns them in the 20 | * provided callback. 21 | * @param {function(Array,string,number)} callback callback function to 22 | * retrieve row information. The rows Array, message, and status are 23 | * returned as the callback parameters 24 | * @param {string} spreadsheetId the spreadsheet ID (optional if using 25 | * a default spreadsheet ID) 26 | * @returns {undefined} 27 | */ 28 | spreadsheet.getAllRows = function(callback, spreadsheetId) { 29 | spreadsheetId = spreadsheetId || spreadsheet.defaultSpreadsheetId; 30 | var params = { 31 | 'spreadsheetId': spreadsheetId, 32 | 'action': 'get' 33 | }; 34 | var url = []; 35 | url.push(spreadsheet.MACRO_URL); 36 | url.push('?'); 37 | for (var key in params) { 38 | url.push(key); 39 | url.push('='); 40 | url.push(params[key]); 41 | url.push('&'); 42 | } 43 | url.pop(); 44 | 45 | var xhr = new XMLHttpRequest(); 46 | xhr.onreadystatechange = function() { 47 | if (xhr.readyState === 4) { 48 | //Always callback an array, message, and status, even if 49 | //the web app returned an error 50 | var rows = []; 51 | var message = xhr.statusText; 52 | var status = xhr.status; 53 | 54 | if (xhr.status === 200) { 55 | var response = JSON.parse(xhr.responseText); 56 | message = response.message; 57 | status = xhr.status; 58 | 59 | if (response.status === 200) { 60 | rows = response.data; 61 | } 62 | } 63 | if (typeof callback === 'function') { 64 | callback(rows, message, status); 65 | } 66 | } 67 | }; 68 | xhr.open('GET', url.join(''), true); 69 | xhr.send(); 70 | }; 71 | 72 | /** 73 | * Adds a row to the provided spreadsheet, and calls the provided 74 | * callback function upon completion. 75 | * @param {Array} row Array of row data to append 76 | * @param {function(string,number)} callback callback function that is 77 | * called with a message and status number indicating the result of 78 | * the appended row 79 | * @param {string} spreadsheetId the spreadsheet ID (optional if using 80 | * a default spreadsheet ID) 81 | * @returns {undefined} 82 | */ 83 | spreadsheet.appendRow = function(row, callback, spreadsheetId) { 84 | if (typeof callback === 'string') { 85 | spreadsheetId = callback; 86 | } 87 | spreadsheetId = spreadsheetId || spreadsheet.defaultSpreadsheetId; 88 | var params = { 89 | 'spreadsheetId': spreadsheetId, 90 | 'action': 'post', 91 | 'row': JSON.stringify(row) 92 | }; 93 | var url = []; 94 | url.push(spreadsheet.MACRO_URL); 95 | url.push('?'); 96 | for (var key in params) { 97 | url.push(key); 98 | url.push('='); 99 | url.push(params[key]); 100 | url.push('&'); 101 | } 102 | url.pop(); 103 | 104 | var xhr = new XMLHttpRequest(); 105 | xhr.onreadystatechange = function() { 106 | if (xhr.readyState === 4) { 107 | var message = xhr.statusText; 108 | var status = xhr.status; 109 | 110 | if (xhr.status === 200) { 111 | var response = JSON.parse(xhr.responseText); 112 | message = response.message; 113 | status = response.status; 114 | } 115 | if (typeof callback === 'function') { 116 | callback(message, status); 117 | } 118 | } 119 | }; 120 | xhr.open('GET', url.join(''), true); 121 | xhr.send(); 122 | }; 123 | })(window, JSON); -------------------------------------------------------------------------------- /scripts/lib/stillthere.js: -------------------------------------------------------------------------------- 1 | (function(window, Date, $) { 2 | //StillThere scope 3 | var stillthere = {}; 4 | window.stillthere = stillthere; 5 | 6 | /** 7 | * Defines event types when a user has timed out. 8 | *

9 | * The LOADED event is fired once when stillthere is finished loading. 10 | * This should be listened to whenever modifying the contents of 11 | * stillthere.overlay. 12 | *

13 | * The STILL_THERE event is fired once when the user has not made a gesture 14 | * for however long (ms) specified in stillthere.timeoutStillThere. A 15 | * progress bar will be displayed indicating the amount of time left 16 | * before the timeout event is triggered. 17 | *

18 | * The TIMEOUT event is fired once after the STILL_THERE event and 19 | * indicates that an app session should be reset. 20 | */ 21 | stillthere.Event = { 22 | 'TIMEOUT': 'timeout', 23 | 'STILL_THERE': 'still-there', 24 | 'LOADED': 'loaded' 25 | }; 26 | 27 | /** 28 | * The jQuery overlay selector 29 | */ 30 | stillthere.overlay; 31 | /** 32 | * Timestamp of the last user gesture 33 | */ 34 | stillthere.lastGesture; 35 | /** 36 | * Amount of time (ms) until the overlay asks if the user is still there 37 | */ 38 | stillthere.timeoutStillThere = 30000; 39 | /** 40 | * Amount of time (ms) until the overlay times out 41 | */ 42 | stillthere.timeout = 60000; 43 | /** 44 | * How often to check for user gesture updates 45 | */ 46 | stillthere.checkInterval = 1000; 47 | 48 | /** 49 | * Map of listener functions. 50 | */ 51 | var listeners = {}; 52 | /** 53 | * Indicates that the STILL_THERE event has fired at least once 54 | * @type Boolean 55 | */ 56 | var stillThereFired = false; 57 | /** 58 | * Indicates that the TIMEOUT event has fired at least once 59 | * @type Boolean 60 | */ 61 | var timeoutFired = false; 62 | 63 | /** 64 | * Adds an event listener to the stillthere overlay. 65 | *

66 | * The type parameter may be "timeout", "still-there", or "loaded" as 67 | * defined in the stillthere.Event object. 68 | * @param {string} type the type of event to listen to 69 | * @param {function)} listener the listener to add 70 | * @returns {undefined} 71 | */ 72 | stillthere.addEventListener = function(type, listener) { 73 | if (typeof listener === 'function') { 74 | listeners[listener] = { 75 | 'type': type, 76 | 'listener': listener 77 | }; 78 | } 79 | }; 80 | /** 81 | * Removes an event listener from the stillthere overlay. 82 | *

83 | * If a listener is not provided, all listeners of the provided type 84 | * are removed. 85 | *

86 | * The type parameter may be "timeout", "still-there", or "loaded" as 87 | * defined in the stillthere.Event object. 88 | * @param {string} type the type of event to listen to 89 | * @param {function} listener the listener to remove 90 | * @returns {undefined} 91 | */ 92 | stillthere.removeEventListener = function(type, listener) { 93 | var removeAll = typeof listener === 'undefined'; 94 | for (var key in listeners) { 95 | if (listeners[key].type === type) { 96 | if (removeAll || listeners[key].listener === listener) { 97 | delete listeners[key]; 98 | } 99 | } 100 | } 101 | }; 102 | /** 103 | * Fires all listeners of the specified type. 104 | * @param {string} type the type of event to fire 105 | * @returns {undefined} 106 | */ 107 | var fireListeners = function(type) { 108 | for (var key in listeners) { 109 | if (listeners[key].type === type) { 110 | listeners[key].listener(); 111 | } 112 | } 113 | }; 114 | 115 | /** 116 | * Forces the display of the "timeout" overlay and triggers the 117 | * TIMEOUT event. 118 | * @returns {undefined} 119 | */ 120 | stillthere.showTimeout = function() { 121 | stillthere.lastGesture = stillthere.timeout + 1; 122 | }; 123 | /** 124 | * Indicates whether or not the overlay is currently visible. 125 | * @returns {boolean} true if the overlay is visible, or false if not 126 | */ 127 | stillthere.isOverlayVisible = function() { 128 | return stillthere.overlay.is(':visible'); 129 | }; 130 | /** 131 | * Shows the overlay if it is not already visible. 132 | * @returns {undefined} 133 | */ 134 | var showOverlay = function() { 135 | if (!stillthere.isOverlayVisible()) { 136 | stillthere.overlay.show(); 137 | } 138 | }; 139 | /** 140 | * Hides the overlay if it is not already hidden. 141 | * @returns {undefined} 142 | */ 143 | var hideOverlay = function() { 144 | if (stillthere.isOverlayVisible()) { 145 | stillthere.overlay.hide(); 146 | } 147 | }; 148 | 149 | /** 150 | * Checks to see if enough time has passed since the last user gesture 151 | * to trigger a STILL_THERE or TIMEOUT event. 152 | * @returns {undefined} 153 | */ 154 | var checkTimeout = function() { 155 | var timePassed = Date.now() - stillthere.lastGesture; 156 | 157 | showOverlay(); 158 | if (timePassed > stillthere.timeout) { 159 | if (!timeoutFired) { 160 | //Only fire event once 161 | fireListeners(stillthere.Event.TIMEOUT); 162 | timeoutFired = true; 163 | stopProgress(); 164 | } 165 | } 166 | else if (timePassed > stillthere.timeoutStillThere) { 167 | if (!stillThereFired) { 168 | //Only fire event once 169 | fireListeners(stillthere.Event.STILL_THERE); 170 | stillThereFired = true; 171 | startProgress(); 172 | } 173 | } 174 | else { 175 | //User still active, reset things 176 | stillThereFired = false; 177 | timeoutFired = false; 178 | hideOverlay(); 179 | } 180 | setTimeout(checkTimeout, stillthere.checkInterval); 181 | }; 182 | /** 183 | * Hides the progress bar. 184 | * @returns {undefined} 185 | */ 186 | var stopProgress = function() { 187 | stillthere.overlay.find('.progress').hide(); 188 | }; 189 | /** 190 | * Displays the progress bar and starts it. 191 | * @param {boolean} update if true, update the progress bar, 192 | * if false, start the progress bar 193 | * @returns {undefined} 194 | */ 195 | var startProgress = function(update) { 196 | var progress = stillthere.overlay.find('.progress'); 197 | var updateInterval = 100; 198 | if (update) { 199 | var value = parseInt(progress.attr('value')); 200 | if (value < progress.attr('max')) { 201 | progress.attr('value', value + 1); 202 | setTimeout(function() { 203 | startProgress(true); 204 | }, updateInterval); 205 | } 206 | } 207 | else { 208 | var max = (stillthere.timeout - stillthere.timeoutStillThere) / updateInterval; 209 | progress.attr('value', 0).attr('max', max); 210 | progress.show(); 211 | startProgress(true); 212 | } 213 | }; 214 | 215 | /** 216 | * Updates the user's last gesture timestamp. 217 | * @returns {undefined} 218 | */ 219 | var updateLastGesture = function() { 220 | stillthere.lastGesture = Date.now(); 221 | }; 222 | 223 | /** 224 | * Initializes the stillthere overlay, appends it to the body of the 225 | * app, and adds listeners for user gestures. 226 | * @returns {undefined} 227 | */ 228 | var initialize = function() { 229 | stillthere.overlay = $('

').addClass('still-there'); 230 | stillthere.overlay.append($('
').addClass('background')); 231 | stillthere.overlay.append($('
').addClass('foreground') 232 | .append($('
').addClass('message')) 233 | .append($('').addClass('progress'))); 234 | stillthere.overlay.appendTo($('body')); 235 | 236 | window.addEventListener('click', updateLastGesture); 237 | window.addEventListener('mousemove', updateLastGesture); 238 | window.addEventListener('touchmove', updateLastGesture); 239 | window.addEventListener('keydown', updateLastGesture); 240 | 241 | updateLastGesture(); 242 | checkTimeout(); 243 | fireListeners(stillthere.Event.LOADED); 244 | }; 245 | 246 | window.addEventListener('load', initialize); 247 | })(window, Date, jQuery); -------------------------------------------------------------------------------- /scripts/lib/stripe.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | //Check if sandbox-scripts.js exists 3 | if (!window.sandbox) { 4 | throw new Error('stripe.js requires sandbox. sandbox-scripts.js is not loaded in the "sandbox-scripts" iframe.'); 5 | } 6 | 7 | //Stripe Scope 8 | var stripe = {}; 9 | window.stripe = stripe; 10 | 11 | /** 12 | * This object contains callback functions for different calls to 13 | * stripe.chargeCard(). 14 | *

15 | * When a charge is requested, the passed callback function is assigned 16 | * a unique source identifier, which is passed as the 'source' property in 17 | * the sandbox message, and used to retrieve the callback when a response 18 | * has been received. 19 | */ 20 | stripe.callbacks = {}; 21 | 22 | /** 23 | * Charges a credit card. 24 | *

25 | * Note that for testing purposes, this function does not actually charge 26 | * the provided card. 27 | * @param {Card} card the Card to charge (received from swiper.js library) 28 | * @param {number} amount the amount to charge 29 | * @param {function(object)} callback callback function that receives an 30 | * object with a boolean property 'success' that indicates the status 31 | * of the charge, and a String property 'message' that contains a short 32 | * description of the success or failure of a charge 33 | * @returns {undefined} 34 | */ 35 | stripe.chargeCard = function(card, amount, callback) { 36 | var source = 'ChargeMessage' + Math.floor((Math.random() * 899) + 100); 37 | stripe.callbacks[source] = callback; 38 | var message = { 39 | 'source': source, 40 | 'script': 'stripe', 41 | 'number': card.getNumber(), 42 | 'expMonth': card.getExpMonth(), 43 | 'expYear': card.getExpYear(), 44 | 'amount': amount 45 | }; 46 | window.sandbox.message(message); 47 | }; 48 | 49 | //Add message listener for sandbox messages 50 | window.addEventListener('message', function(e) { 51 | //Verify that the message is from sandbox-stripe.js 52 | if (e.data.script === 'stripe') { 53 | //Retrieve the callback associate with the event's source 54 | var callback = stripe.callbacks[e.data.source]; 55 | if (callback !== undefined) { 56 | callback(e.data); 57 | } 58 | } 59 | }); 60 | })(window); -------------------------------------------------------------------------------- /scripts/lib/swiper.js: -------------------------------------------------------------------------------- 1 | (function(window, document, $, Date) { 2 | //Swiper Score 3 | var swiper = {}; 4 | window.swiper = swiper; 5 | 6 | /** 7 | * The swiper event type 8 | */ 9 | swiper.EVENT = 'card-swipe'; 10 | /** 11 | * The character key that indicates a line end (default 13, carriage return) 12 | *

13 | * This can be set if the card reader uses a different line end key. 14 | */ 15 | swiper.LINE_END = 13; 16 | /** 17 | * Splits line data into three tracks 18 | * @type RegExp 19 | */ 20 | var TRACK_REGEX = /(%.*?\?)?(;.*?\?)?/; 21 | /** 22 | * Splits track one into its sub-components 23 | * @type RegExp 24 | */ 25 | var TRACK_1_REGEX = /%([A-Z])([0-9]{1,19})\^([^\^]{2,26})\^([0-9]{4}|\^)([0-9]{3}|\^)([^\?]+)\?/; 26 | /** 27 | * Splits track two into its sub-components 28 | * @type RegExp 29 | */ 30 | var TRACK_2_REGEX = /;([0-9]{1,19})=([0-9]{4}|=)([0-9]{3}|=)([^\?]+)\?/; 31 | /** 32 | * Dummy card buffer (4242-4242-4242-4242 John Doe 01/15 33 | */ 34 | var DUMMY_CARD = '%B4242424242424242^DOE/JOHN^15011011000?;B4242424242424242=15011011000?'.split(''); 35 | 36 | /** 37 | * Indicates that the swiper is scanning for card data. 38 | *

39 | * Set to true before swiping a card. 40 | */ 41 | swiper.scanning = false; 42 | /** 43 | * Configurable delay (ms) before the swiper buffer is cleared. 44 | *

45 | * This delay ensures a user cannot type on a keyboard and submit a card 46 | * swipe. It should not be lower than however long it takes the card 47 | * reader to submit a full line of data to the computer. 48 | */ 49 | swiper.delay = 250; 50 | /** 51 | * Bypass key to submit a dummy card swipe (default back-tick (tilde) ` key) 52 | */ 53 | swiper.bypass = 96; 54 | /** 55 | * When true, the bypass key will submit a dummy card swipe. 56 | *

57 | * Set to false for deployment. 58 | */ 59 | swiper.enableBypass = true; 60 | 61 | /** 62 | * Timestamp when a swipe event started, used with swiper.delay to 63 | * determine valid card swipes. 64 | */ 65 | var swipeStart = 0; 66 | /** 67 | * Array of jQuery selectors that will be triggered when a swipe event 68 | * occurs. 69 | */ 70 | var triggers = []; 71 | /** 72 | * jQuery focus selector that can be set using swiper.setFocus(). 73 | *

74 | * If multiple triggers are used, and a situation occurs where only one 75 | * needs to be triggered, the focus can be set. Whenever the focus is 76 | * set, a card swipe event is triggered on that focus instead of the 77 | * triggers added via swiper.addTrigger. 78 | *

79 | * Focus is lost on a card swipe, so it must be reset every time it is 80 | * needed. 81 | */ 82 | var focus; 83 | /** 84 | * Buffer of character keys for the swipe event. 85 | */ 86 | var buffer = []; 87 | 88 | window.addEventListener('load', function() { 89 | //Register global keypress listener 90 | document.addEventListener('keypress', swiped); 91 | }); 92 | 93 | /** 94 | * Add a jQuery selector to be triggered when a swipe event occurs 95 | * @param {jQuery|string} selector the jQuery selector to be triggered 96 | * @returns {undefined} 97 | */ 98 | swiper.addTrigger = function(selector) { 99 | triggers.push(selector); 100 | }; 101 | /** 102 | * Set a jQuery selector to be focused for the next swipe event. 103 | *

104 | * The next swipe event will only trigger the provided selector. It will 105 | * not trigger any previous selectors added by swiper.addTrigger() until 106 | * the next swipe event. 107 | *

108 | * Focus is lost after a swipe event (even failed attempts). It must be 109 | * reset each time as needed. 110 | * @param {jQuery|string} selector the jQuery selector to trigger next 111 | * @returns {undefined} 112 | */ 113 | swiper.setFocus = function(selector) { 114 | focus = selector; 115 | }; 116 | /** 117 | * Removes the previous set focus. The next swipe event will trigger 118 | * all jQuery selectors added by swiper.addTrigger(). 119 | * @returns {undefined} 120 | */ 121 | swiper.removeFocus = function() { 122 | focus = undefined; 123 | }; 124 | 125 | /** 126 | * The global keyboard listener 127 | * @param {Event} e keypress event 128 | */ 129 | var swiped = function(e) { 130 | if (swiper.scanning) { 131 | e.preventDefault(); 132 | var keyCode = e.keyCode; 133 | 134 | if (swipeStart === 0) { 135 | swipeStart = Date.now(); 136 | } 137 | //If more time has passed since the swipe started (swiper.delay), 138 | //reset the buffer. 139 | if ((Date.now() - swipeStart) > swiper.delay) { 140 | swipeStart = 0; 141 | buffer = []; 142 | } 143 | 144 | //If using a bypass key, set the buffer to dummy card info and 145 | //change the key to indicate line end 146 | if (swiper.enableBypass && keyCode === swiper.bypass) { 147 | buffer = DUMMY_CARD; 148 | keyCode = swiper.LINE_END; 149 | } 150 | 151 | //When the line end key is detected, trigger the swipe event 152 | //and reset the buffer 153 | if (keyCode === swiper.LINE_END) { 154 | //Check if the delay is valid 155 | if ((Date.now() - swipeStart) < swiper.delay) { 156 | var card = new swiper.Card(buffer.join('')); 157 | var event = document.createEvent('HTMLEvents'); 158 | event.initEvent(swiper.EVENT, true, true); 159 | 160 | if (focus) { 161 | $(focus).trigger(swiper.EVENT, card); 162 | } 163 | else { 164 | for (var i = 0; i < triggers.length; i++) { 165 | $(triggers[i]).trigger(swiper.EVENT, card); 166 | } 167 | } 168 | } 169 | swipeStart = 0; 170 | buffer = []; 171 | } 172 | else { 173 | //Add keypress character to buffer 174 | buffer.push(String.fromCharCode(e.keyCode)); 175 | } 176 | } 177 | }; 178 | 179 | /** 180 | * The Card object represents a card read by the card reader. 181 | *

182 | * A card is always returned by the card reader and card.isValid() should 183 | * be checked before using the card object. 184 | * @param {string} line card reader track data 185 | * @returns {undefined} 186 | */ 187 | swiper.Card = function(line) { 188 | 189 | //Protected this scope 190 | var self = this; 191 | 192 | //Card tracks (only track 1 and track 2 are supported) 193 | this.track1 = {}; 194 | this.track1.valid = false; 195 | this.track2 = {}; 196 | this.track2.valid = false; 197 | this.track3 = {}; 198 | this.track3.valid = false; 199 | this.line = line; 200 | 201 | /** 202 | * Indicates if the card has valid track data. This method should be 203 | * called before using the card. 204 | *

205 | * Note that this method only indicates if the card contains 206 | * properly formatted track data. It does not indicate the validity of 207 | * the card number, expiration date, or any information contained on 208 | * the card. 209 | * @returns {boolean} true if the track data is valid, or false if not 210 | */ 211 | this.isValid = function() { 212 | return self.track1.valid || self.track2.valid; 213 | }; 214 | /** 215 | * Retrieves the primary account number for the card, or -1 if the 216 | * card is not valid. 217 | * @returns {number} primary account number, usually 16 digits long 218 | */ 219 | this.getNumber = function() { 220 | if (self.track1.valid) { 221 | return self.track1.number; 222 | } 223 | else if (self.track2.valid) { 224 | return self.track2.number; 225 | } 226 | return -1; 227 | }; 228 | /** 229 | * Retrieves the last four digits of the primary account number, or 230 | * -1 if the card is not valid. 231 | * @returns {number} last 4 digits of the primary account number 232 | */ 233 | this.getLast4 = function() { 234 | var numString = self.getNumber().toString(); 235 | if (numString.length > 4) { 236 | return parseInt(numString.substring(numString.length - 4)); 237 | } 238 | return -1; 239 | }; 240 | /** 241 | * Retrieves the expiration month (1-12) of the card, or -1 if the 242 | * card is invalid. 243 | *

244 | * Note that this method returns a number, so there is no formatting 245 | * for single digit months (1 is returned instead of 01 for January). 246 | * @returns {number} expiration month 247 | */ 248 | this.getExpMonth = function() { 249 | if (self.track1.valid) { 250 | return self.track1.expMonth; 251 | } 252 | else if (self.track2.valid) { 253 | return self.track2.expMonth; 254 | } 255 | return -1; 256 | }; 257 | /** 258 | * Retrieves the expiration year of the card, or -1 259 | * if the card is invalid. 260 | *

261 | * Note that this method returns the four digit year, not that last 262 | * two digits retrieved from the card. The last two digits can be 263 | * retrieved by subtracting 2000 from this value. 264 | * @returns {number} four digit expiration year 265 | */ 266 | this.getExpYear = function() { 267 | if (self.track1.valid) { 268 | return self.track1.expYear; 269 | } 270 | else if (self.track2.valid) { 271 | return self.track2.expYear; 272 | } 273 | return -1; 274 | }; 275 | /** 276 | * Retrieves the last name on the card, or an empty String if the 277 | * card is invalid. 278 | *

279 | * Note that most all card name data is uppercase and will need 280 | * formatting if this is a problem. 281 | * @returns {string} last name 282 | */ 283 | this.getLastName = function() { 284 | if (self.track1.valid) { 285 | return self.track1.nameParts[0]; 286 | } 287 | return ''; 288 | }; 289 | /** 290 | * Retrieves the first name on the card, or an empty String if the 291 | * card is invalid. 292 | *

293 | * Note that most all card name data is uppercase and will need 294 | * formatting if this is a problem. 295 | * @returns {string} first name 296 | */ 297 | this.getFirstName = function() { 298 | if (self.track1.valid) { 299 | return self.track1.nameParts[1]; 300 | } 301 | return ''; 302 | }; 303 | 304 | /** 305 | * Parses line data from a card reader into this Card object. 306 | * @param {string} line line data from card reader 307 | * @returns {Card} this card the line was parsed into 308 | */ 309 | this.parse = function(line) { 310 | var tracks = line.match(TRACK_REGEX); 311 | self.track1.data = tracks[1]; 312 | self.track2.data = tracks[2]; 313 | self.track3.data = tracks[3]; 314 | 315 | if (self.track1.data) { 316 | var t1 = self.track1.data.match(TRACK_1_REGEX); 317 | if (t1 !== null && t1.length === 7) { 318 | self.track1.valid = true; 319 | self.track1.format = t1[1]; 320 | self.track1.number = parseFloat(t1[2]); 321 | self.track1.name = t1[3]; 322 | self.track1.nameParts = self.track1.name.split('/'); 323 | self.track1.exp = parseInt(t1[4]); 324 | self.track1.expYear = parseInt('20' + t1[4].substring(0, 2)); 325 | self.track1.expMonth = parseInt(t1[4].substring(3)); 326 | self.track1.service = parseInt(t1[5]); 327 | self.track1.discretionary = t1[6]; 328 | } 329 | } 330 | if (self.track2.data) { 331 | var t2 = self.track2.data.match(TRACK_2_REGEX); 332 | if (t2 !== null && t2.length === 5) { 333 | self.track2.valid = true; 334 | self.track2.number = parseFloat(t2[1]); 335 | self.track2.exp = parseInt(t2[2]); 336 | self.track2.expYear = parseInt('20' + t2[2].substring(0, 2)); 337 | self.track2.expMonth = parseInt(t2[2].substring(3)); 338 | self.track2.service = parseInt(t2[3]); 339 | self.track2.discretionary = t2[4]; 340 | } 341 | } 342 | return self; 343 | }; 344 | 345 | if (line) { 346 | //Parse the card if line data is provided 347 | self.parse(line); 348 | } 349 | }; 350 | 351 | /** 352 | * Generates a unique hash number for the provided card. This number 353 | * may be negative. 354 | *

355 | * The hash can be used to uniquely identify a card across applications, 356 | * though it is not a substitute for a third-party card hash/fingerprint. 357 | * If the library being used to charge cards has a built-in hash/fingerprint 358 | * system, that should be used instead. 359 | * @param {Card} card the card to generate a hash for 360 | * @returns {number} unique hash number (may be negative) 361 | */ 362 | swiper.generateCardHash = function(card) { 363 | if (card && card.line) { 364 | var s = card.line; 365 | var hash = 0; 366 | for (var i = 0; i < s.length; i++) { 367 | var c = s.charCodeAt(i); 368 | hash = ((hash << 5) - hash) + c; 369 | hash = hash & hash; 370 | } 371 | return hash; 372 | } 373 | return 0; 374 | }; 375 | })(window, document, jQuery, Date); -------------------------------------------------------------------------------- /scripts/objects/movie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A Movie object defines an individual movie that can be 3 | * attached to Showings. 4 | *

5 | * Each Movie is distinguished by an ID and by the movie title. 6 | * Both fields should be unique. 7 | * @constructor 8 | * @returns {Movie} 9 | */ 10 | function Movie() { 11 | 12 | /** 13 | * This Movie 14 | * @type {Movie} 15 | */ 16 | var self = this; 17 | 18 | /** 19 | * The ID of the movie, must be unique, -1 by default 20 | * @type {number} 21 | */ 22 | this.id = -1; 23 | /** 24 | * The title of the movie, must be unique, blank by default 25 | * @type {string} 26 | */ 27 | this.title = ''; 28 | /** 29 | * The MPAA rating of the movie 30 | * @type {string} 31 | */ 32 | this.rating = ''; 33 | /** 34 | * The runtime, usually formatted as XXmin 35 | * @type {string} 36 | */ 37 | this.runtime = ''; 38 | /** 39 | * A link to the movie poster to be used in a 'src' attribute 40 | * @type {string} 41 | */ 42 | this.poster = 'images/loading.gif'; 43 | /** 44 | * A link to a blurred out version of the movie poster, to be used in a 45 | * 'src' attribute 46 | * @type {string} 47 | */ 48 | this.posterBlur = this.poster; 49 | /** 50 | * A short synopsis of the movie's plot 51 | * @type {string} 52 | */ 53 | this.synopsis = ''; 54 | /** 55 | * An array of Strings, no larger than 7, of the movie cast members 56 | * @type {Array.} 57 | */ 58 | this.cast = []; 59 | /** 60 | * An array of Strings, no larger than 7, of the movie directors 61 | * @type {Array.} 62 | */ 63 | this.directors = []; 64 | 65 | /** 66 | * Sets the provided tag's movie data related children to this movie's 67 | * information. 68 | *

69 | * The provided element's children should implement any number of the 70 | * following tags to retrieve movie data: 71 | *

    72 | *
  • movie-title
  • 73 | *
  • movie-rating
  • 74 | *
  • movie-runtime
  • 75 | *
  • movie-poster
  • 76 | *
  • movie-synopsis
  • 77 | *
  • movie-cast
  • 78 | *
  • movie-directors
  • 79 | *
80 | * It is recommended that all tags are div tags, with the exception of the 81 | * movie-poster class, which should be on an img tag. 82 | *

83 | * If the movie-poster tag is not an img tag, the background-image property 84 | * of the tag will be set to the movie poster. 85 | * @param {!jQuery|string} selector the jQuery selector or a string 86 | * selector to set movie data to 87 | * @returns {jQuery} the jQuery movie data selector 88 | */ 89 | this.setMovieData = function(selector) { 90 | var s = $(selector); 91 | s.find('.movie-title').html(self.title); 92 | s.find('.movie-rating').html('Rating: ' + self.rating + ''); 93 | s.find('.movie-runtime').html(self.runtime); 94 | 95 | s.find('.movie-poster').attr('src', self.poster); 96 | s.find('.movie-poster').css('background-image', 'url("' + self.poster + '")'); 97 | s.find('.movie-poster.blur').attr('src', self.posterBlur); 98 | s.find('.movie-poster.blur').css('background-image', 'url("' + self.posterBlur + '")'); 99 | 100 | s.find('.movie-synopsis').html(self.synopsis); 101 | var cast = []; 102 | var castLimit = (self.cast.length > 7 ) ? 7 : self.cast.length; 103 | for (var i = 0; i < castLimit; i++) { 104 | cast.push('

'); 105 | cast.push(self.cast[i]); 106 | cast.push('
'); 107 | } 108 | s.find('.movie-cast').html(cast.join('')); 109 | var directors = []; 110 | var directorsLimit = (self.directors.length > 7 ) ? 7 : self.directors.length; 111 | for (var i = 0; i < directorsLimit; i++) { 112 | directors.push('
'); 113 | directors.push(self.directors[i]); 114 | directors.push('
'); 115 | } 116 | s.find('movie-directors').html(directors.join('')); 117 | return selector; 118 | }; 119 | 120 | /** 121 | * Creates and returns a jQuery div tag for this Movie that 122 | * is used in a carousel of several movies. 123 | *

124 | * This div tag contains the movie poster and the movie's rating. 125 | * @returns {jQuery} jQuery div tag for movie carousel 126 | */ 127 | this.getCarouselDiv = function() { 128 | var div = $('

').addClass('movie').attr('data-id', self.id); 129 | div.append($('').addClass('movie-poster').attr('src', self.poster)); 130 | var movieData = $('
').addClass('movie-data'); 131 | movieData.append($('
').addClass('movie-rating')); 132 | self.setMovieData(movieData); 133 | div.append(movieData); 134 | 135 | return div; 136 | }; 137 | } -------------------------------------------------------------------------------- /scripts/objects/pricing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The Pricing class is used to group several Ticket 3 | * objects under one price name scheme. 4 | *

5 | * For example, if there are four Tickets with a single 6 | * TicketType called "Standard", those four Tickets 7 | * would be added to a single Pricing named "Standard". 8 | *

9 | * The Pricing class is used by an individual Theater 10 | * to denote the price scheme of that theater. 11 | * @constructor 12 | * @param {number} id the ID of the pricing scheme, must be unique 13 | * @param {string} name the name of the pricing scheme, must be unique 14 | * @returns {Pricing} 15 | */ 16 | function Pricing(id, name) { 17 | 18 | /** 19 | * The ID of this Pricing scheme, default -1 20 | * @type {number} 21 | */ 22 | this.id = isNaN(id) ? -1 : id; 23 | /** 24 | * The name of this Pricing scheme, default blank 25 | * @type {string} 26 | */ 27 | this.name = typeof name === 'string' ? name : ''; 28 | /** 29 | * The Tickets associated with this Pricing scheme 30 | * @type {Array.} 31 | */ 32 | this.tickets = []; 33 | } -------------------------------------------------------------------------------- /scripts/objects/receipt.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The Receipt class represents a receipt of selected 3 | * Tickets for a specific Showing. It also contains 4 | * payment information. 5 | * @constructor 6 | * @param {Showing} showing the selected Showing 7 | * @returns {Receipt} 8 | */ 9 | function Receipt(showing) { 10 | 11 | /** 12 | * This Receipt 13 | * @type {Receipt} 14 | */ 15 | var self = this; 16 | /** 17 | * The ID of this Receipt, defaults to a random XX-XXX format, where X is 18 | * any numerical digit. 19 | * @type {string} 20 | */ 21 | this.id = (Math.floor(Math.random() * 89) + 10).toString() + '-' + (Math.floor(Math.random() * 899) + 100).toString(); 22 | /** 23 | * The selected Showing for this Receipt 24 | * @type {Showing} 25 | */ 26 | this.showing = typeof showing === 'undefined' ? new Showing() : showing; 27 | /** 28 | * The Tickets selected for purchase 29 | * @type {Array.} 30 | */ 31 | this.tickets = []; 32 | /** 33 | * The type of payment, usually 'Credit', 'Cash', or 'Gift' 34 | * @type {string} 35 | */ 36 | this.paymentType = ''; 37 | /** 38 | * An optional Object representing more details of the payment type. 39 | * @type {Object} 40 | */ 41 | this.paymentObject = {}; 42 | /** 43 | * A short description of payment information that should be displayed. 44 | * For example, this could be the last four digits of the card charged, 45 | * or change due. 46 | * @type {string} 47 | */ 48 | this.paymentTypeInfo = ''; 49 | 50 | /** 51 | * Retrieves an Array of Ticket objects with the "quantity" property added 52 | * to each Ticket object, indicating the number of Tickets selected. 53 | *

54 | * By default, the Receipt's tickets Array can contain multiples of each 55 | * Ticket. This function will condense those duplicates and return the 56 | * results. 57 | * @returns {Array.} 58 | */ 59 | this.getTicketsWithQuantity = function() { 60 | var ticketQuantities = []; 61 | for (var i = 0; i < self.tickets.length; i++) { 62 | var found = -1; 63 | for (var j = 0; j < ticketQuantities.length; j++) { 64 | if (ticketQuantities[j].ticketType.name === self.tickets[i].ticketType.name) { 65 | found = j; 66 | break; 67 | } 68 | } 69 | if (found > -1) { 70 | ticketQuantities[j].quantity += 1; 71 | } 72 | else { 73 | self.tickets[i].quantity = 1; 74 | ticketQuantities.push(self.tickets[i]); 75 | } 76 | } 77 | 78 | return ticketQuantities; 79 | }; 80 | /** 81 | * Creates an object containing the minimum receipt information to 82 | * recreate tickets for printing. This object is used to store the 83 | * receipt externally. 84 | * @returns {Object} 85 | */ 86 | this.createStorageObject = function() { 87 | var store = {}; 88 | 89 | store.id = self.id; 90 | store.theater = self.showing.theater.name; 91 | store.movie = self.showing.movie.title; 92 | store.rating = self.showing.movie.rating; 93 | store.time = self.showing.time; 94 | store.date = self.showing.date; 95 | store.stubs = []; 96 | 97 | for (var i = 0; i < self.tickets.length; i++) { 98 | var stub = {}; 99 | stub.type = self.tickets[i].ticketType.name; 100 | stub.price = self.tickets[i].price; 101 | store.stubs.push(stub); 102 | } 103 | 104 | store.cardHash = swiper.generateCardHash(self.paymentObject); 105 | 106 | return store; 107 | }; 108 | } -------------------------------------------------------------------------------- /scripts/objects/session.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The Session class represents the current user's session. 3 | *

4 | * A Session contains only two properties, the current selected Showing, and 5 | * the current Receipt. 6 | * @returns {Session} 7 | */ 8 | function Session() { 9 | 10 | /** 11 | * The user's currently selected Showing 12 | * @type {Showing} 13 | */ 14 | this.showing = new Showing(); 15 | /** 16 | * The user's current Receipt 17 | * @type {Receipt} 18 | */ 19 | this.receipt = new Receipt(this.showing); 20 | } -------------------------------------------------------------------------------- /scripts/objects/showing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The Showing class represents a showtime for a specific Movie in a specific 3 | * Theater. 4 | * @param {number} id the ID of this Showing 5 | * @param {Movie} movie the Movie this Showing is for 6 | * @param {string} date a string description of the date of the Showing 7 | * @param {string} time a string description of the time of the Showing 8 | * @param {Theater} theater the Theater the Showing is in 9 | * @returns {Showing} 10 | */ 11 | function Showing(id, movie, date, time, theater) { 12 | 13 | /** 14 | * This Showing 15 | * @type {Showing} 16 | */ 17 | var self = this; 18 | 19 | /** 20 | * The ID of this Showing, defaults to -1 21 | * @type {number} 22 | */ 23 | this.id = isNaN(id) ? -1 : id; 24 | /** 25 | * The Movie for this Showing, defaults to a new Movie object 26 | * @type {Movie} 27 | */ 28 | this.movie = typeof movie === 'undefined' ? new Movie() : movie; 29 | /** 30 | * The date of this Showing as a string, usually MMM d. For example, Jan 1 31 | * @type {string} 32 | */ 33 | this.date = typeof date === 'string' ? date : ''; 34 | /** 35 | * The time of this Showing as a string, usually hh:mm. For example, 11:30am 36 | * @type {string} 37 | */ 38 | this.time = typeof time === 'string' ? time : ''; 39 | /** 40 | * The Theater this Showing is located in, defaults to a new Theater object 41 | * @type {Theater} 42 | */ 43 | this.theater = typeof theater === 'undefined' ? new Theater() : theater; 44 | 45 | /** 46 | * Sets the provided tag's showing data related children to this Showing's 47 | * information. 48 | *

49 | * The provided element's children should implement any number of the 50 | * following tags to retrieve showing data: 51 | *

    52 | *
  • showing-date
  • 53 | *
  • showing-time
  • 54 | *
  • showing-type
  • 55 | *
56 | * It is recommended that all tags are div tags. 57 | * @param {jQuery|string} selector the jQuery selector or a string 58 | * selector to set showing data to 59 | * @returns {jQuery} the jQuery showing data selector 60 | */ 61 | this.setShowingData = function(selector) { 62 | var s = $(selector); 63 | s.find('.showing-date').html(self.date); 64 | s.find('.showing-time').html(self.time); 65 | s.find('.showing-type').html(self.theater.pricing.name); 66 | return s; 67 | }; 68 | 69 | /** 70 | * Retrieves a button tag that contains this Showing's ID as a 71 | * "data-id" attribute, along with the Showing's time and type. 72 | * @returns {jQuery} the jQuery button tag 73 | */ 74 | this.getShowingButton = function() { 75 | var button = $(' 61 | 64 |
65 |
66 | 67 |
68 |
Now Playing...
69 |
70 | 71 |
72 |
73 | 74 |
75 |
76 | 77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | 87 |
Rating: 88 |
89 |
90 |
91 |
92 |
Synopsis
93 |
94 |
95 |
96 |
97 |
Cast
98 |
99 |
100 |
101 | 102 | 103 |
104 |
105 |
106 |
107 |
Select a Showing
108 |
109 |
110 |
111 |
112 |
113 |
114 | 115 | 116 |
117 |
118 |
119 | 120 |
121 |
Select Your Tickets
122 |
123 |
124 |
125 | 126 |
Rating: 127 |
128 |
129 |
130 |
131 |
() 132 |
133 |
134 |
135 |
Add a different type of ticket
136 |
137 |
138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 |
Subtotal:$
Tax:$0.00
Total:$
155 |
156 |
157 | 158 |
159 |
160 |
161 |
162 |
163 | 164 | 165 |
166 |
167 | 168 |
169 |
Purchase Tickets
170 |
171 |
172 |
Purchase Summary
173 |
174 |
175 |
Select a payment method
176 |
177 | 178 | 179 | 180 |
181 |
182 |
183 |
184 |
185 |
Please Insert Cash
186 |
187 |
188 |
189 |
190 |
Please Swipe Your Credit Card
191 |
192 |
193 |
194 |
195 |
Please Swipe Your Gift Card
196 |
197 |
198 |
199 |
200 |
201 |
202 | 203 | 204 | 205 | 206 |
207 |
208 | 209 |
210 |
Purchase Tickets
211 |
212 |
213 |
Purchase Summary
214 |
215 |
216 |
217 |
Payment Type:
218 |
219 |
220 |
221 |
222 | 223 |
224 |
225 |
226 | 227 |
228 |
229 | 230 | 260 | 261 |
262 |
Print Tickets
263 |
264 |
265 |

266 | Confirm that the tickets below are correct, then select the “Print Tickets” button to continue. 267 |
If the tickets below are not correct, please select the “Cancel” button and alert an attendant at the manned Box Office. 268 |

269 |
270 |
271 |
272 |
273 | 274 | 275 |
276 |
277 | 278 |
279 |
Collect Your Tickets
280 |
281 |
282 |

Please retrieve your tickets from the ticket dispenser below.

283 |

Thanks for choosing , and enjoy your movie!

284 |
285 |
286 |
287 | 288 |
289 |
290 |
291 | 292 | 293 | 294 | --------------------------------------------------------------------------------