Adding a tab in Blazor server app overrides the data sources (and names) from all previous tabs - tabs

I'm still new to this, so pardon me if there's an obvious answer.
I have created a tabbed interface with an interesting flaw. Clicking on button 1 creates a tab with the correct DxDataGrid and tab name, but when button 2 is clicked, the newly created tab 2 and tab 1 show the data and tab name that apply to button 2. The same thing happens if I click the buttons in the opposite order, and the same behavior occurs upon subsequent button clicks.
This behavior may be related: when you create tab 1, the grid loads immediately, but you must click each tab (after tab two's creation) in order to get them to load.
<DxMenuItem Text="Button 1" Click='() => AddNewTab("Grid1")' />
<DxMenuItem Text="Button 2" Click='() => AddNewTab("Grid2")' />
<DxTabs RenderMode="TabsRenderMode.OnDemand">
#for (int i = 0; i < count; i++)
{
#switch (caseStmtTabName[tdiIndex])
{
case "Grid1":
#renderGrid1BrowsePage(tdiIndex)
break;
case "Grid2":
#renderGrid2BrowsePage(tdiIndex)
break;
}
}
</DxTabs>
#code
int tdiIndex = 0;
int count = 0;
string[] caseStmtTabName = new string[99];
public void AddNewTab(string tabName)
{
tdiIndex++;
caseStmtTabName[tdiIndex] = tabName;
count++;
}
private RenderFragment renderGrid1BrowsePage(int index)
{
RenderFragment grid1Item = grid1TabChildContent =>
{
grid1TabChildContent.OpenComponent<DxTabPage>(0);
grid1TabChildContent.AddAttribute(1, "Text", "Grid1");
grid1TabChildContent.AddAttribute(2, "ActiveTabIndex", index);
grid1TabChildContent.AddAttribute(3, "ChildContent", (RenderFragment)((grid1TabPageChildContent) =>
{
grid1TabPageChildContent.OpenComponent<Grid1>(0);
grid1TabPageChildContent.CloseComponent();
}));
grid1TabChildContent.CloseComponent();
};
//tdiIndex++;
return grid1Item;
}
private RenderFragment renderGrid2BrowsePage(int index)
{
RenderFragment grid2Item = grid2TabChildContent =>
{
grid2TabChildContent.OpenComponent<DxTabPage>(0);
grid2TabChildContent.AddAttribute(1, "Text", "Grdi2");
grid2TabChildContent.AddAttribute(2, "ActiveTabIndex", index);
grid2TabChildContent.AddAttribute(3, "ChildContent", (RenderFragment)((grid2TabPageChildContent) =>
{
grid2TabPageChildContent.OpenComponent<Grid2>(0);
grid2TabPageChildContent.CloseComponent();
}));
grid2TabChildContent.CloseComponent();
};
//tdiIndex++;
return grid2Item;
}
I understand that rewriting the "renderGridPages" for each button is WET, but passing a string into the data type will be another task for another day (along with a few other things above).
Thanks for any advice you have!

Your code is behaving as you've coded it.
Click on button 1 and you increment tdiIndex to 1 and add "Grid1" to caseStmtTabName at index 1. You display one tab.
Click on button 2 and you increment tdiIndex to 2 and add Grid2 to caseStmtTabName at index 2.
Now look at you loop code:
#switch (caseStmtTabName[tdiIndex])
At this point tdiIndex is 2 for both loop iterations and thus caseStmtTabName[tdiIndex] returns "Grid2" and thus
case "Grid2":
#renderGrid2BrowsePage(tdiIndex)
break;
gets called both times. Exactly what I believe you see.
I don't use DevExpress so can't produce an exact answer. Here's a generic version of your code using standard Blazor components and Bootstrap to demonstrate the techniques you can use.
#page "/"
<div class="m-2 p-2">
<button class="btn btn-primary" #onclick="() => AddTab(1)">Add Grid 1</button>
<button class="btn btn-dark" #onclick="() => AddTab(2)">Add Grid 2</button>
<button class="btn btn-info" #onclick="() => AddTab(3)">Add Grid 3</button>
</div>
<ul class="nav nav-pills">
#foreach (var tab in this.displayTabs)
{
<li class="nav-item">
<a class="nav-link #this.ActiveTab(tab) hand" aria-current="page" #onclick="() => SetActiveContent(tab)">#tab.Name</a>
</li>
}
</ul>
#foreach (var tab in this.displayTabs)
{
<div #key="tab" class="#this.ShowTab(tab)">
#tab.Content
</div>
}
<style>
.show-content {
display: block;
}
.hide-content {
display: none;
}
.hand {
cursor: pointer;
}
</style>
#code {
class Tab
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public RenderFragment? Content { get; set; }
}
List<Tab> tabs = new();
IEnumerable<Tab> displayTabs => this.tabs.OrderBy(item => item.Id);
int activeTabIndex;
string ActiveTab(Tab tab)
=> GetActiveIndex() == tab.Id ? "active" : string.Empty;
string ShowTab(Tab tab)
=> GetActiveIndex() == tab.Id ? "show-content" : "hide-content";
void SetActiveContent(Tab tab)
=> activeTabIndex = tab.Id;
int GetActiveIndex()
{
if (tabs.Any(item => item.Id == activeTabIndex))
return activeTabIndex;
if (tabs.Count > 0)
return tabs[0].Id;
return 0;
}
void AddTab(int index)
{
if (tabs.Any(item => item.Id == index))
{
tabs.Remove(tabs.First(item => item.Id == index));
return;
}
tabs.Add(new Tab()
{
Id = index,
Name = $"Grid {index}",
Content = (builder) =>
{
builder.AddMarkupContent(0, $"<div>Hello {index}</div>");
}
});
activeTabIndex = index;
}
}

Related

Items are not filtered when the checkbox is unchecked

I have an array of four categories. I would like to put each individual category on a separate checkbox so that when clicked the array is filtered by a specific category. With the help of the code below, I want to do the following: if the array of checkboxes already has one of the categories, it will not be added the second time by clicking. Unfortunately, I don't have the filtering to remove the category when the checkbox is unchecked.
Please tell me what is wrong with? And please tell me, can this logic be written better? Thank you very much
public allProductsCategories: string[] = ["сars", "clothes", "home", "sport"];
public checkedCategoriesFilter: string[] = [];
public filterProductsCategoryCheckboxClick(event: Event): void {
const isCheckboxChecked = (<HTMLInputElement>event.target).checked;
const checkboxDataValue = (<HTMLInputElement>event.target).value;
if (isCheckboxChecked) {
if (!this.checkedCategoriesFilter.includes(checkboxDataValue)) {
this.checkedCategoriesFilter.push(checkboxDataValue)
} else {
return;
}
} else {
console.log('unchecked')
this.checkedCategoriesFilter.filter(category => category === checkboxDataValue);
}
console.log(this.checkedCategoriesFilter);
}
html
<div *ngFor="let checkboxCategory of allProductsCategories">
<input
type="checkbox"
[value]="checkboxCategory"
(change)="filterProductsCategoryCheckboxClick($event)"
>
</div>
Filter method return an array and need to store it in array.
Update your component.ts file code as given below:
public allProductsCategories: string[] = ['сars', 'clothes', 'home', 'sport'];
public checkedCategoriesFilter: any = [];
public filterProductsCategoryCheckboxClick(event: Event): void {
const isCheckboxChecked = (<HTMLInputElement>event.target).checked;
const checkboxDataValue = (<HTMLInputElement>event.target).value;
if (isCheckboxChecked) {
if (!this.checkedCategoriesFilter.includes(checkboxDataValue)) {
this.checkedCategoriesFilter.push(checkboxDataValue);
} else {
return;
}
} else {
console.log('unchecked');
this.checkedCategoriesFilter = this.checkedCategoriesFilter.filter(
(category) => {
return category !== checkboxDataValue;
}
);
}
console.log(this.checkedCategoriesFilter);
}

CKEditor Blazor integration

I am trying to use CKeditor with Blazor.
I used Online builder to create a custom build, with ImageUpload and Base64UploadAdapter, and it is integrated in BlazorApp.
I can successfully show it on the page, and put / get HTML content from it.
Source of the working version for Blazor app is here https://gitlab.com/dn-misc/BlazorCKEditor1/
But as I would like to inser image as Base64 encoded string directly in HTML content, when I try to upload image I get following error:
Assertion Failed: Input argument is not an HTMLInputElement (from content-script.js)
I have successfully implemented Chris Pratt implementation. Check this out:
IMPORTANT: this works with ClassicEditor ONLY.
Blazor component, I called mine InputCKEditor.razor. Yeah I know, no very original.
#namespace SmartApp.Components
#inherits InputTextArea
#inject IJSRuntime JSRuntime
<textarea #attributes="AdditionalAttributes"
id="#Id"
class="#CssClass"
value="#CurrentValue"></textarea>
#code {
string _id;
[Parameter]
public string Id
{
get => _id ?? $"CKEditor_{_uid}";
set => _id = value;
}
readonly string _uid = Guid.NewGuid().ToString().ToLower().Replace("-", "");
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
await JSRuntime.InvokeVoidAsync("CKEditorInterop.init", Id, DotNetObjectReference.Create(this));
await base.OnAfterRenderAsync(firstRender);
}
[JSInvokable]
public Task EditorDataChanged(string data)
{
CurrentValue = data;
StateHasChanged();
return Task.CompletedTask;
}
protected override void Dispose(bool disposing)
{
JSRuntime.InvokeVoidAsync("CKEditorInterop.destroy", Id);
base.Dispose(disposing);
}
}
Then, you have to put this in your interop.js
CKEditorInterop = (() => {
var editors = {};
return {
init(id, dotNetReference) {
window.ClassicEditor
.create(document.getElementById(id))
.then(editor => {
editors[id] = editor;
editor.model.document.on('change:data', () => {
var data = editor.getData();
var el = document.createElement('div');
el.innerHTML = data;
if (el.innerText.trim() === '')
data = null;
dotNetReference.invokeMethodAsync('EditorDataChanged', data);
});
})
.catch(error => console.error(error));
},
destroy(id) {
editors[id].destroy()
.then(() => delete editors[id])
.catch(error => console.log(error));
}
};
})();
Now time to use it:
<form>
<label class="col-xl-3 col-lg-3 col-form-label text-sm-left text-lg-right">Description</label>
<div class="col-lg-9 col-xl-6">
<InputCKEditor #bind-Value="_model.Description" class="form-control form-control-solid form-control-lg"></InputCKEditor>
<ValidationMessage For="#(() => _model.Description)" />
</div>
</form>

Angular 4 ngfor or ngif

I don't know what to use for my problem.
I'm getting some information from my HTTP request, and I need to show a prerequisite from a Product when it has a prerequisite. When it doesn't have it I want it to show me a static message like <h1>Test</h1>.
This is my Typescript
public getAllProducts() {
return new Promise((resolve, reject) => {
this.http
.get(
`${this.kioskservice.getAPIUrl()}products/?apikey=${this.kioskservice.getAPIKey()}&format=json`
)
.toPromise()
.then(
res => {
this.config = res.json();
// Get all product names
if (this.config && this.config.length) {
this.names = [];
for (let i = 0; i < this.config.length; i++) {
this.names.push(this.config[i]['Name']);
}
}
// Get all preruiquisites of the products
if (this.config && this.config.length) {
this.prerequisites = [];
for (let i = 0; i < this.config.length; i++) {
this.prerequisites.push(this.config[i]['Prerequisites']);
}
}
console.log(this.prerequisites);
resolve();
},
msg => {
throw new Error("Couldn't get all Bookings: " + msg);
}
);
});
}
public getNames() {
return this.names
}
public getPrerequisites() {
return this.prerequisites
}
}
This is my HTML
<li *ngFor="let prerequisite of productservice.getPrerequisites()">
<i>{{prerequisite}}</i>
</li>
<li *ngFor="let prerequisite of !productservice.getPrerequisites()">
<i>TEST</i>
</li>
The HTML above will show me all 40 Objects that I receive from my HTTP request, but only 4 of those 40 have a Prerequisite text so I will get 36 empty <i> elements and 4 <i> elements with the prerequisite text in it.
UPDATE
Image of Array of Objects
You only need one *ngFor to loop over the array, then use *ngIf to decide what to show.
<li *ngFor="let prerequisite of productservice.getPrerequisites()">
<i *ngIf="prerequisite">{{prerequisite}}</i>
<i *ngIf="!prerequisite">TEST</i>
</li>

Auto save a drop down list

I have a drop down list for feed back. I save the data when save button is pressed. I want to auto save the drop down list without using save button how i can do it?
here is my ontroller
public ActionResult SelectFeedBack(int id)
{
YelloAdminDbContext db = new YelloAdminDbContext();
ViewBag.FeedBack = new SelectList(db.FeedBack, "FeedBackId", "FeedBackDrpDown");
return PartialView();
}
[HttpPost]
public ActionResult SelectFeedBack(int FeedBack, int id)
{
YelloAdminDbContext db = new YelloAdminDbContext();
if (ModelState.IsValid)
{
var temp = db.FeedBack.Find(FeedBack);
db.SaveFeedBack.Add(new SaveFeedBack { LoginId = id, feedback = temp });
db.SaveChanges();
return JavaScript("alert ('success');");
}
return JavaScript("alert ('error');");
}
public ActionResult DisplayFeedBack(int id)
{
YelloAdminDbContext db = new YelloAdminDbContext();
var Feed = db.SaveFeedBack.Where(x => x.LoginId == id).FirstOrDefault();
if (Feed == null)
{
return Content("No Feed Back Entered");
}
return Content(Feed.feedback.FeedBackDrpDown);
}
and my view is
#model MyYello.Admin.Models.FeedBack
#{
ViewBag.Title = "Feed Back";
}
<h2>Add Notes</h2>
#using (Html.BeginForm("SelectFeedBack", "Admin", FormMethod.Post))
{#Html.ValidationSummary(true);
<fieldset>
#Html.HiddenFor(item => item.FeedBackId)
<legend>Create Notes</legend>
<div class="editor-label">
#Html.LabelFor(item => item.FeedBackDrpDown)
</div>
#Html.DropDownList("FeedBack")
<p><input type="Submit" value="Save" id="Save" /></p>
</fieldset>
}
How i can save the data without using button.
Use some event like focusout which in turn will auto submit the form .
$(this.document).ready(function () {
var form = $("form");
form.submit();
});

Actions with the same parameters in MVC 4 and Razor

In my Controller I have two Actions, the POST action is called when a combo is changed or the update button is pressed. This is working correctly.
When I do a Create, I use a return RedirectToAction("Index") if the create was successful.
Here is where the problem starts. I need that the combo in the index get the selectedvalue of the id used in the create, but I cannot pass another parameter in the Index action, because the POST Action already has one.
How can I pass a parameter to the GET Action and update the combo, with the selectedvalue from the Create Action?
I tried to use a "Return View()", it worked but not as expected.
These are the Actions:
[HttpGet]
public ActionResult Index()
{
ViewBag.ID_CONTRATO = new SelectList(db.CAT_CONTRATOS.Where(t => (t.BAJA == null || t.BAJA == false)), "ID_CONTRATO", "CONTRATO");
ViewBag.listado = Enumerable.Empty<CAT_CONCEPTOS>();
return View();
}
[HttpPost]
public ActionResult Index(int Contrato = 0)
{
if (Contrato == 0)
{
ViewBag.ID_CONTRATO = new SelectList(db.CAT_CONTRATOS.Where(t => (t.BAJA == null || t.BAJA == false)), "ID_CONTRATO", "CONTRATO");
ViewBag.listado = Enumerable.Empty<CAT_CONCEPTOS>();
}
else
{
ViewBag.ID_CONTRATO = new SelectList(db.CAT_CONTRATOS.Where(t => (t.BAJA == null || t.BAJA == false)), "ID_CONTRATO", "CONTRATO", Contrato);
var cat_conceptos = db.CAT_CONCEPTOS.Include(c => c.CAT_CONTRATOS).Where(t => ((t.BAJA == null || t.BAJA == false) && (t.ID_CONTRATO == Contrato)));
ViewBag.listado = cat_conceptos;
}
return View();
}
[HttpPost]
public ActionResult Create(CAT_CONCEPTOS cat_conceptos)
{
if (ModelState.IsValid)
{
using (TransactionScope scope = new TransactionScope())
{
db.CAT_CONCEPTOS.Add(cat_conceptos);
db.SaveChanges();
TransaccionesController t = new TransaccionesController();
t.Transaccionar("CAT_CONCEPTOS", "Create", cat_conceptos.ID_CONCEPTO, "Se creo un concepto");
scope.Complete();
return RedirectToAction("Index");
}
}
ViewBag.ID_CONTRATO = new SelectList(db.CAT_CONTRATOS.Where(t => (t.BAJA == null || t.BAJA == false)), "ID_CONTRATO", "CONTRATO", cat_conceptos.ID_CONTRATO);
return View(cat_conceptos);
}
And the View:
#model IEnumerable<SAC.Models.CAT_ACTIVOS>
#{
string controlador = ViewContext.RouteData.Values["Controller"].ToString();
}
#section head{
<link href="#Styles.Url("~/content/DataTables/css")" rel="stylesheet"/>
}
#{
ViewBag.Title = "Activos";
}
<h2>#ViewBag.Title</h2>
<div class="row-fluid">
<div class="span12">
<div class="row-fluid">
#using (Html.BeginForm("Index", "Activos"))
{
<div class="span9">
#Html.DropDownList("Contrato", (SelectList)ViewBag.ID_CONTRATO, "Seleccione un Contrato", new { #class = "span12", onchange = #"var form = document.forms[0]; form.submit();" })
</div>
<div class="span1">
<button type="submit" class="btn btn-danger span12"><i class="icon-refresh"></i></button>
</div>
<div class="span2">
#Html.ActionLink("Agregar", "Create", #controlador, new { #class = "btn btn-danger span12" })
</div>
}
</div>
</div>
</div>
#Html.Partial("Shared/_GridActivos", (IEnumerable<SAC.Models.CAT_ACTIVOS>)ViewBag.listado)
#section JavaScript{
#Scripts.Render("~/Scripts/DataTables/js")
}
This is one of the cases where TempData comes in handy, as a way to store that information during the redirect.
TempData["MyId"] = t.ID;
return RedirectToAction("Index");
Then, in your Index action:
if (TempData["MyId"] != null) {
// Retrieve ID and object from the database to load the model.
} else {
// Your existing code.
}
Depending on the size and serializability of your model, you may even be able to store the created model object in TempData and retrieve that in Index.