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

Change timer for opening modal to transitionEnd event listener #7

Merged
merged 4 commits into from
Jun 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
138 changes: 70 additions & 68 deletions addon/components/modal.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
/* eslint-disable quote-props, no-magic-numbers */
import Ember from 'ember';
import { hasTransitions, onTransitionEnd } from 'ember-modal-service/utils/css-transitions';

const {
Component,
computed,
inject: { service },
observer,
on,
run: { later },
String: { camelize }
run,
String: { camelize },
RSVP
} = Ember;

export const ANIMATION_DELAY = 400;

/**
* Component to wrap modal objects.
*
Expand Down Expand Up @@ -66,7 +65,9 @@ export default Component.extend({
* @property data-id
* @type {String}
*/
'data-id': null,
'data-id': computed('model.fullname', function() {
return camelize(this.get('model.fullname'));
}),

/**
* Modal is visible/hidden. This property is read from CSS.
Expand All @@ -79,67 +80,20 @@ export default Component.extend({
}),

/**
* Animation appearance delay.
*
* @property animationDelay
* @type Number
*/
animationDelay: 25,

/**
* On did insert element, scroll to top and set element as visible.
* On did insert element, set element as visible and set data-id.
*
* @method didInsertElement
* @event onDidInsertElement
*/
didInsertElement() {
this._super();

const animationDelay = this.get('animationDelay');

later(() => {
if (!this.isDestroyed) {
this.set('visible', true);
}
}, animationDelay);

this.set('data-id', camelize(this.get('model.fullname')));
},


/**
* When the promise has been settled, close the view.
*
* @method hasBeenSettled
* @private
*/
_hasBeenSettled: on('init', function() {
// Prevent triggering Ember.onerror on promise resolution.
this.get('model.promise')
.catch(() => {}, 'Modal: empty catch to prevent exception')
.finally(() => this._close(), 'Modal: closing modal');
}),

/**
* Observes the visible property to toggle actions.
*
* @method visibleDidChange
*/
visibleDidChange: observer('visible', function() {
const visible = this.get('visible');

if (visible) {
this.didOpen();
} else {
this.willClose();
}
onDidInsertElement: on('didInsertElement', function() {
run.scheduleOnce('afterRender', this._open.bind(this));
}),

/**
* Resolve current promise and close modal.
*
* @method resolve
*/
resolve(data, label = `Modal: resolved '${this.get('model.fullname')}'`) {
resolve(data, label = `Component '${this.get('model.fullname')}': fulfillment`) {
this.get('model.deferred').resolve(data, label);
},

Expand All @@ -148,25 +102,37 @@ export default Component.extend({
*
* @method reject
*/
reject(data, label = `Modal: rejected '${this.get('model.fullname')}'`) {
reject(data, label = `Component '${this.get('model.fullname')}': rejection`) {
this.get('model.deferred').reject(data, label);
},

/**
* Open action.
* Action to know when modal is fully opened.
*
* @method open
* @return Boolean
* @method didOpen
*/
didOpen() {},

/**
* Close action.
* Turn on visibility and send didOpen event.
*
* @method close
* @return Boolean
* @method _open
* @private
*/
willClose() {},
_open() {
if (this.isDestroyed) {
return;
}
const element = this.$().get(0);

this.set('visible', true);

if (hasTransitions(element)) {
onTransitionEnd(element, this.didOpen.bind(this), 'all', true);
} else {
this.didOpen();
}
},

/**
* Set modal as not visible and remove modal from array later.
Expand All @@ -175,11 +141,21 @@ export default Component.extend({
* @private
*/
_close() {
if (this.isDestroyed) {
return;
}

const element = this.$().get(0);

// Close modal.
this.set('visible', false);

// Remove modal from array.
later(this, this._remove, ANIMATION_DELAY);
// Remove modal from array when transition ends.
if (hasTransitions(element)) {
onTransitionEnd(element, this._remove.bind(this), 'all', true);
} else {
this._remove();
}
},

/**
Expand All @@ -189,9 +165,34 @@ export default Component.extend({
* @private
*/
_remove() {
if (this.isDestroyed) {
return;
}

this.get('modal.content').removeObject(this.get('model'));
},

/**
* When the promise has been settled, close the view.
*
* @method hasBeenSettled
* @private
*/
_hasBeenSettled: on('init', function() {
// Prevent triggering Ember.onerror on promise resolution.
this.get('model.promise').catch((e) => {
if (e instanceof Error) {
return RSVP.reject(e, `Component '${this.get('model.fullname')}': bubble error`);
}

// Ignore rejections due to not being real errors here.
return e;
}, `Component '${this.get('model.fullname')}': catch real errors or ignore`).finally(
this._close.bind(this),
`Component '${this.get('model.fullname')}': close modal`
);
}),

actions: {

/**
Expand All @@ -213,5 +214,6 @@ export default Component.extend({
reject() {
this.reject(...arguments);
}

}
});
12 changes: 12 additions & 0 deletions addon/utils/css-transitions/has-transitions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Returns whether an element has CSS transitions applied directly on itself.
*
* @method hasTransitions
* @param {Element} element
* @return Boolean
*/
export default function hasTransitions(element) {
const { transitionProperty, transitionDuration } = window.getComputedStyle(element);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asegura que getComputedStyle funciona en safari

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to CanIUse. It seems to work on modern browsers.


return !(transitionProperty === 'all' && transitionDuration === '0s');
}
7 changes: 7 additions & 0 deletions addon/utils/css-transitions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import onTransitionEnd from './on-transition-end';
import hasTransitions from './has-transitions';

export {
onTransitionEnd,
hasTransitions
};
53 changes: 53 additions & 0 deletions addon/utils/css-transitions/on-transition-end.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Ember from 'ember';

const { run } = Ember;
const eventNames = {
transition: 'transitionend',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd',
WebkitTransition: 'webkitTransitionEnd',
msTransition: 'MSTransitionEnd'
};

/**
* Find transition-end event name on current browser.
*
* @method findTransitionEventName
* @return Boolean
* @private
*/
function findTransitionEventName() {
const div = document.createElement('div');
const key = Object.keys(eventNames).find((eventName) => eventName in div.style);

return eventNames[key];
}

const transitionEndEventName = findTransitionEventName();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

div por parámetro

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


/**
* Subscribes a callback to a transition-end event by transition property on a given element.
*
* @method onTransitionEnd
* @param {Element} element
* @param {Function} callback
* @param {String} transitionProperty
* @param {Boolean} once
*/
export default function onTransitionEnd(element, callback, transitionProperty = 'all', once = false) {
const fn = (e) => {
const { propertyName, type } = e;

if (transitionProperty !== 'all' && propertyName !== transitionProperty) {
return;
}

if (once) {
element.removeEventListener(type, fn, true);
}

run(null, callback, e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #9

};

element.addEventListener(transitionEndEventName, fn, true);
}
1 change: 1 addition & 0 deletions app/utils/css-transitions/has-transitions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-modal-service/utils/css-transitions/has-transitions';
1 change: 1 addition & 0 deletions app/utils/css-transitions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-modal-service/utils/css-transitions';
1 change: 1 addition & 0 deletions app/utils/css-transitions/on-transition-end.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-modal-service/utils/css-transitions/on-transition-end';
5 changes: 4 additions & 1 deletion ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');

module.exports = function(defaults) {
var app = new EmberAddon(defaults, {
// Add options here
babel: {
includePolyfill: true,
compileModules: true
}
});

/*
Expand Down