ASP .NET Core: View grid of multiple items to be edited - asp.net-core

I'm trying to construct a view where the records from a table (machines) get uploaded and that each one can be edited in a view.
This is my Get method:
public async Task<IActionResult> Test()
{
return View(await _context.Machines.Include(t => t.MachineTypes).Include(p => p.Suppliers).AsNoTracking().ToListAsync());
}
Pretty simple for what I need. I'm getting a list of the table Machines and including the related tables: MachineTypes and Suppliers because this information should be able to be edited in the view.
Now, with the view I'm quite lost, sorry for this.
I started defining the model to be used:
#model IEnumerable<Application.Models.Machine>
Question[Answered] Is correct to use an IEnumerable?
Answer by uni: Yes
Then, as seen in some examples, I use a Html.BeginForm to indicate the controller and action:
#using (Html.BeginForm("Machines","TestPost"))
Sidenote: The Post method is not defined yet so I'm using any name for now
Then comes the iteration to read every element of the list:
#Html.ValidationSummary(true)
#for(int i = 0; i < Model.Count() ; i++)
Question[Answered]: In this part I'm not sure if 'Model.Machine.Count()' is correct.
Answer by uni: It's correct. No need to specify 'Machine'
Inside the iteration I don't know how to code to show the MachineName (MchName) to begin with. I'm doing this:
#for(int i = 0; i < Model.Count() ; i++)
{
<tr>
<td>
#Html.HiddenFor(modelItem => modelItem.Machine[i].Id)
#Html.DisplayNameFor(model => model.MchName)
</td>
</tr>
}
Question[Answered]: In this part:
#Html.HiddenFor(modelItem => modelItem.Machine[i].Id)
I don't know why I need to use the HiddenFor, I saw it on another example in the forum.
Answer by uni: This is one of many ways to send the values to the controller.
Question[Answered]: Also, this part
#Html.HiddenFor(modelItem => modelItem.Machine[i].Id)
Answer by uni: 'Machine' don't need to be included, same as before
Continuation of the View
For the iteration part, since 'Machine' don't need to be included...
[NEW]Question: How should I call the item wanted from the Machine table?
I've got this:
#for(int i = 0; i < Model.Count() ; i++)
{
<tr>
<td>
#Html.HiddenFor(modelItem => i.Id)
#Html.DisplayNameFor(modelItem => model.MchName)
</td>
</tr>
}
Both of this are wrong, but I was trying to figure out how to show the item inside the iteration. I wonder if, since I'm using an IEnumerable, it would be best if I use a #foreach instead of a for();
Thanks

Question: Is correct to use an IEnumerable?
Answer: Yes
Question: In this part I'm not sure if 'Model.Machine.Count()' is correct, and well, I'm getting the error: IEnumerable does not contain a definition for Machine. Is it enough if I put it this way?: #for(int i = 0; i < Model.Count() ; i++)
Answer: You already have a collection of Machines (IEnumerable<Application.Models.Machine>) which is stored in your model so Model.Count() would be the correct way to go as each Machine model doesn't have a child Machine object
Question: #Html.HiddenFor(modelItem => modelItem.Machine[i].Id) I don't know why I need to use the HiddenFor, I saw it on another example in the forum.
Answer: When you do an HTTP post you have many ways to send values to the controller action. Hidden fields are a way to do that. In this case you are sending the id of the machine as part of your form submission.
Question: #Html.HiddenFor(modelItem => modelItem.Machine[i].Id) is showing the same error: IEnumerable does not contain a definition for Machine. I tough I should mention the table.
Answer: The answer to this one is the same as the answer that I mentioned above regarding the Count() invocation in the for loop.
Hope this helps!

Related

Need assistance with Prestashop label code

I want to add a Best Seller label to products in Prestashop 1.6, on both product list and product pages, the same as the labels for New Products. Can anybody point me in the right direction to accomplishing this, if it is possible, i.e will it require a controller etc.
At this stage all I have tried is simply,
{if isset($best_sellers) && $best_sellers|#count > 0}
<span class="bestseller">
<label>{l s='best seller' mod='modulename'}</label>
</span>
{/if}
This did not work. I don't need any help with the css, I can handle that. Thanks
Hello again,
Using some of the information you gave me below I have come up with the following to add a best seller label to my custom module without using an override.
Following is the function:
public function getcustomBestSellersLabel($params)
{
$id_product = (int)$params['product']['id_product'];
return Db::getInstance()->executeS('SELECT `id_product`, `sale_nbr` AS customnbr FROM '._DB_PREFIX_.'product_sale ps WHERE `id_product` = '.(int)$id_product.' ORDER BY `customnbr` DESC LIMIT 5');
}
and in my hook
public function hookdisplayCustomBestSellersTag($params)
{
$id_product = (int)Tools::getValue('id_product');
if (!$this->isCached('custombestsellertag.tpl', $this->getCacheId($id_product))) {
$custombestsellers = CustomModule::getCustomBestSellersLabel($params);
$this->context->smarty->assign(array(
'custombestsellers' => Module::getInstanceByName('CustomModule')->getCustomBestSellersLabel($params),
'id_product' => $id_product,
));
}
return $this->display(__FILE__, 'views/templates/front/custombestsellerlabel.tpl', $this->getCacheId());
}
and in my tpl
{if (isset($custombestsellers) && $custombestsellers)}
<span class="custom-best-seller-label">
<label>{l s='best seller' mod='CustomModule'}</label>
</span>
{/if}
This actually works except for one problem, the LIMIT doesn't work. I have tested it in phpMyAdmin where it does work. I have tested it with LIMIT 5 and there is no change, instead all products in the product_sale database table are showing a label.
Can you give me some advice on how to fix this problem.
Thank you.
It's much more complicated than that, to achieve that you need:
create override of Product.php method called getProductProperties
check if product is in TOP X in terms of sales in PREFIX_product_sale table
if so, mark it as a bestseller by adding a new variable (alternative is to do this directly in SQL statement)
if you'll do everything properly and new variable will be available you would have a chance to use it like this:
{if $product.is_bestseller}bestseller label{/if}

Improvement With Xpath Search?

I am a student learning selenium and trying to set a checkbox(by placing a tick) that resides on the same row as a target document. Here is how the row looks:
<span><input>Checkbox</input></span> - <span>_lblDocumentCategoryDescription</span> - <span>_lblDocumentdescription</span>
Each row consists of a checkbox for that row, a category and a description. I am provided the description and need to set the corresponding checkbox at the beginning of the row.
Although I have a solution that works I am looking for a better method as my method is very slow and I can call it multiple times, compounding the slowness. The page in question has 315 fixed different documents that might possibly be added (to a merge and print feature). I usually add at least 3 rows so need to run the search
I am currently using xpath to search since my knowledge of CSS is limited and it seems like traversing up the DOM (no :parent?) is not easy unless using some javascript, which I am trying to avoid. I have read that executing JS doesn't emulate the user correctly so I guess xpath is the solution?
As I know there must be a quicker/better/more efficient solution I am hoping others might have found better?
Here is what I am currently using:
Method Call
CPTools,setStupidCheckbox("Sublease Addendum", true);
Method:
public CPTools setStupidCheckbox(String documentToPrint, Boolean onIfTrue) {
this.log.trace("Starting: setStupidCheckbox; parameter: documentToPrint " + documentToPrint);
String xpathForCheckboxes = "/ancestor::tr//input";
try {
String xpathForDocumentDescriptions = String.format("//span[contains(text(), '%s')]", documentToPrint);
tools.waitForElement(By.xpath(xpathForDocumentDescriptions), 2);
if (driver.findElement(By.xpath(xpathForDocumentDescriptions)).isDisplayed()) {
driver.findElementByXPath(xpathForDocumentDescriptions + xpathForCheckboxes).click();
}
} catch (org.openqa.selenium.NoSuchElementException | ElementNotVisibleException e) {
}
this.log.trace("Ending: setStupidCheckbox");
return this;
}
Here is the full xpath for a table row (the dynamic part of the id is the _ctl316):
<tr class="DataGridCellData">
<td class="DataGridCellDataCenterAligned">
<span class="DataLabel"><input id="ctl01_PageBody_grdDocument_ctl316_chkSelect" name="ctl01$PageBody$grdDocument$ctl316$chkSelect" onclick="javascript:uiHelper.GridCellCheck(this, 'chkAllSelect');" type="checkbox"></span>
</td><td>
<span id="ctl01_PageBody_grdDocument_ctl316_lblDocumentCategoryDescription">Approval Form</span>
</td><td align="left">
<span id="ctl01_PageBody_grdDocument_ctl316_lblDocumentdescription">Sublease Addendum</span>
</td><td align="center">
</td>
</tr>
Thanks in advance for any possible help.
Try below XPath if span text (Sublease Addendum) is unique:
//td[.= 'Sublease Addendum']

Foreach property in item from list

I'm revamping a small helpdesk application (moving from ASP classic to MVC4). This is my first full MVC application. I've opted to go with Razor, and I feel like it's pretty intuitive. But I've hit a wall on this part.
I get a list of tickets from the database to display to the user. What I would like is to dynamically create a table. Right now I have put the headers in place manually, which is fine. If there's a way to do that dynamically, though, I'm open to suggestions.
But the crucial piece is to get each property from the ticket. I have a Ticket class that has over 20 properties. I'll be working on whittling those down to the minimum we want to display, but as a starting point, I'm trying to throw them all up on the screen.
I have the following:
#model IList<Helpdesk4.Models.Ticket>
...
#foreach (var ticket in Model)
{
<tr>
#foreach (var item in ticket)
{
<td>item</td>
}
</tr>
}
But I can't run that foreach on ticket. I'm enough of a noob that I don't totally understand why, but I think that the properties have to be loaded and so can't be enumerated by default. So without pulling up each property name, how do I just get each property's value from ticket?
I am using NHibernate for the queries to the db if that makes any difference.
The absolutely clearest and easiest way to do this if to manually add each of the properties values to the row. This does require a small amount of "extra" work, but it is a one time thing, and 20 properties is not that much. It also gives you much finer control over exactly how each property is displayed, if it is aligned right or left, etc.
What you end up with is something like this
#foreach (var ticket in Model)
{
<tr>
<td>
#ticket.FirstProperty
</td>
<td class="align-right">
#ticket.SecondProperty
</td>
<td class="align-center bold">
#Html.DisplayFor(m => ticket.ThirdProperty)
</td>
<td>
<i>#ticket.FourthProperty</i>
</td>
...
</tr>
}
Stylings and such added for emphasis of customizability.
Good luck with your first MVC project!
Foreach only works with an enumerable item, and general classes don't implement IEnumerable. You need to put each item on there yourself. The best way would be to use something like Html.TextBoxFor(m => m.Property) for each property.
edit:
The best way would be to just use Html.LabelFor on the object itself.
#for(int index = 0; index < Model.Count; ++index)
{
<tr>
#Html.LabelFor(m => m[index])
<tr>
}

Configure list of items to send to server

I want the user to be able to construct a list of items client-side in a form and then send the entire list to the server when the form is submitted.
Each item has several properties that can be set. For example, here's an Order model:
class Order
{
int Id;
string Description;
Guid UserId;
string Product;
}
The user should be able to add and configure as many orders as they want before submitting the form.
I thought I could do this with the Telerik grid using InCell editing. Unfortunately, the list of available options for each field will change based on the value chosen for other fields. I couldn't make this work with InCell editing because I couldn't access the other ui elements from my javascript event handlers.
I also tried to do this with normal InLine editing, but the only item returned is the new Order to be added. I couldn't access the GridModel to add the new Order. I don't want to persist the Order list until the user is done editing.
Any help would be greatly appreciated.
EDIT: If I go with the solution of making my own list, I have this problem with cascading comboboxes. Each combobox has a name like "UserId_0" based on what index it is in the list.
But now how do I get the downstream list?
Here's my two comboboxes:
<td>
#(Html.Telerik().ComboBox()
.Name("UserId_" + i.ToString())
.BindTo(new SelectList(Model.UserIds, "Id", "Name"))
.Placeholder("Select UserId...")
.AutoFill(true)
.CascadeTo("Product_" + i.ToString())
.SelectedIndex(0))
</td>
<td>
#(Html.Telerik().ComboBox()
.Name("Product_" + i.ToString())
.AutoFill(true)
.DataBinding(binding => binding.Ajax().Select("GetProducts", "Order"))
.Placeholder("Select Product...")
.SelectedIndex(0))
</td>
And my Controller method looks like this. It's hacky as hell, but how do I access the value otherwise?
public JsonResult GetProducts(Guid? UserId)
{
// Hack!
ValueProviderResult userResult = ValueProvider.GetValue("UserId");
for (int i=0; i < 10; i++)
{
userResult = ValueProvider.GetValue("UserId_" + i);
if (userResult != null)
{
break;
}
}
if (userResult != null)
{
// Get list of products based on value
return Json(new SelectList(productList, "Id", "Name"));
}
else
{
return Json(null);
}
}
Do not waste your time to achieve this with the some widget - better check how to send collection of items to the server and implement your own form with checkboxes or so.
If you want to go with some of the widgets - you can try the Grid in a way similar to this demo.

jQuery: Select elements with Incrementing ID names?

and thanks in advance for your help!
Here's my situation: I have a set of divs whose IDs have an incrementing number applied to the end of the name using PHP. Each of these divs are added dynamically with PHP (They are a series of FAQ questions with a hidden div container with the answers, that slide down when the question is clicked.) [Live Example][1]
There is no limit to the number of questions that appear on the page, because this is being used for a Wordpress theme and my client wants to add new questions as they go along.
Here's an example of the structure for each FAQ question using the PHP:
<?php var $faqnum = 0; $faqnum++; ?>
<div id="faqwrap<?php $faqnum; ?>">
<h4>What data is shared?</h4>
<div id="faqbox<?php $faqnum; ?>" class="slidebox">
<p>Data sharing is defined by the type of service:</p>
<ul class="list">
<li>Third-party access to data (Enhanced Services only is strictly controlled and determined by the SDA)</li>
<li>All members must participate in points of contact and conjunction assessment but can choose whether to participate in other services</li>
<li>Participation in a service requires the member to provide associated data<br />
</ul>
</div>
</div>
Now this is what I have currently in jQuery, and it works, but only if I add a new one every time my client wants to add a new question.
$(document).ready(function() {
$('.slidebox*').hide();
// toggles the slidebox on clicking the noted link
$("#faqwrap1 a:not(div.slidebox a)").click(function() {
$("#faqbox1.slidebox").slideToggle('normal');
$('div.slidebox:not(#faqbox1)').slideUp('normal');
return false;
});
});
I thought of maybe doing something with a declared variable, like this:
for (var x = 0; x < 100; x++;) {
$('#[id^=faqwrap]'+ x 'a:not(div.slidebox a)')...
}
I hope this is clear enough for you! Again, I thank you in advance. :)
The best way to handle this is to not use the IDs, but use classes for the outer element. So your PHP would be altered like this:
<?php var $faqnum = 0; $faqnum++; ?>
<div id="faqwrap<?php $faqnum; ?>" class="question">
<h4>What data is shared?</h4>
<div id="faqbox<?php $faqnum; ?>" class="slidebox">
<p>Data sharing is defined by the type of service:</p>
<ul class="list">
<li>Third-party access to data (Enhanced Services only is strictly controlled and determined by the SDA)</li>
<li>All members must participate in points of contact and conjunction assessment but can choose whether to participate in other services</li>
<li>Participation in a service requires the member to provide associated data<br />
</ul>
</div>
</div>
Your JQuery would be rewritten with the selector for the class "question".
$(document).ready(function() {
$('.slidebox*').hide();
// toggles the slidebox on clicking the noted link
$(".question a:not(div.slidebox a)").click(function() {
/* close everything first */
$('div.slidebox').slideUp('normal');
/* selects and opens the the slidebox inside the div */
$(".slidebox", this).slideToggle('normal');
return false;
});
});
This will get you the effect you are looking for. The key differences in the JQuery is the way you get the slidebox inside the question that got clicked. I'm using the scoped selection $(".slidebox", this) to get just the slidebox inside the clicked ".question" element.
The subtle visual difference is that the slideUp() happens before the slideToggle(). This will essentially close any open queries before it opens the desired one. If you keep your animations fast, this will be more than fine. The advantage of this approach is that you don't have to worry about the count of questions on a page, and the selectors are most likely more optimized than the for loop.
Edit
I adjusted the PHP code to use a class for "slidetoggle" instead of an id. It's technically an HTML error to have multiple IDs that are the same. It can throw off some assistive technologies for people with dissabilities. I'm assuming that section of code was repeated several times on the page.
Without changing your current markup, this would work:
// toggles the slidebox on clicking the noted link
$("div[id=^faqwrap]").each(function () {
var $faqwrap= $(this);
$faqwrap.find("h4 > a").click(function () {
var $currentSlidebox = $faqwrap.children(".slidebox");
$currentSlidebox.slideToggle('normal');
$('div.slidebox').not($currentSlidebox).slideUp('normal');
return false;
});
});
Maybe you can find a few suggestions in the above code that help you.
Like #Berin, I'd also recommend giving a separate CSS class to the outer DIV and using that as a selector, instead of $("div[id=^faqwrap]").