Skip to content

Commit

Permalink
React.js support and improvements
Browse files Browse the repository at this point in the history
- moved the DOM manipulating logic inside the `applyStyle` modifier
- the callback is now called using `.onCreate` method
- added `.onUpdate` callback
- improved README examples
  • Loading branch information
FezVrasta committed Apr 7, 2016
1 parent 9d13d4c commit 5521b0d
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 54 deletions.
36 changes: 30 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
Popper.js is a library used to create **poppers** in web applications.

## Wut? Poppers?
A popper is an element on the screen which "pops out" from the natural flow of your application.
A popper is an element on the screen which "pops out" from the natural flow of your application.
Common examples of poppers are tooltips and popovers.

## So, yet another tooltip library?
Well, basically, **no**.
Popper.js is built from the ground up to being modular and fully ~~hackable~~ **customizable**.
It supports a **plugin system** you can use to add particular behaviors to your poppers.
Well, basically, **no**.
Popper.js is built from the ground up to being modular and fully ~~hackable~~ **customizable**.
It supports a **plugin system** you can use to add particular behaviors to your poppers.
It's **AMD** and **CommonJS** compatible and it's well documented thanks to our [JSDoc page](https://fezvrasta.github.io/popper.js/documentation.html).


## The Library
Popper.js is mostly a library with the job of making sure your popper stays near the defined reference element (if you want so).
Popper.js is mostly a library with the job of making sure your popper stays near the defined reference element (if you want so).
Additionally, it provides an easy way to generate your popper element if you don't want to use one already in your DOM.

### Installation
Expand Down Expand Up @@ -71,9 +71,33 @@ var anotherPopper = new Popper(
);
```

### Callbacks
```js
var reference = document.querySelector('.my-button');
var popper = document.querySelector('.my-popper');
var anotherPopper = new Popper(reference, popper).onCreate(instance) {
// instance is Popper.js instance
}).onUpdate(function(data) {
// data is an object containing all the informations computed by Popper.js and used to style the popper and its arrow
});
```

### React.js and Ember.js integration
If you prefer to let your framework apply the styles to your DOM objects, you can follow an approach like the one below:
```js
var reference = document.querySelector('.my-button');
var popper = document.querySelector('.my-popper');
var anotherPopper = new Popper(reference, popper, {
modifiersIgnored: ['applyStyle'] // prevent Popper.js from applying styles to your DOM
}).onUpdate(function(data) {
// export data in your framework and use its content to apply the style to your popper
});
```


If you are wondering about the available options of the third argument, check out [our documentation](http://fezvrasta.github.io/popper.js/documentation.html#new_Popper_new)

Visit our [GitHub Page](https://fezvrasta.github.io/popper.js) to see a lot of examples of what you can already do right now!
Visit our [GitHub Page](https://fezvrasta.github.io/popper.js) to see a lot of examples of what you can do right now!


## Notes
Expand Down
116 changes: 75 additions & 41 deletions src/popper.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
arrowElement: '[x-arrow]',

// list of functions used to modify the offsets before they are applied to the popper
modifiers: [ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip'],
modifiers: [ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip', 'applyStyle'],

modifiersIgnored: [],
};
Expand Down Expand Up @@ -124,29 +124,24 @@
* how alter the placement when a flip is needed. (eg. in the above example, it would first flip from right to left,
* then, if even in its new placement, the popper is overlapping its trigger, it will be moved to top)
*
* @param {Array} [options.modifiers=[ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip']]
* @param {Array} [options.modifiers=[ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip', 'applyStyle']]
* List of functions used to modify the data before they are applied to the popper, add your custom functions
* to this array to edit the offsets and placement.
* The function should reflect the @params and @returns of preventOverflow
*
* @param {Array} [options.modifiersIgnored=[]]
* Put here any built-in modifier name you want to exclude from the modifiers list
* The function should reflect the @params and @returns of preventOverflow
*
* @param {Function}
* If the last argument of Popper.js is a function, it will be executed after the initialization of the popper
* it's scope will be window, the first argument will be the Popper.js instance.
*/
function Popper(trigger, popper, options/*, callback*/) {
function Popper(trigger, popper, options) {
this._trigger = trigger;
this.state = {};

// if the popper variable is a configuration object, parse it to generate an HTMLElement
// generate a default popper if is not defined
var isNotDefined = popper === undefined || popper === null;
var isFunction = typeof popper === 'function';
var isConfig = popper && popper.constructor.name === 'Object';
if ( isNotDefined || isFunction || isConfig) {
if ( isNotDefined || isConfig) {
this._popper = this.parse(isConfig ? popper : {});
}
// otherwise, use the given HTMLElement as popper
Expand Down Expand Up @@ -179,10 +174,6 @@

// setup event listeners, they will take care of update the position in specific situations
this._setupEventListeners();

if (typeof arguments[arguments.length -1] === 'function') {
arguments[arguments.length -1].call(root, this);
}
}


Expand Down Expand Up @@ -224,35 +215,32 @@

data = this.runModifiers(data, this._options.modifiers);

// apply the final offsets to the popper
// NOTE: 1 DOM access here
var styles = {
position: data.offsets.popper.position
};

// round top and left to avoid blurry text
var left = Math.round(data.offsets.popper.left);
var top = Math.round(data.offsets.popper.top);

// if gpuAcceleration is set to true and transform is supported, we use `translate3d` to apply the position to the popper
// we automatically use the supported prefixed version if needed
var prefixedProperty;
if (this._options.gpuAcceleration && (prefixedProperty = getSupportedPropertyName('transform'))) {
styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
styles.top = 0;
styles.left = 0;
}
// othwerise, we use the standard `left` and `top` properties
else {
styles.left =left;
styles.top = top;
if (typeof this.state.updateCallback === 'function') {
this.state.updateCallback(data);
}

setStyle(this._popper, styles);
};

// set an attribute which will be useful to style the tooltip (use it to properly position its arrow)
// NOTE: 1 DOM access here
this._popper.setAttribute('x-placement', data.placement);
/**
* If a function is passed, it will be executed after the initialization of popper with as first argument the Popper instance.
* @method
* @memberof Popper
* @param {Function} callback
*/
Popper.prototype.onCreate = function(callback) {
// the createCallbacks return as first argument the popper instance
callback(this);
};

/**
* If a function is passed, it will be executed after each update of popper with as first argument the set of coordinates and informations
* used to style popper and its arrow.
* @method
* @memberof Popper
* @param {Function} callback
*/
Popper.prototype.onUpdate = function(callback) {
this.state.updateCallback = callback;
};

/**
Expand Down Expand Up @@ -565,6 +553,52 @@
*/
Popper.prototype.modifiers = {};

/**
* Apply the computed styles to the popper element
* @method
* @memberof Popper.modifiers
* @argument {Object} data - The data object generated by `update` method
* @returns {Object} The same data object
*/
Popper.prototype.modifiers.applyStyle = function(data) {
// apply the final offsets to the popper
// NOTE: 1 DOM access here
var styles = {
position: data.offsets.popper.position
};

// round top and left to avoid blurry text
var left = Math.round(data.offsets.popper.left);
var top = Math.round(data.offsets.popper.top);

// if gpuAcceleration is set to true and transform is supported, we use `translate3d` to apply the position to the popper
// we automatically use the supported prefixed version if needed
var prefixedProperty;
if (this._options.gpuAcceleration && (prefixedProperty = getSupportedPropertyName('transform'))) {
styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
styles.top = 0;
styles.left = 0;
}
// othwerise, we use the standard `left` and `top` properties
else {
styles.left =left;
styles.top = top;
}

setStyle(this._popper, styles);

// set an attribute which will be useful to style the tooltip (use it to properly position its arrow)
// NOTE: 1 DOM access here
this._popper.setAttribute('x-placement', data.placement);

// if the arrow modifier is required and the arrow style has been computed, apply the arrow style
if (this.isModifierRequired(this.modifiers.applyStyle, this.modifiers.arrow) && data.offsets.arrow) {
setStyle(data.arrowElement, data.offsets.arrow);
}

return data;
};

/**
* Modifier used to shift the popper on the start or end of its reference element side
* @method
Expand Down Expand Up @@ -889,8 +923,8 @@
arrowStyle.left = left;
arrowStyle.top = ''; // make sure to remove any old style from the arrow
}

setStyle(arrow, arrowStyle);
data.offsets.arrow = arrowStyle;
data.arrowElement = arrow;

return data;
};
Expand Down
25 changes: 18 additions & 7 deletions tests/test-popper.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ describe('Popper.js', function() {
var ref = appendNewRef(1, 'ref', relative);
var popper = appendNewPopper(2, 'popper');

new TestPopper(ref, popper, function(pop) {
new TestPopper(ref, popper).onCreate(function(pop) {
expect(popper.getBoundingClientRect().top).toBeApprox(63);
expect(popper.getBoundingClientRect().left).toBeApprox(5);
pop.destroy();
Expand All @@ -120,7 +120,7 @@ describe('Popper.js', function() {
ref.style.marginTop = '200px';
var popper = appendNewPopper(2, 'popper');

new TestPopper(ref, popper, function(pop) {
new TestPopper(ref, popper).onCreate(function(pop) {
expect(popper.getBoundingClientRect().top).toBeApprox(-800 + 263);
expect(popper.getBoundingClientRect().left).toBeApprox(5);
pop.destroy();
Expand Down Expand Up @@ -152,7 +152,7 @@ describe('Popper.js', function() {
ref.style.marginTop = '200px';
var popper = appendNewPopper(2, 'popper');

new TestPopper(ref, popper, function(pop) {
new TestPopper(ref, popper).onCreate(function(pop) {
// force redraw
window.dispatchEvent(new Event('resize'));

Expand Down Expand Up @@ -180,7 +180,7 @@ describe('Popper.js', function() {
var ref = appendNewRef(1, 'ref', fixed);
var popper = appendNewPopper(2, 'popper', fixed);

new TestPopper(ref, popper, function(pop) {
new TestPopper(ref, popper).onCreate(function(pop) {
// force redraw
window.dispatchEvent(new Event('resize'));

Expand All @@ -195,7 +195,7 @@ describe('Popper.js', function() {
var reference = appendNewRef(1);
var popper = appendNewPopper(2);

new TestPopper(reference, popper, function(pop) {
new TestPopper(reference, popper).onCreate(function(pop) {
pop.destroy();
expect(popper.style.top).toBe('');
done();
Expand All @@ -205,7 +205,7 @@ describe('Popper.js', function() {
it('creates a popper using the default configuration', function(done) {
var reference = appendNewRef(1);

new TestPopper(reference, function(instance) {
new TestPopper(reference).onCreate(function(instance) {
expect(document.querySelectorAll('.popper').length).toBe(1);
document.body.removeChild(instance._popper);
done();
Expand All @@ -217,10 +217,21 @@ describe('Popper.js', function() {

new TestPopper(reference, {
content: 'something'
}, function(instance) {
}).onCreate(function(instance) {
expect(instance._popper.innerText).toBe('something');
document.body.removeChild(instance._popper);
done();
});
});

it('creates a popper and sets an onUpdate callback', function(done) {
var reference = appendNewRef(1);

new TestPopper(reference, {content: 'react'}, {
modifiersIgnored: ['applyStyle']
}).onUpdate(function(data) {
expect(data.offsets.popper.top).toBeApprox(46);
done();
});
});
});

0 comments on commit 5521b0d

Please sign in to comment.