I’m having a hard time figuring out how to iterate over a ControlArray that contains Controlgroups in a template. In TypeScript, so far I have created the ControlArray, and by iterating over data received from a remote service, I added a few ControlGroups to the array. Everything fine up to this point, and I can see the expected data structure in the console.
In the template, I have this:
<div *ngFor="#c of categories.controls">
<div ngControlGroup="c">
</div>
</div>
... where categories is the ControlArray (which holds an array of ControlGroups in its controls property). When I leave out the inner <div>, I don’t get an error, which suggests that Angular agrees with me that categories.controls is indeed an array. But as soon as I re-add the inner <div> (where I expect the local variable c to be one of the objects in the array), I get an exception with message “Cannot find control 'c' in [c in ]”. Also, I tried various other syntactical approaches, but none of them worked. In addition to a “Cannot find control …” method I also got “Cannot find a differ supporting object …”, but that didn’t take me any further.
Any hints regarding what I’m doing wrong?
ngControlGroup is defining a new control group. If I understand your question correctly, you want to actually be editing items within a control group inside a control array. Check out this plnkr: https://plnkr.co/edit/3gM2TuMGBW13HNATUcCO
<div *ngFor="#c of categories.controls; #i = index">
Control group {{i}}:
<div>
<input type="text" class="form-control m-b" [ngFormControl]="c.controls.title"/>
<input type="text" class="form-control m-b" [ngFormControl]="c.controls.id"/>
</div>
</div>
One error is
ngControlGroup="c"
which doesn't do any binding. It passes the literal c to ngControlGroup. It should be:
[ngControlGroup]="c"
The errors that are still produced after this fix seem because there are no controls.
Error is resolved by changing
ngControlGroup="c"
into
attr.ngControlGroup="c"
Because by assigning c to ngControlGroup you are just assigning the value instead of any binding. but strange why [ngControlGroup] still produces some error.apart from these here is working example
https://plnkr.co/edit/Yw21a1aSivNg4G6gYkhF?p=preview
Related
I'm wrapping my head around forms and ngModel currently. So far everything works great except for the radio buttons.
They are loaded and all but I can't manage to read the touched state.
I guess it is because they are generated within a for loop and therefore can't be referenced
<fieldset [ngClass]="{'has-error': someReference.touched
&& someReference.invalid}">
<legend>Salutation</legend>
<div>
<label for="salutation_m">
<input id="salutation_m" name="salutation" type="radio"
[ngModel]="registration.salutation" #someReference="ngModel">
<span>Mister</span>
</label>
</div>
</fieldset>
So far it looks like the reference can't be read. I don't get any errors but even when the element is touched and invalid the class is not set.
Any hints on what I'm doing wrong?
Update
Changed #someReference to #someReference="ngModel" which solves the problem for predefined radio buttons but not for generated ones inside of a for loop.
I'm guessing in the case of a for loop there is a problem with the scope.
I'm using Watir WebDriver with Firefox.
Here are two asserts for the same element. The first works, but not the second:
assert_match(/Please add user to GMT/, #browser.small(:class, "error").text)
assert_match(/Please add user to GMT/, #browser.div(:class, "eight mobile-three columns").small(:class, "error").text)
I need the second assert to work, because there are 8 error messages on the page, which are presented if the user does not populate 8 mandatory fields. All 8 errors have the identical HTML. So, I need to be able to step down from the parent.
Here is the HTML:
<div class="eight mobile-three columns">
<a id="btnAddUserToGMT" class="success radius button expand error" onclick="AddUserToGMT();" data-reveal-id="addToGMT" href="#"> … </a>
<small class="error" style="margin-top:0px;">
Please add user to GMT
</small>
</div>
Here is the error message:
Watir::Exception::UnknownObjectException: unable to locate element, using {:class=>"error", :tag_name=>"small"}
C:/Ruby193/lib/ruby/gems/1.9.1/gems/watir-webdriver-0.6.4/lib/watir-webdriver/elements/element.rb:490:in `assert_exists'
C:/Ruby193/lib/ruby/gems/1.9.1/gems/watir-webdriver-0.6.4/lib/watir-webdriver/elements/element.rb:85:in `text'
C:/Documents and Settings/Asserts_01.rb:22:in `testBasic'
The complaint for the second assert is:
unable to locate element, using {:class=>"error", :tag_name=>"small"}
And yet that same using was OK for the first assert.
Problem solved.
After discussion with the developer, it appears that by some unusual manipulation of automatically generated HTML, the text of the error message appears at its correct location on the page. But the assertion must be based on a different tag, which is specified at some completely different position on the page.
For example, I was trying to assert on this code at the correct position:
<small class="error" style="margin-top:0px;">
Gender is required
</small
Even unique xPath generated by FirePath failed to find this.
What I should have asserted on was a HIDDEN tag on a completely different part of the page.
<input id="errorMsgGenderID" name="errorMsgGenderID" type="hidden" value="Gender is required" />
There were several such tags for each mandatory field that was not populated, all bunched together on the same line. They were all "input" tags, which puzzled me.
This application is for running a writing contest.
Coodinators are assigning entries to judges for them to judge. I have three sets of data I retrieve from the server, a judge list, an entries list and an assignment list that ties the two together. There can be a variable number of input fields...if a judge has agreed to judge 4 entries, there will be 4 inputs...if 7, then 7.
I have all of that working OK, but only insofar as the entry number can be input and the data updated.
Now I would like confirm that the entryID IS a valid ID by checking the list and also to show a field or two on the screen so the coordinator knows that they typed in the right entry.
The relevant section of the HTML
<div ng-app>
<div id="assignment" ng-controller="AssignData" ng-init="JudgeID=107;CategorySelect='MS';PublishSelect='P'">
<div ng-show="loaded">
<form class="entryform ng-cloak" name="assignform" ng-submit="sendForm()">
<p>Entry numbers assigned to this judge</p>
<p ng-repeat="assign in (formassigns =(assigns | filter:AssignedJudge))">
<input type="text" ng-model="assign.entryid" required/>
{{entries.authorname}} {{entries.entrytitle}}
</p>
<button type="submit">Save Assignments</button>
<p>This will keep the assignments attached to this judge.
You will be able to send all of your assignments to all
of your judges when you are finished.</p>
</form>
</div>
</div>
</div>
The part that I haven't been able to figure out is how to make entries.authorname and entries.entrytitle show up when the user types in an entryid that is in entries.entryid.
assigns and entries are both arrays of records using JSON
assigns is JSON made up of assigns.id, assigns.judgeid, assigns.entryid.
entries is JSON made up of entries.entryid, entries.entrytitle, entries.authorname
When assigns arrives, entryid is empty. The form is used to fill in the entryid and when it is filled in, I'd like to be able to show next to it the title and authorname for that entry.
NOTE: I've added some important information at the end of this answer. So please read to the end before you decide what you're going to do.
You're going to have to do something that does the look up.
Also a few other changes I'd add, mostly so you can actually validate the items in your repeat.
(There's a summary of what I did after the psuedo code below).
<div ng-app>
<div id="assignment" ng-controller="AssignData"
ng-init="JudgeID=107;CategorySelect='MS';PublishSelect='P'">
<div ng-show="loaded">
<form class="entryform ng-cloak" name="assignform" ng-submit="sendForm()">
<p>Entry numbers assigned to this judge</p>
<p ng-repeat="assign in (formassigns =(assigns | filter:AssignedJudge))"
ng-form="assignForm">
<input type="text" ng-model="assign.entryid"
ng-change="checkEntryId(assign, assignForm)"
name="entryid" required/>
<span ng-show="assignForm.entryid.$error.required">required</span>
<span ng-show="assignForm.$error.validEntry">
{{assignForm.$error.validEntry[0]}}</span>
{{assign.entry.authorname}} {{assign.entry.entrytitle}}
</p>
<button type="submit">Save Assignments</button>
<p>This will keep the assignments attached to this judge.
You will be able to send all of your assignments to all
of your judges when you are finished.</p>
</form>
</div>
</div>
</div>
Then in your controller, you'd add a function like so (be sure to inject $http or a service you wrote to pull the values from the server):
$scope.checkEntryId = function(assign, form) {
$http.get('/CheckEntry?id=' + assign.entryid,
function(entry) {
if(entry) {
assign.entry = entry;
form.$setValidity('validEntry', true);
} else {
form.$setValidity('validEntry', false, 'No entry found with that id');
}
}, function() {
form.$setValidity('validEntry', true, 'An error occurred during the request');
console.log('an error occurred');
});
};
The basic idea above:
Use ng-form on your repeating elements to allow for validation of those dynamic parts.
Create a function that you can pass your item and your nested form to.
In that function, make your AJAX call to see if the entry is valid.
Check the validity based on the response, and call $setValidity on your nested form you passed to the function.
Use ng-show on a span (or something) in your nested form to show your validation messages.
Also, assign your checked entry to your repeated object for display purposes. (you could use a seperate array if you want, I suppose, but that would probably get unnecessarily complicated).
I hope that helps.
EDIT: Other thoughts
You might want to wrap your call in a $timeout or some sort of throttling function to prevent the entry id check from spamming yoru server. This is an implementation detail that's totally up to you.
If this is a check you do all over the place, you'll probably want to create a directive to do it. The idea would be very similar, but you'll do the check inside of a $parser on the ngModelController.
The method I showed above will still actually update the model's entryid, even if it's invalid. This is usually not a big deal. If it is, you'll want to go with what I suggested in "other thought #2", which is a custom validation directive.
If you need more information about validation via custom directives I did a blog entry on that a while back
I'm a beginner about Razor, and sometimes I get stuck with really simple things.
I have this foreach loop:
#foreach (dynamic item in ViewBag.EAList)
{
<li>
#using (#Html.BeginForm("Duplicate, "Daily"))
{
<p>#item.AuthorComment</p>
#Html.Hidden("EstadoDeAlmaID", #item.EAID)
#Html.Hidden("PosterID", Session["id"].ToString())
<input type="submit" value="Send" />
}
</li>
}
This line:
#Html.Hidden("EstadoDeAlmaID", #item.EAID)
Doesn't work, and I don't know how to make it work, I tried many ways, without #, with (--), with #(--)...
Could someone help me to display the dynamic value in my hidden field?
In addition, if someone know about a good Razor samples websites, I would be very thankful.
I had the same problem, found that a simple cast solved my problem.
#Html.Hidden("id", (string) ViewBag.ebook.isbn)
In Razor, once you are in "C# land", you no longer need to prefix values with # sign.
This should suffice:
#Html.Hidden("EstadoDeAlmaID", item.EAID)
Check out Scott Gu's article covering the syntax for more help.
Update
And I would also move your <li></li> within your using block, as Razor works better when you wrap HTML blocks inside of a code blocks.
Also, your Html.BeginForm should live outside of your loop.
#using (#Html.BeginForm("Duplicate, "Daily"))
{
<ul>
#foreach (? item in ViewBag.EAList)
{
<li>
<p>#item.AuthorComment</p>
#Html.Hidden("EstadoDeAlmaID", item.EAID)
#Html.Hidden("PosterID", Session["id"].ToString())
<input type="submit" value="Send" />
</li>
}
</ul>
}
Where ? in the foreach loop is the type of your items in EAList.
To avoid the Extension methods cannot be dynamically dispatched exception, use a model instead of ViewBag so you will not be using dynamic objects (this will avoid all the unnecessary casting in the View and is more in line with MVC style in general):
In your action when you return the view:
return View("ViewName", db.EAList.ToList());
In your view, the first line should be:
#model IEnumerable<EAListItem> //or whatever the type name is
Then just do:
#foreach(var item in Model)
You got the error, "Extension methods cannot be dynamically dispatched"... therein lies your trouble.
You should declare you loop variable not to be of type dynamic, but of the actual type in the collection. Then remove the # from the item.EAID call inside the #Html.Hidden() call.
The simple solution for me was to use ViewData instead of ViewBag. ViewBag is just a dynamic wrapper around ViewData anyway.
#Html.Hidden("ReportID", ViewData["ReportID"])
but I don't know if this will help in your case or not since you are creating dynamic items in your foreach loop.
I have found that when i want to use the view bag data in the HTML
Getting back to basics has often worked for me
<input type="hidden" name="Data" id="Data" value="#ViewBag.Data" />
this gave the same result.
Hi,
I have a View class that contains a list, this list explains the available files that the user have uploaded (rendered with an html helper).
To maintain this data on submit I have added the following to the view :
<%: Html.HiddenFor(model => model.ModelView.Files)%>
I was hoping that the mode.ModelView.Files list would be returned to the action on submit but it is not?
Is it not possible to have a list as hiddenfield?
More information : The user submit a couple of files that is saved on the service, when saved thay are refered to as GUID and is this list that is sent back to the user to render the saved images. The user makes some changes in the form and hit submit again the image list will be empty when getting to the control action, why?
BestRegards
Is it not possible to have a list as hiddenfield?
Of course that it is not possible. A hidden field takes only a single string value:
<input type="hidden" id="foo" name="foo" value="foo bar" />
So if you need a list you need multiple hidden fields, for each item of the list. And if those items are complex objects you need a hidden field for each property of each item of the list.
Or a much simpler solution is for this hidden field to represent some unique identifier:
<input type="hidden" id="filesId" name="filesId" value="123" />
and in your controller action you would use this unique identifier to refetch your collection from wherever you initially got it.
Yet another possibility is to persist your model into the Session (just mentioning the Session for the completeness of my answer sake, but it's not something that I would actually recommend using).
Before I start I'd just like to mention that this is an example of one of the proposed solutions that was marked as the answer. Darrin got it right, here's an example of an implementation of the suggested solution...
I had a similar problem where I needed to store a generic list of type int in a hiddenfield. I tried the standard apporach which would be:
<%: Html.HiddenFor(foo => foo.ListOfIntegers) %>
That would however cause and exception. So I tried Darrin's suggestion and replaced the code above with this:
<%
foreach(int fooInt in Model.ListOfIntegers)
{ %>
<%: Html.Hidden("ListOfIntegers", fooInt) %>
<% } %>
This worked like a charm for me. Thanks Darrin.