@ryannerd/ember-toggle-button
Version:
Ember CLI ember-toggle-button component add-on.
238 lines (198 loc) • 7.11 kB
JavaScript
import Ember from 'ember';
import layout from '../templates/components/ember-toggle-button';
const POLL_TIMER = 150;
export default Ember.Component.extend(
{
// Needed since this component is an Ember add-on.
layout: layout,
// Used to generate a random string to be used as a unique Id.
randomId: Ember.computed(function()
{
return Math.random().toString(36).substring(7);
}),
// The interval timer id (note this is a global property and is never updated via this.set() )
pollTimer: null,
// Establish properties and set up hooks to the target element.
init()
{
this._super(...arguments);
// This sets the outer <span id={{targetId}}>around the target</span>
let targetId = this.get('randomId');
this.set('targetId', targetId);
// Set the toggle button id
this.set('buttonId', 'toggle-button__' + targetId);
// TODO: add toggleState - true=open, false=closed
// this.set('toggleState', true);
this.set('useDefaultToggleAction', this.getWithDefault('useDefaultToggleAction', true));
this.set('toggleWidth', this.getWithDefault('toggleWidth', '5%'));
// Set the left and top offset to 0 as default unless overridden.
this.set('topOffset', this.getWithDefault('topOffset', 0));
this.set('leftOffset', this.getWithDefault('leftOffset', 0));
// TODO: Add open-offset and closed-offset
},
didInsertElement()
{
// Figure out the element that is wrapped in the toggle-button block.
let targetId = this.get('targetId');
// Get the first element in the yield block.
let target = Ember.$('span#' + targetId + ' >');
// Sanity check
if (target.length === 1)
{
// Add toggle-target_xxx class as the target element identifier.
target.addClass('toggle-target_' + targetId);
// Set this class so that when elements are polled we can scoop up ALL elements.
target.addClass('toggle-button__targetElement');
// Get the current position (left and top) and width of the target element
let position = target.position();
// Sanity check
if (position)
{
// Jquery magic
let left = position.left;
let top = position.top;
let width = target.outerWidth();
let height = target.outerHeight();
// Save the original position and size of the target element as data-* attributes.
// This is done to work around the fact that didInsertElement does not allow property changes.
// And also calling this.get('topOffset') for example is an expensive call and was causing visible jitter.
target.attr('data-original-top', top);
target.attr('data-original-left', left);
target.attr('data-original-width', width);
target.attr('data-original-height', height);
target.attr('data-toggle-button-top-offset', this.get('topOffset'));
target.attr('data-toggle-button-left-offset', this.get('leftOffset'));
/* TODO: Add open-offset and closed-offset
target.attr('data-toggle-button-top-open-offset', this.get('topOffset'));
target.attr('data-toggle-button-top-closed-offset', this.get('topOffset'));
target.attr('data-toggle-button-left-open-offset', this.get('leftOffset'));
target.attr('data-toggle-button-left-closed-offset', this.get('leftOffset'));
*/
// Save the toggle button id (including the hash) as a data-* attribute.
target.attr('data-toggle-button__id', '#' + this.get('buttonId'));
// If we don't have an element poll timer then we will create one.
// Notice we are NOT using this.get().
if (this.pollTimer === null)
{
// Do not use this.set('pollTimer') to get around deprecation notices,
// and so that pollTimer can be used globally (in case this component is used more than once in a template).
this.pollTimer =setInterval(() =>
{
this.pollElements();
}, POLL_TIMER);
}
}
}
},
/**
* Use Jquery to select every element we are interested in and updates the position of the toggle button accordingly.
* Fires every POLL_TIMER (see didInsertElement() ).
*
*/
pollElements()
{
// Grab ALL the elements we are interested in.
Ember.$('.toggle-button__targetElement').each(function(i, element)
{
// Process each element as a Jquery object
let target = Ember.$(element);
// Get the current position (left and top) and width of the target element
let position = target.position();
// Sanity check
if (position)
{
// Jquery magic
let left = position.left;
let top = position.top;
let width = target.outerWidth();
// let height = target.outerHeight();
// Grab the button id from data-toggle-button-id attribute (this is assigned in didInsertElement).
let buttonId = target.attr('data-toggle-button__id');
// Sanity check
if (buttonId)
{
// Get the actual toggle button element via Jquery.
let button = Ember.$(buttonId);
// Sanity check
if (button)
{
// Add the offsets to the top and left and calculate the position of the toggle button.
let topOffset = target.attr('data-toggle-button-top-offset');
let newTop = top + parseInt(topOffset);
let leftOffset = target.attr('data-toggle-button-left-offset');
let newLeft = (left + width + parseInt(leftOffset));
// TODO: calculate open-offset and closed-offset
// Use Jquery again this time to set the position of the toggle button.
button.css('top', newTop.toString() + 'px');
button.css('left', newLeft.toString() + 'px');
}
}
}
});
},
// Unhook our pollTimer when the toggle-button component is removed.
didDestroyElement()
{
this._super(...arguments);
clearInterval(this.pollTimer);
},
/**
* Fires when ANYTHING is clicked on the page.
*
* @param e - event
*/
click: function(e)
{
// Sanity check
if (e)
{
// Check if what was clicked is the button and not anything else.
let element= Ember.$(e.target);
// Sanity check
if (element)
{
// We are only interested if the "actual" button was clicked on.
if (element.attr('id') === this.get('buttonId'))
{
let isOpen;
if (element.hasClass('toggle-right'))
{
element.removeClass('toggle-right');
element.addClass('toggle-left');
isOpen = false;
}
else
{
element.removeClass('toggle-left');
element.addClass('toggle-right');
isOpen = true;
}
// Let any bound controller know that the state changed and what the target is.
let target = Ember.$('.toggle-target_' + this.get('targetId'));
if (target)
{
if (this.get('useDefaultToggleAction'))
{
this.defaultToggleAction(isOpen, target);
}
this.sendAction('toggleButtonClicked', isOpen, target);
}
}
}
}
},
defaultToggleAction(isOpen, target)
{
if (isOpen)
{
let originalWidth = target.attr('data-original-width');
originalWidth = parseInt(originalWidth) + 'px';
target.animate({width: originalWidth}, 150);
}
else
{
target.attr('data-original-width', target.outerWidth());
target.animate({width: this.get('toggleWidth')}, 150);
}
}
});