1- import { isPresent , isBlank , BaseException } from 'angular2/src/facade/lang' ;
2- import { List , MapWrapper } from 'angular2/src/facade/collection' ;
1+ import { isPresent , isBlank , BaseException , assertionsEnabled , RegExpWrapper } from 'angular2/src/facade/lang' ;
2+ import { List , MapWrapper , StringMapWrapper } from 'angular2/src/facade/collection' ;
33import { DOM } from 'angular2/src/facade/dom' ;
44import { SelectorMatcher } from '../selector' ;
55import { CssSelector } from '../selector' ;
@@ -10,6 +10,10 @@ import {CompileStep} from './compile_step';
1010import { CompileElement } from './compile_element' ;
1111import { CompileControl } from './compile_control' ;
1212
13+ import { isSpecialProperty } from './element_binder_builder' ; ;
14+
15+ var PROPERTY_BINDING_REGEXP = RegExpWrapper . create ( '^ *([^\\s\\|]+)' ) ;
16+
1317/**
1418 * Parses the directives on a single element. Assumes ViewSplitter has already created
1519 * <template> elements for template directives.
@@ -29,13 +33,13 @@ export class DirectiveParser extends CompileStep {
2933 _selectorMatcher :SelectorMatcher ;
3034 constructor ( directives :List < DirectiveMetadata > ) {
3135 super ( ) ;
36+ var selector ;
37+
3238 this . _selectorMatcher = new SelectorMatcher ( ) ;
3339 for ( var i = 0 ; i < directives . length ; i ++ ) {
3440 var directiveMetadata = directives [ i ] ;
35- this . _selectorMatcher . addSelectable (
36- CssSelector . parse ( directiveMetadata . annotation . selector ) ,
37- directiveMetadata
38- ) ;
41+ selector = CssSelector . parse ( directiveMetadata . annotation . selector ) ;
42+ this . _selectorMatcher . addSelectable ( selector , directiveMetadata ) ;
3943 }
4044 }
4145
@@ -67,19 +71,85 @@ export class DirectiveParser extends CompileStep {
6771 // Note: We assume that the ViewSplitter already did its work, i.e. template directive should
6872 // only be present on <template> elements any more!
6973 var isTemplateElement = DOM . isTemplateElement ( current . element ) ;
70- this . _selectorMatcher . match ( cssSelector , ( directive ) => {
71- if ( directive . annotation instanceof Viewport ) {
72- if ( ! isTemplateElement ) {
73- throw new BaseException ( 'Viewport directives need to be placed on <template> elements or elements with template attribute!' ) ;
74- } else if ( isPresent ( current . viewportDirective ) ) {
75- throw new BaseException ( 'Only one template directive per element is allowed!' ) ;
76- }
77- } else if ( isTemplateElement ) {
78- throw new BaseException ( 'Only template directives are allowed on <template> elements!' ) ;
79- } else if ( ( directive . annotation instanceof Component ) && isPresent ( current . componentDirective ) ) {
80- throw new BaseException ( 'Only one component directive per element is allowed!' ) ;
81- }
74+ var matchedProperties ; // StringMap - used in dev mode to store all properties that have been matched
75+
76+ this . _selectorMatcher . match ( cssSelector , ( selector , directive ) => {
77+ matchedProperties = updateMatchedProperties ( matchedProperties , selector , directive ) ;
78+ checkDirectiveValidity ( directive , current , isTemplateElement ) ;
8279 current . addDirective ( directive ) ;
8380 } ) ;
81+
82+ // raise error if some directives are missing
83+ checkMissingDirectives ( current , matchedProperties , isTemplateElement ) ;
84+ }
85+ }
86+
87+ // calculate all the properties that are used or interpreted by all directives
88+ // those properties correspond to the directive selectors and the directive bindings
89+ function updateMatchedProperties ( matchedProperties , selector , directive ) {
90+ if ( assertionsEnabled ( ) ) {
91+ var attrs = selector . attrs ;
92+ if ( ! isPresent ( matchedProperties ) ) {
93+ matchedProperties = StringMapWrapper . create ( ) ;
94+ }
95+ if ( isPresent ( attrs ) ) {
96+ for ( var idx = 0 ; idx < attrs . length ; idx += 2 ) {
97+ // attribute name is stored on even indexes
98+ StringMapWrapper . set ( matchedProperties , attrs [ idx ] , true ) ;
99+ }
100+ }
101+ // some properties can be used by the directive, so we need to register them
102+ if ( isPresent ( directive . annotation ) && isPresent ( directive . annotation . bind ) ) {
103+ var bindMap = directive . annotation . bind ;
104+ StringMapWrapper . forEach ( bindMap , ( value , key ) => {
105+ // value is the name of the property that is intepreted
106+ // e.g. 'myprop' or 'myprop | double' when a pipe is used to transform the property
107+
108+ // keep the property name and remove the pipe
109+ var bindProp = RegExpWrapper . firstMatch ( PROPERTY_BINDING_REGEXP , value ) ;
110+ if ( isPresent ( bindProp ) && isPresent ( bindProp [ 1 ] ) ) {
111+ StringMapWrapper . set ( matchedProperties , bindProp [ 1 ] , true ) ;
112+ }
113+ } ) ;
114+ }
115+ }
116+ return matchedProperties ;
117+ }
118+
119+ // check if the directive is compatible with the current element
120+ function checkDirectiveValidity ( directive , current , isTemplateElement ) {
121+ if ( directive . annotation instanceof Viewport ) {
122+ if ( ! isTemplateElement ) {
123+ throw new BaseException ( `Viewport directives need to be placed on <template> elements or elements ` +
124+ `with template attribute - check ${ current . elementDescription } ` ) ;
125+ } else if ( isPresent ( current . viewportDirective ) ) {
126+ throw new BaseException ( `Only one viewport directive can be used per element - check ${ current . elementDescription } ` ) ;
127+ }
128+ } else if ( isTemplateElement ) {
129+ throw new BaseException ( `Only template directives are allowed on template elements - check ${ current . elementDescription } ` ) ;
130+ } else if ( ( directive . annotation instanceof Component ) && isPresent ( current . componentDirective ) ) {
131+ throw new BaseException ( `Multiple component directives not allowed on the same element - check ${ current . elementDescription } ` ) ;
132+ }
133+ }
134+
135+ // validates that there is no missing directive - dev mode only
136+ function checkMissingDirectives ( current , matchedProperties , isTemplateElement ) {
137+ if ( assertionsEnabled ( ) ) {
138+ var ppBindings = current . propertyBindings ;
139+ if ( isPresent ( ppBindings ) ) {
140+ // check that each property corresponds to a real property or has been matched by a directive
141+ MapWrapper . forEach ( ppBindings , ( expression , prop ) => {
142+ if ( ! DOM . hasProperty ( current . element , prop ) && ! isSpecialProperty ( prop ) ) {
143+ if ( ! isPresent ( matchedProperties ) || ! isPresent ( StringMapWrapper . get ( matchedProperties , prop ) ) ) {
144+ throw new BaseException ( `Missing directive to handle '${ prop } ' in ${ current . elementDescription } ` ) ;
145+ }
146+ }
147+ } ) ;
148+ }
149+ // template only store directives as attribute when they are not bound to expressions
150+ // so we have to validate the expression case too (e.g. !if="condition")
151+ if ( isTemplateElement && ! current . isViewRoot && ! isPresent ( current . viewportDirective ) ) {
152+ throw new BaseException ( `Missing directive to handle: ${ current . elementDescription } ` ) ;
153+ }
84154 }
85155}
0 commit comments