I have the following partial view which renders a drop down list:
#model MartinDog.Core.Models.Section
#{
#Html.DropDownListFor(x=>x.Name
, new SelectList(Model.Dock.DockTemplate.Columns,
"Id", "FriendlyName",
Model.DockTemplateColumn.Id.ToString())
, new { #id = "ddlb_dockTemplateColumns" +
Model.Id.ToString()})
}
I render it on my page like so:
#{Html.RenderPartial("_Admin_Page_DockTemplateColumnDropDown", Model);}
The partial view is rendered once for every Section object. A Section object one I've created and is editable in a jquery dialog box (change the name, display order, dock template column, etc.)
On the test page I am using, this Section dialog box is rendered four times (as there are four of them in my parent object).
The problem:
*The SelectedValue in the SelectList for the drop down never gets set* - that is to say, the correct item in the drop down list is never selected when the dialog is displayed and I can't quite work out why.
I thought it might be because the drop down is rendered four times, so I tried rendering it for just one of the 'Sections' but still the same problem.
Anyone know what I can do?
***edit
Not sure if I'm doing it in a sucky way. I had thought of building the dialog just once with jquery and json but I'd prefer to do it this way as it just feels cleaner.
I do this:
In controller action (Edit for example):
ViewData["ProvinceID"] = new SelectList(dc.Provinces.OrderBy(p => p.NameAr), "ID", "NameAr", factory.ProvinceID);
and Markup:
<%: Html.DropDownList("ProvinceID") %>
See? so it is a list of factories and it has a Province field and what I want you to notice is the 4th parameter in the SelectList constructor, I passed factory.ProvinceID so the DropDownList knows which option to be set on. Otherwise the DropDownList will show the default value (the first one).
P.S: It is your job to change to Razor syntax; I don't use it.
Hope that helps.
Doh... fixed - totally my own fault.
I had set up my html.dropdownlistfor like so
#Html.DropDownListFor(x=>x.Name,
When it should've been like so:
#Html.DropDownListFor(x=>x.DockTemplateColumn.Id,
Setting the first argument to x=>x.DockTemplateColumn.Id (which uniquely identifies the items in my list) instead of x.Name fixed the issue straight away.
Just thought I'd post it here in case someone else makes the same mistake I did.
edit
Found the answer here:
C# mvc 3 using selectlist with selected value in view
Related
I have an view that extends the current project view, where we add multiple tabs (notebook pages) to show information from other parts of a project.
One of these pages is an overview page that summarizes what is under the other tabs, and I'd like to link the headlines for each section directly to each displayed page. I've currently solved this by using the index of each tab and calling bootstrap's .tab('show') method on the link within the tab:
$(".overview-link").click(function (e) {
e.preventDefault();
var sel = '.nav-tabs a:eq(' + $(this).data('tab-index') + ')';
$(sel).tab('show');
});
This works since I've attached a data-tab-index="<int>" to each header link in my widget code, but it's brittle - if someone adds a tab later, the current indices will be broken. Earlier I relied on the anchor on each tab, but that broke as well (and would probably break if a new notebook page were inserted as well).
Triggering a web client redirect / form link directly works, but I want to show a specific page in the view:
this.do_action({
type: 'ir.actions.act_window',
res_model: 'my.model.name',
res_id: 'my.object.id',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
target: 'current'
});
Is there any way to link / redirect the web client directly to a specific notebook page tab through the do_action method or similar on FormWidget?
If I understood well you want to select the tab from the JavaScript (jQuery) FormWidget taking into account that the id could change if anybody install another module that adds another tab
Solution 0
You can add a class to the page in the xml form view. You can use the id of the element selected by this class name in order to call the right anchor and select the right tab item. This should happen when the page is completely loaded:
<page class="nb_page_to_select">
$('a[href=#' + $('.nb_page_to_select').attr('id') + ']').click()
NOTE: As you have said the following paragrah I assume that you know where to run this instruction. The solution I suggest is independent of the index.
This works since I've attached a data-tab-index="<int>" to each
header link in my widget code, but it's brittle - if someone adds a
tab later, the current indices will be broken. Earlier I relied on the
anchor on each tab, but that broke as well (and would probably break
if a new notebook page were inserted as well).
Solution 1
When the page is loaded you can get the tab list DOM object like this:
var tablist = $('ul[role="tablist"]')
And then you can click on the specifict tab, selecing by the text inside the anchor. So you don't depend on the tab index:
tablist.find('a:contains("Other Information")').click()
I think if you have two tabs with the same text does not make any sense, so this should be sufficient.
Solution 2
Even if you want to be more specific you can add a class to the notebook to make sure you are in the correct notebook
<notebook class="nt_to_change">
Now you can use one of this expressions in order to select the tab list
var tablist = $('div.nt_to_change ul.nav-tabs[role="tablist"]')
// or
var tablist = $('div.nt_to_change ul[role="tablist"]')
Solution 3
If the contains selector doesn't convince you because it should be equal you can do this as well to compare and filter
tablist.find('a').filter(function() {
return $.trim($(this).text()) === "Other Information";
}).click();
Where "Other Information" is the string of the notebook page
I didn't tried the solution I'm giving to you, but if it doesn't work at least may be it makes you come up with some idea.
There's a parameter for XML elements named autofocus (for buttons and fields is default_focus and takes 1 or 0 as value). If you add autofocus="autofocus" to a page in XML, this page will be the displayed one when you open the view.
So, you can try to add this through JavaScript, when the user clicks on the respective link -which honestly, I don't know how to achieve that by now-. But you can add a distinctive context parameter to each link in XML, for example context="{'page_to_display': 'page x'}". When you click on the link, I hope these context keys will arrive to your JS method.
If not, you can also modify the fields_view_get method (here I wrote how to do that: Odoo - Hide button for specific user) to check if you get the context you've added to your links and add the autofocus parameter to the respective page.
As you said:
This works since I've attached a data-tab-index="" to each header
link in my widget code, but it's brittle - if someone adds a tab
later, the current indices will be broken.
I assume that your app allow multi-user interaction in realtime, so you have to integrate somewhere in your code, an update part function.
This function will trig if something has changed and cleanout the data to rebuilt the index in order to avoid that the current indices will be broken.
In my app I want to create a new Risk ( an instance of Risk object ) and when it is created I want to display 5 checkboxes and three radio buttons. Selected options are specific to each instance of Risk object.
Later I want to display a list of all added Risks with an Edit option button on each Risk. I want my app to restore the view specific to a selected Risk ( when an Edit button on a selected risk is clicked ) - with Risk name, all checkboxes and radio-buttons checked as selected previously. And I want to be able to edit these checkbox selections again so that all new changes were properly reflected in MySQL.
As a newbie in Thymeleaf I did the following:
<div th:each="top : ${topic}">
<input type="checkbox" th:field="*{topic}" th:checked="${top.checked}" th:value="${top.name}"/><label th:text="${top.name}">Something is wrong !</label>
</div>
I am sure that Controller and Hibernate/MySQL part works properly ( I checked using Logs ).
This works just fine - but only if I have selected only one checkbox ( initially when I added a risk ).
If I select more than one checkbox (when adding a risk) and later select this risk for editing no checkboxes are checked.
What is wrong ?
After some research I found the following text in Thymeleaf’s documentation:
“… th:field would have taken care of that and would have added a checked="checked" attribute to the corresponding input tags.”.
Also I found this guidance :
http://forum.thymeleaf.org/The-checked-attribute-of-the-checkbox-is-not-set-in-th-each-td3043675.html
Then I managed to develop a couple of small apps and I want to share what I found out and hope it will help someone.
( may be it is too detailed for experienced people, but I want it to be clear to all )
I do not want to repeat what is already in the above-mentioned Thymeleaf’s forum page ( see Administrator’s first response / explanation for detail - second in forum thread ) - just want to make a small summary and stress out few points:
you indeed do not need to add ‘checked’ when using th:each;
you must add th:field=“{…}” which should have the name of the field in your model class (referred by Thymeleaf as form-backing bean - th:object ) to which checkboxes are related. More on this: I stated above that my ‘form-backing bean’ was Risk.java. And for each Risk object instance the selected checkboxes represent topics(s) specific to this Risk instance. And selected topics are assigned to field ‘topic’ of Risk.java's instance (and hence in related table in MySQL when instance is saved). That field’s name should go inside th:field=“{…}” as th:field=“*{topic}” in my case. When you select checkboxes Thymeleaf will save selected values to Risk.java’s topic field using its setTopic method and when it needs to restore view Thymeleaf will use Risk.getTopic method to get info on earlier selected items.
all values of checkboxes (or radio-buttons ) should come from another source - it could be an Enum if you need a static set of checkboxes or if you need checkboxes to be dynamically generated you can use a class ( I needed static set of checkboxes for my app, but I decided to try to create a dynamic one as well - see links to my Github repo’s below to see the code I managed to develop ). So for my app I created an Enum Topics with all values for checkboxes and Enum Types with all values for radio-buttons. Then in your controller class you should add all values to Model’s attribute - I did this as I used an Enum:
model.addAttribute("topics", Topics.values());
model.addAttribute("types", Types.values());
(if you need dynamic ones do the following:
model.addAttribute("topics", topicsService.findAll());
model.addAttribute("types", typesService.findAll());
)
Then you should have something similar to:
<div>
<div th:each="top : ${topics}">
<input type="checkbox" th:field="*{topic}" th:value="${top.id}"/><label th:text=" | ${top.name}|">Something is wrong !</label>
</div>
</div>
<div>
<div th:each="typ : ${types}">
<input type="radio" th:field="*{type}" th:value="${typ.id}"/><label th:text="| ${typ.name} |">Something is wrong !</label>
</div>
</div>
where:
as mentioned, th:field=“{topic}" corresponds to form-backing Model class - Risk.java’s field. Same for th:field=“{type}" ;
topics in th:each="top : ${topics}” should match with attribute name you provided in controller.
And the MOST important part is that th:field=“*{topic}” should return an array.
th:field=“*{topic}” returning an array of selected items and th:each returning array of all options Thymeleaf should now be able to mark checkboxes / radio buttons as checked when values in first array match values in second array.
Since in case of radio buttons you can select only one option th:field=“*{type}” does not actually return an array - it returns only one item. But in case of checkboxes it should be an array - so the ‘topic’ field in Risk.java must return an array.
For this we need a converter - a class named e.g. StringListConverter that implements AttributeConverter….
( I learned how I can do it here. If not this answer in www.stackoverflow.com I would not be able to finalyze this app and would not be writing all this:
https://stackoverflow.com/a/34061723/6332774 )
Then in your form-backing model class - Risk.java in my case you need to do something like :
#Convert(converter = StringListConverter.class)
private List<String> topic = new ArrayList<>();
private String type;
Type can simply be a String.
That is it.
( I wanted to display checkboxes in table form indicating number of columns needed - I could do it, but I am not sure how clean it is or how safe it is. Related code is in riskformtable.html in example project linked below.
I posted a related question here - Thymeleaf - Displaying checkboxes in table form when using th:each - is what I am doing safe?
Also I wanted to use different color for all risk items with even sequential number in my risk’s list - it is in index.html
See full example code using links below )
Links to my GitHub repos:
example with static set of checkboxes : https://github.com/aripovula/Spring-Thymeleaf_checkboxes_and_radiobuttons
example with dynamically generated checkboxes : https://github.com/aripovula/Spring-Thymeleaf_dynamic_checkboxes_n_radio-buttons
I would like to pass the file name of a partial view as data retrieved from the viewbag as such:
<div id="Zone1">#Html.Partial(ViewBag.ZoneControl1)</div>
Where the "ZoneControl1" property of the ViewBag is the name of the desired partial view retrieved from elsewhere (i.e. database, web service, etc.). If I include the text as a literal string i.e.:
<div id="Zone1">#Html.Partial("Controls/MyPartial")</div>
It works just fine. If I pass that literal string as a property of the ViewBag from the controller, or even just create a variable in the consuming view, i.e.:
#{string zone1 = "Controls/MyPartial";}
<div id="Zone1">#Html.Partial(zone1)</div>
It doesn't work. The page appears to be loading but never displays anything in the browser. Again, this works fine if I hardcode the partial view name, but not if it is passed as data from a variable or property. Anyone know why this is happening? If this is intended or unavoidable behavior, is there a workaround?
You can't use dynamic in Html.Partial (which is what ViewBag is) because it accepts only strings. One quick way around this would be to cast your ViewBag.ZoneControl:
#Html.Partial((string)ViewBag.ZoneControl1)
As for the second part (zone1 = "Controls/MyPartial") I was unable to duplicate that.
The following code is what I wrote to test it and it works just fine.
#{ string zone1 = "Controls/MyPartial"; }
<div>#Html.Partial(zone1)</div>
I assume the answer with casting the ViewBag is what you're really looking for in this case.
Well, I have it working now and I'm not exactly sure what fixed it. I copied the razor code\markup and deleted that view and created a new view and pasted in the old code. The only difference was that when I created the new view, via the wizard, I specified to NOT use a master page and the resulting page had code to specify:
#{
Layout = null;
}
The original page was created using a master page and then I changed my mind and took out the layout directive entirely. Anyway, after making those changes, it WORKED! So, I initially surmised that the reason was that a view must specify "layout = null" if not using a master page. BUT, I then took out the "layout = null" code in this new page and it still worked! So... not sure what went wrong, but to sum up:
As #BuildStarted correctly noted, you can use a property of the ViewBag object as the partial view path, but you must cast it as a string for it to work properly. So, the premise for this question was incorrect and something else was mucking things up. Just not sure what.
After reading a few question/answers here I have managed to work out how to add a Select list to a form and fill it with data, like so:
#Html.DropDownList("S", new SelectList(ViewBag.S, "Id", "Nme"), "-- Sel a S --")
And it works perfectly. However I would like to add some client-side Validation To validate whether the user has selected an option and not left it at the Default.
I'm using the standard jquery stuff that comes with mvc 3, so presumably I have to do something with HTML.ValidationMessage, but what?
And Can't for the life of me work out how.
TIA.
Ok I had a look through how its done in JQuery land and found just by adding an htmlattribute like so:
new {#class='required'}
to my Html.DropDownList statement, and adding validationMessage, fixes the problem for me.
If you are using the jquery validation then you may simply add the css class reuired and have the required validation for the dropdownlist, provided the default value is empty.
First, if a dropdown is required, add the [Required] attribute to your model property.
Then, enable client side validation somewhere at the top of your view:
<% Html.EnableClientValidation() %>
Then add
#Html.ValidationMessage("S", "*")
Above will only work if the 'default' selection has a null or empty value.
Also ensure you've got the correct js files referenced in the script tags at the top of your page
I'm trying to pass a list of a few items to a view via the ViewData to create a drop down list. This shouldn't be too difficult, but I'm new to MVC, so I'm probably missing something obvious.
The controller assigns the list to the ViewData:
ViewData["ImageLocatons"] = new SelectList(gvr.ImageLocations);
and the view tries to render it into a drop down list:
<%= Html.DropDownList("Location", ViewData["ImageLocations"] as SelectList) %>
However, when I run it, I get this error:
There is no ViewData item of type 'IEnumerable' that has the key 'Location'.
Any ideas why this isn't working? Also, shouldn't it be looking for the key "ImageLocations" rather than location?
If you use:
ViewData["Location"] = new SelectList(gvr.ImageLocations);
and
<%= Html.DropDownList("Location") %>
Your life will be a lot easier.
Also check out the typo (missing i) when setting the ViewData in your example (ImageLocatons => ImageLocations). This causes the second parameter you pass to DropDownList to be null. This will cause the MVC engine to search for Location.
Is it possible that your ViewData was reset?
Try putting a break point in your View on the line where you emit the drop down list.
Then do a quick watch on ViewData["ImageLocations"].
Make sure that there is a value here when the view tries to use it.