Skip to content

Commit

Permalink
Merge pull request #1456 from lucasnetau/setData-fixes
Browse files Browse the repository at this point in the history
Initialise formRender in cases of undefined or empty formData
  • Loading branch information
kevinchappell authored Oct 19, 2023
2 parents a23eb75 + 48700de commit a491812
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 91 deletions.
17 changes: 10 additions & 7 deletions docs/formRender/actions/clear.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
Clears all user data from the form, even tinyMCE.

## Usage
<pre><code class="js">const formRenderOptions = {
```javascript
const formRenderOptions = {
formData: '[{"type":"text","label":"Text Field","name":"text-1526099104236","subtype":"text"}]';
}
const formRenderInstance = $('#render-container').formRender(formRenderOptions);
const html = formRenderInstance('html'); // HTML DOM nodes
const renderContainer = $('#render-container')
const formRenderInstance = renderContainer.formRender(formRenderOptions);
const html = renderContainer.formRender('html'); // HTML DOM nodes
const htmlString = html.outerHTML;
</code></pre>
```
// User enters data
<pre><code class="js">formRenderInstance.clear();
// or
$('#render-container').formRender('clear')</code></pre>

```javascript
$('#render-container').formRender('clear')
```
// User entered data is cleared
4 changes: 2 additions & 2 deletions docs/formRender/actions/html.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
To grab the rendered form's HTML complete with user entered data

## Usage
<pre><code class="js">
```javascript
const html = $('#render-container').formRender('html'); // HTML string
</code></pre>
```

### In Action
<p data-height="300" data-theme-id="22927" data-slug-hash="wWvyaM" data-default-tab="result" data-user="kevinchappell" data-embed-version="2" class="codepen"></p>
8 changes: 4 additions & 4 deletions docs/formRender/actions/render.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# `render` action

Programmtically render or re-render a formRender instance with new data
Programmatically render or re-render a formRender instance with new data

Arguments
| Arg | Type | Value(s) | Default |
Expand All @@ -9,9 +9,9 @@ Arguments
| options | {Object} | override options for new render | {} |

## Usage
<pre><code class="js">const wrap = $('.render-wrap');
```javascript
const wrap = $('.render-wrap');
const formRender = wrap.formRender();
// then
wrap.formRender('render', formData);
// or
formRender.actions.render(formData)</code></pre>
```
5 changes: 3 additions & 2 deletions docs/formRender/actions/setData.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
Set formData *without* rendering or re-initializing formRender.

## Usage
<pre><code class="js">const wrap = $('.render-wrap');
```javascript
const wrap = $('.render-wrap');
const formRender = wrap.formRender();
// then
wrap.formRender('setData', formData);
</code></pre>
```

## Demo
<p data-height="300" data-theme-id="22927" data-slug-hash="MWrgyJV" data-default-tab="js,result" data-user="kevinchappell" data-pen-title="formRender: userData" class="codepen">See the Pen</p>
36 changes: 18 additions & 18 deletions docs/formRender/actions/userData.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,46 @@ The UserData option aims to enable formRender to both capture the form data befo

## Usage
1. Setup formRender with base definition as normal
<pre><code class="js">var formRenderOptions = {
```javascript
var formRenderOptions = {
formData: '[{"type":"text","label":"Text Field","name":"text-1526099104236","subtype":"text"}]';
}
var formRenderInstance = $('#render-container').formRender(formRenderOptions);
</code></pre>
```

1. After the user has entered data into the rendered form, their data is accessible from `formRenderInstance.userData` or `$('#render-container').formRender('userData')`
<pre><code class="js">console.log(formRenderInstance.userData);
```javascript
console.log(formRenderInstance.userData);
// "[{"type":"text","label":"Full Name","name":"text-1526099104236","subtype":"text","userData":["John Smith"]}]"
</code></pre>
```

UserData works for autocomplete, select, checkbox-group, radio-group, text, email, color, tel, number, hidden, date, textarea, textarea-tinymce.

For fields that have an "other" option, a value that is not in the pre-defined values is assumed to be the "other" value.

<pre><code class="js">var formData = '[{"type":"checkbox-group","label":"Checkbox Group","name":"checkbox-group-1526095813035","other":true,"values":[{"label":"Option 1","value":"option-1"},{"label":"2","value":"2"}],"userData":["option-1","Bilbo \\\"baggins\\\""]}]';
```javascript
var formData = '[{"type":"checkbox-group","label":"Checkbox Group","name":"checkbox-group-1526095813035","other":true,"values":[{"label":"Option 1","value":"option-1"},{"label":"2","value":"2"}],"userData":["option-1","Bilbo \\\"baggins\\\""]}]';

var formRenderOptions = {formData};
var frInstance = $('#renderMe').formRender(formRenderOptions);</code></pre>
var frInstance = $('#renderMe').formRender(formRenderOptions);
```

A common use case for userData would be to create a form, save the input data from the form into a database, and then refresh the page with the saved data. Simply stringify userData when posting.

1. Capture form data
<pre>
<code>
$.ajax({
```javascript
$.ajax({
type: "POST",
url: url,
data: JSON.stringify(frInstance.userData)
});
</code>
</pre>
});
```

2. Send the saved JSON data back into formRender to display data
<pre>
<code>
var fbOptions.formData = {SavedJsonFromDatabase};
var frInstance = $('#renderMe').formRender(fbOptions);
</code>
</pre>
```javascript
var fbOptions.formData = {SavedJsonFromDatabase};
var frInstance = $('#renderMe').formRender(fbOptions);
```

## Demo
<p data-height="300" data-theme-id="22927" data-slug-hash="QGjqbV" data-default-tab="js,result" data-user="kevinchappell" data-pen-title="formRender: userData" class="codepen">See the Pen</p>
Expand Down
63 changes: 29 additions & 34 deletions src/js/form-render.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class FormRender {
container: false, // string selector or Node element
dataType: 'json',
disableHTMLLabels: false,
formData: false,
formData: [],
i18n: Object.assign({}, defaultI18n),
messages: {
formRendered: 'Form Rendered',
Expand Down Expand Up @@ -72,12 +72,12 @@ class FormRender {
}

// parse any passed formData
if (!this.options.formData) {
return false
if (this.options.formData) {
this.options.formData = this.parseFormData(this.options.formData)
} else {
this.options.formData = []
}

this.options.formData = this.parseFormData(this.options.formData)

// ability for controls to have their own configuration / options of the format control identifier (type, or type.subtype): {options}
control.controlConfig = options.controlConfig || {}

Expand Down Expand Up @@ -142,7 +142,7 @@ class FormRender {
/**
* Clean up passed object configuration to prepare for use with the markup function
* @param {Object} field - object of field configuration
* @param {number} instanceIndex - instance index
* @param {number} [instanceIndex] - instance index
* @return {Object} sanitized field object
*/
sanitizeField(field, instanceIndex) {
Expand Down Expand Up @@ -196,10 +196,9 @@ class FormRender {
// Begin the core plugin
const rendered = []

// generate field markup if we have fields
if (opts.formData) {
// instantiate the layout class & loop through the field configuration
const engine = new opts.layout(opts.layoutTemplates, false, opts.disableHTMLLabels)
// instantiate the layout class & loop through the field configuration
const engine = new opts.layout(opts.layoutTemplates, false, opts.disableHTMLLabels)
if (opts.formData.length) {
for (let i = 0; i < opts.formData.length; i++) {
const fieldData = opts.formData[i]
const sanitizedField = this.sanitizeField(fieldData, instanceIndex)
Expand All @@ -210,33 +209,29 @@ class FormRender {

rendered.push(field)
}
} else {
opts.notify.warning(opts.messages.noFormData)
}

if (element) {
this.instanceContainers[instanceIndex] = element
}
if (element) {
this.instanceContainers[instanceIndex] = element
}

// if rendering, inject the fields into the specified wrapper container/element
if (opts.render && element) {
element.emptyContainer()
element.appendFormFields(rendered)

runCallbacks()
opts.notify.success(opts.messages.formRendered)
} else {
/**
* Retrieve the html markup for a passed array of DomElements
* @param {Array} fields - array of dom elements
* @return {string} fields html
*/
const exportMarkup = fields => fields.map(elem => elem.innerHTML).join('')
formRender.markup = exportMarkup(rendered)
}
// if rendering, inject the fields into the specified wrapper container/element
if (opts.render && element) {
element.emptyContainer()
element.appendFormFields(rendered)

runCallbacks()
opts.notify.success(opts.messages.formRendered)
} else {
const noData = utils.markup('div', opts.messages.noFormData, {
className: 'no-form-data',
})
rendered.push(noData)
opts.notify.error(opts.messages.noFormData)
/**
* Retrieve the html markup for a passed array of DomElements
* @param {Array} fields - array of dom elements
* @return {string} fields html
*/
const exportMarkup = fields => fields.map(elem => elem.innerHTML).join('')
formRender.markup = exportMarkup(rendered)
}

if (opts.disableInjectedStyle === true) {
Expand Down
29 changes: 15 additions & 14 deletions src/js/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@ const processClassName = (data, field) => {

if (classes && classes.length > 0) {
className += ` ${classes.join(' ')}`
}

// Now that the row- & col- types were lifted, remove from the actual input field and any child elements
if (field.classList) {
field.classList.remove(...classes)
}

//field may be a single element, dom fragment, or an array of elements
if (!Array.isArray(field)) {
field = [field]
}
field.forEach(item => item.querySelectorAll('[class*=row-],[class*=col-]').forEach(element => {
if (element.classList) {
element.classList.remove(...classes)
// Now that the row- & col- types were lifted, remove from the actual input field and any child elements
//field may be a single element, dom fragment, or an array of elements
if (!Array.isArray(field)) {
field = [field]
}
}))
field.forEach(item => {
if (item.classList) {
item.classList.remove(...classes)
}
item.querySelectorAll('[class*=row-],[class*=col-]').forEach(element => {
if (element.classList) {
element.classList.remove(...classes)
}
})
})
}
}

return className
Expand Down
17 changes: 17 additions & 0 deletions tests/form-builder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,23 @@ describe('FormBuilder typeUserAttrs detection', () => {
input = fbWrap.find('.button-field .testAttribute-wrap label')
expect(input.text()).toBe('override')
})
test('lifts bootstrap classes from field', async () => {
const fbWrap = $('<div>')
const fb = await fbWrap.formBuilder({}).promise
fb.actions.addField({ type: 'text', className: 'form-control row-1 col-md-4'})
const input = fbWrap.find('.form-field[type="text"] .prev-holder input')
expect(input.attr('class')).toContain('form-control')
expect(input.attr('class')).not.toContain('row-1')
expect(input.attr('class')).not.toContain('col-md-4')
fb.actions.addField({ type: 'select', className: 'form-control row-1 col-md-4', values: {'yes':'yes','no':'no'}})
const select = fbWrap.find('.form-field[type="select"] .prev-holder select')
expect(select.attr('class')).toContain('form-control')
expect(select.attr('class')).not.toContain('row-1')
expect(select.attr('class')).not.toContain('col-md-4')
fb.actions.addField({ type: 'autocomplete', className: 'form-control row-1 col-md-4'})
const auto = fbWrap.find('.form-field[type="autocomplete"] .prev-holder .form-group')
expect(auto.find('[class*=row-],[class*=col-]')).toHaveLength(0)
})
})

describe('FormBuilder can return formData', () => {
Expand Down
52 changes: 42 additions & 10 deletions tests/form-render.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,13 @@ const basicTextAreaDef = {
'required': false,
'label': 'Text Area',
'className': 'form-control',
'description': 'A nice help bubble',
'name': 'textarea-elem',
'access': false,
'subtype': 'textarea',
'userData': ['AValue'],
}

describe('FormRender invalid setup', () => {
test('will fail on no formData', () => {
const errors = [], warnings = [], success = []
$('<div>').formRender({notify: errorHandler(errors,warnings,success)})
expect(errors[0]).toBe('No form data.')
expect(success).toHaveLength(0)
})
})

describe('Form Rendering', () => {
test('can render json form document', () => {
const container = $('<div>')
Expand All @@ -31,7 +23,7 @@ describe('Form Rendering', () => {
const html = container.formRender('html')

expect(success[0]).toBe('Form Rendered')
expect(html).toContain('<textarea class="form-control" name="textarea-elem" user-data="AValue" id="textarea-elem"></textarea>')
expect(html).toContain('<textarea class="form-control" name="textarea-elem" user-data="AValue" id="textarea-elem" title="A nice help bubble"></textarea>')
expect(container.find('textarea[name=textarea-elem]').val()).toBe('AValue')
})

Expand Down Expand Up @@ -81,4 +73,44 @@ describe('Form Rendering', () => {
fb.clear()
expect(fb.userData[0].userData).not.toEqual(['string to find'])
})

test('will load empty form on no formData', () => {
const errors = [], warnings = [], success = []
$('<div>').formRender({notify: errorHandler(errors,warnings,success)})
expect(warnings[0]).toBe('No form data.')
expect(success).toHaveLength(1)
})

test('will load empty form on undefined formData', () => {
const errors = [], warnings = [], success = []
$('<div>').formRender({notify: errorHandler(errors,warnings,success), formData: undefined})
expect(warnings[0]).toBe('No form data.')
expect(success).toHaveLength(1)
})

test('Can set data then render after initialisation of empty form', () => {
const errors = [], warnings = [], success = []
const container = $('<div>')
container.formRender({notify: errorHandler(errors,warnings,success)})
container.formRender('setData', [basicTextAreaDef])
let textarea = container.find('#textarea-elem')
expect(textarea).toHaveLength(0)

container.formRender('render')

textarea = container.find('#textarea-elem')
expect(textarea).not.toHaveLength(0)
})

test('Can render after initialisation of empty form', () => {
const errors = [], warnings = [], success = []
const container = $('<div>')
container.formRender({notify: errorHandler(errors,warnings,success)})
let textarea = container.find('#textarea-elem')
expect(textarea).toHaveLength(0)

container.formRender('render', [basicTextAreaDef])
textarea = container.find('#textarea-elem')
expect(textarea).not.toHaveLength(0)
})
})

0 comments on commit a491812

Please sign in to comment.