PDA

View Full Version : [CLOSED] DirectEvents and CodeBehind



bbros
Jan 11, 2021, 10:27 AM
Hi, I need to create a reusable custom control, with server-side actions (the control is a grid with a toolbar, the buttons are like save, new, and so on).

I don't know how I can handle DirectEvents in a class and not on the same page.

I want to recreate the example of your DirectEvents page using an external class even just for a button.

DirectEvents.cshtml

@page "{handler?}"
@model BBros.MySamples.Pages.DirectEventsModel
@{
ViewData["Title"] = "DirectEvent";
}
<ext-section target="Main">
<ext-container id="myContainer" region="Center" scrollable="true" paddingAsString="30 20 30 50" model="Model.MyContainer">
</ext-container>
</ext-section>

DirectEvents.cshtml

using Ext.Net;
using Ext.Net.Core;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;

namespace BBros.MySamples.Pages
{
public class DirectEventsModel : PageModel
{
public Container MyContainer { get; set; }

public void OnGet()
{
MyContainer = new Container();
MyContainer.Items.Add(Samples.GetButton());
}
}
}

Samples.cs

using Ext.Net;

namespace BBros.MySamples
{
public class Samples
{
public static Button GetButton()
{
var b = new Button
{
Id = "myCodeBehindButton",
Text = "Click me!"
};

//b.DirectEvents.Click.Event += myCodeBehindButton_Click();
//HERE - What can I do?

return b;
}

}
}


Is there an equivalent of this way?



b.DirectEvents.Click.Event += myCodeBehindButton_Click()

private void myCodeBehindButton_Click(object sender, DirectEventArgs e)
{
var msg = $"Server Time is { DateTime.Now.ToString("H:mm:ss tt") } :+1:";
this.X().Toast(Mojee.Replace(msg));
return this.Direct();
}

Thank you!

fabricio.murta
Jan 11, 2021, 3:18 PM
Hello @bbros!

I think that the problem there would be that the events are not wired up in ASP.NET 5/core as they were in ASP.NET WebForms. This was both a nice thing and a limitation in some cases, but you just want to bind the same direct event to several buttons in the same page?

Take a look at this example:
- Events > Direct Events > Duration Messages (https://examples.ext.net/#/events/directevents/duration_messages)

See how the DoSomething direct event is set up? You give it a pageHandler, set the method as POST and depending on the nature of the event, other settings, like Load type, in the first sample therein.

This suggests any control you give the same direct event will handle it without even knowing other controls share the same one. Thus something like this should be the way to go:



var b = new Button
{
Id = "myCodeBehindButton",
Text = "Click me!"
};

b.DirectEvents.Click.Method = HttpMethod.POST;
b.DirectEvents.Click.PageHandler = "myCodeBehindButton_Click";


Then any button -- or commponent as a matter of fact -- you bind this direct event will trigger it.

But again... That's not quite right. Actually the code above is what would match markup, but you would just find the nice b.DirectEvent.Click.PageHandler setting, missing.

Analyzing how it should work, one can find that using b.DirectEvents.Click.Url = HttpContext.Request.Path + "?handler=" + "myCodeBehindButton_Click" does the job; thus you can simplify your life by just extending the direct events in code behind with a C# extension method.

Place this anywhere in your project:



public static class ExtNetExtensions
{
public static void PageHandler(this DirectEventHandler de, Microsoft.AspNetCore.Http.HttpContext context, string pageHandler)
{
de.Method = HttpMethod.POST;
de.Url = context.Request.Path + "?handler=" + pageHandler;
}
}


And then you would be able to change your code to just:



var b = new Button
{
Id = "myCodeBehindButton",
Text = "Click me!"
};

b.DirectEvents.Click.PageHandler(HttpContext, "myCodeBehindButton_Click");


The extension (last line being valid) would affect your whole project. But keep in mind the specific direct event must be present just like the example linked above has, in the same page's code behind. Nothing prevents you from calling a common method from your project, from the direct event, though -- or manually pointing the direct event to a specific page instead of HttpContext.Request.Path. Unfortunately the HttpContext should be passed as it detains informations pertaining the current request and won't be available in the global/static scope.

Hope this helps!

bbros
Jan 11, 2021, 5:53 PM
Thank you for the quick reply, you gave me good points!


[...] but you just want to bind the same direct event to several buttons in the same page?

Not exactly that way.
In WebForms I have been able to build a "Special" GridPanel which I use in place of the standard one.
There is a Toolbar with Add, Delete, Export To Excel and other buttons.
The grid doesn't know anything about its data and it's fully built in codebehind; each page that instantiates this grid sets the DataAdapter and the DataTable and some other configuration parameter.
I've been able to add built-in features, like DoubleClick editing with a popup window and a DataPanel and so on.

Different projects have implemented the "special grid" and I saved tons of code in the last years.

What I'm trying to do is recreate the same grid, extending yours, and giving the SQL Server abilities to create, delete, edit records.

I'm evaluating a different approach, still thinking if it is a good practice...

In the "super grid" project (that will be shared in different solutions) I add a new class like this one

public class DirectEventsPageModel : PageModel
{
public IActionResult OnPostMyCodeBehindButtonClick()
{
var msg = $"Server Time is { DateTime.Now.ToString("H:mm:ss tt") }";
this.X().Toast(msg);
return this.Direct();
}
}

and in each of specific project I add a single RazorPage which inherits the common one (called DirectEvents)



public class DirectEventsModel : SuperGridProject.DirectEventsPageModel
{
public void OnGet()
{
}
}

So the button can point to the inheriting page in this way


myButton.DirectEvents.Click.Method = HttpMethod.POST;
myButton.DirectEvents.Click.Url = $"/DirectEvents/MyCodeBehindButtonClick";


This works and accomplishes what it has to do.

I'll study on it (and I'll keep studying Razor, MVC, .NET 5 as well)!

I'm sorry for asking sometimes question not always ExtNet-Related, so Double Thank you !

fabricio.murta
Jan 11, 2021, 10:48 PM
Hello again, @bbros!

If code reusability is what you want, maybe you could use a base class for PageModel like you are doing, but also add facilities that configures the grid for you (and guarantees the direct event signatures match what they should have).

There is a world of alternatives for that. For one, you could have a "base model" (just how you are envisioning in your last post) within the SuperGridProject like this:



public class SuperGridModel : PageModel
{
public virtual IActionResult OnPostHandleGridAdd()
{
this.X().Toast("Adding item.");

return this.Direct();
}

public virtual IActionResult OnPostHandleGridRemove(int id)
{
this.X().Toast("Removing item: " + id);

return this.Direct();
}

public virtual IActionResult OnPostHandleGridEdit(int id)
{
this.X().Toast("Editing item: " + id);

return this.Direct();
}

private GridPanel superGrid = null;

public GridPanel SuperGrid
{
get
{
if (superGrid != null)
{
return superGrid;
}

superGrid = new GridPanel();
var gridTopBar = new Toolbar();

gridTopBar.Items.Add(new Button()
{
Text = "Add",
OnDirectClick = "HandleGridAdd"
});

var removeBtn = new Button()
{
Text = "Remove"
};

string pageHandlerPath(string pageHandler)
{
return HttpContext.Request.Path + "?handler=" + pageHandler;
};

void bindPageHandler(DirectEventHandler directEvent, string pageHandler)
{
directEvent.Method = HttpMethod.POST;
directEvent.Url = pageHandlerPath(pageHandler);
directEvent.Before = new JsFunction("if (!this.up('grid').hasSelection()) { Ext.toast('Please select a record.'); return false; } else { return true; }");
directEvent.ExtraParams.Add(new DirectEventParameter()
{
Key = "id",
Value = "function() { var grid = this.up('gridpanel'); if (grid.hasSelection()) return this.up('gridpanel').getSelection()[0].get('id'); }",
Mode = ParameterMode.Raw
});
}

bindPageHandler(removeBtn.DirectEvents.Click, "HandleGridRemove");

gridTopBar.Items.Add(removeBtn);

var editBtn = new Button()
{
Text = "Edit"
};

bindPageHandler(editBtn.DirectEvents.Click, "HandleGridEdit");

gridTopBar.Items.Add(editBtn);

superGrid.Tbar = gridTopBar;

ensureGridStore();

superGrid.Store = this.gridStore;

return superGrid;
}
}

private Store gridStore = null;

private void ensureGridStore()
{
if (this.gridStore == null)
{
this.gridStore = new Store();
}
}

protected List<object> Data
{
get
{
ensureGridStore();
return this.gridStore.Data.As<List<object>>();
}
set
{
ensureGridStore();
this.gridStore.Data = value;
}
}
}


I hope it is not too overcomplicated, but basically, it holds its own grid and facilitates how data is bound to it, so then in order to use it, all you need in your page would be inherit from this class and set up everything else from the grid taking advantage of the facilities of the class:



public class IndexModel : SuperGridModel
{
public void OnGet()
{
this.SuperGrid.Title = "Super GridPanel instance";

// Syntax sugar to feed data to the grid.
this.Data = new List<object>
{
new { Id = 1, Name = "Homer" },
new { Id = 2, Name = "Lisa" },
new { Id = 3, Name = "Marge" },
new { Id = 4, Name = "Bart" },
new { Id = 5, Name = "Maggie" }
};
}

public override IActionResult OnPostHandleGridEdit(int id)
{
this.X().Toast("Custom-fashionedly editing item: " + id);

return this.Direct();
}
}


Notice how we use a custom edit event in the model. But that's not all! You may have noticed I didn't design the grid columns anywhere yet. That's because I can still set up what I feel like from markup!



<ext-gridPanel model="Model.SuperGrid" id="GridPanel1" width="500" height="800">
<columns>
<ext-column dataIndex="id" text="Id" width="80" />
<ext-column dataIndex="name" text="Character" flex="1" />
</columns>
</ext-gridPanel>


- The Model.SuperGrid comes from SuperGridModel class, so it would be intrinsic to any page inheriting from it.
- The columns could have been designed in code behind, this, and width/height, was done from markup just to illustrate the possibilities.

I am not sure you are still looking for answers here, and the possibilities are a lot, but maybe you find this approach interesting.

Hope this helps!

bbros
Jan 12, 2021, 8:07 AM
I am not sure you are still looking for answers here, and the possibilities are a lot, but maybe you find this approach interesting.

It is the answer I was looking for :)
Thank you very much again and again and again...

fabricio.murta
Jan 12, 2021, 11:58 AM
Glad it helped and thanks for the feedback!