Coder Social home page Coder Social logo

hydrostack / hydro Goto Github PK

View Code? Open in Web Editor NEW
685.0 17.0 16.0 1 MB

Hydro brings stateful and reactive components to ASP.NET Core without writing JavaScript

Home Page: https://usehydro.dev

License: MIT License

Batchfile 0.07% C# 88.59% JavaScript 11.33%
asp-net-core asp-net-mvc razor-pages reactive webapp

hydro's Introduction

Hydro


Bring stateful and reactive components to ASP.NET Core without writing JavaScript


Hydro is an extension to ASP.NET Core MVC and Razor Pages. It extends View Components to make them reactive and stateful with ability to communicate with each other without page reloads. As a result, you can create powerful components and make your application to feel like SPA with zero or minimal amount of the JavaScript code (depending on the needs) and without separate front-end build step. It can be used in new or existing ASP.NET Core applications.

Hydro utilizes the following technologies to make it all work:

  • Razor views (*.cshtml)
    Razor views form the backbone of Hydro's UI generation. They allow for a familiar, server-side rendering strategy that has been a foundation of .NET web development for many years. These *.cshtml files enable a seamless mix of HTML and C# code, allowing for robust and dynamic webpage generation.

  • AJAX
    AJAX calls are used to communicate between the client and the server, specifically to send the application state to the server, receive updates and responses back, and then store this state to be used in subsequent requests. This ensures that each request has the most up-to-date context and information.

  • Alpine.js
    Alpine.js stands as a base for requests execution and DOM swapping. But beyond that, Alpine.js also empowers users by providing a framework for adding rich, client-side interactivity to the standard HTML. So, not only does it serve Hydro's internal operations, but it also provides an expansion point for users to enhance their web applications with powerful, interactive experiences.

Documentation

Samples

Installation

In ASP.NET Core Razor Pages / MVC project 6.0+ install Hydro package:

dotnet add package Hydro

If you don't have application yet, you can create it first:

dotnet new webapp -o MyApp
cd MyApp

In your application's startup code (either Program.cs or Startup.cs):

builder.Services.AddHydro();

...

app.UseHydro(builder.Environment);

In _ViewImports.cshtml add:

@addTagHelper *, {Your project assembly name}
@addTagHelper *, Hydro

In layout's head tag:

<meta name="hydro-config" />
<script defer src="~/hydro/hydro.js" asp-append-version="true"></script>
<script defer src="~/hydro/alpine.js" asp-append-version="true"></script>

Quick start

To create Hydro component, go to your components folder, for example in case of Razor Pages: ~/Pages/Components/, and create these files:

<!-- Counter.cshtml -->

@model Counter

<div>
  Count: <strong>@Model.Count</strong>
  <button hydro-on:click="@(() => Model.Add())">
    Add
  </button>
</div>
// Counter.cs

public class Counter : HydroComponent
{
    public int Count { get; set; }
    
    public void Add()
    {
        Count++;
    }
}

Usage

To use your new component, you can render it in your Razor Page (e.g. Index.cshtml) in two ways:

via Hydro tag helper:

...
<hydro name="Counter"/>
...

via ASP.NET Core tag helper:

...
<vc:counter/>
...

or via extension method:

...
@await Component.Hydro("Counter")
...

And voilà! You can test your component by clicking on the Add button.

Apps using Hydro

External libraries

Hydro repository contains Alpine.js libraries MIT licensed.

License

Hydro is Copyright © Krzysztof Jeske and other contributors under the MIT license

hydro's People

Contributors

kjeske avatar taublast avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hydro's Issues

hydro-link issues in .cshtml partials

Sometimes when adding a hydro-link attribute, the link becomes broken with a "/null" path. (ex https://localhost:7231/null when it should be https://localhost:7231/jobs).

The 2 scenarios I've had this happen in are:

  1. An anchor tag in a normal .cshtml partial (not extending HydroComponent). For example, I have a _NavBar.cshtml partial for my Navbar. It's very dumb, just abstracts some markup. The link in this will return https://localhost:7231/null.
<nav class="w-full bg-base-200">
	<div class="py-6 px-8 max-w-7xl mx-auto">
		<div class="flex justify-center items-center">
			<div>
				<a href="/" hydro-link>
					Home
				</a>
			</div>
		</div>
	</div>
</nav>
  1. A for loop in a .cshtml page. I have a page that loops through job openings. Each loop builds an anchor tag for that jobs page. Adding hydro-link will result in https://localhost:7231/null
@foreach (var job in Model.Jobs)
{
  <a href="/jobs/@job.Slug">Link to job</a>
}

Create tutorials section in the documentation

Current documentation shows the methods of using the library, but wider examples of full use cases would be useful.

Areas to start with:

  • Sample folder structure for app using Hydro
  • Real-world example of communication between components
  • Styling good practices working with ASP.NET Core MVC/Razor Pages
  • Using Fluent Validation
  • Trying to solve common UI challenges without JavaScript
    • Dropdowns
    • Dialogs
    • Popups/toasts

InvalidOperationException following the getting started

Trying to follow the getting started.

On a Mac,

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Hydro" Version="0.14.2" />
  </ItemGroup>

</Project>
using Hydro.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddHydro(); // Hydro

var app = builder.Build();

app.UseStaticFiles();
app.UseRouting();
app.MapRazorPages();
app.UseHydro(builder.Environment); // Hydro

app.Run();

The inex page shows below error:

InvalidOperationException: The view 'Components/Counter/Counter.cshtml' was not found. The following locations were searched: /Pages/Counter.cshtml
Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult.EnsureSuccessful(IEnumerable<string> originalLocations)
Microsoft.AspNetCore.Mvc.ViewComponents.ViewViewComponentResult.ExecuteAsync(ViewComponentContext context)
Hydro.HydroComponent.GetComponentHtml()
Hydro.HydroComponent.GetComponentHtml()
Hydro.HydroComponent.GenerateComponentHtml(string componentId, IPersistentState persistentState)
Hydro.HydroComponent.RenderStaticComponent(IPersistentState persistentState)
Hydro.HydroComponent.InvokeAsync(object parameters, string key)
Microsoft.AspNetCore.Mvc.ViewComponents.DefaultViewComponentInvoker.InvokeAsyncCore(ObjectMethodExecutor executor, object component, ViewComponentContext context)
Microsoft.AspNetCore.Mvc.ViewComponents.DefaultViewComponentInvoker.InvokeAsync(ViewComponentContext context)
Microsoft.AspNetCore.Mvc.ViewComponents.DefaultViewComponentInvoker.InvokeAsync(ViewComponentContext context)
Microsoft.AspNetCore.Mvc.ViewComponents.DefaultViewComponentHelper.InvokeCoreAsync(ViewComponentDescriptor descriptor, object arguments)
Hydro.TagHelpers.HydroComponentTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, int i, int count)
MyApp.Pages.Pages_Index.ExecuteAsync() in Index.cshtml
+
    ViewData["Title"] = "Home page";

hydro-link navigation does not reset scroll position on new page

When navigating to a new page with hydro-link, the scroll position remains the same as it was on the previous page, requiring the user to scroll back up to the top of the page.

A better UX would be resetting the scroll position to the top of the new page.

Attached video example of issue.

scroll-position-issue.mp4

Push to windows history state

Hello,
I have a problem that i dont know how to overcome.I have a page that has filters and based on that filters list products and every time a filter is changed i want to update the history of the browser so when the user shares link or refresh the page all the filters will be applied.Right now i can't find a way to execute javascript from .net.I have created a workaround that intercepts the fetch method in javascript and after the request is completed looks for my custom header 'Hydro-Push-History' and pushes history if is present.

Maybe hydro should have some kind of mechanism for executing javascript as part of the request like when you could redirect the user or change the location(but that makes another request).

Maybe Location method should have a parameter that instructs hydro to change browser url but not make a new request.

I can contributed if necessary.

ASP NET MVC parameters compatibility

We are in the context of a .NET 8 MVC app (not Razor Pages).
Failed to pass parameters to components. Imported the Hydro project to debug why.

How do I pass parameters:

@await Component.InvokeAsync("TestComponent", new { TestProperty = true })

HydroComponent method public async Task<IHtmlContent> InvokeAsync(object parameters = null, string key = null) is always receiving parameters as null, whatever.

Once you change its signature to public async Task<IHtmlContent> InvokeAsync(object parameters = null) parameters start to work.
So for me, its .NET MVC Component.InvokeAsync passing parameters to a valid signature only.

I am using a custom build and it looks good so far. What would be the best way to fix this for the library in general?

Fix compiler warning CS4014 related to not awaited action

Let's consider the following async action:

public async Task Save()
{
    // ...
}

And usage in the view:

<button hydro-on:click="@(() => Model.Save())">Save</button>

hydro-on tag helper is expecting Expression<Action> to read the method and params, not to execute, so awaiting this statement would be an overkill. But compiler doesn't know that, and shows a warning:

Warning CS4014 : Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Switch from Expression<Action> to something else, so compiler doesn't treat it as a warning anymore.

Using Hydro-Trigger. The HttpHeader protocol says that only Ansi code is available.

I'm pretty good for your update hydro v0.12.0.
I'm testing hydro-sale's toast using Hydro-Trigger. However, it breaks when I type in Korean as Message. The HttpHeader protocol says that only Ansi code is available.
When I tried to solve this problem, I had to encode and decode it with base64.
Wouldn't it be possible to solve this at once with a request-body or response-body without using a header.

[HydroComponent.cs]

  private void PopulateDispatchers()
  {
      if (!_dispatchEvents.Any())
      {
          return;
      }

      var data = _dispatchEvents
          .Select(e => new { name = e.Name, data = e.Data, scope = e.Scope, operationId = e.OperationId })
          .ToList();

var json = JsonConvert.SerializeObject(data);
var bytes = Encoding.UTF8.GetBytes(json);
var base64Json = Convert.ToBase64String(bytes);

HttpContext.Response.Headers.TryAdd(HydroConsts.ResponseHeaders.Trigger, base64Json);
  }

[Hydro.js]

const triggerHeader = response.headers.get('Hydro-Trigger');
if (triggerHeader) {

    // Base64 to ArrayBuffer
    const binaryString = window.atob(triggerHeader);
    const len = binaryString.length;
    const bytes = new Uint8Array(len);
    for (let i = 0; i < len; i++) {
        bytes[i] = binaryString.charCodeAt(i);
    }

    // decoding
    const decoder = new TextDecoder('utf-8');
    const decodedHeader = decoder.decode(bytes);
    const triggers = JSON.parse(decodedHeader);

    triggers.forEach(trigger => {
        if (trigger.scope === 'parent' && !parentComponent) {
            return;
        }

        const eventScope = trigger.scope === 'parent' ? parentComponent.id : 'global';
        const eventName = `${eventScope}:${trigger.name}`;
        const eventData = {
            detail: {
                data: trigger.data,
                operationId: trigger.operationId
            }
        };

        document.dispatchEvent(new CustomEvent(eventName, eventData));
    });
}

Partial loading replaces whole Body instead of RenderBody() location

As title, would love ajax-loaded content to replace an appropriate location instead of the whole body.
Happens for component updates and Location calls.

This makes it impossible to have _Layout=null for ajax-calls to achieve faster rendering.

Solved in my fork, will make a PR.

Better support for file upload via binding

  • Support for binding file inputs: <input type="file" hydro-bind>
  • Append hydro-request css class on fields using bindings that are currently synching (useful especially when uploading large files via <input type="file" hydro-bind>)

Add a cookie manager for complex objects

Add a functionality to store/read complex objects in cookies.

To implement:

T HydroComponent.Cookies.Get<T>(string key)
HydroComponent.Cookies.Set<T>(string key, T value)

Value will be serialized and encrypted (optionally?)

Issue with ApplyObjectFromDictionary Method Handling Integer Values as Long

There is an issue with the ApplyObjectFromDictionary method where integer values in the IDictionary<string, object> source are being interpreted as long during debugging. This causes an InvalidCastException when attempting to set properties on the target object if the target property is of type int.

This issue is particularly problematic when working with JSON deserialization where numeric values are commonly interpreted as long. The proposed solution ensures compatibility with various numeric types and enhances the robustness of the method.

Example Code

public class TargetClass
{
    public int ExampleProperty { get; set; }
}

var target = new TargetClass();
var source = new Dictionary<string, object>
{
    { "ExampleProperty", 42 } // int value
};

ApplyObjectFromDictionary(target, source); // Exception occurs here

Modified Code

private void ApplyObjectFromDictionary<T>(T target, IDictionary<string, object> source)
{
    if (source == null || target == null)
    {
        return;
    }

    var targetType = target.GetType();

    foreach (var sourceProperty in source)
    {
        var targetProperty = targetType.GetProperty(sourceProperty.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);

        if (targetProperty == null || !targetProperty.CanWrite)
        {
            continue;
        }

        var value = sourceProperty.Value;

        if (value != null)
        {
            // Convert ChangeType
            if (!targetProperty.PropertyType.IsInstanceOfType(value))
            {
                try
                {
                    value = Convert.ChangeType(value, targetProperty.PropertyType);
                }
                catch (Exception ex)
                {
                    throw new InvalidCastException($"Type mismatch in {sourceProperty.Key} parameter: {ex.Message}");
                }
            }
            
            targetProperty.SetValue(target, value);
        }
    }
}

Cannot read multi-part form data (fields and uploads) after submit

The question was raised here how to acquire form content without hydro bind, the actual answer was that it is for now not possible.

For my app that was a stopper, it requires to read multi-part form data: fields along with uploads.

Hydro core is already ready to support that with a really small addition.

Solved this in my fork, will add a PR for the ability to send/read/bind/validate multi-part form data upon submit.

Persist components' state across hot page reloads

When using built-in ASP.NET Core mechanism for hot reloading pages after changing source code, the whole page is being reloaded, therefore all the components loose their state. It's inconvenient when working on some places deep down the component hierarchy that resets every time.

Possible solution would be to add a custom hot-reload mechanism that will send messages via web sockets stream. As a response, browser could send a request to the back-end for the new version of current page, but additionally all the state would be sent together with that request.

To do:

  • Investigate
  • Implement

Make components markup to have a "key" attribute

When morphing, key html attribute is used to identify elements and compare with new version of the document, so elements that are the same, will stay, new will be added, not present will be removed. It's especially useful when looping through collections:

@foreach (var item in Model.List)
{
  <li key="[email protected]">
    @item.Text
  </li>
}

When new item appear, the rest will stay untouched, thanks to key.

Hydro components also use key parameter for similar purpose, when doing the rendering on the server.

Today, when rendering a Hydro component per each collection item, we need to write:

@foreach (var item in Model.List)
{
  <div key="[email protected]">
    <hydro name="Item" key="[email protected]" />
  </div>
}

Hydro component needs a key parameter and a parent element with key attribute. It's an overkill, so the goal is to make it possible to get the same effect without the parent element:

@foreach (var item in Model.List)
{
  <hydro name="Item" key="[email protected]" />
}

Handle JS events on a Hydro component tag

Inside Hydro component view there should be a way to dispatch a JS event and then catch it on the parent component:

Child.cshtml:

<div>
  <input x-on:change="$hydro.dispatch('customchange', $el.target.value)">
</div>

Parent.cshtml:

<div>
  <hydro name="Child" hydro-on:customchange="@(() => Model.UpdateValue(Param.JS('$event.detail')))"/>
</div>

It would be useful when building complex components using JavaScript and wanting them to talk to a parent component, without using extra request to the server just to pass the event.

Another advantage is that when doing a handler on a hydro tag, we know the context of the event, when having the same component with the same handler rendered more than once, so:

<div>
  <hydro name="Child" key="1" hydro-on:customchange="@(() => Model.UpdateValue("1", Param.JS('$event.detail')))"/>
  <hydro name="Child" key="2" hydro-on:customchange="@(() => Model.UpdateValue("2", Param.JS('$event.detail')))"/>
</div>

Support dynamic component generation

Make it possible to automatically produce a Hydro component when an event occurs.

Example:

public Task AddPost()
{
    var post = new Post();
    database.Add(post);
    await database.SaveChanges();

    DispatchGlobal(new PostCreated(PostId: post.Id));
}
public class Post : HydroComponent
{
    public Guid PostId { get; set; }
}
<hydro name="Post" generate-on="typeof(PostCreated)" />

When AddPost action is triggered, it dispatches PostCreated event, which causes a new instance of Post component to render (thanks to generate-on) with the properties taken from the event (PostId).

Support subjects in the events

Possibility to dispatch/subscribe to events with specific subject. In that way one even can be dispatched with different subject, and then one component listens to subject 1, another to subject 2, still operating on the same event type.

Forms not keeping data and refresh by itself

Hello. Very interesting project. Keep up working on it.

I found a bug with forms on latest 0.14.0 release. On the latest release, when you click the submit button on a form, the page the data is lost and the page refresh by itself. I'm using the example with form validation from the documentation like in this example below. Even if i bind the input using hydro-bind the symtoms are the same.

public class FormValid : HydroComponent
{
[Required, MaxLength(50)]
public string? Name { get; set; }
public void Submit()
{
if (string.IsNullOrEmpty(Name) == true)
{
Console.WriteLine("Name is null");
}
Console.WriteLine("A:" + Name);
}
}

@model FormValid
<form hydro-on:submit="@(() => Model.Submit())">
<label asp-for="Name"></label>
<input class="form-control" asp-for="Name" hydro-bind />
<span asp-validation-for="Name"></span>
<button class="btn btn-outline-primary" type="submit">Submit</button>
</form>

This example works ok in 0.11.0.

Navigation initiated in components to "/" results in error

Hi there,

I have a form that on submit, saves some data and then redirects (without page reload) the user to the home page "/".

public void Submit()
{
  if (!Validate())
  {
    return;
  }
  
  var newContact = new Contact();
  newContact.Name = Name;
  newContact.Email = Email;
  Database.Contacts.Add(newContact);

  Location(Url.Page("/"));
}

The location change results in an error. It looks like when passing "" or "/" to the Url.Page method, the resulting path is null. Network request below showing "path": null. This works fine when passing urls that aren't the home page.
image

Few questions - Authentication and using hydro with HTMX

Hi, I just came across Hydro. Looks awesome.
I have been using HTMX + .net MVC for all my front end for over a year now.

A few questions -

  1. Is there a community or discord so I can add discussion items? Or are GitHub issues the right way?
  2. In my SaaS I want all the components and methods to be authenticated. How do you access the current user ID on the action methods in the components? I saw an authentication section on the docs, but did not see any samples around this.
  3. I plan to use this with HTMX - Use HTMX for all the navigation with boosting, cross-component swaps, OOB swaps which I do not see hydro having currently. Are there any issues with using the 2 together?

Thanks in advance.

Isn't there a limit to the length if you send View Component's model through Header as a Post Method?

Isn't there a limit to the length if you send View Component's model through Requet Header as a Post Method?
Is there a reason why you didn't implement it as a Request Body?

I've solved the problem with Transient attributes,
but I think there will be a lot of data from the model. Is there a good solution in this case?

[additional]
I'm testing hydro-sale's toast using Hydro-Trigger. However, it breaks when I type in Korean as Message. The HttpHeader protocol says that only Ansi code is available.
When I tried to solve this problem, I had to encode and decode it with base64.
Wouldn't it be possible to solve this at once with a request-body or response-body without using a header.

  private void PopulateDispatchers()
  {
      if (!_dispatchEvents.Any())
      {
          return;
      }

      var data = _dispatchEvents
          .Select(e => new { name = e.Name, data = e.Data, scope = e.Scope, operationId = e.OperationId })
          .ToList();

var json = JsonConvert.SerializeObject(data);
var bytes = Encoding.UTF8.GetBytes(json);
var base64Json = Convert.ToBase64String(bytes);

HttpContext.Response.Headers.TryAdd(HydroConsts.ResponseHeaders.Trigger, base64Json);
  }
const triggerHeader = response.headers.get('Hydro-Trigger');
if (triggerHeader) {

    // Base64 to ArrayBuffer
    const binaryString = window.atob(triggerHeader);
    const len = binaryString.length;
    const bytes = new Uint8Array(len);
    for (let i = 0; i < len; i++) {
        bytes[i] = binaryString.charCodeAt(i);
    }

    // decoding
    const decoder = new TextDecoder('utf-8');
    const decodedHeader = decoder.decode(bytes);
    const triggers = JSON.parse(decodedHeader);

    triggers.forEach(trigger => {
        if (trigger.scope === 'parent' && !parentComponent) {
            return;
        }

        const eventScope = trigger.scope === 'parent' ? parentComponent.id : 'global';
        const eventName = `${eventScope}:${trigger.name}`;
        const eventData = {
            detail: {
                data: trigger.data,
                operationId: trigger.operationId
            }
        };

        document.dispatchEvent(new CustomEvent(eventName, eventData));
    });
}

child content RenderFragments?

This is just a question. I don't see this in the documentation.
I have a Blazor component like this:

<div class="card m-4 p-2">
    <div class="card-body">
        <h5 class="card-title fw-bold">@Title</h5>
        @ChildContent
    </div>
</div>

@code {
    [Parameter]
    public string Title { get; set; } = string.Empty;

    [Parameter, EditorRequired]
    public RenderFragment ChildContent { get; set; } = default!;
}

Is there a way to pass markup as an argument to hydro component?

Component parameters that are complex objects are not updated when the form submit button is clicked.

This may be a little difficult to explain but hopefully, I'll supply enough code for the problem:

I have the following snippet:

public class EditProfileForm(SessionHandler<SessionState> sessionHandler, 
    IProfileReader profileReader, ILocationReader locationReader,
    IWriter<Profile> writer) : HydroComponent
{

    [Transient]
    public bool ProfileCreated { get; set; }

    [Transient]
    public bool ProfileUpdated { get; set; }
    
    public EditViewModel? Profile { get; set; }
    
}

And a very long form supporting the Profile viewModel.

<form method="post">
    @if (Model.ProfileUpdated)
    {
        <div class="alert alert-success alert-dismissible fade show" role="alert">
            Profile updated
            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
        </div>
    }
    <input type="hidden" asp-for="Profile.Id" />
    <input type="hidden" asp-for="Profile.NameIdentifier"  />

    <div class="row mb-3">
        <label asp-for="Profile.Email" class="col-md-2 col-form-label text-md-end"></label>
        <div class="col-md-6 col-lg-5 col-xl-4 col-xxl-3">
            <input asp-for="Profile.Email" class="form-control" 
                   readonly="@(!string.IsNullOrEmpty(Model.Profile?.Email) ? "readonly" : string.Empty)">
            <span asp-validation-for="Profile.Email" class="text-danger"></span>
        </div>
    </div>
    
    <div class="row">
        <div class="col-md-8 col-lg-7 col-xl-6 col-xxl-5 text-end">
            @if (Model.ProfileExists)
            {
                <button type="submit" class="btn btn-primary" 
                        hydro-on:click="@(() => Model.SaveAsync())">
                    Update Profile
                </button>
            }
            else
            {
                <button type="submit" class="btn btn-primary float-end"
                        hydro-on:click="@(() => Model.SaveAsync())">
                    Create Profile
                </button>
            }
        </div>
    </div>
</form>

Problem is here:

    public async Task SaveAsync()
    {
        if (!Validate() || !ProfileCreationValidation())
            return;

        var nameIdentifierClaim = HttpContext.GetClaim(ClaimTypes.NameIdentifier);

        //if (nameIdentifierClaim is null || !IsUserAuthorized(nameIdentifierClaim))
        //{
        //    ModelState.AddModelError(nameof(nameIdentifierClaim), "Claim cannot be null");
        //}
            

        var profileId = new ProfileId((Profile?.Id).GetValueOrDefault());
        var profile = profileReader
            .FirstOrDefault(p => p.Id == profileId);
            

        profile = BuildProfile(profile!);
        await writer.SaveAsync(profile)
            .ConfigureAwait(false);

        SetProfileState(ProfileExists);
    }

When you come into this save async method, both the ModelState and the Profile viewModel don't show updates. There are no fields in the ModelState, and the viewModel object is unchanged. So do the component parameter sub properties not survive on post submission, or am I missing something? I'm asking in particular because it seems none of the examples show this use case, and I'm wondering if it's supported.

Thank you.

Redirect to Error Page on Authorisation Failure

I have built my own version of the IHydroAuthorizationFilter and attached to to my page which now doesn't render as per the documentation.

What I was wondering though is if there is any way of redirecting the user to a "Permission Denied" page in the application rather than just showing an empty page to enhance the user experience.

Thanks In Advance,

Stuart Ferguson

Component not updating although the DOM upon the return of a response

Hello,

I have encountered an issue for which I don't know if it's a bug or my miscomprehension of the Hydro's concept.

To illustrate the situation, I will use a simple component that displays a list of items:

<div>
    <ul>
        @foreach (var item in Model.Items)
        {
            <li>@item</li>
        }
        
        <button type="button" hydro-on:click="@(()=>Model.Remove())">Remove first</button>
    </ul>
</div>

Then in the model there is a property containing a list of items and a method that removes the first element of the list.

public List<string> Items { get; set; } 

public override void Mount()
{
    Items = ["Item 1", "Item 2", "Item 3"];
}

public void Remove()
{
    Items.RemoveAt(0);
}

When the button is clicked and the Remove method is triggered, I would expect the component to be swapped in the DOM. However, this does not happen although the response contains a new <ul> holding just the Item 2 and Item 3 <li> elements.

Thanks a lot!
Nejc

Use text editor in component

Initialize CKEditor (or another editor) in a component view with JavaScript. It works initially, but when any hydro-bound input changes, the component re-renders and the editor is lost.
How can I use the editor with a component?

validation difficulty

hello! I'm having trouble using form validation. When I submit a form with a require field missing, I get a validation error as expected.

image

Here's my form markup:

<form hydro-on:submit="@(() => Model.SubmitAsync())">		
	<div class="d-flex align-items-start">
		<div class="me-2 w-100">
			<input type="email" asp-for="Email" placeholder="Email address" class="form-control" maxlength="65" hydro-bind />
			<span asp-validation-for="Email" class="text-danger"></span>
		</div>
		<button class="ms-2 btn btn-primary" type="submit">Submit</button>
	</div>
</form>

and relevant part of model class

public class EmailLeads : HydroComponent
{	
	public string? AlertClass { get; set; }
	public string? Message { get; set; }

	[Required(ErrorMessage = "Email is required")]		
	public string? Email { get; set; } = default!;

	public async Task SubmitAsync()
	{
		//if (!ModelState.IsValid) return;
		if (!Validate()) return;

		// stuff happens in db
	}	
}

The problem is after I fill in a value and click Submit, the validation has not refreshed, and Validate returns false.
I put a breakpoint in my Submit method, and I check the value of Email property, and I see it's null. Do I need to put some other binding event on my html input?

image

does binding support 2 way binding ?

Im wondering if binding works boths ways from the ui to the model and the model to the UI. ?
https://usehydro.dev/features/binding.html

My use case is the following :
Say in my component i want to store the object state in a jsonObject called ContentItem .
Using the Binding method i could add custom logic to update the json object and properties based on property name and value .

But if i change the state of the ContentItem variable and its properties inside the class does it update the binding values in the UI ?

Add browser history snapshots

Keep the cached DOM for browser history entries, so when user navigate back or next, the DOM is restored. Since the Hydro components state is kept on the page, their state will be restored as well automatically.

Issue is based on this discussion.

Things to think about

  • There might be situations where you want to clear the cache when you did changes to database and you don't want to restore previous state when going back, as it might be stale

Integrating with orchardcore framework

Interesting project.
Im looking into this project for integrating with an orchardcore module to acheive a similar effect as done with https://github.com/hydrostack/hydro-sales.
So basically the core logic is embedded in a hydro component , based on the docs, that is then rendered on a .cshtml page in razor.
Correct ?
Thinking out loud here . OrchardCore uses the concept of shapes which is similar to a view component or hydro component.
Im wondering if hydro components and shapes could somehow be integrated to acheive the same effect as the hydro sales app within an existing orchardcore module.
Any thoughts ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.