Angular 2 - Change nested form input value - javascript

I made a filter for my nested inputs, im using javascript to filter a link whenever paste event is fired.
function fixLink(foo){
if (foo.includes('youtube') && (foo.includes('watch')) || foo.includes('vimeo') && !foo.includes('video') ) {
foo = foo.includes('youtube') ? 'https://www.youtube.com/embed/' + foo.slice(foo.indexOf('=') + 1) : foo;
foo = foo.includes('vimeo') ? 'https://player.vimeo.com/video/' + foo.slice(foo.indexOf('com/') + 4) : foo;
}
return foo;
}
input.addEventListener('paste', () => {
setTimeout(() => {
input.text = fixLink(input.value);
input.value = fixLink(input.value);
}, 100)
});
This is my HTML
#Component({
selector: 'video-control',
template: `<div class="form-group p-0 mb-2" [formGroup]="video">
<div class="input-group group-social">
<input [disabled]="onHold" class="form-control" formControlName="url" type="text" (focus)="setUrl($event.target)" placeholder="https://www.youtube.com/watch?v=IWfWqDhx65s">
<button type="button" class="remove-photo-gallery btn btn-sm btn-danger" (click)="removed.emit(index)">
<i class="fas fa-trash"></i>
</button>
</div>
</div>`,
})
It changes input value but when i save the value it comes as if the filter didn't work.
I can only get my filter to work if i add something else to the input like a space.

What I can deduce from your HTML is that you have a FormGroup called video and it has a FormControl called url. These are the edits I think you need to make.
Update your input element to use the Angular (paste) event emitter:
<input [disabled]="onHold" class="form-control" formControlName="url" type="text" (focus)="setUrl($event.target)" (paste)="onPaste($event)" placeholder="https://www.youtube.com/watch?v=IWfWqDhx65s">
Then in your component have the following method:
onPaste(event: ClipboardEvent) {
const clipboardData = event.clipboardData || window.clipboardData;
const fixedLink = this.fixLink(clipboardData.getData('text'));
window.setTimout(() => this.video.get('url').value = fixedLink);
}
You would also need to move this fixLink function to be part of the component as well.
If your hope is to have plain JS update the Angular FormGroup then you're using Angular wrong and I strongly advise against continuing along that path.

Try to use two-way binding to the value of the input element and trigger fixLink function on the paste event to change the property that is bound to the input value.

Related

How to add input field on click more than once

I am trying to create a button that creates an input field every time I click on it. Currently when I click it it creates only only one input field, how can I make so that every time I click it I get an input field?
Here is my html:
<div id="new-input-container"> </div>
<p class="add-new-shareholders-p"><i class="fa fa-plus-circle fa-lg" #click="createNewInputFields"/>
and my function:
createNewInputFields() {
var container = document.getElementById('new-input-container')
container.innerHTML = "<input type='text'/>";
}
Using document.createElement, you can create new input HTMLElement and using container.appendChild function, you can add that new element to the div selector as follows.
function createNewInputFields() {
var container = document.getElementById('new-input-container');
const newElem = document.createElement("input");
newElem.setAttribute("type", "text");
container.appendChild(newElem);
}
<div id="new-input-container"> </div>
<button onclick="createNewInputFields()">Add New</button>
Depending on your logic, you can try one of these solutions :
First logic : Create inputs by a number
<template>
<div id="app">
<input type="text" v-for="i in numberOfInputs" :key="i" />
<br />
<button #click="addInput">Add input</button>
</div>
</template>
<script>
export default {
name: "App",
data: () => ({
numberOfInputs: 0,
}),
methods: {
addInput: function () {
this.numberOfInputs++;
},
},
};
</script>
Second logic: Create inputs by values
<template>
<div id="app">
<input
type="text"
v-for="(value, i) in values"
:key="i"
v-model="values[i]"
/>
<br />
<button #click="addInput">Add input</button>
</div>
</template>
<script>
export default {
name: "App",
data: () => ({
values: [],
}),
methods: {
addInput: function () {
let value = "";
this.values.push(value);
},
},
};
</script>
Your function will work fine, the only change that you need to make is, instead of setting value using innerHTML property, you can use the insertAdjacentHTML( ) function.
The reason your code only adds the input field once is because what innerHTML is doing here is just overwrite existing HTML each time you set a value using the assignment operator (=). That is because, innerHTML property just gets the innerHTML from the element like it is a string. One option is to concatinate the new input html to the existing innerHTML, the other is to use insertAdjacentHTML( ) function.
insertAdjacentHTML( ) will add adjacent html each time, takes in 2 parameters, first one is the place you want to insert which can be which can be one of ('beforebegin' 'beforeend' 'afterbegin' 'afterend'), in your case the best would be 'beforeend'. The second parameter is the HTML you want to add.
document.querySelector('.btn').addEventListener('click', createNewInputFields);
function createNewInputFields() {
const container = document.getElementById('new-input-container');
const inputHtml = "<input type='text'/>";
container.insertAdjacentHTML('beforeend', inputHtml);
}
<div id="new-input-container"></div>
<p class="add-new-shareholders-p">
<button class='btn'> create field</button>
</p>
I just fixed the bugs in your code.
To add a new item, you must use +=.
If you use only = the first time you will add an item.
But with each subsequent click, the item will be rewritten
The problem with this option is that when you add new fields, the content of the already created ones will be removed!
function createNewInputFields() {
var container = document.getElementById('new-input-container')
container.innerHTML += "<input type='text'/>";
}
<div id="new-input-container"> </div>
<p class="add-new-shareholders-p"><i class="fa fa-plus-circle fa-lg" onclick="createNewInputFields()">Click Me</i></p>
The correct way to add a new field while keeping the idea of your code and saving information in already created fields is:
function createNewInputFields() {
var container = document.getElementById('new-input-container');
var x = document.createElement("input");
x.setAttribute('type', 'text');
container.appendChild(x);
}
<div id="new-input-container"> </div>
<p class="add-new-shareholders-p"><i class="fa fa-plus-circle fa-lg" onclick="createNewInputFields()">Click Me</i></p>

onchange only detects first checkbox of div

This is the div
<div class="col-lg-4 pre-scrollable" id="all-menus-div">
<h3 class="text-center">Tum menu listesi</h3>
<div class="list-group" id="all-menus-checkboxes" th:each="menu : ${allMenusList}">
<input th:id="${menu.id}" th:text="${menu.item}" th:value="${menu.item}" type="checkbox"/>
</div>
<button class="btn btn-success" disabled id="add-menus-to-role-btn" type="submit">Ekle</button>
</div>
<button class="btn btn-success" id="update-menus-for-role-btn" type="submit">Rolu guncelle</button>
</div>
it gets from model object.
This is the function of it for onchange:
$('#all-menus-checkboxes').on('change', function () { // on change of state
var addButton = document.getElementById('add-menus-to-role-btn');
lengthOfCheckedAllMenus = $('#all-menus-div :checked').length;
debugger;
console.log(" lengthOfCheckedAllMenus: " + lengthOfCheckedAllMenus);
addButton.disabled = lengthOfCheckedAllMenus <= 0;
});
it calls this function when I click the first checkbox only. And i can see only the log at this time. So, only button disabled becomes false only when i click the first one.
When i click others, nothing happens, no logs.
But when i click for example second one, then click first one, it shows 2 of them are selected.
Why is that?
Simple allMenuslist:
[MenuDTO{id=1, href='/check-deposit-money', menuName='Kontrol-Onay Ekranları', roles=[RoleDTO{id=1, name='ADMIN'}], iconName='null', item='Cüzdana Para Yükleme - Kontrol', className='null'}]
onchange event must to bind on the <input>.
bind event must to after <input> appended.
But your <input> like by dynamic generation, recommend use 'event delegation':
add class to <input>.
<input class="all-menus-checkboxes" />
use event delegation.
<script>
$('#all-menus-div').on('change', '.all-menus-checkboxes', function () { ... }
</script>
try this
$('#all-menus-checkboxes > input[type=checkbox]').on('change', function () { // on change of state
var addButton = document.getElementById('add-menus-to-role-btn');
lengthOfCheckedAllMenus = $('#all-menus-div :checked').length;
debugger;
console.log(" lengthOfCheckedAllMenus: " + lengthOfCheckedAllMenus);
addButton.disabled = lengthOfCheckedAllMenus <= 0;
});

Angular: how to fire ngChange when input type="number" but not entered a number

I'm building an angular directive, where I have two number inputs and both together represent an age range.
What I want to achieve is being able to use this directive the following way:
<input-age-range
name="ageRange"
ng-model="filterUpdateVM.ageRange">
</input-age-range>
<span ng-if="myCtrlVM.form.ageRange.$error.ageRange">
Please, enter a valid age range.
</span>
and be able to show a custom error when the entered age range is not correct.
My directive html template looks this way:
<div class="col-md-3">
<input type="number"
class="form-control"
placeholder="Minimum age"
name="ageMin"
min="18"
max="80"
ng-change="checkAndValidateAgeRange()"
ng-model="ageRange.ageMin"
ng-model-options="{allowInvalid: true}">
</div>
<div class="col-md-3">
<input type="number"
class="form-control"
placeholder="Maximum age"
name="ageMax"
min="18"
max="80"
ng-change="checkAndValidateAgeRange()"
ng-model="ageRange.ageMax"
ng-model-options="{allowInvalid: true}">
</div>
Every time the user types anything in an input, I wish to check the if the entered range is correct:
Both ages should be numbers
Both ages should be between 18 and 80
ageMin <= ageMax
So far ng-model-options="{allowInvalid: true}" will let the ngChange function inside my directive to be triggered in case any of the ages entered is not between the desired age range (18-80) - this way I can do some checking and set an input error if there's any and show it.
My problem here is that if the age entered at first is not a number, ngChange is not called: it won't be able to do the error checking so there won't be any errors to be shown. How can I have my ngChange function called in this case, without changing my input type="number"?
EDIT: Jsfiddle added: http://jsfiddle.net/afkf96qh/
Finally after a day of trying to figure out what was going on, I achieved the behavior I was looking for.
First of all, ng-model-options="{allowInvalid: true}" had nothing to do in solving my problem, so I removed that out of my input. My directive html template looks this way now:
<div class="col-md-3">
<input type="number"
class="form-control"
placeholder="Minimum age"
name="ageMin"
min="18"
max="80"
ng-model="ageRange.ageMin"
ng-change="checkAndValidateAgeRange()">
</div>
<div class="col-md-3">
<input type="number"
class="form-control"
placeholder="Maximum age"
name="ageMax"
min="18"
max="80"
ng-model="ageRange.ageMax"
ng-change="checkAndValidateAgeRange()">
</div>
The problem that caused not triggering ng-change="checkAndValidateAgeRange()" was the way I was initializing the ageRange variable at the ngModel $render function:
angular
.module('myModule')
.directive('myDirective', myDirective);
function myDirective() {
var directive = {
templateUrl: 'myTemplate.html',
restrict: 'E',
require: ['^form', 'ngModel'],
link: linkFunc
};
return directive;
/////////
function linkFunc(scope, element, attrs, ctrls) {
scope.form = ctrls[0];
scope.ageRange = {};
scope.checkAndValidateAgeRange = checkAndValidateAgeRange;
var ngModel = ctrls[1];
ngModel.$render = getAgeRange;
function checkAndValidateAgeRange() {
// fired when the model changes
};
// $render function
function getAgeRange() {
scope.ageRange.ageMin = ngModel.$viewValue.ageMin;
scope.ageRange.ageMax = ngModel.$viewValue.ageMax;
};
};
};
In some cases, both of the ngModel object's properties passed to the directive - ageMin and ageMax - could be undefined.
And we all know what happens when we have an input with restrictions such as type="[whatever]", min="18" or max="80" and the data entered in that input does not follow those requirements: it is set to undefined in the model.
So, if the above properties were initially passed to the directive set to undefined and you entered a value in the <input type="number ...> that, for example, was not numeric... the binded model's value would still be undefined, so there wouldn't be any changes in the model making the ng-change not trigger.
What I finally did to solve this, was to initialize both ageRange.ageMin and ageRange.ageMax to null if they are passed as undefined, at my $render function:
ngModel.$render = getAgeRange;
// $render function
function getAgeRange() {
scope.ageRange.ageMin = ngModel.$viewValue.ageMin || null;
scope.ageRange.ageMax = ngModel.$viewValue.ageMax || null;
};
This way, when entering an invalid input the model's property value will change from null to undefined causing the ng-change to trigger.
checkout this fiddle
$scope.checkAndValidateAgeRange = function(name, model) {
if (name === 'ageMin') {
if (angular.isNumber(model)) {
if (model < $scope.age.min) {
$scope.minAgeMessage = 'You are under age';
} else if (model > $scope.age.max) {
$scope.minAgeMessage = 'You are too old';
} else {
$scope.minAgeMessage = '';
}
} else {
$scope.minAgeMessage = 'Enter Only Numbers';
$scope.ageRange.ageMin = null;
}
}
if (name === 'ageMax') {
if (angular.isNumber(model)) {
if (model <= $scope.ageRange.ageMin) {
$scope.maxAgeMessage = 'You are under age';
} else if (model > $scope.age.max) {
$scope.maxAgeMessage = 'You are too old';
} else {
$scope.maxAgeMessage = '';
}
} else {
$scope.maxAgeMessage = 'Enter Only Numbers';
$scope.ageRange.ageMax = null;
}
}
}
you can't use allowInvalid on type='number' as given here. You can set type='text' and then handle validation inside the ng-change handler if your intent if using type='number' is showing validation messages.
To set validity of form inside a controller refer to this link.

ng-repeat not updating information after $scope was updated

I have an ng-repeat which creates a form with some starting data. Then the user is free to modify said data and the changes should appear in the form. Before that, the user submitted data are sanitized by another function, which is called by an ng-click on a button.
Everything works well under the hood (I checked my $scope.some_array, from which ng-repeat takes the data and the new data is in the right place) but nothing happens on the page.
The element:
<li ng-repeat="field in some_array" id="field-{{$index}}">
<div class="{{field.field_color}}">
<button type="button" ng-click="save_field($index)">done</button>
{{field.nice_name}}
</div>
<div id="field-input-{{$index}}">
<input type="text" id="{{field.tag}}" value="{{field.content}}">
<label for="{{field.tag}}">{{field.nice_name}}</label>
</div>
</li>
save_field code:
$scope.save_field = function (index) {
console.log($scope.some_array[index]["content"])
var value = $("#field-" + index).children("div").children("input").val()
var name = $scope.some_array[index]["name"]
var clean_value = my_clean(value)
if (norm_value === "") {
return
}
$scope.some_array[index]["content"] = clean_value
console.log($scope.some_array[index]["content"])
}
On the console I see:
10.03.16
10/03/16
Which is right, but in the form I only see 10.03.16. I already tried putting $timeout(function(){$scope.$apply()}) as the last line of my function, but the output is still the same.
You shouldn't use input like this if you want to bind a variable to it. Digest loop will refresh the value but it will not be updated visibly because this is not html native behavior.
Use ng-model instead, it will update view value of the input as expected:
<input type="text" id="{{field.tag}}" ng-model="field.content">
Also using ng-model your variable will be updated when user modify the input, so you can retrieve it to do some treatments much more easily in save_field function, without jQuery:
$scope.save_field = function (index) {
if (norm_value === "") {
return;
}
$scope.some_array[index]["content"] = my_clean($scope.some_array[index]["content"]);
};
More infos: https://docs.angularjs.org/api/ng/directive/ngModel

Meteor binding values for inputs with event generation

I have a form input with a value bound to a reactive data source:
<input type="text" name="first-name" id="first-name" required value="{{currentUser.profile.firstName}}" />
I want to watch 'change' events on the input:
$('#first-name').change(function() { alert('Value changed!'); });
This works fine if I change the value directly in the input. However, if the value changes reactively, the change event doesn't fire.
What's the best way to bind values to form elements so that the 'change' event fires if the reactive data source changes?
The Optimum Solution would be to use manuel:viewmodel. You keep the state of the UI in a javascript object and bind the UI elements to properties of that object.
Example :
First add the package to the project with meteor add manuel:viewmodel
Then in the Html do the Following :
<template name="loginBox">
First Name: <input type="text" data-bind="value: first"/>
<br/>
Last Name: <input type="text" data-bind="value: last"/>
<div data-bind="text: greeting"></div>
<a data-bind="enabled: canEnter, hover: showError" class="btn-primary">Enter Site</a>
<span data-bind="text: errorText" class="text-error"></span>
</template>
Then in the Javascript file do the Necessary Bindings
Template.loginBox.viewmodel({
first: '',
last: '',
greeting: function() {
return "Hello " + this.first() + " " + this.last();
},
canEnter: function() {
return !!this.first() && !!this.last();
},
showError: false,
errorText: function() {
if (this.canEnter() || !this.showError()) {
return '';
}
return "Please enter your first and last name";
}
});
Here we're binding the value of the input/text element to the property 'first' of the viewmodel.
The result is that the viewmodel object will be kept in sync with the input box. If you change the value in the texbox then the value of the viewmodel's 'first' property will also change and vice versa.
For More information http://viewmodel.meteor.com/
Here's my initial solution, which I'm not particularly thrilled with:
Template.signUpPersonalDetails.rendered = function() {
this.autorun(function() {
if (Meteor.user() && Meteor.user().userAccount) {
var userAccount = Meteor.user().userAccount;
$('#first-name').val(userAccount.firstName).change();
$('#last-name').val(userAccount.lastName).change();
$('#email').val(userAccount.email).change();
$('#phone-number').val(userAccount.phoneNumber).change();
$('#postcode').val(userAccount.shippingPostcode).change();
}
});
};

Categories

Resources