├── .gitignore ├── LICENSE ├── app.js ├── demo ├── api │ └── data.json ├── css │ ├── fonts │ │ └── roboto.woff │ └── style.css ├── index.html ├── js │ ├── arrivals.js │ ├── main.js │ ├── page.js │ └── vendor │ │ └── knockout-3.3.0.js ├── launcher-icon-144.png ├── launcher-icon-192.png ├── launcher-icon-256.png ├── launcher-icon-96.png ├── launcher-icon.png ├── manifest.json ├── offline.html └── sw.js ├── grunt ├── aliases.js ├── uglify.js └── watch.js ├── gruntfile.js ├── package.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | ## TODO: Comment the next line if you want to checkin your 137 | ## web deploy settings but do note that will include unencrypted 138 | ## passwords 139 | #*.pubxml 140 | 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt 208 | 209 | # LightSwitch generated files 210 | GeneratedArtifacts/ 211 | _Pvt_Extensions/ 212 | ModelManifest.xml 213 | 214 | # Custom project files 215 | js/build/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Incredible Web 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | app.use(express.static('demo')); 5 | 6 | app.listen(3000, function() { 7 | console.log('Demo is running on localhost:3000'); 8 | }); -------------------------------------------------------------------------------- /demo/api/data.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "title": "Malta to Amsterdam", 3 | "status": "On time", 4 | "time": "08:45" 5 | }, { 6 | "title": "Malta to London", 7 | "status": "Delayed", 8 | "time": "09:45" 9 | }, { 10 | "title": "Malta to Poznan", 11 | "status": "On time", 12 | "time": "10:00" 13 | }, { 14 | "title": "Malta to London", 15 | "status": "On time", 16 | "time": "11:45" 17 | }, { 18 | "title": "Malta to Rome", 19 | "status": "On time", 20 | "time": "12:05" 21 | }, { 22 | "title": "Malta to Paris", 23 | "status": "Delayed", 24 | "time": "12:35" 25 | }] -------------------------------------------------------------------------------- /demo/css/fonts/roboto.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IncredibleWeb/pwa-tutorial/6e0c5f458529c2c6682f1d9767a00a724dd1fe10/demo/css/fonts/roboto.woff -------------------------------------------------------------------------------- /demo/css/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Roboto Regular'), local('Roboto-Regular'), url('./fonts/roboto.woff') format('woff'); 6 | } 7 | 8 | html { 9 | font-family: sans-serif; 10 | -ms-text-size-adjust: 100%; 11 | -webkit-text-size-adjust: 100%; 12 | } 13 | 14 | body { 15 | margin: 0; 16 | } 17 | 18 | article, 19 | aside, 20 | details, 21 | figcaption, 22 | figure, 23 | footer, 24 | header, 25 | hgroup, 26 | main, 27 | menu, 28 | nav, 29 | section, 30 | summary { 31 | display: block; 32 | } 33 | 34 | audio, 35 | canvas, 36 | progress, 37 | video { 38 | display: inline-block; 39 | vertical-align: baseline; 40 | } 41 | 42 | audio:not([controls]) { 43 | display: none; 44 | height: 0; 45 | } 46 | 47 | [hidden], 48 | template { 49 | display: none; 50 | } 51 | 52 | a { 53 | background-color: transparent; 54 | } 55 | 56 | a:active, 57 | a:hover { 58 | outline: 0; 59 | } 60 | 61 | abbr[title] { 62 | border-bottom: 1px dotted; 63 | } 64 | 65 | b, 66 | strong { 67 | font-weight: bold; 68 | } 69 | 70 | dfn { 71 | font-style: italic; 72 | } 73 | 74 | h1 { 75 | font-size: 2em; 76 | margin: 0.67em 0; 77 | } 78 | 79 | mark { 80 | background: #ff0; 81 | color: #000; 82 | } 83 | 84 | small { 85 | font-size: 80%; 86 | } 87 | 88 | sub, 89 | sup { 90 | font-size: 75%; 91 | line-height: 0; 92 | position: relative; 93 | vertical-align: baseline; 94 | } 95 | 96 | sup { 97 | top: -0.5em; 98 | } 99 | 100 | sub { 101 | bottom: -0.25em; 102 | } 103 | 104 | img { 105 | border: 0; 106 | } 107 | 108 | svg:not(:root) { 109 | overflow: hidden; 110 | } 111 | 112 | figure { 113 | margin: 1em 40px; 114 | } 115 | 116 | hr { 117 | -moz-box-sizing: content-box; 118 | box-sizing: content-box; 119 | height: 0; 120 | } 121 | 122 | pre { 123 | overflow: auto; 124 | } 125 | 126 | code, 127 | kbd, 128 | pre, 129 | samp { 130 | font-family: monospace, monospace; 131 | font-size: 1em; 132 | } 133 | 134 | button, 135 | input, 136 | input[type=submit], 137 | .button, 138 | .button:visited, 139 | optgroup, 140 | select, 141 | textarea { 142 | color: inherit; 143 | font: inherit; 144 | margin: 0; 145 | } 146 | 147 | button { 148 | overflow: visible; 149 | } 150 | 151 | button, 152 | select { 153 | text-transform: none; 154 | } 155 | 156 | button, 157 | html input[type="button"], 158 | html [type="button"].button, 159 | input[type="reset"], 160 | [type="reset"].button, 161 | input[type="submit"], 162 | [type="submit"].button { 163 | -webkit-appearance: button; 164 | cursor: pointer; 165 | } 166 | 167 | button[disabled], 168 | html input[disabled], 169 | html [disabled].button { 170 | cursor: default; 171 | } 172 | 173 | button::-moz-focus-inner, 174 | input::-moz-focus-inner, 175 | input[type=submit]::-moz-focus-inner, 176 | .button::-moz-focus-inner, 177 | .button:visited::-moz-focus-inner { 178 | border: 0; 179 | padding: 0; 180 | } 181 | 182 | input, 183 | input[type=submit], 184 | .button, 185 | .button:visited { 186 | line-height: normal; 187 | } 188 | 189 | input[type="checkbox"], 190 | [type="checkbox"].button, 191 | input[type="radio"], 192 | [type="radio"].button { 193 | box-sizing: border-box; 194 | padding: 0; 195 | } 196 | 197 | input[type="number"]::-webkit-inner-spin-button, 198 | [type="number"].button::-webkit-inner-spin-button, 199 | input[type="number"]::-webkit-outer-spin-button, 200 | [type="number"].button::-webkit-outer-spin-button { 201 | height: auto; 202 | } 203 | 204 | input[type="search"], 205 | [type="search"].button { 206 | -webkit-appearance: textfield; 207 | /* 1 */ 208 | -moz-box-sizing: content-box; 209 | -webkit-box-sizing: content-box; 210 | /* 2 */ 211 | box-sizing: content-box; 212 | } 213 | 214 | input[type="search"]::-webkit-search-cancel-button, 215 | [type="search"].button::-webkit-search-cancel-button, 216 | input[type="search"]::-webkit-search-decoration, 217 | [type="search"].button::-webkit-search-decoration { 218 | -webkit-appearance: none; 219 | } 220 | 221 | fieldset { 222 | border: 1px solid #c0c0c0; 223 | margin: 0 2px; 224 | padding: 0.35em 0.625em 0.75em; 225 | } 226 | 227 | legend { 228 | border: 0; 229 | /* 1 */ 230 | padding: 0; 231 | /* 2 */ 232 | } 233 | 234 | textarea { 235 | overflow: auto; 236 | } 237 | 238 | optgroup { 239 | font-weight: bold; 240 | } 241 | 242 | table { 243 | border-collapse: collapse; 244 | border-spacing: 0; 245 | } 246 | 247 | td, 248 | th { 249 | padding: 0; 250 | } 251 | 252 | 253 | /* Keyframes */ 254 | 255 | @-moz-keyframes ripple-animation { 256 | 0% { 257 | -moz-transform: scale(1); 258 | transform: scale(1); 259 | opacity: 0.4; 260 | } 261 | 100% { 262 | -moz-transform: scale(100); 263 | transform: scale(100); 264 | opacity: 0; 265 | } 266 | } 267 | 268 | @-webkit-keyframes ripple-animation { 269 | 0% { 270 | -webkit-transform: scale(1); 271 | transform: scale(1); 272 | opacity: 0.4; 273 | } 274 | 100% { 275 | -webkit-transform: scale(100); 276 | transform: scale(100); 277 | opacity: 0; 278 | } 279 | } 280 | 281 | @keyframes ripple-animation { 282 | 0% { 283 | -moz-transform: scale(1); 284 | -ms-transform: scale(1); 285 | -webkit-transform: scale(1); 286 | transform: scale(1); 287 | opacity: 0.4; 288 | } 289 | 100% { 290 | -moz-transform: scale(100); 291 | -ms-transform: scale(100); 292 | -webkit-transform: scale(100); 293 | transform: scale(100); 294 | opacity: 0; 295 | } 296 | } 297 | 298 | html { 299 | background-color: #eee; 300 | } 301 | 302 | body, 303 | select, 304 | input, 305 | input[type=submit], 306 | .button, 307 | .button:visited, 308 | textarea { 309 | color: #000; 310 | color: rgba(0, 0, 0, 0.7); 311 | font-family: "Roboto", Arial, sans-serif; 312 | font-weight: 300; 313 | line-height: 1.5em; 314 | font-size: 16px; 315 | -webkit-font-smoothing: antialiased; 316 | -moz-osx-font-smoothing: grayscale; 317 | } 318 | 319 | * *, 320 | * *:before, 321 | * *:after { 322 | -moz-box-sizing: border-box; 323 | -webkit-box-sizing: border-box; 324 | box-sizing: border-box; 325 | } 326 | 327 | body { 328 | background-color: #FDFFFD; 329 | } 330 | 331 | ul, 332 | ol { 333 | padding: 0; 334 | } 335 | 336 | p { 337 | margin: 0; 338 | line-height: 1.5em; 339 | } 340 | 341 | .content { 342 | max-width: 72em; 343 | margin-left: auto; 344 | margin-right: auto; 345 | } 346 | 347 | .container { 348 | min-height: 100vh; 349 | display: flex; 350 | flex-direction: column; 351 | } 352 | 353 | header { 354 | position: fixed; 355 | top: 0; 356 | left: 0; 357 | right: 0; 358 | padding: 0 0.5em; 359 | background-color: #29BDBB; 360 | height: 3em; 361 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); 362 | line-height: 3em; 363 | color: #fff; 364 | transform: translateZ(0); 365 | backface-visibility: hidden; 366 | } 367 | 368 | header h3 { 369 | margin: 0; 370 | } 371 | 372 | .arrivals-list { 373 | margin: 0; 374 | list-style: none; 375 | display: flex; 376 | flex-direction: column; 377 | transition: opacity 0.2s ease-in-out; 378 | padding: 0 0.5em; 379 | } 380 | 381 | .arrivals-list.loading { 382 | opacity: 0.2; 383 | } 384 | 385 | #main { 386 | margin-top: 3em; 387 | width: 100%; 388 | position: relative; 389 | } 390 | 391 | .item { 392 | border-radius: 4px; 393 | margin: 0.5em auto; 394 | padding: 1em; 395 | width: 100%; 396 | background-color: #fff; 397 | text-align: left; 398 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); 399 | } 400 | 401 | .item .title, 402 | .item .status, 403 | .item .time { 404 | display: block; 405 | } 406 | 407 | .item .title { 408 | font-weight: bold; 409 | } 410 | 411 | .item .status { 412 | float: right; 413 | text-transform: uppercase; 414 | } 415 | 416 | #offline { 417 | background: #f00; 418 | color: #fff; 419 | padding: 1em; 420 | text-align: center; 421 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); 422 | position: absolute; 423 | left: 0; 424 | right: 0; 425 | top: 0; 426 | } 427 | 428 | .clearfix:before, 429 | .clearfix:after { 430 | content: " "; 431 | display: table; 432 | } 433 | 434 | .clearfix:after { 435 | clear: both; 436 | } 437 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Sky-High Airport Arrivals 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Arrivals

19 |
20 |
21 |
22 |
23 | 30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /demo/js/arrivals.js: -------------------------------------------------------------------------------- 1 | var Arrivals = (function() { 2 | function ArrivalViewModel() { 3 | var self = this; 4 | self.title = ""; 5 | self.status = ""; 6 | self.time = ""; 7 | } 8 | 9 | function ArrivalApiService() { 10 | var self = this; 11 | 12 | // retrieves all arrivals from the API 13 | self.getAll = function() { 14 | return new Promise(function(resolve, reject) { 15 | var request = new XMLHttpRequest(); 16 | request.open('GET', './api/data.json'); 17 | 18 | request.onload = function() { 19 | // success 20 | if (request.status === 200) { 21 | // resolve the promise with the parsed response text (assumes JSON) 22 | resolve(JSON.parse(request.response)); 23 | } else { 24 | // error retrieving file 25 | reject(Error(request.statusText)); 26 | } 27 | }; 28 | 29 | request.onerror = function() { 30 | // network errors 31 | reject(Error("Network Error")); 32 | }; 33 | 34 | request.send(); 35 | }); 36 | }; 37 | } 38 | 39 | function ArrivalAdapter() { 40 | var self = this; 41 | 42 | self.toArrivalViewModel = function(data) { 43 | if (data) { 44 | var vm = new ArrivalViewModel(); 45 | vm.title = data.title; 46 | vm.status = data.status; 47 | vm.time = data.time; 48 | return vm; 49 | } 50 | return null; 51 | }; 52 | 53 | self.toArrivalViewModels = function(data) { 54 | if (data && data.length > 0) { 55 | return data.map(function(item) { 56 | return self.toArrivalViewModel(item); 57 | }); 58 | } 59 | return []; 60 | }; 61 | } 62 | 63 | function ArrivalController(arrivalApiService, arrivalAdapter) { 64 | var self = this; 65 | 66 | self.getAll = function() { 67 | // retrieve all the arrivals from the API 68 | return arrivalApiService.getAll().then(function(response) { 69 | return arrivalAdapter.toArrivalViewModels(response); 70 | }); 71 | }; 72 | } 73 | 74 | 75 | // initialize the services and adapters 76 | var arrivalApiService = new ArrivalApiService(); 77 | var arrivalAdapter = new ArrivalAdapter(); 78 | 79 | // initialize the controller 80 | var arrivalController = new ArrivalController(arrivalApiService, arrivalAdapter); 81 | 82 | return { 83 | loadData: function() { 84 | // retrieve all routes 85 | document.querySelector(".arrivals-list").classList.add('loading') 86 | arrivalController.getAll().then(function(response) { 87 | // bind the arrivals to the UI 88 | Page.vm.arrivals(response); 89 | document.querySelector(".arrivals-list").classList.remove('loading') 90 | }); 91 | } 92 | } 93 | 94 | })(); 95 | -------------------------------------------------------------------------------- /demo/js/main.js: -------------------------------------------------------------------------------- 1 | // register the service worker if available 2 | if ('serviceWorker' in navigator) { 3 | navigator.serviceWorker.register('./sw.js').then(function(reg) { 4 | console.log('Successfully registered service worker', reg); 5 | }).catch(function(err) { 6 | console.warn('Error whilst registering service worker', err); 7 | }); 8 | } 9 | 10 | window.addEventListener('online', function(e) { 11 | // re-sync data with server 12 | console.log("You are online"); 13 | Page.hideOfflineWarning(); 14 | Arrivals.loadData(); 15 | }, false); 16 | 17 | window.addEventListener('offline', function(e) { 18 | // queue up events for server 19 | console.log("You are offline"); 20 | Page.showOfflineWarning(); 21 | }, false); 22 | 23 | // check if the user is connected 24 | if (navigator.onLine) { 25 | Arrivals.loadData(); 26 | } else { 27 | // show offline message 28 | Page.showOfflineWarning(); 29 | } 30 | 31 | // set knockout view model bindings 32 | ko.applyBindings(Page.vm); 33 | -------------------------------------------------------------------------------- /demo/js/page.js: -------------------------------------------------------------------------------- 1 | var Page = (function() { 2 | 3 | // declare the view model used within the page 4 | function ViewModel() { 5 | var self = this; 6 | self.arrivals = ko.observableArray([]); 7 | } 8 | 9 | // expose the view model through the Page module 10 | return { 11 | vm: new ViewModel(), 12 | hideOfflineWarning: function() { 13 | // enable the live data 14 | document.querySelector(".arrivals-list").classList.remove('loading') 15 | // remove the offline message 16 | document.getElementById("offline").remove(); 17 | // load the live data 18 | }, 19 | showOfflineWarning: function() { 20 | // disable the live data 21 | document.querySelector(".arrivals-list").classList.add('loading') 22 | // load html template informing the user they are offline 23 | var request = new XMLHttpRequest(); 24 | request.open('GET', './offline.html', true); 25 | 26 | request.onload = function() { 27 | if (request.status === 200) { 28 | // success 29 | // create offline element with HTML loaded from offline.html template 30 | var offlineMessageElement = document.createElement("div"); 31 | offlineMessageElement.setAttribute("id", "offline"); 32 | offlineMessageElement.innerHTML = request.responseText; 33 | document.getElementById("main").appendChild(offlineMessageElement); 34 | } else { 35 | // error retrieving file 36 | console.warn('Error retrieving offline.html'); 37 | } 38 | }; 39 | 40 | request.onerror = function() { 41 | // network errors 42 | console.error('Connection error'); 43 | }; 44 | 45 | request.send(); 46 | } 47 | } 48 | 49 | })(); 50 | -------------------------------------------------------------------------------- /demo/js/vendor/knockout-3.3.0.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Knockout JavaScript library v3.3.0 3 | * (c) Steven Sanderson - http://knockoutjs.com/ 4 | * License: MIT (http://www.opensource.org/licenses/mit-license.php) 5 | */ 6 | 7 | (function() {(function(p){var y=this||(0,eval)("this"),w=y.document,M=y.navigator,u=y.jQuery,E=y.JSON;(function(p){"function"===typeof define&&define.amd?define(["exports","require"],p):"function"===typeof require&&"object"===typeof exports&&"object"===typeof module?p(module.exports||exports):p(y.ko={})})(function(N,O){function J(a,d){return null===a||typeof a in Q?a===d:!1}function R(a,d){var c;return function(){c||(c=setTimeout(function(){c=p;a()},d))}}function S(a,d){var c;return function(){clearTimeout(c); 8 | c=setTimeout(a,d)}}function K(b,d,c,e){a.d[b]={init:function(b,k,h,l,g){var m,x;a.w(function(){var q=a.a.c(k()),n=!c!==!q,r=!x;if(r||d||n!==m)r&&a.Z.oa()&&(x=a.a.la(a.e.childNodes(b),!0)),n?(r||a.e.T(b,a.a.la(x)),a.Ja(e?e(g,q):g,b)):a.e.ma(b),m=n},null,{q:b});return{controlsDescendantBindings:!0}}};a.h.ka[b]=!1;a.e.R[b]=!0}var a="undefined"!==typeof N?N:{};a.b=function(b,d){for(var c=b.split("."),e=a,f=0;fa.a.m(c,b[g])&&c.push(b[g]);return c},Ka:function(a,b){a=a||[];for(var c=[],g=0,d=a.length;gd?g&&b.push(c):g||b.splice(d,1)},za:f,extend:d,Fa:c,Ga:f?c:d,A:b,pa:function(a,b){if(!a)return a;var c={},g;for(g in a)a.hasOwnProperty(g)&&(c[g]=b(a[g],g,a));return c},Ra:function(b){for(;b.firstChild;)a.removeNode(b.firstChild)},Jb:function(b){b=a.a.O(b);for(var c=(b[0]&&b[0].ownerDocument||w).createElement("div"),g=0,d=b.length;g< 13 | d;g++)c.appendChild(a.S(b[g]));return c},la:function(b,c){for(var g=0,d=b.length,e=[];gg?a.setAttribute("selected",b):a.selected=b},ib:function(a){return null===a||a===p?"":a.trim?a.trim():a.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},Dc:function(a,b){a=a||"";return b.length>a.length?!1:a.substring(0,b.length)===b},jc:function(a,b){if(a===b)return!0;if(11===a.nodeType)return!1;if(b.contains)return b.contains(3===a.nodeType? 15 | a.parentNode:a);if(b.compareDocumentPosition)return 16==(b.compareDocumentPosition(a)&16);for(;a&&a!=b;)a=a.parentNode;return!!a},Qa:function(b){return a.a.jc(b,b.ownerDocument.documentElement)},tb:function(b){return!!a.a.vb(b,a.a.Qa)},v:function(a){return a&&a.tagName&&a.tagName.toLowerCase()},n:function(b,c,d){var m=g&&l[c];if(!m&&u)u(b).bind(c,d);else if(m||"function"!=typeof b.addEventListener)if("undefined"!=typeof b.attachEvent){var e=function(a){d.call(b,a)},f="on"+c;b.attachEvent(f,e);a.a.C.fa(b, 16 | function(){b.detachEvent(f,e)})}else throw Error("Browser doesn't support addEventListener or attachEvent");else b.addEventListener(c,d,!1)},qa:function(b,c){if(!b||!b.nodeType)throw Error("element must be a DOM node when calling triggerEvent");var g;"input"===a.a.v(b)&&b.type&&"click"==c.toLowerCase()?(g=b.type,g="checkbox"==g||"radio"==g):g=!1;if(u&&!g)u(b).trigger(c);else if("function"==typeof w.createEvent)if("function"==typeof b.dispatchEvent)g=w.createEvent(h[c]||"HTMLEvents"),g.initEvent(c, 17 | !0,!0,y,0,0,0,0,0,!1,!1,!1,!1,0,b),b.dispatchEvent(g);else throw Error("The supplied element doesn't support dispatchEvent");else if(g&&b.click)b.click();else if("undefined"!=typeof b.fireEvent)b.fireEvent("on"+c);else throw Error("Browser doesn't support triggering events");},c:function(b){return a.F(b)?b():b},cb:function(b){return a.F(b)?b.B():b},Ia:function(b,c,g){var d;c&&("object"===typeof b.classList?(d=b.classList[g?"add":"remove"],a.a.o(c.match(m),function(a){d.call(b.classList,a)})):"string"=== 18 | typeof b.className.baseVal?e(b.className,"baseVal",c,g):e(b,"className",c,g))},Ha:function(b,c){var g=a.a.c(c);if(null===g||g===p)g="";var d=a.e.firstChild(b);!d||3!=d.nodeType||a.e.nextSibling(d)?a.e.T(b,[b.ownerDocument.createTextNode(g)]):d.data=g;a.a.mc(b)},Rb:function(a,b){a.name=b;if(7>=g)try{a.mergeAttributes(w.createElement(""),!1)}catch(c){}},mc:function(a){9<=g&&(a=1==a.nodeType?a:a.parentNode,a.style&&(a.style.zoom=a.style.zoom))},kc:function(a){if(g){var b=a.style.width; 19 | a.style.width=0;a.style.width=b}},Bc:function(b,c){b=a.a.c(b);c=a.a.c(c);for(var g=[],d=b;d<=c;d++)g.push(d);return g},O:function(a){for(var b=[],c=0,g=a.length;c",""]||!f.indexOf("", 28 | ""]||(!f.indexOf("",""]||[0,"",""],k="ignored
"+f[1]+b+f[2]+"
";for("function"==typeof c.innerShiv?e.appendChild(c.innerShiv(k)):e.innerHTML=k;f[0]--;)e=e.lastChild;c=a.a.O(e.lastChild.childNodes)}return c};a.a.gb=function(b,d){a.a.Ra(b);d=a.a.c(d);if(null!==d&&d!==p)if("string"!=typeof d&&(d=d.toString()),u)u(b).html(d);else for(var c=a.a.ca(d,b.ownerDocument),e=0;ef[0]?r+f[0]:f[0]),r);for(var r=1===v?r:Math.min(d+(f[1]||0),r),v=d+v-2,t=Math.max(r,v),G=[],A=[],p=2;dc;c++)b=b();return b})};a.toJSON=function(b,c,d){b=a.Vb(b);return a.a.jb(b,c,d)};c.prototype={save:function(b,c){var d=a.a.m(this.keys,b);0<=d?this.mb[d]=c:(this.keys.push(b),this.mb.push(c))},get:function(b){b=a.a.m(this.keys,b);return 0<=b?this.mb[b]:p}}})();a.b("toJS",a.Vb);a.b("toJSON",a.toJSON);(function(){a.i={s:function(b){switch(a.a.v(b)){case "option":return!0===b.__ko__hasDomDataOptionValue__?a.a.f.get(b,a.d.options.ab):7>=a.a.M?b.getAttributeNode("value")&&b.getAttributeNode("value").specified? 51 | b.value:b.text:b.value;case "select":return 0<=b.selectedIndex?a.i.s(b.options[b.selectedIndex]):p;default:return b.value}},Y:function(b,d,c){switch(a.a.v(b)){case "option":switch(typeof d){case "string":a.a.f.set(b,a.d.options.ab,p);"__ko__hasDomDataOptionValue__"in b&&delete b.__ko__hasDomDataOptionValue__;b.value=d;break;default:a.a.f.set(b,a.d.options.ab,d),b.__ko__hasDomDataOptionValue__=!0,b.value="number"===typeof d?d:""}break;case "select":if(""===d||null===d)d=p;for(var e=-1,f=0,k=b.options.length, 52 | h;f=n){c.push(x&&h.length?{key:x, 53 | value:h.join("")}:{unknown:x||h.join("")});x=n=0;h=[];continue}}else if(58===t){if(!n&&!x&&1===h.length){x=h.pop();continue}}else 47===t&&r&&1a.a.M&&(a.g.register=function(a){return function(b){w.createElement(b); 78 | return a.apply(this,arguments)}}(a.g.register),w.createDocumentFragment=function(b){return function(){var d=b(),f=a.g.$b,k;for(k in f)f.hasOwnProperty(k)&&d.createElement(k);return d}}(w.createDocumentFragment))})();(function(b){function d(b,c,d){c=c.template;if(!c)throw Error("Component '"+b+"' has no template");b=a.a.la(c);a.e.T(d,b)}function c(a,b,c,d){var e=a.createViewModel;return e?e.call(a,d,{element:b,templateNodes:c}):d}var e=0;a.d.component={init:function(f,k,h,l,g){function m(){var a=x&& 79 | x.dispose;"function"===typeof a&&a.call(x);q=null}var x,q,n=a.a.O(a.e.childNodes(f));a.a.C.fa(f,m);a.w(function(){var l=a.a.c(k()),h,t;"string"===typeof l?h=l:(h=a.a.c(l.name),t=a.a.c(l.params));if(!h)throw Error("No component name specified");var p=q=++e;a.g.get(h,function(e){if(q===p){m();if(!e)throw Error("Unknown component '"+h+"'");d(h,e,f);var l=c(e,f,n,t);e=g.createChildContext(l,b,function(a){a.$component=l;a.$componentTemplateNodes=n});x=l;a.Ja(e,f)}})},null,{q:f});return{controlsDescendantBindings:!0}}}; 80 | a.e.R.component=!0})();var P={"class":"className","for":"htmlFor"};a.d.attr={update:function(b,d){var c=a.a.c(d())||{};a.a.A(c,function(c,d){d=a.a.c(d);var k=!1===d||null===d||d===p;k&&b.removeAttribute(c);8>=a.a.M&&c in P?(c=P[c],k?b.removeAttribute(c):b[c]=d):k||b.setAttribute(c,d.toString());"name"===c&&a.a.Rb(b,k?"":d.toString())})}};(function(){a.d.checked={after:["value","attr"],init:function(b,d,c){function e(){var e=b.checked,f=x?k():e;if(!a.Z.Ca()&&(!l||e)){var h=a.k.u(d);g?m!==f?(e&&(a.a.ga(h, 81 | f,!0),a.a.ga(h,m,!1)),m=f):a.a.ga(h,f,e):a.h.ra(h,c,"checked",f,!0)}}function f(){var c=a.a.c(d());b.checked=g?0<=a.a.m(c,k()):h?c:k()===c}var k=a.Nb(function(){return c.has("checkedValue")?a.a.c(c.get("checkedValue")):c.has("value")?a.a.c(c.get("value")):b.value}),h="checkbox"==b.type,l="radio"==b.type;if(h||l){var g=h&&a.a.c(d())instanceof Array,m=g?k():p,x=l||g;l&&!b.name&&a.d.uniqueName.init(b,function(){return!0});a.w(e,null,{q:b});a.a.n(b,"click",e);a.w(f,null,{q:b})}}};a.h.V.checked=!0;a.d.checkedValue= 82 | {update:function(b,d){b.value=a.a.c(d())}}})();a.d.css={update:function(b,d){var c=a.a.c(d());null!==c&&"object"==typeof c?a.a.A(c,function(c,d){d=a.a.c(d);a.a.Ia(b,c,d)}):(c=String(c||""),a.a.Ia(b,b.__ko__cssValue,!1),b.__ko__cssValue=c,a.a.Ia(b,c,!0))}};a.d.enable={update:function(b,d){var c=a.a.c(d());c&&b.disabled?b.removeAttribute("disabled"):c||b.disabled||(b.disabled=!0)}};a.d.disable={update:function(b,d){a.d.enable.update(b,function(){return!a.a.c(d())})}};a.d.event={init:function(b,d,c, 83 | e,f){var k=d()||{};a.a.A(k,function(h){"string"==typeof h&&a.a.n(b,h,function(b){var g,m=d()[h];if(m){try{var k=a.a.O(arguments);e=f.$data;k.unshift(e);g=m.apply(e,k)}finally{!0!==g&&(b.preventDefault?b.preventDefault():b.returnValue=!1)}!1===c.get(h+"Bubble")&&(b.cancelBubble=!0,b.stopPropagation&&b.stopPropagation())}})})}};a.d.foreach={Ib:function(b){return function(){var d=b(),c=a.a.cb(d);if(!c||"number"==typeof c.length)return{foreach:d,templateEngine:a.P.Va};a.a.c(d);return{foreach:c.data,as:c.as, 84 | includeDestroyed:c.includeDestroyed,afterAdd:c.afterAdd,beforeRemove:c.beforeRemove,afterRender:c.afterRender,beforeMove:c.beforeMove,afterMove:c.afterMove,templateEngine:a.P.Va}}},init:function(b,d){return a.d.template.init(b,a.d.foreach.Ib(d))},update:function(b,d,c,e,f){return a.d.template.update(b,a.d.foreach.Ib(d),c,e,f)}};a.h.ka.foreach=!1;a.e.R.foreach=!0;a.d.hasfocus={init:function(b,d,c){function e(e){b.__ko_hasfocusUpdating=!0;var f=b.ownerDocument;if("activeElement"in f){var g;try{g=f.activeElement}catch(m){g= 85 | f.body}e=g===b}f=d();a.h.ra(f,c,"hasfocus",e,!0);b.__ko_hasfocusLastValue=e;b.__ko_hasfocusUpdating=!1}var f=e.bind(null,!0),k=e.bind(null,!1);a.a.n(b,"focus",f);a.a.n(b,"focusin",f);a.a.n(b,"blur",k);a.a.n(b,"focusout",k)},update:function(b,d){var c=!!a.a.c(d());b.__ko_hasfocusUpdating||b.__ko_hasfocusLastValue===c||(c?b.focus():b.blur(),a.k.u(a.a.qa,null,[b,c?"focusin":"focusout"]))}};a.h.V.hasfocus=!0;a.d.hasFocus=a.d.hasfocus;a.h.V.hasFocus=!0;a.d.html={init:function(){return{controlsDescendantBindings:!0}}, 86 | update:function(b,d){a.a.gb(b,d())}};K("if");K("ifnot",!1,!0);K("with",!0,!1,function(a,d){return a.createChildContext(d)});var L={};a.d.options={init:function(b){if("select"!==a.a.v(b))throw Error("options binding applies only to SELECT elements");for(;0a.a.M)var k=a.a.f.I(),h=a.a.f.I(),l=function(b){var c=this.activeElement;(c=c&&a.a.f.get(c,h))&&c(b)},g=function(b,c){var d=b.ownerDocument;a.a.f.get(d,k)||(a.a.f.set(d,k,!0),a.a.n(d,"selectionchange",l));a.a.f.set(b,h,c)};a.d.textInput={init:function(b,c,l){function h(c,d){a.a.n(b,c,d)}function k(){var d=a.a.c(c());if(null===d||d===p)d="";w!==p&&d===w?setTimeout(k,4):b.value!==d&&(u=d,b.value=d)}function v(){A|| 93 | (w=b.value,A=setTimeout(t,4))}function t(){clearTimeout(A);w=A=p;var d=b.value;u!==d&&(u=d,a.h.ra(c(),l,"textInput",d))}var u=b.value,A,w;10>a.a.M?(h("propertychange",function(a){"value"===a.propertyName&&t()}),8==a.a.M&&(h("keyup",t),h("keydown",t)),8<=a.a.M&&(g(b,t),h("dragend",v))):(h("input",t),5>e&&"textarea"===a.a.v(b)?(h("keydown",v),h("paste",v),h("cut",v)):11>d?h("keydown",v):4>f&&(h("DOMAutoComplete",t),h("dragdrop",t),h("drop",t)));h("change",t);a.w(k,null,{q:b})}};a.h.V.textInput=!0;a.d.textinput= 94 | {preprocess:function(a,b,c){c("textInput",a)}}})();a.d.uniqueName={init:function(b,d){if(d()){var c="ko_unique_"+ ++a.d.uniqueName.fc;a.a.Rb(b,c)}}};a.d.uniqueName.fc=0;a.d.value={after:["options","foreach"],init:function(b,d,c){if("input"!=b.tagName.toLowerCase()||"checkbox"!=b.type&&"radio"!=b.type){var e=["change"],f=c.get("valueUpdate"),k=!1,h=null;f&&("string"==typeof f&&(f=[f]),a.a.ia(e,f),e=a.a.wb(e));var l=function(){h=null;k=!1;var e=d(),g=a.i.s(b);a.h.ra(e,c,"value",g)};!a.a.M||"input"!= 95 | b.tagName.toLowerCase()||"text"!=b.type||"off"==b.autocomplete||b.form&&"off"==b.form.autocomplete||-1!=a.a.m(e,"propertychange")||(a.a.n(b,"propertychange",function(){k=!0}),a.a.n(b,"focus",function(){k=!1}),a.a.n(b,"blur",function(){k&&l()}));a.a.o(e,function(c){var d=l;a.a.Dc(c,"after")&&(d=function(){h=a.i.s(b);setTimeout(l,0)},c=c.substring(5));a.a.n(b,c,d)});var g=function(){var e=a.a.c(d()),f=a.i.s(b);if(null!==h&&e===h)setTimeout(g,0);else if(e!==f)if("select"===a.a.v(b)){var l=c.get("valueAllowUnset"), 96 | f=function(){a.i.Y(b,e,l)};f();l||e===a.i.s(b)?setTimeout(f,0):a.k.u(a.a.qa,null,[b,"change"])}else a.i.Y(b,e)};a.w(g,null,{q:b})}else a.va(b,{checkedValue:d})},update:function(){}};a.h.V.value=!0;a.d.visible={update:function(b,d){var c=a.a.c(d()),e="none"!=b.style.display;c&&!e?b.style.display="":!c&&e&&(b.style.display="none")}};(function(b){a.d[b]={init:function(d,c,e,f,k){return a.d.event.init.call(this,d,function(){var a={};a[b]=c();return a},e,f,k)}}})("click");a.J=function(){};a.J.prototype.renderTemplateSource= 97 | function(){throw Error("Override renderTemplateSource");};a.J.prototype.createJavaScriptEvaluatorBlock=function(){throw Error("Override createJavaScriptEvaluatorBlock");};a.J.prototype.makeTemplateSource=function(b,d){if("string"==typeof b){d=d||w;var c=d.getElementById(b);if(!c)throw Error("Cannot find template with ID "+b);return new a.t.l(c)}if(1==b.nodeType||8==b.nodeType)return new a.t.ha(b);throw Error("Unknown template type: "+b);};a.J.prototype.renderTemplate=function(a,d,c,e){a=this.makeTemplateSource(a, 98 | e);return this.renderTemplateSource(a,d,c,e)};a.J.prototype.isTemplateRewritten=function(a,d){return!1===this.allowTemplateRewriting?!0:this.makeTemplateSource(a,d).data("isRewritten")};a.J.prototype.rewriteTemplate=function(a,d,c){a=this.makeTemplateSource(a,c);d=d(a.text());a.text(d);a.data("isRewritten",!0)};a.b("templateEngine",a.J);a.kb=function(){function b(b,c,d,h){b=a.h.bb(b);for(var l=a.h.ka,g=0;g]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi,c=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;return{lc:function(b, 100 | c,d){c.isTemplateRewritten(b,d)||c.rewriteTemplate(b,function(b){return a.kb.xc(b,c)},d)},xc:function(a,f){return a.replace(d,function(a,c,d,e,m){return b(m,c,d,f)}).replace(c,function(a,c){return b(c,"\x3c!-- ko --\x3e","#comment",f)})},dc:function(b,c){return a.H.$a(function(d,h){var l=d.nextSibling;l&&l.nodeName.toLowerCase()===c&&a.va(l,b,h)})}}}();a.b("__tr_ambtns",a.kb.dc);(function(){a.t={};a.t.l=function(a){this.l=a};a.t.l.prototype.text=function(){var b=a.a.v(this.l),b="script"===b?"text": 101 | "textarea"===b?"value":"innerHTML";if(0==arguments.length)return this.l[b];var d=arguments[0];"innerHTML"===b?a.a.gb(this.l,d):this.l[b]=d};var b=a.a.f.I()+"_";a.t.l.prototype.data=function(c){if(1===arguments.length)return a.a.f.get(this.l,b+c);a.a.f.set(this.l,b+c,arguments[1])};var d=a.a.f.I();a.t.ha=function(a){this.l=a};a.t.ha.prototype=new a.t.l;a.t.ha.prototype.text=function(){if(0==arguments.length){var b=a.a.f.get(this.l,d)||{};b.lb===p&&b.Na&&(b.lb=b.Na.innerHTML);return b.lb}a.a.f.set(this.l, 102 | d,{lb:arguments[0]})};a.t.l.prototype.nodes=function(){if(0==arguments.length)return(a.a.f.get(this.l,d)||{}).Na;a.a.f.set(this.l,d,{Na:arguments[0]})};a.b("templateSources",a.t);a.b("templateSources.domElement",a.t.l);a.b("templateSources.anonymousTemplate",a.t.ha)})();(function(){function b(b,c,d){var e;for(c=a.e.nextSibling(c);b&&(e=b)!==c;)b=a.e.nextSibling(e),d(e,b)}function d(c,d){if(c.length){var e=c[0],f=c[c.length-1],h=e.parentNode,k=a.L.instance,r=k.preprocessNode;if(r){b(e,f,function(a, 103 | b){var c=a.previousSibling,d=r.call(k,a);d&&(a===e&&(e=d[0]||b),a===f&&(f=d[d.length-1]||c))});c.length=0;if(!e)return;e===f?c.push(e):(c.push(e,f),a.a.na(c,h))}b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.ub(d,b)});b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.H.Xb(b,[d])});a.a.na(c,h)}}function c(a){return a.nodeType?a:0a.a.M?0:b.nodes)?b.nodes():null)return a.a.O(d.cloneNode(!0).childNodes);b=b.text();return a.a.ca(b,e)};a.P.Va=new a.P;a.hb(a.P.Va);a.b("nativeTemplateEngine",a.P);(function(){a.Ya=function(){var a=this.uc=function(){if(!u||!u.tmpl)return 0;try{if(0<=u.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(a){}return 1}();this.renderTemplateSource=function(b, 114 | e,f,k){k=k||w;f=f||{};if(2>a)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var h=b.data("precompiled");h||(h=b.text()||"",h=u.template(null,"{{ko_with $item.koBindingContext}}"+h+"{{/ko_with}}"),b.data("precompiled",h));b=[e.$data];e=u.extend({koBindingContext:e},f.templateOptions);e=u.tmpl(h,b,e);e.appendTo(k.createElement("div"));u.fragments={};return e};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+ 115 | a+" })()) }}"};this.addTemplate=function(a,b){w.write("