@@ -358,11 +358,12 @@ function $CompileProvider($provide) {
358358 // jquery always rewraps, whereas we need to preserve the original selector so that we can modify it.
359359 $compileNodes = jqLite ( $compileNodes ) ;
360360 }
361+ var tempParent = document . createDocumentFragment ( ) ;
361362 // We can not compile top level text elements since text nodes can be merged and we will
362363 // not be able to attach scope data to them, so we will wrap them in <span>
363364 forEach ( $compileNodes , function ( node , index ) {
364365 if ( node . nodeType == 3 /* text node */ && node . nodeValue . match ( / \S + / ) /* non-empty */ ) {
365- $compileNodes [ index ] = jqLite ( node ) . wrap ( '<span></span>' ) . parent ( ) [ 0 ] ;
366+ $compileNodes [ index ] = node = jqLite ( node ) . wrap ( '<span></span>' ) . parent ( ) [ 0 ] ;
366367 }
367368 } ) ;
368369 var compositeLinkFn = compileNodes ( $compileNodes , transcludeFn , $compileNodes , maxPriority ) ;
@@ -420,7 +421,7 @@ function $CompileProvider($provide) {
420421 attrs = new Attributes ( ) ;
421422
422423 // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
423- directives = collectDirectives ( nodeList [ i ] , [ ] , attrs , maxPriority ) ;
424+ directives = collectDirectives ( nodeList [ i ] , [ ] , attrs , i == 0 ? maxPriority : undefined ) ;
424425
425426 nodeLinkFn = ( directives . length )
426427 ? applyDirectivesToNode ( directives , nodeList [ i ] , attrs , transcludeFn , $rootElement )
@@ -509,6 +510,10 @@ function $CompileProvider($provide) {
509510 // iterate over the attributes
510511 for ( var attr , name , nName , ngAttrName , value , nAttrs = node . attributes ,
511512 j = 0 , jj = nAttrs && nAttrs . length ; j < jj ; j ++ ) {
513+ var attrStartName ;
514+ var attrEndName ;
515+ var index ;
516+
512517 attr = nAttrs [ j ] ;
513518 if ( attr . specified ) {
514519 name = attr . name ;
@@ -517,6 +522,11 @@ function $CompileProvider($provide) {
517522 if ( NG_ATTR_BINDING . test ( ngAttrName ) ) {
518523 name = ngAttrName . substr ( 6 ) . toLowerCase ( ) ;
519524 }
525+ if ( ( index = ngAttrName . lastIndexOf ( 'Start' ) ) != - 1 && index == ngAttrName . length - 5 ) {
526+ attrStartName = name ;
527+ attrEndName = name . substr ( 0 , name . length - 5 ) + 'end' ;
528+ name = name . substr ( 0 , name . length - 6 ) ;
529+ }
520530 nName = directiveNormalize ( name . toLowerCase ( ) ) ;
521531 attrsMap [ nName ] = name ;
522532 attrs [ nName ] = value = trim ( ( msie && name == 'href' )
@@ -526,7 +536,7 @@ function $CompileProvider($provide) {
526536 attrs [ nName ] = true ; // presence means true
527537 }
528538 addAttrInterpolateDirective ( node , directives , value , nName ) ;
529- addDirective ( directives , nName , 'A' , maxPriority ) ;
539+ addDirective ( directives , nName , 'A' , maxPriority , attrStartName , attrEndName ) ;
530540 }
531541 }
532542
@@ -565,6 +575,47 @@ function $CompileProvider($provide) {
565575 return directives ;
566576 }
567577
578+ /**
579+ * Given a node with an directive-start it collects all of the siblings until it find directive-end.
580+ * @param node
581+ * @param attrStart
582+ * @param attrEnd
583+ * @returns {* }
584+ */
585+ function groupScan ( node , attrStart , attrEnd ) {
586+ var nodes = [ ] ;
587+ var depth = 0 ;
588+ if ( attrStart && node . hasAttribute && node . hasAttribute ( attrStart ) ) {
589+ var startNode = node ;
590+ do {
591+ if ( ! node ) {
592+ throw ngError ( 51 , "Unterminated attribute, found '{0}' but no matching '{1}' found." , attrStart , attrEnd ) ;
593+ }
594+ if ( node . hasAttribute ( attrStart ) ) depth ++ ;
595+ if ( node . hasAttribute ( attrEnd ) ) depth -- ;
596+ nodes . push ( node ) ;
597+ node = node . nextSibling ;
598+ } while ( depth > 0 ) ;
599+ } else {
600+ nodes . push ( node ) ;
601+ }
602+ return jqLite ( nodes ) ;
603+ }
604+
605+ /**
606+ * Wrapper for linking function which converts normal linking function into a grouped
607+ * linking function.
608+ * @param linkFn
609+ * @param attrStart
610+ * @param attrEnd
611+ * @returns {Function }
612+ */
613+ function groupElementsLinkFnWrapper ( linkFn , attrStart , attrEnd ) {
614+ return function ( scope , element , attrs , controllers ) {
615+ element = groupScan ( element [ 0 ] , attrStart , attrEnd ) ;
616+ return linkFn ( scope , element , attrs , controllers ) ;
617+ }
618+ }
568619
569620 /**
570621 * Once the directives have been collected, their compile functions are executed. This method
@@ -601,6 +652,13 @@ function $CompileProvider($provide) {
601652 // executes all directives on the current element
602653 for ( var i = 0 , ii = directives . length ; i < ii ; i ++ ) {
603654 directive = directives [ i ] ;
655+ var attrStart = directive . $$start ;
656+ var attrEnd = directive . $$end ;
657+
658+ // collect multiblock sections
659+ if ( attrStart ) {
660+ $compileNode = groupScan ( compileNode , attrStart , attrEnd )
661+ }
604662 $template = undefined ;
605663
606664 if ( terminalPriority > directive . priority ) {
@@ -631,11 +689,11 @@ function $CompileProvider($provide) {
631689 transcludeDirective = directive ;
632690 terminalPriority = directive . priority ;
633691 if ( directiveValue == 'element' ) {
634- $template = jqLite ( compileNode ) ;
692+ $template = groupScan ( compileNode , attrStart , attrEnd )
635693 $compileNode = templateAttrs . $$element =
636694 jqLite ( document . createComment ( ' ' + directiveName + ': ' + templateAttrs [ directiveName ] + ' ' ) ) ;
637695 compileNode = $compileNode [ 0 ] ;
638- replaceWith ( jqCollection , jqLite ( $template [ 0 ] ) , compileNode ) ;
696+ replaceWith ( jqCollection , jqLite ( sliceArgs ( $template ) ) , compileNode ) ;
639697 childTranscludeFn = compile ( $template , transcludeFn , terminalPriority ) ;
640698 } else {
641699 $template = jqLite ( JQLiteClone ( compileNode ) ) . contents ( ) ;
@@ -699,9 +757,9 @@ function $CompileProvider($provide) {
699757 try {
700758 linkFn = directive . compile ( $compileNode , templateAttrs , childTranscludeFn ) ;
701759 if ( isFunction ( linkFn ) ) {
702- addLinkFns ( null , linkFn ) ;
760+ addLinkFns ( null , linkFn , attrStart , attrEnd ) ;
703761 } else if ( linkFn ) {
704- addLinkFns ( linkFn . pre , linkFn . post ) ;
762+ addLinkFns ( linkFn . pre , linkFn . post , attrStart , attrEnd ) ;
705763 }
706764 } catch ( e ) {
707765 $exceptionHandler ( e , startingTag ( $compileNode ) ) ;
@@ -723,12 +781,14 @@ function $CompileProvider($provide) {
723781
724782 ////////////////////
725783
726- function addLinkFns ( pre , post ) {
784+ function addLinkFns ( pre , post , attrStart , attrEnd ) {
727785 if ( pre ) {
786+ if ( attrStart ) pre = groupElementsLinkFnWrapper ( pre , attrStart , attrEnd ) ;
728787 pre . require = directive . require ;
729788 preLinkFns . push ( pre ) ;
730789 }
731790 if ( post ) {
791+ if ( attrStart ) post = groupElementsLinkFnWrapper ( post , attrStart , attrEnd ) ;
732792 post . require = directive . require ;
733793 postLinkFns . push ( post ) ;
734794 }
@@ -907,17 +967,20 @@ function $CompileProvider($provide) {
907967 * * `M`: comment
908968 * @returns true if directive was added.
909969 */
910- function addDirective ( tDirectives , name , location , maxPriority ) {
911- var match = false ;
970+ function addDirective ( tDirectives , name , location , maxPriority , startAttrName , endAttrName ) {
971+ var match = null ;
912972 if ( hasDirectives . hasOwnProperty ( name ) ) {
913973 for ( var directive , directives = $injector . get ( name + Suffix ) ,
914974 i = 0 , ii = directives . length ; i < ii ; i ++ ) {
915975 try {
916976 directive = directives [ i ] ;
917977 if ( ( maxPriority === undefined || maxPriority > directive . priority ) &&
918978 directive . restrict . indexOf ( location ) != - 1 ) {
979+ if ( startAttrName ) {
980+ directive = inherit ( directive , { $$start : startAttrName , $$end : endAttrName } ) ;
981+ }
919982 tDirectives . push ( directive ) ;
920- match = true ;
983+ match = directive ;
921984 }
922985 } catch ( e ) { $exceptionHandler ( e ) ; }
923986 }
@@ -1120,30 +1183,50 @@ function $CompileProvider($provide) {
11201183 *
11211184 * @param {JqLite= } $rootElement The root of the compile tree. Used so that we can replace nodes
11221185 * in the root of the tree.
1123- * @param {JqLite } $element The jqLite element which we are going to replace. We keep the shell,
1186+ * @param {JqLite } elementsToRemove The jqLite element which we are going to replace. We keep the shell,
11241187 * but replace its DOM node reference.
11251188 * @param {Node } newNode The new DOM node.
11261189 */
1127- function replaceWith ( $rootElement , $element , newNode ) {
1128- var oldNode = $element [ 0 ] ,
1129- parent = oldNode . parentNode ,
1190+ function replaceWith ( $rootElement , elementsToRemove , newNode ) {
1191+ var firstElementToRemove = elementsToRemove [ 0 ] ,
1192+ removeCount = elementsToRemove . length ,
1193+ parent = firstElementToRemove . parentNode ,
11301194 i , ii ;
11311195
11321196 if ( $rootElement ) {
11331197 for ( i = 0 , ii = $rootElement . length ; i < ii ; i ++ ) {
1134- if ( $rootElement [ i ] == oldNode ) {
1135- $rootElement [ i ] = newNode ;
1198+ if ( $rootElement [ i ] == firstElementToRemove ) {
1199+ $rootElement [ i ++ ] = newNode ;
1200+ for ( var j = i , j2 = j + removeCount - 1 ,
1201+ jj = $rootElement . length ;
1202+ j < jj ; j ++ , j2 ++ ) {
1203+ if ( j2 < jj ) {
1204+ $rootElement [ j ] = $rootElement [ j2 ] ;
1205+ } else {
1206+ delete $rootElement [ j ] ;
1207+ }
1208+ }
1209+ $rootElement . length -= removeCount - 1 ;
11361210 break ;
11371211 }
11381212 }
11391213 }
11401214
11411215 if ( parent ) {
1142- parent . replaceChild ( newNode , oldNode ) ;
1216+ parent . replaceChild ( newNode , firstElementToRemove ) ;
1217+ }
1218+ var fragment = document . createDocumentFragment ( ) ;
1219+ fragment . appendChild ( firstElementToRemove ) ;
1220+ newNode [ jqLite . expando ] = firstElementToRemove [ jqLite . expando ] ;
1221+ for ( var k = 1 , kk = elementsToRemove . length ; k < kk ; k ++ ) {
1222+ var element = elementsToRemove [ k ] ;
1223+ jqLite ( element ) . remove ( ) ; // must do this way to clean up expando
1224+ fragment . appendChild ( element ) ;
1225+ delete elementsToRemove [ k ] ;
11431226 }
11441227
1145- newNode [ jqLite . expando ] = oldNode [ jqLite . expando ] ;
1146- $element [ 0 ] = newNode ;
1228+ elementsToRemove [ 0 ] = newNode ;
1229+ elementsToRemove . length = 1
11471230 }
11481231 } ] ;
11491232}
0 commit comments