Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Loading component #21

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions PrettyBlazor.Tests/Loading/LoadingTests.Render.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// ---------------------------------------------------------------
// Copyright (c) Hassan Habib All rights reserved.
// Licensed under the MIT License.
// See License.txt in the project root for license information.
// ---------------------------------------------------------------

using Bunit;
using Bunit.Rendering;
using FluentAssertions;
using System;
using Xunit;

namespace PrettyBlazor.Tests.Iterations
{
public partial class LoadingTests : TestContext
{
[Fact]
public void ShouldHaveDefaultInitParameters()
{
// given . when
var initialConditionComponent = new Loading();

// then
initialConditionComponent.Evaluation.Should().BeNull();
initialConditionComponent.Ready.Should().BeNull();
initialConditionComponent.Pending.Should().BeNull();
initialConditionComponent.Empty.Should().BeNull();
}

[Fact]
public void ShouldRenderPendingComponentIfEvaluationIsNull()
{
// given
var expectedPendingFragment = CreateRenderFragment(typeof(PendingComponent));
var emptyFragment = CreateRenderFragment(typeof(EmptyComponent));
var readyFragment = CreateRenderFragment(typeof(ReadyComponent));

var componentParameters = new ComponentParameter[]
{
ComponentParameter.CreateParameter(
name: nameof(Loading.Evaluation),
value: null),

ComponentParameter.CreateParameter(
name: nameof(Loading.Pending),
value: expectedPendingFragment),

ComponentParameter.CreateParameter(
name: nameof(Loading.Empty),
value: emptyFragment),

ComponentParameter.CreateParameter(
name: nameof(Loading.Ready),
value: readyFragment)
};

// when
this.renderedConditionComponent =
RenderComponent<Loading>(componentParameters);

// then
this.renderedConditionComponent.Instance.Evaluation
.Should().BeNull();

this.renderedConditionComponent.Instance.Empty
.Should().BeEquivalentTo(emptyFragment);

this.renderedConditionComponent.Instance.Ready
.Should().BeEquivalentTo(readyFragment);

this.renderedConditionComponent.Instance.Pending
.Should().BeEquivalentTo(expectedPendingFragment);

this.renderedConditionComponent.FindComponent<PendingComponent>().Instance
.Should().NotBeNull();

Assert.Throws<ComponentNotFoundException>(() =>
this.renderedConditionComponent.FindComponent<EmptyComponent>());

Assert.Throws<ComponentNotFoundException>(() =>
this.renderedConditionComponent.FindComponent<ReadyComponent>());
}

[Fact]
public void ShouldRenderReadyComponentIfEvaluationIsNotNull()
{
// given
var expectedReadyFragment = CreateRenderFragment(typeof(ReadyComponent));
var emptyFragment = CreateRenderFragment(typeof(EmptyComponent));
var pendingFragment = CreateRenderFragment(typeof(PendingComponent));

var componentParameters = new ComponentParameter[]
{
ComponentParameter.CreateParameter(
name: nameof(Loading.Evaluation),
value: new { }),

ComponentParameter.CreateParameter(
name: nameof(Loading.Ready),
value: expectedReadyFragment),

ComponentParameter.CreateParameter(
name: nameof(Loading.Pending),
value: pendingFragment),

ComponentParameter.CreateParameter(
name: nameof(Loading.Empty),
value: emptyFragment)
};

// when
this.renderedConditionComponent =
RenderComponent<Loading>(componentParameters);

// then
this.renderedConditionComponent.Instance.Evaluation
.Should().NotBeNull();

this.renderedConditionComponent.Instance.Empty
.Should().BeEquivalentTo(emptyFragment);

this.renderedConditionComponent.Instance.Pending
.Should().BeEquivalentTo(pendingFragment);

this.renderedConditionComponent.Instance.Ready
.Should().BeEquivalentTo(expectedReadyFragment);

this.renderedConditionComponent.FindComponent<ReadyComponent>().Instance
.Should().NotBeNull();

Assert.Throws<ComponentNotFoundException>(() =>
this.renderedConditionComponent.FindComponent<PendingComponent>());

Assert.Throws<ComponentNotFoundException>(() =>
this.renderedConditionComponent.FindComponent<EmptyComponent>());
}

[Fact]
public void ShouldRenderEmptyComponentIfEvaluationIsEmptyCollection()
{
// given
var expectedEmptyFragment = CreateRenderFragment(typeof(EmptyComponent));
var readyFragment = CreateRenderFragment(typeof(ReadyComponent));
var pendingFragment = CreateRenderFragment(typeof(PendingComponent));

var componentParameters = new ComponentParameter[]
{
ComponentParameter.CreateParameter(
name: nameof(Loading.Evaluation),
value: Array.Empty<object>()),

ComponentParameter.CreateParameter(
name: nameof(Loading.Empty),
value: expectedEmptyFragment),

ComponentParameter.CreateParameter(
name: nameof(Loading.Ready),
value: readyFragment),

ComponentParameter.CreateParameter(
name: nameof(Loading.Pending),
value: pendingFragment)
};

// when
this.renderedConditionComponent =
RenderComponent<Loading>(componentParameters);

// then
this.renderedConditionComponent.Instance.Evaluation
.Should().NotBeNull();

this.renderedConditionComponent.Instance.Ready
.Should().BeEquivalentTo(readyFragment);

this.renderedConditionComponent.Instance.Pending
.Should().BeEquivalentTo(pendingFragment);

this.renderedConditionComponent.Instance.Empty
.Should().BeEquivalentTo(expectedEmptyFragment);

this.renderedConditionComponent.FindComponent<EmptyComponent>().Instance
.Should().NotBeNull();

Assert.Throws<ComponentNotFoundException>(() =>
this.renderedConditionComponent.FindComponent<PendingComponent>());

Assert.Throws<ComponentNotFoundException>(() =>
this.renderedConditionComponent.FindComponent<ReadyComponent>());
}
}
}
32 changes: 32 additions & 0 deletions PrettyBlazor.Tests/Loading/LoadingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// ---------------------------------------------------------------
// Copyright (c) Hassan Habib All rights reserved.
// Licensed under the MIT License.
// See License.txt in the project root for license information.
// ---------------------------------------------------------------

using Bunit;
using Microsoft.AspNetCore.Components;
using System;

namespace PrettyBlazor.Tests.Iterations
{
public partial class LoadingTests : TestContext
{
private IRenderedComponent<Loading> renderedConditionComponent;

private static RenderFragment CreateRenderFragment(Type type) => builder =>
{
builder.OpenComponent(sequence: 0, componentType: type);
builder.CloseComponent();
};

public class PendingComponent : ComponentBase
{ }

public class ReadyComponent : ComponentBase
{ }

public class EmptyComponent : ComponentBase
{ }
}
}
24 changes: 24 additions & 0 deletions PrettyBlazor/Loading.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@if (Evaluation is null)
{
@Pending
}
else
{
var (isCollectionType, collectionCount) = Evaluation switch
{
List<object> list => (IsCollectionType: true, Count: list.Count),
object[] array => (IsCollectionType: true, Count: array.Length),
Dictionary<object, object> dictionary => (IsCollectionType: true, Count: dictionary.Count()),
IEnumerable<object> ienumerable => (IsCollectionType: true, Count: ienumerable.Count()),
_ => (IsCollectionType: false, Count: 0)
};

if (isCollectionType && collectionCount == 0)
{
@Empty
}
else
{
@Ready
}
}
25 changes: 25 additions & 0 deletions PrettyBlazor/Loading.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// ---------------------------------------------------------------
// Copyright (c) Hassan Habib All rights reserved.
// Licensed under the MIT License.
// See License.txt in the project root for license information.
// ---------------------------------------------------------------

using Microsoft.AspNetCore.Components;

namespace PrettyBlazor
{
public partial class Loading : ComponentBase
{
[Parameter]
public object Evaluation { get; set; }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this property would rather be named Source. From a language perspective, then you would say you are loading a source.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd agree with that. Sadly, this project is dead.


[Parameter]
public RenderFragment Pending { get; set; }

[Parameter]
public RenderFragment Ready { get; set; }

[Parameter]
public RenderFragment Empty { get; set; }
}
}
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,37 @@ button {
```
In the example above, every single element of type button will have the color red. This is just another form of expressing iterations.

#### Loading Structures
One of the most common practices in UI programming is to show or hide certain elements based on the readiness of a specific piece of data. The "loading component" is responsible for evaluating this data to determine whether it is available and ready to be displayed. Additionally, loading components also allow developers to pass in a list of items and display an empty fragment when the list is empty.
This is commonly seen in Blazor applications, where you'll often find code written in the following way:

```html
@if(isLoading)
{
@:Loading...
}
else if (!isLoading && students.Length == 0)
{
@:No results!
}
else
{
<StudentsComponent Value=students />
}
```
One issue with the current implementation is that it requires the use of multiple technologies and programming/markup languages, making it complex and hard to read. A simpler alternative would be to implement the same concept using a fluent markup expression, such as the following:

```html
<Loading Evaluation="Students">
<Pending>Loading..</Pending>
<Ready>
<StudentsComponent Value=students />
</Ready>
<Empty>No results!</Empty>
</Loading>
```
The code snippet shown above demonstrates how it is possible to express a loading indicator and an empty result view in Blazor in a simpler, more readable and elegant way, without the need for any additional C# code in the markup.

### Unobtrusive C#
Over a decade ago, web engineers introduced the concept of unobtrusive JavaScript. which was mainly around the idea that a web application should have it's CSS, HTML and JavaScript code all separated in their own files without one of them having to be using in the other's files.
This earlier concept has changed a lot since then, web applications have evolved dramatically and it seemed that this concept has become less of a priority in some populator frameworks.
Expand Down