Struts2 <s:checkbox> with 'value="true" not rendered as preselect if has Angular `ng-model` - angularjs

I have observed a very peculiar behavior of <s:checkbox> rendering along with Bootstrap 3 and AngularJS.
I have these two <s:checkbox> in my page, wrapped by some elements of Bootstrap 3 styles:
<div class="col-md-1">
<div class="form-group">
<div class="form-other">
<label for="activaCheck"><s:text name="actividad.busqueda.activa"/></label>
<s:checkbox class="form-control" id="activaCheck" name="activaCheck" ng-model="formData.activaCheck" value="true"></s:checkbox>
<s:checkbox class="form-control" id="activaCheck2" name="activaCheck2" value="true"></s:checkbox>
</div>
</div>
</div>
As you can see, the only difference between them, is that the first has attribute ng-model = "xxx", while the second doesn't.
And, in my page, they are rendered differently, although they both are supposed to be pre-selected, because I set value="true". And when we inspect in FF, we can see the first <s:checkbox> has checked="checked", but is not rendered. I have tested in Chrome and FF, same.
I have also tested with <input type="checkbox" /> with ng-model set and checked="checked", the same: not checked when rendered in page.
So I am thinking about AngularJS is taking over part of rendering job which Struts 2 is responsible of, at least in this case. I want some explanation from developers of AngularJS, or this is the expected result?

I got the problem with unchecked checkbox. Because it has ng-model attribute the input control is bound to Angular's $scope. And if the scope doesn't define the property value for the above named checkbox it's not checked. Assumed that AngularJS modifies DOM as soon as it initializes.
I have created plnkr to demonstrate it.
You are right AngularJS starts working after document is loaded. At this time Struts has already done its work and returned html document to the browser. Now Angular continues to prepare the page to work only on one page. Both complement each other, but if Struts use to render
<input type="checkbox" ng-model="checkboxModel.value1" checked="checked">
Angular removes the checked state, because the value is commented
angular.module('checkboxExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.checkboxModel = {
//value1 : true,
value2 : 'YES'
};
}]);

Related

Variable not binding to AngularJS Controller when in a component inside a "scope:false" directive

I'm working on a SPA that has some huge forms that I decided to put inside custom components and just bind all forms to a controller that checks their validity and do some extra stuff before sending them to a server.
The forms are also inside containers that have a ton of classes from my company's web design front-end and some behaviour in JS, so I thought it'd make things cleaner to put these containers in directives and control their behaviours there.
But even thought I set these directives' scopes to false, my forms just lost their bindings to the validation controller.
I reproduced this issue in a plunker
https://plnkr.co/edit/kn9GEF3jhEhAkJGQAQny?p=preview
<!--<my-container>-->
<h1> Header Form</h1>
<header-form form="hForm" model="header"></header-form>
<!--</my-container>-->
<my-container>
<h1> Section Form </h1>
<section-form form="sForm" model="section"></section-form>
</my-container>
where the second form is wrapped by the directive
Any hints? Thanks in advance
This is intended behaviour by angular - transclude creates it's own scope, while hform and sform are bound to the outer scope. See a related question with more information here: AngularJS directive transclude scope=false?
In response to your particular issue, the workaround is to bind the forms to an object, which then makes it available within the transclude.
<!--<my-container>-->
<h1> Header Form</h1>
<header-form form="obj.hForm" model="header"></header-form>
<!--</my-container>-->
<my-container>
<h1> Section Form </h1>
<section-form form="obj.sForm" model="section"></section-form>
</my-container>
HeaderForm <br/>
model: {{header | json}} <br/>
$valid: {{obj.hForm.$valid}}
<br/> <br/>
SectionForm <br/>
model: {{section | json}} <br/>
$valid: {{obj.sForm.$valid}}
<br/>
and in MainController:
$scope.obj = {};
Here is a plunker: https://plnkr.co/edit/NRHA03ucEwKmuMCkpXCq

AngularJS Expression Not Displaying

I'm simply trying to get an AngularJS expression to display on screen. However, nothing shows up between the curly braces. I've inspected the app with ng-inspector and although I see an object being created with an ng-model directive, I can't display the value with the object key.
Furthermore, for testing purposes, I can't even get a simple math expression to display either.
Here's what I'm working with.
<body ng-app="angularApp">
<div ng-controller="firstCtrl">
<input ng-model="project.completed" type="checkbox">
<input ng-model="project.title" type="text" placeholder="Project Title">
<label>
{{project.title}}
1+2={{1 + 2}}
</label>
<input ng-model="project.time" type="text" placeholder="Project Time">
<label for="">{{project.time}}</label>
<button ng-click="helloWorld()">Press Me</button>
</div>
</body>
...and here's the controller:
angular.module('angularApp', [])
.controller('firstCtrl', function($scope) {
$scope.helloWorld = function() {
console.log('You just pressed the button.');
};
$scope.project = {
completed :false,
title :'test',
};
});
The only thing that shows up in the label is '1+2='.
UPDATE: After spending a ridiculous amount of time trying to debug this I have been able to get the first value of the math expression to display -- the '1'. I achieved this by adding a space around the '+' operator. Still, the full expression is not evaluating.
If you're using another templating engine, such as Twig, Liquid, or Django, the curly braces may be being stripped out. This results in the values not displaying or evaluating properly.
The solution I found is editing the interpolation characters or $interpolateProvider like so inside your controller:
angular.module('angularApp', []).config(function($interpolateProvider){
$interpolateProvider.startSymbol('{[{').endSymbol('}]}');
})
Then, just wrap your expression in the new symbols, e.g.:
{[{ 1+2 }]}
...or
{[{ project.title }]}
I had the same issue and finally found it, simply open your web page and press f12 and view the console :) Also remove any unused css. Additionally make sure that you add the "ng-controller" in the tag or some other broad scope so its well covered within the scope.

Set ons-switch with predifined values from Angular controller

I cannot seem to get the correct way to set the 'checked' attribute in ons-switch. This is so that I can setup user configurations page with pre-checked select boxes.
The Docs:
This is a checked switch but how do I set this using a variable in an angular controller?
For example, if ons-switch has a syntax like
I could have done:
I cannot seem to set attribute "checked" with no value in angular, as needed in the docs. I'm also unable to access the variable since it is part of an array of configurations.
Code Example:
controller:
var categInfo = [{Interest:'Classic', isChecked:true}, {Interest:'New', isChecked:false}];
html:
<ons-list-item ng-repeat="interest in categInfo" >
<span style="color: #666">{{interest.Interest}}</span>
<ons-switch modifier="list-item" var="{{interest.Interest}}" checked="{{interest.isChecked}}"></ons-switch>
</ons-list-item>
So what I want is that the html should show buttons that are checked/unchecked depending on interest.isChecked is true or false.
First of all, you need to bind the switch with ng-model, this will allow you to manage the ons-switch behavior directly from the controller. Setting the variable true or false, inside the controller, will automatically change the value of the state of the switch, same thing if you change the state from the switch (AngularJS binding).
If you want to check the status of the switch, you need to check the model value.
Here is a CodePen example. and the relative code.
HTML
<div ng-controller="MyController">
<ons-switch ng-model="switch"></ons-switch>
<ons-button ng-click="changeSwitch()">Change switch status</ons-button>
</div>
Controller
ons.bootstrap()
.controller('MyController', function ($scope) {
$scope.changeSwitch = function() {
$scope.switch = !$scope.switch;
if($scope.switch)
alert('checked');
else
alert('unchecked');
};
});
EDIT: SWITCH ARRAY EXAMPLE
Due to an Onsen UI bug about the initialization of the ons-switch element, I suggest you to use the following code to implement your switch.
<label class="switch">
<input type="checkbox" class="switch__input" checked>
<div class="switch__toggle"></div>
</label>
The appearance will be the same as the ons-switch element. This bug will be fixed in Onsen UI 1.4 release, so you can start using again the switch element after its release.
For what concerns the behavior of an array of switches, it's analog of the single switch. You still need to use 'ng-model' to bind the status of the switch. You are using ng-repeat to display the switch elements so, by using ng-model="item.isChecked", every element will be binded with the relative isChecked value inside the array. Here you can find a working CodePen example, and this is the relative code:
HTML
<div ng-controller="MyController">
<h2>What I am trying</h2>
<div ng-repeat="item in categInfo">
<div>This button should be {{item.isChecked}}</div>
<label class="switch">
<input ng-model="item.isChecked" type="checkbox" class="switch__input" checked>
<div class="switch__toggle"></div>
</label>
</div>
</div>
Controller
ons.bootstrap()
.controller('MyController', function ($scope, $timeout) {
//Need to go through the array and set as checked or not
$scope.categInfo = [{Interest:'Classic', isChecked:true}, {Interest:'New', isChecked:false}];
});

Angular forcing checkbox checked state

I'm currently upgrading our app version of angular js from 1.0.8 to the latest stable release and running into an interesting issue.
We have a set of checkboxes used to filter information, in general the options are:
All
Filter 1
Filter 2
Filter N
The desire is to have the 'All' checkbox when clicked, remain checked. It will do additional logic to determine if the other checkboxes are checked and toggle their checked states off if they are. I was able to accomplish this with 1.0.8, but not with the latest version of angular.
At its most basic of setup, we just have a single checkbox, with a ng-model value set to some boolean scope variable and a ng-click defined to some function.
I have a plunker demo where one can toggle between 1.0.8 and 1.2.18 to illustrate the change, here: http://plnkr.co/edit/WaTj6e33x4SZ2NkKStsC?p=info
HTML:
<body ng-app='example' ng-controller="mainCtrl">
<h1>Hello Plunker!</h1>
<button type="button" ng-click="toggleCb(true)">All Checked</button>
<button type="button" ng-click="toggleCb(false)">All Unchecked</button>
<div>
<input type="checkbox" ng-model="allCheckbox" ng-click="toggleCb(true)" id="all"/>
<label for="all">All</label>
</div>
<div>Imagine more checkbox options</div>
<div>
<p>Value for checkbox is: {{ allCheckbox }}</p>
</div>
</body>
JS:
angular.module('example', [])
.controller('mainCtrl', function($scope) {
$scope.allCheckbox = true;
$scope.toggleCb = function(value) {
console.log('called with value', value);
$scope.allCheckbox = value;
}
});
So this basic example setup has a couple of buttons which when clicked will change the boolean value of the scope variable. That is to illustrate that setting the scope variable is being respected by the input. The input is click-bound as well to the same function and when using 1.0.8 it keeps the checkbox checked.
Has anyone had a need to do this with running the latest version of angular, and if so how have you overcome this issue?

Angular ng-repeat with ng-form, accessing validation in controller

I am trying to generate an editable list using ng-repeat. I want to remind the user to update any edits before moving on, so I am using ng-form to create "nested" forms on the fly because the documentation says I can then use validation on these dynamically created inputs.
While that seems to work within the HTML, I don't see how to access those dynamically created forms and related validation fields in the controller. Specifically, when the user changes the input I use the form $dirty property to bring up a button to tell the user to commit the changes. So far, so good. However, once the changes are committed I want to $setPristine() on the field to indicate that the changes have been set. There may be other ways of ensuring that changes are committed on each input before I allow the main form committed, but this was the best I could come up with.
Unfortunately, even though the documentation says that if I name the ng-form it will be propagated to the $scope object, I can't find a way to access it. $scope.dynamic_form is undefined.
Here is a plunker showing what I mean:
plnk
Thanks!
[EDIT] Just to add to the issue, what does work for this specific example is to add to the ng-click on the dynamically created input:
ng-click="namesForm.name.$setPristine();clean()"
But I still don't have access to the dynamically created form in the controller. I would like, for example, to add a watcher to the namesForm.name.$pristine so that I can set the mainForm.$setValidity(false) whenever the sub-form is $dirty to prevent the user from submitting the main form until all sub-form changes have been committed.
So in a nutshell, the issue is how to access in a parent controller the validation values of a dynamically created nested ngForm?
Updated 2015-01-17:
As pointed out by Leblanc Meneses in the comments Angular 1.3 now supports interpolation with form, ngForm and input directives.
This means that using expressions to name your elements:
<div ng-form="namesForm_{{$index}}" ng-repeat="name in names">
<input type="text"
name="input_{{$index}}_0"></input>
<!-- ... -->
</div>
will work as expected:
$scope['namesForm_0']
$scope.namesForm_1
// Access nested form elements:
$scope.namesForm_1.input_1_0
...
Original answer for Angular <= 1.2:
Working with forms and the ngFormController can get tricky pretty quickly.
You need to be aware that you can dynamically add form elements and inputs but they can't be dynamically named - interpolation does not work in the ngForm or name directives.
For example, if you tried to name your nested forms dynamically like this:
<div ng-form="namesForm_{{$index}}" ng-repeat="name in names">
<!-- ... -->
</div>
Instead of making all the nested forms available on the scope like this: scope['namesForm_0'] you would only have access to the single (last) form with the literal name scope['namesForm_{{$index}}'].
In your situation you need to create a custom directive that will be added along with ngFormto handle setting $pristine$ and $invalid for that form instance.
JavaScript:
This directive will watch the $dirty state of its form to set the $validity to prevent submission when dirty and handle setting the $pristine state when the 'clean' button is pressed.
app.directive('formCleaner', function(){
return {
scope: true,
require: '^form',
link: function(scope, element, attr){
scope.clean = function () {
scope.namesForm.$setPristine();
};
scope.$watch('namesForm.$dirty', function(isDirty){
scope.namesForm.$setValidity('name', !isDirty);
});
}
};
});
HTML:
Then the only change to your HTML is to add the formCleaner directive.
So change your original HTML from this:
<body ng-controller="MainCtrl">
<form name="mainForm" submit="submit()">
<h3>My Editable List</h3>
<div ng-form="namesForm"
ng-repeat="name in names">
<!-- ... -->
</div>
<button class="btn btn-default" type="submit">Submit</button>
</form>
</body>
to this, by adding form-cleaner next to ng-form:
<body ng-controller="MainCtrl">
<form name="mainForm" submit="submit()">
<h3>My Editable List</h3>
<!-- Add the `form-cleaner` directive to the element with `ng-form` -->
<div form-cleaner
ng-form="namesForm"
ng-repeat="name in names">
<!-- ... -->
</div>
<button class="btn btn-default" type="submit">Submit</button>
</form>
</body>
Here is an updated Plunker showing the new behaviour: http://plnkr.co/edit/Lxem5HJXe0UCvslqbJr3?p=preview

Resources