Skip to content

Commit

Permalink
localstripe.js: Implement unmount and destroy on Element
Browse files Browse the repository at this point in the history
The Stripe.js docs do not specify much of the
interaction among [`mount`], [`unmount`], and [`destroy`] so most of the
following description came by experimentation.

`mount` adds the Stripe element to the given DOM element.
`mount` has no effect if its argument refers to the same DOM element
to which the Stripe element is already mounted. It throws an exception
if the Stripe element is already mounted, unless the argument refers to
the same DOM element on which the Stripe element is already mounted.
It also throws an exception if the Stripe element has been destroyed.

`unmount` removes the Stripe element from the DOM. It is idempotent.
Stripe.js throws an exception if the Stripe element has been destroyed;
our version does not.

`destroy` destroys the Stripe element, removing it from the DOM if
necessary. There can be no more than one Stripe element at a time with
a given type, so you must call `destroy` before creating another.
Stripe.js throws an exception if the Stripe element has been destroyed;
our version does not.

This all requires some extra bookkeeping in `Element`, so it has two
new "private" fields that are not present in a real Stripe object:

1. `_domChildren` tracks all of the DOM elements created by `mount`, and
is used both to tell whether the Element is mounted and for `unmount`
and `destroy` to remove the DOM elements.
2. `_stripeElements` keeps a reference to the `elements` object that
created the `Element`, so it can make the `elements` allow another call
to `create` after `destroy` is called.

[`mount`]: https://stripe.com/docs/js/element/mount
[`unmount`]: https://stripe.com/docs/js/element/other_methods/unmount
[`destroy`]: https://stripe.com/docs/js/element/other_methods/destroy
  • Loading branch information
Brandon Dyck authored and adrienverge committed Feb 6, 2021
1 parent 3b6464c commit 2709044
Showing 1 changed file with 67 additions and 30 deletions.
97 changes: 67 additions & 30 deletions localstripe/localstripe-v3.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,40 @@ function openModal(text, confirmText, cancelText) {
}

class Element {
constructor() {
constructor(stripeElements) {
// Element needs a reference to the object that created it, in order to
// thoroughly destroy() itself.
this._stripeElements = stripeElements;
this.listeners = {};
this._domChildren = [];
}

mount(domElement) {
if (typeof domElement === 'string') {
domElement = document.querySelector(domElement);
} else if (!(domElement instanceof window.Element)) {
throw new Error('Invalid DOM element. Make sure to call mount() with ' +
'a valid DOM element or selector.');
}

const span = document.createElement('span');
span.textContent = 'localstripe: ';
domElement.appendChild(span);
if (this._stripeElements._cardElement !== this) {
throw new Error('This Element has already been destroyed. Please ' +
'create a new one.');
}

if (this._domChildren.length) {
if (domElement === this._domChildren[0].parentElement) {
return;
}
throw new Error('This Element is already mounted. Use `unmount()` to ' +
'unmount the Element before re-mounting.');
}

const inputs = {
const labelSpan = document.createElement('span');
labelSpan.textContent = 'localstripe: ';
this._domChildren.push(labelSpan);

this._inputs = {
number: null,
exp_month: null,
exp_year: null,
Expand All @@ -104,41 +124,57 @@ class Element {
const changed = event => {
this.value = {
card: {
number: inputs.number.value,
exp_month: inputs.exp_month.value,
exp_year: '20' + inputs.exp_year.value,
cvc: inputs.cvc.value,
number: this._inputs.number.value,
exp_month: this._inputs.exp_month.value,
exp_year: '20' + this._inputs.exp_year.value,
cvc: this._inputs.cvc.value,
},
postal_code: inputs.postal_code.value,
postal_code: this._inputs.postal_code.value,
}

if (event.target === inputs.number &&
if (event.target === this._inputs.number &&
this.value.card.number.length >= 16) {
inputs.exp_month.focus();
} else if (event.target === inputs.exp_month &&
this._inputs.exp_month.focus();
} else if (event.target === this._inputs.exp_month &&
parseInt(this.value.card.exp_month) > 1) {
inputs.exp_year.focus();
} else if (event.target === inputs.exp_year &&
this._inputs.exp_year.focus();
} else if (event.target === this._inputs.exp_year &&
this.value.card.exp_year.length >= 4) {
inputs.cvc.focus();
} else if (event.target === inputs.cvc &&
this._inputs.cvc.focus();
} else if (event.target === this._inputs.cvc &&
this.value.card.cvc.length >= 3) {
inputs.postal_code.focus();
this._inputs.postal_code.focus();
}

(this.listeners['change'] || []).forEach(handler => handler());
};

Object.keys(inputs).forEach(field => {
inputs[field] = document.createElement('input');
inputs[field].setAttribute('type', 'text');
inputs[field].setAttribute('placeholder', field);
inputs[field].setAttribute('size', field === 'number' ? 16 :
field === 'postal_code' ? 5 :
field === 'cvc' ? 3 : 2);
inputs[field].oninput = changed;
domElement.appendChild(inputs[field]);
Object.keys(this._inputs).forEach(field => {
this._inputs[field] = document.createElement('input');
this._inputs[field].setAttribute('type', 'text');
this._inputs[field].setAttribute('placeholder', field);
this._inputs[field].setAttribute('size', field === 'number' ? 16 :
field === 'postal_code' ? 5 :
field === 'cvc' ? 3 : 2);
this._inputs[field].oninput = changed;
this._domChildren.push(this._inputs[field]);
});

this._domChildren.forEach((child) => domElement.appendChild(child));
}

unmount() {
while (this._domChildren.length) {
this._domChildren.pop().remove();
}
this._inputs = undefined;
}

destroy() {
this.unmount();
if (this._stripeElements._cardElement === this) {
this._stripeElements._cardElement = null;
}
}

on(event, handler) {
Expand All @@ -151,15 +187,16 @@ Stripe = (apiKey) => {
return {
elements: () => {
return {
_cardElement: null,
create: function(type, options) {
if (this._cardElement) {
throw new Error("Can only create one Element of type card");
}
this._cardElement = new Element();
this._cardElement = new Element(this);
return this._cardElement;
},
getElement: function(type) {
return this._cardElement || null;
return this._cardElement;
}
};
},
Expand Down Expand Up @@ -364,7 +401,7 @@ Stripe = (apiKey) => {
}
},

createPaymentMethod: async () => ({}),
createPaymentMethod: async () => {},
};
};

Expand Down

0 comments on commit 2709044

Please sign in to comment.