forked from rc/aircox
radiocampus: style update
This commit is contained in:
371
radiocampus/assets/src/model.js
Normal file
371
radiocampus/assets/src/model.js
Normal file
@ -0,0 +1,371 @@
|
||||
|
||||
/**
|
||||
* Return cookie with provided key
|
||||
*/
|
||||
function getCookie(key) {
|
||||
if(document.cookie && document.cookie !== '') {
|
||||
const cookie = document.cookie.split(';')
|
||||
.find(c => c.trim().startsWith(key + '='))
|
||||
return cookie ? decodeURIComponent(cookie.split('=')[1]) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* CSRF token provided by Django
|
||||
*/
|
||||
var csrfToken = null;
|
||||
|
||||
/**
|
||||
* Get CSRF token
|
||||
*/
|
||||
export function getCsrf() {
|
||||
if(csrfToken === null)
|
||||
csrfToken = getCookie('csrftoken')
|
||||
return csrfToken;
|
||||
}
|
||||
|
||||
|
||||
// TODO: prevent duplicate simple fetch
|
||||
/**
|
||||
* Provide interface used to fetch and manipulate objects.
|
||||
*/
|
||||
export default class Model {
|
||||
/**
|
||||
* Instanciate model with provided data and options.
|
||||
* By default `url` is taken from `data.url_`.
|
||||
*/
|
||||
constructor(data={}, {url=null, ...options}={}) {
|
||||
this.url = url || data.url_;
|
||||
this.options = options;
|
||||
this.commit(data);
|
||||
}
|
||||
|
||||
get created() { return !this.id }
|
||||
get errors() { return this.data && this.data.__errors__ }
|
||||
|
||||
/**
|
||||
* Get instance id from its data
|
||||
*/
|
||||
static getId(data) {
|
||||
return 'id' in data ? data.id : data.pk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fetch options
|
||||
*/
|
||||
static getOptions(options) {
|
||||
return {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'X-CSRFToken': getCsrf(),
|
||||
},
|
||||
...options,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return model instances for the provided list of model data.
|
||||
* @param {Array} items: array of data
|
||||
* @param {Object} options: options passed down to all model instances
|
||||
*/
|
||||
static fromList(items, options={}) {
|
||||
return items ? items.map(d => new this(d, options)) : []
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch item from server
|
||||
*/
|
||||
static fetch(url, {many=false, ...options}={}, args={}) {
|
||||
options = this.getOptions(options)
|
||||
const request = fetch(url, options).then(response => response.json());
|
||||
if(many)
|
||||
return request.then(data => {
|
||||
if(!(data instanceof Array))
|
||||
data = data.results
|
||||
return this.fromList(data, args)
|
||||
})
|
||||
else
|
||||
return request.then(data => new this(data, {url: url, ...args}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch data from server.
|
||||
*/
|
||||
fetch(options) {
|
||||
options = this.constructor.getOptions(options)
|
||||
return fetch(this.url, options)
|
||||
.then(response => response.json())
|
||||
.then(data => this.commit(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call API action on object.
|
||||
*/
|
||||
action(path, options, commit=false) {
|
||||
options = this.constructor.getOptions(options)
|
||||
const promise = fetch(this.url + path, options);
|
||||
return commit ? promise.then(data => data.json())
|
||||
.then(data => { this.commit(data); this.data })
|
||||
: promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set instance's data with provided data. Return None
|
||||
*/
|
||||
commit(data) {
|
||||
this.data = data;
|
||||
this.id = this.constructor.getId(this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update model data, without reset previous value.
|
||||
* Item is marked as updated.
|
||||
*/
|
||||
update(data) {
|
||||
this.data = {...this.data, ...data}
|
||||
this.id = this.constructor.getId(this.data)
|
||||
this.updated = true
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.deleted = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Save instance into localStorage.
|
||||
*/
|
||||
store(key) {
|
||||
window.localStorage.setItem(key, JSON.stringify(this.data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load model instance from localStorage.
|
||||
*/
|
||||
static storeLoad(key) {
|
||||
let item = window.localStorage.getItem(key);
|
||||
return item === null ? item : new this(JSON.parse(item));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if model instance has no data
|
||||
*/
|
||||
get isEmpty() {
|
||||
return !this.data || Object.keys(this.data).findIndex(k => !!this.data[k] && this.data[k] !== 0) == -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Return error for a specific attribute name if any
|
||||
*/
|
||||
error(attr=null) {
|
||||
return attr === null ? this.errors : this.errors && this.errors[attr]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* List of models
|
||||
*/
|
||||
export class Set {
|
||||
constructor(model, {items=[],url=null,args={},unique=null,max=null,storeKey=null}={}) {
|
||||
this.items = [];
|
||||
this.model = model;
|
||||
this.url = url;
|
||||
this.unique = unique;
|
||||
this.max = max;
|
||||
this.storeKey = storeKey;
|
||||
|
||||
for(var item of items)
|
||||
this.push(item, {args: args, save: false});
|
||||
}
|
||||
|
||||
//! Return total items count
|
||||
get length() { return this.items.length }
|
||||
|
||||
//! Return a list of items marked as deleted
|
||||
get deletedItems() {
|
||||
return this.items.filter(i => i.deleted)
|
||||
}
|
||||
|
||||
//! Return a list of created items
|
||||
get createdItems() {
|
||||
return this.items.filter(i => !i.deleted && !i.id)
|
||||
}
|
||||
|
||||
//! Return a list of updated items
|
||||
get updatedItems() {
|
||||
return this.items.filter(i => i.updated)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch multiple items from server
|
||||
*/
|
||||
static fetch(model, url, options=null, args=null) {
|
||||
options = model.getOptions(options)
|
||||
return fetch(url, options)
|
||||
.then(response => response.json())
|
||||
.then(data => (data instanceof Array ? data : data.results)
|
||||
.map(d => new model(d, {url: url, ...args})))
|
||||
}
|
||||
|
||||
fetch({url=null, reset=false, ...options}={}, args=null) {
|
||||
url = url || this.url
|
||||
options = this.model.getOptions(options)
|
||||
return fetch(url, options)
|
||||
.then(response => response.json())
|
||||
.then(data =>
|
||||
(data instanceof Array ? data : data.results)
|
||||
.map(d => new this.model(d, {url: url, ...args}))
|
||||
)
|
||||
.then(data => {
|
||||
if(reset)
|
||||
this.items = data
|
||||
else
|
||||
// TODO: remove duplicate
|
||||
this.items = [...this.items, ...data]
|
||||
return data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit changes to server.
|
||||
* py-ref: `views.mixin.ListCommitMixin`
|
||||
*/
|
||||
commit(url, {getData=null, fields=null, ...options}={}) {
|
||||
if(!getData && fields)
|
||||
getData = (i) => fields.reduce((r, f) => {
|
||||
r[f] = i.data[f]
|
||||
return r
|
||||
}, {})
|
||||
const createdItems = this.createdItems
|
||||
const body = {
|
||||
delete: this.deletedItems.map(i => i.id),
|
||||
update: this.updatedItems.map(getData),
|
||||
create: createdItems.map(getData),
|
||||
}
|
||||
if(!body.delete && !body.update && !body.create)
|
||||
return
|
||||
|
||||
getData = getData || ((i) => i.data);
|
||||
options = this.model.getOptions(options)
|
||||
options.method = "POST"
|
||||
options.body = JSON.stringify(body)
|
||||
return fetch(url, options)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const {created, updated, deleted} = data
|
||||
if(createdItems)
|
||||
this.items = this.items.filter(i => createdItems.indexOf(i) == -1)
|
||||
if(deleted)
|
||||
this.items = this.items.filter(i => deleted.indexOf(i.id) == -1)
|
||||
|
||||
this.extend(created)
|
||||
this.extend(updated)
|
||||
return data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Load list from localStorage
|
||||
*/
|
||||
static storeLoad(model, key, args={}) {
|
||||
let items = window.localStorage.getItem(key);
|
||||
return new this(model, {...args, storeKey: key, items: items ? JSON.parse(items) : []});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store list into localStorage
|
||||
*/
|
||||
store() {
|
||||
this.storeKey && window.localStorage.setItem(this.storeKey, JSON.stringify(
|
||||
this.items.map(i => i.data)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Save item
|
||||
*/
|
||||
save() {
|
||||
this.storeKey && this.store();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item at index
|
||||
*/
|
||||
get(index) { return this.items[index] }
|
||||
|
||||
/**
|
||||
* Find an item by id or using a predicate function
|
||||
*/
|
||||
find(pred) {
|
||||
return pred instanceof Function ? this.items.find(pred)
|
||||
: this.items.find(x => x.id == pred.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find item index by id or using a predicate function
|
||||
*/
|
||||
findIndex(pred) {
|
||||
return pred instanceof Function ? this.items.findIndex(pred)
|
||||
: this.items.findIndex(x => x.id == pred.id);
|
||||
}
|
||||
|
||||
extend(items, options) {
|
||||
items.forEach(i => this.push(i, options))
|
||||
}
|
||||
|
||||
/**
|
||||
* Add item to set, return index.
|
||||
* If item already exists, replace it.
|
||||
*/
|
||||
push(item, {args={},save=true}={}) {
|
||||
item = item instanceof this.model ? item : new this.model(item, args);
|
||||
let index = -1
|
||||
if(this.unique && item.id) {
|
||||
index = this.findIndex(item);
|
||||
if(index > -1)
|
||||
this.items[index] = item
|
||||
}
|
||||
if(index == -1) {
|
||||
if(this.max && this.items.length >= this.max)
|
||||
this.items.splice(0,this.items.length-this.max)
|
||||
this.items.push(item)
|
||||
index = this.items.length-1
|
||||
}
|
||||
save && this.save()
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from set by index
|
||||
*/
|
||||
remove(index, {save=true}={}) {
|
||||
this.items.splice(index,1);
|
||||
save && this.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear items, assign new ones
|
||||
*/
|
||||
reset(items=[]) {
|
||||
// TODO: check reactivity
|
||||
this.items = []
|
||||
for(var item of items)
|
||||
this.push(item)
|
||||
}
|
||||
|
||||
move(from, to) {
|
||||
if(from >= this.length || to > this.length)
|
||||
throw "source or target index is not in range"
|
||||
|
||||
const value = this.items[from]
|
||||
this.items.splice(from, 1)
|
||||
this.items.splice(to, 0, value)
|
||||
}
|
||||
}
|
||||
|
||||
Set[Symbol.iterator] = function () {
|
||||
return this.items[Symbol.iterator]();
|
||||
}
|
Reference in New Issue
Block a user