How to add link to objects property value? - vuejs2

Please suggest a way for me to add link/anchor tag to the value of objects property in vue.js.
I.e. I want the word journey in the title property to have a link. Coudn't able to find a solution since I am new to vue.
My template:
<blog-post v-bind="post"></blog-post>
Vue Script:
post: {
id: 1,
title: 'My Journey with Vue'
}

You can inject HTML code into an HTML component with:
post: {
id: 1,
title: 'My Journey with Vue'
}
<span v-html="post.title"></span>
But, this method is dangerous because, if the post title value comes from an unreliable source, like an user input, it can lead to becoming the cause of possible XSS attacks.
Use v-html carefully.

Related

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: binding select boxes, but don't want to ajax all the options

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.

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.

Integrate bootstrap 3 typeahead and tags input with objects as tags

I am having trouble integrating bootstrap 3 typeahead with tags input but with objects as tags. It works if I use only typeahead on input field, but if I integrate it with tags input then it doesn't work and I don't even get any errors which is really frustrating. Here's my code:
var places = [{name: "New York"}, {name: "Los Angeles"}];
//this works
$('input').typeahead({
source: places
});
//this doesn't
$('input').tagsinput({
freeInput: false,
confirmKeys: [44],
typeahead: {
source: places
}
});
Am I doing something wrong or is this a bug?
If anyone has a working example of this I'd really appreciate some help, it can be typeahead.js instead of bootstrap 3 typeahead, I tried to use that as well and it works but then I have a problem where if I choose a suggested option from typeahead clicking enter submits the whole form instead of just accepting that option as a tag.
You should attach the typeahead to tagsinput via the typeahead option! This is much easier (and what the docs suggests). Then, if you map() places to an array of strings it works :
$('.tagsinput-typeahead').tagsinput({
// other tagsinput options here
typeahead: {
source: places.map(function(item) { return item.name }),
afterSelect: function() {
this.$element[0].value = '';
}
}
})
demo -> http://jsfiddle.net/gm3a1s9k/1/
Notice the afterSelect(), it is needed in order to clear the input.

Dojo declarative vs. programmatic creation of Select elements with stores

I'm trying to hook up a Select element with a Dojo store. The Select element is declared in HTML and I'm trying to give it a store in some JavaScript code.
It seems the Dojo documentation recommends against this and is in favor of programatically creating the Select element when using a store. However this is a yellow flag to me because I like to keep creation of HTML elements separate from their behavior. In this case, it would be ideal if I could keep the Select element in HTML and hook up the store in JavaScript.
Is the statement in the Dojo docs really the 'best practice' for this? I'm looking for opinions from experienced Dojo developers as I'm still getting my feet wet with Dojo.
Intuitively one would use select.set("store", store) to assign/change store to a dijit as all widgets are dojo/Stateful, but surprisingly it does not work.
Anyway there is a method select.setStore(store, selectedValue, fetchArgs) which (also surprisingly) is not deprecated and works.
Define dijit/form/Select without a store:
<select id="select1" data-dojo-type="dijit/form/Select"></select>​
Assign a store to it:
require([
"dojo/ready",
"dijit/registry",
"dojo/store/Memory",
], function(
ready, registry, Memory
) {
ready(function() {
var store1 = new Memory({
idProperty: "value",
data: [
{ value: "AL", label: "Alabama" },
{ value: "AK", label: "Alaska" },
{ value: "AZ", label: "Arizona" }
]
});
var select1 = registry.byId("select1");
select1.set("labelAttr", "label");
select1.setStore(store1, "AZ");
});
});
See it in action at jsFiddle: http://jsfiddle.net/phusick/ZmsYV/
Adding some UX sugar to the aforementioned I would create dijit/form/Select disabled with single option e.g. Loading... and its final desired width:
<select
id="select1"
data-dojo-type="dijit/form/Select"
data-dojo-props="disabled:true"
style="width:150px;"
>
<option>Loading...</option>
</select>​
Then I would enable it after calling setStore():
var select1 = registry.byId("select1");
select1.set("labelAttr", "label");
select1.setStore(store1);
select1.set("disabled", false);
See this enhanced version at work: http://jsfiddle.net/phusick/xdDEm/
Debugging bad store data/definitions can get pretty nasty when doing so declaratively. Additionally, you may run into strange annoyance when trying to create multiple of the same widget following a declaratively built select/store combination. For example (pseudocode):
<div dojotype="dojox.data.QueryReadStore" url="someurl/blah.do" jsId="mystore"/>
<select dojotype="dijit.form.FilteringSelect" store="mystore">
</select>
The would in theory do what you want by binding mystore to the select, however if you were to create multiple of this widget, you'd have an id conflict with "mystore." As a workaround you'd have to do something like jsId="${id}_mystore" for both the jsId and the store's id.
One option if you would like to keep a declarative behavior is to have attachpoints for both your store and your select, then you can simply call selectwidget.set("store",mystore) after initialization.