77using DotVVM . Framework . Binding ;
88using DotVVM . Framework . Binding . Expressions ;
99using DotVVM . Framework . Binding . Properties ;
10+ using DotVVM . Framework . Compilation . ControlTree . Resolved ;
1011using DotVVM . Framework . Compilation . Javascript ;
12+ using DotVVM . Framework . Compilation . Validation ;
1113using DotVVM . Framework . Controls ;
1214using DotVVM . Framework . Hosting ;
1315using DotVVM . Framework . ResourceManagement ;
@@ -39,14 +41,14 @@ public HierarchyRepeater() : base("div")
3941 [ ControlPropertyBindingDataContextChange ( nameof ( DataSource ) ) ]
4042 [ BindingCompilationRequirements ( new [ ] { typeof ( DataSourceAccessBinding ) } , new [ ] { typeof ( DataSourceLengthBinding ) } ) ]
4143 [ MarkupOptions ( Required = true ) ]
42- public IValueBinding < IEnumerable < object > > ? ItemChildrenBinding
44+ public IStaticValueBinding < IEnumerable < object > > ? ItemChildrenBinding
4345 {
44- get => ( IValueBinding < IEnumerable < object > > ? ) GetValue ( ItemChildrenBindingProperty ) ;
46+ get => ( IStaticValueBinding < IEnumerable < object > > ? ) GetValue ( ItemChildrenBindingProperty ) ;
4547 set => SetValue ( ItemChildrenBindingProperty , value ) ;
4648 }
4749
4850 public static readonly DotvvmProperty ItemChildrenBindingProperty
49- = DotvvmProperty . Register < IValueBinding < IEnumerable < object > > ? , HierarchyRepeater > ( t => t . ItemChildrenBinding ) ;
51+ = DotvvmProperty . Register < IStaticValueBinding < IEnumerable < object > > ? , HierarchyRepeater > ( t => t . ItemChildrenBinding ) ;
5052
5153 /// <summary>
5254 /// Gets or sets the template for each HierarchyRepeater item.
@@ -147,7 +149,7 @@ protected override void RenderEndTag(IHtmlWriter writer, IDotvvmRequestContext c
147149
148150 protected override void RenderContents ( IHtmlWriter writer , IDotvvmRequestContext context )
149151 {
150- if ( RenderOnServer )
152+ if ( clientRootLevel is null )
151153 {
152154 foreach ( var child in Children . Except ( new [ ] { emptyDataContainer ! , clientItemTemplate ! } ) )
153155 {
@@ -156,7 +158,7 @@ protected override void RenderContents(IHtmlWriter writer, IDotvvmRequestContext
156158 }
157159 else
158160 {
159- clientRootLevel ! . Render ( writer , context ) ;
161+ clientRootLevel . Render ( writer , context ) ;
160162 }
161163 }
162164
@@ -166,12 +168,17 @@ private void SetChildren(IDotvvmRequestContext context, bool renderClientTemplat
166168 emptyDataContainer = null ;
167169 clientItemTemplate = null ;
168170
169- if ( DataSource is not null )
171+ if ( GetIEnumerableFromDataSource ( ) is { } enumerable )
170172 {
171- CreateServerLevel ( this . Children , context , GetIEnumerableFromDataSource ( ) ! ) ;
173+ CreateServerLevel ( this . Children ,
174+ context ,
175+ enumerable ,
176+ parentPath : ImmutableArray < int > . Empty ,
177+ foreachExpression : this . TryGetKnockoutForeachExpression ( )
178+ ) ;
172179 }
173180
174- if ( renderClientTemplate )
181+ if ( renderClientTemplate && GetDataSourceBinding ( ) is IValueBinding )
175182 {
176183 // whenever possible, we use the dotvvm deterministic ids, but if we are in a client-side template,
177184 // we'd get a binding... so we just generate a random Guid, not ideal but it will work.
@@ -185,7 +192,7 @@ private void SetChildren(IDotvvmRequestContext context, bool renderClientTemplat
185192 Children . Add ( clientRootLevel ) ;
186193 clientRootLevel . Children . Add ( new HierarchyRepeaterLevel {
187194 IsRoot = true ,
188- ForeachExpression = this . TryGetKnockoutForeachingExpression ( ) ,
195+ ForeachExpression = this . TryGetKnockoutForeachExpression ( ) . NotNull ( ) ,
189196 ItemTemplateId = clientItemTemplateId ,
190197 } ) ;
191198 }
@@ -200,19 +207,9 @@ private DotvvmControl CreateServerLevel(
200207 IList < DotvvmControl > c ,
201208 IDotvvmRequestContext context ,
202209 IEnumerable items ,
203- ImmutableArray < int > parentPath = default ,
204- string ? foreachExpression = default )
210+ ImmutableArray < int > parentPath ,
211+ string ? foreachExpression )
205212 {
206- if ( parentPath . IsDefault )
207- {
208- parentPath = ImmutableArray < int > . Empty ;
209- }
210-
211- foreachExpression ??= ( ( IValueBinding ) GetDataSourceBinding ( )
212- . GetProperty < DataSourceAccessBinding > ( )
213- . Binding )
214- . GetKnockoutBindingExpression ( this ) ;
215-
216213 var dataContextLevelWrapper = new HierarchyRepeaterLevel {
217214 ForeachExpression = foreachExpression
218215 } ;
@@ -228,7 +225,7 @@ private DotvvmControl CreateServerLevel(
228225 var index = 0 ;
229226 foreach ( var item in items )
230227 {
231- CreateServerItem ( levelWrapper . Children , context , item , parentPath , index ) ;
228+ CreateServerItem ( levelWrapper . Children , context , item , parentPath , index , foreachExpression is null ) ;
232229 index ++ ;
233230 }
234231 return dataContextLevelWrapper ;
@@ -239,11 +236,12 @@ private DotvvmControl CreateServerItem(
239236 IDotvvmRequestContext context ,
240237 object item ,
241238 ImmutableArray < int > parentPath ,
242- int index )
239+ int index ,
240+ bool serverOnly )
243241 {
244242 var itemWrapper = ItemWrapperCapability . GetWrapper ( ) ;
245243 c . Add ( itemWrapper ) ;
246- var dataItem = new DataItemContainer { DataItemIndex = index } ;
244+ var dataItem = new DataItemContainer { DataItemIndex = index , RenderItemBinding = ! serverOnly } ;
247245 itemWrapper . Children . Add ( dataItem ) ;
248246 dataItem . SetDataContextTypeFromDataSource ( GetDataSourceBinding ( ) ) ;
249247 // NB: the placeholder is needed because during data context resolution DataItemContainers are looked up
@@ -263,7 +261,8 @@ private DotvvmControl CreateServerItem(
263261 var itemChildren = GetItemChildren ( item ) ;
264262 if ( itemChildren . Any ( ) )
265263 {
266- var foreachExpression = ( ( IValueBinding ) ItemChildrenBinding !
264+ var foreachExpression = serverOnly ? null : ( ( IValueBinding ) ItemChildrenBinding
265+ . NotNull ( "ItemChildrenBinding property is required" )
267266 . GetProperty < DataSourceAccessBinding > ( )
268267 . Binding )
269268 . GetParametrizedKnockoutExpression ( dataItem )
@@ -349,6 +348,36 @@ private IEnumerable<object> GetItemChildren(object item)
349348 return ItemChildrenBinding ! . Evaluate ( tempContainer ) ?? Enumerable . Empty < object > ( ) ;
350349 }
351350
351+ [ ControlUsageValidator ]
352+ public static IEnumerable < ControlUsageError > ValidateUsage ( ResolvedControl control )
353+ {
354+ if ( ! control . TryGetProperty ( DataSourceProperty , out var dataSource ) )
355+ {
356+ yield return new ( "DataSource is required on HierarchyRepeater" ) ;
357+ yield break ;
358+ }
359+ if ( dataSource is not ResolvedPropertyBinding { Binding : var dataSourceBinding } )
360+ {
361+ yield return new ( "HierarchyRepeater.DataSource must be a binding" ) ;
362+ yield break ;
363+ }
364+ if ( ! control . TryGetProperty ( ItemChildrenBindingProperty , out var itemChildren ) ||
365+ itemChildren is not ResolvedPropertyBinding { Binding : var itemChildrenBinding } )
366+ {
367+ yield break ;
368+ }
369+
370+ if ( dataSourceBinding . ParserOptions . BindingType != itemChildrenBinding . ParserOptions . BindingType )
371+ {
372+ yield return new (
373+ "HierarchyRepeater.DataSource and HierarchyRepeater.ItemChildrenBinding must have the same binding type, use `value` or `resource` binding for both properties." ,
374+ dataSourceBinding . DothtmlNode ,
375+ itemChildrenBinding . DothtmlNode
376+ ) ;
377+ }
378+ }
379+
380+
352381 /// <summary>
353382 /// An internal control for a level of the <see cref="HierarchyRepeater"/> that renders
354383 /// the appropriate foreach binding.
0 commit comments