AngularJS - Optional required element - coffeescript

I'm attempting to use a custom directive to make a conditional required statement. The first condition I've added is 'firstInArray' to make the element required if it's the first in array of choices (necessary for a UI where you need to pick at least one item, but you could pick indefinitely many):
.directive('variableRequired', [
()->
return {
require: 'ngModel',
link: (scope, el, attrs, ctrl)->
vars = attrs.variableRequired.split(',')
condition = vars[0]
if condition is 'firstInArray'
item = vars[1]
arr = vars[2]
if scope[item] == scope[arr][0]
$(el).removeAttr('variable-required')
$(el).attr('required', 'required')
}
])
When I add scope.$apply() within my directive, the app freezes up (seems like infinite recursion).
Is there a better way to approach this than a custom directive? If not, what's wrong with my directive?

You may be able to use the undocumented ng-required directive, instead of your own custom directive:
<li ng-repeat="itemObj in items">
<input type="text" ng-model="itemObj.text" ng-required="$first">
</li>

Related

Trying to think about how to build a multi step form in angular 2

I am trying to build a small, 3 step form. It would be something similar to this:
The way I did this in react was by using redux to track form completion and rendering the form body markup based on the step number (0, 1, 2).
In angular 2, what would be a good way to do this? Here's what I am attempting at the moment, and I'm still working on it. Is my approach fine? Is there a better way to do it?
I have a parent component <app-form> and I will be nesting inside it <app-form-header> and <app-form-body>.
<app-form>
<app-header [step]="step"></app-header>
<app-body [formData]="formData"></app-body>
</app-form>
In <app-form> component I have a step: number and formData: Array<FormData>. The step is just a index for each object in formData. This will be passed down to the header. formData will be responsible the form data from user. Each time the form input is valid, user can click Next to execute nextStep() to increment the index. Each step has an associated template markup.
Is there a better way to do something like this?
don't overdo it, if it is a simple form you don't need to use the router or a service to pass data between the steps.
something like this will do:
<div class="nav">
</div>
<div id="step1" *ngIf="step === 1">
<form></form>
</div>
<div id="step2" *ngIf="step === 2">
<form></form>
</div>
<div id="step3" *ngIf="step === 3">
<form></form>
</div>
It's still a small template, and you kan keep all of the form and all the data in one component, and if you want to you can replace the ngIf with something that switches css-classes on the step1,2,3 -divs and animate them as the user moves to the next step
If you want to keep things extensible, you could try something like this:
<sign-up>
<create-account
[model]="model"
[hidden]="model.createAccount.finished">
</create-account>
<social-profiles
[model]="model"
[hidden]="model.socialProfiles.finished">
</social-profiles>
<personal-details
[model]="model"
[hidden]="model.personalDetails.finished">
</personal-details>
</sign-up>
export class SignUpVm {
createAccount: CreateAccountVm; //Contains your fields & finished bool
socialProfiles: SocialProfilesVm; //Contains your fields & finished bool
personalDetails: PersonalDetailsVm; //Contains your fields & finished bool
//Store index here if you want, although I don't think you need it
}
#Component({})
export class SignUp {
model = new SignUpVm(); //from sign_up_vm.ts (e.g)
}
//Copy this for personalDetails & createAccount
#Component({})
export class SocialProfiles {
#Input() model: SignUpVm;
}

Adding Bootstrap 3 popover breaks JQuery Validation Plugin

I have a form, which I'm validating using JQuery Validation plugin. Validation works file until I add a Bootstrap 3 popover to the text field with name "taskName" (the one being validated) (please see below) . When I add the popover to this text field, error messages are repeatedly displayed every time the validation gets triggered. Please see the code excerpts and screenshots below.
I've been trying to figure out what is happening, with no success so far. Any help will be greatly appreciated. Thanks in advance!
HTLM Excerpt
The popover content
<div id="namePopoverContent" class="hide">
<ul>
<li><small>Valid characters: [a-zA-Z0-9\-_\s].</small></li>
<li><small>Required at least 3 characters.</small></li>
</ul>
</div>
The form
<form class="form-horizontal" role="form" method="post" action="" id="aForm">
<div class="form-group has-feedback">
<label for="taskName" class="col-md-1 control-label">Name</label>
<div class="col-md-7">
<input type="text" class="form-control taskNameValidation" id="taskName" name="taskName" placeholder="..." required autocomplete="off" data-toggle="popover">
<span class="form-control-feedback glyphicon" aria-hidden="true"></span>
</div>
</div>
...
</form>
JQuery Validate plugin setup
$(function() {
//Overwriting a few defaults
$.validator.setDefaults({
errorElement: 'span',
errorClass: 'text-danger',
ignore: ':hidden:not(.chosen-select)',
errorPlacement: function (error, element) {
if (element.is('select'))
error.insertAfter(element.siblings(".chosen-container"));
else
error.insertAfter(element);
}
});
//rules and messages objects
$("#aForm").validate({
highlight: function(element) {
$(element).closest('.form-group').removeClass('has-success').addClass('has-error');
$(element).parent().find('.form-control-feedback').removeClass('glyphicon-ok').addClass('glyphicon-remove');
},
success: function(element) {
$(element).closest('.form-group').removeClass('has-error').addClass('has-success');
$(element).parent().find('.form-control-feedback').removeClass('glyphicon-remove').addClass('glyphicon-ok');
}
});
$('.taskNameValidation').each(function() {
$(this).rules('add', {
required: true,
alphanumeric: true,
messages: {
required: "Provide a space-separated name."
}
});
});
});
Bootstrap 3 popover setup
$('[data-toggle="popover"]').popover({
trigger: "focus hover",
container: "body",
html: true,
title: "Name Tips",
content: function() { return $('#namePopoverContent').html();}
});
The screenshots
First Edit
It seems I did not make my question clear, so here it goes my first edit.
I'm not using the popover to display the error messages of the validation. The error messages are inserted after each of the fields that fail validation, which is precisely what I want. Hence, this question does not seem to be a duplicate of any other question previously asked.
Regarding the popover, I just want to add an informative popover that gets displayed whenever the user either clicks the text field "taskName" or hovers the mouse over it. Its role is completely independent of the validation.
The question is, then, why adding the (independent) popover is making the validation plugin misbehave, as shown in the screenshots.
I had the very same issue a few days ago and the only solution I found was to use 'label' as my errorElement:.
Change the line errorElement: 'span' to errorElement: 'label' or simply removing the entire line will temporarily fix the issue. ('label' is the default. )
I am not completely sure what the JQ validate + BS popover conflict is, but I will continue to debug.
After some debugging I think I found the issue.
Both jQuery validate and bootstrap 3 popovers are using the aria-describedby attribute. However, the popover code is overwriting the value written by jQuery validate into that attribute.
Example: You have a form input with an id = "name", jQuery validate adds an aria-describedby = "name-error" attribute to the input and creates an error message element with id = "name-error" when that input is invalid.
using errorElement:'label' or omitting this line works because on line 825 of jquery.validate.js, label is hard-coded as a default error element selector.
There are two ways to fix this issue:
Replace all aria-describedby attributes with another attribute name like data-describedby. There are 4 references in jquery.validate.js. Tested.
or
Add the following code after line 825 in jquery.validate.js. Tested.
if ( this.settings.errorElement != 'label' ) {
selector = selector + ", #" + name.replace( /\s+/g, ", #" ) + '-error';
}
I will also inform the jQuery validate developers.
The success option should only be used when you need to show the error label element on a "valid" element, not for toggling the classes.
You should use unhighlight to "undo" whatever was done by highlight.
highlight: function(element) {
$(element).closest('.form-group').removeClass('has-success').addClass('has-error');
$(element).parent().find('.form-control-feedback').removeClass('glyphicon-ok').addClass('glyphicon-remove');
},
unhighlight: function(element) {
$(element).closest('.form-group').removeClass('has-error').addClass('has-success');
$(element).parent().find('.form-control-feedback').removeClass('glyphicon-remove').addClass('glyphicon-ok');
}
(The success option could also be used in conjunction with the errorPlacement option to show/hide tooltips or popovers, just not to do the styling, which is best left to highlight and unhighlight.)
Also, I recommend letting the Validate plugin create/show/hide the error label element, rather than putting it the markup yourself. Otherwise, the plugin will create its own and ignore the one you've created.
In case you were unaware, you cannot use the alphanumeric rule without including the additional-methods.js file.

How to hide a text field in play framework

How to hide a text field in play framework? For example how to hide this field:
#inputText(userProfileForm("name"), '_label -> "Name")
This should work in all browsers:
#inputText(
userProfileForm("name"),
'_label -> "Name",
'style -> "display: none"
)
Note that this only hides the field, not the label etc.
If you want to hide the label aswell, you can't do this with the default helpers.
You can however specify the input field yourself:
<input type="hidden" name="name" value="#userProfileForm.data.get("name")" />
The name should be name of the field in your form (coincidentally name aswell in this case).
I haven't tested the value but it should work, maybe you'll need to strip the " around name.
If you do this, the hidden data will be sent, along with the other data in the form to the server.
Edit:
If the hidden value is empty, that means you didn't bind it to the form when calling the view. You can bind the name to the form like this in Java (I don't know Scala, but it's explained in the Play Scala documentation):
Map<String, String> data = new HashMap<>();
data.put("name","get the username here");
return ok(index.render(userProfileForm.bind(data));
Another option (which is cleaner in my opinion) is to simply pass the username as an argument to the view. The controller becomes:
return ok(index.render(userProfileForm, "username goes here"));
And in the view you can then simply do:
#(userProfileForm : Form[UserProfileForm])(name : String)
#helper.form(...){
<input type="hidden" name="name" value="#name" />
//...
}
The inputText helper takes a varargs of (Symbol, Any), which represent the html attributes. If you are using html5 you can just add the hidden attribute:
#inputText(userProfileForm("name"), '_label -> "Name", 'hidden -> "hidden")
normally the hidden attribute has no value, but I couldn't find any information about how to do this with the helpers. In Chrome at least it works like this as well.
edit:
btw, you can also just use html instead of the helper:
<input attribute=value hidden />
I know that this is an old question, but i had a similar issue, i wanted to hide an inputText and still, handle it using the helpers.
The best and cleanest way to do it is to write your own helper adding a custom value to tell when to hide the element itself.
I came to a solution like this
my own field constructor
#(elements: helper.FieldElements)
#if(elements.args.contains('hideIt)){
#elements.input
}else{
<div class="#if(elements.hasErrors) {error}">
<div class="input">
#elements.input
<span class="errors">#elements.errors.mkString(", ")</span>
<span class="help">#elements.infos.mkString(", ")</span>
</div>
</div>
}
that i used in the view file like this:
#inputText(form("fieldName"),
'hidden -> "true",
'hideIt -> true
)
now you are done :)
While it worked, I didn't like the hashmap version provided by #Aerus, preferring to use the statically typed forms when possible, so I came up with an alternative.
final UserProfileForm initial = new UserProfileForm("get the username here");
return ok(index.render(Form.form(UserProfileForm.class).fill(initial));
Then, in the profile, you can do as follows:
<input type="hidden" name="name" value="#userProfileForm("name").value" />

#each with call to helper ignores first entry in array

I'm trying to generate a menu with handlebars, based on this array (from coffeescript):
Template.moduleHeader.modules = -> [
{ "moduleName": "dashboard", "linkName": "Dashboard" }
{ "moduleName": "roomManager", "linkName": "Raumverwaltung" }
{ "moduleName": "userManager", "linkName": "Benutzerverwaltung" }
]
The iteration looks like this (from the html code):
{{#each modules}}
<li {{this.isActive this.moduleName}}>
<a class="{{this.moduleName}}" href="#">{{this.linkName}}</a>
</li>
{{/each}}
{{this.isActive}} is defined like this (coffeescript code again):
Template.moduleHeader.isActive = (currentModuleName) ->
if currentModuleName is Session.get 'module'
"class=active"
Based on Session.get 'module' the appropriate menu item is highlighted with the css class active.
On reload, the Session-variable module contains 'dashboard', which is the first entry in that array, however, it does not get the active-class. If I add an empty object as the first item to the modules-array, the 'dashboard'-item is properly highlighted.
The strange thing is, that the first item (dashboard) is rendered fine, but it does not have the active-class, which raises the impression, that the function this.isActive is not called for the first item.
Am I doing it wrong?
Really going out on a whim here but my traditional approach would be below. I don't know whether it will work but it was too big for a comment, yet again it's not a guaranteed fix. Let me know how it goes:
Replace the HTML with
{{#each modules}}
<li {{isActive}}>
<a class="{{moduleName}}" href="#">{{linkName}}</a>
</li>
{{/each}}
Replace the CS with
Template.moduleHeader.isActive = () ->
currentModule = this
currentModuleName = currentModule.moduleName
if currentModuleName is Session.get 'module'
"class=active"
If its rendering, the each block is working correctly. There must be an issue in the isActive helper. Most likely the if block isn't working as you think it should be. Put a console.log in there and see if it is entered, and put another inside the if (printf debugging). I find this is usually the easiest way to do quick debugging of handlebars helpers.
A useful helper in js for inspecting values inside of a context is as follows
Handlebars.registerHelper("debug", function(optionalValue) {
console.log("Current Context");
console.log("====================");
console.log(this);
if (optionalValue) {
console.log("Value");
console.log("====================");
console.log(optionalValue);
}
});
Then you can call this inside an each block and see all the values the current context/a specific value has.

How do I set two mutually exclusive check boxes in Jquery?

$(document).ready(function() {
$("input:txtAge1").click(function(event) {
if ($(txtAge1).attr("checked") == true) {
$(txtAge2).attr("checked", "unchecked");
$(txtAge2).attr("checked") == false)
}
if ($(txtAge2).attr("checked") == true) {
$(txtAge1).attr("checked", "unchecked");
$(txtAge1).attr("checked") == false)
}
});
});
<input type="checkbox" id="txtAge1" name="option1" value=""/>21<br>
<input type="checkbox" id="txtAge2" name="option2" value=""/>55<br>
I am trying to select either one checkbox or the other. So if one box is UNchecked, it should either be not allowed or force the
other box to BE checked ...in other words, enforce either one or the other but never allow
a "undefined" condition
Maybe I'm dumbing down the issue a bit, but why not try using radio buttons?
You can set one to be selected to avoid the user submitting an empty value.
Update: Since your customer wants checkboxes, here's a solution in jQuery:
$(document).ready(function(){
$('.radioButton').click(function() {
$('.radioButton').prop("checked", false);
$(this).prop("checked", true);
});
});
That's the jQuery code. You should set your input boxes up like this:
<input type="checkbox" id="txtAge1" class="radioButton" name="option1" value=""/>21
<input type="checkbox" id="txtAge2" class="radioButton" name="option2" value=""/>55
That should work, but it's untested. I might've missed something.
One solution is to add two click events, one for each checkbox. When one is clicked, the other is unclicked.
$("#checkbox1").click(function() {
$("#checkbox2").prop('checked', false);
});
$("#checkbox2").click(function() {
$('#checkbox1').prop('checked', false);
});
I ran into this issue recently, except I needed checkboxes instead of radio buttons as having both options unchecked was a requirement. I resolved it with something like this (adapted to the OP's code):
<input type="checkbox" id="txtAge1" />21
<input type="checkbox" id="txtAge2" />55
$(document).ready({
$("#txtAge1").click(function() {
if($("#txtAge1").is(':checked')) {
$("#txtAge2").prop('checked', false);
}
});
$("#txtAge2").click(function() {
if($("#txtAge2").is(':checked')) {
$("#txtAge1").prop('checked', false);
}
});
)};
Might not be that pretty, but it works.
I also wanted to note the excellent link http://rndnext.blogspot.com/2009/08/mutually-exclusive-html-select-elements.html here.
One caution though, I used it to mutex two dynamically generated select lists inside a div. . Since the content to be manipulated is not available at page load, it was not working as expected. Following solutions at jQuery - selecting dynamically created divs helped resolve the issue.
I would use this function. Allows mutual exclusion and allows uncheking:
$('#divId').find(':checkbox').click(function() {
var state=$(this).prop("checked");
$(':checkbox').prop("checked", false);
$(this).prop("checked", state);
});
$("input[type=checkbox]").click(function () {
$(this).siblings().prop("checked", false);
})