Skip to content

Commit

Permalink
feat: custom option attributes
Browse files Browse the repository at this point in the history
resolves #1038, #986, #956
  • Loading branch information
kevinchappell committed Aug 24, 2020
1 parent 17669c6 commit c072cae
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 62 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"semi": ["error", "never"],
"no-unused-vars": 1,
"no-prototype-builtins": 0,
"arrow-parens": ["warn", "as-needed"],
"prefer-const": [
1,
{
Expand Down
12 changes: 8 additions & 4 deletions src/demo/js/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,10 @@ jQuery(function($) {
onAddField: fieldId => {
setCurrentFieldIdValues(fieldId)
},
onAddOption: (optionTemplate, optionIndex) => {
optionTemplate.label = `Option ${optionIndex + 1}`
optionTemplate.value = `option-${optionIndex + 1}`
onAddOption: (optionTemplate, {index}) => {
optionTemplate.label = optionTemplate.label || `Option ${index + 1}`
optionTemplate.value = optionTemplate.value || `option-${index + 1}`

return optionTemplate
},
onClearAll: () => window.sessionStorage.removeItem('formData'),
Expand Down Expand Up @@ -316,7 +317,10 @@ jQuery(function($) {
demoApi.appendChild(generateActionTable(demoActions, columns))

if (formData && formData !== '[]') {
document.getElementById('set-form-data-value').value = window.JSON.stringify(JSON.parse(formData), null, ' ')
const setFormDataInputValue = document.getElementById('set-form-data-value')
if (setFormDataInputValue) {
setFormDataInputValue.value = window.JSON.stringify(JSON.parse(formData), null, ' ')
}
}

langSelect.addEventListener(
Expand Down
4 changes: 2 additions & 2 deletions src/js/control/select.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import control from '../control'
import { trimObj } from '../utils'

/**
* Text input class
Expand Down Expand Up @@ -134,7 +135,7 @@ export default class controlSelect extends control {

// build & return the DOM elements
if (type == 'select') {
this.dom = this.markup(optionType, options, data)
this.dom = this.markup(optionType, options, trimObj(data, true))
} else {
this.dom = this.markup('div', options, { className: type })
}
Expand Down Expand Up @@ -194,7 +195,6 @@ export default class controlSelect extends control {
return
}

// let foundMatch = false
for (let i = 0; i < selectedOptions.length; i++) {
if (input.value === selectedOptions[i]) {
input.setAttribute('checked', true)
Expand Down
83 changes: 40 additions & 43 deletions src/js/form-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
closest,
safename,
forceNumber,
getContentType
} from './utils'
import { css_prefix_text } from '../fonts/config.json'

Expand Down Expand Up @@ -271,23 +272,16 @@ const FormBuilder = function(opts, element, $) {
* @return {String} field options markup
*/
const fieldOptions = function(fieldData) {
const { type, values, name } = fieldData
const { type, values } = fieldData
let fieldValues
const optionActions = [m('a', mi18n.get('addOption'), { className: 'add add-opt' })]
const fieldOptions = [m('label', mi18n.get('selectOptions'), { className: 'false-label' })]
const isMultiple = fieldData.multiple || type === 'checkbox-group'
const optionDataTemplate = label => {
const optionData = {
label,
value: hyphenCase(label),
}

if (type !== 'autocomplete') {
optionData.selected = false
}

return optionData
}
const optionDataTemplate = label => ({
selected: false,
label,
value: hyphenCase(label),
})

if (!values || !values.length) {
let defaultOptCount = [1, 2, 3]
Expand All @@ -308,7 +302,9 @@ const FormBuilder = function(opts, element, $) {
const optionActionsWrap = m('div', optionActions, { className: 'option-actions' })
const options = m(
'ol',
fieldValues.map(option => selectFieldOptions(name, option, isMultiple)),
fieldValues.map((option, index) => {
const optionData = config.opts.onAddOption(option, {type, index, isMultiple})
return selectFieldOptions(optionData, isMultiple)}),
{
className: 'sortable-options',
},
Expand Down Expand Up @@ -512,11 +508,10 @@ const FormBuilder = function(opts, element, $) {

/**
* Detects the type of user defined attribute
* @param {String} attr attribute name
* @param {Object} attrData attribute config
* @return {String} type of user attr
*/
function userAttrType(attr, attrData) {
function userAttrType(attrData) {
return (
[
['array', ({ options }) => !!options],
Expand Down Expand Up @@ -553,7 +548,7 @@ const FormBuilder = function(opts, element, $) {

for (const attribute in typeUserAttr) {
if (typeUserAttr.hasOwnProperty(attribute)) {
const attrValType = userAttrType(attribute, typeUserAttr[attribute])
const attrValType = userAttrType(typeUserAttr[attribute])
const orig = mi18n.get(attribute)
const tUA = typeUserAttr[attribute]
const origValue = tUA.value || ''
Expand Down Expand Up @@ -974,35 +969,37 @@ const FormBuilder = function(opts, element, $) {
}

// Select field html, since there may be multiple
const selectFieldOptions = function(name, optionData, multipleSelect) {
const selectFieldOptions = function(optionData, multipleSelect) {
const optionTemplate = { selected: false, label: '', value: '' }
const optionInputType = {
selected: multipleSelect ? 'checkbox' : 'radio',
}
const optionDataOrder = ['value', 'label', 'selected']
const optionInputs = []
const optionTemplate = { selected: false, label: '', value: '' }

optionData = Object.assign(optionTemplate, optionData)

for (let i = optionDataOrder.length - 1; i >= 0; i--) {
const prop = optionDataOrder[i]
if (optionData.hasOwnProperty(prop)) {
const attrs = {
type: optionInputType[prop] || 'text',
className: 'option-' + prop,
value: optionData[prop],
name: name + '-option',
const optionInputTypeMap = {
boolean: (value, prop) => {
const attrs = {value, type: optionInputType[prop] || 'checkbox'}
if (value) {
attrs.checked = !!value
}
return['input', null, attrs]
},
number: value => ['input', null, {value, type: 'number'}],
string: (value, prop) => (['input', null, {value, type: 'text', placeholder: mi18n.get(`placeholder.${prop}`) || ''}]),
array: values => ['select', values.map(({label, value}) => m('option', label, {value}))],
object: ({tag, content, ...attrs}) => [tag, content, attrs],
}

attrs.placeholder = mi18n.get(`placeholder.${prop}`) || ''
optionData = {...optionTemplate, ...optionData}

if (prop === 'selected' && optionData.selected === true) {
attrs.checked = optionData.selected
}
const optionInputs = Object.entries(optionData).map(([prop, val]) => {
const optionInputDataType = getContentType(val)

optionInputs.push(m('input', null, attrs))
}
}
const [tag, content, attrs] = optionInputTypeMap[optionInputDataType](val, prop)
const optionClassName = `option-${prop} option-attr`
attrs['data-attr'] = prop
attrs.className = attrs.className ? `${attrs.className} ${optionClassName}` : optionClassName

return m(tag, content, attrs)
})

const removeAttrs = {
className: `remove btn ${css_prefix_text}cancel`,
Expand Down Expand Up @@ -1296,6 +1293,7 @@ const FormBuilder = function(opts, element, $) {
// Attach a callback to add new options
$stage.on('click', '.add-opt', function(e) {
e.preventDefault()
const type = $(e.target).closest('.form-field').attr('type')
const $optionWrap = $(e.target).closest('.field-options')
const $multiple = $('[name="multiple"]', $optionWrap)
const $firstOption = $('.option-selected:eq(0)', $optionWrap)
Expand All @@ -1307,11 +1305,10 @@ const FormBuilder = function(opts, element, $) {
isMultiple = $firstOption.attr('type') === 'checkbox'
}

const name = $firstOption.attr('name').replace(/-option$/, '')

const optionTemplate = { selected: false, label: '', value: '' }
const optionData = config.opts.onAddOption(optionTemplate, $('.sortable-options', $optionWrap).children().length)
$('.sortable-options', $optionWrap).append(selectFieldOptions(name, optionData, isMultiple))
const $sortableOptions = $('.sortable-options', $optionWrap)
const optionData = config.opts.onAddOption(optionTemplate, {type, index: $sortableOptions.children().length, isMultiple})
$sortableOptions.append(selectFieldOptions(optionData, isMultiple))
})

$stage.on('mouseover mouseout', '.remove, .del-button', e => $(e.target).closest('li').toggleClass('delete'))
Expand Down
2 changes: 1 addition & 1 deletion src/js/form-render.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ class FormRender {
.filter(fieldData => fieldData.subtype === 'tinymce')
.forEach(fieldData => window.tinymce.get(fieldData.name).save())

this.instanceContainers.forEach((container) => {
this.instanceContainers.forEach(container => {
const userDataMap = $('select, input, textarea', container)
.serializeArray()
.reduce((acc, { name, value }) => {
Expand Down
24 changes: 15 additions & 9 deletions src/js/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,16 +129,22 @@ export default class Helpers {
const $options = $('.sortable-options li', field)

$options.each(i => {
const $option = $($options[i])
const selected = $('.option-selected', $option).is(':checked')
const attrs = {
label: $('.option-label', $option).val(),
value: $('.option-value', $option).val(),
}
const option = $options[i]
const stringAttrs = option.querySelectorAll('input[type=text], input[type=number], select')
const boolAttrs = option.querySelectorAll('input[type=checkbox], input[type=radio]')
const attrs = {}

forEach(stringAttrs, i => {
const stringAttr = stringAttrs[i]
const attrName = stringAttr.dataset.attr
attrs[attrName] = stringAttr.value
})

if (selected) {
attrs.selected = selected
}
forEach(boolAttrs, i => {
const boolAttr = boolAttrs[i]
const attrName = boolAttr.getAttribute('data-attr')
attrs[attrName] = boolAttr.checked
})

options.push(attrs)
})
Expand Down
2 changes: 1 addition & 1 deletion src/js/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default class layout {
className: processClassName(data, field)
})
},
hidden: (field) => {
hidden: field => {
// no wrapper any any visible elements
return field
}
Expand Down
6 changes: 4 additions & 2 deletions src/sass/_stage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,10 @@
padding: 0;

> li {
display: flex;
cursor: move;
margin: 1px;
padding-right: 28px;

&:nth-child(1) .remove {
display: none;
Expand All @@ -447,7 +449,7 @@
.remove {
position: absolute;
opacity: 1;
right: 14px;
right: 8px;
height: 18px;
width: 18px;
top: 14px;
Expand All @@ -473,7 +475,7 @@

input[type='text'] {
width: calc(44.5% - 17px);
margin: 0 1%;
margin: 0 3px;
float: none;
}
}
Expand Down

0 comments on commit c072cae

Please sign in to comment.