Skip to content
Advertisement

Cannot Move Divs in Blazor

I have a Blazor Server App with the following DIV tags

<div class=mainScreen id="outerBox" style="width:@(TotalWidth)px;height:@(TotalHeight)px;">
    foreach(Device thisDevice in MyDevices)
    {
      <div class=column id="@MainDiv" style="width:@(thisDevice.Width)px;height:@(thisDevice.Height)px;left:@thisDevice.XCoordinate;top:@thisDevice.YCoordinate">
        Main Content Here...
      </div>
    }
    </div>

I attempted to set the Height, Width, and X/Y coordinates using the code samples from this page – https://blazor.tips/blazor-how-to-ready-window-dimensions/ but that never worked and simply threw an uncaught exception no matter where I placed Try… blocks.

I then moved to a more straightforward JS call:

 await Task.Run(async () =>
 {
  //Commenting out the OnInitializeAsync makes no difference but needs to be commented out when embedded
  //On the main component
  await this.OnInitializedAsync();
  string data = await JSRuntime.InvokeAsync<string>("getMyWindow", new object[] { });
  JObject offsets = (JObject)JsonConvert.DeserializeObject(data);
  TotalHeight = offsets.Value<int>("height");
  TotalHeight = offsets.Value<int>("width");
}

//In my JS file, the function looks as follows:
function getMyWindow() {
    var obj = {};
    obj.width = window.width;
    obj.height = window.height;
    return JSON.stringify(obj);
}

If I make this call directly in the code, nothing ever happens – even with the OnInitializeAsync commented out.

var result = SetDimensions().Result;

If I place this method in the OnAfterRendor method:

protected override void OnAfterRender(bool firstRender)
{
    if (firstRender)
    {
        if (!SetTheDivs)
            SetTheDivs = SetDimensions().Result;

        StateHasChanged();
    }
}
 
protected override void OnInitialized()
{
   base.OnInitialized();
   this.OnAfterRender(true);
}

everything hangs until I kill the project. There are never any errors but the code never runs when I place breakpoints on the height or width statements.

I even added in the Async version to no avail:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        await SetDimensions();

        StateHasChanged();
    }
}

protected override async Task OnInitializedAsync()
{
    await this.OnAfterRenderAsync(true);
}

Same result as everything hangs. I am at a complete loss as to how to proceed and I really could use some help!

As a point of clarity, it is the call to the JS that results in the hang:

string data = await JSRuntime.InvokeAsync<string>("getMyWindow", new object[] { });

I added in some alerts but they never run:

function getMyWindow() {
    var obj = {};
    alert("hi");
    obj.width = screen.width;
    obj.height = screen.height;
    alert("ho");
    return JSON.stringify(obj);
}

Thank you for your time!

BTW – I did change the double await to string data = JSRuntime.InvokeAsync<string>("getMyWindow", new object[] { }).Result;

UPDATE: I moved the JS call outside of the await altogether and I got the error:

InvalidOperationException: JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendered. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method.

In this case, I am literally calling the method from the OnAfterRenderAsync method:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    await base.OnInitializedAsync();
    if (firstRender)
    {
        await SetDimensions();

        StateHasChanged();
    }
}

Advertisement

Answer

Not sure what you want… Copy the code below and run it, and tell us if that is what you were trying to get.

Index.razor

@page "/"

<div class=mainScreen id="outerBox" style="width:@($"{TotalWidth}px");height:@($"{TotalHeight}px"); background-color: green; top:60px; position:absolute">
    @foreach (Device device in devices)
    {
    <div class=column style="width:@($"{device.Width}px");height:@($"{device.Height}px");margin-left:@($"{device.Left}px");margin-top:@($"{device.Top}px"); background-color:aliceblue">
       @device.Name: Main Content Here...
    </div>
    }
</div>

@code {
    private int TotalWidth = 520;
    private int TotalHeight = 530;

    private IList<Device> devices = Enumerable.Range(1, 5).Select(i => new Device { Name = $"Name {i}", Width = 520, Height = 100, Left = 0, Top = 5 }).ToList();

    public class Device
    {
        public string Name { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public int Left { get; set; }
        public int Top { get; set; }
    }
}

Note: The OnInitialized{Async} pair of methods are the life-cycle methods of the base class ComponentBase. They are automatically called by the Blazor framework when a Razor component is being created. They are executed only once. You may override them, and add your logics, but you SHOULD never call them manually from your code.

This:

protected override async Task OnInitializedAsync()
{
    await this.OnAfterRenderAsync(true);
} 

This is wrong and must never be done. You should not call OnAfterRender{Async}. It is the Blazor framework that should call OnAfterRender{Async}, not the developer. Could you try to comprehend what your code is doing…

Try to understand that though the Razor components are defined as C# classes, they are special cases of Classes, that require special handling by the framework…

Update

Ken Tola, the following code I believe does what you’re looking for. It reads the width and height of the window object, pass it to the Index component, and relocate your dear divs. Note that before the app relocates the divs, I check the values of the width and height, and determine the dimensions of the divs. This is of course is done for demonstration purposes, and you can manipulate those values as you wish…

Index.razor

@page "/"
    
@implements IDisposable
@inject IJSRuntime JSRuntime

    
<div class=mainScreen id="outerBox" style="width:@($" {TotalWidth}px");height:@($"{TotalHeight}px"); background-color: green; top:60px; position:absolute">
    @foreach (Device device in devices)
    {
        <div class=column style="width:@($" {device.Width}px");height:@($"{device.Height}px");margin-left:@($"{device.Left}px");margin-top:@($"{device.Top}px"); background-color:aliceblue">
            @device.Name: Main Content Here...
        </div>
    }
</div>

@code
{

    private DotNetObjectReference<BrowserService> objRef;
    private BrowserService BSS;

    private int TotalWidth; 
    private int TotalHeight; 

    private IList<Device> devices = Enumerable.Range(1, 5).Select(i => new Device { Name = $"Name {i}", Width = 520, Height = 100, Left = 0, Top = 5 }).ToList();

    public class Device
    {
        public string Name { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public int Left { get; set; }
        public int Top { get; set; }
    }
      
    protected override void OnInitialized()
    {
        BSS = new BrowserService();

        objRef = DotNetObjectReference.Create(BSS);

        BSS.Notify += OnNotify;
    }
    
    public void Dispose()
    {
        BSS.Notify -= OnNotify;

        objRef?.Dispose();
    }

    public async Task OnNotify()
    {
        // Note that the notifier only notify your component 
        // that data is ready, and that the dimensions are read
        // from a property. You can instead define event handler
        // that pass the data in the form of EventArgs... 
        TotalWidth = BSS.Dimension.Width >= 877 ? 520 : 550;
        TotalHeight = BSS.Dimension.Height >= 550 ? 800 : 1200;
    
        await InvokeAsync(() => StateHasChanged());
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        // This code is excuted only once, in order to initialize
        // the JavaScript objects
        if (firstRender)
        {
            await JSRuntime.InvokeAsync<object> 
                  ("myJsFunctions.getDimensions", objRef);
                
        }
    }

}

BrowserService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.JSInterop;

 public class BrowserService
    {
        public event Func<Task> Notify;
#nullable enable
        public Dimension? Dimension { get; set; }
#nullable disable

        [JSInvokableAttribute("GetDimensions")]
        public async Task GetDimensions(string dimension)
        {
            JsonSerializerOptions options = new(JsonSerializerDefaults.Web)
            {
                WriteIndented = true
            };
            var _dimension = System.Text.Json.JsonSerializer.Deserialize(dimension, typeof(Dimension), options);
            Dimension = (Dimension)_dimension;

            if (Notify != null)
            {
                await Notify?.Invoke();
            }
        }
    }
    public class Dimension
    {
        public int Width { get; set; }
        public int Height { get; set; }

    }
}

Startup.ConfigureServices

services.AddScoped<BrowserService>();

_Host.cshtml

<script src="_framework/blazor.server.js"></script>

<script type="text/javascript">
    window.myJsFunctions = {

        getDimensions: function (dotnetHelper) {
            var dimension = {
                 width: window.innerWidth,
                 height: window.innerHeight
            };
            var json = JSON.stringify(dimension);

            return dotnetHelper.invokeMethodAsync('GetDimensions', json);
        }
    };
</script>

Note: Consider handling the relocation of the div elements when the window is resized. It should be responsive, right ? Not sure that in your case you can employ media query. Any how, as you can see, I have designed the code in such a way that it takes into account that your div elements may need to be relocate again and again, thus it constantly (when resizing) notifies your Index component of the changing dimensions. I guess this merits a new question…..

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement