%PDF- %PDF-
| Direktori : /home/opti/workflow/src/public/app-assets/vendors/formBuilder-master/src/js/ |
| Current File : /home/opti/workflow/src/public/app-assets/vendors/formBuilder-master/src/js/utils.js |
/**
* Cross file utilities for working with arrays,
* sorting and other fun stuff
*/
window.fbLoaded = {
js: [],
css: [],
}
window.fbEditors = {
quill: {},
tinymce: {},
}
/**
* Remove null or undefined values
* @param {Object} attrs {attrName: attrValue}
* @return {Object} Object trimmed of null or undefined values
*/
export const trimObj = function(attrs) {
const xmlRemove = [null, undefined, '', false, 'false']
for (const attr in attrs) {
if (xmlRemove.includes(attrs[attr])) {
delete attrs[attr]
} else if (Array.isArray(attrs[attr])) {
if (!attrs[attr].length) {
delete attrs[attr]
}
}
}
return attrs
}
/**
* Test if attribute is a valid HTML attribute
* @param {String} attr
* @return {Boolean}
*/
export const validAttr = function(attr) {
const invalid = [
'values',
'enableOther',
'other',
'label',
// 'style',
'subtype',
]
return !invalid.includes(attr)
}
/**
* Convert an attrs object into a string for xml node
* @param {Object} attrs object of attributes for markup
* @return {string}
*/
export const xmlAttrString = attrs =>
Object.entries(attrs)
.map(([key, val]) => `${hyphenCase(key)}="${val}"`)
.join(' ')
/**
* Convert an attrs object into a string
*
* @param {Object} attrs object of attributes for markup
* @return {string}
*/
export const attrString = attrs =>
Object.entries(attrs)
.map(([key, val]) => validAttr(key) && Object.values(safeAttr(key, val)).join(''))
.filter(Boolean)
.join(' ')
/**
* Convert attributes to markup safe strings
* @param {String} name attribute name
* @param {String} value attribute value
* @return {Object} {attrName: attrValue}
*/
export const safeAttr = (name, value) => {
name = safeAttrName(name)
let valString
if (value) {
if (Array.isArray(value)) {
valString = escapeAttr(value.join(' '))
} else {
if (typeof value === 'boolean') {
value = value.toString()
}
valString = escapeAttr(value.trim())
}
}
value = value ? `="${valString}"` : ''
return {
name,
value,
}
}
/**
* recursively flatten a nested array
* @param {Array} arr to be flattened
* @return {Array} flattened array
*/
export const flattenArray = arr =>
arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flattenArray(val) : val), [])
export const safeAttrName = name => {
const safeAttr = {
className: 'class',
}
return safeAttr[name] || hyphenCase(name)
}
/**
* Convert strings into lowercase-hyphen
*
* @param {String} str
* @return {String}
*/
export const hyphenCase = str => {
// eslint-disable-next-line no-useless-escape
str = str.replace(/[^\w\s\-]/gi, '')
str = str.replace(/([A-Z])/g, function($1) {
return '-' + $1.toLowerCase()
})
return str.replace(/\s/g, '-').replace(/^-+/g, '')
}
/**
* convert a hyphenated string to camelCase
* @param {String} str
* @return {String}
*/
export const camelCase = str => str.replace(/-([a-z])/g, (m, w) => w.toUpperCase())
/**
* Bind events to an element
* @param {Object} element DOM element
* @param {Object} events object full of events eg. {click: evt => callback}
* @return {void}
*/
export const bindEvents = (element, events) => {
if (events) {
for (const event in events) {
if (events.hasOwnProperty(event)) {
element.addEventListener(event, evt => events[event](evt))
}
}
}
}
/**
* Generate a unique name attribute
* @param {Object} field
* @return {String} name
*/
export const nameAttr = function(field) {
const epoch = new Date().getTime()
const prefix = field.type || hyphenCase(field.label)
return prefix + '-' + epoch
}
/**
* Determine content type
* @param {Node | String | Array | Object} content
* @return {String}
*/
export const getContentType = content => {
if (content === undefined) {
return content
}
return [
['array', content => Array.isArray(content)],
['node', content => content instanceof window.Node || content instanceof window.HTMLElement],
['component', () => content && content.dom],
[typeof content, () => true],
].find(typeCondition => typeCondition[1](content))[0]
}
/**
* Generate markup wrapper where needed
*
* @param {string} tag
* @param {String|Array|Object} content we wrap this
* @param {Object} attributes
* @return {Object} DOM Element
*/
export const markup = function(tag, content = '', attributes = {}) {
let contentType = getContentType(content)
const { events, ...attrs } = attributes
const field = document.createElement(tag)
const appendContent = {
string: content => {
field.innerHTML += content
},
object: config => {
const { tag, content, ...data } = config
return field.appendChild(markup(tag, content, data))
},
node: content => {
return field.appendChild(content)
},
array: content => {
for (let i = 0; i < content.length; i++) {
contentType = getContentType(content[i])
appendContent[contentType](content[i])
}
},
function: content => {
content = content()
contentType = getContentType(content)
appendContent[contentType](content)
},
undefined: () => {},
}
for (const attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
const name = safeAttrName(attr)
const attrVal = Array.isArray(attrs[attr]) ? unique(attrs[attr].join(' ').split(' ')).join(' ') : attrs[attr]
field.setAttribute(name, attrVal)
}
}
if (content) {
appendContent[contentType](content)
}
bindEvents(field, events)
return field
}
/**
* Convert html element attributes to key/value object
* @param {Object} elem DOM element
* @return {Object} ex: {attrName: attrValue}
*/
export const parseAttrs = elem => {
const attrs = elem.attributes
const data = {}
forEach(attrs, attr => {
let attrVal = attrs[attr].value || ''
if (attrVal.match(/false|true/g)) {
attrVal = attrVal === 'true'
} else if (attrVal.match(/undefined/g)) {
attrVal = undefined
}
if (attrVal) {
data[camelCase(attrs[attr].name)] = attrVal
}
})
return data
}
/**
* Convert field options to optionData
* @param {NodeList} options DOM elements
* @return {Array} optionData array
*/
export const parseOptions = options => {
const data = []
for (let i = 0; i < options.length; i++) {
const optionData = {
...parseAttrs(options[i]),
label: options[i].textContent,
}
data.push(optionData)
}
return data
}
/**
* Parse XML formData
* @param {String} xmlString
* @return {Array} formData array
*/
export const parseXML = xmlString => {
const parser = new window.DOMParser()
const xml = parser.parseFromString(xmlString, 'text/xml')
const formData = []
if (xml) {
const fields = xml.getElementsByTagName('field')
for (let i = 0; i < fields.length; i++) {
const fieldData = parseAttrs(fields[i])
const options = fields[i].getElementsByTagName('option')
if (options && options.length) {
fieldData.values = parseOptions(options)
}
formData.push(fieldData)
}
}
return formData
}
/**
* Converts escaped HTML into usable HTML
* @param {String} html escaped HTML
* @return {String} parsed HTML
*/
export const parsedHtml = html => {
const escapeElement = document.createElement('textarea')
escapeElement.innerHTML = html
return escapeElement.textContent
}
/**
* Escape markup so it can be displayed rather than rendered
* @param {String} html markup
* @return {String} escaped html
*/
export const escapeHtml = html => {
const escapeElement = document.createElement('textarea')
escapeElement.textContent = html
return escapeElement.innerHTML
}
// Escape an attribute
export const escapeAttr = str => {
const match = {
'"': '"',
'&': '&',
'<': '<',
'>': '>',
}
const replaceTag = tag => match[tag] || tag
return typeof str === 'string' ? str.replace(/["&<>]/g, replaceTag) : str
}
// Escape attributes
export const escapeAttrs = attrs => {
for (const attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
attrs[attr] = escapeAttr(attrs[attr])
}
}
return attrs
}
// forEach that can be used on nodeList
export const forEach = function(array, callback, scope) {
for (let i = 0; i < array.length; i++) {
callback.call(scope, i, array[i]) // passes back stuff we need
}
}
/**
* Remove duplicates from an array of elements
* @param {Array} array array with possible duplicates
* @return {Array} array with only unique values
*/
export const unique = array => {
return array.filter((elem, pos, arr) => arr.indexOf(elem) === pos)
}
/**
* Removes a value from an array
* @param {String|Number} val
* @param {Array} arr
*/
export const removeFromArray = (val, arr) => {
const index = arr.indexOf(val)
if (index > -1) {
arr.splice(index, 1)
}
}
/**
* Loads an array of scripts using jQuery's `getScript`
* @param {Array|String} scriptScr scripts
* @param {String} path optional to load form
* @return {Promise} a promise
*/
export const getScripts = (scriptScr, path) => {
const $ = jQuery
let _arr = []
if (!Array.isArray(scriptScr)) {
scriptScr = [scriptScr]
}
if (!isCached(scriptScr)) {
_arr = jQuery.map(scriptScr, src => {
const options = {
dataType: 'script',
cache: true,
url: (path || '') + src,
}
return jQuery.ajax(options).done(() => window.fbLoaded.js.push(src))
})
}
_arr.push(jQuery.Deferred(deferred => $(deferred.resolve)))
return jQuery.when(..._arr)
}
/**
* Checks if remote resource is already loaded
* @param {String|Array} src url of remote script or css
* @param {String} type 'js' or 'css'
* @return {Boolean} isCached
*/
export const isCached = (src, type = 'js') => {
let isCached = false
const cache = window.fbLoaded[type]
if (Array.isArray(src)) {
isCached = src.every(s => cache.includes(s))
} else {
isCached = cache.includes(src)
}
return isCached
}
/**
* Appends stylesheets to the head
* @param {Array} scriptScr
* @param {String} path
* @return {void}
*/
export const getStyles = (scriptScr, path) => {
if (!Array.isArray(scriptScr)) {
scriptScr = [scriptScr]
}
scriptScr.forEach(src => {
// if a string is passed, assume a href URL
let type = 'href'
let key = src
let id = ''
// if an object is passed, work out details from it's properties
if (typeof src == 'object') {
type = src.type || (src.style ? 'inline' : 'href')
id = src.id
src = type == 'inline' ? src.style : src.href
key = id || src.href || src.style
}
// check we haven't already loaded this css
if (isCached(key, 'css')) {
return
}
// append the style into the head
if (type == 'href') {
const link = document.createElement('link')
link.type = 'text/css'
link.rel = 'stylesheet'
link.href = (path || '') + src
document.head.appendChild(link)
} else {
$(`<style type="text/css">${src}</style>`)
.attr('id', id)
.appendTo($(document.head))
}
// record this is cached
window.fbLoaded.css.push(key)
})
}
/**
* Capitalizes a string
* @param {String} str uncapitalized string
* @return {String} str capitalized string
*/
export const capitalize = str => {
return str.replace(/\b\w/g, function(m) {
return m.toUpperCase()
})
}
export const merge = (obj1, obj2) => {
const mergedObj = Object.assign({}, obj1, obj2)
for (const prop in obj2) {
if (mergedObj.hasOwnProperty(prop)) {
if (Array.isArray(obj2[prop])) {
mergedObj[prop] = Array.isArray(obj1[prop]) ? unique(obj1[prop].concat(obj2[prop])) : obj2[prop]
} else if (typeof obj2[prop] === 'object') {
mergedObj[prop] = merge(obj1[prop], obj2[prop])
} else {
mergedObj[prop] = obj2[prop]
}
}
}
return mergedObj
}
/**
*
* @param {Node} el
* @param {String} evts events to bind to
* @param {Function} cb
* @return {Array} events
*/
export const addEventListeners = (el, evts, cb) => evts.split(' ').forEach(e => el.addEventListener(e, cb, false))
/**
* Find the closest parent by class
* @param {Object} el DOM element
* @param {String} cls class
* @return {Object} DOM Element
*/
export const closest = (el, cls) => {
const className = cls.replace('.', '')
while ((el = el.parentElement) && !el.classList.contains(className));
return el
}
/**
* Add a mobile class
* @todo find css only solution
* @return {String} Mobile class added to formBuilder
*/
export const mobileClass = () => {
let mobileClass = ''
;(a => {
// eslint-disable-next-line
if (
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
a
)
) {
mobileClass = 'fb-mobile'
}
})(navigator.userAgent || navigator.vendor || window.opera)
return mobileClass
}
/**
* Make strings safe to be used as classes
*
* @param {String} str string to be converted
* @return {String} converter string
*/
export const safename = str => {
return str.replace(/\s/g, '-').replace(/[^a-zA-Z0-9[\]_-]/g, '')
}
/**
* Strips non-numbers from a number only input
*
* @param {string} str string with possible number
* @return {string} string without numbers
*/
export const forceNumber = str => str.replace(/[^0-9]/g, '')
// subtract the contents of 1 array from another
export const subtract = (arr, from) => {
return from.filter(function(a) {
return !~this.indexOf(a)
}, arr)
}
export const insertStyle = srcs => {
srcs = Array.isArray(srcs) ? srcs : [srcs]
const promises = srcs.map(
({ src, id }) =>
new Promise((resolve, reject) => {
if (window.fbLoaded.css.includes(src)) {
return resolve(src)
}
const formeoStyle = markup('link', null, {
href: src,
rel: 'stylesheet',
id,
})
document.head.insertBefore(formeoStyle, document.head.firstChild)
})
)
return Promise.all(promises)
}
export const removeStyle = id => {
const elem = document.getElementById(id)
return elem.parentElement.removeChild(elem)
}
const utils = {
addEventListeners,
attrString,
camelCase,
capitalize,
closest,
getContentType,
escapeAttr,
escapeAttrs,
escapeHtml,
forceNumber,
forEach,
getScripts,
getStyles,
hyphenCase,
isCached,
markup,
merge,
mobileClass,
nameAttr,
parseAttrs,
parsedHtml,
parseOptions,
parseXML,
removeFromArray,
safeAttr,
safeAttrName,
safename,
subtract,
trimObj,
unique,
validAttr,
}
export default utils