UNPKG

@busy-web/components

Version:

Ember addon library for usefull components.

217 lines (190 loc) 5.61 kB
/** * @module Components * */ import { assert } from '@ember/debug'; import { loc } from '@ember/string'; import { isNone, isEmpty } from '@ember/utils'; import { computed, get, set } from '@ember/object'; import Component from '@ember/component'; import CloseOnEscape from '../mixins/close-on-escape'; import BindOutsideClick from '../mixins/bind-outside-click'; import layout from '../templates/components/bc-select'; /** * `Component/BCSelect` * Component select menu for displaying a list of items to a user. * * @class BCSelect * @namespace Components * * @property model {array} Array of key-value pair objects * @property itemLabel {string} The key of the key-value pair to display in the option list. * @property defaultLabel {string} The start label to display in the select menu. `Default: Select` * @property defaultFirstOption {boolean} True if the first option in the model array should be displayed. `Default: false` * @property menuTitle {string} This should be set if the select menu doesn't change titles when selected. This option overrides defaultLabel and defaultFirstOption. * @property onSelect {string} The function name to call when an item is selected. onSelect will pass the selected item to the listener. * @property targetObject {object} The View where the onSelect function can be called. `Default: controller` */ export default Component.extend(CloseOnEscape, BindOutsideClick, { layout, classNames: ['bc-select'], classNameBindings: ['isOpen:active', 'isTop:top', 'small'], __closeActionName: 'closeMenu', /** * isOpen tracks the menu open state * * @private * @property isOpen * @type boolean */ isOpen: false, isTop: false, small: false, openTop: false, highlightSlected: true, model: null, /** * internal use to track the current selected item from the list * * @private * @property selectedItem * @type object */ selectedItem: computed('model.@each._selected', 'model.[]', function() { let selected = null; if (!isNone(get(this, 'model'))) { selected = this.getSelected(); } return selected; }), getSelected() { const items = get(this, 'model'); let selected = null; if (typeof items === 'object' && typeof items.forEach === 'function') { items.forEach(item => { if (get(item, '_selected')) { selected = item; } }); } else { Object.keys(items).forEach(key => { if (get(items[key], '_selected')) { selected = items[key]; } }); } return selected; }, itemLabel: '', defaultLabel: loc('Select'), defaultFirstOption: false, menuTitle: computed('selectedItem', function() { let label = get(this, 'defaultLabel'); let selectedItem = get(this, 'selectedItem'); assert('"itemLabel" must be set to a property of the model', !isEmpty(get(this, 'itemLabel'))); if (!isNone(selectedItem)) { label = get(selectedItem, get(this, 'itemLabel')); } else if (get(this, 'defaultFirstOption')) { selectedItem = get(this, 'model').objectAt(0); label = get(selectedItem, get(this, 'itemLabel')); } return label; }), checkPosition() { const elem = this.$().offsetParent(); let menuHeightTop = elem.height() - (elem.height() - (this.$().offset().top - elem.offset().top)) - 20; // height of the container minus the the bottom space from the top of the button. let menuHeightBot = elem.height() - ((this.$().offset().top + this.$().height()) - elem.offset().top) - 20; // height of the container minus the bottom of the select button. const maxHeight = parseInt(window.getComputedStyle(this.$('.select-container').get(0))['max-height'], 10); if (menuHeightTop > 200) { if (menuHeightBot < maxHeight) { if (menuHeightTop > menuHeightBot) { if (menuHeightTop < maxHeight) { menuHeightTop = menuHeightTop > 150 ? menuHeightTop : 150; this.$('.select-container').css('max-height', menuHeightTop); } return true; } else { menuHeightBot = menuHeightBot > 150 ? menuHeightBot : 150; this.$('.select-container').css('max-height', menuHeightBot); } } } return false; }, unselectAll() { const items = get(this, 'model'); if (typeof items === 'object' && typeof items.forEach === 'function') { items.forEach(item => { set(item, '_selected', false); }); } else { Object.keys(items).forEach(key => { set(items[key], '_selected', false); }); } }, /** * handles an item click action * and sends the action to any listeners * * @private * @method itemClicked * @param item {object} The list item that was clicked * @returns {void} */ itemClicked(item) { this.unselectAll(); set(item, '_selected', true); this.close(); this.sendAction('onSelect', item); }, onOutsideClick() { this.close(); }, onEscape() { this.close(); return false; }, open() { if (!get(this, 'isDestroyed')) { this.bindEscape(); this.bindClick(); set(this, 'isOpen', true); if (get(this, 'openTop')) { set(this, 'isTop', true); } else { if (this.checkPosition()) { set(this, 'isTop', true); } else { set(this, 'isTop', false); } } } }, close() { if (!get(this, 'isDestroyed')) { this.unbindEscape(); this.unbindClick(); set(this, 'isOpen', false); } }, actions: { openMenu() { if (!get(this, 'isOpen')) { this.open(); } else { this.close(); } }, closeMenu() { this.close(); }, clickItemAction(item) { if (!get(item, '_unselectable')) { this.itemClicked(item); } return false; }, stopPropagation() { return false; } } });