6 Ревизии

Автор SHA1 Съобщение Дата
  Bing Sun 16793dc865
Table: support loading status widgets преди 5 години
  Bing Sun 92a2022efa
make_model: complete the xhr cancelling logic преди 5 години
  Bing Sun 986413d433
postgest: apply xhr config before additional xhr hook преди 5 години
  Bing Sun f545118fc7
make_model: prelinary loading state support преди 5 години
  Bing Sun ef4e0b1a15
make_model: polish the state & cache handling преди 5 години
  Bing Sun c9d13d1cfd
make_model: wrap random state variables преди 5 години
променени са 3 файла, в които са добавени 105 реда и са изтрити 62 реда
  1. +21
    -17
      src/components/Table.js
  2. +10
    -1
      src/postgrest.js
  3. +74
    -44
      src/util/make_model.js

+ 21
- 17
src/components/Table.js Целия файл

@@ -87,10 +87,11 @@ export default initial_vnode => {
model.select(offset, limit) model.select(offset, limit)
} }


return [
m('table', {
style: vnode.attrs.show ? undefined : {display: 'none'},
}, [
return m('main', {
style: vnode.attrs.show ? undefined : {display: 'none'}
}, [
// the table
m('table', [
// always show table header // always show table header
m('thead', m('tr', [ m('thead', m('tr', [
vnode.attrs.serial ? m('th.centered', '序号') : undefined, vnode.attrs.serial ? m('th.centered', '序号') : undefined,
@@ -120,21 +121,24 @@ export default initial_vnode => {


return m(`td${column.class || ''}`, {style: column.style || undefined}, v) return m(`td${column.class || ''}`, {style: column.style || undefined}, v)
}) })
]))) : m('', 'Empty')
]))) : undefined
]), ]),


// page navigation
pages.length > 1 && vnode.attrs.show ?
m('.centered',
m('.pagination.centered', pages.map((page, i) => page.show ? m('a', {
class: page.active ? 'active' : '',
onclick: e => {
offset = i*limit
model.select(offset, limit)
}
}, i+1) : undefined))
) : undefined
]
// status area
model.data(offset, limit).length ? undefined : (
model.loading() ? (vnode.attrs.loading_indicator || m('', 'Loading')) : (vnode.attrs.empty_indicator || m('', 'Empty'))
),

// the page navigation
(pages.length > 1) ? m('.centered', m('.pagination.centered', pages.map(
(page, i) => page.show ? m('a', {
class: page.active ? 'active' : '',
onclick: e => {
offset = i*limit
model.select(offset, limit)
}
}, i+1) : undefined))) : undefined
])
} }
} }
} }

+ 10
- 1
src/postgrest.js Целия файл

@@ -110,11 +110,20 @@ export default (api_root, auth={}) => {


// 2. return values on Prefer // 2. return values on Prefer
// if (['POST', 'PATCH'].includes(options.method) && options.headers && !options.headers.Prefer) // if (['POST', 'PATCH'].includes(options.method) && options.headers && !options.headers.Prefer)
if (['POST', 'PATCH'].includes(options.method))
if (['POST', 'PATCH'].includes(options.method)) {
// if the config is already set
let _old_config = options.config

options.config = xhr => { options.config = xhr => {
// apply the provided xhr config
let _old_config_xhr = _old_config(xhr)
if (_old_config_xhr)
xhr = _old_config_xhr

xhr.setRequestHeader('Prefer', 'return=representation') xhr.setRequestHeader('Prefer', 'return=representation')
return xhr return xhr
} }
}


// normalize params: since options.params is an object, it cannot support // normalize params: since options.params is an object, it cannot support
// duplicated query keys, while postgrest supports that. // duplicated query keys, while postgrest supports that.


+ 74
- 44
src/util/make_model.js Целия файл

@@ -37,20 +37,25 @@ export default (options={}) => {


// private model cache & meta-info // private model cache & meta-info
let _cache = { let _cache = {
// upstream (data) cache
// upstream data cache
data: null, data: null,
count: null, count: null,
upstream_limit: null,
upstream_limit: null
}


// local cache
// model state
let _state = {
// model ambient that determines the data content
ambient: null, ambient: null,
last_ambient: null, last_ambient: null,
// async states
xhr: null,
promise: null,
loading: false,
loading_offset: null,
loading_limit: null
} }


// some random variables to make things work
let _xhr = null
let _promise = null

// construct the model // construct the model
let _model = { let _model = {
// reflection methods // reflection methods
@@ -58,24 +63,34 @@ export default (options={}) => {
cache() { return _cache }, cache() { return _cache },
data(offset=0, limit=Infinity) { return _cache.data && _cache.data.slice(offset, offset+limit) || [] }, data(offset=0, limit=Infinity) { return _cache.data && _cache.data.slice(offset, offset+limit) || [] },
ambient_changed() { ambient_changed() {
return _cache.ambient != _cache.last_ambient
return _state.ambient != _state.last_ambient
},
loading() {
return _state.loading
}, },
reset() {
let ambient_queries = [

// clean state and cache if necessary
clean() {
// always clean async state
_state.xhr = null
_state.promise = null
_state.loading = false
_state.loading_offset = null
_state.loading_limit = null

// update ambient state
_state.last_ambient = _state.ambient
_state.ambient =JSON.stringify([
...(_configs.selects ? _configs.selects : []), ...(_configs.selects ? _configs.selects : []),
...(_configs.wheres ? _configs.wheres.map(where => ({label: where.label, op: where.op, value: typeof where.value == 'function' ? where.value() : where.value})) : []), ...(_configs.wheres ? _configs.wheres.map(where => ({label: where.label, op: where.op, value: typeof where.value == 'function' ? where.value() : where.value})) : []),
...(_configs.order ? _configs.order : []) ...(_configs.order ? _configs.order : [])
]

_cache.last_ambient = _cache.ambient
_cache.ambient =JSON.stringify(ambient_queries)
])


// clean cache if ambient changes
if (this.ambient_changed()) { if (this.ambient_changed()) {
_cache.data = [] _cache.data = []
_cache.count = null _cache.count = null
_cache.upstream_limit = null _cache.upstream_limit = null
_xhr = null
//this.fully_loaded = false
} }
}, },


@@ -89,24 +104,30 @@ export default (options={}) => {
if (this.data(offset, limit).length > 0 && !this.data(offset, limit).includes(undefined)) if (this.data(offset, limit).length > 0 && !this.data(offset, limit).includes(undefined))
return Promise.resolve(this.data(offset, limit)) return Promise.resolve(this.data(offset, limit))


// be lazy 2: if there is a promise, return it if ambient matches or
// cancel it
if (_promise != null)
return _promise
// be lazy 2: if the model is loading the same slice of data with the
// same ambient, return the current promise; otherwise cancel it
if (this.loading()) {
if (!this.ambient_changed() && _state.loading_offset == offset && _state.loading_limit == limit)
return _state.promise
else {
if (_state.xhr != null) _state.xhr.abort()
_state.xhr = null
}
}


// TODO
// ambient = select + order + limit/offset
// if ambient is changed, cancel currently loading xhr; otherwise return
// current promise
// update state
_state.loading = true
_state.loading_offset = offset
_state.loading_limit = limit


// now the hard work
_promise = _configs.api.request({
// construct the promise
_state.promise = _configs.api.request({
method: 'GET', method: 'GET',
url: _configs.endpoint, url: _configs.endpoint,
headers: _cache.count == null ? { headers: _cache.count == null ? {
Prefer: 'count=exact' Prefer: 'count=exact'
} : {}, } : {},
config: xhr => _xhr = xhr,
config: xhr => _state.xhr = xhr,
queries: [ queries: [
// transform model state to postgest queries // transform model state to postgest queries
// selects // selects
@@ -135,7 +156,7 @@ export default (options={}) => {
] ]
}).then(response => { }).then(response => {
// gather begin/end/count // gather begin/end/count
let [_range, _count] = _xhr.getResponseHeader('content-range').split('/')
let [_range, _count] = _state.xhr.getResponseHeader('content-range').split('/')
let [_begin, _end] = _range.split('-').map(v => ~~v) let [_begin, _end] = _range.split('-').map(v => ~~v)


// update count if presented // update count if presented
@@ -161,15 +182,13 @@ export default (options={}) => {
if (_end - _begin + 1 < limit) if (_end - _begin + 1 < limit)
console.warn('The response range is narrower than requested, probably due to an upstream hard limit.') console.warn('The response range is narrower than requested, probably due to an upstream hard limit.')


// clean model state
_promise = null
this.reset()

// return data // return data
return _cache.data.slice(_begin, _end + 1) return _cache.data.slice(_begin, _end + 1)
}).finally(() => {
this.clean()
}) })


return _promise
return _state.promise
}, },


select_all() { select_all() {
@@ -177,14 +196,27 @@ export default (options={}) => {
if (this.data().length == _cache.count && !this.data().includes(undefined)) if (this.data().length == _cache.count && !this.data().includes(undefined))
return Promise.resolve(this.data()) return Promise.resolve(this.data())


// be lazy 2: if there is a promise, return it if ambient matches or
// cancel it
if (_promise != null)
return _promise
// be lazy 2: if the model is loading the same slice of data with the
// same ambient, return the current promise; otherwise cancel it
if (this.loading()) {
if (!this.ambient_changed() && _state.loading_offset == offset && _state.loading_limit == limit)
return _state.promise
else {
if (_state.xhr != null) _state.xhr.abort()
_state.xhr = null
}
}

// update state
_state.loading = true
_state.loading_offset = 0
_state.loading_limit = Infinity


_promise = _configs.api.request({
// construct the promise
_state.promise = _configs.api.request({
method: 'POST', method: 'POST',
url: '/rpc/select_all', url: '/rpc/select_all',
config: xhr => _state.xhr = xhr,
body: { body: {
endpoint: _configs.endpoint, endpoint: _configs.endpoint,
selects: _configs.selects || [], selects: _configs.selects || [],
@@ -201,13 +233,11 @@ export default (options={}) => {


return data return data
}) })

// clean state
_promise = null
this.reset()
}).finally(() => {
this.clean()
}) })


return _promise
return _state.promise
}, },


export(options={}) { export(options={}) {
@@ -268,6 +298,6 @@ export default (options={}) => {
} }


// initialize model // initialize model
_model.reset()
_model.clean()
return _model return _model
} }

Зареждане…
Отказ
Запис