Vue.js: binding select boxes, but don't want to ajax all the options - vue.js

Good day. I'm using Vue.js to render an arbitrary number of select elements from the data in a component.
Here's sample JSON data that indicates there are two select elements, each with one or more options.
{
"dropdowns":[
{
"cd":"UG9ydGZvbGlv",
"formname":"sp_filter_UG9ydGZvbGlv",
"nm":"Portfolio",
"selected":"1a",
"options":[
{
"cd":"1a",
"val":"Option 1A"
}
]
},
{
"cd":"UHJvZHVjdCBOYW1l",
"formname":"sp_filter_UHJvZHVjdCBOYW1l",
"nm":"Product Name",
"selected":"2b",
"options":[
{
"cd":"2a",
"val":"Option 2A"
},
{
"cd":"2b",
"val":"Option 2B"
}
]
}
]
}
Here's the template HTML:
<form>
<div v-for="dropdown in dropdowns">
<div v-if="dropdown.availableToView">
<h4>{{dropdown.nm}}</h4>
<select v-model="dropdown.selected" v-on:change="triggerUpdate">
<option value="">(Make a selection)</option>
<option v-for="option in dropdown.options" :value="option.cd">{{option.val}}</option>
</select>
</div>
</div>
</form>
So far so good.
I've got the data loading and Vue is building the dropdowns.
When the user changes any select box (remember there can be an arbitrary number of them), the trigger action needs to submit ALL of the elements in the form via ajax. It sounds like the most correct option is to bind the form fields to the underlying component data, as I've done.
My triggerUpdate looks like this:
methods: {
triggerUpdate: function() {
axios({
method: "post",
url: actionURL,
data: this.dropdowns
})
.then(response => (this.data = response));
}
}
...but this submits the entire dropdowns data element, including all of the options in each select box. It's unnecessary to send all of the options in. I just want to send each field name along with its selected option (i.e. the "value").
I know i could serialize the whole form and make that my ajax payload. But that seems to be making an "end run" around Vue.js. Everyone talks about having your form fields bound to the Vue model...is it then correct to basically ignore the model when making an ajax request whose purpose is to then update the model?
I'm relatively new to Vue.js so I'd appreciate help with what I'm overlooking here. How should I go about sending in the data from the form (a) while using proper Vue.js binding and (b) without sending extraneous data?
Thanks for your time.

If you need to post only the selected values, and you store those in each dropdown's selected property, the sensible approach seems to be just mapping it to a simple array of name/value objects.
Try this (it assumes the name of each field is the formname property, if it isn't you can just replace it):
var submitData = this.dropdowns.map((dropdown) => {
return { name: dropdown.formname, value: dropdown.selected };
});
Then you send submitData in your ajax request.

Related

Vue class components dynamically add component depending on answer from backend

So from the backend I get a array of objects that look kind of like this
ItemsToAdd
{
Page: MemberPage
Feature: Search
Text: "Something to explain said feature"
}
So i match these values to enums in the frontend and then on for example the memberpage i do this check
private get itemsForPageFeatures(): ItemsToAdd[] {
return this.items.filter(
(f) =>
f.page== Pages.MemberPage &&
f.feature != null
);
}
What we get from the backend will change a lot over time and is only the same for weeks at most. So I would like to avoid to have to add the components in the template as it will become dead code fast and will become a huge thing to have to just go around and delete dead code. So preferably i would like to add it using a function and then for example for the search feature i would have a ref on the parent like
<SearchBox :ref="Features.Search" />
and in code just add elements where the ItemsToAdd objects Feature property match the ref
is this possible in Vue? things like appendChild and so on doesn't work in Vue but that is the closest thing i can think of to kind of what I want. This function would basically just loop through the itemsForPageFeatures and add the features belonging to the page it is run on.
For another example how the template looks
<template>
<div class="container-fluid mt-3">
<div
class="d-flex flex-row justify-content-between flex-wrap align-items-center"
>
<div class="d-align-self-end">
<SearchBox :ref="Features.Search" />
</div>
</div>
<MessagesFilter
:ref="Features.MessagesFilter"
/>
<DataChart
:ref="Features.DataChart"
/>
So say we got an answer from backend where it contains an object that has a feature property DataChart and another one with Search so now i would want components to be added under the DataChart component and the SearchBox component but not the messagesFilter one as we didnt get that from the backend. But then next week we change in backend so we no longer want to display the Search feature component under searchbox. so we only get the object with DataChart so then it should only render the DataChart one. So the solution would have to work without having to make changes to the frontend everytime we change what we want to display as the backend will only be database configs that dont require releases.
Closest i can come up with is this function that does not work for Vue as appendChild doesnt work there but to help with kind of what i imagine. So the component to be generated is known and will always be the same type of component. It is where it is to be placed that is the dynamic part.
private showTextBoxes() {
this.itemsForPageFeatures.forEach((element) => {
let el = this.$createElement(NewMinorFeatureTextBox, {
props: {
item: element,
},
});
var ref = `${element.feature}`
this.$refs.ref.appendChild(el);
});
}
You can use dynamic components for it. use it like this:
<component v-for="item in itemsForPageFeatures" :is="getComponent(item.Feature)" :key="item.Feature"/>
also inside your script:
export default {
data() {
return {
items: [
{
Page: "MemberPage",
Feature: "Search",
Text: "Something to explain said feature"
}
]
};
},
computed: {
itemsForPageFeatures() {
return this.items.filter(
f =>
f.Page === "MemberPage" &&
f.Feature != null
);
}
},
methods: {
getComponent(feature) {
switch (feature) {
case "Search":
return "search-box";
default:
return "";
}
}
}
};

Convert 3rd party REST API into html forms?

I want to convert an API service into a forms the user of my app can fill in. I think I need the right vocabulary to ask farther questions. Is there a term for this process?
Hydra is the only vocab I know about in the topic, though they don't support this kind of conversion. https://www.hydra-cg.com/spec/latest/core/ HAL forms is another solution: https://rwcbook.github.io/hal-forms/ but I guess based on the name that it is not abstract enough. You need to separate hyperlinks from forms. The former is for describing the interface of the webservice the later is for describing the GUI, which can use the webservice amongst many different types of clients. E.g. a select/choice parameter can be converted into radio button, checkbox, select input, range, etc. There needs to be some sort of mapping about this and this decision is made by the client developers, not by the service developers. Supporting multiple languages and labelling must be mapped too.
What you need is very detailed hyperlink descriptions and you can turn your hyperlinks into forms and hyperlink parameters into input fields. There are certain types of parameters e.g.
text (password?, multiline?, lengthRange, verified:regex|url|email|etc.)
repetition (source e.g. password double check)
select(alternatives[], selected[], selectionSize)
date(range, selectedRange, selectionSize)
time(range, selectedRange, selectionSize)
color(alternatives|range, selectedRange, selectionSize)
file(multi?, sizeRange, MIME-types[])
...
Better to support only what you need, because I think you need to write it yourself. Try to be very general, abstract instead of specific in this vocab. E.g. select can support select single and select multiple. The single can be supported with selectionSize=1. The alternatives can be added with a list or with another hyperlink which can be part of the response or lazy loaded, or it can be even an URI template which expects a keyword. The data of the fields can depend on each other, e.g. first you select city and the next field is street selection where you download the data by filling an URI template. So these fields can be interrelated and doing it properly requires a complex vocab.
From the REST API side of story binding these to the hyperlinks is relative easy:
{
id: "/api/users/123"
type: "/docs/User"
userid: 123,
addCar: {
type: "/docs/User/addCar",
method: "PUT",
uri: {
template: "/api/users/{id}/cars/{plateNumber}",
id: {
type: "docs/Text",
range: "docs/Car/id",
value: {
type: "/docs/Query",
context: "./resource",
select: "userid",
},
readOnly: true,
required: true
},
plateNumber: {
type: "/docs/Text",
range: {
id: "/docs/Car/plateNumber",
verified: {
type: "/docs/verification/regex",
pattern: "[A-Z]{4,4}\d{2,2}"
}
},
required: true
}
},
body: {
brand: {
type: "/docs/Selection",
range: "/docs/Car/Brand/id",
alternatives: {
type: "/docs/Car/Brand/listBrands",
method: "GET",
uri: "/api/car-brands"
},
value: null,
selectionSize: [1,1],
required: true
}
}
}
}
The generated HTML form can be something like the following with radio input, but you can use select input too:
<form onsubmit="magic(this); return false">
<input type="hidden" id="user_id" name="user_id" value="123">
<input type="text" id="plate_number" name="plate_number" pattern="[A-Z]{4,4}\d{2,2}" required="required"><br>
<label for="plate_number">Plate Number</label><br>
Select Brand:
<input type="radio" id="brand_vw" name="brand" value="VW" required="required">
<label for="brand_vw">Volkswagen</label><br>
<input type="radio" id="brand_ford" name="brand" value="Ford">
<label for="brand_ford">Ford</label><br>
<input type="submit" value="Add Car">
</form>
The request is something like:
PUT /api/users/123/cars/ABCD12 {brand: "Ford"}
When you do it with an automated client you do:
carService.getUser({id: 123}).addCar({plateNumber: "ABCD12", brand: "Ford"})
When you do it with GUI, then:
carService = new Service("/docs")
// GET /docs/* might be cached
// or you can download a single JSON-LD docs file and use # for terms
// you bookmarked the getUser link from a previous call
// or you get it with GET /api/
// you can fill it with the actual user
user = carService.getUser({id: jwt.userId})
// GET /api/users/123
// you find the addCar link and generate a form from it
// GET /api/car-brands
// the user fills the form and sends it with magic
user.addCar({plateNumber: "ABCD12", brand: "Ford"})
// PUT /api/users/123/cars/ABCD12 {brand: "Ford"}
As you can see this can be very complicated and the upper hyperlink description is ad-hoc. If you need a proper RDF vocab, then it takes serveral years to design it properly. Still this kind of technique could be used in theory.
A complete client cannot be generated, because that involves knowing what you are doing or what you possibly need and in which part of the client. E.g. in this case it is where to display the form, when do we need this form and why, where to get the actual user id and the hyperlink from, etc. If the user has to decide everything about this, then in theory it can be generated and would look like a simple webpage, which can be browsed.

vue js append parameters to URL

I am using vuejs3 and I want to make a filter. When user click to the link I want to append the url and push it to browser address bar for now. Later I will do ajax request to update page with product list.
So far I am able to send parameters to URL, but only one item from one group.From first color group I want user to select only one but from second size group I want user to select multiple.
I want this type of URL: localhost:8080/product?color=red&size=medium&size=large
<template>
<div class="products">
<div class="multi_filters">
<h1>Multi Filter By Color</h1>
Red color
Blue color
</div>
<div class="single_filter">
<h1>Multi Size</h1>
Medium
Large
</div>
</div>
</template>
<script>
export default {
data() {
return {
filters:{},
selectedFilters:{}
}
},
methods:{
activateFilter(key,value){
this.selectedFilters = Object.assign({},this.selectedFilters,{[key]:value})
console.log(this.selectedFilters)
this.$router.replace({
query: {
...this.selectedFilters
}
})
}
}
}
</script>
You are expecting size to be an array, this post will helps.
Submitting multi-value form fields, i.e. submitting arrays through GET/POST vars, can be done several different ways, as a standard is not necessarily spelled out.
Three possible ways to send multi-value fields or arrays would be:
?cars[]=Saab&cars[]=Audi (Best way- PHP reads this into an array)
?cars=Saab&cars=Audi (Bad way- PHP will only register last value)
?cars=Saab,Audi (Haven't tried this)

Event handling after HTML injection with Vue.js

Vue is not registering event handler for HTML injected objects. How do I do this manually or what is a better way to work around my problem?
Specifically, I send a query to my server to find a token in text and return the context (surrounding text) of that token as it exists in unstructured natural language. The server also goes through the context and finds a list of those words that also happen to be in my token set.
When I render to my page I want all of these found tokens in the list to be clickable so that I can send the text of that token as a new search query. The big problem I am having is my issue does not conform to a template. The clickable text varies in number and positioning.
An example of what I am talking about is that my return may look like:
{
"context": "When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected",
"chunks": ['human events', 'one people', 'political bands']
}
And the resulting output I am looking for is the sentence looks something like this in psuedocode:
When in the Course of <a #click='search("human events")'>human events</a>, it becomes necessary for <a #click='search("one people")'>one people</a> to dissolve the <a #click='search("political bands")'>political bands</a> which have connected
This is what I have tried so far though the click handler is not registered and the function never gets called:
<v-flex xs10 v-html="addlink(context.context, context.chunks)"></v-flex>
and in my methods section:
addlink: function(words, matchterms){
for(var index in matchterms){
var regquery = matchterms[index].replace(this.regEscape, '\\$&');
var query = matchterms[index];
var regEx = new RegExp(regquery, "ig");
words = words.replace(regEx, '<a href=\'#\' v-on:click.prevent=\'doSearch("'+ query +'")\'>' + query + '</a>');
}
return words;
}
As I said, this does not work and I know why. This is just showing that because of the nature of the problem is seems like regex is the correct solution but that gets me into a v-html injection situation. Is there something I can do in Vue to register the event handlers or can some one tell me a better way to load this data so I keep my links inline with the sentence and make them functional as well?
I've already posted one answer but I've just realised that there's a totally different approach that might work depending on your circumstances.
You could use event delegation. So rather than putting click listeners on each <a> you could put a single listener on the wrapper element. Within the listener you could then check whether the clicked element was an <a> (using event.target) and act accordingly.
Here's one way you could approach it:
<template>
<div>
<template v-for="segment in textSegments">
<a v-if="segment.link" href="#" #click.prevent="search(segment.text)">
{{ segment.text }}
</a>
<template v-else>
{{ segment.text }}
</template>
</template>
</div>
</template>
<script>
export default {
data () {
return {
"context": "When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected",
"chunks": ['human events', 'one people', 'political bands']
}
},
computed: {
textSegments () {
const chunks = this.chunks
// This needs escaping correctly
const re = new RegExp('(' + chunks.join('|') + ')', 'gi')
// The filter removes empty strings
const segments = this.context.split(re).filter(text => text)
return segments.map(segment => {
return {
link: segment.match(re),
text: segment
}
})
}
},
methods: {
search (chunk) {
console.log(chunk)
}
}
}
</script>
I've parsed the context text into an array of segments that can then be handled cleanly using Vue's template syntax.
I've used a single RegExp and split, which will not discard matches if you wrap them in a capture group, (...).
Going back to your original example, v-html only supports native HTML, not Vue template syntax. So you can add events using onclick attributes but not #click or v-on:click. However, using onclick wouldn't provide easy access to your search method, which is scoped to your component.

V-select bug while selecting elements in Vuejs

I'm building a small application in vuejs 2 where I'm using v-select package for select box, Problem I'm facing is:
I've declared v-select in my component something like this:
<div class="form-group"><label class="col-sm-2 control-label">Company name:</label>
<div class="col-sm-6">
<v-select :options="companyOptions" v-model="company_name" :on-search="getOptions" placeholder="Company name"></v-select>
</div>
</div>
So accordingly I'm having data defined as company_name, and I'm calling an axios event to get the searchable data, while the component is being loaded I'm calling index data of first 50 set for initial selection and if anybody types then I'm calling a function getOptions to get data related to the input, now suppose if somebody selects any data and then removes it again from the selection and again search with key press event the searchable data is not displayed, I can see that my axios call is working fine and I'm able to get the relevant data. but it is not displaying in dropdown as it says:
Error in render function: "TypeError: Cannot read property 'label' of null"
Which is coming from the company_name model which was selected. Following is my code in codepen
In this my axios is not working as it says mixed content:
https://codepen.io/anon/pen/Bdeqam?editors=1011' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://connect.stellar-ir.com/api/companies'. This request has been blocked; the content must be served over HTTPS.
So I'm unable to explain properly in this code set. But my code looks same as declared in codepen.
Help me out in this.
The error is because your computed values are undefined and undefined is not a string, so no string methods (toLowerCase()) are available. The response.data.model.data must look like this:
[
{
id: 1234,
name: 'example'
}, {
id: 12345,
name: 'example2'
}
]
if you get an object instead of an array push it to the array: this.serverData.push(response.data.model.data)
Replace your axios call with:
this.serverData = [
{
id: 1234,
name: 'example'
}, {
id: 12345,
name: 'example2'
}
]
to test it.
In your getOptions() method you calling loading(true or false), but your fetchIndexData() method has an asynchronous axios call. Use async/await, a callback function or a promise chain to wait for the data and show the loading indicator correctly.
On every keypress an request is send to the server i would recommend to use a debounce function.
Tipp
Line 42: https://stackoverflow.com/a/42028776/6429774
axios.post('http://connect.stellar-ir.com/api/companies', searchData).then(response => {
if(response.status === 200)
{
this.serverData = response.data.model.data
}
}).catch(error => {
console.log(error)
});