consul/ui-v2/app/components/data-source/index.js

140 lines
3.8 KiB
JavaScript

import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { set, get } from '@ember/object';
import { schedule } from '@ember/runloop';
/**
* Utility function to set, but actually replace if we should replace
* then call a function on the thing to be replaced (usually a clean up function)
*
* @param obj - target object with the property to replace
* @param prop {string} - property to replace on the target object
* @param value - value to use for replacement
* @param destroy {(prev: any, value: any) => any} - teardown function
*/
const replace = function(
obj,
prop,
value,
destroy = (prev = null, value) => (typeof prev === 'function' ? prev() : null)
) {
const prev = obj[prop];
if (prev !== value) {
destroy(prev, value);
}
return set(obj, prop, value);
};
export default Component.extend({
tagName: '',
data: service('data-source/service'),
dom: service('dom'),
logger: service('logger'),
onchange: function(e) {},
onerror: function(e) {},
loading: 'eager',
isIntersecting: false,
init: function() {
this._super(...arguments);
this._listeners = this.dom.listeners();
this._lazyListeners = this.dom.listeners();
this.guid = this.dom.guid(this);
},
willDestroyElement: function() {
this.actions.close.apply(this);
this._listeners.remove();
this._lazyListeners.remove();
this._super(...arguments);
},
didInsertElement: function() {
this._super(...arguments);
if (this.loading === 'lazy') {
this._lazyListeners.add(
this.dom.isInViewport(this.dom.element(`#${this.guid}`), inViewport => {
set(this, 'isIntersecting', inViewport);
if (!this.isIntersecting) {
this.actions.close.bind(this)();
} else {
this.actions.open.bind(this)();
}
})
);
}
},
didReceiveAttrs: function() {
this._super(...arguments);
if (this.loading === 'eager') {
this._lazyListeners.remove();
}
if (this.loading === 'eager' || this.isIntersecting) {
this.actions.open.apply(this, []);
}
},
actions: {
// keep this argumentless
open: function() {
// get a new source and replace the old one, cleaning up as we go
const source = replace(
this,
'source',
this.data.open(this.src, this, this.open),
(prev, source) => {
// Makes sure any previous source (if different) is ALWAYS closed
this.data.close(prev, this);
}
);
const error = err => {
try {
this.onerror(err);
this.logger.execute(err);
} catch (err) {
this.logger.execute(err);
}
};
// set up the listeners (which auto cleanup on component destruction)
const remove = this._listeners.add(this.source, {
message: e => {
try {
this.onchange(e);
} catch (err) {
error(err);
}
},
error: e => {
if (get(e, 'error.errors.firstObject.status') !== '429') {
error(e);
}
},
});
replace(this, '_remove', remove);
// dispatch the current data of the source if we have any
if (typeof source.getCurrentEvent === 'function') {
const currentEvent = source.getCurrentEvent();
if (currentEvent) {
schedule('afterRender', () => {
try {
this.onchange(currentEvent);
} catch (err) {
error(err);
}
});
}
}
},
// keep this argumentless
close: function() {
if (typeof this.source !== 'undefined') {
this.data.close(this.source, this);
replace(this, '_remove', undefined);
set(this, 'source', undefined);
}
},
},
});