<script>
import _ from 'lodash';
import CollapseDown from 'vue-material-design-icons/MenuDown.vue';
import CollapseUp from 'vue-material-design-icons/MenuUp.vue';
import Help from 'vue-material-design-icons/HelpCircleOutline.vue';
/**
* @typedef optionsTypes
* @prop {String} name - Options Name
* @prop {String | Array} type - Options Types either that accept string value of `string`, `number` and/or `boolean`
* @prop {Object[] | Array | Object | String | Null} value - Default value of options
* @prop {String} category - Category of options
*/
/**
* @typedef aceEditor
* @prop {Ace} Ace - Ace Constructor Object
* @prop {Ace.Editor} editor - Ace Editor Initialized
* @see {@link https://ace.c9.io/#nav=api} API Reference
*/
/**
* @component OptionsContainerComponent
* @desc Options Container Component that will generate options and compare from module options
* @lifecycle created Get `options` from `this.getOptions` async function that call server GET route to get current user saved options
* @lifecycle mounted Attach emit listener **on** for `customCodeEditor:getOptions` to `this.updateOptions`
* @lifecycle beforeDestroy Attach emit listener **off** for `customCodeEditor:getOptions` to `this.updateOptions`
* @lifecycle render Only render when `this.editor` & `this.optionsTypes` is available
*/
export default {
components: {
CollapseDown,
CollapseUp,
Help
},
props: {
/**
* @vprop {optionsTypes} optionsTypes - Default Options Types
*/
optionsTypes: {
type: Object,
required: true
},
/**
* @vprop {Object[]} cache - Cache Storage
*/
cache: {
type: Array,
required: true
},
/**
* @vprop {aceEditor} editor - Ace Editor JS
*/
editor: {
type: Object
},
/**
* @vprop {String} search - Search input
*/
search: {
type: String
}
},
data() {
return {
/**
* @member {Object} - To store original options
*/
originalOptions: {},
/**
* @member {Object} - To grab modified custom-code-editor module for editor options
*/
options: {},
/**
* @member {Object.<Boolean>} - Title Click Check
*/
titleClick: {}
};
},
created() {
this.getOptions()
.then((options) => {
// Save to original Options first
if (Object.keys(this.originalOptions).length === 0) {
this.originalOptions = _.assign({}, _.cloneDeep(this.editor.getOptions()), _.isUndefined(apos.customCodeEditor.browser, `fieldAce.${this.$parent.field.name}`) ? !_.isUndefined(apos.customCodeEditor.browser.ace, 'options') ? apos.customCodeEditor.browser.ace.options : {} : !_.isUndefined(apos.customCodeEditor.browser.fieldAce[this.$parent.field.name], `options`) ? apos.customCodeEditor.browser.fieldAce[this.$parent.field.name].options : {});
}
try {
if (options.status !== 'error') {
return JSON.parse(options.message);
} else {
throw new Error(options.message);
}
} catch (e) {
throw new Error(e.message);
}
})
.then((result) => this.options = _.assign({}, this.options, result))
.then(() => this.$forceUpdate())
.catch((message) => {
apos.notify(message, {
dismiss: true,
type: 'error'
});
});
},
mounted() {
this.$root.$on('customCodeEditor:getOptions', this.updateOptions);
},
beforeUnmount() {
this.$root.$off('customCodeEditor:getOptions', this.updateOptions);
// eslint-disable-next-line vue/require-explicit-emits
this.$emit('resetCache');
},
methods: {
/**
* @method getOptions
* @desc ```js
* // Example object returns
* {
* status: 'success',
* message: '{"cursorStyle": true}'
* }
* // Example empty object returns
* {
* status: 'empty',
* message: '{}'
* }
* ```
* @async
* @return {Object} - `options` object to override editor options
*/
async getOptions() {
try {
let getOptions = await apos.http.get(apos.customCodeEditor.browser.action + '/options', {});
return getOptions;
} catch (e) {
console.warn('Unable to get options due to error:\n', e);
throw new Error(e);
}
},
/**
* @method saveOptions
* @desc ```js
* // Example object returns
* {
* status: 'success',
* message: 'Options saved!'
* }
* ```
* @async
* @param {Object} copyOptions - Grab `options` object from modified options container and save it to current user logged in
* @return {Object} Returns status from server
*/
async saveOptions(copyOptions) {
try {
let saveOptions = await apos.http.post(apos.customCodeEditor.browser.action + '/submit', {
body: {
[apos.customCodeEditor.alias]: copyOptions
}
});
return saveOptions;
} catch (e) {
console.warn('Save options ERROR', JSON.parse(e));
throw new Error(JSON.parse(e));
}
},
/**
* @method deleteOptions
* @desc ```js
* // Example object returns
* {
* status: 'success',
* message: 'Success delete options!'
* }
* ```
* @async
* @return {Object} Returns status delete options
*/
async deleteOptions() {
try {
let deleteOptions = await apos.http.delete(apos.customCodeEditor.browser.action +
'/remove', {});
return deleteOptions;
} catch (e) {
console.warn('Delete options ERROR', JSON.parse(e));
throw new Error(JSON.parse(e));
}
},
/**
* @method buttonOptionsClick
* @desc Trigger emits
* @param {HTMLEvent} e - HTML Event Listener
*/
buttonOptionsClick(e) {
let button = e.currentTarget;
let allCopy = {};
let inputEmits = {};
let self = this;
this.$el.querySelectorAll('li:not([data-header])').forEach(function (value, i) {
let key = Object.keys(self.cache[i])[0];
let cacheValue = self.cache[i];
let input = value.querySelector("[name='" + value.id + "']");
// Detect changes by comparing all cache with incoming list arrays.
// This will be useful and only executes if it not matches the cache value
switch (true) {
case (/select/g).test(input.type) && !_.isUndefined(cacheValue[input.name]):
if (button.className === 'delete-options') {
// Reset the cache first, then run checking
self.$emit('updateCache', {
property: input.name,
value: self.originalOptions[input.name]
});
}
// Transform the value
let value = (input.options[input.selectedIndex].value === 'true' || input.options[input.selectedIndex].value === 'false') ? JSON.parse(input.options[input.selectedIndex].value) : input.options[input.selectedIndex].value;
if (value !== cacheValue[input.name]) {
let passValue = '';
if (button.className === 'copy-options' || button.className ===
'save-options') {
// Assign to local copy to pass it as local reference
allCopy[input.name] = input.options[input.selectedIndex].value;
// Pass Value to emit
passValue = input.options[input.selectedIndex].value;
} else if (button.className === 'undo-options') {
// Revert to default value
input.value = cacheValue[input.name];
// Pass Value to emit
passValue = cacheValue[input.name];
// Delete assigned self.options
delete self.options[key];
// And reset options on editor
self.editor.setOption(input.name, cacheValue[input.name]);
} else if (button.className === 'delete-options') {
// Revert to default value based on module options
input.value = self.originalOptions[input.name];
// Pass Value to emit
passValue = self.originalOptions[input.name];
// And reset options on editor
self.editor.setOption(input.name, self.originalOptions[input.name]);
}
inputEmits[input.name] = {
input: input,
value: passValue,
button: button,
allCopy: allCopy
};
}
break;
case (/range/g).test(input.type):
if (button.className === 'delete-options') {
// Reset the cache first, then run checking
self.$emit('updateCache', {
property: input.name,
value: self.originalOptions[input.name]
});
}
if (
parseFloat(input.value) !== cacheValue[input.name] &&
input.getAttribute('value') !== null
) {
let passValue = '';
if (button.className === 'copy-options' || button.className ===
'save-options') {
// Assign to local copy to pass it as local reference
allCopy[input.name] = parseFloat(input.value);
// Pass Value to emit
passValue = parseFloat(input.value);
} else if (button.className === 'undo-options') {
// Revert to default value
input.value = cacheValue[input.name];
// Pass value to emit
passValue = cacheValue[input.name];
// Display none on span value
input.nextElementSibling.style.display = 'none';
// Delete assigned self.options
delete self.options[key];
// And reset options on editor
self.editor.setOption(input.name, cacheValue[input.name]);
// Remove the attribute as default
input.removeAttribute('value');
} else if (button.className === 'delete-options') {
// Revert to default value based on module options
input.value = self.originalOptions[input.name];
// Pass value to emit
passValue = self.originalOptions[input.name];
// Display none on span value
input.nextElementSibling.style.display = 'none';
// And reset options on editor
self.editor.setOption(input.name, self.originalOptions[input.name]);
// Remove the attribute as default
input.removeAttribute('value');
}
inputEmits[input.name] = {
input: input,
value: passValue,
button: button,
allCopy: allCopy
};
}
break;
case (/checkbox/g).test(input.type):
if (button.className === 'delete-options') {
// Reset the cache first, then run checking
self.$emit('updateCache', {
property: input.name,
value: _.isUndefined(self.originalOptions[input.name]) ? false : self.originalOptions[input.name]
});
}
if (input.checked !== cacheValue[input.name]) {
if (button.className === 'copy-options' || button.className ===
'save-options') {
allCopy[input.name] = input.checked;
} else if (button.className === 'undo-options') {
// Revert to default value
input.checked = cacheValue[input.name];
// Delete assigned self.options
delete self.options[key];
// And reset options on editor
self.editor.setOption(input.name, cacheValue[input.name]);
} else if (button.className === 'delete-options') {
// Revert to default value based on module options
input.checked = self.originalOptions[input.name];
// And reset options on editor
self.editor.setOption(input.name, self.originalOptions[input.name]);
}
inputEmits[input.name] = {
input: input,
value: input.checked,
button: button,
allCopy: allCopy
};
}
break;
}
});
if (button.className === 'copy-options') {
// Merge allCopy options
if (Object.keys(self.options).length > 0) {
allCopy = Object.assign(self.options, allCopy);
// Loop and find if existing default saved options detected matches module options
for (let key of Object.keys(self.originalOptions)) {
if (Object.prototype.hasOwnProperty.call(self.originalOptions, key)) {
// Only allow non-module options to be copy
if (self.originalOptions[key] === allCopy[key]) {
delete allCopy[key];
}
}
}
}
// Will use clipboard.js, much more functional to all browsers
button.dataset.clipboardText = JSON.stringify(allCopy);
// Click again to copy the dataset
button.click();
} else if (button.className === 'save-options') {
if (Object.keys(allCopy).length > 0) {
self.saveOptions(allCopy).then((data) => {
if (data.status === 'success') {
return apos.notify(data.message, {
dismiss: true,
type: 'success'
});
}
return apos.notify(data.message, {
dismiss: true,
type: 'error'
});
}).catch((e) => {
return apos.notify('Unable to save options. Please try again', {
type: 'error',
dismiss: true
});
});
} else {
return apos.notify(
'ERROR : Save unsuccessful, options empty. Try adjust your desire options than your default settings.', {
type: 'error',
dismiss: 8
});
}
} else if (button.className === 'delete-options') {
self.deleteOptions().then((result) => {
if (result.status === 'success') {
// Set self.options to be empty too
self.options = {};
// Loop the optionsTypes, if there is `saveValue` assigned to it, delete it
for (let categoryKey of Object.keys(self.optionsTypes)) {
if (Object.prototype.hasOwnProperty.call(self.optionsTypes, categoryKey)) {
for (let key of Object.keys(self.optionsTypes[categoryKey])) {
if (!_.isUndefined(self.optionsTypes[categoryKey][key].saveValue)) {
self.$emit('updateOptionsTypes', {
category: categoryKey,
name: self.optionsTypes[categoryKey][key].name,
saveValue: undefined
});
}
}
}
}
return apos.notify('Saved options successfully removed', {
type: 'success',
dismiss: true
});
} else {
return apos.notify('ERROR : ' + result.message, {
type: 'error',
dismiss: true
});
}
}).catch((e) => {
return apos.notify('ERROR : ' + e.message, {
type: 'error',
dismiss: true
});
});
}
if (Object.keys(inputEmits).length > 0) {
for (let key in inputEmits) {
if (Object.prototype.hasOwnProperty.call(inputEmits, key)) {
self.emitOptions(inputEmits[key]);
}
}
}
},
/**
* @method optionsInputs
* @desc Init Options Lists and append to List Header
* @param {Object} object - Object of optionsTypes merge with saveValue
* @param {String} type - Either `slider`, `dropdownArray`, `dropdownObject` or `checkbox`
* @param {aceEditor} editor - Ace JS Editor
* @param {Vue.VNode} h - Vue render function
* @return {Vue.VNode} Returns Lists of options in a category
*/
optionsInputs(object, type, editor, h) {
let display = '';
// Only override display when keyword search happens
if (this.search.length > 0) {
let findKeyword = this.$parent.$parent.getName(object.name).indexOf(this.search);
// Only allow matched input, make display none for the rest of the list
if (findKeyword === -1) {
display = 'none';
}
}
// Hide when titleClick for the category is true
if (this.titleClick[_.camelCase(object.category)]) {
display = 'none';
}
let lists = h('li', {
class: 'lists-inputs',
attrs: {
'data-category': this.$parent.$parent.getName(object.category),
id: object.name
},
style: {
display
}
}, []);
switch (type) {
case 'slider':
(function (self) {
// Create <label> element
let label = h('label', {
class: 'label-text',
style: {
textTransform: 'capitalize'
},
domProps: {
for: object.name
}
}, self.$parent.$parent.getName(object.name) + ': ');
// Create <span> for slider output
let output = h('span', {
class: 'range-slider__value',
style: {
display: 'none'
}
}, '');
// Create <input> element
let input = h('input', {
class: 'range-slider__range',
domProps: {
value: editor.getOptions()[object.name].value,
name: object.name,
type: 'range',
max: object.value.max,
min: object.value.min,
step: object.value.steps
},
on: {
input: (e) => {
let percent = (e.currentTarget.value - object.value.min) / (
object.value.max - object.value.min);
let newPos = (parseInt(getComputedStyle(e.currentTarget)
.width) - e.currentTarget.style.marginLeft) * percent;
e.currentTarget.nextElementSibling.style.left = newPos + 'px';
e.currentTarget.nextElementSibling.style.display = null;
e.currentTarget.nextElementSibling.innerHTML = e.currentTarget
.value;
},
change: (e) => {
e.target.setAttribute('value', e.currentTarget.value);
editor.setOption(object.name, e.currentTarget.value);
},
mouseup: (e) => {
e.currentTarget.nextElementSibling.style.display = 'none';
}
}
}, []);
// Set selected & editor options
if (!_.isUndefined(object.saveValue)) {
input.data.domProps.value = object.saveValue;
editor.setOption(object.name, object.saveValue);
} else if (_.isUndefined(object.saveValue)) {
(editor.getOptions()[object.name]) ? input.data.domProps.value = editor
.getOptions()[object.name] : input.data.domProps.value = 0;
}
let cache = {
[object.name]: (!_.isUndefined(object.saveValue)) ? object.saveValue : editor
.getOptions()[object.name]
};
if (!self.cache.some(eachCache => Object.prototype.hasOwnProperty.call(eachCache, object.name))) {
self.$emit('pushCache', cache);
}
// Help Text
if (object.help) {
let helpIcon = h(Help, {
class: ['tooltip'],
attrs: {
'style': 'color: blue !important'
},
props: {
size: 14
}
}, []);
let helpText = h('span', {
class: ['tooltiptext']
}, [ object.help ]);
helpIcon.children.push(helpText);
label.children.push(helpIcon);
}
lists.children.push(label);
lists.children.push(input);
lists.children.push(output);
})(this);
break;
case 'dropdownArray':
(function (self) {
// Create <label> element
let label = h('label', {
class: 'label-text',
style: {
textTransform: 'capitalize'
},
attrs: {
for: object.name
}
}, self.$parent.$parent.getName(object.name) + ': ');
// Create <select> element
let select = h('select', {
domProps: {
name: object.name
},
on: {
change: (e) => {
editor.setOption(object.name, e.currentTarget.value);
}
}
}, object.value.map((val, i) => {
// Create <option> element
let selected = false;
// Set selected & editor options
if (object.saveValue === val) {
selected = true;
editor.setOption(object.name, object.saveValue);
} else if (_.isUndefined(object.saveValue)) {
(editor.getOptions()[object.name] === val) ? selected = true : null;
}
return h('option', {
domProps: {
value: val,
selected: selected
}
}, val);
}));
let cache = {
[object.name]: (!_.isUndefined(object.saveValue)) ? object.saveValue : editor
.getOptions()[object.name]
};
if (!self.cache.some(eachCache => Object.prototype.hasOwnProperty.call(eachCache, object.name))) {
self.$emit('pushCache', cache);
}
// Help Text
if (object.help) {
let helpIcon = h(Help, {
class: ['tooltip'],
attrs: {
'style': 'color: blue !important'
},
props: {
size: 14
}
}, []);
let helpText = h('span', {
class: ['tooltiptext']
}, [ object.help ]);
helpIcon.children.push(helpText);
label.children.push(helpIcon);
}
lists.children.push(label);
lists.children.push(select);
})(this);
break;
case 'dropdownObject':
(function (self) {
// Create <label> element
let label = h('label', {
class: 'label-text',
style: {
textTransform: 'capitalize'
},
domProps: {
for: object.name
}
}, self.$parent.$parent.getName(object.name) + ': ');
// Create <select> element
let select = h('select', {
domProps: {
name: object.name
},
on: {
change: (e) => {
let value = (e.currentTarget.value === 'true' || e.currentTarget
.value === 'false') ? JSON.parse(e.currentTarget
.value) : e.currentTarget.value;
editor.setOption(object.name, value);
}
}
}, object.value.map((val, i) => {
// Create <option> element
let selected = false;
// Set selected & editor options
if (object.saveValue === val) {
selected = true;
editor.setOption(object.name, object.saveValue);
} else if (_.isUndefined(object.saveValue)) {
(editor.getOptions()[object.name] === val.value) ? selected = true : null;
}
return h('option', {
domProps: {
value: val.value,
selected: selected
}
}, val.value);
}));
let cache = {
[object.name]: (!_.isUndefined(object.saveValue)) ? object.saveValue : editor
.getOptions()[object.name]
};
if (!self.cache.some(eachCache => Object.prototype.hasOwnProperty.call(eachCache, object.name))) {
self.$emit('pushCache', cache);
}
// Help Text
if (object.help) {
let helpIcon = h(Help, {
class: ['tooltip'],
attrs: {
'style': 'color: blue !important'
},
props: {
size: 14
}
}, []);
let helpText = h('span', {
class: ['tooltiptext']
}, [ object.help ]);
helpIcon.children.push(helpText);
label.children.push(helpIcon);
}
lists.children.push(label);
lists.children.push(select);
})(this);
break;
case 'checkbox':
(function (self) {
// Create <label> element
let label = h('label', {
class: 'label-text',
style: {
textTransform: 'capitalize'
},
domProps: {
for: object.name
}
}, self.$parent.$parent.getName(object.name) + ': ');
let checked = null;
if (!_.isUndefined(object.saveValue)) {
checked = object.saveValue;
editor.setOption(object.name, object.saveValue);
} else if (_.isUndefined(object.saveValue)) {
editor.getOptions()[object.name] ? checked = editor.getOptions()[object.name] : null;
}
// Create <checkbox> element
let input = h('input', {
domProps: {
type: 'checkbox',
name: object.name,
checked: checked
},
class: 'error',
on: {
change: (e) => {
if (e.currentTarget.checked) {
editor.setOption(object.name, true);
} else {
editor.setOption(object.name, false);
}
}
}
}, []);
let cache = {
[object.name]: (!_.isUndefined(object.saveValue)) ? object.saveValue : !!editor
.getOptions()[object.name]
};
if (!self.cache.some(eachCache => Object.prototype.hasOwnProperty.call(eachCache, object.name))) {
self.$emit('pushCache', cache);
}
// Help Text
if (object.help) {
let helpIcon = h(Help, {
class: ['tooltip'],
attrs: {
'style': 'color: blue !important'
},
props: {
size: 14
}
}, []);
let helpText = h('span', {
class: ['tooltiptext']
}, [ object.help ]);
helpIcon.children.push(helpText);
label.children.push(helpIcon);
}
lists.children.push(label);
lists.children.push(input);
})(this);
break;
}
return lists;
},
/**
* @method loopOptions
* @desc Loop function to loop with current save options and init with `optionsInput()` function
* @param {Object} myOptions - Object of editor options that either saved from user options or default value
* @param {Vue.VNode} h - Vue render function
* @return {Vue.VNode} Returns <ul> element that are grouped all the lists in the children
*/
loopOptions(myOptions, h) {
let editor = this.editor;
let self = this;
// Create new <ul> element to group all lists in its children
let unorderedLists = h('ul', {
class: 'editor-options-container'
}, []);
// Create default <li> element as starting Header List element
let listHeader = h('li', {
style: {
marginBottom: '60px'
}
}, [ ]);
// Grab props Options Types
let optionsTypes = this.optionsTypes;
// Loop Group By Options
for (let categoryKey in optionsTypes) {
let display = '';
// Only override display when keyword search happens
if (this.search.length > 0) {
// Filter keyword that has the value
let filterKeyword = _.filter(optionsTypes[categoryKey], (val) => self.$parent.$parent.getName(
val.name).indexOf(self.search) > -1);
// Only hide header list if it not match with the filter keyword
if (filterKeyword.length === 0) {
display = 'none';
}
}
// Create new listHeader
listHeader = h('li', {
style: {
marginBottom: '60px',
display
}
}, []);
// Assign Attributes to listHeader
listHeader.data.attrs = {
'data-category': this.$parent.$parent.getName(categoryKey),
'data-header': this.$parent.$parent.getName(categoryKey),
id: categoryKey
};
// Assign Data Title Click for checking
if (_.isUndefined(this.titleClick[_.camelCase(categoryKey)])) {
this.titleClick[_.camelCase(categoryKey)] = false;
}
// Create new <h1> title
let h1 = h('h1', {
class: 'editor-options-title',
style: {
cursor: 'pointer'
},
on: {
click: this.listHeaderClick.bind(self)
}
}, [
' ' + this.$parent.$parent.getName(categoryKey) + ' Options' + ' ',
h(this.titleClick[_.camelCase(categoryKey)] ? CollapseUp : CollapseDown, {
attrs: {
'data-category': this.$parent.$parent.getName(categoryKey),
'data-icon': this.titleClick[_.camelCase(categoryKey)] ? 'up' : 'down'
}
}, [])
]);
// Push <h1> to new listHeader created
listHeader.children.push(h1);
// Finally push the listHeader to <ul> parent element
unorderedLists.children.push(listHeader);
// Loop existing Editor Options
// Something is wrong in here. Should do filter instead
for (let key of Object.keys(this.editor.getOptions())) {
if (Object.prototype.hasOwnProperty.call(editor.getOptions(), key)) {
let groupedOptions = optionsTypes[categoryKey].find((val) => val.name === key);
// Assign child of listHeader
if (groupedOptions && groupedOptions.name === key && categoryKey === groupedOptions
.category) {
switch (true) {
case _.isArray(groupedOptions.value) && !_.every(groupedOptions.value, _.isObject):
groupedOptions = !_.isUndefined(myOptions[key]) ? apos.util.assign(
groupedOptions, {
saveValue: myOptions[key]
}) : groupedOptions;
listHeader.children.push(this.optionsInputs(groupedOptions, 'dropdownArray',
editor, h));
break;
case _.isArray(groupedOptions.value) && _.every(groupedOptions.value, _.isObject):
groupedOptions = !_.isUndefined(myOptions[key]) ? apos.util.assign(
groupedOptions, {
saveValue: myOptions[key]
}) : groupedOptions;
listHeader.children.push(this.optionsInputs(groupedOptions, 'dropdownObject',
editor, h));
break;
case _.isObject(groupedOptions.value):
groupedOptions = !_.isUndefined(myOptions[key]) ? apos.util.assign(
groupedOptions, {
saveValue: myOptions[key]
}) : groupedOptions;
listHeader.children.push(this.optionsInputs(groupedOptions, 'slider', editor,
h));
break;
case groupedOptions.type === 'boolean':
groupedOptions = !_.isUndefined(myOptions[key]) ? apos.util.assign(
groupedOptions, {
saveValue: myOptions[key]
}) : groupedOptions;
listHeader.children.push(this.optionsInputs(groupedOptions, 'checkbox', editor,
h));
break;
}
// Update Options Types Value
self.$emit('updateOptionsTypes', {
category: categoryKey,
name: groupedOptions.name,
saveValue: !_.isUndefined(myOptions[key]) ? myOptions[key] : undefined,
value: groupedOptions.value
});
}
}
}
}
return unorderedLists;
},
/**
* @method
* @desc When list header is clicked. `this.$forceUpdate()` triggers when done update titleClick[category]
* @param {HTMLEvent} e - HTML Event Click
*/
listHeaderClick(e) {
let category = _.camelCase(e.currentTarget.parentElement.dataset.category);
let condition = this.titleClick[category];
this.titleClick[category] = !condition;
this.$forceUpdate();
},
/**
* @method emitOptions
* @desc Emit Events to `$root` by check the `input.type`
* @param {{ input: HTMLElement, value: String | Boolean, button: HTMLElement, allCopy: Object }} value - Grab Options Value
*/
emitOptions({ input, value, button, allCopy }) {
// Emit event to alert other similar components
switch (true) {
case (/select/g).test(input.type):
/**
* @event component:OptionsContainerComponent~customCodeEditor:getOptions
* @desc ```js
* // This function emits on input type `select`
* this.$root.$emit('customCodeEditor:getOptions', {
* customCodeEditor: {
* field: this.$parent.field.name,
* input: input,
* name: input.name,
* value: value.toString(),
* action: button.className.replace('-options', '').trim(),
* options: allCopy,
* button: button
* }
* });
* ```
*/
this.$root.$emit('customCodeEditor:getOptions', {
customCodeEditor: {
field: this.$parent.field.name,
input: input,
name: input.name,
value: value.toString(),
action: button.className.replace('-options', '').trim(),
options: allCopy,
button: button
}
});
break;
case (/range/g).test(input.type):
/**
* @event component:OptionsContainerComponent~customCodeEditor:getOptions
* @desc ```js
* // This function emits on input type `range`
* this.$root.$emit('customCodeEditor:getOptions', {
* customCodeEditor: {
* field: this.$parent.field.name,
* input: input,
* name: input.name,
* value: parseFloat(input.value),
* action: button.className.replace('-options', '').trim(),
* options: allCopy,
* button: button
* }
* });
* ```
*/
this.$root.$emit('customCodeEditor:getOptions', {
customCodeEditor: {
field: this.$parent.field.name,
input: input,
name: input.name,
value: parseFloat(input.value),
action: button.className.replace('-options', '').trim(),
options: allCopy,
button: button
}
});
break;
case (/checkbox/g).test(input.type):
/**
* @event component:OptionsContainerComponent~customCodeEditor:getOptions
* @desc ```js
* // This function emits on input type `checkbox`
* this.$root.$emit('customCodeEditor:getOptions', {
* customCodeEditor: {
* field: this.$parent.field.name,
* input: input,
* name: input.name,
* value: input.checked,
* action: button.className.replace('-options', '').trim(),
* options: allCopy,
* button: button
* }
* });
* ```
*/
this.$root.$emit('customCodeEditor:getOptions', {
customCodeEditor: {
field: this.$parent.field.name,
input: input,
name: input.name,
value: input.checked,
action: button.className.replace('-options', '').trim(),
options: allCopy,
button: button
}
});
break;
}
},
/**
* @method updateOptions
* @desc Update Options whenever other similar OptionsContainer is modified
* @param {Event} e - Vue Event Emitter
* @fires component:OptionsContainerComponent~customCodeEditor:getOptions
*/
updateOptions(e) {
if (!_.isUndefined(e.customCodeEditor) && !_.isUndefined(e.customCodeEditor.value) && e.customCodeEditor.field !== this.$parent.field.name) {
// Find input from this current component
let input = this.$el.querySelector(`[name="${e.customCodeEditor.input.name}"]`);
switch (e.customCodeEditor.input.type) {
case 'checkbox':
input.checked = e.customCodeEditor.value;
break;
default:
input.value = e.customCodeEditor.value;
input.removeAttribute('value');
}
if (e.customCodeEditor.action) {
let copyButton = this.$parent.$el.querySelector('button.copy-options');
switch (e.customCodeEditor.action) {
case 'copy':
copyButton.dataset.clipboardText = JSON.stringify(e.customCodeEditor.options);
break;
case 'undo':
// Remove self.options[key] if available
if (this.options[e.customCodeEditor.name]) {
delete this.options[e.customCodeEditor.name];
}
copyButton.dataset.clipboardText = JSON.stringify(this.options);
break;
case 'delete':
if (this.options) {
this.options = {};
delete copyButton.dataset.clipboardText;
}
break;
}
}
}
}
},
render(h) {
if (this.editor && this.optionsTypes) {
return this.loopOptions(this.options, h);
} else {
return null;
}
}
};
</script>
<style scoped lang="scss">
@import '../scss/index.scss';
.label-text {
color: $dim-gray;
align-self: stretch;
margin-bottom: 10px;
@include arial-14-regular;
}
.editor-options-container {
list-style-type: none;
.editor-options-title {
color: $dark-slate-gray-3;
text-align: left;
@include arial-20-bold;
}
}
// Tooltip
.tooltip {
position: relative;
display: inline-block;
border-bottom: 1px dotted black;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: black;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 5px;
/* Position the tooltip */
position: absolute;
z-index: 1;
right: 20px;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}
.lists-inputs {
padding: 10px 0 10px 0;
gap: 8px;
display: flex;
flex-direction: column;
// Select
& select {
padding: 5px 20px;
width: 80%;
font-size: 12px;
border-radius: 5px;
background: #f8f8f8;
border: none;
font-size: 15px;
}
// Checkbox
input[type='checkbox'] {
display: block;
border: none;
background-color: #ccc;
width: 62px;
height: 27px;
border-radius: 3px;
box-shadow: inset 0 1px 4px rgba(0, 0, 0, .2);
cursor: pointer;
position: relative;
transition: background-color 1s;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
input[type='checkbox'].error {
background-color: #FF4C1F;
}
input[type='checkbox']:after {
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
width: 45%;
height: 80%;
background-color: #fdfdfd;
margin: 4%;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0, 0, 0, .2);
background: rgb(255, 255, 255);
background: -moz-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(243, 243, 243, 1) 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(255, 255, 255, 1)), color-stop(100%, rgba(243, 243, 243, 1)));
background: -webkit-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(243, 243, 243, 1) 100%);
background: -o-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(243, 243, 243, 1) 100%);
background: -ms-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(243, 243, 243, 1) 100%);
background: linear-gradient(to bottom, rgba(255, 255, 255, 1) 0%, rgba(243, 243, 243, 1) 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f3f3f3', GradientType=0);
transition: .5s all;
}
input[type='checkbox']:checked {
background-color: #89F869;
}
input[type='checkbox']:checked:after {
left: 45%;
}
// Slider
/* Range Slider */
.range-slider__range {
appearance: none;
width: calc(100% - (73px));
height: 19px;
border-radius: 5px;
border: 1px solid #E1E1E1;
background: #F0F0F0;
outline: none;
padding: 0;
display: inline-block;
margin: 0;
}
.range-slider__range::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 23px;
height: 23px;
border-radius: 5px;
background: #484848;
cursor: pointer;
transition: background .15s ease-in-out;
}
.range-slider__range::-webkit-slider-thumb:hover {
background: #3a3a3a;
}
.range-slider__range:active::-webkit-slider-thumb {
background: #2e2b2b;
}
.range-slider__range::-moz-range-thumb {
width: 23px;
height: 23px;
border: 0;
border-radius: 5px;
background: #484848;
cursor: pointer;
transition: background .15s ease-in-out;
}
.range-slider__range::-moz-range-thumb:hover {
background: #3a3a3a;
}
.range-slider__range:active::-moz-range-thumb {
background: #2e2b2b;
}
.range-slider__range:focus::-webkit-slider-thumb {
box-shadow: 0 0 0 3px #fff, 0 0 0 6px #2e2b2b;
}
.range-slider__value {
display: inline-block;
position: relative;
width: fit-content;
color: #fff;
line-height: 20px;
text-align: center;
border-radius: 3px;
background: #484848;
padding: 5px 10px;
margin-left: 8px;
}
.range-slider__value:after {
position: absolute;
inset: -45% auto auto 2px;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #2c3e50;
border-top: 7px solid transparent;
content: '';
}
::-moz-range-track {
background: #d7dcdf;
border: 0;
}
input::-moz-focus-inner,
input::-moz-focus-outer {
border: 0;
}
}
</style>