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>
Related
The requirement is to rename the files before uploading it to the system. So I tired using v-model but failed. So I came up with the following which is working great just that I cannot retain my input text value. The preventdefault isn't working.
Here I uploaded 3 images and rename them as First, Second and Third.
<div>
<input type="file" #change="addFile" multiple>Choose file
</div>
<div v-if="selectedFiles && selectedFiles.length>0">
<div v-for="(item, index) in selectedFiles">
<span>{{ item.name }}</span>
<input type="text" v-model="" placeholder="Rename File" #change="renameFile(item,event)"/>
<button #click="removeFile(event,item,index)"></button>
</div>
</div>
<button #click="uploadDrawings">Upload Files</button>
data: {
selectedFiles: [],
renameFiles: [],
finalFiles: [],
},
methods: {
addFile(event) {
_.each(Array.from(event.target.files), function (file) {
var existed = _.find(app.selectedFiles, function (item) {
return item.name == file.name;
});
if (!existed) {
app.selectedFiles.push(file);
}
});
},
renameFile(item, event) {
if (app.renameFiles.filter(x => x.oldName == item.name).length > 0) {
var objIndex = app.renameFiles.findIndex(x => x.oldName == item.name);
app.renameFiles[objIndex].newName = event.target.value;
}
else {
app.renameFiles.push({
oldName: item.name,
newName: event.target.value
})
}
},
removeFile(e,item, index) {
e.preventDefault(); // while removing an item, the text value changes.
app.selectedFiles.splice(index, 1);
var objIndex = app.renameFiles.findIndex(x => x.oldName == item.name);
app.renameFiles.splice(objIndex, 1);
},
uploadDrawings() {
app.isLoading = true;
if (app.selectedFiles.length == 0) {
return;
}
_.each(app.selectedFiles, function (item) {
var blob = item.slice(0, item.size, 'image/jpg');
var name = app.renameFiles.filter(x => x.oldName == item.name);
app.finalFiles.push(new File([blob], name[0].newName + '.jpeg', { type: 'image/jpg' }));
});
}
So, On removing item from top to bottom the text box does not retain its value. First was assigned for 091646.
However, the finalFiles has the correct naming files. Its just the textbox which has a problem.
Please help me to retain the textbox values.
Thanks in advance.
Missing keys
The items in the v-for are not explicitly keyed in your markup, so Vue automatically keys them by their index. As a rendering optimization, Vue reuses existing elements identified by their keys. This can cause an issue when an item is added to or removed from the middle of the list, where surrounding items take on new keys equal to their new indices.
Solution
Apply the key attribute to each v-for item using a unique ID (not equal to index). In this case, the index combined with the filename would suffice:
<div v-for="(item, index) in selectedFiles" :key="index + item.name">
Missing v-models
When the template is re-rendered (occurs when removing an item from the middle of the list), the textboxes lose their values. This is because they have no model bound to them.
Solution
Update renameFiles[] to store only strings (instead of objects), where each string corresponds to a file in selectedFiles[] by the same index. That would obviate the file lookup code throughout your methods, so the code would be simplified to this:
export default {
⋮
methods: {
addFile(event) {
this.selectedFiles = Array.from(event.target.files)
},
removeFile(e, item, index) {
this.selectedFiles.splice(index, 1)
this.renameFiles.splice(index, 1)
},
⋮
},
}
That change also enables using renameFiles[] as v-model on the <input>s for the rename strings:
<input type="text" v-model="renameFiles[index]" placeholder="Rename File" />
demo
So I have a component called TitleForm which contains an input and a button and what I simply want is that whenever I click the button to set the value attr to blank. However whenever I try that I get the error Method "resetValue" has type "object" in the component definition. Did you reference the function correctly?
TitleForm has a prop so the input value can be set to a specific value whenever it needs to.
<template lang="html">
<div class="title-form">
<input type="text" id="title" :value="value" placeholder="Enter title" autocomplete="off">
<div class="buttons">
<button #click="resetValue" id="delete-button"></button>
</div>
</div>
</template>
<script>
export default {
name: 'TitleForm',
props: {
value: String
},
methods: {
resetValue: {
function() {
document.getElementById('title').value = '';
}
}
}
}
</script>
You put your function inside an object. The way you have declared it, your onclick-Event is trying to call resetValue as an object which does not work.
methods: {
resetValue: {
function() {
document.getElementById('title').value = '';
}
}
}
Instead, remove the curly braces and declare resetValue as a function:
methods: {
resetValue: function() {
document.getElementById('title').value = '';
}
}
try
methods: {
resetValue(){
document.getElementById('title').value = '';
}
}
I have a few input fields
<input type="text" placeholder="name">
<input type="text" placeholder="age">
<input type="text" placeholder="gender">
<input type="text" placeholder="interest">
and every time I write on those input fields it should reflect to the textarea and output a format with | or pipe symbol
Example:
<textarea>
name|age|gender|interest
</textarea>
and when I add another set of fields it writes it on the second line
<textarea>
name|age|gender|interest
name|age|gender|interest
name|age|gender|interest
</textarea>
Also "Number of Children" quantity needs to adjust automatically based on per line in the text area or how many children.
Here is My fiddle to make it more clearer https://jsfiddle.net/sjgrLcqx/4/
I did a few things here.
I made your HTML string a single variable so that when I changed it I didn't have to do so twice.
I added classes to your inputs so that I could figure out which one the user is typing into.
I used a few jQuery methods you might not be aware of, like index() and parent().
I used a few JavaScript functions to iterate through the properties on the child object I created to make creating a string from its attributes easier.
Look over the code and let me know if you have any questions. Also, next time, maybe try this yourself, even if you have no idea where to start. Just keep trying stuff until something starts to work. Coding is challenging but that's what's fun about it.
jQuery(document).ready(function () {
var childInfoArray = [];
var formHtml = '<div class="optionBox"><div class="block" style=""><input class="crow fullName" type="text" placeholder="Full name"><input class="crow width50 marginsmall age" type="text" placeholder="Age"><input class="crow width50 nomargin gender" type="text" placeholder="gender"><input class="crow interest" type="text" placeholder="Interest"><span class="remove">Remove this section</span></div><div class="block"><span class="add">Add another child\'s info</span></div></div>';
jQuery('#frmPaymentSantasgrotto').append(formHtml);
jQuery('.add').click(function () {
jQuery('.block:last').before(formHtml);
});
jQuery('.optionBox').on('click', '.remove', function () {
jQuery(this).parent().remove();
});
jQuery('.optionBox').on('keyup', 'input', function () {
var index = $(this).parent().index('div.block');
var child = {};
if (childInfoArray[index] != null) {
child = childInfoArray[index];
}
else {
child = {
fullName: '',
age: '',
gender: '',
interest: ''
}
}
if ($(this).hasClass('fullName')) {
child.fullName = jQuery(this).val();
}
else if ($(this).hasClass('age')) {
child.age = jQuery(this).val();
}
else if ($(this).hasClass('gender')) {
child.gender = jQuery(this).val();
}
else if ($(this).hasClass('interest')) {
child.interest = jQuery(this).val();
}
childInfoArray[index] = child;
printChildArray();
});
function printChildArray() {
var childInfoString = "";
childInfoArray.forEach(child => {
Object.values(child).forEach((attribute, index) => {
childInfoString += attribute;
if (index !== Object.keys(child).length - 1) {
childInfoString += ' | ';
}
else {
childInfoString += ' \n';
}
});
});
$('textarea').html(childInfoString);
}
});
Let say i have a object called,
scope.rec = {a: 2, b: 3, name: a,b};
And i split the "name" key like scope.x = scope.rec.name.split(","); then scope.x will become an array.
Now i need to iterate over "scope.x" in the view and get the value associated with the matching property name on scope.rec. I only want to iterate over the valid property names, so I will need to use a filter on scope.x, but it is not working as I would expect.
Once I get the first part working, I will also need to add functionality to multiply the values of the scope.rec properties together - in the example above, it is only 2 numbers (a,b), but it could be more than 2.
Below is the code that I tried.
scope.x =
scope.rec.name.split(",");
scope.myFilter = function(y) {
if(!scope.rec.hasOwnProperty(y)) return false;
scope.ys = Number(scope.rec[y]);
return scope.ys;
};
html:
<div ng-repeat="y in x | filter:myFilter">
<label for="{{y}}">{{y}}</label>
<input type="number" id="{{y}}" value={{ys}}>
</div>
<div><input id="calc" type="number" ng-model="calc()" disabled></div>
Now the ys in the input is same for both inputs, and the calc() function does not calculate the values correctly.
Appreciate your help in advance.
your filter (at least how you use it in your view) will receive an array with all elements, not just one. So you need to return a complete array
angular.module('myApp').filter('myFilter', function() {
return function(arrayOfYs, recFromScope) {
var filtered = [];
arrayOfYs.forEach(function(y){
if(!recFromScope.hasOwnProperty(y)) return;
// if the value in the object is already a number, it is not necessary to use Number. If it is not the case, add it
filtered.push(scope.rec[y]);
});
return filtered;
}
});
and return the filtered data.
According to your view, you need to use angular filters.
for you input you should use this
<input type="number" id="{{y}}" value={{y}}>
although I would remove that id - ids needs to be unique and probably there are values repeated.
for your calc() function you can use reduce to multiply them
$scope.calc = function(){
return $scope.filteredItems.reduce(function(prev, current) {
return prev * current;
}, 1);
};
and to get a reference to $scope.filteredItems use this in your view
<div ng-repeat="y in (filteredItems = (x | filter:myFilter:rec))">
You could do something like this:
angular.module('myApp', [])
.controller('MyController', MyController);
function MyController($scope) {
$scope.result = 1;
$scope.recObj = {};
$scope.rec = {
a: 2,
b: 3,
c: 5,
name: 'a,b,c,d'
};
function initData() {
var dataKeysArr = $scope.rec.name.split(",");
for (var dataKey of dataKeysArr) {
if ($scope.rec.hasOwnProperty(dataKey)) {
$scope.recObj[dataKey] = $scope.rec[dataKey];
}
}
};
initData();
// Watch
$scope.$watchCollection(
"recObj",
function() {
$scope.result = 1;
for (var dataKey in $scope.recObj) {
$scope.result *= $scope.recObj[dataKey];
};
}
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MyController">
<div ng-repeat="(key, val) in recObj">
<label for="{{key}}">{{key}}</label>
<input type="number" id="{{key}}" ng-model="recObj[key]">
</div>
<div>
<input id="calc" type="number" ng-model="result" disabled>
</div>
</div>
</div>
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