@billow/vue-utilities
v1.0.2
Published
Useful set of utilities for Vue apps. Under continuous development.
Downloads
8
Maintainers
Readme
Vue Utiities, by Billow
A useful set of utilities designed specifically for Vue apps. Includes the following features:
- Handy set of filters.
- Simple model class for your Vuex stores. Need a full-fledged ORM? Try vue-orm.
- Dot-notation-based mutation helpers for Vuex.
- Passport, a basic local-storage-based OAuth client manager, for general use with a password grant client. Need to authenticate with third-parties? Try vue-authenticate
- Factories: auto-register components (static or lazy) and Vuex stores (supports sync-helpers and nested mutations).
- FormData (as well as a type-safe counterpart that converts non-files to a JSON payload that your backend will need to translate when compiling a request).
- String, Object and Array helpers.
💡 WIP: These docs are a work in progress, and may be moved to a dedicated site at a later stage.
Filters
abbreviateCount
Shorten long numbers into their abbreviated counter-parts.
(input, precision = 1)
2500 | abbreviateCount // 2.5k
25000 | abbreviateCount // 25k
250000 | abbreviateCount // 250k
2510000 | abbreviateCount // 2.5m
2510000 | abbreviateCount(2) // 2.51m
accounting
Format numbers in accounting format, using declarative rules. Uses accounting-big.
(currency = 'R', precision = 2, thousandsSeparator = ',', precisionSeparator = '.', format = '%s %v')
2000 | accounting // R 2,000.00
2000 | accounting('$') // $ 2,000.00
15982.115 | accounting('R', 3) // R 15,982.115
2000 | accounting('R', 2, ' ') // R 2 000.00
2000 | accounting('€', 2, '.', ',') // € 2.000,00
2000 | accounting('CAD', 2, ',', '.', '%v %s') // 2,000.00 CAD
calendar
Formats date objects using a calendar format. Uses moment.calendar internally.
(sameElse = 'D MMMM Y', utc = false)
date | calendar
date | calendar('DD/MM/YYYY')
date | calendar('DD/MM/YYYY', true) // uses a UTC offset
convertBreaks
Convert line breaks to HTML breaks
<div v-html="$options.filters.nl2br(multilineString)"></div>
currency
Format numbers as currency using the native toLocaleString
. Uses the number
if not using Intl
-based formatting (which requires three currency symbol characters).
(currency = 'R', locale = 'en-GB', minimumFractionDigits = 2, maximumFractionDigits = 2)
2000 | currency // R 2,000.00
2000 | currency('$') // $ 2,000.00
123456.789 | currency('₹', 'en-IN') // ₹ 1,23,456.79
Note: This filter is a mix-match between
Intl.NumberFormat
and manual currency-symbol concatenation. Unfortunately, the spec forIntl
uses a non-standard format for South African currencies (1 000,00 as opposed to 1,000.00). If you’d prefer to use the Intl spec for formatting currencies, simply provide the three-digit currency code instead of the symbol and the applicable locale. If this route is taken, all rules related to Intl.NumberFormat apply.
2000 | currency('ZAR', 'en-ZA')
2000 | currency('ZAR')
date
Format a date using moment.format.
format = 'Y-MM-DD', utc = false
date | date
date | date('DD/MM/YYYY')
date | date('DD/MM/YYYY', true) // uses a UTC offset
filesize
Given a filesize in bytes, format the size.
base = 2, exponent = -1
38889823 | filesize // 37.09 MB
38889823 | filesize(2, 3) // 0.04 GB
format
Format a string by replacing placeholders (in :placeholder
or {placeholder}
format, by default) within it.
(format|object)
, where the argument is the format string and the input is the object from which data is obtained, or vice-versa.
user | format('{name} has role of :role') // Mike has a role of member
':name has role of {role}' | format(user) // Mike has a role of member
Where:
user = {
name: 'Mike',
role: 'member'
}
If you’d like to use custom container formats, you can specify them in the second argument as an array of strings:
'(name) has a role of *role' | format({ name: 'Mike', role: 'admin }, ['(|)', '*'])
Each format may be separated by a pipe if you’d like to surround the entire placeholder. Either side of the pipe may have many characters as needed. Where a pipe is not specified, the entire string will be used for the left of the placeholder.
fromNow
Return a date in human-friendly format.
(suffix = true, utc = false)
'2018-09-10' | fromNow // in 4 months
'2018-05-24 07:24:00' | fromNow // 4 minutes ago
'2018-05-24 07:24:00' | fromNow(false) // 4 minutes
'2018-05-24 07:24:00' | fromNow(false, false) // uses a UTC offset
get
Using dot-notation, get the value of the key/path provided from an object. If the key does not exist, the path will be printed instead, failing which a fallback can be set.
(path, fallback = undefined)
user | get('profile.avatar.url') // https://test.test/img.jpg
user | get('profile.avatar.nothing') // profile.avatar.nothing
user | get('profile.avatar.nothing', 'Not set') // Not set
Where:
let user = {
profile: {
avatar: {
url: 'https://test.test/img.jpg'
}
}
}
lcfirst
Return the input with the first character in lower case.
'Howzit' | lcfirst // howzit
lcwords
Return the input with the first character in lower case.
'No Howzit' | lcfirst // no howzit
log
Log the input to the console.
something.to.log | log
number
Similar to the currency
filter, but without the currency symbol. The currency filter relies on the number filter for locale-based formatting.
(minimumFractionDigits = 2, maximumFractionDigits = 2, locale = 'en-GB')
2000 | number // 2,000.00
2000.211 | number(3) // 2,000.211
2000.21109 | number(3, 5) // 2,000.21109
12345678.7891 | number(3, 5, 'en-IN') // 1,23,456.7891
or
Similar to using the logical-or (a || b
, where a
is falsy for example), only or
uses a stricter truthy-check, unless loose is true.
(fallback, loose = false)
undefined | or('n/a')
null | or('n/a')
'' | or('n/a')
0 | or('n/a') // 0
0 | or('n/a', true) // n/a
false | or('n/a') // false
false | or('n/a', true) // n/a
ordinal
Return the ordinal of the input.
1 | ordinal // 1st
2 | ordinal // 2nd
3 | ordinal // 3rd
4 | ordinal // 4th
plain
Convert HTML to plain text.
<div>body | plain</div>
<!-- In laoreet exercitation? Accusantium, pede? Nascetur, [...] -->
export default {
data: () => ({
body: '<p>In laoreet exercitation? Accusantium, pede? <br><br>' +
'Nascetur, aspernatur pretium feugiat, nullam rerum suscipit pulvinar' +
'dignissimos nulla sint, rerum dolorum fames hac.</p>'
})
}
plural
Pluralise a word when the input is a number greater than 1. Arguments are swappable.
(count|singular)
, where the argument is the singular word and the input is the count, or vice-versa.
1 | plural('apple') // apple
2 | plural('apple') // apples
1 | plural('fish') // fish
2 | plural('fish') // fish
'apple' | plural(1)
'apple' | plural(2)
'fish' | plural(1)
'fish' | plural(2)
replace
Pass the input string to a String.prototype.replace
.
(string, replace)
'foo bar' | replace('foo', 'boo') // boo bar
'A123E' | replace(/\d/, '*') // A*23E
'A123E' | replace(/\d/g, '*') // A***E
toFixed
Convert the input to a number and apply fixed precision
(precision = 2)
"2.983726" | toFixed // 2.98
truncate
Truncate the input to the nearest word or character, using the specified omission character.
(length = 20, omitWith = '…', useWordBoundary = true)
'Error gravida dis phasellus deleniti, nostrud.' | truncate
// Error gravida dis…
'Error gravida dis phasellus deleniti, nostrud.' | truncate(15)
// Error gravida…
'Error gravida dis phasellus deleniti, nostrud.' | truncate(15, '[…]')
// Error gravida[…]
'Error gravida dis phasellus deleniti, nostrud.' | truncate(15, '[…]', false)
// Error gravida d[…]
ucfirst
Return the input with the first character in upper case.
'howzit' | ucfirst // Howzit
ucwords
Return the input with the first character in upper case.
'no howzit' | lcfirst // No Howzit
yesNo
A friendly way to display boolean values.
(yes = 'yes', no = 'no')
true | yesNo // yes
false | yesNo // no
true | yesNo('yep', 'nope') // yep
false | yesNo('yep', 'nope') // nope
Model class
Often-times you’ll be setting data in your state based on a server-response, such as a user. A simple model class is provided to add some functionality atop this data, making it easier to work with in general.
The basic construct of a model is as follows (note the export notation – this is achieved with the StoreFactory):
// store/models/user.js
import { Model } from '@billow/vue-utilities/vuex/model'
export class User extends Model {
constructor(payload = null) {
super(payload || {
name: '',
email: ''
})
}
}
Here, we have a User
class that extends Model
, providing it with additional functionality, such as tracking whether or not the model is dirty (has been changed) or converting it to FormData
.
Now, we use the model in our state:
// store/modules/user.js
import { User } from '../models/user'
export let state = {
user: new User
}
Models are aware of their clean/dirty state. To turn this off, simply construct the model with a null
payload, and turn off history by setting the second argument to true
.
new User(null, true)
Hydration
Models are also hydratable, either through the hydrate
method, or the constructor:
Constructor (most efficient)
Create a new instance of the model, hydrating it with the provided payload, effectively resetting the original state.
export let mutations = {
setUser: (state, payload) => state.user = new User(payload)
}
The hydrate
method
Hydrate the model instance with the provided payload, optionally resetting the original state.
export let mutations = {
setUser: (state, payload) => state.user.hydrate(payload) // sets the original state to the payload
setUser: (state, payload) => state.user.hydrate(payload, false) // retains the original state
}
Checking the state of the model
To determine if a model is dirty, simply use the isDirty
getter on the model instance:
// User.vue
<template>
<p>User has unsaved changes: {{ user.isDirty | yesNo }}</p>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: mapState('user', ['user'])
}
</script>
Check if tracking is enabled
You can also check if the model is tracking it’s state using the noHistory
getter on the instance. Alternatively, you can check if originals have been set using the hasOriginals
getter.
Reset the model state
If a model is dirty, you can reset it to the payload that was passed in the constructor or through the hydrate method. To do this, simply call the reset
method on the model instance. If there are no originals, then nothing will happen.
Cloning a model
Though meant to be used internally, feel free to clone the model with the clone
method on the instance. This will return a fully-qualified clone of the model, however it will not be reactive unless it is set to something that Vuex can observe.
Model transformations
Note: This feature may change in the future to use only dot-notation (see below), or will at least support it over and above the syntax below. If the current syntax is removed, it will only be removed in a major release.
Sometimes, you’ll find yourself in the position where you have a subkey of type Object
in your model, but you want that subkey’s value to be of type Number
for the purposes of API compatibility. For example, let’s say you have the following data in a model:
{
name: 'Mike',
email: '[email protected]',
country: {
id: 1,
name: 'South Africa'
}
}
When this payload goes up to your API, you might want to only pass the ID of the country. This is where transformations come in – they allow you to easily grab parts of the model and move them to the root, optionally using a custom key to replace it.
There are two types of transformations you can make. The first type simply hoists the value you're looking for in a subkey to the root of the model. For example:
return user.transform({
country: 'id'
})
This tells the model to return only the id
under the country
key, whilst retaining the key name, yielding:
{
name: 'Mike',
email: '[email protected]',
country: 1
}
All well and good, but what if you need to rename the key to country_id
? This is where the second type of transformation comes in. By passing an array with two parameters that resemble the current and new key names (or an object resembling the current key and the new key pair), we can rename the key easily:
return user.transform({
country: ['id', 'country_id'] // or
// country: { id: 'country_id' }
})
These both yield:
{
name: 'Mike',
email: '[email protected]',
country_id: 1
}
Hoist by dot-notation
Because models support dot-notation out of the the box (see the methods below for this), we can further expand on our transformations by hoisting a deeply nested key’s value and optionally renaming it:
return user.transform({ country: 'profile.country.id' })
return user.transform({ country: { 'profile.country.id': 'country_id' } })
This will yield the same result as before, only we’re now working with a country object inside a the user’s profile.
Using dot-notation
As mentioned, models use dot-notation out of the box. With this strategy, we can easily get, pick and omit data from a model.
get
Get a piece of data from the model, with an optional fallback if the data is falsy.
<p>{{ user.get('profile.country.name') }}</p>
<p>{{ user.get('profile.country.name', 'Unknown') }}</p>
Tip: Use the
get
filter when you’re not working with a model.
pick
Return a limited set of data that your API may require.
return user.pick(['name', 'email'])
Yielding:
{
name: 'Mike',
email: '[email protected]',
}
omit
In the same vein, omit data that you don’t need:
return user.omit(['profile'])
Which yields the same thing.
Conversions
You can convert a model to string, form-data and JSON-based-form-data using the built-in helper methods:
return user.toString()
return user.toFormData()
return user.toJsonFormData('data')
In the first case, the model’s enumerable props are simply sent off to JSON.stringify
and returned.
In the latter two cases, the first converts the model to a standard FormData
object, and the second converts it to a type-safe version of FormData
, where anything that is not a file gets added to a single key in JSON format. Your backend will need to parse this out when parsing compiling the incoming request. For more information, there’s a nice little article on it (see the middleware example for Laravel).
Mutation Helpers
…
API Client
…
Passport
…
Component and Store Factories
…
Array, Object and String Utilities
…