The current situation
I have implemented a Vue plugin that manages the calls to the server using queues.
I have an auto-save UI that whenever user leaves an input the Client Side code sends the UiState to the server for storing in the DB. Since the user may enter and leave many inputs in short time the Ajax Requests might not received from the server in sequence.
Since I send the complete UiState object and the server might not receive the requests in the same order as send, I might not save the last change at the server but a previous.
This is why I implemented the following plugin for Vue. It uses a Queue and if by the time the server responds to the save request a new save request has been triggered, it sends only the latest save-request.
import axios from "Axios"
import qs from 'qs';
export default {
install(Vue, pluginOptions) {
var ajs = {}; //ajax-saving
var defaultPluginOptions = {
indicatorElement: null
};
// saving flags
ajs.isSaving = false;
ajs.pendingSaving = false;
ajs.pendingRequests = [];
// the indicator element
ajs.indicator = null;
ajs.indicatorTitle = null;
Vue.prototype.$ajaxSaveData = function (serverUrl, data, callback) {
//use pending flag for the function to recall itself when it finishes
if (ajs.isSaving) {
ajs.pendingSaving = true;
ajs.pendingRequests.push({ serverUrl: serverUrl, data: data, callback: callback });
console.log('%cAjaxSaving %cThe save action is queued!', 'color:#67A9BF;', 'color:#E3DC29');
return;
}
onSaveStarted();
axios({
url: serverUrl,
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data: qs.stringify(data),
})
.then(result => {
onSaveEnded();
if (callback) callback(true, result);
})
.catch(function (error) {
console.log(error);
onSaveEnded(true);
if (callback) callback(false);
})
}
// saving events
var onSaveStarted = function () {
ajs.isSaving = true;
ajs.indicator.removeClass('error');
ajs.indicator.addClass('saving');
ajs.indicator.show();
ajs.indicatorTitle.html("Saving");
console.log('%cAjaxSaving %cStarted!', 'color:#67A9BF;', 'color:#7DBF67');
}
var onSaveEnded = function (error) {
ajs.isSaving = false;
ajs.indicator.removeClass('saving');
if (error) {
ajs.indicator.addClass('error');
ajs.indicatorTitle.html("Error");
console.log('%cAjaxSaving %cError!', 'color:#67A9BF;', 'color:#E60909');
} else {
ajs.indicatorTitle.html("Saved");
}
console.log('%cAjaxSaving %cEnded!', 'color:#67A9BF;', 'color:#091152');
// execute the pending savings
if (ajs.pendingSaving) {
var pendingToProcess = ajs.pendingRequests;
ajs.pendingRequests = [];
ajs.pendingSaving = false;
console.log('%cAjaxSaving %cExecuting Pending!', 'color:#67A9BF;', 'color:#E83CC5');
// process all pending request found in stack.
// skip older requests if newer are found
var req;
var processedRequests = [];
while ((req = pendingToProcess.pop()) != null) {
if (!processedRequests.includes(req.serverUrl)) {
processedRequests.push(req.serverUrl);
var executingReq = req;
setTimeout(function () {
Vue.prototype.$ajaxSaveData(executingReq.serverUrl, executingReq.data, executingReq.callback);
}, 0);
}
}
}
}
var testOptions = function () {
if (!ajs.options.indicatorElement) {
throw ('indicator element is not set');
}
}
//TODO: Remove jquery
// Plugin initialization
var createIndicator = function () {
ajs.indicator = $(ajs.options.indicatorElement);
ajs.indicatorTitle = ajs.indicator.find('span');
if (ajs.indicatorTitle.length === 0) {
ajs.indicatorTitle = $('');
ajs.indicator.append(ajs.indicatorTitle);
}
}
var initialize = function () {
//initialize options and elements needed from plugin
ajs.options = $.extend({}, defaultPluginOptions, pluginOptions);
testOptions();
createIndicator();
};
initialize();
}
}
To use this:
import Vue from "vue";
import VueAjaxSaving from '@/Plugins/VueAjaxSaving'
Vue.use(VueAjaxSaving, { indicatorElement: '#ajaxIndicator' });
new Vue({
data: {
uiState: {....}
},
methods:{
updateUiState() {
this.$ajaxSaveData('/Page/UpdateUiState', { uiState: this.uiState }, () => this.someCallback());
},
}
})
The plugin takes as parameter an element(the jQuery selector) and updates the ui with the saving state.
The need
I am refactoring my code to use Vuex for my state and I want the Vuex module to handle saving when something is mutated.
I want to provide my Vuex Modules a function that handles the save in the same way my Vue components did. With the ease of calling ajaxSaveData('url',data).then(...) from any Vuex action.
I need to update a UI part with the saving status.
The questions
What do I have to use to fill my needs?
Vuex plugin?
A class function that is imported within the Module file and used as dirty as it sounds?
A new module that contains all the saving logic ajaxSaveData? I suppose any module can dispatch it's actions.
How to update the UI for the ajaxSaveData Vuex (plugin/Class/Module)?