{"id":22069828,"url":"https://github.com/romagny13/spa-lib","last_synced_at":"2025-03-23T18:43:40.491Z","repository":{"id":57366746,"uuid":"82499857","full_name":"romagny13/spa-lib","owner":"romagny13","description":"TypeScript SpaLib","archived":false,"fork":false,"pushed_at":"2017-03-14T17:06:46.000Z","size":465,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-12-31T22:35:35.505Z","etag":null,"topics":["http","indexeddb","oauth","promise","spa","storage","typescript"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/romagny13.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-02-20T00:24:01.000Z","updated_at":"2017-03-14T13:19:38.000Z","dependencies_parsed_at":"2022-08-23T20:10:46.992Z","dependency_job_id":null,"html_url":"https://github.com/romagny13/spa-lib","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romagny13%2Fspa-lib","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romagny13%2Fspa-lib/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romagny13%2Fspa-lib/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romagny13%2Fspa-lib/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/romagny13","download_url":"https://codeload.github.com/romagny13/spa-lib/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":236112927,"owners_count":19096850,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http","indexeddb","oauth","promise","spa","storage","typescript"],"created_at":"2024-11-30T20:13:17.398Z","updated_at":"2025-01-29T00:51:55.716Z","avatar_url":"https://github.com/romagny13.png","language":"JavaScript","readme":"# SpaLib\n\n[![Build Status](https://travis-ci.org/romagny13/spa-lib.svg?branch=master)](https://travis-ci.org/romagny13/spa-lib)\n\n```\nnpm i spa-lib -S\n```\n\n- Storage (local, session, cookies and indexedDB services)\n- Cache\n- Messenger\n- Observable and ObservableArray\n- Form binding and validation\n- Http (with interceptors)\n- OAuth (Google, Facebook, ...easy to extend)\n- TypeScript Promise polyfill\n\n\n## es6 / TypeScript\n\nExample\n```js\nimport { Observable, TSPromise  } from 'spa-lib';\n// or\nimport * as SpaLib from 'spa-lib';\n```\n\n## es5\n\n```html\n \u003cscript src=\"node_modules/spa-lib/dist/spa.lib.js\"\u003e\u003c/script\u003e\n```\n\nExample\n```js\nvar messenger = new SpaLib.Messenger();\n```\n\n## Observables\n\n- Observe value changes\n```js\nconst vm = {\n    title: 'My title'\n};\n\n// observe an object's property\nlet observable = new Observable(vm, 'title');\nobservable.subscribe((value) =\u003e {\n\n});\n\nvm.title = 'My new title'; // =\u003e valuechanged\n```\n\n- Observe array changes\n```js\nlet items = ['a', 'b'];\nlet observableArray = new ObservableArray(items);\nobservableArray.subscribe((event, value, index) =\u003e {\n    switch (event) {\n        case 'added':\n\n            break;\n        case 'removed':\n\n            break;\n        case 'filtered':\n\n            break;\n        case 'sorted':\n\n            break;\n    }\n});\n// example with push\nitems.push('c', 'd', 'e');\n```\n\nSupport :\n- push\n- unshift\n- splice\n- shift\n- pop\n- sort\n- filter\n- resetFilter\n\n### Shorthands\n\n```js\nconst vm = {\n    title: 'My title'\n};\n\nobserve(vm, 'title', (value) =\u003e {\n\n});\n```\n\n```js\nlet items = ['a', 'b'];\n\nobserveArray(items, (event, value, index) =\u003e {\n\n});\n```\n\n## Form binding and validation\n\n```js\n// model\nlet user = {\n    firstname: 'marie',\n    lastname: 'bellin',\n    email: '',\n    age: 20,\n    list: '2',\n    preference: 'b', \n    likes: ['Milk', 'Cakes'] // binding on array\n};\n\n// form config (validation on 'submit' by default or 'valuechanged')\nconst formConfig = new FormConfig()\n    // form element name, validators array, updateType? ('valuechanged' by default or 'lostfocus')\n    .addFormElementConfig('firstname', [Validator.required(), Validator.minLength(3)])\n    .addFormElementConfig('lastname', [Validator.maxLength(10)])\n    .addFormElementConfig('email', [Validator.pattern(/^[a-zA-Z0-9.!#$%\u0026’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$/, 'Please enter a valid email.')])\n    .addFormElementConfig('age', [Validator.custom((value) =\u003e {\n        return value \u003e 0 \u0026\u0026 value \u003c 120;\n    }, 'Oops ??')])\n    .addFormElementConfig('agree', [Validator.required()]) // validation with an element not in the model (checkbox for example)\n    .addFormElementConfig('likes', [Validator.custom(() =\u003e {\n        return user.likes.length \u003e 0;\n    }, 'Please select one or more items.')]);\n\n// form binding\nconst formBinding = new FormBinding('#myform', user, formConfig);\n```\nNote: validation messages are appended to html element on error with css class 'has-error' or 'has-success' (could be desabled with form config).\n\non validation state changed\n```js\nformBinding.onStateChanged((element, errors) =\u003e {\n    console.log('State changed', element, errors);\n});\n```\n\non submit (create a sumary for example)\n```js\nconst sumary: any = document.querySelector('.sumary');\nformBinding.onSubmit((response: FormSubmittedResponse) =\u003e {\n    sumary.innerHTML = '';\n    sumary.style.display = 'block';\n    if (response.hasError) {\n        response.errors.forEach((error: FormElementError) =\u003e {\n            let p = document.createElement('p');\n            p.innerHTML = error.message;\n            sumary.appendChild(p);\n        });\n    }\n    else {\n        let json = JSON.stringify(response.source);\n        sumary.innerHTML = `\n            \u003ch2\u003eOk (refresh on validation state changed)\u003c/h2\u003e\n            \u003cpre\u003e${json}\u003c/pre\u003e\n        `;\n    }\n});\n```\n\nAllow to bind and validate with a simple form (cons: actually interact with the DOM)\n\n```html\n\u003cform id=\"myform\" class=\"form-horizontal\"\u003e\n    \u003cdiv class=\"form-group\"\u003e\n        \u003clabel class=\"control-label\" for=\"firstname\"\u003eFirstname:\u003c/label\u003e\n        \u003cinput class=\"form-control\" type=\"text\" id=\"firstname\" name=\"firstname\" /\u003e\n    \u003c/div\u003e\n\n    \u003cdiv class=\"form-group\"\u003e\n        \u003clabel class=\"control-label\" for=\"lastname\"\u003eLastname:\u003c/label\u003e\n        \u003cinput class=\"form-control\" type=\"text\" id=\"lastname\" name=\"lastname\" /\u003e\n    \u003c/div\u003e\n\n    \u003cdiv class=\"form-group\"\u003e\n        \u003clabel class=\"control-label\" for=\"email\"\u003eEmail:\u003c/label\u003e\n        \u003cinput class=\"form-control\" type=\"text\" id=\"email\" name=\"email\" /\u003e\n    \u003c/div\u003e\n\n    \u003cdiv class=\"form-group\"\u003e\n        \u003clabel class=\"control-label\" for=\"age\"\u003eAge:\u003c/label\u003e\n        \u003cinput class=\"form-control\" type=\"number\" id=\"age\" name=\"age\" /\u003e\n    \u003c/div\u003e\n\n    \u003cdiv class=\"form-group\"\u003e\n        \u003clabel class=\"control-label\" for=\"list\"\u003eList (no validation):\u003c/label\u003e\n        \u003cselect class=\"form-control\" name=\"list\"\u003e\n                    \u003coption value=\"1\"\u003e1\u003c/option\u003e\n                    \u003coption value=\"2\"\u003e2\u003c/option\u003e\n                    \u003coption value=\"3\"\u003e3\u003c/option\u003e\n                \u003c/select\u003e\n    \u003c/div\u003e\n\n    \u003cdiv class=\"form-group\"\u003e\n        \u003ch3\u003ePreference\u003c/h3\u003e\n        \u003cinput type=\"radio\" name=\"preference\" value=\"a\"\u003eA\u003cbr\u003e\n        \u003cinput type=\"radio\" name=\"preference\" value=\"b\"\u003eB\u003cbr\u003e\n        \u003cinput type=\"radio\" name=\"preference\" value=\"c\"\u003eC\n    \u003c/div\u003e\n\n    \u003cdiv class=\"form-group\"\u003e\n        \u003ch3\u003eLike (multiple choice)\u003c/h3\u003e\n        \u003cinput type=\"checkbox\" name=\"likes\" value=\"Cakes\"\u003eCakes\u003cbr\u003e\n        \u003cinput type=\"checkbox\" name=\"likes\" value=\"Milk\"\u003eMilk\u003cbr\u003e\n        \u003cinput type=\"checkbox\" name=\"likes\" value=\"Nutella\"\u003eNutella\n    \u003c/div\u003e\n    \u003cbr/\u003e\n    \u003cdiv class=\"checkbox\"\u003e \u003clabel\u003e\u003cinput type=\"checkbox\" name=\"agree\"\u003eAgree to conditions\u003c/label\u003e\u003c/div\u003e\n    \u003cinput class=\"btn btn-default\" type=\"submit\" value=\"Submit\" /\u003e\n\u003c/form\u003e\n```\n\n## Messenger\n\n```js\nlet messenger = new Messenger();\n// subscribe (one or more arguments)\nmessenger.subscribe('MyEvent', (result1, result2, result3) =\u003e {\n\n});\n\n// publish \nmessenger.publish('MyEvent', 'my string', 10, { name: 'my object result' });\n```\n\n## Http\n\n### Send a request (GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH)\n```js\nconst baseUrl = 'http://jsonplaceholder.typicode.com';\nconst http = new Http();\nconst request = new HttpRequest({ url: `${baseUrl}/posts` });\nhttp.send(request).then((response: HttpResponse) =\u003e {\n    let posts = JSON.parse(response.body);\n});\n```\n- Post JSON \n```js\nconst data = {\n    title: 'My post',\n    body: 'My content'\n};\nhttp.post(`${baseUrl}/posts`, JSON.stringify(data)).then((response: HttpResponse) =\u003e {\n    let post = JSON.parse(response.body);\n});\n```\n\n- Post form\n```js\nlet body = 'title=' + encodeURIComponent('My post') + '\u0026content=' + encodeURIComponent('My content');\nconst request = new HttpRequest({\n    method: 'POST',\n    headers: { 'content-type': 'application/x-www-form-urlencoded' },\n    url: `${baseUrl}/posts`,\n    body\n});\nhttp.send(request).then((response: HttpResponse) =\u003e {\n    let post = JSON.parse(response.body);\n});\n```\n\n- FormData\n```js\nlet data = new FormData();\ndata.append('id', '101');\ndata.append('title', 'My post');\ndata.append('content', 'My content');\n\nconst request = new HttpRequest({\n    method: 'POST',\n    url: `${baseUrl}/posts`,\n    body: data\n});\nhttp.send(request).then((response: HttpResponse) =\u003e {\n    let post = JSON.parse(response.body);\n});\n```\n\n- Set a timeout (milliseconds)\n```js\nconst request = new HttpRequest({\n    url: `${baseUrl}/posts`,\n    timeout: 1000\n});\nhttp.send(request).then(() =\u003e {\n\n}, (response: HttpResponse) =\u003e {\n    // handle : \n    // - response with error status code \n    // - xhr error | timeout | abort\n});\n```\n\n- Abort a request\n```js\nconst request = new HttpRequest({ url: `${baseUrl}/posts`});\nhttp.send(request).then(() =\u003e { }, (response) =\u003e {});\nrequest.abort();\n```\n- Handle progress\n```js\n\nconst request = new HttpRequest({\n    url: `${baseUrl}/posts`,\n    progress: (event) =\u003e {\n        // ...\n    }\n});\nhttp.send(request).then(() =\u003e {});\n```\n- Response type =\u003e blob\n```js\nconst request = new HttpRequest({ url: 'http://res.cloudinary.com/romagny13/image/upload/v1464052663/dotnet_oh0ryu.png', responseType: 'blob' });\nhttp.send(request).then((response: HttpResponse) =\u003e {\n\n});\n```\n\n### REST Shorthands (get,post,put,delete)\n```js\nconst data = {\n    title: 'My post',\n    body: 'lorem ipsum ...'\n};\nhttp.post(`${baseUrl}/posts`, JSON.stringify(data)).then((response: HttpResponse) =\u003e {\n\n});\n```\n- put \n```js\nconst data = {\n    id: 1,\n    title: 'My updated post',\n    content: 'My updated content'\n};\nhttp.put(`${baseUrl}/posts/1`, JSON.stringify(data)).then((response: HttpResponse) =\u003e {\n    let post = JSON.parse(response.body);\n});\n```\n- delete\n```js\nhttp.delete(`${baseUrl}/posts/1`).then((response: HttpResponse) =\u003e {\n\n});\n```\n\n- With an access_token\n```js\nlet access_token = 'ya29.CjCmA3KKBZH7ElidD3j_peoQaPdy2G099Ek6DYuYRfwFqSMXpR3i2_2xSjjHBo6FNNo';\nhttp.get(`${baseUrl}/posts`, access_token).then((response: HttpResponse) =\u003e {});\n```\n\n### Interceptors\n\nCreate an interceptor\n```js\nhttp.interceptors.push({\n    before(request: HttpRequest, next: Function) {\n\n        next(true); // cancel if false\n    },\n    after(response: HttpResponse, next: Function) {\n        \n        next(true);\n    }\n});\n```\n\n### Load\n```js\nhttp.load('http://localhost/blog/mytemplate.html', (template) =\u003e {\n\n});\n```\n\n## OAuth\n\n\u003ca href=\"https://github.com/romagny13/spa-lib/tree/master/example/oauth\"\u003eLook at the OAuth example\u003c/a\u003e\n\n## TSPromise\n\n\u003ca href=\"https://github.com/romagny13/ts-promise\"\u003eDocumentation\u003c/a\u003e\n\n## Cache \n\n```js\n// create a cache\nconst cache = new Cache();\nlet k = 'mykey';\nlet item = { name: 'my value' };\n// store\ncache.store(k, item);\n// check\nlet hasItem = cache.has(k);\n// get\nlet result = cache.retrieve(k);\n// save to local storage\nlet storageKey = '__cache';\ncache.save(storageKey);\n// restore cache from local storage\ncache.restore(storageKey);\n// remove an item by key\ncache.remove(k);\n// clear\ncache.clear();\n```\n\n## Storage\n\n### Cookies\n```js\nconst cookieService = new CookieService();\nlet name = 'mycookie';\n// create a cookie (arguments: name, value, expirationDays, path, domain, secure)\ncookieService.set(name, value, 10);\n// check\nlet hasCookie = cookieService.has(name)\n// get\nlet result = cookieService.get(name);\n// delete\nlet result = cookieService.delete(name);\n// clear all cookies\ncookieService.clear();\n```\n\n### Local and session storage\n\n```js\nlet localService = new StorageService();\n\nlet key = 'mykey';\n// set\nlocalService.set(key, 'my value'); // value could be an object\n// check\nlet hasItem = localService.has(key)\n// get\nlet item = localService.get(key);\n// remove\nlocalService.remove(key);\n// clear\nlocalService.clear();\n```\n\n```js\nlet sessionService = new StorageService('session');\n\nlet key = 'mykey';\n// set\nsessionService.set(key, 'my value'); // value could be an object\n// check\nlet hasItem = sessionService.has(key)\n// get\nlet item = sessionService.get(key);\n// remove\nsessionService.remove(key);\n// clear\nsessionService.clear();\n```\n\n### indexedDB\n\n\u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB\"\u003eMDN\u003c/a\u003e\n\n```js\nconst dbConfigs = [{\n    name: 'testdb', // db name\n    created: true, // add created and modified fields\n    // object stores\n    objectStores: [{\n        name: 'users',\n        key: { keyPath: 'id', autoIncrement: true }, // key\n        indexes: [{ name: 'username', definition: { unique: false } }] // indexes\n    }]\n}];\nconst indexeddbService = new IndexedDBService(dbConfigs);\n// open /create / update the db (if db version is updated)\nindexeddbService.openCreateDb('testdb', 1).then((result) =\u003e {\n \n // then could get, insert, update,... data\n\n}, (error) =\u003e {\n\n});\n```\n- Insert\n```js\nindexeddbService.insert('users', { userName: 'marie' }).then((result) =\u003e {\n\n});\n```\n\n- get all\n\n```js\nindexeddbService.getAll('users').then((result: any[]) =\u003e {\n\n});\n```\n- get one\n\n```js\n// object store and key\nindexeddbService.getOne('users', 1).then((result) =\u003e {\n\n};\n```\n\n- update\n```js\nlet user = {\n    userName: 'updated'\n};\nindexeddbService.update('users', 1, user).then((result) =\u003e {\n\n});\n```\n\n- delete\n```js\nindexeddbService.delete('users', 1).then((result) =\u003e {\n\n});\n```\n\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromagny13%2Fspa-lib","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fromagny13%2Fspa-lib","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromagny13%2Fspa-lib/lists"}