@stratusjs/angularjs
Version:
This is the AngularJS package for StratusJS.
789 lines (787 loc) • 39.2 kB
JavaScript
System.register(["lodash", "@stratusjs/runtime/stratus", "@stratusjs/core/errors/errorBase", "@stratusjs/core/misc", "@stratusjs/core/datastore/modelBase", "@stratusjs/core/environment", "@stratusjs/core/datastore/xhr", "../injector", "./collection", "toastify-js"], function (exports_1, context_1) {
"use strict";
var lodash_1, stratus_1, errorBase_1, misc_1, modelBase_1, environment_1, xhr_1, injector_1, collection_1, toastify_js_1, injector, $rootScope, serviceVerify, ModelOptionKeys, Model;
var __moduleName = context_1 && context_1.id;
return {
setters: [
function (lodash_1_1) {
lodash_1 = lodash_1_1;
},
function (stratus_1_1) {
stratus_1 = stratus_1_1;
},
function (errorBase_1_1) {
errorBase_1 = errorBase_1_1;
},
function (misc_1_1) {
misc_1 = misc_1_1;
},
function (modelBase_1_1) {
modelBase_1 = modelBase_1_1;
},
function (environment_1_1) {
environment_1 = environment_1_1;
},
function (xhr_1_1) {
xhr_1 = xhr_1_1;
},
function (injector_1_1) {
injector_1 = injector_1_1;
},
function (collection_1_1) {
collection_1 = collection_1_1;
},
function (toastify_js_1_1) {
toastify_js_1 = toastify_js_1_1;
}
],
execute: function () {
injector = injector_1.getInjector();
serviceVerify = async () => {
return new Promise(async (resolve, _reject) => {
if ($rootScope) {
resolve(true);
return;
}
if (!injector) {
injector = injector_1.getInjector();
}
if (injector) {
$rootScope = injector.get('$rootScope');
}
if ($rootScope) {
resolve(true);
return;
}
setTimeout(() => {
if (environment_1.cookie('env')) {
console.log('wait for $rootScope service:', {
$rootScope
});
}
serviceVerify().then(resolve);
}, 250);
});
};
exports_1("ModelOptionKeys", ModelOptionKeys = ["autoSave", "autoSaveInterval", "autoSaveHalt", "collection", "completed", "manifest", "serviceId", "stagger", "target", "targetSuffix", "toast", "type", "urlRoot", "urlSync", "watch", "withCredentials", "payload", "convoy", "headers", "ignoreKeys", "received"]);
Model = class Model extends modelBase_1.ModelBase {
name = 'Model';
target = null;
type = null;
manifest = false;
stagger = false;
toast = true;
identifier = null;
urlRoot = '/Api';
targetSuffix = null;
serviceId = null;
header = new modelBase_1.ModelBase();
meta = new modelBase_1.ModelBase();
route = new modelBase_1.ModelBase();
collection = null;
xhr;
withCredentials = false;
headers = {};
pending = false;
error = false;
completed = false;
saving = false;
changedExternal = false;
watch = true;
status = null;
autoSave = false;
autoSaveInterval = 4000;
autoSaveHalt = true;
autoSaveTimeout = null;
urlSync = false;
bracket = {
match: /\[[\d+]]/,
search: /\[([\d+])]/g,
attr: /(^[^[]+)/
};
throttle = lodash_1.throttle(this.fetch, 1000);
initialize = null;
constructor(options = {}, attributes) {
super(attributes);
options = typeof options !== 'object' ? {} : options;
options.received = options.received || false;
lodash_1.extend(this, this.sanitizeOptions(options));
if (options.convoy) {
const convoy = misc_1.isJSON(options.convoy) ? JSON.parse(options.convoy) : options.convoy;
if (lodash_1.isObject(convoy)) {
this.meta.set(convoy.meta || {});
const payload = convoy.payload;
if (lodash_1.isObject(payload)) {
lodash_1.extend(this.data, payload);
this.completed = true;
options.received = true;
}
else {
console.error('malformed payload:', payload);
}
}
else {
console.error('malformed convoy:', convoy);
}
}
if (options.payload) {
const payload = misc_1.isJSON(options.payload) ? JSON.parse(options.payload) : options.payload;
if (lodash_1.isObject(payload)) {
lodash_1.extend(this.data, payload);
this.completed = true;
options.received = true;
}
else {
console.error('malformed payload:', payload);
}
}
this.header = new modelBase_1.ModelBase();
this.meta = new modelBase_1.ModelBase();
this.route = new modelBase_1.ModelBase();
if (!lodash_1.isEmpty(this.collection)) {
if (this.collection.target) {
this.target = this.collection.target;
}
if (this.collection.meta.has('api')) {
this.meta.set('api', this.collection.meta.get('api'));
}
}
this.recv = options.received ? lodash_1.cloneDeep(this.data) : {};
this.sent = {};
this.ignoreKeys = options.ignoreKeys || ['$$hashKey'];
if (this.target) {
this.urlRoot += '/' + misc_1.ucfirst(this.target);
}
const that = this;
this.initialize = lodash_1.once(this.initialize || function defaultInitializer() {
if (that.completed && (that.watch || that.autoSave)) {
that.watcher().then();
}
if (that.manifest && !that.getIdentifier()) {
that.sync('POST', that.meta.has('api') ? {
meta: that.meta.get('api'),
payload: {}
} : {}).catch(async (error) => {
console.error('MANIFEST:', error);
if (!that.toast) {
return;
}
const errorMessage = that.errorMessage(error);
const formatMessage = errorMessage ? `: ${errorMessage}` : '.';
toastify_js_1.default({
text: `Unable to Manifest ${that.target}${formatMessage}`,
duration: 12000,
close: true,
stopOnFocus: true,
style: {
background: '#E14D45',
}
}).showToast();
that.errorMessage(error);
});
}
});
if (!this.stagger) {
this.initialize();
}
}
resetXHRFlags() {
this.pending = false;
this.saving = false;
}
sanitizeOptions(options) {
const sanitizedOptions = {};
lodash_1.forEach(ModelOptionKeys, (key) => {
const data = lodash_1.get(options, key);
if (lodash_1.isUndefined(data)) {
return;
}
lodash_1.set(sanitizedOptions, key, data);
});
return sanitizedOptions;
}
async watcher() {
if (this.watching) {
return true;
}
this.watching = true;
if (!$rootScope) {
await serviceVerify();
}
$rootScope.$watch(() => this.data, (_newData, _priorData) => this.handleChanges(), true);
}
handleChanges(changeSet) {
const isUserChangeSet = lodash_1.isUndefined(changeSet);
if (isUserChangeSet) {
changeSet = super.handleChanges();
}
if (!changeSet || lodash_1.isEmpty(changeSet)) {
return changeSet;
}
if (this.error && !this.completed && this.getIdentifier()) {
const action = isUserChangeSet ? 'save' : 'sync url for';
console.warn(`Blocked attempt to ${action} a persisted model that has not been fetched successfully.`);
return;
}
if (!isUserChangeSet) {
if (environment_1.cookie('env')) {
console.info('Attempting URL Sync for non-User ChangeSet:', changeSet);
}
}
if (this.urlSync) {
if (lodash_1.get(changeSet, 'id')) {
const newUrl = misc_1.setUrlParams({
id: lodash_1.get(changeSet, 'id') || this.getIdentifier()
});
if (newUrl !== document.location.href) {
window.location.replace(newUrl);
}
}
const version = misc_1.getAnchorParams('version');
const versionId = !lodash_1.isEmpty(version) ? parseInt(version, 10) : 0;
if (versionId && versionId !== lodash_1.get(changeSet, 'version.id')) {
if (environment_1.cookie('env')) {
console.warn('replacing version:', versionId);
}
}
}
if (!isUserChangeSet) {
return;
}
this.saveIdle();
this.throttleTrigger('change', this);
if (this.collection) {
this.collection.throttleTrigger('change', this);
}
return changeSet;
}
getIdentifier() {
return (this.identifier = this.get('id') || this.route.get('identifier') || this.identifier);
}
getType() {
return (this.type = this.type || this.target || 'orphan');
}
getHash() {
return this.getType() + (lodash_1.isNumber(this.getIdentifier()) ? this.getIdentifier().toString() : this.getIdentifier());
}
isNew() {
return !this.getIdentifier();
}
url() {
let url = this.getIdentifier() ? `${this.urlRoot}/${this.getIdentifier()}` : `${this.urlRoot}${this.targetSuffix || ''}`;
if (misc_1.getUrlParams('version')) {
url += url.includes('?') ? '&' : '?';
url += 'options[version]=' + misc_1.getUrlParams('version');
}
return url;
}
serialize(obj, chain) { return misc_1.serializeUrlParams(obj, chain); }
sync(action, data, options) {
this.pending = true;
this.trigger('change', this);
if (this.collection) {
this.collection.pending = true;
this.collection.throttleTrigger('change');
}
this.sent = lodash_1.cloneDeep(this.data);
return new Promise(async (resolve, reject) => {
action = action || 'GET';
options = options || {};
const request = {
method: action,
url: this.url(),
headers: lodash_1.clone(this.headers),
withCredentials: this.withCredentials,
};
if (!lodash_1.isUndefined(data)) {
if (['GET', 'DELETE'].includes(action)) {
if (lodash_1.isObject(data) && Object.keys(data).length) {
request.url += request.url.includes('?') ? '&' : '?';
request.url += this.serialize(data);
}
}
else {
request.headers['Content-Type'] = 'application/json';
request.data = data;
}
}
if (environment_1.cookie('env')) {
console.log('Prototype:', request);
}
if (Object.prototype.hasOwnProperty.call(options, 'headers') && typeof options.headers === 'object') {
Object.keys(options.headers).forEach((headerKey) => {
request.headers[headerKey] = options.headers[headerKey];
});
}
this.xhr = new xhr_1.XHR(request);
this.xhr.send().then((response) => {
this.status = this.xhr.status;
if (this.watch || this.autoSave) {
this.watcher();
}
const propagateError = () => {
this.error = true;
this.resetXHRFlags();
if (this.collection) {
this.collection.pending = false;
}
this.trigger('error', this);
this.trigger('complete', this);
if (this.collection instanceof collection_1.Collection) {
this.collection.throttleTrigger('change');
}
};
if (!lodash_1.isObject(response) && !lodash_1.isArray(response)) {
const error = new errorBase_1.ErrorBase({
payload: response,
message: `Invalid Payload: ${request.method} ${request.url}`
}, {});
propagateError();
reject(error);
return;
}
this.header.set(this.xhr.getAllResponseHeaders() || {});
this.meta.set(response.meta || {});
this.route.set(response.route || {});
const payload = response.payload || response;
this.error = false;
if ((this.meta.has('success') && !this.meta.get('success'))) {
this.error = true;
}
else if (lodash_1.isArray(payload) && payload.length) {
this.recv = lodash_1.head(payload);
}
else if (lodash_1.isObject(payload) && !lodash_1.isArray(payload)) {
this.recv = payload;
}
else {
if (!this.meta.has('status') && !this.meta.has('success')) {
this.error = true;
}
console.warn(`Invalid Payload: ${request.method} ${request.url}`);
}
if (this.error) {
const error = new errorBase_1.ErrorBase({
payload,
message: `Invalid Payload: ${request.method} ${request.url}`
}, {});
propagateError();
reject(error);
return;
}
const incomingChangeSet = this.completed ? lodash_1.cloneDeep(misc_1.patch(this.recv, this.sent)) : {};
if (!lodash_1.isEmpty(incomingChangeSet)) {
if (environment_1.cookie('env')) {
console.log('Incoming ChangeSet detected:', environment_1.cookie('debug_change_set')
? JSON.stringify(incomingChangeSet)
: incomingChangeSet);
}
this.handleChanges(incomingChangeSet);
}
const intermediateData = lodash_1.cloneDeep(this.recv);
const intermediateChangeSet = lodash_1.cloneDeep(misc_1.patch(this.data, this.sent));
if (!lodash_1.isEmpty(intermediateChangeSet)) {
if (environment_1.cookie('env')) {
console.log('Intermediate ChangeSet detected:', environment_1.cookie('debug_change_set')
? JSON.stringify(intermediateChangeSet)
: intermediateChangeSet);
}
lodash_1.forEach(intermediateChangeSet, (element, key) => {
lodash_1.set(intermediateData, key, element);
});
}
this.data = lodash_1.cloneDeep(intermediateData);
this.changed = false;
this.changedExternal = false;
this.saving = false;
this.handleChanges();
this.patch = {};
this.resetXHRFlags();
this.completed = true;
if (this.collection) {
this.collection.pending = false;
}
this.meta.clearTemp();
this.trigger('success', this);
this.trigger('change', this);
this.trigger('complete', this);
if (this.collection instanceof collection_1.Collection) {
this.collection.throttleTrigger('change');
}
resolve(this.data);
return;
})
.catch((error) => {
this.status = 500;
this.error = true;
this.resetXHRFlags();
console.error(`XHR: ${request.method} ${request.url}`, error);
reject(error);
return;
});
});
}
fetch(action, data, options) {
return new Promise(async (resolve, reject) => {
this.sync(action, data || this.meta.get('api'), options)
.then(resolve)
.catch(async (error) => {
this.status = 500;
this.error = true;
this.resetXHRFlags();
console.error('FETCH:', error);
if (!this.toast) {
reject(error);
return;
}
const errorMessage = this.errorMessage(error);
const formatMessage = errorMessage ? `: ${errorMessage}` : '.';
toastify_js_1.default({
text: `Unable to Fetch ${this.target}${formatMessage}`,
duration: 12000,
close: true,
stopOnFocus: true,
style: {
background: '#E14D45',
}
}).showToast();
reject(error);
return;
});
});
}
save(options) {
this.saving = true;
options = options || {};
if (!lodash_1.isObject(options)) {
console.warn('invalid options supplied:', options);
options = {};
}
if (lodash_1.has(options, 'force') && options.force) {
options.patch = lodash_1.has(options, 'patch') ? options.patch : false;
return this.doSave(options);
}
if (!this.isNew() && (this.pending || !this.completed || lodash_1.isEmpty(this.toPatch()))) {
console.warn(`Blocked attempt to save ${lodash_1.isEmpty(this.toPatch()) ? 'an empty payload' : 'a duplicate XHR'} to a persisted model.`);
return new Promise((resolve, _reject) => {
this.saving = false;
resolve(this.data);
});
}
return this.doSave(options);
}
doSave(options) {
options = options || {};
if (!lodash_1.isObject(options)) {
console.warn('invalid options supplied:', options);
options = {};
}
options.patch = lodash_1.has(options, 'patch') ? options.patch : true;
return new Promise(async (resolve, reject) => {
this.sync(this.getIdentifier() ? 'PUT' : 'POST', this.toJSON({
patch: options.patch
}))
.then(resolve)
.catch(async (error) => {
this.error = true;
this.resetXHRFlags();
console.error('SAVE:', error);
if (!this.toast) {
reject(error);
return;
}
const errorMessage = this.errorMessage(error);
const formatMessage = errorMessage ? `: ${errorMessage}` : '.';
toastify_js_1.default({
text: `Unable to Save ${this.target}${formatMessage}`,
duration: 12000,
close: true,
stopOnFocus: true,
style: {
background: '#E14D45',
}
}).showToast();
reject(error);
return;
});
});
}
saveIdle() {
if (this.autoSaveTimeout) {
clearTimeout(this.autoSaveTimeout);
}
if (this.pending || !this.completed || this.isNew() || lodash_1.isEmpty(this.toPatch())) {
return;
}
if (this.autoSaveHalt && !this.autoSave) {
return;
}
this.autoSaveTimeout = setTimeout(() => {
if (!this.autoSaveHalt && !this.autoSave) {
this.saveIdle();
return;
}
this.save().then();
}, this.autoSaveInterval);
}
throttleSave() {
return new Promise((resolve, reject) => {
const request = this.throttle();
console.log('throttle request:', request);
request.then((data) => {
console.log('throttle received:', data);
resolve(data);
}).catch(reject);
});
}
toJSON(options) {
options = options || {};
if (!lodash_1.isObject(options)) {
options = {};
}
options.patch = (options.patch && !this.isNew());
let data = super.toJSON(options);
const metaData = this.meta.get('api');
if (metaData) {
data = {
meta: metaData,
payload: data
};
}
return data;
}
buildPath(path) {
const acc = [];
if (!lodash_1.isString(path)) {
return acc;
}
let cur;
let search;
lodash_1.forEach(path.split('.'), (link) => {
if (link.match(this.bracket.match)) {
cur = this.bracket.attr.exec(link);
if (cur !== null) {
acc.push(cur[1]);
cur = null;
}
else {
cur = false;
}
search = this.bracket.search.exec(link);
while (search !== null) {
if (cur !== false) {
cur = parseInt(search[1], 10);
if (!isNaN(cur)) {
acc.push(cur);
}
else {
cur = false;
}
}
search = this.bracket.search.exec(link);
}
}
else {
acc.push(link);
}
});
return acc;
}
get(attr) {
if (typeof attr !== 'string' || !this.data || typeof this.data !== 'object') {
return undefined;
}
return lodash_1.get(this.data, attr);
}
find(attr, key, value) {
if (typeof attr === 'string') {
attr = this.get(attr);
}
return !lodash_1.isArray(attr) ? attr : attr.find((obj) => obj[key] === value);
}
set(attr, value) {
if (!attr) {
console.warn('No attr for model.set()!');
return this;
}
if (typeof attr === 'object') {
lodash_1.forEach(attr, (v, k) => this.setAttribute(k, v));
return this;
}
this.setAttribute(attr, value);
return this;
}
setAttribute(attr, value) {
if (typeof attr !== 'string') {
console.warn('Malformed attr for model.setAttribute()!');
return false;
}
lodash_1.set(this.data, attr, value);
this.throttleTrigger('change', this);
this.throttleTrigger(`change:${attr}`, value);
}
toggle(attribute, item, options) {
if (typeof options === 'object' &&
!lodash_1.isUndefined(options.multiple) &&
lodash_1.isUndefined(options.strict)) {
options.strict = true;
}
options = lodash_1.extend({
multiple: true
}, lodash_1.isObject(options) ? options : {});
const request = attribute.split('[].');
let target = this.get(request.length > 1 ? request[0] : attribute);
if (lodash_1.isUndefined(target) ||
(options.strict && lodash_1.isArray(target) !==
options.multiple)) {
target = options.multiple ? [] : null;
this.set(request.length > 1 ? request[0] : attribute, target);
}
if (lodash_1.isArray(target)) {
if (lodash_1.isUndefined(item)) {
this.set(attribute, null);
}
else if (!this.exists(attribute, item)) {
target.push(item);
}
else {
lodash_1.forEach(target, (element, key) => {
const child = (request.length > 1 &&
typeof element === 'object' && request[1] in element)
? element[request[1]]
: element;
const childId = (typeof child === 'object' && child.id)
? child.id
: child;
const itemId = (typeof item === 'object' && item.id)
? item.id
: item;
if (childId === itemId || (lodash_1.isString(childId) && lodash_1.isString(itemId) && misc_1.strcmp(childId, itemId) === 0)) {
target.splice(key, 1);
}
});
}
}
else if (typeof target === 'object' || typeof target === 'number') {
this.set(attribute, !this.exists(attribute, item) ? item : null);
}
else if (lodash_1.isUndefined(item)) {
this.set(attribute, !target);
}
return this.get(attribute);
}
pluck(attr) {
if (typeof attr !== 'string' || attr.indexOf('[].') === -1) {
return this.get(attr);
}
const request = attr.split('[].');
if (request.length <= 1) {
return undefined;
}
attr = this.get(request[0]);
if (!attr || !lodash_1.isArray(attr)) {
return undefined;
}
const list = lodash_1.filter(lodash_1.map(attr, (element) => lodash_1.get(element, request[1])));
return list.length ? list : undefined;
}
exists(attribute, item) {
if (!item) {
attribute = this.get(attribute);
return typeof attribute !== 'undefined' && attribute;
}
if (typeof attribute === 'string' && item) {
attribute = this.pluck(attribute);
if (lodash_1.isArray(attribute)) {
return typeof attribute.find((element) => element === item || ((typeof element === 'object' && element.id && element.id === item) || lodash_1.isEqual(element, item))) !== 'undefined';
}
return attribute === item || (typeof attribute === 'object' && attribute.id && (lodash_1.isEqual(attribute, item) || attribute.id === item));
}
return false;
}
destroy() {
if (this.isNew()) {
return new Promise((resolve, _reject) => {
this.throttleTrigger('change');
if (this.collection) {
this.collection.remove(this);
}
resolve(this.data);
});
}
return new Promise(async (_resolve, reject) => {
let deleteData = {};
if (!lodash_1.isEmpty(this.meta.get('api'))) {
deleteData = this.meta.get('api');
}
this.sync('DELETE', deleteData)
.then((_data) => {
if (this.error) {
reject(this.error);
return;
}
this.throttleTrigger('change');
if (this.collection) {
this.collection.remove(this);
}
_resolve(this.data);
})
.catch(async (error) => {
this.error = true;
this.resetXHRFlags();
console.error('DESTROY:', error);
if (!this.toast) {
reject(error);
return;
}
const errorMessage = this.errorMessage(error);
const formatMessage = errorMessage ? `: ${errorMessage}` : '.';
toastify_js_1.default({
text: `Unable to Delete ${this.target}${formatMessage}`,
duration: 12000,
close: true,
stopOnFocus: true,
style: {
background: '#E14D45',
}
}).showToast();
reject(error);
return;
});
});
}
errorMessage(error) {
if (error instanceof errorBase_1.ErrorBase) {
console.error(`[${error.code}] ${error.message}`, error);
return error.code !== 'Internal' ? error.message : null;
}
const digest = (error.responseText && misc_1.isJSON(error.responseText)) ? JSON.parse(error.responseText) : null;
if (!digest) {
return null;
}
const message = lodash_1.get(digest, 'meta.status[0].message') || lodash_1.get(digest, 'error.exception[0].message') || null;
if (!message) {
return null;
}
if (!environment_1.cookie('env') && lodash_1.has(digest, 'error.exception[0].message')) {
console.error('[xhr] server:', message);
return null;
}
return message;
}
};
exports_1("Model", Model);
stratus_1.Stratus.Services.Model = [
'$provide', ($provide) => {
$provide.factory('Model', [
() => {
return Model;
}
]);
}
];
stratus_1.Stratus.Data.Model = Model;
}
};
});
//# sourceMappingURL=model.js.map