Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to pass in a DOM Node to Drop-in creation #165

Merged
merged 9 commits into from
May 22, 2017
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ unreleased
----------
- Add built css to npm build
- Fix typo in Dutch translations
- Add ability to pass in a DOM Node to Drop-in as an alternative to a CSS selector

1.0.2
-----
Expand Down
6 changes: 3 additions & 3 deletions spec/braintree_web_drop_in_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,10 @@
end

describe "setup" do
it "requires a selector" do
visit "http://#{HOSTNAME}:#{PORT}?selector=null"
it "requires a selector or container" do
visit "http://#{HOSTNAME}:#{PORT}?container=null&selector=null"

expect(find("#error")).to have_content("options.selector is required.")
expect(find("#error")).to have_content("options.container is required.")
end

it "requires authorization" do
Expand Down
27 changes: 18 additions & 9 deletions src/dropin.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ var VERSION = process.env.npm_package_version;
*
* braintree.dropin.create({
* authorization: 'CLIENT_AUTHORIZATION',
* selector: '#dropin-container'
* container: '#dropin-container'
* }, function (err, dropinInstance) {
* submitButton.addEventListener('click', function () {
* dropinInstance.requestPaymentMethod(function (err, payload) {
Expand Down Expand Up @@ -117,26 +117,35 @@ Dropin.prototype = Object.create(EventEmitter.prototype, {
});

Dropin.prototype._initialize = function (callback) {
var container, localizedStrings, localizedHTML, strings;
var localizedStrings, localizedHTML, strings;
var dropinInstance = this; // eslint-disable-line consistent-this
var container = this._merchantConfiguration.container || this._merchantConfiguration.selector;

this._injectStylesheet();

if (!this._merchantConfiguration.selector) {
if (!container) {
analytics.sendEvent(this._client, 'configuration-error');
callback(new DropinError('options.container is required.'));
return;
} else if (this._merchantConfiguration.container && this._merchantConfiguration.selector) {
analytics.sendEvent(this._client, 'configuration-error');
callback(new DropinError('options.selector is required.'));
callback(new DropinError('Must only have one options.selector or options.container.'));
return;
}

container = document.querySelector(this._merchantConfiguration.selector);
if (typeof container === 'string') {
container = document.querySelector(container);
}

if (!container) {
if (!container || container.nodeType !== 1) {
analytics.sendEvent(this._client, 'configuration-error');
callback(new DropinError('options.selector must reference a valid DOM node.'));
callback(new DropinError('options.selector or options.container must reference a valid DOM node.'));
return;
} else if (container.innerHTML.trim()) {
}

if (container.innerHTML.trim()) {
analytics.sendEvent(this._client, 'configuration-error');
callback(new DropinError('options.selector must reference an empty DOM node.'));
callback(new DropinError('options.selector or options.container must reference an empty DOM node.'));
return;
}

Expand Down
9 changes: 5 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ var VERSION = process.env.npm_package_version;
* @description This function is the entry point for `braintree.dropin`. It is used for creating {@link Dropin} instances.
* @param {object} options Object containing all {@link Dropin} options:
* @param {string} options.authorization A [tokenization key](https://developers.braintreepayments.com/guides/authorization/tokenization-key/javascript/v3) or a [client token](https://developers.braintreepayments.com/guides/authorization/client-token). If authorization is a client token created with a [customer ID](https://developers.braintreepayments.com/guides/drop-in/javascript/v3#customer-id), Drop-in will render saved payment methods and automatically store any newly-added payment methods in their Vault record.
* @param {string} options.selector A selector for an empty element, such as a `<div>`, where Drop-in will be included on your page. E.g. `#dropin-container`.
* @param {string|HTMLElement} options.container A reference to an empty element, such as a `<div>`, where Drop-in will be included on your page or the selector for the empty element. e.g. `#dropin-container`.
* @param {string} options.selector Deprecated: Now an alias for `options.container`.
* @param {string} [options.locale=`en_US`] Use this option to change the language, links, and terminology used throughout Drop-in. Supported locales include:
* `da_DK`,
* `de_DE`,
Expand Down Expand Up @@ -79,7 +80,7 @@ var VERSION = process.env.npm_package_version;
*
* braintree.dropin.create({
* authorization: 'CLIENT_AUTHORIZATION',
* selector: '#dropin-container'
* container: '#dropin-container'
* }, function (err, dropinInstance) {
* if (err) {
* // Handle any errors that might've occurred when creating Drop-in
Expand All @@ -104,7 +105,7 @@ var VERSION = process.env.npm_package_version;
* <caption>Setting up a Drop-in instance to accept credit cards, PayPal, and PayPal Credit</caption>
* braintree.dropin.create({
* authorization: 'CLIENT_AUTHORIZATION',
* selector: '#dropin-container',
* container: '#dropin-container',
* paypal: {
* flow: 'checkout',
* amount: 10.00,
Expand Down Expand Up @@ -143,7 +144,7 @@ var VERSION = process.env.npm_package_version;
*
* braintree.dropin.create({
* authorization: 'CLIENT_AUTHORIZATION',
* selector: '#dropin-container'
* container: '#dropin-container'
* }, function (err, dropinInstance) {
* if (err) {
* // Handle any errors that might've occurred when creating Drop-in
Expand Down
2 changes: 1 addition & 1 deletion test/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ <h1>Braintree Drop-in demo</h1>
// If you are copying this demo app for your integration,
// use your own client token or tokenization key
authorization: 'sandbox_f252zhq7_hh4cpc39zq4rgjcg',
selector: '#dropin-container',
container: '#dropin-container',
paypal: {
flow: 'checkout',
amount: '10.00', // be sure to validate this amount on your server
Expand Down
86 changes: 77 additions & 9 deletions test/unit/dropin.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('Dropin', function () {
this.dropinOptions = {
client: this.client,
merchantConfiguration: {
selector: '#foo',
container: '#foo',
authorization: fake.tokenizationKey
}
};
Expand Down Expand Up @@ -72,17 +72,33 @@ describe('Dropin', function () {
}.bind(this));
});

it('errors out if no selector given', function (done) {
it('errors out if no selector or container are given', function (done) {
var instance;

delete this.dropinOptions.merchantConfiguration.selector;
delete this.dropinOptions.merchantConfiguration.container;

this.sandbox.stub(analytics, 'sendEvent');

instance = new Dropin(this.dropinOptions);

instance._initialize(function (err) {
expect(err.message).to.equal('options.selector is required.');
expect(err.message).to.equal('options.container is required.');
expect(analytics.sendEvent).to.be.calledWith(instance._client, 'configuration-error');
done();
});
});

it('errors out if both a selector and container are given', function (done) {
var instance;

this.dropinOptions.merchantConfiguration.selector = {value: '#bar'};

this.sandbox.stub(analytics, 'sendEvent');

instance = new Dropin(this.dropinOptions);

instance._initialize(function (err) {
expect(err.message).to.equal('Must only have one options.selector or options.container.');
expect(analytics.sendEvent).to.be.calledWith(instance._client, 'configuration-error');
done();
});
Expand Down Expand Up @@ -143,24 +159,24 @@ describe('Dropin', function () {
}.bind(this));
});

it('throws an error with a selector that points to a nonexistent DOM node', function (done) {
it('throws an error with a container that points to a nonexistent DOM node', function (done) {
var instance;

this.dropinOptions.merchantConfiguration.selector = '#garbage';
this.dropinOptions.merchantConfiguration.container = '#garbage';

this.sandbox.stub(analytics, 'sendEvent');

instance = new Dropin(this.dropinOptions);

instance._initialize(function (err) {
expect(err).to.be.an.instanceOf(Error);
expect(err.message).to.equal('options.selector must reference a valid DOM node.');
expect(err.message).to.equal('options.selector or options.container must reference a valid DOM node.');
expect(analytics.sendEvent).to.be.calledWith(instance._client, 'configuration-error');
done();
});
});

it('throws an error if merchant container is not empty', function (done) {
it('throws an error if merchant container from options.container is not empty', function (done) {
var instance;
var div = document.createElement('div');

Expand All @@ -172,7 +188,45 @@ describe('Dropin', function () {

instance._initialize(function (err) {
expect(err).to.be.an.instanceOf(Error);
expect(err.message).to.equal('options.selector must reference an empty DOM node.');
expect(err.message).to.equal('options.selector or options.container must reference an empty DOM node.');
expect(analytics.sendEvent).to.be.calledWith(instance._client, 'configuration-error');
done();
});
});

it('throws an error if merchant container from options.container is not empty', function (done) {
var instance;
var div = document.createElement('div');

this.container.appendChild(div);

this.dropinOptions.merchantConfiguration.container = this.container;

this.sandbox.stub(analytics, 'sendEvent');

instance = new Dropin(this.dropinOptions);

instance._initialize(function (err) {
expect(err).to.be.an.instanceOf(Error);
expect(err.message).to.equal('options.selector or options.container must reference an empty DOM node.');
expect(analytics.sendEvent).to.be.calledWith(instance._client, 'configuration-error');
done();
});
});

it('throws an error if merchant container from options.container is not a domNode-like object', function (done) {
var instance;
var fakeDiv = {appendChild: 'fake'};

this.dropinOptions.merchantConfiguration.container = fakeDiv;

this.sandbox.stub(analytics, 'sendEvent');

instance = new Dropin(this.dropinOptions);

instance._initialize(function (err) {
expect(err).to.be.an.instanceOf(Error);
expect(err.message).to.equal('options.selector or options.container must reference a valid DOM node.');
expect(analytics.sendEvent).to.be.calledWith(instance._client, 'configuration-error');
done();
});
Expand All @@ -192,6 +246,20 @@ describe('Dropin', function () {
}.bind(this));
});

it('accepts a selector', function (done) {
var instance;

delete this.dropinOptions.merchantConfiguration.container;
this.dropinOptions.merchantConfiguration.selector = '#foo';

instance = new Dropin(this.dropinOptions);

instance._initialize(function (err) {
expect(err).to.not.exist;
done();
});
});

it('inserts dropin into container', function (done) {
var instance = new Dropin(this.dropinOptions);

Expand Down