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 ! } ) )
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- this . AppendChildren ( CreateServerLevel ( context , GetIEnumerableFromDataSource ( ) ! ) ) ;
173+ this . AppendChildren ( CreateServerLevel (
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.
@@ -184,7 +191,7 @@ private void SetChildren(IDotvvmRequestContext context, bool renderClientTemplat
184191 Children . Add ( clientRootLevel ) ;
185192 clientRootLevel . AppendChildren ( new HierarchyRepeaterLevel {
186193 IsRoot = true ,
187- ForeachExpression = this . TryGetKnockoutForeachingExpression ( ) ,
194+ ForeachExpression = this . TryGetKnockoutForeachExpression ( ) . NotNull ( ) ,
188195 ItemTemplateId = clientItemTemplateId ,
189196 } ) ;
190197 }
@@ -198,19 +205,9 @@ private void SetChildren(IDotvvmRequestContext context, bool renderClientTemplat
198205 private DotvvmControl CreateServerLevel (
199206 IDotvvmRequestContext context ,
200207 IEnumerable items ,
201- ImmutableArray < int > parentPath = default ,
202- string ? foreachExpression = default )
208+ ImmutableArray < int > parentPath ,
209+ string ? foreachExpression )
203210 {
204- if ( parentPath . IsDefault )
205- {
206- parentPath = ImmutableArray < int > . Empty ;
207- }
208-
209- foreachExpression ??= ( ( IValueBinding ) GetDataSourceBinding ( )
210- . GetProperty < DataSourceAccessBinding > ( )
211- . Binding )
212- . GetKnockoutBindingExpression ( this ) ;
213-
214211 var dataContextLevelWrapper = new HierarchyRepeaterLevel {
215212 ForeachExpression = foreachExpression
216213 } ;
@@ -220,7 +217,7 @@ private DotvvmControl CreateServerLevel(
220217 var index = 0 ;
221218 foreach ( var item in items )
222219 {
223- levelWrapper . AppendChildren ( CreateServerItem ( context , item , parentPath , index ) ) ;
220+ levelWrapper . AppendChildren ( CreateServerItem ( context , item , parentPath , index , foreachExpression is null ) ) ;
224221 index ++ ;
225222 }
226223 return dataContextLevelWrapper ;
@@ -230,10 +227,11 @@ private DotvvmControl CreateServerItem(
230227 IDotvvmRequestContext context ,
231228 object item ,
232229 ImmutableArray < int > parentPath ,
233- int index )
230+ int index ,
231+ bool serverOnly )
234232 {
235233 var itemWrapper = ItemWrapperCapability . GetWrapper ( ) ;
236- var dataItem = new DataItemContainer { DataItemIndex = index } ;
234+ var dataItem = new DataItemContainer { DataItemIndex = index , RenderItemBinding = ! serverOnly } ;
237235 itemWrapper . Children . Add ( dataItem ) ;
238236 dataItem . SetDataContextTypeFromDataSource ( GetDataSourceBinding ( ) ) ;
239237 // NB: the placeholder is needed because during data context resolution DataItemContainers are looked up
@@ -252,7 +250,8 @@ private DotvvmControl CreateServerItem(
252250 var itemChildren = GetItemChildren ( item ) ;
253251 if ( itemChildren . Any ( ) )
254252 {
255- var foreachExpression = ( ( IValueBinding ) ItemChildrenBinding !
253+ var foreachExpression = serverOnly ? null : ( ( IValueBinding ) ItemChildrenBinding
254+ . NotNull ( "ItemChildrenBinding property is required" )
256255 . GetProperty < DataSourceAccessBinding > ( )
257256 . Binding )
258257 . GetParametrizedKnockoutExpression ( dataItem )
@@ -336,6 +335,36 @@ private IEnumerable<object> GetItemChildren(object item)
336335 return ItemChildrenBinding ! . Evaluate ( tempContainer ) ?? Enumerable . Empty < object > ( ) ;
337336 }
338337
338+ [ ControlUsageValidator ]
339+ public static IEnumerable < ControlUsageError > ValidateUsage ( ResolvedControl control )
340+ {
341+ if ( ! control . TryGetProperty ( DataSourceProperty , out var dataSource ) )
342+ {
343+ yield return new ( "DataSource is required on HierarchyRepeater" ) ;
344+ yield break ;
345+ }
346+ if ( dataSource is not ResolvedPropertyBinding { Binding : var dataSourceBinding } )
347+ {
348+ yield return new ( "HierarchyRepeater.DataSource must be a binding" ) ;
349+ yield break ;
350+ }
351+ if ( ! control . TryGetProperty ( ItemChildrenBindingProperty , out var itemChildren ) ||
352+ itemChildren is not ResolvedPropertyBinding { Binding : var itemChildrenBinding } )
353+ {
354+ yield break ;
355+ }
356+
357+ if ( dataSourceBinding . ParserOptions . BindingType != itemChildrenBinding . ParserOptions . BindingType )
358+ {
359+ yield return new (
360+ "HierarchyRepeater.DataSource and HierarchyRepeater.ItemChildrenBinding must have the same binding type, use `value` or `resource` binding for both properties." ,
361+ dataSourceBinding . DothtmlNode ,
362+ itemChildrenBinding . DothtmlNode
363+ ) ;
364+ }
365+ }
366+
367+
339368 /// <summary>
340369 /// An internal control for a level of the <see cref="HierarchyRepeater"/> that renders
341370 /// the appropriate foreach binding.
0 commit comments