I have a code block here that is 90% working. The idea is that I have an autocomplete (searches via axios with a LIKE condition on the database but I simplified the result set here). When you type in the input, it searches and returns matching results in a dropdown. If you select an option, it replaces the search text in the input with the actual selected value. Then if you click to add another zone it clones the divs with the input.
The issue is that when I create another div and start searching in the input it glitches and then it also triggers the dropdown on both divs (or more if you add more than two). But it doesn't actually allow text to be entered in the newly added zones.
How can I fix this so that each zone actually has it's own input and results dropdown so that I can send something on save that has distinct values for each div?
new Vue({
components: {},
el: "#commonNameDiv",
data() {
return {
searchString: [],
results: [],
savedAttributes: [],
cards: []
}
},
methods: {
autoComplete() {
this.results = [];
console.log(this.searchString);
if (this.searchString.length > 2) {
this.results = [
{attribute_value:"apple"},
{attribute_value:"banane"}
]
}
},
saveAttribute(result) {
this.savedAttributes = [];
console.log('cool');
this.savedAttributes.push(result.attribute_value);
console.log('here is the attribute');
console.log(this.savedAttributes);
this.searchString = result.attribute_value;
this.results = [];
},
addCard: function() {
this.cards.push({
index: ''
})
}
}
})
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="commonNameDiv">
<div class="uk-grid">
<div class="uk-width-2-10" >
<input size="4" type="text" name="mapNumber">
</div>
<div class="uk-width-6-10">
<input style="width:100%" type="text" placeholder="what are you looking for?" v-model="searchString" v-on:keyup="autoComplete" class="form-control">
<div class="panel-footer componentList" v-if="results.length">
<ul class="list-group">
<li class="list-group-item" v-for="result in results">
<a v-on:click="saveAttribute(result)">{{ result.attribute_value }}</a>
</li>
</ul>
</div>
</div>
<div class="uk-width-2-10" style="border: 1px solid black; height:50px; width: 50px; margin: 0 auto;" >
</div>
</div>
<div v-for="(card,index) in cards" class="uk-grid">
<div class="uk-width-2-10">
<input size="4" type="text" name="mapNumber">
</div>
<div class="uk-width-6-10">
<input style="width:100%" type="text" placeholder="what are you looking for?" v-model="searchString[index]" v-on:keyup="autoComplete" class="form-control">
<div class="panel-footer componentList" v-if="results.length">
<ul class="list-group">
<li class="list-group-item" v-for="result in results">
<a v-on:click="saveAttribute(result)">#{{ result.attribute_value }}</a>
</li>
</ul>
</div>
</div>
<div class="uk-width-2-10" style="border: 1px solid black; height:50px; width: 50px; margin: 0 auto;">
</div>
</div>
<div style="height: 35px;">
</div>
<div>
<a v-on:click="addCard">Add another zone</a>
</div>
</div>
All your variables and/or properties need to be unique, currently they are not. The easiest way would be in my mind to just store all that you need inside the card object you are using. When you are done, you can just extract the data you need from your array.
Template where we have removed the first div you have, let's just push an empty card object to the array. Our card object will look like this:
{
index: "",
value: "" // the end value
results: [] // the search results will be stored here
}
So in template we use value as v-model for the input and display results for the user to pick their value. Also I suggest you use in all v-for iterations a key. Here I use the index, but that is really not that efficient! But anyway, so the stripped down version of template would look like:
<div v-for="(card, i) in cards" :key="i">
<div>
<input
placeholder="what are you looking for?"
v-model="card.value"
v-on:keyup="autoComplete($event, card)"
>
<div v-if="card.results.length">
<ul>
<li v-for="(result, i) in card.results" :key="i">
<a v-on:click="saveAttribute(result, card)">#{{ result.attribute_value }}</a>
</li>
</ul>
</div>
</div>
</div>
<div>
<a v-on:click="addCard">Add another zone</a>
</div>
The methods would be:
methods: {
autoComplete(ev, card) {
if (ev.target.value.length > 2) {
// here would be actual results...
card.results = [
{ attribute_value: "apple" },
{ attribute_value: "banane" }
];
}
},
saveAttribute(result, card) {
card.value = result.attribute_value;
},
addCard() {
this.cards.push({
index: "",
value: "",
results: []
});
}
}
and lastly, like mentioned, call addCard in a life cycle hook. Here I use created:
created() {
this.addCard();
}
Related
What I need to do is to display the filtered array in the DOM once I click on any value from the dropdown. The return from filtered function is right but I couldn't update the DOM.
HTML CODE
This is my side dropdown list that I take the value from
<div class="col-md-3">
<div class="widget">
<h4 class="widget-title">Sort By</h4>
<div>
<select class="form-control" (change)="selectChangeHandler($event)">
<option value="Man">Man</option>
<option value="Women">Women</option>
<option value="Accessories">Accessories</option>
<option value="Shoes">Shoes</option>
</select>
</div>
</div>
</div>
Table that shows the results on DOM take the value from above dropdown list to be able to show them by category. Unfortunately the DOM doesn't change (I used NGX pagination library), please see my TS code that sends the filtered array to the loop? So, I don't understand why it didn't update.
<div class="col-md-9">
<div class="row" id="top">
<div class="col-md-4" *ngFor="let item of collection | paginate: { itemsPerPage: 100, currentPage: p ,id: 'foo' }">
<div class="product-item">
<div class="product-thumb">
<span class="bage">Sale</span>
<img class="img-responsive" src="https://via.placeholder.com/150" alt="product-img" />
<div class="preview-meta">
<ul>
<li>
<span data-toggle="modal" data-target="#product-modal">
<i class="fas fa-search"></i>
</span>
</li>
<li>
</i>
</li>
<li>
<i class="fas fa-shopping-cart"></i>
</li>
</ul>
</div>
</div>
<div class="product-content">
<h4>{{item.name}}</h4>
<p class="price">{{item.price}}</p>
</div>
</div>
</div>
Type Script Code
export class AllproductsComponent implements OnInit {
allProducts:any[]=[]
filteredData=[...this.allProducts]
p: number = 1;
collection: any[] = this.filteredData;
constructor(private _allproducts:ProductService) {
console.log(this.collection);
}
ngOnInit(): void {
this.getAllProducts()
}
getAllProducts():any{
this._allproducts.getAllproducts().subscribe(res=>{
console.log(res.data);
this.allProducts = res.data
this.collection =res.data
})
}
pageChanged(pee:number){
document.getElementById("top").scrollIntoView()
}
selectChangeHandler(value:any){
console.log(value.target.value);
console.log(this.filteredData);
this.filteredData = this.allProducts.filter(key=>{
if(value.target.value === "Man"){
if(key.price > 20)return this.allProducts
}else if(value.target.value === "Women"){
if(key.price < 20) return this.allProducts
}else {
return this.allProducts
}
})
console.log(this.filteredData);
}
}
I appreciate your help, thanks a lot.
allProducts:any[]=[]
filteredData=[...this.allProducts]
collection: any[] = this.filteredData;
you are initializing data before it is called from your api , so it's normal it will never work. You have to reinitialize it inside your method
this._allproducts.getAllproducts().subscribe(res=>{ ..});
I am trying to build a simple on page search that uses event listeners to look at a containers data and then hides that whole container if it doesn't have the required information.
So far I have:
// get search element
let searchInput = document.getElementById ('searchInput');
// add event listener
searchInput.addEventListener ('keyup', searchPage);
function searchPage(){
//search input detection
let searchValue = document.getElementById('searchInput').value;
//set parameters to search from
let parent = document.getElementById('product-container');
let child = parent.getElementsByTagName('span');
for(let i = 0;i < child.length;i++){
let a = child[i];
if(a.innerHTML.indexOf(searchValue) >= -1) {
child[i].parentNode.style.display = '';
} else {
child[i].parentNode.style.display = 'none';
};
};
};
But this only acts on the first product-container it finds, there are 5 such containers on the page.
How do I make this look through all containers, but hide the ones that don't contain any of the information typed in the search bar.
I am getting products from an API so using html replace to add to the following template:
<script id="template" type="text/template">
<div class="product">
<div class="product--header">{{ type }}</div>
<div class="product--image"><img src="../app/assets/images/no-image.png" alt="no image"> </div>
<div class="product--information" id="product--information">
<div class="product--title"><span>{{ name }}</span></div>
<!--This is just a place holder we would house the prices here if they were on the API -->
<div class="product--price">£55</div>
<div class="product--brand"><strong>Brand:</strong><span> {{ brand }}</span></div>
<div class="product--colour"><strong>Colour:</strong><span> {{ colour }}</span></div>
<div class="product--sizes">
<select>
<option value="" disabled selected>Select Size </option>
{{ options }}
</select>
</div>
<div class="product--description"><strong>Description:</strong><br><div class="product--description__content"><span> {{ description }} </span></div></div>
<div class="product--code"><strong>Product ID:</strong><span> {{ productid }}</span></div>
<div class="product--buttons">
<button class="btn--buy" aria-label="Add to Basket">Add to basket</button>
<button class="btn--save" aria-label="Save for Later">Save for later</button>
</div>
<button class="product--add-to-wishlist" aria-label="Add to Wishlist"><i class="fas fa-heart"></i></button>
</div>
</div>
</script>
The search box code is as follows:
<input type="text" name="search" id="searchInput" placeholder="Enter Search...">
and the code that the template goes into is:
<div id="product-container">
<div class="featured"></div>
<div class="products"></div>
</div>
Because you have multiple product containers, use document.getElementsByClassName() instead of document.getElementById() and provide product-container class as argument.
let searchInput = document.getElementsByClassName ('container');
You need to modify searchPage() method. Instead of using document to find searchValue and parent use this.
let searchValue = this.getElementsByClassName('searchInput')[0].value;
let parent = this.getElementsByClassName('container')[0];
Please, add HTML code.
EDIT: If I understand correctly you have one search input which will search multiple product containers. Here is one simple example, which you can easily apply to your problem.
HTML:
<input type="text" name="search" id="searchInput" placeholder="Enter Search...">
<div class="product-container">
<span class="product">Kiwi</span>
<p>Kiwi description</p>
</div>
<div class="product-container">
<span class="product">Banana</span>
<p>Banana description</p>
</div>
<div class="product-container">
<span class="product">Apple</span>
<p>Apple description</p>
</div>
JS:
let searchInput = document.getElementById ('searchInput');
searchInput.addEventListener ('keyup', searchPage);
function searchPage(){
let searchValue = this.value.toUpperCase();
let products = document.getElementsByClassName('product');
for(let i = 0; i < products.length; i++) {
console.log(products[i].innerHTML.toUpperCase());
if (products[i].innerHTML.toUpperCase().indexOf(searchValue) > -1)
products[i].parentNode.style.display = '';
else
products[i].parentNode.style.display = 'none';
};
};
CSS:
.product-container {
display: flex;
flex-direction: column;
margin-bottom: 10px;
background: grey;
}
.product-container span {
font-size: 20px;
}
.product {
display: block;
}
https://jsfiddle.net/gardelin/koc5eg6v/25/
I have been wrestling with an issue with the way v-model works when you have two inputs in the same child component. I am passing in an array of objects from a parent component and need to update different parts of an object in an array of objects with the two input fields. I can get one input field to work but have no idea how to have both inputs effect different items in the object (which is in an array). I am new to Vue and don't totally understand the functionality of passing an array of objects and affecting specific items in those objects.
Parent Component
<template>
<div class="budgetGroup">
<header><input title="CardTitle" type="text" v-bind:placeholder="budgetItemHeading"></header>
<div class="budgetItemContainer">
<div class="budgetItemRow">
<!--creates new component when click event happens and places below-->
<div v-for="(input, index) in budgetRows" :key="index">
<budgetItemRowContent v-model="budgetRows"></budgetItemRowContent>
<progress data-min="0" data-max="100" data-value="20"></progress>
</div>
</div>
</div>
<footer class="budgetGroupFooter">
<div class="budgetGroupFooter-Content budgetGroupFooter-Content--Narrow">
<button class="addBudgetItem" id="addBudgetItem" v-on:click="createNewContent()">
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<path fill="#FD0EBF" d="M3 0v3h-3v2h3v3h2v-3h3v-2h-3v-3h-2z"></path>
</svg>
Add Item
</button>
</div>
</footer>
</div>
export default {
name: 'budgetGroup',
components: {
budgetItemRowContent,
BudgetItemButton,
},
data: () => {
return {
budgetItemHeading: 'Housing',
// creates array containing object for budget row information
budgetRows: [
{inputbudget: '', amountbudgeted: 0, remaining: 0, id: uniqId()},
],
};
},
methods: {
// creates new budgetRow when button is clicked
createNewContent() {
this.budgetRows.push({inputbudget: '', amountbudgeted: 0, remaining: 0, id: uniqId() });
},
},
};
Child Component- needing both inputs to affect different items in object
<template>
<div class="budgetItemRowContent">
<div class="budgetItemRow-Column">
<div class="budgetItemLabel">
//Input that is suppose to update InputBudget in object
<input type="text" maxlength="32" placeholder="Label" class="input-Budget-Inline-Small budgetItemRow-Input">
</div>
</div>
<!--input that will update amoundbudgeted -->
<div class="budgetItemRow-Column">
<div class="amountBudgetedInputContainer">
//Input that will be updating amountBudgeted in object
<input v-model.number="amount" class="amountBudgetedNumber budgetItemRow-Input input-Budget-Inline-Small" type="number" placeholder="$">
</div>
</div>
<div class="budgetItemRow-Column">
<span class="budgetItemSecondColumnMoney-Spent">
<span class="money-symbol">$</span>
<span class="money-integer"></span>
<!--<span class="money-decimal">.</span>-->
<!--<span class="money-fractional">04</span>-->
</span>
</div>
</div>
<script>
export default {
//only one prop that is being updated(dont know how to have two seperate items in object to get updated
props: ['value'],
computed: {
amount: {
set(newVal) {
this.$emit('input', newVal);
},
get() {
return this.value;
},
},
},
};
I have three views which posts a JSON-object to backend to generate a PDF. The first in the hierarchy works, not the two others. The code is practically identical, and uses the same method to programmatically add a hidden input-field with the JSON-data.
I cannot for the life of me figure out what the problem is.
First i thought Knockout was creating the problem since the form-elements are added in a foreach-loop, in a AJAX-success-event, but every view is identical in this respect.
I've tried all variations of renaming, also I've tried to create the form and input programatically, appending them to the body and submitting. Still only the first works.
WORKING VIEW:
<div data-bind="visible: rep.messages().length > 0">
<div data-bind="foreach: rep.messages">
<div id="error" data-bind="css: color">
<div><span data-bind="css: icon"></span></div>
<div><div data-bind="html: text"></div></div>
<div>
<form action="/createPDF.html" method="POST" id="individlonpdf" name="loneandringar" target="_blank">
<span onclick=""></span>
Skriv ut
</form>
</div>
</div>
</div>
</div>
NOT WORKING VIEW:
<div data-bind="visible: rem.newEmployeeMessages().length > 0" id="rem-msg-area">
<div data-bind="foreach: rem.newEmployeeMessages">
<div id="error" data-bind="css: color">
<div><span data-bind="css: icon"></span></div>
<div><div data-bind="html: text"></div></div>
<div>
<form action="/createPDF.html" method="POST" id="ateranstallpdf" name="avanmalan" target="_blank">
<span onclick=""></span>
Skriv ut
</form>
</div>
</div>
</div>
</div>
JAVASCRIPT
Each click event-method is then directed towards the same print-method
Not-working:
rem.print = function(){
mainPdfPrint("ateranstallpdf", data, "avanmalan");
}
Working:
rep.print = function () {
mainPdfPrint("individlonpdf", data, "loneandringar");
}
The main-method:
function mainPdfPrint(formid, datasource, name, stringified) {
formid = "#" + formid;
var url = $(formid).attr("action");
var data = stringified ? datasource : JSON.stringify(datasource);
var inp = $("<input>", { "value": data, "name": name, "type": "hidden" });
if ($(formid + " input:hidden").length) {
$(formid + " input:hidden").remove();
}
$(formid).append(inp);
IK.global.logAnalytics(url);
document.forms[name].submit();
}
Both working and non-working form-elements are part of the document.forms-collection as they should be.
Any suggestions would be greatly appreciated!
UPDATE
After suggestions here, I've tried a different approach:
<div class="col-xs-12" id="" data-bind="visible:messages().length > 0">
<div data-bind="foreach: messages">
<div class="col-xs-12" id="error" data-bind="css:color">
<div class="col-xs-12 msg-templates">
<div class="col-xs-1 no-pad-l info-sign">
<span class="glyphicon" data-bind="css:icon"></span>
</div>
<div class="col-xs-9 no-pad-l">
<div class="col-xs-12 no-pad-lr msg" data-bind="html:text"></div>
</div>
<div class="col-xs-2 no-pad-lr text-right print">
<form class="col-xs-12 no-pad-l" action="/foretag/individkort/skapaPdfAvanmalan.html" method="POST" id="avanmalanpdf" name="avanmalan" target="_blank">
<span class="glyphicon glyphicon-print" onclick=""></span>
<input type="hidden" name="avanmalan" data-bind="value:ko.toJSON($root.printableEmployee)" />
Skriv ut
</form>
</div>
</div>
</div>
</div>
</div>
The problem still persists however, but setting a delay in the print-method triggers the submit-event:
self.print = function () {
setTimeout(function () {
document.forms.avanmalan.submit();
}, 600);
}
Your approach indicates that you seem to be not really familiar with knockout and try to do too much in a "jQuery" kind of way.
With knockout, your user interface is a function of your view model. All changes in the DOM have to be a result of changes in the underlying data. The task of manually adding input fields with JS code is not part of this approach.
In the following, there is no custom code that modifies the DOM. Everything is done by knockout.
var vm = {
rep: {
messages: [
{ color: '', icon: '', text: '<b>Message 1</b> - Text', data: 'some data here 1' },
{ color: '', icon: '', text: '<b>Message 2</b> - Text', data: 'some data here 2' },
{ color: '', icon: '', text: '<b>Message 3</b> - Text', data: 'some data here 3' }
]
},
catchPost: function () {
// dummy function to prevent an actual form POST
console.log("...would send the following data to the server:");
console.log(ko.toJSON(this.data, null, 2));
return false;
}
};
ko.applyBindings(vm);
button.submit {
border: 0px none;
padding: 0;
margin: 0;
text-decoration: underline;
background-color: transparent;
color: blue;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="with: rep">
<div data-bind="foreach: messages">
<div id="error" data-bind="css: color">
<div><span data-bind="css: icon"></span></div>
<div><div data-bind="html: text"></div></div>
<div>
<form action="/createPDF.html" method="POST" target="_blank" data-bind="submit: $root.catchPost">
<input type="hidden" name="loneandringar" data-bind="value: ko.toJSON(data)">
<button class="submit" id="print-tgl">Skriv ut</button>
</form>
</div>
</div>
</div>
</div>
There is no need for any custom code in such a simple scenario. Unless you have an explicit dependency on jQuery UI (or a similar library), I suggest you remove jQuery from your application altogether. You can use a dedicated Ajax library if you require it - such as reqwest - but generally speaking, jQuery is more of a hindrance than a help in knockout applications.
In my KnockoutJS app, I am looping over an observable array and displaying some stuff like this:
<div id="user-list-container" data-bind="foreach: users">
<div class="row order-line list-row">
<div class="medium-7 small-10 columns">
<i class="fi-torso tip-right"></i>
</div>
<div class="medium-3 columns">
<a href="#" class="button split tiny info radius">
<i data-bind="text:role"></i>
<span data-dropdown="leftDrop" data-options="align:left"></span>
</a>
</div>
<div class="medium-2 small-2 columns">
<i class="fi-trash" title="#Texts.Remove"></i>
</div>
<ul id="leftDrop" class="f-dropdown" data-dropdown-content>
<li>Foreman</li>
<li>Worker</li>
</ul>
</div>
</div>
Everything works fine and all the elements are shown, but when I click one item to operate on that particular item, each item then has the value of last item in the array.
In my Javascript function:
self.makeWorker = function (user) {
var project = self.selectedProject();
var account = self.accounts.findByKey(user.accountId);
var ur = user;
console.log(user);
console.log(this);
if (project.role() != "Worker") {
var data = {
role: "Worker",
organizationId: project.organizationId,
projectId: project.id,
accountId: user.accountId
}
TippNett.Project.ChangeRole(data, function (result) {
project.users.findByKey(user.id).role("Worker");
ur.role("Worker");
account.roles.findByKey(user.id).role("Worker");
});
}
}
The value passed to the function is always the last value in 'users' observable array.
Any input on why this is happening? See the image for more briefing: