Skip to content

Commit

Permalink
feat(native-form): add native form (#62)
Browse files Browse the repository at this point in the history
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
TheSharpieOne authored Aug 20, 2018
1 parent 8c8534a commit 8aa0471
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 0 deletions.
33 changes: 33 additions & 0 deletions packages/native-form/README.md
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.
20 changes: 20 additions & 0 deletions packages/native-form/flattenObject.js
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;
36 changes: 36 additions & 0 deletions packages/native-form/index.js
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();
};
20 changes: 20 additions & 0 deletions packages/native-form/package.json
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"
}
}
177 changes: 177 additions & 0 deletions packages/native-form/tests/test.js
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();
});
});
});

0 comments on commit 8aa0471

Please sign in to comment.