237
237
* as those elements need to created and cloned in a special way when they are defined outside their
238
238
* usual containers (e.g. like `<svg>`).
239
239
* * See also the `directive.templateNamespace` property.
240
- *
240
+ * The `$transclude` function has a property called `$slots`, which is a hash of slot names to slot transclusion
241
+ * functions. If a slot was declared but not filled its value on the `$slots` object will be `null`.
241
242
*
242
243
* #### `require`
243
244
* Require another directive and inject its controller as the fourth argument to the linking function. The
337
338
* The contents are compiled and provided to the directive as a **transclusion function**. See the
338
339
* {@link $compile#transclusion Transclusion} section below.
339
340
*
340
- * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
341
- * directive's element or the entire element:
341
+ * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the
342
+ * directive's element, the entire element or parts of the element:
342
343
*
343
344
* * `true` - transclude the content (i.e. the child nodes) of the directive's element.
344
345
* * `'element'` - transclude the whole of the directive's element including any directives on this
345
346
* element that defined at a lower priority than this directive. When used, the `template`
346
347
* property is ignored.
348
+ * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template.
349
+ * See {@link ngTransclude} for more information.
350
+ *
351
+ * Mult-slot transclusion is declared by providing an object for the `transclude` property.
352
+ * This object is a map where the keys are the canonical name of HTML elements to match in the transcluded HTML,
353
+ * and the values are the names of the slots. If the name is prefixed with a `?` then that slot is optional.
347
354
*
355
+ * The slots are made available as `$transclude.$slots` on the transclude function that is passed to the
356
+ * linking functions as the fifth parameter, and can be injected into the directive controller.
348
357
*
349
358
* #### `compile`
350
359
*
@@ -1511,7 +1520,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
1511
1520
// so that they are available inside the `controllersBoundTransclude` function
1512
1521
var boundSlots = boundTranscludeFn . $$slots = createMap ( ) ;
1513
1522
for ( var slotName in transcludeFn . $$slots ) {
1514
- boundSlots [ slotName ] = createBoundTranscludeFn ( scope , transcludeFn . $$slots [ slotName ] , previousBoundTranscludeFn ) ;
1523
+ if ( transcludeFn . $$slots [ slotName ] ) {
1524
+ boundSlots [ slotName ] = createBoundTranscludeFn ( scope , transcludeFn . $$slots [ slotName ] , previousBoundTranscludeFn ) ;
1525
+ } else {
1526
+ boundSlots [ slotName ] = null ;
1527
+ }
1515
1528
}
1516
1529
1517
1530
return boundTranscludeFn ;
@@ -1855,32 +1868,42 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
1855
1868
} else {
1856
1869
1857
1870
var slots = createMap ( ) ;
1871
+
1858
1872
$template = jqLite ( jqLiteClone ( compileNode ) ) . contents ( ) ;
1859
1873
1860
1874
if ( isObject ( directiveValue ) ) {
1861
1875
1862
- // We have transclusion slots - collect them up and compile them and store their
1863
- // transclusion functions
1876
+ // We have transclusion slots,
1877
+ // collect them up, compile them and store their transclusion functions
1864
1878
$template = [ ] ;
1865
- var slotNames = createMap ( ) ;
1879
+
1880
+ var slotMap = createMap ( ) ;
1866
1881
var filledSlots = createMap ( ) ;
1867
1882
1868
- // Parse the slot names: if they start with a ? then they are optional
1869
- forEach ( directiveValue , function ( slotName , key ) {
1870
- var optional = ( slotName . charAt ( 0 ) === '?' ) ;
1871
- slotName = optional ? slotName . substring ( 1 ) : slotName ;
1872
- slotNames [ key ] = slotName ;
1873
- slots [ slotName ] = [ ] ;
1883
+ // Parse the element selectors
1884
+ forEach ( directiveValue , function ( elementSelector , slotName ) {
1885
+ // If an element selector starts with a ? then it is optional
1886
+ var optional = ( elementSelector . charAt ( 0 ) === '?' ) ;
1887
+ elementSelector = optional ? elementSelector . substring ( 1 ) : elementSelector ;
1888
+
1889
+ slotMap [ elementSelector ] = slotName ;
1890
+
1891
+ // We explicitly assign `null` since this implies that a slot was defined but not filled.
1892
+ // Later when calling boundTransclusion functions with a slot name we only error if the
1893
+ // slot is `undefined`
1894
+ slots [ slotName ] = null ;
1895
+
1874
1896
// filledSlots contains `true` for all slots that are either optional or have been
1875
1897
// filled. This is used to check that we have not missed any required slots
1876
1898
filledSlots [ slotName ] = optional ;
1877
1899
} ) ;
1878
1900
1879
1901
// Add the matching elements into their slot
1880
1902
forEach ( $compileNode . contents ( ) , function ( node ) {
1881
- var slotName = slotNames [ directiveNormalize ( nodeName_ ( node ) ) ] ;
1903
+ var slotName = slotMap [ nodeName_ ( node ) ] ;
1882
1904
if ( slotName ) {
1883
1905
filledSlots [ slotName ] = true ;
1906
+ slots [ slotName ] = slots [ slotName ] || [ ] ;
1884
1907
slots [ slotName ] . push ( node ) ;
1885
1908
} else {
1886
1909
$template . push ( node ) ;
@@ -1894,9 +1917,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
1894
1917
}
1895
1918
} ) ;
1896
1919
1897
- forEach ( Object . keys ( slots ) , function ( slotName ) {
1898
- slots [ slotName ] = compilationGenerator ( mightHaveMultipleTransclusionError , slots [ slotName ] , transcludeFn ) ;
1899
- } ) ;
1920
+ for ( var slotName in slots ) {
1921
+ if ( slots [ slotName ] ) {
1922
+ // Only define a transclusion function if the slot was filled
1923
+ slots [ slotName ] = compilationGenerator ( mightHaveMultipleTransclusionError , slots [ slotName ] , transcludeFn ) ;
1924
+ }
1925
+ }
1900
1926
}
1901
1927
1902
1928
$compileNode . empty ( ) ; // clear contents
@@ -2125,6 +2151,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2125
2151
// is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
2126
2152
transcludeFn = controllersBoundTransclude ;
2127
2153
transcludeFn . $$boundTransclude = boundTranscludeFn ;
2154
+ // expose the slots on the `$transclude` function
2155
+ transcludeFn . $slots = boundTranscludeFn . $$slots ;
2128
2156
}
2129
2157
2130
2158
if ( controllerDirectives ) {
@@ -2221,16 +2249,22 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2221
2249
futureParentElement = hasElementTranscludeDirective ? $element . parent ( ) : $element ;
2222
2250
}
2223
2251
if ( slotName ) {
2252
+ // slotTranscludeFn can be one of three things:
2253
+ // * a transclude function - a filled slot
2254
+ // * `null` - an optional slot that was not filled
2255
+ // * `undefined` - a slot that was not declared (i.e. invalid)
2224
2256
var slotTranscludeFn = boundTranscludeFn . $$slots [ slotName ] ;
2225
- if ( ! slotTranscludeFn ) {
2257
+ if ( slotTranscludeFn ) {
2258
+ return slotTranscludeFn ( scope , cloneAttachFn , transcludeControllers , futureParentElement , scopeToChild ) ;
2259
+ } else if ( isUndefined ( slotTranscludeFn ) ) {
2226
2260
throw $compileMinErr ( 'noslot' ,
2227
2261
'No parent directive that requires a transclusion with slot name "{0}". ' +
2228
2262
'Element: {1}' ,
2229
2263
slotName , startingTag ( $element ) ) ;
2230
2264
}
2231
- return slotTranscludeFn ( scope , cloneAttachFn , transcludeControllers , futureParentElement , scopeToChild ) ;
2265
+ } else {
2266
+ return boundTranscludeFn ( scope , cloneAttachFn , transcludeControllers , futureParentElement , scopeToChild ) ;
2232
2267
}
2233
- return boundTranscludeFn ( scope , cloneAttachFn , transcludeControllers , futureParentElement , scopeToChild ) ;
2234
2268
}
2235
2269
}
2236
2270
}
0 commit comments