2 var $ = require('jquery');
3 var _ = require('lodash');
4 var Marionette = require('backbone.marionette');
5 var cocktail = require('backbone.cocktail');
6 var Mutant = require('mutantjs');
7 var SelectableMixin = require('./selectable-mixin');
8 var itemTemplate = require('./tmpl/dropdownItem.hbs');
9 var dataset = require('../../utils/dataset-shim');
11 module.exports = (function() {
12 /* Transition period on css */
15 function findMaxZIndex(element) {
17 while (element && element != document) {
18 var style = window.getComputedStyle(element, null);
21 var zIndex = style.getPropertyValue('z-index');
22 if (zIndex && zIndex !== 'auto') {
23 zIndex = parseInt(zIndex, 10);
30 element = element.parentNode;
36 var backdropClass = 'dropdown-backdrop';
37 var backdropSelector = '.' + backdropClass;
39 var DropdownItemView = Marionette.ItemView.extend({
41 template: itemTemplate,
42 initialize: function(options) {
43 if (options && options.template) {
44 this.template = options.template;
46 if (options && options.serializeData) {
47 this.serializeData = options.serializeData;
50 className: function() {
51 if (this.model.get('divider')) {
57 onRender: function() {
58 /** Add anything in the dataset attribute to the Anchor tag's dataset */
59 var ds = this.model.get('dataset');
61 var a = this.el.querySelector('a');
64 Object.keys(ds).forEach(function(key) {
65 dataset.set(a, key, ds[key]);
70 dataset.set(this.el, 'cid', this.model.cid);
74 var activeDropdown = null;
80 var DropdownMenuView = Marionette.CollectionView.extend({
81 childView: DropdownItemView,
83 className: 'dropdown dropdown-hidden selectable',
89 'click li a': 'clicked'
92 childViewOptions: function() {
94 if (this.options.itemTemplate) {
95 options.template = this.options.itemTemplate;
97 if (this.options.itemSerializeData) {
98 options.serializeData = this.options.itemSerializeData;
103 initialize: function(options) {
104 if (options.targetElement) {
105 this.setTargetElement(options.targetElement);
108 this.options = _.extend({}, DEFAULTS, options);
110 this.dropdownClass = options.dropdownClass;
112 /* From the selectable-mixin */
113 this.listenTo(this, 'selectClicked', function() {
118 setTargetElement: function(el) {
119 this.targetElement = el;
120 this.$targetElement = $(el);
124 return !this.$el.hasClass('dropdown-hidden');
127 onRender: function() {
128 var zIndex = findMaxZIndex(this.targetElement) + 5;
132 this.el.style.zIndex = zIndex;
133 if (this.dropdownClass) {
134 this.el.classList.add(this.dropdownClass);
138 onDestroy: function() {
139 if (this.mutant) this.mutant.disconnect();
140 $(backdropSelector).off(this.backdropClickedCallback);
143 clicked: function() {
144 if (!this.collection) {
149 backdropClickedCallback: function() {
150 $(backdropSelector).remove();
151 if (activeDropdown) {
152 var t = activeDropdown;
153 activeDropdown = null;
158 getPosition: function() {
159 var el = this.targetElement;
162 _.forIn(el.getBoundingClientRect(), (v, k) => (pos[k] = v));
163 _.forIn(this.$targetElement.offset(), (v, k) => (pos[k] = v));
168 hasItems: function() {
169 if (this.collection) return this.collection.length > 0;
175 onAddChild: function() {
178 if (!this.active() && this.showWhenItems && this.hasItems()) {
186 onRemoveChild: function() {
189 if (!this.hasItems()) {
191 this.showWhenItems = true;
199 if (this.active()) return;
201 if (!this.hasItems()) {
202 this.showWhenItems = true;
206 var $e = this.render().$el;
209 // Stop any impending actions from hide
210 window.clearTimeout(this.hideTimeoutId);
212 $(backdropSelector).remove();
213 if (activeDropdown) {
214 activeDropdown.hide();
217 activeDropdown = this;
219 var zIndex = parseInt(this.el.style.zIndex, 10);
220 // Create the backdrop
222 .querySelector('body')
225 `<div class="${backdropClass} ${this.options.backdropClass}" style="z-index: ${zIndex -
229 // Add the click event listener
230 $(backdropSelector).on('click', this.backdropClickedCallback);
232 this.setActive(this.selectedModel);
234 $e.detach().css({ top: 0, left: 0, display: 'block' });
235 $e.appendTo($('body'));
238 $e.removeClass('dropdown-hidden');
241 this.mutant = new Mutant(e, this.mutationReposition, {
255 this.showWhenItems = false;
256 if (!this.active()) return;
257 // $el.find('li.active:not(.divider):visible').removeClass('active');
258 $el.addClass('dropdown-hidden');
259 $(backdropSelector).remove();
261 this.hideTimeoutId = window.setTimeout(function() {
262 $el.css({ display: 'none' });
264 activeDropdown = null;
267 mutationReposition: function() {
269 if (!this.active()) return;
272 // This is very important. If you leave it out, Chrome will likely crash.
273 if (this.mutant) this.mutant.takeRecords();
277 reposition: function() {
279 var pos = this.getPosition();
281 var actualWidth = e.offsetWidth;
284 if (this.options.placement === 'left') {
287 left = pos.left - actualWidth + pos.width;
290 var tp = { top: pos.top + pos.height, left: left };
291 this.applyPlacement(tp);
294 applyPlacement: function(offset) {
300 if ('left' in offset && offset.left < 0) {
306 if (replace) $e.offset(offset);
310 var isActive = this.active();
318 keydown: function(e) {
334 cocktail.mixin(DropdownMenuView, SelectableMixin);
335 return DropdownMenuView;