An input inside a component breaks two-way binding of AngularJS - javascript

I created a simple component:
(function (module) {
'use strict';
function SomeCtrl() {
this.foo = function (event) {
this.someArray.forEach(function (part, index, array) {
//somelogic
array[index] = array[index] + " (foo)";
});
}
}
module.component('componentName', {
templateUrl: 'blah.html',
controller: SomeCtrl,
bindings: {
someArray: '=',
}
});
})(module);
Html template is also simple:
<div class="input-group">
<input type="text" class="form-control" ng-model="$ctrl.someArray" />
<span class="input-group-btn">
<a class="btn btn-default" ng-click="$ctrl.foo($event)">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
</a>
</span>
</div>
{{$ctrl.someArray}}
Basically it's some input, and button. Input is bound with array, and on button click array is modified.
Unfortunately component behaves strange. Expression ({{$ctrl.someArray}}) is updated, but input value stays the same.
On the other hand, when user modify input value, then expression changes properly.
There are no errors thrown, no nothing. It's not even one-way binding, but unusual block of data flow...

var module = angular.module('myApp',[]);
module.component('componentName', {
controller: function SomeCtrl() {
this.foo = function (event, array) {
this.someArray.forEach(function (part, index, array) {
//somelogic
// array[index] ={'no':array[index].no+ " foo"};
console.log(array[index] ={'no':array[index].no+ " foo"});
});
}
},
template: `<div class="input-group">
<input type="text" ng-repeat="some in $ctrl.someArray" class="form-control" ng-model="some.no" /><br>{{$ctrl.someArray}}
<button class="btn btn-default" ng-click="$ctrl.foo($event, $ctrl.someArray)">
Okay
</button>
</div>`,
bindings: {
someArray: '=',
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.min.js"></script>
<body ng-app="myApp">
<div ng-init="arrays=[{'no':'1'},{'no':'2'},{'no':'3'},{'no':'4'},{'no':'5'}]">
<component-name some-array="arrays"></component-name>
</div>
</body>

The problem lies in how the array is updated:
this.someArray.forEach(function (part, index, array) {
//somelogic
array[index] = array[index] + " (foo)";
});
It seems that AngularJS doesn't detect this kind of changes performed on the array. Solution is quite simple:
var newArray = []
this.someArray.forEach(function (part, index, array) {
//somelogic
newArray.push(array[index] + " (foo)");
});
this.someArray = newArray;

Related

Multiple V-model Javascript Vue Js

I want to bind two functions here into one HTML element. Bellow the code of Vue function
var table_content = new Vue({
el: '#table_content',
data: {
arr: [],
_arr_original: [],
price: 0,
},
created(){
// some stuff here
},
methods:{
change(index,index1){
arr = this.arr[index]
arr.total = arr.data[index1].Koef * arr.data[index1].harga_satuan
Vue.set(this.arr,index,arr)
}
},
computed:{
modify_price:{
get(){
return this.price.toLocaleString()
},
set(value){
var v = parseInt(value.replace(/,/g,''))
isNaN(v) ? "" : this.price = v;
}
},
}
})
HTML element
<table class="tableizer-table table-hover" id="table_content">
<thead>
<td>
<div class="handsontableInputHolder">
<textarea tabindex="-1" name="uom" class="handsontableInput area-custom val2 text-center" style="" v-model="data.harga_satuan modify_price" v-on:keyup="change(0,index)"></textarea>
</div>
{{-- <input name="txtEmmail" class="val2"/> --}}
</td>
<td>
<div class="handsontableInputHolder">
<textarea tabindex="-1" name="total" class="handsontableInput area-custom multTotal text-center" disabled style="">#{{ data.Koef * data.harga_satuan }}</textarea>
</div>
</td>
//..
The idea is I want to bind function change and modify_price. So here are the detail
Function `change` will handle any input from user and count a total on HTML DOM `total`
Function `modify_price` will `get` input from user (number) and auto number formating with comma that input. In other hands function set will convert text with comma and turns into number.
So how I can running that code simmulteanously and binding both of function? I have check from this github issue that
Component v-model is designed for single value input components that attend to similar use cases for native input elements.
For a complex component that manages the synchronization of more than
one values, explicit prop/event pairs is the proper solution. In this
particular case I don't think the saved keystrokes are worth the added
complexity of additional syntax.
Any idea? Thank you.
After a day doing some research, I found a solution to have 2 v-model in one HTML DOM. Here are my solution
First, we need vue.component with v-model binded to computed parameter
Vue.component('number-input', {
props: ["value"],
template: `
<div>
<input type="textarea" class="val2 bhnPrice alatPrice handsontableInput area-custom text-center text-bold" v-model="displayValue" #blur="isInputActive = false" #focus="isInputActive = true"/>
</div>`,
data: function() {
return {
isInputActive: false
}
},
computed: {
displayValue: {
get: function() {
if (this.isInputActive) {
return this.value.toString()
} else {
return this.value.toFixed(0).replace(/(\d)(?=(\d{3})+(?:\.\d+)?$)/g, "$1,")
}
},
set: function(modifiedValue) {
let newValue = parseFloat(modifiedValue.replace(/[^\d\.]/g, ""))
if (isNaN(newValue)) {
newValue = 0
}
this.$emit('input', newValue)
}
}
}
});
Second, change HTML DOM as bellow
<div class="handsontableInputHolder">
<number-input style="" v-model="data.harga_satuan" v-on:keyup="change(2,index)"></number-input>
</div>

how to iterate through checkbox object

I have three checkboxes:-
<label>Type:</label>
<label>one</label>
<input type="checkbox" name="type" ng-model="a.Type[0]"/>
<label>two</label>
<input type="checkbox" name="type" ng-model="a.Type[1]"/>
<label>three</label>
<input type="checkbox" name="type" ng-model="a.Type[2]"/>
These checkboxes are stored in the backend as an array of string.
My value is displaying in:-
<li class="list-group-item"><strong>Type</strong>: <span ng-repeat="values in myArray">{{values}}</span></li>
My directive code is:-
(function () {
'use strict';
angular.module('myApp.components')
.directive('abc', abc);
abc.$inject = ['$http', '$timeout', 'ApiServices'];
function abc($http, $timeout, ApiServices) {
return {
restrict: 'EA',
scope: {
},
link: function (scope, el, attrs) {
scope.a = {};
$('#abc').on('hide.bs.modal', function () {
scope.active = false;
});
$('#abc').on('shown.bs.modal', function () {
scope.active = true;
scope.myArray = [];
scope.iterate = function () {
for (var key in scope.a.Type) {
if (scope.a.Type.hasOwnProperty(key)) {
scope.myArray.push(scope.a.Type[key]);
}
}
};
scope.iterate();
},
templateUrl: ''
};
}
})();
Here what I am trying to do is- I want to iterate through 'Type' checkboxes and pushed the values in 'myArray', and then display only the values.
Probably, my example below is somewhat similar to what you are looking for:
HTML:
<span>Select Type:</span>
<div ng-repeat="type in myArray">
<input type="checkbox" ng-model="type.value" /> {{type.type}}
</div>
<span>Selected Types:</span>
<div ng-repeat="type in myArray">
<span ng-show="type.value"> {{type.type}}</span>
</div>
JS:
$scope.a = {};
$scope.a.Type = ["One","Two","Three"]; // your string array
$scope.myArray = [];
$scope.a.Type.forEach(function(item){
$scope.myArray.push({type: item, value: false }); // create new array to make it selectable via checkbox
});
I guess, you no more need your directive now. The iteration and filtering is done in HTML itself.
I hope this helps.

Error: Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters

I have use the below link to make a filter on select option based on input text :
1. http://jsfiddle.net/tyrsius/67kgm/
2. http://www.knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
3. Using Knockout to Filter ViewModel Data Using Multiple Fields/Columns and Controls
Faced a Error: Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.
Did some research and found the below link to solve
1. Possible Bug: Breeze.js 1.5 -- Cannot write a value to a ko.computed unless you specify a 'write' option
2. Cannot write a value to a ko.computed unless you specify a 'write' option
But still facing the error not able to find out the solution. Can anyone please help me I know this question has been ask most of the time . Ironically I am not able to find the solution.Where is the mistake??.
function AccessLevelVM() {
var self = this;
self.AccessLevel_nameSearch = ko.observable();
self.PositionTypeJobDesc = ko.computed(function () {
var filter = self.AccessLevel_nameSearch();
if (!filter) {
return self.PositionTypeJobDesc();
} else {
return ko.utils.arrayFilter(self.PositionTypeJobDesc(), function (item) {
return ko.utils.stringStartsWith(PositionTypeJobDesc.PositionByDept.toLowerCase(), filter.toLowerCase());
});
}
});
}
$(document).ready(function () {
ko.utils.stringStartsWith = function (string, startsWith) {
string = string || "";
if (startsWith.length > string.length) return false;
return string.substring(0, startsWith.length) === startsWith;
};
var Model = new AccessLevelVM()
ko.applyBindings(Model, document.getElementById('AccessLevelForm'));
});
HTML
<div class="panel-body">
<div class="row">
<div class="col-md-12">
<div class="input-group">
<input type="text" name="SearchAccessLevel" class="form-control" placeholder="search" data-bind="value: AccessLevel_nameSearch, valueUpdate: 'afterkeydown'" />
<span class="input-group-addon glyphicon glyphicon-search"></span>
</div>
</div>
</div>
<div class="well" style="max-height: 300px;">
<select class="form-control input-sn" style="width: 94%;" size="4" name="sometext"
data-bind="options: PositionTypeJobDesc, optionsText: 'PositionByDept', optionsValue: 'PositionDepartmentRelId', optionCaption: ' Choose Job Position ... ', value: selectedPositionType, validationElement: selectedPositionType, event: { change: OnChangeJobPosition } " data-required="true">
</select>
</div>
</div>
You should try something like this
View:
<input type="text" data-bind="value:search" />
<div data-bind="foreach:filteredArray">
<span data-bind="text:name"></span>
</div>
ViewModel:
$(function() {
ko.applyBindings(new ViewModel());
});
var ViewModel = function () {
self.array=ko.observableArray([{'name':'charlie'},{'name':'sheen'}]);
self.search=ko.observable();
self.filteredArray= ko.computed(function () {
var filter = self.search();
if (!filter) {
return self.array();
} else {
return ko.utils.arrayFilter(self.array(), function (item) {
return ko.utils.stringStartsWith(item.name.toLowerCase(), filter.toLowerCase());
});
}
});
};
Working fiddle here

Autocomplete with min Length angularjs

I am using in my application angular.js and html. So, I would like to autocomplete a textbox,but only when the length of the field "UserSearch" is greater than 3.
MY HTML
<div class="col-sm-8">
<div class="input-group">
<input data-ng-model="UserSearch" ng-change="selectSearchType(UserSearch)" list="title" type="text" class="form-control" placeholder="Name to search">
<span class="input-group-btn">
<button class="btn btn-primary" type="button"><i class="glyphicon glyphicon-search"></i></button>
</span>
<datalist id="{{test}}">
<option data-ng-repeat=" user in AllUser" value="{{user.name}}">
</datalist>
</div>
</div>
MY JS
$scope.test = "";
$scope.selectSearchType = function (UserSearch) {
if (UserSearch.length > 3) {
$scope.test = "title";
$http.get("/api/getAllUser?SearchUser=" + UserSearch).success(function (data) {
$scope.AllUser = data;
})
}
else {
$scope.test = "";
}
}
I still have the same problem.. When i type a name in the field, two names appears in the datalist, but when I click on the triangle on the top of the texbox, all data appears... What can I do to fix this problem ?
If you're using the HTML5 datalist, then in essence you're rolling your own autocomplete.
What I would recommend, is to create a directive that encapsulates your above code.
So this:
<div class="input-group">
<input data-ng-model="UserSearch" ng-change="selectSearchType(UserSearch)" list="title" type="text" class="form-control" placeholder="Name to search">
<span class="input-group-btn">
<button class="btn btn-primary" type="button"><i class="glyphicon glyphicon-search"></i></button>
</span>
<datalist id="{{test}}">
<option data-ng-repeat=" user in AllUser" value="{{user.name}}">
</datalist>
</div>
Turns into this:
<autocomplete
search="UserSearch"
on-select="selectSearchType"
min-search="3"
list="user in AllUser">
</autocomplete>
The basic directive looks like this:
var app = // get your angular module
app.directive('autocomplete', function () {
return {
replace: true,
restrict: 'E',
scope: {
search: "=",
minSearch: "=",
list: "=",
onSelect: "="
},
templateUrl: 'template/autocomplete-template.html', // use the above template
link: function (scope, el, attrs) {
scope.$watch('UserSearch', function() {
if(scope.search.length > scope.minSearch) {
// this onSelect function will callback
// into your controller
scope.onSelect(search);
}
});
}
}
});
You can now call into your controller to make your AJAX call. This callback will allow you to use the component in different places in your project.
As #Rich comments, it's not certain what the problem seems to be. However, check this fiddle. It uses $scope.$watch to watch for changes in UserSearch, firing a request through $http if the text is longer than 3 characters.
$scope.$watch('UserSearch', function(UserSearch) {
if (UserSearch && UserSearch.length > 3) {
$http.get('/api/getAllUser?SearchUser=' + UserSearch).success(function(users) {
$scope.AllUser = users;
}).error(function() {
$scope.AllUser = [{name: 'test'}, {name: 'example'}];
});
}
});

Trigger NgChecked in Angular JS

I need to work out a way of triggering NgChecked to re-evaluate it's expression after a user submits a form (which reloads the data). Ideally the trigged would be after the data has been reloaded, so the checkbox state is in line with that of the data and the amendments that have been made.
I've tried calling the expression directly after loadData() is called, however to no avail.
Do I instead need to use something like NgUpdate?
Any suggestions would be very much appreciated. Please see my code below:
view.html
<form ng-submit="updateinfo(item.downloadID); showDetails = ! showDetails;">
<input class="form-control" style="margin:5px 3px;" type="text"
ng-model="item.title" value="{{item.title}}"
placeholder="{{item.title}}"/>
<div class="checkbox" style="margin:5px 3px;">
<label for="downloadLive">
<input name="downloadLive" type="checkbox" ng-model="item.dlLive" ng-checked="liveCheckBoxState(item.dlLive);" ngTrueValue="1" ngFalseValue ="0"> Download live
</label>
</div>
<input class="btn btn-default form-control" style="margin:5px 3px;" type="submit"/>
</form>
controllers.js
// a scope function to edit a record
$scope.updateinfo = function(downloadID) {
id = downloadID
var result = $scope.items.filter(function( items ) {
return items.downloadID == id;
});
updatedata = $scope.items
$http({
method : 'PUT',
url : '/view1/downloadedit/'+id,
data : result
});
$scope.loadData();
};
//return correct state checkbox for downloadlive
$scope.liveCheckBoxState = function(dlLive) {
console.log(dlLive);
if (dlLive == 1) {
return true;
} else {
return false;
};
};
// a scope function to load the data
$scope.loadData = function () {
$http.get('/view1/downloadData').success(function (data) {
$scope.items = data;
});
};
$scope.loadData();

Categories