Skip to content

Instantly share code, notes, and snippets.

@piboistudios
Last active December 9, 2018 09:40
Show Gist options
  • Save piboistudios/46911dd3a7690b7d21637dbcd9c4d7f2 to your computer and use it in GitHub Desktop.
Save piboistudios/46911dd3a7690b7d21637dbcd9c4d7f2 to your computer and use it in GitHub Desktop.
ViewData Mixin for Vue.js; requires axios
const axios = require('axios').default;
const AxiosTransformer = (data, headers) => data
const consume = value => {
if (value instanceof Function) {
return value();
}
else return value
}
const propOrOther = (vm, value) => {
const otherName = `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
return vm[otherName] !== undefined ? vm[otherName] : vm[value];
}
const consumePropOrOther = (vm, value) => {
return consume(propOrOther(vm, value));
}
const defaultNothing = () => ({
type: Object | Function,
default: () => null
});
const defaultTransformation = () => ({
type: Function | Array,
default: AxiosTransformer
});
const data = () => ({
view: {
data: null
},
internal: {
busy: false
}
});
const viewDataMixin = {
props: {
apiUrl: String | Function,
method: {
type: String | Function,
default: 'get',
},
baseURL: Function,
transformRequest: defaultTransformation(),
transformResponse: defaultTransformation(),
headers: Object | Function,
params: defaultNothing(),
data: defaultNothing(),
auth: defaultNothing(),
lazy: {
type: Boolean | Function,
default: true,
},
debounceTime: {
type: Number | Function,
default: -1
},
concatenate: Boolean | Function,
onError: {
type: Function,
default: console.error
},
},
computed: {
ViewData() {
const retVal = this.view.data;
if (!retVal && consumePropOrOther(this, "lazy")) this.GetViewData();
return retVal || []
},
Busy() {
return this.internal.busy;
}
},
data,
methods: {
Reset(cfg = { keepData: true, retry: false }) {
if (consumePropOrOther(this, "debounceTime") < 0) this.internal.busy = false;
const { keepData, retry } = cfg;
!keepData && (this.view.data = null);
retry && this.GetViewData();
return;
},
AddViewData(data) {
this.view.data instanceof Array && this.view.data.push(data);
},
RemoveViewData(query, count = 1) {
if (!this.view.data instanceof Array) return;
for (let index in this.view.data) {
const object = this.view.data[index];
const match = true;
const keys = Object.keys(query);
for (let index in keys) {
const key = keys[index];
if (object[key] !== query[key]) {
match = false;
break;
};
}
if (match) {
this.view.data.splice(index, 1);
if (--count === 0) break;
}
};
},
GetViewData() {
if (this.internal.busy) return;
this.internal.busy = true;
const debounceTime = consumePropOrOther(this, "debounceTime");
if (debounceTime > -1) {
setTimeout(() => (this.internal.busy = false), debounceTime);
}
axios.request({
url: consumePropOrOther(this, "apiUrl"),
method: consumePropOrOther(this, "method"),
baseURL: consumePropOrOther(this, "baseUrl"),
transformRequest: propOrOther(this, "transformRequest"),
transformResponse: propOrOther(this, "transformResponse"),
headers: consumePropOrOther(this, "headers"),
params: consumePropOrOther(this, "params"),
data: consumePropOrOther(this, "data"),
auth: consumePropOrOther(this, "auth")
})
.then(response => {
if (debounceTime < 0) this.internal.busy = false;
const { data } = response;
if (data instanceof Array && consumePropOrOther(this, "concatenate")) {
if (!this.view.data) this.view.data = [];
this.view.data = this.view.data.concat(data);
}
else {
this.view.data = data;
}
}).catch(propOrOther(this, "onError"));
}
}
}
export default viewDataMixin;
/* example usage:
<my-component
api-url="/api/people"
:on-error="err => Reset({keepData: false, retry: true})"
:params="{personNamed: 'piboi'}"
:transform-response="(data, headers) => Object.assign({headers}, data)"
/>
MyComponent.vue:
<template>
<div class="my-component">
My name is {{ ViewData.name }}
</div>
</template>
<script>
import ViewDataMixin from 'ViewDataMixin';
export default: {
mixins: [ViewDataMixin]
}
</script>
*/
@piboistudios
Copy link
Author

image
image

@piboistudios
Copy link
Author

piboistudios commented Dec 8, 2018

Also supports debouncing:

<my-component
  api-url="/api/i-always-fail-but-you-can-retry-if-you-want-but-please-dont-spam-me"
  :on-error="err => Reset({keepData: false, retry: true})"
  debounce-time="5000"
/>

If you don't use debouncing, it will instantly retry when a response is received.

@piboistudios
Copy link
Author

piboistudios commented Dec 8, 2018

All props can be functions that evaluate to a value of their expected value-type:

<template>
  <my-component
    :api-url="getApiUrl"
    :on-error="err => Reset()"
  />
</template>
<script>
 methods: {
    getApiUrl() {
      if(new Date().getDay() === 6) return '/api/only-on-saturdays';
      else return '/api/business-as-usual'
    }
 }
}
</script>

@piboistudios
Copy link
Author

piboistudios commented Dec 9, 2018

By default, using the ViewData property lazy loads the data.

You can disable this with prop ":lazy="false":

<my-component
  :lazy="false"
  api-url="/api/people"
  :on-error="err => Reset()"
/>

MyComponent.vue:

<template>
  <form @submit.prevent="GetViewData">
     <input v-model="query"/>
  </form>
  <ul v-if="ViewData.length">
    <li v-for="(element, index) in ViewData" :key="index" v-text="element"></li>
  </ul>
</template>
<script>
export default {
  computed: {
    params() {
       return { query: this.query};
    }
  },
  data() {
    return {
      query: "",
    }
  }
}
</script>

@piboistudios
Copy link
Author

piboistudios commented Dec 9, 2018

You can alternatively override the props inside of the component itself by capitalizing the first letter:
MyComponent.vue:

import ViewData from 'Mixins/ViewData'
export default {
  mixins: [ViewData],
  computed: {
    ApiUrl() {
      return new Date().getDay() === 6 ? '/api/only-on-saturdays' : '/api/business-as-usual';
    },
    OnError() {
      return () => this.Reset();
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment