Add data to viewmodel from view - asp.net-mvc-4

I'm using asp.net MVC 4. NOTE I'm using arrays for this. I know about list use em all of the time but this should be arrays.
QUESTION
Now It's a really long list and lots of checkboxes. In order to package this into a more userfriendly UI, I would like a dropbox with all certificates. And a button that says "add mandatory certificate" aswell as a button that says "add desirable certificate".
My solution(feel free to criticize)
Create part
Add two string properties to the viewmodel alternatively / or two arrays
Create a dropdownlist.
Create two buttons (one for mandatory, second for desirable)
Create two a hidden fields
Logical part
On button click add/append the value of the certificate in the dropdown list to either the
hidden field for "mandatory" or "desirable".
In controller split the strings on ",", and build arrays of choosen "mandatory" / "desirable" certficates.
Is that particulary ugly or how should I do it?
I have a the following view
#using (Html.BeginForm())
{
for (int i = 0; i <= Model.Certificates.Count - 1; i++)
{
#Html.HiddenFor(x => x.Certificates[i].Value)
#Html.HiddenFor(x => x.Certificates[i].Name)
#Html.CheckBoxFor(x => x.Certificates[i].Checked)
#Html.LabelFor(x => x.Certificates[i].Checked, Model.Certificates[i].Name)
#Html.DropDownListFor(m => m.Certificates[i].SearchIncluded,
new SelectList(new []
{ new { Selected = true, Text = "Not included", Value = "Not included"},
new { Selected = false, Text = "Mandatory", Value = "Mandatory"},
new { Selected = false, Text = "Desirable", Value = "Desirable"},
}, "Value","Text","Selected"));
<br />
}
<Input type = "submit" Name = "SearchButton" value = "Search" />
}

I guess I have a few critiques
create a partial view for the add a certificate form.
potenially create a partial view for the Certificate display within the loop, so the loop would look like
#Html.Parital("CertificatePartial", x.Certificates[i])
if your forms are identical for the "Mandatory" or "Desirable" certificate, have a single "Save" button at the bottom of the Add A Certificate form. If the forms are identical, then the mandatory or desirable condition should be a property of the certificate. It can be included using a radio button on the form so the user can choose between "Mandatory" or "Desirable".
and I have another possibliiliy regaruding the way it works, but is speculation based on some things I don't really know about the intent or layout of your page
you could always use an ajax form post to save the new certificatation data, and then use javascript result to append it to your certification list, ie
public ActionResult AddCertificate (AddCertificateViewModel model){
...save to database
return Partial("CertificateParital", CertificateModel);
}
you would do this by using an Ajax Form
#using(Ajax.BeginForm("action","controller",new AjaxOptions{InsertionMode = InsertionMode.InsertAfter, UpdateTargetId = "CertificationsListContainer"}))
I'm kind of assuming this works the same way as a blog post and comments.

Related

Remove "MIME type" column from Filent Content List

I am Using a Script Adapter by passing payload to get contend for a Content List from "Search with values" event
When Contend get loaded to content list , i have a custom view to preview them. But if i clicked on MIME type column , It opens a separate view with the mapped viewer
So I need to remove this column or make it un-clickable
1) I am passing search values to content list's "Search with values" event , from where can i handle Content List's contend loading ,any Dojo Event i can use ?
2) With Script Adapter can i do this without going for a "response filter"
Edit :
As Nicely explained by "Ivo Jonker" (in his answer - "or try to specifically locate the widgets on your page" and with his example code)
responsed = page.ContentList8.ecmContentList.getResultSet();
var cols = responsed.structure.cells[0];
for (i=cols.length-1; i>0; i--){
var col = cols[i];
if (col.field=="mimeTypeIcon")
cols.splice(i,1);
}
page.ContentList78.ecmContentList.setResultSet(responsed);
I simply remove this row. Thanks Again and lovely blog , hope you keep posting more great articles.
The values passed through the Search With Values event will eventually be handled by the icm.pgwidget.contentlist.dijit.DocumentSearchHandler
that in turn creates a SearchTemplate to execute the search (ecm.model.SearchTemplate.prototype.search). One option would be to aspect/before/around the DocumentSearchHandler#query to manipulat the searchresults and by that way to remove the column.
The wiring however does not provide any handles to achieve this for a specific query-resultset combination leaving you to either fix this on a global scale (icm.pgwidget.contentlist.dijit.DocumentSearchHandler.prototype#query), or try to specifically locate the widgets on your page.
Personally, taking into account #2, i'd go for the responsefilter-option if you feel the global solution wouldn't be a problem, or alternatively i'd personally prefer to create a simple ICM widget that instantiates/implements a "plain" ecm.widget.listView.ContentList and exposes a wire to set the ecm.model.Resultset.
You'd then be able to create your own Searchquery in a scriptadapter, remove the column, and pass the resultset.
The script adapter could be something like:
var scriptadapter=this;
var queryParams={};
queryParams.query = "SELECT * FROM Document where id in /*your list*/";
queryParams.retrieveAllVersions = false;
queryParams.retrieveLatestVersion = true;
queryParams.repository = ecm.model.desktop.repositories[0];
queryParams.resultsDisplay = {
"sortBy": "{NAME}",
"sortAsc": true,
"columns": ["{NAME}"],
"honorNameProperty": true};
var searchQuery = new ecm.model.SearchQuery(queryParams);
searchQuery.search(function(response/*ecm.model.Resultset*/){
//remove the mimeTypeIcon
var cols = response.structure.cells[0];
for (i=cols.length-1; i>0; i--){
var col = cols[i];
if (col.field=="mimeTypeIcon")
cols.splice(i,1);
}
//emit the resultset to your new contentlist, be sure to block the regular synchrounous output of the scriptadapter
scriptadapter.onPublishEvent("icm.SendEventPayload",response);
//The contentlist wire would simply do contentlist.setResultSet(response);
});

Create UI to manage many to many relationship between classes in custom module in Kentico

I'm attempting to store and manage some relational data within Kentico's (Kentico 10) database. I'm having trouble fitting this into Kentico's best practices.
I have the following data:
1. Company - a list of companies
2. PostalCode - a list of international postal codes, their country and their lat/long
3. CompanyPostalCodes - many to many table of postal codes served by each company AND a Boolean flag that describes how the company serves that postal code (The postal codes are service / delivery areas)
I've read the documentation on creating custom modules
and have successfully created a module, classes and UI to manage companies and postal codes.
I've also read through documentation on creating a binding class
and have created one. (I've added the aforementioned Boolean field there too.)
I am stuck at trying to create a UI to manage the many to many relationship between them. Ideally, I would have a way to select multiple postal codes (and key in postal codes that do not yet exist in the PostalCode table) from the Edit Company page. I would also need to be able to set the flag field for each postal code on a given company's edit page. (If this is overly complicated, I could have a tab for postal codes with the flag and another tab for those without.) But I am open to any suggestion on how to manage the relationship in the UI.
Any suggestions?
If your binding object is more than just a reference to two tables (it has fields) then here are a couple options.
First make sure your edit in the company is the ui page type vertical listing, and under that add a "general" page of type edit object, along with your binding UI. Then...
Use the binding UI template, but extend the ui and adjust the listing to point to a custom unigrid xml that has an edit so you can not only add bindings but edit the object.
Do #1 except modify the unigrid to have the extra column for the check box, alter that columns rendering so it returns a check box object (the flag check box) and add a save button to the header (all doable through a ui extender) that loops through the items and saves the company postal with check box)
Have a separate object listing UI page that is an object listing on the binding object, so you have one to create the bindings, another to edit them. Note sometimes you can't select the object because it's a binding object in the properties and you need to click the black arrow next to the drop down and manually type in the class.
Create a completely custom object listing or an extended unigrid and do it however you want.
I've done all 4 personally, #2 is probably the most complex but smoothest management. If your want to pick one you want to go with I can give some samples!
------------------- EDIT -------------------
On second look, #2 you can't really extend the save functionality, i had to go a custom route (#4) to have a UniGrid that had changable fields that could be retrieved on save.
Here's the Code
// ASCX
<!-- DO NOT Arranged or add to the columns without adjusting the back end code, as it references by index -->
<div class="GridContent">
<cms:UniGrid EnableTheming="true" ShowActionsMenu="false" ShowActionsLabel="false" ShowExportMenu="false" ShowObjectMenu="false" runat="server" ID="gridSizes" OrderBy="enabled desc, SizeOrder" AllColumns="SizeID, Enabled, SizeDisplayName, PriceAdjustment, Upcharge, VendorUpcharge" ApplyPageSize="false" PageSize="##ALL##">
<GridActions Enabled="false" />
<GridColumns>
<ug:Column runat="server" Source="SizeID" Caption="SizeID" CssClass="hidden" AllowSorting="false" />
<ug:Column runat="server" Source="Enabled" Caption="Enabled" ExternalSourceName="Enabled" AllowSorting="false" />
<ug:Column runat="server" Source="SizeDisplayName" CssClass="DisplayName" Caption="Size Display Name" AllowSorting="false" />
<ug:Column runat="server" Source="Upcharge" Caption="Upcharge" ExternalSourceName="Upcharge" AllowSorting="false" />
<ug:Column runat="server" Source="VendorUpcharge" Caption="Vendor Upcharge" ExternalSourceName="VendorUpcharge" AllowSorting="false" />
</GridColumns>
<PagerConfig ShowPageSize="false" ShowDirectPageControl="false" PageSizeOptions="##ALL##" runat="server" Visible="false" />
</cms:UniGrid>
</div>
<cms:FormSubmitButton runat="server" ID="btnSaveItems" OnClick="btnSave_Click" />
// Code Behind
private void SetSizesTable()
{
QueryDataParameters parameters = new QueryDataParameters();
parameters.Add("#SkuID", GetSkuID());
int totalRecords = 0;
DataSet ds = YourDataCallHere;
gridSizes.DataSource = ds;
gridSizes.OnExternalDataBound += GridSizes_OnExternalDataBound;
gridSizes.DataBind();
}
private object GridSizes_OnExternalDataBound(object sender, string sourceName, object parameter)
{
// Replace the Enabled and Upcharge with actual controls, this way the user can adjust them
// and then those values retrieved when saved.
switch(sourceName.ToLower())
{
case "enabled":
var enabled = ValidationHelper.GetBoolean(parameter, false);
CheckBox cbxEnabled = new CheckBox();
cbxEnabled.Checked = enabled;
cbxEnabled.TabIndex = 100;
return cbxEnabled;
case "upcharge":
case "vendorupcharge":
var price = ValidationHelper.GetDecimal(parameter, 0);
CMSTextBox txtBox = new CMSTextBox();
txtBox.Text = price.ToString("F2");
txtBox.TabIndex = 1000;
return txtBox;
default:
return parameter;
}
}
// Save logic here
protected void btnSave_Click(object sender, EventArgs e)
{
// Loop through the actual control rows so we can retrieve the values and update.
ControlFinder<GridViewRow> GridViewRowFinder = new ControlFinder<GridViewRow>();
GridViewRowFinder.FindChildControlsRecursive(gridSizes);
bool ErrorOccurred = false;
// Skip the first and last as they are the header / action rows
foreach (GridViewRow RowItem in GridViewRowFinder.FoundControls.Skip(1).Take(GridViewRowFinder.FoundControls.Count()-2))
{
try
{
// Retrieve the values from the controls. These are based on the Cell index so any modification to the
// UniGrid may break this and need updating!
int SizeID = ValidationHelper.GetInteger(((LiteralControl)RowItem.Cells[1].Controls[0]).Text, -1);
bool isChecked = ((CheckBox)RowItem.Cells[2].Controls[0]).Checked;
decimal Upcharge = ValidationHelper.GetDecimal(((CMSTextBox)RowItem.Cells[4].Controls[0]).Text, 0);
decimal VendorUpcharge = ValidationHelper.GetDecimal(((CMSTextBox)RowItem.Cells[5].Controls[0]).Text, 0);
if (Upcharge > 0 || VendorUpcharge > 0)
{
isChecked = true;
}
// Grab any existing Sku Size
var ExistingSkuSize = SkuSizeInfoProvider.GetSkuSizeInfo(GetSkuID(), SizeID);
// Update the Sku Size
if (!isChecked && ExistingSkuSize != null)
{
// Delete existing since unchecked
ExistingSkuSize.Delete();
}
else if (isChecked && ExistingSkuSize == null)
{
// Create new one since it does not exist
SkuSizeInfo newSkuSize = new SkuSizeInfo();
newSkuSize.SkuID = GetSkuID();
newSkuSize.SizeID = SizeID;
newSkuSize.Upcharge = Upcharge;
newSkuSize.VendorUpcharge = VendorUpcharge;
newSkuSize.SkuSizeGuid = Guid.NewGuid();
newSkuSize.SkuSizeLastModified = DateTime.Now;
newSkuSize.Insert();
}
else if (isChecked && (ExistingSkuSize.Upcharge != Upcharge || ExistingSkuSize.VendorUpcharge != VendorUpcharge))
{
// Just update the upcharge
ExistingSkuSize.Upcharge = Upcharge;
ExistingSkuSize.VendorUpcharge = VendorUpcharge;
ExistingSkuSize.Update();
}
} catch(Exception ex)
{
ErrorOccurred = true;
EventLogProvider.LogException("ProductSizes", "UPDATEERROR", ex, additionalMessage: string.Format("Unable to Set/Update the Sku Size for SkuID {0}, this could be because the grid was altered.", GetSkuID()));
}
}
if(ErrorOccurred)
{
AddWarning("An error occured on some items while saving, please check the Event Log.");
} else
{
AddConfirmation("Product Sizes Updated.");
// Force refresh as otherwise it messes up
URLHelper.Redirect(HttpContext.Current.Request.Url.PathAndQuery);
}
}
}

Get SelectedItem in Dropdownlist without FormMethod.Post

I have a dropdownlist that is bound to a list in my model. Model.list_IDs
The current page is an "editing" page where a user would inherently change a property of the model by using the dropdownlist.
#Html.DropDownListFor(model => Model.ID, Model.list_IDs, new { style = "width:250px" })
The items within the dropdownlist are not intuitive, so I would like to provide a button, that when clicked, retrieves additional information via a stored procedure (called from the controller via a method called GetDetails)
The button is created through an action link, and I plan to display a partial view (like a focused pop up window) that shows the additional information once the button is clicked.
#Html.ActionLink(" ", "GetDetails", new { id = Model.ID.ToString() }, new { #class = "lnkMagnify16", title = "View Details" })
Obviously Model.ID.ToString() is incorrect, because it will only send the model's current ID, rather than the ID currently selected in the dropdownlist.
How can I change Model.ID.ToString() to represent the dropdownlist's current selected item?
I know there is a way to do this using FormMethod.Post (Get selected item in DropDownList ASP.NET MVC) but I do not want to reload the page with a submit button. I'd also like to avoid "third party" approaches like JavaScript if possible.
JavaScript is not a "third party" approach, and it's also the only way to accomplish what you want here. Whether by a standard form post or via AJAX (JavaScript), you must make a new request to the server to get the new information you want.
Now, since all you're specifically after is a way to dynamically update the the Model.ID value in the link, you can do that without AJAX or a form post, but you still must use JavaScript, since all dynamic behavior client-side originates from JavaScript. Basically, you'd need to bind to the change event of the dropdown and alter the href property of your link.
document.getElementById('ID').addEventListener('change', function () {
var selectedId = this.options[this.selectedIndex].value;
document.getElementById('AwesomeLink').href = // alter `href` with `selectedId`
});
However, that link is still going to change the whole view if the user clicks it. If you truly want the user to stay on the page, then you'll need to fetch the response of that link using AJAX and then either add it to the page somehow, whether that be directly or via a modal popup (which will required yet even further JavaScript to achieve). Also, it's not clear how the Model.ID value ends up in the URL, i.e. whether it's part of the path (/some/url/{id}/) or as a query string param (/some/url/?id={id}). If it's the former, I'd recommend switching to the latter, as it will make it much easier to build your URL client-side.
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", function (response) {
// add response.responseText to the DOM
});
xhr.open("GET", "/some/url/?id=" + selectedId);
xhr.send();

how to open multiple windows from the controller in asp.net MVC

How to open multiple windows from the controller?
For example, I have a controller that gets all the clients and for each client I want that his info is displayed in a different window or tab.
Is that possible using asp.net MVC 4?
I've done this exact thing, it was based on a button click which would fire multiple windows based on the parameters. Here's what you could do. Your controller would be like this...
[HttpPost]
public ActionResult CopyOpportunitySave(CopyOpportunityViewModel model)
{
ViewModel model = new ViewModel();
foreach (var url in "List object that contains URLS")
{
model.ClientURLs.Add(url);
}
return View("CopyOpportunity", model);
}
So now your Model class will have a property like
public List<string> ClientURLs{get; set;}
which is populated by all the URLs you will need to do a window.Open.
and Now in your view you can say
#foreach (var u in ViewModel.ClientURLs)
{
<script>
window.open(u);
</script>
}
just as long as the script inside the foreach loop is getting executed, it will work
This won't be possible by just using native MVC 4. However, one could think of some work-arounds which might resemble your requirements. For starters, is it necessary to actually open a 'browser tab' per client, or could this also be a tab page per client in one single HTML-document. If the latter is an option, you could take a look at different 'tab' controls being available in HTML.
If your really want a new browser window per client, my hint would be to return the list of possible clients to one HTML page and then use scripting in that HTML page to open a popup window for each client. For each popup / client an additional request needs to be send to the server.
Just make the link (<a></a>) that opens the client information have a target = "_blank":
John Smith
If you are using Html helpers it could be like this:
#Html.ActionLink("John Smith", "Details", "Clients", new {id = 1}, new { target = "_blank"})
This will open a new browser tab with the page you need.

Using Rich Text Editor in Orchard CMS Custom Module

I am creating a custom module in Orchard CMS 1.7.1
So far all is going well (ish)
One of my properties is a string (called InfoBubbleHtml).
I would like to utilise the rich text edit for this input on my modules create/edit form.
Is there a helper already in place that would render my property as a rich text area rather than just a textarea or input field?
If not, what would i need to do to be able to have this property render the rich text editor?
Thanks in advance.
Update
As an update/addition to this question... Any HTML that I enter into my current text area is being ignored; guessing to do with request validation. how is this disabled for my controller so i can allow html in the admin?
Update 2
Ok, so i have figured out how to add this to my view by using:
#Display.Body_Editor(Text:Model.InfoBubbleHtml,EditorFlavor:"html")
Any idea how to set the ID of the editor?
#Display.Body_Editor(Text:Model.InfoBubbleHtml,EditorFlavor:"html") is rendering a shape named Body.Editor.cshtml.
This file lives in : Orchard.Web\Core\Common\Views\Body.Editor.cshtml
And it's content is
#using Orchard.Utility.Extensions;
#{
string editorFlavor = Model.EditorFlavor;
}
#Html.TextArea("Text", (string)Model.Text, 25, 80, new { #class = editorFlavor.HtmlClassify() })
So using this Shape you cannot set Id, Model is the anon that you send on the Display (Text and EditorFlavor).
Shapes.cs on Orchard.Core/Common is hooking an alternate using the EditoFlavor string.
public void Discover(ShapeTableBuilder builder) {
builder.Describe("Body_Editor")
.OnDisplaying(displaying => {
string flavor = displaying.Shape.EditorFlavor;
displaying.ShapeMetadata.Alternates.Add("Body_Editor__" + flavor);
});
}
So the final file that is rendered : TinyMVC\Views\Body-Html.Editor.cshtml
using Orchard.Environment.Descriptor.Models
#{
var shellDescriptor = WorkContext.Resolve<ShellDescriptor>();
}
<script type="text/javascript">
var mediaPickerEnabled = #(shellDescriptor.Features.Any(x => x.Name == "Orchard.MediaPicker") ? "true" : "false");
var mediaLibraryEnabled = #(shellDescriptor.Features.Any(x => x.Name == "Orchard.MediaLibrary") ? "true" : "false");
</script>
#{
Script.Require("OrchardTinyMce");
Script.Require("jQueryColorBox");
Style.Require("jQueryColorBox");
}
#Html.TextArea("Text", (string)Model.Text, 25, 80,
new Dictionary<string,object> {
{"class", "html tinymce"},
{"data-mediapicker-uploadpath",Model.AddMediaPath},
{"data-mediapicker-title",T("Insert/Update Media")},
{"style", "width:100%"}
})
You need to add this to the template and include another parameter in the TextArea Dictionary parameter named : {"id", "THE ID YOU LIKE"}.
Take a look at the docs about Shapes if you want to learn more Docs
You can patch the TinyMVC\Views\Body-Html.Editor.cshtml file to enable it to use custom Name/ID for the text area. The modification is quite simple - replace
#Html.TextArea("Text", (string)Model.Text, 25, 80,
with
#Html.TextArea((string)Model.PropertyName ?? "Text", (string)Model.Text, 25, 80,
Now you can use additional parameter PropertyName in your call:
#Display.Body_Editor(PropertyName:"InfoBubbleHtml", Text:Model.InfoBubbleHtml, EditorFlavor:"html")
The downside is that you are patching a foreign code and have to reapply this patch every time this file in TinyMVC module is updated.