-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(native-form): add native form (#62)
Submit JSON data via a native form, not AJAX. Useful when you need to open a new page with a POST action.
- Loading branch information
1 parent
8c8534a
commit 8aa0471
Showing
5 changed files
with
286 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# native form | ||
> Submit JSON data via a native form, not AJAX. Useful when you need to open a new page with a POST action. | ||
## Install | ||
|
||
``` | ||
npm install @availity/native-form --save | ||
``` | ||
|
||
## Usage | ||
|
||
```js | ||
nativeForm(spaceId[, params[, formAttributes]]); | ||
``` | ||
|
||
### Required params | ||
|
||
- spaceId: String | ||
|
||
### Optional params | ||
|
||
- params: Object. Additional parameters you want sent in the post. | ||
- formAttributes: Object. Set/override the form attributes like `target`, `method`, and `action`. `method` defaults to "post", `action` will default to "\`/ms/api/availity/internal/spaces/magneto/sso/v1/saml/${spaceId}\`", and `target` will default to "_blank". Additional attributes can be defined and should be valid on an HTML form element. | ||
|
||
### | ||
|
||
```js | ||
import nativeForm from '@availity/native-form'; | ||
|
||
nativeForm('12312312312', {myExtraParam: 'myExtraParamValue'}, {target: '_top'}); | ||
``` | ||
|
||
When `nativeForm` is called it wil create a native HTML form and submit it. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
const flattenObject = ob => | ||
Object.keys(ob).reduce((toReturn, k) => { | ||
if (Object.prototype.toString.call(ob[k]) === '[object Date]') { | ||
toReturn[k] = ob[k].toJSON(); | ||
} else if (ob[k] && typeof ob[k] === 'object') { | ||
const flatObject = flattenObject(ob[k]); | ||
const isArray = Array.isArray(ob[k]); | ||
Object.keys(flatObject).forEach(k2 => { | ||
toReturn[ | ||
`${k}${isArray ? k2.replace(/^(\d+)(\..*)?/, '[$1]$2') : `.${k2}`}` | ||
] = flatObject[k2].toString(); | ||
}); | ||
} else { | ||
toReturn[k] = ob[k].toString(); | ||
} | ||
|
||
return toReturn; | ||
}, {}); | ||
|
||
export default flattenObject; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import flattenObject from './flattenObject'; | ||
|
||
const required = field => { | ||
throw new Error(`${field} is required and was not provided`); | ||
}; | ||
|
||
export default ( | ||
spaceId = required('spaceId'), | ||
params = {}, | ||
formAttributes = {} | ||
) => { | ||
const mergedOptions = Object.assign( | ||
{ | ||
method: 'post', | ||
action: `/ms/api/availity/internal/spaces/magneto/sso/v1/saml/${spaceId}`, | ||
target: '_blank', | ||
}, | ||
formAttributes | ||
); | ||
const form = document.createElement('form'); | ||
Object.keys(mergedOptions).forEach(key => { | ||
form.setAttribute(key, mergedOptions[key]); | ||
}); | ||
const flat = flattenObject(params); | ||
const fields = Object.keys(flat) | ||
.map(key => { | ||
const name = key.replace(/\[\d+\]/g, '[]'); | ||
const value = flat[key]; | ||
return `<input type="hidden" name="${name}" value="${value}" />`; | ||
}) | ||
.join(''); | ||
|
||
form.insertAdjacentHTML('beforeend', fields); | ||
document.body.appendChild(form); | ||
form.submit(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"name": "@availity/native-form", | ||
"version": "1.0.0", | ||
"description": "Submit JSON data via a native form, not AJAX. Useful when you need to open a new page with a POST action.", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "test" | ||
}, | ||
"keywords": [ | ||
"availity", | ||
"form", | ||
"native", | ||
"submission" | ||
], | ||
"author": "Evan Sharp <evan.sharp@availity.com>", | ||
"license": "MIT", | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import nativeForm from '../'; | ||
import flattenObject from '../flattenObject'; | ||
|
||
describe('nativeForm', () => { | ||
const complexObject = { | ||
a: { b: 'c', d: 1, e: ['f', 'g', 2, 3, { h: { i: 'j', k: [4, 'l'] } }] }, | ||
}; | ||
|
||
describe('flattenObject', () => { | ||
test('should return an object', () => { | ||
expect( | ||
Object.prototype.toString.call(flattenObject({ a: { b: 'c' } })) | ||
).toBe('[object Object]'); | ||
}); | ||
describe('returned object', () => { | ||
let result; | ||
beforeEach(() => { | ||
result = flattenObject(complexObject); | ||
}); | ||
test('should be 1 level deep', () => { | ||
Object.keys(result).forEach(key => { | ||
expect(Object.prototype.toString.call(result[key])).toBe( | ||
'[object String]' | ||
); | ||
}); | ||
}); | ||
test('should correctly represent the keys as dot notation', () => { | ||
const expected = { | ||
'a.b': 'c', | ||
'a.d': '1', | ||
'a.e[0]': 'f', | ||
'a.e[1]': 'g', | ||
'a.e[2]': '2', | ||
'a.e[3]': '3', | ||
'a.e[4].h.i': 'j', | ||
'a.e[4].h.k[0]': '4', | ||
'a.e[4].h.k[1]': 'l', | ||
}; | ||
expect(result).toEqual(expected); | ||
}); | ||
}); | ||
}); | ||
describe('default export', () => { | ||
beforeEach(() => { | ||
window.HTMLFormElement.prototype.submit = jest.fn(); | ||
}); | ||
|
||
afterEach(() => { | ||
const form = document.querySelector('form'); | ||
if (form) form.remove(); | ||
}); | ||
test('spaceId is required', () => { | ||
expect(() => nativeForm()).toThrowError( | ||
'spaceId is required and was not provided' | ||
); | ||
}); | ||
|
||
test('create a form', () => { | ||
nativeForm('spaceId'); | ||
expect(document.querySelector('form')).not.toBeNull(); | ||
}); | ||
|
||
describe('the form', () => { | ||
test('should be added to the body (to be able to be submitted)', () => { | ||
nativeForm('spaceId'); | ||
expect(document.body.querySelector('form')).not.toBeNull(); | ||
}); | ||
|
||
test('action should have the space id in the URL by default', () => { | ||
nativeForm('spaceId123'); | ||
expect(document.querySelector('form').getAttribute('action')).toBe( | ||
'/ms/api/availity/internal/spaces/magneto/sso/v1/saml/spaceId123' | ||
); | ||
}); | ||
|
||
test('action should be overridable', () => { | ||
nativeForm('spaceId123', {}, { action: '/my/url/here' }); | ||
expect(document.querySelector('form').getAttribute('action')).toBe( | ||
'/my/url/here' | ||
); | ||
}); | ||
|
||
test('method should be post by default', () => { | ||
nativeForm('spaceId'); | ||
expect(document.querySelector('form').getAttribute('method')).toBe( | ||
'post' | ||
); | ||
}); | ||
|
||
test('method should be overridable', () => { | ||
nativeForm('spaceId123', {}, { method: 'get' }); | ||
expect(document.querySelector('form').getAttribute('method')).toBe( | ||
'get' | ||
); | ||
}); | ||
|
||
test('target should be _blank by default', () => { | ||
nativeForm('spaceId'); | ||
expect(document.querySelector('form').getAttribute('target')).toBe( | ||
'_blank' | ||
); | ||
}); | ||
|
||
test('target should be overridable', () => { | ||
nativeForm('spaceId123', {}, { target: '_top' }); | ||
expect(document.querySelector('form').getAttribute('target')).toBe( | ||
'_top' | ||
); | ||
}); | ||
|
||
test('addtional attributes can be defined', () => { | ||
nativeForm('spaceId123', {}, { id: 'myForm', class: 'my-form' }); | ||
expect(document.querySelector('form').getAttribute('id')).toBe( | ||
'myForm' | ||
); | ||
expect(document.querySelector('form').getAttribute('class')).toBe( | ||
'my-form' | ||
); | ||
}); | ||
|
||
test('should have no field if no data was provided', () => { | ||
nativeForm('spaceId123'); | ||
expect(document.querySelector('input')).toBeNull(); | ||
}); | ||
|
||
test('should have fields if was provided', () => { | ||
nativeForm('spaceId123', { a: 'b' }); | ||
expect(document.querySelector('input')).not.toBeNull(); | ||
}); | ||
|
||
describe('the fields', () => { | ||
test('should have a field for each piece of data (no more, no less)', () => { | ||
nativeForm('spaceId123', complexObject); | ||
expect(document.querySelectorAll('input').length).toBe( | ||
Object.keys(flattenObject(complexObject)).length | ||
); | ||
}); | ||
|
||
test('the names will match the flat object, but have the array index value removed (the way the back-end likes it)', () => { | ||
// Note: this causes multiple fields to have the same name, so we have to account for that | ||
nativeForm('spaceId123', complexObject); | ||
const flat = flattenObject(complexObject); | ||
const count = {}; | ||
Object.keys(flat).forEach(key => { | ||
const name = key.replace(/\[\d+\]/g, '[]'); | ||
count[name] = count[name] || 0; | ||
expect( | ||
document.querySelectorAll(`[name="${name}"`)[count[name]] | ||
).not.toBeNull(); | ||
count[name] += 1; | ||
}); | ||
}); | ||
|
||
test('data should match the values within the object', () => { | ||
// Note: this causes multiple fields to have the same name, so we have to account for that | ||
nativeForm('spaceId123', complexObject); | ||
const flat = flattenObject(complexObject); | ||
const count = {}; | ||
Object.keys(flat).forEach(key => { | ||
const name = key.replace(/\[\d+\]/g, '[]'); | ||
count[name] = count[name] || 0; | ||
const value = flat[key]; | ||
expect( | ||
document.querySelectorAll(`[name="${name}"`)[count[name]].value | ||
).toBe(value); | ||
count[name] += 1; | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
test('submit the form', () => { | ||
nativeForm('spaceId'); | ||
expect(window.HTMLFormElement.prototype.submit).toHaveBeenCalled(); | ||
}); | ||
}); | ||
}); |