Skip to content
This repository was archived by the owner on Nov 12, 2017. It is now read-only.

Commit d933c8b

Browse files
author
Gustavo Henke
committed
fix(dropdown): transclude options content manually
As of Angular.js v1.2.18, nested transclusion with ng-repeat wasn't binding to the correct scope (see angular/angular.js#7842). To fix that, we should manually transclude the content of options and then, finally, compile the options with ng-repeat. Fixes #14
1 parent 35d3e7f commit d933c8b

File tree

3 files changed

+120
-114
lines changed

3 files changed

+120
-114
lines changed

bower.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
"name": "frontkit",
33
"version": "0.0.1",
44
"dependencies": {
5-
"angular": "1.2.17"
5+
"angular": "~1.2.10"
66
},
77
"devDependencies": {
8-
"angular-mocks": "1.2.17",
8+
"angular-mocks": "~1.2.10",
99
"holderjs": "~2.3.1",
1010
"highlightjs": "~8.0.0",
1111
"normalize.css": "~3.0.0"

src/scripts/dropdown.js

+117-111
Original file line numberDiff line numberDiff line change
@@ -135,21 +135,7 @@
135135

136136
ctrl.parseOptions = function( model ) {
137137
var currPromise;
138-
$scope.$watch( model, watchFn, true );
139-
$scope.$watch( model, function( newValue, oldValue ) {
140-
var isFn = ng.isFunction;
141-
var isPromise = !!newValue && isFn( newValue.then );
142-
isPromise &= !!oldValue && isFn( oldValue.then );
143-
144-
// We won't handle anything here unless both values are promises.
145-
if ( !ng.equals( newValue, oldValue ) || !isPromise ) {
146-
return;
147-
}
148-
149-
watchFn( newValue );
150-
});
151-
152-
function watchFn( value ) {
138+
$scope.$watch( model, function watchFn( value ) {
153139
var promise;
154140
currPromise = null;
155141

@@ -174,7 +160,7 @@
174160

175161
currPromise = promise;
176162
}
177-
}
163+
}, true );
178164
};
179165

180166
function clearSearch() {
@@ -204,9 +190,10 @@
204190
]);
205191

206192
module.directive( "dropdownOptions", [
193+
"$compile",
207194
"repeatParser",
208195
"dropdownConfig",
209-
function( repeatParser, dropdownConfig ) {
196+
function( $compile, repeatParser, dropdownConfig ) {
210197
var definition = {};
211198

212199
definition.restrict = "EA";
@@ -215,112 +202,131 @@
215202
definition.templateUrl = "templates/dropdown/options.html";
216203
definition.require = "^dropdown";
217204

218-
definition.compile = function( tElement, tAttr ) {
219-
var model;
220-
var option = tElement.querySelector( ".dropdown-option" );
221-
var repeat = repeatParser.parse( tAttr.items );
222-
223-
if ( !repeat ) {
205+
definition.compile = function( tElement ) {
206+
// When in a detached case, we won't let compile go ahead
207+
if ( !tElement.parent().length ) {
224208
return;
225209
}
226210

227-
model = repeat.expr;
228-
repeat.expr = "$dropdown.options";
229-
230-
option.attr( "ng-repeat", repeatParser.toNgRepeat( repeat ) );
231-
option.attr( "ng-click", "$dropdown.addItem( " + repeat.item + " )" );
232-
option.attr( "ng-class", "{" +
233-
"active: $dropdown.activeOption === " + repeat.item +
234-
"}" );
235-
236-
return function( scope, element, attr, $dropdown ) {
237-
var list = element[ 0 ];
238-
configureOverflow();
239-
240-
$dropdown.parseOptions( model );
241-
$dropdown.valueKey = tAttr.value || null;
242-
243-
scope.$watch( "$dropdown.open", adjustScroll );
244-
scope.$watch( "$dropdown.activeOption", adjustScroll );
245-
246-
function adjustScroll() {
247-
var fromScrollTop, index, activeElem;
248-
var options = $dropdown.options;
249-
var scrollTop = list.scrollTop;
250-
251-
if ( ng.isArray( options ) ) {
252-
index = options.indexOf( $dropdown.activeOption );
253-
} else {
254-
// To keep compatibility with arrays, we'll init the index as -1
255-
index = -1;
256-
Object.keys( options ).some(function( key, i ) {
257-
if ( options[ key ] === $dropdown.activeOption ) {
258-
index = i;
259-
return true;
260-
}
261-
});
262-
}
211+
return definition.link;
212+
};
263213

264-
activeElem = list.querySelectorAll( ".dropdown-option" )[ index ];
214+
definition.link = function( scope, element, attr, $dropdown, transclude ) {
215+
var list = element[ 0 ];
216+
var option = element.querySelector( ".dropdown-option" );
217+
var repeat = repeatParser.parse( attr.items );
218+
219+
// If we have a repeat expr, let's use it to build the option list
220+
if ( repeat ) {
221+
$dropdown.parseOptions( repeat.expr );
222+
repeat.expr = "$dropdown.options";
223+
224+
// Option list building
225+
transclude(function( childs ) {
226+
option.append( childs );
227+
});
228+
229+
// Add a few directives to the option...
230+
option.attr( "ng-repeat", repeatParser.toNgRepeat( repeat ) );
231+
option.attr( "ng-click", "$dropdown.addItem( " + repeat.item + " )" );
232+
option.attr( "ng-class", "{" +
233+
"active: $dropdown.activeOption === " + repeat.item +
234+
"}" );
235+
236+
// ...and compile it
237+
$compile( option )( scope );
238+
}
265239

266-
if ( !$dropdown.open || !activeElem ) {
267-
// To be handled!
268-
return;
269-
}
240+
// Configure the overflow for this list
241+
configureOverflow();
270242

271-
fromScrollTop = activeElem.offsetTop - list.scrollTop;
272-
273-
// If the option is above the current scroll, we'll make it appear on the
274-
// top of the scroll.
275-
// Otherwise, it'll appear in the end of the scroll view.
276-
if ( fromScrollTop < 0 ) {
277-
scrollTop = activeElem.offsetTop;
278-
} else if ( list.clientHeight <= fromScrollTop + activeElem.clientHeight ) {
279-
scrollTop = activeElem.offsetTop +
280-
activeElem.clientHeight -
281-
list.clientHeight;
282-
}
243+
// Set the value key
244+
$dropdown.valueKey = attr.value || null;
283245

284-
list.scrollTop = scrollTop;
285-
}
246+
// Scope Watches
247+
// ---------------------------------------------------------------------------------
248+
scope.$watch( "$dropdown.open", adjustScroll );
249+
scope.$watch( "$dropdown.activeOption", adjustScroll );
250+
251+
// Functions
252+
// ---------------------------------------------------------------------------------
253+
function adjustScroll() {
254+
var fromScrollTop, index, activeElem;
255+
var options = $dropdown.options;
256+
var scrollTop = list.scrollTop;
286257

287-
function configureOverflow() {
288-
var height;
289-
var view = list.ownerDocument.defaultView;
290-
var styles = view.getComputedStyle( list, null );
291-
var display = element.css( "display" );
292-
var size = dropdownConfig.optionsPageSize;
293-
var li = $( "<li class='dropdown-option'>&nbsp;</li>" )[ 0 ];
294-
element.prepend( li );
295-
296-
// Temporarily show the element, just to calculate the li height
297-
element.css( "display", "block" );
298-
299-
// Calculate the height, considering border/padding
300-
height = li.clientHeight * size;
301-
height = [ "padding", "border" ].reduce(function( value, prop ) {
302-
var top = styles.getPropertyValue( prop + "-top" ) || "";
303-
var bottom = styles.getPropertyValue( prop + "-bottom" ) || "";
304-
305-
value += +top.replace( "px", "" ) || 0;
306-
value += +bottom.replace( "px", "" ) || 0;
307-
308-
return value;
309-
}, height );
310-
311-
// Set overflow CSS rules
312-
element.css({
313-
"overflow-y": "auto",
314-
"max-height": height + "px"
258+
if ( ng.isArray( options ) ) {
259+
index = options.indexOf( $dropdown.activeOption );
260+
} else {
261+
// To keep compatibility with arrays, we'll init the index as -1
262+
index = -1;
263+
Object.keys( options ).some(function( key, i ) {
264+
if ( options[ key ] === $dropdown.activeOption ) {
265+
index = i;
266+
return true;
267+
}
315268
});
269+
}
316270

317-
// And finally, set the element display to the previous value
318-
element.css( "display", display );
271+
activeElem = list.querySelectorAll( ".dropdown-option" )[ index ];
319272

320-
// Also remove the dummy <li> created previously
321-
$( li ).remove();
273+
if ( !$dropdown.open || !activeElem ) {
274+
// To be handled!
275+
return;
276+
}
277+
278+
fromScrollTop = activeElem.offsetTop - list.scrollTop;
279+
280+
// If the option is above the current scroll, we'll make it appear on the
281+
// top of the scroll.
282+
// Otherwise, it'll appear in the end of the scroll view.
283+
if ( fromScrollTop < 0 ) {
284+
scrollTop = activeElem.offsetTop;
285+
} else if ( list.clientHeight <= fromScrollTop + activeElem.clientHeight ) {
286+
scrollTop = activeElem.offsetTop +
287+
activeElem.clientHeight -
288+
list.clientHeight;
322289
}
323-
};
290+
291+
list.scrollTop = scrollTop;
292+
}
293+
294+
function configureOverflow() {
295+
var height;
296+
var view = list.ownerDocument.defaultView;
297+
var styles = view.getComputedStyle( list, null );
298+
var display = element.css( "display" );
299+
var size = dropdownConfig.optionsPageSize;
300+
var li = $( "<li class='dropdown-option'>&nbsp;</li>" )[ 0 ];
301+
element.prepend( li );
302+
303+
// Temporarily show the element, just to calculate the li height
304+
element.css( "display", "block" );
305+
306+
// Calculate the height, considering border/padding
307+
height = li.clientHeight * size;
308+
height = [ "padding", "border" ].reduce(function( value, prop ) {
309+
var top = styles.getPropertyValue( prop + "-top" ) || "";
310+
var bottom = styles.getPropertyValue( prop + "-bottom" ) || "";
311+
312+
value += +top.replace( "px", "" ) || 0;
313+
value += +bottom.replace( "px", "" ) || 0;
314+
315+
return value;
316+
}, height );
317+
318+
// Set overflow CSS rules
319+
element.css({
320+
"overflow-y": "auto",
321+
"max-height": height + "px"
322+
});
323+
324+
// And finally, set the element display to the previous value
325+
element.css( "display", display );
326+
327+
// Also remove the dummy <li> created previously
328+
$( li ).remove();
329+
}
324330
};
325331

326332
return definition;

src/templates/dropdown/options.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
<ul class="dropdown-options">
2-
<li class="dropdown-option" ng-transclude></li>
2+
<li class="dropdown-option"></li>
33
</ul>

0 commit comments

Comments
 (0)