Aspnetcore: Componentes basados ​​en plantillas

Creado en 27 mar. 2018  ·  60Comentarios  ·  Fuente: dotnet/aspnetcore

Por ejemplo, debería poder crear un componente Grid o ListView que se vincule a cada elemento de una colección mediante una plantilla de definición.

area-blazor

Comentario más útil

Mi opinión personal es que Razor debería ceñirse lo más posible a los modismos HTML, en lugar de desplazarse hacia los modismos que vemos en XAML. Eso significaría parecerse más a la jerarquía de elementos que ve en <table> lugar de cosas típicas de XAML. Podríamos ser lo suficientemente genéricos para apoyar a cualquiera de los dos, pero todavía estoy muy firmemente en el campo de "Razor es HTML" en este momento.

Todos 60 comentarios

Específicamente, esto significa que necesitamos alguna forma de indicar que una parte determinada del contenido secundario dentro de un componente debe compilarse como Action<RenderTreeBuilder, T> , para algún tipo T que necesitamos poder resolver dinámicamente desde el uso en el diseño. tiempo. Entonces, nuestro codegen en tiempo de diseño podrá producir el contexto intellisense correcto al escribir expresiones de Razor dentro de ese elemento hijo.

Ejemplo:

<MyListWithHeader RowItems=@People>
    <Title>
        Here I'm still in the outer context so can reference properties on the component itself 
        such as <strong i="8">@TitleText</strong>
    </Title>
    <RowTemplate> <!-- Somehow need intellisense to know that RowTemplate is meaningful here, and that its "item" value is taken from the generic type arg on the RowItems property -->
        Here I'm in the context of an Action<RenderTreeBuilder, Person> because we supplied
        an IEnumerable<Person> as the value for RowItems, so I can write @item.FirstName
    </RowTemplate>
</MyListWithHeader>

<strong i="9">@functions</strong>
{
    string TitleText { get; set; }
    IEnumerable<Person> People { get; set; }

    class Person
    {
        public string FirstName { get; set; }
    }
}

No estoy seguro de qué tan general queremos hacer esta función. Posiblemente menos general que lo indicado en el esquema anterior. @rynowak sería el experto en lo que es razonable implementar aquí. Toda la complejidad está en el momento del diseño / compilación. No requiere ningún soporte de tiempo de ejecución que se me ocurra.

Hola, intenté lograr esto pasando un IEnumerablecomo parámetro, pude producir una cuadrícula pero no pude actualizarla cuando el parámetro cambió. Este es el código:

<table class="@ClassName">
  <tr>
    <strong i="7">@foreach</strong> (var col in Columns)
    {
      <th>@col</th>
    }
  </tr>

  <strong i="8">@foreach</strong> (System.Data.DataRow row in table.Rows)
  {
    <tr>
      <strong i="9">@foreach</strong> (var col in Columns)
      {
        <td>@row[col]</td>
      }
      <strong i="10">@if</strong> (Select != null)
      {
        <td><a @onclick(() => Select(row))>Seleccionar</a></td>
      }
    </tr>
  }
</table>

@functions{
    public string ClassName { get; set; }
    public List<string> Columns { get; set; }
    public object DataSource { get; set; }
    private System.Data.DataTable table;


    private string Log;

    //call backs it seens I will have many of them

    public Action Change { get; set; }
    public Action<object> Select { get; set; }

    protected void UpdateState()
    {
      int count = ((IEnumerable<object>)DataSource).Count();
      Log = $"Table count rows:{table.Rows.Count} count object ${count}";
      StateHasChanged();
    }

    protected override void OnInit()
    {
      base.OnInit();

      Change += StateHasChanged;

      var enumerable = (IEnumerable<object>)DataSource;
      table = new System.Data.DataTable();

      if (Columns == null)
      {
        Columns = new List<string>();
        Columns.AddRange(enumerable.FirstOrDefault().GetType().GetProperties().Select(x => x.Name).ToList());
      }


      Columns.ForEach(x =>
      {
        table.Columns.Add(x);
      });

      enumerable.ToList()
                .ForEach(x =>
                {
                  var row = table.NewRow();

                  foreach (var property in x.GetType().GetProperties())
                  {
                    row[property.Name] = property.GetValue(x);
                  }

                  table.Rows.Add(row);
                });

      StateHasChanged();
    }
}

@SteveSandersonMS , ¿ Title y RowTemplate en su ejemplo están destinados a ser propiedades de MyListWithHeader o componentes especiales? Si son propiedades, valdría la pena pensar en formas alternativas de expresar esto ...

Trabajo mucho con XAML y cuando comencé a crear prototipos con Blazor, rápidamente sentí la necesidad de esta función. ¿Crees que la sintaxis del elemento de propiedad de XAML podría ser razonable en este escenario? Si bien es bastante detallado, expresa claramente que estamos asignando propiedades aquí en lugar de agregar componentes secundarios a MyListWithHeader . Ejemplo:

<MyListWithHeader>
    <MyListWithHeader.Title>
       ...
    </MyListWithHeader.Title>
    <MyListWithHeader.RowTemplate>
       ...
    </MyListWithHeader.RowTemplate>
</MyListWithHeader>

Mi opinión personal es que Razor debería ceñirse lo más posible a los modismos HTML, en lugar de desplazarse hacia los modismos que vemos en XAML. Eso significaría parecerse más a la jerarquía de elementos que ve en <table> lugar de cosas típicas de XAML. Podríamos ser lo suficientemente genéricos para apoyar a cualquiera de los dos, pero todavía estoy muy firmemente en el campo de "Razor es HTML" en este momento.

totalmente en apoyo de esto. si podemos hacer la lista del elemento "ObservableCollection" en lugar de un "IEnumerable", sería una ventaja. avanzar hacia xaml como pattrens nos da un sistema de plantillas adecuado, del cual carece html, todos los frameworks populares en la interfaz de usuario web tienen un sistema de plantillas, lo que los hace muy extensibles, obviamente blazor debería tener uno también, y en lugar de hacer uno nuevo Sería genial tener uno que ya sea amado por MS y muchos desarrolladores de .Net también, técnicamente todavía estará basado en html / css, por lo que todos los desarrolladores de html aún se sentirán atraídos por la tecnología porque no será xaml (no sé por qué html guyx odia xaml) pero solo seguirá los patrones de xaml.

@DamianEdwards Estoy totalmente de acuerdo con usted con respecto a "Razor es HTML". Sin embargo, los valores de atributo en HTML siempre se especifican como atributos en la etiqueta de apertura. No hay forma de (ni es necesario) expresar valores de atributos complejos como plantillas como elementos secundarios. Por ejemplo, no puedes escribir

<a>
    <href>http://dot.net</href>
</a>

Así que creo que lo que queremos técnicamente no se puede expresar en HTML puro.
Aquí hay un resumen de las opciones que veo actualmente:

  1. Cíñete a la sintaxis HTML : los valores de propiedad solo se pueden especificar en la etiqueta de apertura de un componente.

    • Creo que eso es lo que optó por React, ejemplo:
      <MyListWithHeader RowTemplate={(data) => <p>...</p>} />
    • Pro : muy parecido a la sintaxis HTML normal
    • Desventaja : se vuelve complicado para valores de varias líneas, es decir, plantillas más complejas
  2. Cíñete a la sintaxis HTML : según el contexto, los nodos secundarios de un componente pueden representar elementos anidados o asignaciones de propiedades.

    • Pro : muy parecido a la sintaxis HTML normal
    • Con : Puede resultar confuso ya que las asignaciones de propiedades y los elementos secundarios son sintácticamente indistinguibles. En el ejemplo, <Title>...</Title> podría ser un componente independiente, mientras que <RowTemplate>...</RowTemplate> podría ser una propiedad (un atributo) del <MyListWithHeader> circundante; sin IntelliSense, realmente no lo sabe
    • Desventaja : puede causar conflictos de nombres si hay un componente y una propiedad con el mismo nombre
  3. Invente una nueva sintaxis : la <strong i="39">@functions</strong> { } o algo totalmente diferente.

    • Ventaja : permite plantillas arbitrariamente complejas sin dejar de ser legible
    • Con : podría alienar a los desarrolladores web

Es totalmente posible que esté malinterpretando algo aquí. Si es así, por favor hágamelo saber.

@SvenEV No creo que la sintaxis xaml alejará a nadie, si te das cuenta, la sintaxis de la plantilla para angular, vue, react y millones de otros marcos, todos tienen diferentes formas y diferentes sintaxis para hacer las cosas, todavía todos son amados, ¿NO? De manera similar, blazor puede tener su propia sintaxis, el único profesional en su caso será que no inventaremos una nueva sintaxis solo trayendo una sintaxis ya popular a este marco (por lo tanto, menos curva de aprendizaje para los desarrolladores de xaml) pero en lo que respecta a los desarrolladores web, aprenderá las plantillas de Blazor de todos modos, ya sea que Blazor siga xaml o no, la curva de aprendizaje para los desarrolladores web seguirá siendo la misma, la única forma en que los desarrolladores web se mantendrán alejados de este inconveniente es si Blazor usa html directamente, que no creo que sea tan extensible como un modelo de plantilla. :)

por lo que una sintaxis de plantilla totalmente nueva brinda una curva de aprendizaje a cada desarrollador. pero una sintaxis similar a xaml resuelve al menos el problema de la mitad de los desarrolladores. pero xaml también se parece mucho a html, ambos son marcados después de todo. una vez que lo aprendas, será pan comido, lo bueno es que abt xaml es, no tienes que preocuparte en absoluto por css, todo el estilo se realiza con propiedades directas en los controles. e incluso compila el estilo también hecho con la misma sintaxis, pero por otro lado html y css son lenguajes totalmente diferentes con diferente sintaxis y estructura de hacer las cosas.

Pero, ¿por qué necesitamos plantillas? Me refiero a cuando puedes hacer foreach allí mismo en Razor. Me parece mucho más fácil que hacer plantillas.
¿O se debe a la reutilización? Puedo imaginar un componente separado que lo haga para cada uno en ese caso.

Las plantillas de

Bueno, no sé nada de esto. IMO foreach es una plantilla, simplemente una más poderosa y puede colocar una plantilla "foreach" en un componente diferente y llamarlo desde cualquier lugar con esas dos líneas de código.

foreach es solo un bucle y sí, para un control más simple, podemos hacer esto, como para un ListView básico (tabla), simplemente podemos hacer un control que tome algo de entrada, lo haga foreachon y escupe una tabla, y luego solo necesitamos poner una etiquetaalgo como esto y luego podremos producir una tabla, también si queremos una vista de lista personalizada, con un diseño muy moderno, altamente interactivo, etc., entonces podemos hacer otro control de componente con foreach, y mucho cutom css, y mucho de código blazor en un archivo, tal vez varios archivos, y envuelva todo eso en 1 componente, y luego vuelva a usar todo eso solo con 1 etiqueta. ¿Ves a dónde estoy llegando con esto? no importa cuánto código tenga un control, todo eso se puede envolver en 1 etiqueta :) @MihaMarkic

No se trata solo de renderizar varios elementos con la misma plantilla. También es necesario para personalizar varias partes de un componente. Actualmente, un componente solo puede tener uno de esos "huecos" en su diseño que se puede llenar con ChildContent , una propiedad predefinida, como esta:

<div style="some fancy styling">
    <strong i="7">@ChildContent</strong>
</div>

Ahora imagine que desea escribir un componente con múltiples "agujeros" como un cuadro de diálogo que le permite personalizar el contenido y el encabezado, o un botón donde puede personalizar el contenido interno, pero también especificar plantillas para el menú contextual y la información sobre herramientas. quizás. Entonces sería bueno si pudiera escribir algo como lo siguiente (cualquiera que sea la sintaxis entonces):

<MyButton>
    <h2>I am the button caption (the usual ChildContent)</h2>

    <MyButton.ContextMenu>
        <button>Context menu button 1</button>
        <button>Context menu button 2</button>
    </MyButton.ContextMenu>

    <MyButton.ToolTip>
        This explains <strong>what the button does</strong>. It is displayed when hovering over the button.
    </MyButton.ToolTip>
</MyButton>

@SvenEV ¿No es eso solo tener múltiples agujeros y no requerir estrictamente una plantilla?

sí, este es un buen ejemplo de otro caso de uso, cada parte debe ser personalizable.

@MihaMarkic Es cierto, estamos hablando de dos cosas diferentes aquí:

  1. Soporte para múltiples agujeros ( RenderFragment s / Action<RenderTreeBuilder> ) como en mi ejemplo
  2. Soporte para plantillas que tienen entrada de datos ( Action<RenderTreeBuilder, T> ) como en ListView, etc.

Creo que el primero es un requisito para el segundo y espero que ambas funciones se implementen en algún momento. Eso nos permitiría construir algunas poderosas bibliotecas de componentes.

¿Qué problema está tratando de resolver? Me pregunto, ya que no he visto este tipo de cosas en otros marcos de SPA.

¿Entra en conflicto con una estructura basada en componentes? Además, ¿va en contra de las prácticas normales de desarrollo web solo para forzar una estructura más "similar a una aplicación"?

@AWulkan no va en contra de la estructura normal de componentes, pero sí, va ligeramente hacia una estructura similar a la de desarrollo de aplicaciones, pero no significa que se aleje del desarrollo web, porque todos los marcos populares también están haciendo lo mismo, todo el propósito del concepto de SPA fue este. SPA se creó para hacer que el desarrollo web se pareciera más al desarrollo de aplicaciones. y ahora todos los marcos siguen a spa. el siguiente paso del spa es ahora pwa. ¿Sabes qué es un pwa correcto? presenta algunas características importantes de las aplicaciones nativas, como notificaciones y sincronización fuera de línea, por lo que, en última instancia, el desarrollador web en su conjunto está tratando de seguir el desarrollo de la aplicación nativa.

@AWulkan Como parece que está familiarizado con Vue, solo miré los documentos de Vue para ver si hay algo similar. Hay y se llama " Tragamonedas ", en particular "Tragamonedas con nombre" y "Tragamonedas con ámbito"; de eso se trata exactamente este problema. Es el mismo concepto. ¿Quizás esto te ayude a entender el problema?

@SvenEV Okey, entiendo un poco a qué te refieres. Es una característica bastante de nicho en lo que a mí respecta. Gracias por aclararlo.

¿Utilizará este borrador como modelo?
https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md

Parece que las ranuras de Vue.js son similares a las plantillas de datos XAML.
La creación de plantillas ofrece una forma poderosa de personalizar componentes existentes, un caso que pude ver es un ListView en el que, de forma predeterminada, sin plantillas es que simplemente itera sobre la lista de cadenas y la muestra.

<ListView list=<strong i="7">@ListOfUrlsAsStrings</strong> />

Con las plantillas, podría anular eso para decir, mostrar en su lugar una lista de etiquetas de imagen usando el Type T pasado.

<ListView list=@ListOfUrlsAsStrings>
    <ListView.ListTemplate>
         <img src=<strong i="12">@item</strong> alt="overriden by a template" />
        <CustomComponentHere />
    </ListView.ListTemplate>
</ListView>

Esto aumentaría enormemente la reutilización de los componentes, ya que pueden ser dinámicos. Los proveedores de componentes de terceros pueden ser más creativos al crear componentes de esa manera.

sí, exactamente con el enlace de la plantilla de datos se vuelve más simple y observable

Esperamos que los componentes de Blazor obtengan un buen soporte para anidar y crear plantillas.
Anteriormente, tuvimos varias discusiones con @Mehul , @NTaylorMullen , @DamianEdwards sobre el mismo tema para Razor Tag Helpers en las que intentamos explicar por qué necesitamos un enfoque más similar a XAML. Problemas relevantes de GitHub: aspnet / Razor # 474, aspnet / Razor # 576, aspnet / Razor # 570.

Desarrollamos componentes de interfaz de usuario como cuadrículas de datos y el marcado típico para configurar un widget puede ser tan complicado como este:

<TabPanel>
    <Grid>
        <DataSource>
            <More about data source />
        </DataSource>

        <Column />
        <Column>
            <Lookup>
                <DataSource>
            . . .
            <CellTemplate>
                arbitrary html
            </CellTemplate>
        </Column>

        <DetailTemplate>
            <div>arbitrary content</div>
            <ThirdPartyComponent />

            <Grid>
                detail grid configuration with own templates (and there may be more levels)
            </Grid>
        </DetailTemplate>
    </Grid>
</TabPanel>

Para tal jerarquía, un desarrollador querría tener un conocimiento del contexto y una inteligencia precisos.

@AlekseyMartynov ¿podemos tener xaml como estilo + animaciones también a largo plazo?

Estoy muy contento de que esta idea esté ganando terreno. Dado que MS fue pionero en la idea de controles seleccionables con Silverlight, WPF y UWP, tiene sentido hacer que esto sea parte de Blazor.

Una pregunta serían los contextos de datos / códigos. Cuando la plantilla cshtml hace referencia al código en el objeto C # enlazado, probablemente no estará en el mismo contexto que el componente más grande. Entonces, ¿no se requeriría algún tipo de enlace de datos dinámico? ¿O una plantilla tendría un requisito de TargetType estricto para que el analizador sepa cómo resolver referencias a miembros de objetos vinculados a datos?

En esa nota, ¿qué pasa con el enlace de datos dinámicos y las propiedades de dependencia? Por lo que puedo decir, no habría nada que lo impidiera en este momento. ¿O la reflexión será problemática si C # o IL terminan compilándose en wasm en lugar de ser interpretados por mono?

Implementar lo que sugiere @AlekseyMartynov (o una variación de algún tipo) es, en mi opinión, fundamental para el éxito de Blazor. En la práctica, muchos desarrolladores que escriben aplicaciones empresariales dependen de un amplio conjunto de componentes de terceros fáciles de usar y espero que este proyecto llegue muy pronto al punto en que diferentes proveedores puedan comenzar a desarrollar componentes Blazor.

En mi proyecto Xamzor , he implementado un soporte de plantillas limitado como este:

<ListView ItemTemplate="typeof(MyCustomTemplate)" ... />

donde MyCustomTemplate es otro componente de Blazor con una propiedad "DataContext" que es asignada por ListView. Si bien esto funciona bastante bien, aún es deseable definir plantillas en línea en lugar de en un componente separado, especialmente si las plantillas son pequeñas y simples.

Tal vez los componentes contextuales de ember.js puedan proporcionar algo de inspiración: https://www.mutuallyhuman.com/blog/2016/09/23/creating-an-accordion-with-contextual-components-in-ember

@SvenEV Estoy de acuerdo en que, en el caso de plantillas simples, las plantillas en línea serían útiles, pero aún deberíamos tener la opción de plantillas de componentes separadas como las que implementó en su proyecto, porque eso nos ayuda a separar las preocupaciones y la reutilización de la plantilla :)

Tres cosas en mi cabeza:

  1. Haga que los componentes sean jerárquicos de forma nativa (por ejemplo, la propiedad ParentComponentType; para que pueda hacer cumplir MyTableComponent -> MyRowComponent -> MyColumnComponent). Si ParentComponentType es nulo, el componente puede ser hijo de cualquier tipo de componente (por ejemplo, MyTableComponent.ParentComponentType puede ser nulo).

La jerarquía se aplica a través de declaraciones en las definiciones de los componentes como:

`` C #
// En MyRowComponent
Escriba ParentComponentType {get; } = typeof (MyTableComponent);
...

// In MyColumnComponent
Type ParentComponentType {get; } = typeof(MyRowComponent); 
...
Thus, MyColumnComponent can only be created within a MyRowComponent, and MyRowComponent can only be created within a MyTableComponent.

2. Allow components to be extended via ITemplateable interface. When the ITemplateable.Template [Tag] property is valued, it overrides the component's default rendering. Internally, it's something like:

```C#
    if (component is ITemplateable)
    {
        if (component.Template != null)
        {
            … return template rendering ...
        }
    }
    … return default rendering ...
  1. Cree una interfaz ITemplate que se pueda implementar como una clase base opcional BlazorTemplate, que se puede utilizar para objetivar la programación con plantillas.

Uso de muestra donde MyColumnComponent es ITemplateable:

    <MyTableComponent>  
        <MyRowComponent>  
            <MyColumnComponent>  
                <Template>  
                    …  template codes here; can contain a call to another component ...
                </Template>  
            </MyColumnComponent>  
        </MyRowComponent>  
    </MyTableComponent>  

Con MyColumnComponent definido como ITemplateable, admite contener una etiqueta <Template> … </Template> . Las entradas de esta etiqueta anulan la representación predeterminada de MyColumnComponent.

Internamente, MyColumnComponent puede tener / usar una implementación BlazorTemplate para permitirle manejar programáticamente comportamientos, eventos y características especiales de la plantilla.

Estoy votando por la sintaxis del elemento de propiedad

<Button>
  <Button.ContextMenu>
    <ContextMenu>
      <MenuItem Header="1">First item</MenuItem>
      <MenuItem Header="2">Second item</MenuItem>
    </ContextMenu>
  </Button.ContextMenu>

  Right-click me!
</Button>

Como alternativa a

<Button ContextMenu=@GetContextMenuRenderFragment()>
  Right-click me!
</Button>

Me gusta, pero los puristas de la web, ¿no mirarían esto demasiado como XAML?

No, estarán bien, property element syntax es una alternativa a html attribute syntax (sin reemplazo). Pero estoy saliendo de la cantidad de posibilidades que ofrece para los desarrolladores de componentes (UI controla a los desarrolladores de bibliotecas)

También me gusta, ya que transmite más claramente cómo se relaciona el niño con el padre, esto es especialmente bueno en la creación de plantillas, ya que podría distinguir que es una plantilla de datos de un padre en lugar de un componente personalizado que no tiene relación con el componente principal.

Como esto:

<List>
    <List.DataTemplate>
      <!-- Template Here -->
    </List.DataTemplate>
    <ComponentWithNoRelationToTheList />
</List>

Esto le da una idea de que DataTemplate está relacionado con List que con una plantilla personalizada en general. Aunque estoy de acuerdo, como XAML, debería ser solo una alternativa.

¿Qué pasa si "Plantilla" es una palabra clave reservada / especial para cualquier componente de Blazor que se puede adaptar a la plantilla? Cuando se usa como etiqueta, puede contener los códigos de plantilla. Cuando se utiliza como atributo, puede especificar una plantilla con nombre, que puede contener los códigos de la plantilla.

entonces, imaginemos un componente como:

ComponentTemplateTest.cshtml

<strong i="7">@functions</strong>
{
  [Parameter]
  public RenderFragment Template { get; set; }

  [Parameter]
  public RenderFragment<int> TemplateWithInt { get; set; }

  [Parameter]
  public RenderFragment<TestObject> TemplateWithObject { get; set; }

  public class TestObject
  {
      public string PropString { get; set; }
      public int PropInt { get; set; }
  }

  private TestObject testobject = new TestObject();
}

<div>
    <div><button onclick="@((x) => this.testobject.PropInt++)">test</button></div>
    <div>@Template</div>
    <div>@((RenderFragment)(x => TemplateWithInt(x, testobject.PropInt)))</div>
    <div>@((RenderFragment)(x => TemplateWithObject(x, testobject)))</div>
</div>

podemos usarlo como

<ComponentTemplateTest>
    <Template>
        test simple template
    </Template>

    <TemplateWithInt params="param1">
        <strong i="11">@param1</strong>
    </TemplateWithInt>

    <TemplateWithObject params="paramObject">
        @paramObject.PropInt
    </TemplateWithObject>
</ComponentTemplateTest>

necesitamos un atributo como params porque Blazor escribe el código como

            builder.OpenComponent<StandaloneApp.ComponentTemplateTest>(16);
            builder.AddAttribute(17, "template", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
                builder2.AddContent(18, "\n        test simple template\n    ");
            }
            ));
            builder.AddAttribute(19, "templatewithint", (Microsoft.AspNetCore.Blazor.RenderFragment<System.Int32>)((builder2, param1) => {
                builder2.AddContent(20, param1);
            }
            ));
            builder.AddAttribute(21, "templatewithobject", (Microsoft.AspNetCore.Blazor.RenderFragment<StandaloneApp.ComponentTemplateTest.TestObject>)((builder2, paramObject) => {
                builder2.AddContent(22, "\n        ");
                builder2.AddContent(23, paramObject.PropInt);
                builder2.AddContent(24, "\n    ");
            }
            ));
            builder.CloseComponent();

sería suficiente? ¿Algo falta?

@uazo Solo tenga en cuenta que sin la sintaxis del elemento de propiedad es imposible distinguir entre property y component .

@danieldegtyarev Creo que estamos hablando aquí de dos cosas diferentes ... mi objetivo es solo una plantilla para componentes por ahora ...

echa un vistazo a https://github.com/uazo/Blazor/tree/dev-experimental-templated-component
necesitas construir e instalar esa extensión ...

La "plantilla" utilizada como etiqueta es declarativa y, naturalmente, está integrada:

<MyCom MyComProp="MyComPropValue">
    <Template>
        Components, @MyCom.MyComProp, HTML, content and <strong i="6">@values</strong>
    </Template>
</MyCom>

"Plantilla" utilizada como etiqueta para asignar una "plantilla con nombre". Internamente, la plantilla nombrada tendrá acceso a su componente principal, por lo que algo como MyCom.MyComProp también es posible:

<MyCom MyComProp="MyComPropValue">
    <Template Name="MyTemplate {Content: @content; Values: @values}" />
</MyCom>

"Plantilla" se utiliza como atributo para asignar una "plantilla con nombre". Internamente, la plantilla nombrada tendrá acceso a su componente principal, por lo que algo como MyCom.MyComProp también es posible:

<MyCom 
    MyComProp="MyComPropValue"
    Template="MyTemplate {Content: @content; Values: @values}" />

La "Plantilla" utilizada como etiqueta o como atributo debería resultar en la misma compilación, de modo que el componente representaría la información de la plantilla proporcionada, que simplemente anula la representación predeterminada del componente.

Una "plantilla con nombre" es una plantilla reutilizable. Crear una "plantilla con nombre" es casi como crear un componente. Sin embargo, la gran diferencia es que una plantilla con nombre solo se puede utilizar a través del soporte de un componente para "Plantilla".

Una plantilla puede contener componentes, pero es necesario realizar comprobaciones para evitar posibles bucles infinitos cuando, por ejemplo, un componente que utiliza una plantilla que utiliza el mismo componente está codificado ... ¿error de diseño / tiempo de compilación, tal vez?

Preguntas: ¿Puede una plantilla con nombre heredar de otra plantilla? ¿Se puede restringir el uso de una plantilla con nombre solo con tipos de componentes específicos?

Creo que ustedes deberían optar por la sintaxis HTML tanto como sea posible, no introduzcan cosas nuevas. Sin sintaxis xaml de Xamarin o componentes feos de formularios web asp.net (repetidor asp, etc.).
Soy del mundo asp.net mvc, recuerdo la alegría y la libertad cuando cambié de "asp.net webforms" a "asp.net mvc", no más componentes extraños como asp: repetidor y así sucesivamente.
Por favor, no permita que BLAZOR se convierta en un nuevo "formulario web asp.net".

Estoy de acuerdo con @GoranHalvarsson . La creación de plantillas en Blazor debería ser solo la implementación de RenderFragment genérico, con el mismo caso de uso (primer ejemplo en la publicación de

Para eso, actualicé mi rama, ahora con soporte completo para intellisense en visual studio (params y niño permitido).

¿Alguien puede darme algunos comentarios?

@GoranHalvarsson Estoy de acuerdo. El MVP puede ser para que los componentes de Blazor tengan soporte de plantilla básica. En este sentido, una "Plantilla" puede contener HTML, componentes, contenido y códigos Razor. En su forma más básica, los componentes y las plantillas de Blazor deberían seguir siendo códigos HTML + Razor reconocibles. La sintaxis declarativa se ajusta a esta factura, que puede ser la palabra clave <Template> o @template , por ejemplo.

Un elemento de la siguiente fase pueden ser las API / bibliotecas de extensibilidad de Blazor que permiten a los creadores de componentes promover su propia marca, por ejemplo: sintaxis similar a HTML helper (Progress Telerik / Kendo UI) o sintaxis similar a Xamarin / XAML (ooui.wasm, Platform .Uno) pueden ser entregas a nivel de terceros / proveedores. Dentro de Blazor, la flexibilidad de la programación y la compilación de plantillas se puede trabajar en estrecha colaboración con los socios creadores de componentes para abordar problemas / necesidades / requisitos específicos.

Hay una sugerencia en este hilo sobre las plantillas reutilizables. Las plantillas reutilizables o "plantillas con nombre" pueden promover la reutilización del código. Aunque, sinceramente, no le veo mucho valor y puede ser de baja prioridad. Lo más probable es que, si está destinado a ser reutilizado, ¿quizás debería ser un componente? ;-)

@uazo En mi <Template> parece anular la naturaleza declarativa de simplemente usar el parámetro / valor directamente dentro de la etiqueta <Template> .

También tengo dudas sobre permitir que los componentes admitan múltiples propiedades de plantilla. En este sentido, haciéndolo demasiado freestyle. Creo que Blazor debería tener una forma más simple y estándar de admitir plantillas de componentes, tal vez a través de la interfaz ITemplate y algunos ajustes del compilador aquí y allá.

@SteveSandersonMS para https://github.com/aspnet/Blazor/issues/404#issuecomment -377229910
con aspnet / AspNetCore # 15829 el código será

MyListWithHeader.cshtml

<strong i="10">@functions</strong>
{
    [Parameter]
    public RenderFragment Title { get; set; }

// NOT MANDATORY
    [Parameter]
    public RenderFragment ChildContent { get; set; }
// *******************

    [Parameter]
    public IEnumerable<example.Person> RowItems { get; set; }

    [Parameter]
    public RenderFragment<example.Person> RowTemplate { get; set; }
}

<div>
    <strong i="11">@Title</strong>
</div>

<strong i="12">@foreach</strong> (var item in RowItems)
{
    <div>
    @RowTemplate.Render(item)
    </div>
}

<strong i="13">@ChildContent</strong>

ejemplo.cshtml

<strong i="17">@page</strong> "/component-test"

<MyListWithHeader RowItems=@People>
    <MyListWithHeader.Title>
        Here I'm still in the outer context so can reference properties on the component itself
        such as <strong i="18">@TitleText</strong>
    </MyListWithHeader.Title>

    <MyListWithHeader.RowTemplate WithParams="item">
        <!-- Somehow need intellisense to know that RowTemplate is meaningful here, and that its "item" value is taken from the generic type arg on the RowItems property -->
        Here I'm in the context of an Action RenderTreeBuilder, Person
        because we supplied
        an IEnumerable Person as the value for RowItems, so I can write <b>@item.FirstName</b>
    </MyListWithHeader.RowTemplate>
</MyListWithHeader>

<strong i="19">@functions</strong>
{
  string TitleText { get; set; }
  IEnumerable<Person> People { get; set; }

  protected override void OnInit()
  {
      var people = new List<Person>();
      people.Add(new Person { FirstName = "uno" });
      people.Add(new Person { FirstName = "due" });
      people.Add(new Person { FirstName = "tre" });

      this.People = people;
  }

  public class Person
  {
      public string FirstName { get; set; }
  }
}

con soporte completo para intellisense!

@uazo ¡ Muy muy bien! Solo hay una cosa: eliminemos WithParams="item" y usemos @item para acceder al 'contexto' de la plantilla

uhm .. pero si item ya existía en el componente example ?
Dado que el nombre de la variable utilizado para acceder al 'contexto' de la plantilla no se puede descubrir, con WithParams , los desarrolladores pueden decidir el nombre de la variable.

¿Está pensando en un nombre predeterminado por cable (como ChildContent ), con o sin WithParams opcional? ¿O una sintaxis como <MyListWithHeader.RowTemplate WithParams=@item> ?

Mirando hacia atrás, un nombre predeterminado no estaría bien para plantillas anidadas ...

Pensemos. La plantilla es la generación de código con un parámetro. La plantilla solo tiene acceso a este elemento. Entonces, si 'reservamos' una variable item , es segura y no puede entrar en conflicto. Las plantillas anidadas tienen su propio alcance y el uso de @item sigue siendo seguro.

Por cierto, parece que esta plantilla: scope debe tener su bloque @functions

tengamos

<MyListWithHeader RowItems=@People>
    <MyListWithHeader.Title>
        ...<strong i="6">@TitleText</strong>
    </MyListWithHeader.Title>

    <MyListWithHeader.RowTemplate WithParams="item">
        ... <b>@item.FirstName</b>

        <MyListWithHeader>
            <MyListWithHeader.RowTemplate WithParams="item">
                @item.FirstName
            </MyListWithHeader.RowTemplate>
        </MyListWithHeader>

    </MyListWithHeader.RowTemplate>
</MyListWithHeader>

será renderizado como

protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
{
    base.BuildRenderTree(builder);
    builder.OpenComponent<StandaloneApp.Pages.testtemplated.MyListWithHeader>(0);
    builder.AddAttribute(1, "RowItems", Microsoft.AspNetCore.Blazor.Components.RuntimeHelpers.TypeCheck<System.Collections.Generic.IEnumerable<StandaloneApp.Pages.testtemplated.example.Person>>(People));
    builder.AddAttribute(2, "Title", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
        builder2.AddContent(3, "\n        Here I\'m still in the outer context so can reference properties on the component itself\n        such as ");
        builder2.AddContent(4, TitleText);
        builder2.AddContent(5, "\n    ");
    }
    ));
    builder.AddAttribute(6, "RowTemplate", (Microsoft.AspNetCore.Blazor.RenderFragment<StandaloneApp.Pages.testtemplated.example.Person>)((builder2, item) => {
        builder2.AddContent(7, "\n        ");
        builder2.AddContent(8, "\n        Here I\'m in the context of an Action RenderTreeBuilder, Person\n        because we supplied\n        an IEnumerable Person as the value for RowItems, so I can write ");
        builder2.OpenElement(9, "b");
        builder2.AddContent(10, item.FirstName);
        builder2.CloseElement();
        builder2.AddContent(11, "\n\n        ");
        builder2.OpenComponent<StandaloneApp.Pages.testtemplated.MyListWithHeader>(12);
// line 43
        builder2.AddAttribute(13, "RowTemplate", (Microsoft.AspNetCore.Blazor.RenderFragment<StandaloneApp.Pages.testtemplated.example.Person>)((builder3, item) => {
            builder3.AddContent(14, "\n                ");
            builder3.AddContent(15, item.FirstName);
            builder3.AddContent(16, "\n            ");
        }
        ));
        builder2.CloseComponent();
        builder2.AddContent(17, "\n\n    ");
    }
    ));
    builder.CloseComponent();
}

con

Error   CS0136  A local or parameter named 'item' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter

StandaloneApp\obj\Debug\netstandard2.0\Pages\testtemplated\example.g.cs line 43

Sí, claro, tienes que resolverlo durante el codegen.
Pero ... creo que la plantilla debe compilarse en un component (posiblemente un componente especial) en lugar de RenderFragment porque necesitamos administrar con su estado, por ejemplo, para invocar StateHasChanged () cuando @item.FirstName muta.

Sí, claro, tienes que resolverlo durante el codegen.

No creo que sea posible.
Durante el codegen (que es modificado por la maquinilla de afeitar) se cruzan todos los nodos, algunos son html, otros son el nodo c #: aquellos con c # estarían mal porque las variables no estarían definidas y por lo tanto el tipo de variable no sería conocido (y necesitamos recuperar una variable de tipo T de RenderFragment<T> , porque sabemos T ) ... así que no sé cuál es el nombre de la variable a usar.
Probablemente podrías buscar esa variable indefinida, pero parece un desastre

Pero ... creo que la plantilla debe compilarse en un componente (posiblemente un componente especial) en lugar de RenderFragment porque necesitamos administrar con su estado, por ejemplo, para invocar StateHasChanged () [email protected] muta.

No creo que sea necesario introducir nada más.
Si necesita un componente, use BlazorComponent , si necesita una "propiedad de plantilla", use RenderFragment<T>
Después de todo, StateHasChanged() para un RenderFragment es invocado por StateHasChanged() del componente subyacente, no necesito forzar la invocación de cambios de ninguna otra manera

Lamento intervenir aquí porque suena como una gran conversación, pero ...

¿No se resolvería esto con un concepto de contexto de datos como WPF / UWP? Luego, en tu plantilla, siempre estarías haciendo referencia a this.DataContext.[Member] .

@ peter0302 A mí personalmente me gusta esta idea ...

El desafío sigue estando fuertemente tipado. Por supuesto, esto se aborda en XAML mediante el enlace que se realiza en tiempo de ejecución. La plantilla de datos aquí probablemente tendría que admitir la escritura a través de genéricos. Pero definitivamente factible.

Por otra parte, también apoyaría un mecanismo de enlace de tiempo de ejecución completamente dinámico.

Puede optar por indicar el tipo de datos de alguna manera a través de una declaración. Algo similar a x:DataType en XAML, aunque inferir que debería ser más ideal y menos propenso a errores.

@uazo ¡ Mi opinión es que se ve muy bien! Aquí están mis pensamientos ...

1) Que una propiedad de tipo RenderFragment se asigne con un nombre calificado de una propiedad en una etiqueta como <MyListWithHeader.Title /> parece bien, pero el manejo rompe mi intuición anterior de que las propiedades con ParameterAttribute están configuradas a través de atributos html. ¿Qué tal tener otro atributo c # para esas propiedades para distinguir ese comportamiento? ¿Como [ContentParameter] ?

2) Que a una propiedad de tipo RenderFragment<Person> se le asigne una etiqueta de propiedad con atributos
<MyListWithHeader.RowTemplate WithParams="item" /> sintió un poco extraño al principio. No es un atributo que establece una propiedad de parámetro, pero es qué nombre de variable usar en la expresión lambda durante la generación de código local para esa plantilla. Aquí nuevamente sería bueno tener otra sintaxis para notar la diferencia. Pero no estoy seguro de cuán creativo puede ser uno para cumplir con las herramientas y el compilador. <MyListWithHeader.RowTemplate params(item) /> ¿quizás?

3) Si quiero mostrar <MyListWithHeader RowItems=<strong i="16">@PeopleOnline</strong> /> y <MyListWithHeader RowItems=<strong i="18">@PeopleOffline</strong> /> , ¿tengo que especificar las plantillas de nuevo? ¿Hay alguna forma de reutilizarlos? Estaba pensando en hacer alguna inicialización basada en modelos a través de este mecanismo, similar a EditorFor con diferentes selectores de propiedades usando las mismas plantillas.

Puede ser un poco arriesgado discutir la implementación cuando soy nuevo en el marco, pero ...

Si opta por tener un atributo de parámetro separado para asignar plantillas como se discutió en el punto 1 anterior, entonces podría agregarle un indicador booleano (predeterminado verdadero) para decir si se debe aplicar un valor predeterminado (si no se proporciona explícitamente). Luego, puede resolver el punto 3 anterior introduciendo una interfaz como IDefaultTemplateProvider que se puede usar para proporcionar definiciones para las etiquetas de propiedad de plantilla por separado. Luego, esos se pueden descubrir a través de la reflexión de la misma manera que descubre IComponente (s) y los usa en los casos en que se establece el indicador de uso predeterminado y el usuario no proporciona uno. Puede permitir que varios componentes de plantilla implementen la interfaz, pero lanzar una excepción inequívoca si se encuentra una etiqueta de propiedad varias veces. De esa manera, puede proporcionar plantillas estandarizadas a un componente genérico específico de una aplicación que se usa varias veces y obtener la resolución en tiempo de diseño, ¿quizás?

Creo que describir mi experiencia de aprendizaje en torno a esto te da una buena base…, y tengo algunas ideas para discutir…

Quiero realizar una configuración de componentes basada en modelos a través de metadatos y archivos de recursos para recuperar texto traducido estático, validación y similares. Estaba pensando que este mecanismo de creación de plantillas podría ser lo que lo haría posible. Para evaluarlo hice una interfaz public interface IRenderFragment<T> { RenderFragment UsingDataItem(T item); } y creé un componente ordinario que también implementa esa interfaz para ser utilizada como plantilla. Luego hizo un componente genérico usándolo así

  <InputField For=@DataSource(Model, x => x.NewTodo) Template="@InputBasic.Template" />

Por lo tanto, de esta manera, el componente InputField tiene información sobre cómo recuperar metadatos, etc. y puede introducirlos en un modelo que se representa en una plantilla que usted proporcione. Funciona muy bien.

En XAML, es el único mecanismo para representar cosas cuando hay un comportamiento circundante implementado en un componente, como en una vista de lista, entonces necesita proporcionar una plantilla de elemento. En Blazor no es necesario, ya que puede usar código con un bucle para tener ese comportamiento circundante. Entonces me di cuenta de que podría haber hecho lo mismo aquí. En su lugar, podría haber recuperado un modelo de metadatos en código y alimentarlo en un componente normal. @{ var inputModel = InputModelHelper.GetFor(ViewModel, x => x.NewTodo); } <InputBasic DataItem="@inputModel"/>

Tal vez sea obvio para algunos ... así que las plantillas no son absolutamente necesarias para mi caso de uso. Pero me hizo preguntarme qué es lo que realmente quiero. ¿Quizás sea más en la línea de inyectar atributos y contenido en elementos existentes? Y tal vez hacerlo al poder definir un atributo personalizado como:

  <label For=”@DataSource(Model, x => x.NewTodo)”></label>
  <input For=”@DataSource(Model, x => x.NewTodo)” />

Para probar si es posible agregar nodos de atributo en un elemento existente, hice una función (devuelve una cadena vacía para ese nodo de contenido) después de la etiqueta abierta <label>@For(x => x.NewTodo)</label> que extiende el proceso de representación agregando nodos de atributo (y un contenido nodo en el caso del elemento de etiqueta) al constructor. (El constructor se capturó en el método BuildRenderTree). El constructor tiene la posición del nodo de etiqueta, por lo que se puede ver fácilmente en su búfer si se ha establecido un atributo y, por lo tanto, este mecanismo no debe anularlo. Realmente funciona. Es un poco más difícil para el elemento de entrada ya que es un elemento vacío (necesita ser reabierto y la función no se puede colocar entre los atributos), pero como prueba de concepto parece funcionar. ¿Cómo se puede respaldar esto? ¿Tiene sentido?

Supongo que esta función de extensión / continuación de renderizado se puede conectar en varios lugares. Para que piense ... tal vez tener la capacidad de inyectar un "RenderElementExtensionHandler" y si un componente tiene dicha propiedad, se compila una llamada en la posición en la que se han agregado todos los atributos y después de cerrar el código generado en aquellos elementos que tienen atributos, como For en este caso.
¿Es esto algo que se puede usar para otras cosas o es un caso marginal? ¿Existen otras ideas para extender el comportamiento a los elementos existentes?

Lo siento si se alargó un poco ...

¿Por qué no renderizar accesorios?

<List RenderHeader={(text) => (<h4>{text}</h4>)}>
    {({ item, index }) => (
        <li value={index} class={item.Type}>
            {item.Content}
        </li>)}
</List>

Hecho:
b2c8b1aec98e65054c590d356fdf15e6a5e6a822
409026c2f41926d85d18ea2abf345453fa083e34
ce11f517d370b501433a62a56fbd4be1704000ad

¿Fue útil esta página
0 / 5 - 0 calificaciones