Skip to content

Commit 6809173

Browse files
author
Jake Harding
committed
Refactor views.
1 parent c82e097 commit 6809173

File tree

3 files changed

+382
-518
lines changed

3 files changed

+382
-518
lines changed

src/dropdown_view.js

Lines changed: 100 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -5,121 +5,128 @@
55
*/
66

77
var DropdownView = (function() {
8-
var html = {
9-
suggestionsList: '<span class="tt-suggestions"></span>'
10-
},
11-
css = {
12-
suggestionsList: { display: 'block' },
13-
suggestion: { whiteSpace: 'nowrap', cursor: 'pointer' },
14-
suggestionChild: { whiteSpace: 'normal' }
15-
};
168

179
// constructor
1810
// -----------
1911

2012
function DropdownView(o) {
21-
utils.bindAll(this);
13+
var that = this, onMouseEnter, onMouseLeave, onSuggestionClick,
14+
onSuggestionMouseEnter, onSuggestionMouseLeave;
2215

2316
this.isOpen = false;
24-
this.isEmpty = true;
2517
this.isMouseOverDropdown = false;
2618

19+
// bound functions
20+
onMouseEnter = utils.bind(this._onMouseEnter, this);
21+
onMouseLeave = utils.bind(this._onMouseLeave, this);
22+
onSuggestionClick = utils.bind(this._onSuggestionClick, this);
23+
onSuggestionMouseEnter = utils.bind(this._onSuggestionMouseEnter, this);
24+
onSuggestionMouseLeave = utils.bind(this._onSuggestionMouseLeave, this);
25+
2726
this.$menu = $(o.menu)
28-
.on('mouseenter.tt', this._handleMouseenter)
29-
.on('mouseleave.tt', this._handleMouseleave)
30-
.on('click.tt', '.tt-suggestion', this._handleSelection)
31-
.on('mouseover.tt', '.tt-suggestion', this._handleSuggestionMouseover)
32-
.on('mouseleave.tt', '.tt-suggestion', this._handleSuggestionMouseleave);
27+
.on('mouseenter.tt', onMouseEnter)
28+
.on('mouseleave.tt', onMouseLeave)
29+
.on('click.tt', '.tt-suggestion', onSuggestionClick)
30+
.on('mouseenter.tt', '.tt-suggestion', onSuggestionMouseEnter)
31+
.on('mouseleave.tt', '.tt-suggestion', onSuggestionMouseLeave);
32+
33+
this.sections = utils.map(o.sections, initializeSection);
34+
35+
utils.each(this.sections, function(i, section) {
36+
that.$menu.append(section.getRoot());
37+
section.onSync('rendered', that._onRendered, that);
38+
});
3339
}
3440

35-
utils.mixin(DropdownView.prototype, EventTarget, {
36-
// private methods
37-
// ---------------
41+
// instance methods
42+
// ----------------
43+
44+
utils.mixin(DropdownView.prototype, EventEmitter, {
3845

39-
_handleMouseenter: function() {
46+
// ### private
47+
48+
_onMouseEnter: function onMouseEnter($e) {
4049
this.isMouseOverDropdown = true;
4150
},
4251

43-
_handleMouseleave: function() {
52+
_onMouseLeave: function onMouseLeave($e) {
4453
this.isMouseOverDropdown = false;
4554
},
4655

47-
_handleSelection: function($e) {
48-
var $suggestion = $($e.currentTarget);
49-
this.trigger('suggestionSelected', extractSuggestion($suggestion));
56+
_onSuggestionClick: function onSuggestionClick($e) {
57+
this.trigger('suggestionClicked', $($e.currentTarget));
5058
},
5159

52-
_handleSuggestionMouseover: function($e) {
53-
var $suggestion = $($e.currentTarget);
60+
_onSuggestionMouseEnter: function onSuggestionMouseEnter($e) {
61+
this._setCursor($($e.currentTarget));
62+
},
5463

55-
this._getSuggestions().removeClass('tt-is-under-cursor');
56-
$suggestion.addClass('tt-is-under-cursor');
64+
_onSuggestionMouseLeave: function onSuggestionMouseLeave($e) {
65+
this._removeCursor();
5766
},
5867

59-
_handleSuggestionMouseleave: function($e) {
60-
var $suggestion = $($e.currentTarget);
68+
_onRendered: function onRendered() {
69+
this.trigger('suggestionsRendered');
70+
},
6171

62-
$suggestion.removeClass('tt-is-under-cursor');
72+
_getSuggestions: function getSuggestions() {
73+
return this.$menu.find('.tt-suggestion');
6374
},
6475

65-
_show: function() {
66-
// can't use jQuery#show because $menu is a span element we want
67-
// display: block; not dislay: inline;
68-
this.$menu.css('display', 'block');
76+
_getCursor: function getCursor() {
77+
return this.$menu.find('.tt-cursor');
6978
},
7079

71-
_hide: function() {
72-
this.$menu.hide();
80+
_setCursor: function setCursor($el) {
81+
$el.addClass('tt-cursor');
7382
},
7483

75-
_moveCursor: function(increment) {
76-
var $suggestions, $cur, nextIndex, $underCursor;
84+
_removeCursor: function removeCursor() {
85+
this._getCursor().removeClass('tt-cursor');
86+
},
7787

78-
// don't bother moving the cursor if the menu is closed or empty
79-
if (!this.isVisible()) {
80-
return;
81-
}
88+
_moveCursor: function moveCursor(increment) {
89+
var $suggestions, $oldCursor, newCursorIndex, $newCursor;
8290

91+
if (!this.isOpen) { return; }
92+
93+
$oldCursor = this._getCursor();
8394
$suggestions = this._getSuggestions();
84-
$cur = $suggestions.filter('.tt-is-under-cursor');
8595

86-
$cur.removeClass('tt-is-under-cursor');
96+
this._removeCursor();
8797

88-
// shifting before and after modulo to deal with -1 index of search input
89-
nextIndex = $suggestions.index($cur) + increment;
90-
nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1;
98+
// shifting before and after modulo to deal with -1 index
99+
newCursorIndex = $suggestions.index($oldCursor) + increment;
100+
newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1;
91101

92-
if (nextIndex === -1) {
102+
if (newCursorIndex === -1) {
93103
this.trigger('cursorRemoved');
94104

95105
return;
96106
}
97107

98-
else if (nextIndex < -1) {
99-
// circle to last suggestion
100-
nextIndex = $suggestions.length - 1;
108+
else if (newCursorIndex < -1) {
109+
newCursorIndex = $suggestions.length - 1;
101110
}
102111

103-
$underCursor = $suggestions.eq(nextIndex).addClass('tt-is-under-cursor');
112+
this._setCursor($newCursor = $suggestions.eq(newCursorIndex));
104113

105114
// in the case of scrollable overflow
106115
// make sure the cursor is visible in the menu
107-
this._ensureVisibility($underCursor);
116+
this._ensureVisible($newCursor);
108117

109-
this.trigger('cursorMoved', extractSuggestion($underCursor));
118+
this.trigger('cursorMoved');
110119
},
111120

112-
_getSuggestions: function() {
113-
return this.$menu.find('.tt-suggestions > .tt-suggestion');
114-
},
121+
_ensureVisible: function ensureVisible($el) {
122+
var elTop, elBottom, menuScrollTop, menuHeight;
115123

116-
_ensureVisibility: function($el) {
117-
var menuHeight = this.$menu.height() +
118-
parseInt(this.$menu.css('paddingTop'), 10) +
119-
parseInt(this.$menu.css('paddingBottom'), 10),
120-
menuScrollTop = this.$menu.scrollTop(),
121-
elTop = $el.position().top,
122-
elBottom = elTop + $el.outerHeight(true);
124+
elTop = $el.position().top;
125+
elBottom = elTop + $el.outerHeight(true);
126+
menuScrollTop = this.$menu.scrollTop();
127+
menuHeight = this.$menu.height() +
128+
parseInt(this.$menu.css('paddingTop'), 10) +
129+
parseInt(this.$menu.css('paddingBottom'), 10);
123130

124131
if (elTop < 0) {
125132
this.$menu.scrollTop(menuScrollTop + elTop);
@@ -130,153 +137,65 @@ var DropdownView = (function() {
130137
}
131138
},
132139

133-
// public methods
134-
// --------------
135-
136-
destroy: function() {
137-
this.$menu.off('.tt');
138-
139-
this.$menu = null;
140-
},
141-
142-
isVisible: function() {
143-
return this.isOpen && !this.isEmpty;
144-
},
145-
146-
closeUnlessMouseIsOverDropdown: function() {
147-
// this helps detect the scenario a blur event has triggered
148-
// this function. we don't want to close the menu in that case
149-
// because it'll prevent the probable associated click event
150-
// from being fired
151-
if (!this.isMouseOverDropdown) {
152-
this.close();
153-
}
154-
},
140+
// ### public
155141

156-
close: function() {
142+
close: function close() {
157143
if (this.isOpen) {
158-
this.isOpen = false;
159-
this.isMouseOverDropdown = false;
160-
this._hide();
144+
this.isOpen = this.isMouseOverDropdown = false;
161145

162-
this.$menu
163-
.find('.tt-suggestions > .tt-suggestion')
164-
.removeClass('tt-is-under-cursor');
146+
this._removeCursor();
147+
this.$menu.hide();
165148

166149
this.trigger('closed');
167150
}
168151
},
169152

170-
open: function() {
153+
open: function open() {
171154
if (!this.isOpen) {
172155
this.isOpen = true;
173-
!this.isEmpty && this._show();
156+
157+
// can't use jQuery#show because $menu is a span element we want
158+
// display: block; not dislay: inline;
159+
this.$menu.css('display', 'block');
174160

175161
this.trigger('opened');
176162
}
177163
},
178164

179-
setLanguageDirection: function(dir) {
180-
var ltrCss = { left: '0', right: 'auto' },
181-
rtlCss = { left: 'auto', right:' 0' };
182-
183-
dir === 'ltr' ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss);
165+
setLanguageDirection: function setLanguageDirection(dir) {
166+
this.$menu.css(dir === 'ltr' ? css.ltr : css.rtl);
184167
},
185168

186-
moveCursorUp: function() {
169+
moveCursorUp: function moveCursorUp() {
187170
this._moveCursor(-1);
188171
},
189172

190-
moveCursorDown: function() {
173+
moveCursorDown: function moveCursorDown() {
191174
this._moveCursor(+1);
192175
},
193176

194-
getSuggestionUnderCursor: function() {
195-
var $suggestion = this._getSuggestions()
196-
.filter('.tt-is-under-cursor')
197-
.first();
198-
199-
return $suggestion.length > 0 ? extractSuggestion($suggestion) : null;
177+
getDatumForSuggestion: function getDatumForSuggestion($el) {
178+
return $el.length ? SectionView.extractDatum($el) : null;
200179
},
201180

202-
getFirstSuggestion: function() {
203-
var $suggestion = this._getSuggestions().first();
204-
205-
return $suggestion.length > 0 ? extractSuggestion($suggestion) : null;
181+
getDatumForCursor: function getDatumForCursor() {
182+
return this.getDatumForSuggestion(this._getCursor().first());
206183
},
207184

208-
renderSuggestions: function(dataset, suggestions) {
209-
var datasetClassName = 'tt-dataset-' + dataset.name,
210-
wrapper = '<div class="tt-suggestion">%body</div>',
211-
compiledHtml,
212-
$suggestionsList,
213-
$dataset = this.$menu.find('.' + datasetClassName),
214-
elBuilder,
215-
fragment,
216-
$el;
217-
218-
// first time rendering suggestions for this dataset
219-
if ($dataset.length === 0) {
220-
$suggestionsList = $(html.suggestionsList).css(css.suggestionsList);
221-
222-
$dataset = $('<div></div>')
223-
.addClass(datasetClassName)
224-
.append(dataset.header)
225-
.append($suggestionsList)
226-
.append(dataset.footer)
227-
.appendTo(this.$menu);
228-
}
229-
230-
// suggestions to be rendered
231-
if (suggestions.length > 0) {
232-
this.isEmpty = false;
233-
this.isOpen && this._show();
234-
235-
elBuilder = document.createElement('div');
236-
fragment = document.createDocumentFragment();
237-
238-
utils.each(suggestions, function(i, suggestion) {
239-
suggestion.dataset = dataset.name;
240-
compiledHtml = dataset.template(suggestion.datum);
241-
elBuilder.innerHTML = wrapper.replace('%body', compiledHtml);
242-
243-
$el = $(elBuilder.firstChild)
244-
.css(css.suggestion)
245-
.data('suggestion', suggestion);
246-
247-
$el.children().each(function() {
248-
$(this).css(css.suggestionChild);
249-
});
185+
getDatumForTopSuggestion: function getDatumForTopSuggestion() {
186+
return this.getDatumForSuggestion(this._getSuggestions().first());
187+
},
250188

251-
fragment.appendChild($el[0]);
252-
});
189+
update: function update(query) {
190+
utils.each(this.sections, updateSection);
253191

254-
// show this dataset in case it was previously empty
255-
// and render the new suggestions
256-
$dataset.show().find('.tt-suggestions').html(fragment);
257-
}
258-
259-
// no suggestions to render
260-
else {
261-
this.clearSuggestions(dataset.name);
262-
}
263-
264-
this.trigger('suggestionsRendered');
192+
function updateSection(i, section) { section.update(query); }
265193
},
266194

267-
clearSuggestions: function(datasetName) {
268-
var $datasets = datasetName ?
269-
this.$menu.find('.tt-dataset-' + datasetName) :
270-
this.$menu.find('[class^="tt-dataset-"]'),
271-
$suggestions = $datasets.find('.tt-suggestions');
195+
empty: function empty() {
196+
utils.each(this.sections, clearSection);
272197

273-
$datasets.hide();
274-
$suggestions.empty();
275-
276-
if (this._getSuggestions().length === 0) {
277-
this.isEmpty = true;
278-
this._hide();
279-
}
198+
function clearSection(i, section) { section.clear(); }
280199
}
281200
});
282201

@@ -285,7 +204,7 @@ var DropdownView = (function() {
285204
// helper functions
286205
// ----------------
287206

288-
function extractSuggestion($el) {
289-
return $el.data('suggestion');
207+
function initializeSection(o) {
208+
return new SectionView(o);
290209
}
291210
})();

0 commit comments

Comments
 (0)