Is there a way to break out of a <g:each>? I have a page wherein I'm iterating through a list and I have to make sure that a checkbox is checked if that was the value stored in DB.
To make it a little clearer, please consider something like:
<g:each in=${list1}>
<g:each in=${list2}>
<g:if test="${list1.id == list2.id}">
<input type="checkbox" ... checked="checked" />
</if>
</g:each>
...
</g:each>
where list1 is, say Domain1.list() (i.e. ALL possible values) and list2 is Domain2.find(...) (i.e. SELECTED values)
In the g:each, I need to display ALL of list1 (hence, the "..." after the inner each) with a checkbox but I need to make sure that those in list2 (user-selected items that were saved to DB) should be checked accordingly (if statement).
Now, if the checked status was changed on the first iteration, i need to get out of the inner each... any way to do this?
Thanks!
Nope, not with the each clause.
I'd just write my own taglib that takes list1 and list2 and does the iteration for you, yielding back to the
<g:eachCheckedItem list1="${list1}" list2="${list2}">
<input type="checkbox" ... checked="checked"/>
</g:eachCheckedItem>
And in your taglib class:
def eachCheckedItem = { attrs, body ->
def list1 = attrs.list1
def list2 = attrs.list2
list1.findAll { list2.contains(it) }.each {
out << body(listItem: it) // access to listItem variable inside gsp
}
}
Something like that (tuned to your specific problem) is easy to write, and also cleans up your gsp file quite a bit. I use these kinds of custom iterators all the time in my taglibs.
If I understood you correctly, you need something like this:
<g:each var="elem1" in="${list1}">
<g:if test="${list2.any{it.id==elem1.id}}">
<input type="checkbox" checked="checked" />
</g:if>
...
</g:each>
There is no g:any tag, but as Ted pointed out, it would be easy to write one (left as an exercise to the reader). Then you could simplify the the inner tag to something like this:
<g:any test="${it.id==elem1.id}" in="${list2}">...</g:any>
You should do this in the model, so you then only have a simple loop in the view. Then it's just a matter of making the controller call Domain.findMyList() or whatever.
For the googlers looking for an answer to the original poster's question, there isn't a break command in gsp. There are some better responses here, the best one of which in my opinion is try and make use of .findAll { .. } to find only the set you would expect to work on prior to a 'break'.
http://markmail.org/message/tt2einl3ntwgzdep#query:grails%20gsp%20break%20out%20of%20loop+page:1+mid:nzhgwdsgkrwkurt4+state:results
Related
How can I sort the options in by name in the GSP file?
<select name="degree" id="degree" class="required" value="${userDegree}" >
<option value="">Please Select</option>
<g:each in="${Degree.list()}" var="degree">
<g:if test="${degree.name == userDegree}">
<option value="${degree.name}" selected="selected">${degree.name}</option>
</g:if>
</g:each>
</select>
Yes you can fetch list of Degree objects and sort them on your view but it is not recommended to mix view with db logic.
Say later you want to filter out the list of invalid degrees, what will you do? Add the logic on the view? That is going to be messy.
So I suggest you do it like this, get the list of Degrees on your controller (making this controller call another service to give you the list of degrees is even more good) and pass them to the UI.
degrees = Degree.list(sort:"name", order:"asc")
then pass it to the view in the Model map.
Your geach will be like this:
<g:each in="${degrees.list()}" var="degree">
Please visit this link for recommendations while coding using grails by Burt Beckwith it is very important talk.
If you always want degree sorted by name in alphabetical order I recommend that you put this in your domain class mapping:
static mapping = {
...
sort name: "asc"
}
IMO you should move to controller the composition of the list, order there, and pass it the way you want it to the view and use it in the view with "from".
For the sorting itself look to Groovy/Grails : How to sort the list of objects by id
For the inclusion in the select look http://grails.github.io/grails-doc/2.4.5/ref/Tags/select.html
You can put the list order at the select tag as shown below:
<select name="degree" id="degree" class="required" from="{$Degree.list('sort':name, order: 'asc')}" value="${userDegree}" >
...
...
</select>
I have an EditorFor template that I call with an array of items (there may be a few) and I reference the indexes individually (not in a loop) so I can lay them out a certain way
#Html.EditorFor(x => Model.SomeViewModels[0], "SomeTemplate")
all works fine but ideally I want to reference the array by a nicer indexer
#Html.EditorFor(x => Model.SomeViewModels["Item to Find"], "SomeTemplate")
for better readability and some flexibility to cope if the index changes
so I was wondering if I could set up a custom indexer on the Model - like this
public ObjecToReturn this[string TextToFind]
{
get
{
return ObjectToReturn based on TextToFind
}
}
and it does work - and pulls the value through correctly however when I look at the Html the input fields all have names like name=[Item to Find].AnswerValue which I can see why but it messes up the post back
Just wondered if any clever person had worked out how I can send in Model.SomeViewModels["Item to Find"] to the EditorFor yet the underlying Html in the template reflects Model.SomeViewModels[0] (0 being the index of "Item to find" in the array) so the Postback works
Or maybe this is the wrong approach - I'm hoping I'm missing something straightforward.
Thanks in advance
By default the DefaultModelBinder binds collections where the indexers start at zero and are consecutive. You can make this work by adding a hidden input for a Index property (note Index is not a property of your model - its just a special value used for binding collections). For example
<input type="text" name="SomeViewModels[ABC]" value = "" />
<input type="hidden" name="SomeViewModels.Index" value="ABC" />
<input type="text" name="SomeViewModels[XYZ]" value = "" />
<input type="hidden" name="SomeViewModels.Index" value="XYZ" />
The addition of the hidden input where the value attribute matches the indexer value allows the DefaultModelBinder to correctly bind the collection
Where the server is sending the client a complex object and the goal is to transition from C#'s 'foreach' to KnockoutJS's 'data-bind="foreach: ' consider this code that populates a shopping cart with various pieces of info:
#{
foreach (var item in GetItems(Model))
{
<dt>
<input type="radio" id='mode_#(item.ID)' name="mode" value="#item.ID" />
#item.Label - $#item.PriceToAdd
</dt>
<dd>
#Html.Raw(item.Explanation) </dd>
}
}
}
Should the server's code be adjusted to flatten out the object before rendering the View or can KnockoutJS deal with unwrapping it? Would it get easier if the server sends JSON?
FOLLOWING UP:
It becomes clear the question boils down to mapping plugin and mfanto's first answer gets me part the way there:
self.items = ko.mapping.fromJS(#Html.Raw(JsonConvert.SerializeObject(Model.Items)));
firebug shows me output of:
self.items = ko.mapping.fromJS([{"ID":60},{"ID":62},{"ID":63},{"ID":64},{"ID":9}]);
Perhaps mapper fails because one of my Items (id=9) has different elements than the rest.
Probably I need to research one of the more advances usages of mapper?
FORMATTED OUTPUT COMPARES VALUES RETURNED BY JsonConvert vs. JavaScriptSerializer
...
self.itemsJSON = ko.mapping.fromJS(#Html.Raw(JsonConvert.SerializeObject(Model.Items)));
self.items = #Html.Raw(new JavaScriptSerializer().Serialize(Model.Items));
when the above code renders to a breakpoint in Firebug:
self.itemsJSON = ko.mapping.fromJS([{"ID":60},{"ID":62},{"ID":63},{"ID":64},{"ID":9}]);
self.items = [ //line breaks inserted for clarity
{"Explanation":"Item1's text.","Label":"Item1's Label","MsgConfirm":null,"PriceToAdd":1255,"TaxExempt":false,"PercentToAdd":0,"SortOrder":1,"ID":60},
{"Explanation":"Item2's text.","Label":"Item2's Label","MsgConfirm":null,"PriceToAdd":1255,"TaxExempt":false,"PercentToAdd":0,"SortOrder":2,"ID":62},
{"Explanation":"Item3's text.","Label":"Item3's Label","MsgConfirm":null,"PriceToAdd":295,"TaxExempt":false,"PercentToAdd":0,"SortOrder":3,"ID":63},
{"Explanation":"Item4's text.","Label":"Item4's Label","MsgConfirm":null,"PriceToAdd":395,"TaxExempt":false,"PercentToAdd":0,"SortOrder":4,"ID":64},
{"Explanation":null,"Label":"[foo]","MsgConfirm":null,"PriceToAdd":150,"TaxExempt":false,"PercentToAdd":0,"SortOrder":99,"ID":9}
];
thx
You don't need to flatten the object before you use Knockout. The ko.mapping plugin will create viewmodels with observable properties, and can handle complex nested objects.
To use it with an ASP.NET MVC model, use #Html.Raw() and a Json serializer (in this case Json.NET:
function AppViewModel() {
var self = this;
self.items = ko.mapping.fromJS(#Html.Raw(JsonConvert.SerializeObject(Model.Items)));
}
ko.applyBindings(new AppViewModel());
From there, you can use foreach:
<table>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: PriceToAdd()"></td>
</tr>
</tbody>
</table>
You can go either way with this. Render it on the server with Razor or render it on the client with knockout... The more fundamental question is where do you want to render it. There is no right or wrong answer here.
If you go with knockout, you need to deal with more than just having the server possibly flatten out your model. Knockout will require ajax requests to both read and then save your data and this is where the two solutions fundamentally differ I don't see any JavaScript as part of your solution and without that component, providing a ko solution is pretty impossible.
If you are thinking about using knockout simply as a client side templating engine, then something like jsrender is likely a better solution.
So I have two separate models, one 'items' model, the second a 'sites'
model... I'm using KO to bind this data to two separate elements on
the DOM (and working as needed), but I've found a need to replace one
of my bound columns on one of my models, with data from the other.
On my 'items' model, I have a site ID column, that I'd love to swap
with the actual 'SiteName' property on that model (simple name, value-
pair - SiteName, SiteId)... Does any one know a way to do this within
KO?
I really want to keep the model data itself in tact on the server side,
verses just creating a custom model on the server side that does it for me.
I'm sure I could give those elements a special class, and loop
through them and replace them manually with jQuery, but I thought that
KO might have an easier way of doing this.
Thanks!
I have tried something like this, but it doesn't seem to work (yes, I know div tags within a table element are not standards based, I just wanted to see if it would work, and if it did, I'd transition from the table to another formatting option)
<tbody data-bind="foreach: items">
<tr data-bind="click: updateItem">
<td data-bind="text: ItemName"></td>
<div data-bind="foreach: sites">
<div data-bind="if: items.SiteId = sites.SiteId">
<td data-bind="text: sites.SiteName"></td>
</div>
</div>
The jQuery to do this using my returned model is:
$(function () {
$('#allItems tr .siteIdCell').each(function () {
for (i in allSites) {
if (allSites[i].SiteId == $(this).html()) {
$(this).html(allSites[i].SiteName);
}
}
});
});
Where .siteIdCell is the class I applied to the columns using this value, and allSites is the object array I'm receiving via JSON.
Not sure if I'll get many responses, but I just figured I'd update this if anyone else has the same issue, and there's no ability to do this in KO.
I have seen lots of questions relating to this topic.
I am using asp.net MVC 1.0
Problem area
If I use
<%= Html.CheckBox("Status", true) %>
Then why It renders like
<input checked="checked" id="Status" name="Status" type="checkbox" value="true" /><input name="Status" type="hidden" value="false" />
I put this in foreach loop and I have 5 rows.
when I submit form with true,true,true,false,false
then I get true,false,true,false,true,false,false,false
i.e. for false => false.
for true => true,false
If I use
<input type="checkbox" value="true" name="Status" checked="checked" />
Then I don't get unchecked one's.
so how do I overcome form this problem?
Please don't post answer with using loop in formcollection object and checking each key!
I know this isn't the elegant one but this is what I did:
collection["response"].Replace("true,false", "true").Split(',').ToList();
In your example, when you submit form with true,true,true,false,false and you get
true,false,true,false,true,false,false,falseit is interesting to note that you are not actually getting eight values back, but five arrays that merely looks like this is the case because all of the values are joined.
I know you asked to not get a loop for your answer, but I can use one to demonstrate what is really happening here:
foreach (string key in postedForm.AllKeys) {
// "value" will contain a joined/comma-separated list of ALL values,
// including something like "true,false" for a checked checkbox.
string value = postedForm[key].GetValue;
// "values" will be an array, where you can access its first element,
// e.g., values[0], to get the actual intended value.
string[] values = postedForm.GetValues(key);
}
So, for your checked boxes, you'll get a values array with two elements, and for unchecked boxes, you'll get just a single-element array.
Thus, to answer your question how do you overcome this problem, the answer lies in using GetValues rather than GetValue, and thinking of your posted fields as arrays rather than strings.
Best of luck!
Personally I think having to check for "true,false" everywhere on the server is a pain. I wrote a jquery fix that will remove the extra hidden field created by the Html.Checkbox helper when a box is checked, then add it back if the box is unchecked. Server values will always be "true" or "false". Checkbox lists are kind of subjective in how you want them to act, which I discuss, but I'm removing "false" from the value set, which means the form value will be excluded if all boxes in the list are unchecked.
http://www.mindstorminteractive.com/blog/?p=386
I've had pretty good success using this technique. Please let me know if you try it out and have issues.
You'll have to do your own model binding for the CheckBox values.
Get the list of values from the FormCollection or Request.Form for that CheckBox id and replace true,false with true:
string chkBoxString = Request.Form["checkboxID"].Replace("true,false", "true")
Now you have a list of whether a CheckBox was selected or not.... do the rest yourself :)
It renders so because default binder requires the FormCollection to have a value for nonnullable parameters. Using this technique we are sure that the value will be sent even the checkbox is not checked (by default the value sent only when it's checked). If you use this controller method with just one html input you'll get error on form post with unchecked checkbox (value of checkbox will not be posted and binder will not know what to use for value of isItemSelected):
public ActionResult SomeActionMethod(bool isItemSelected)
You can try use something like this with just one html input:
public ActionResult SomeActionMethod(bool? isItemSelected)
But in this case isItemSelected will be null or will be true. And it will never become false.
Well there are couple of ways you can do based on your requirement.
I use this method.
<input type="checkbox" value="<%= item.ID %>" name="check" checked="checked")" />
This is may checkboxes.
On server side I will also have array of ID's of item in the model.
So I check it whether it is in array
var strArray = form["checkbox"]; //Request.form["checkbox"] or "FormCollection form" in action parameter; array of ID's in comma separated string.
Different people have different tests.
this was intended to use for just just simple CheckBox, what you want is checkboxList, which is not yet cover in the API of ASP.net MVC
If you looking for some thing like checkboxlist, maybe you should write your own helper, provide you understand HTML well..
That's it! :)
Easier just to check whether AttemptedValue.Contains("true") - it will, if it's checked, not if it's unchecked....
in the View :
<input id="radio5" type="checkbox" name="rb_abc" value="5"/>
Controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult YourForm(FormCollection fc)
{
if (fc["rb_abc"] == "on")
{
//Hey .. you have selected a Radio Button.. just kidding.. its a CheckBox
}
}
To get checkbox value even it is true or false
var boolValue = bool.Parse(collection.GetValues("checkboxID")[0])