Vue.js 2: Highlight string occurrence - vue.js

Is there a convenient way to highlight all string occurrences of a string in a text or an element?
Something like the filter method from vue.js 1?

The only problem with my solution is, that the whole v-html-text is now lowercase..
I defined the highlight-method in the methods-block:
methods: {
highlight(words, query) {
if(query === '') { return words }
if(typeof(words) === 'number') {
words = '' + words + ''
}
// when removing the .toLowerCase() your search becomes case-sensitive
return words.toLowerCase().replace(query, '<span style="background: yellow;">' + query + '</span>')
}
}
In my template it looks like this:
<span v-html="highlight('This is some sort of test', 'some')"> // should now highlight 'some'

There are filters in vuejs2 as well. You just create your own method to highlight.
<div>{{ 'some-text' | highlight }}
new Vue({
// ...
filters: {
highlight: function (value) {
// logic
}
}
})

Related

vue JS - Append data (strings) to a placeholder

I am a absolut beginner with vue.js. So I did a little app for learning. But I stuck a little bit with append-Data to a placeholder, if that is the right term, for
{{ message1 }}
Here is my code:
<script>
Vue.createApp({
data() {
return {
bheight: '',
bwidth: '',
}
},
methods: {
domath(event) {
alert(`Hello!`)
}
}
}).mount('#app')
</script>
The "domath" method is triggred by a button. That work (the allert show up). I have this placeholder "message1. {{ message1 }}
What I want is this: If the button is clicked, I want the data from "bheight" and "bwidth" appends to the placeholder {{ message1 }}
Later I want to do a math with this both variables. And append the result in the placeholder.
Id do not figured out how I can to this. Can some help me please?
You need to have an additional variable:
data() {
return {
bheight: '',
bwidth: '',
message1: ''
}
And then:
domath(event) {
this.message1 += this.bwidth + ', ' + this.bheight;
}
Using this is crucial iin this context.
Try something like this:
Add computed Property
computed: {
placeholder() {
return this.errors.has('end_date') ? 'Your placeholder text' : ''
}
}
Next Steps:
Bind to your computed placeholder property with v-bind:placeholder="placeholder"

Vue styling with a method

I'm trying to style a card with a method called needsApprove
<v-card #click="displayAmendments(article)" :style="needsApprove(article)? 'background: black;' : ''">
<h5 class="text-center p-2"
v-text="article.num + '. ' + article.title">
</h5>
</v-card>
Tried compured property:
needsApprove(article) {
article.amendments.forEach(amendment => {
if(amendment.approved == 0) {
return true
}
else {
return false
}
})
},
Tried method:
needsApprove(article) {
article.amendments.forEach(amendment => {
if(amendment.approved == 0) {
return true
}
else {
return false
}
})
},
It doesn't seem to work, it does return true although the styling doesn't seem to work, is this kind of thing possible? What am i doing wrong?
Try out a computed property with parameter like :
needsApprove() {
return articel=>article.amendments.some(am=>am.approved==0)
}
Boussadjra Brahim answered how to fix it but did not explained why it does not work.
forEach returns nothing and is used only to make certain action with every element of collection. To return something you can also use 'every' instead of 'some' to make sure that every element of collection satisfies the condition.

how to append multiple query parameters in url - NuxtJS

I am creating a nuxt ecommerce application. I have a situation where I have more than 10,000 items in a category and i want to create related filters for the products.
My question is how do i append url (add & remove query parameters) so that i can filter products.
I have tried something like this by adding a change event to !
<ul>
<li>
<b-form-checkbox #change="filterProduct">
<label class="ui__label_checkbox">Apple</label>
</b-form-checkbox>
</li>
<li >
<b-form-checkbox #change="filterProduct">
<label class="ui__label_checkbox">Mango</label>
</b-form-checkbox>
</li>
</ul>
methods:{
filterProduct() {
this.$router.push({ query: Object.assign({}, this.$route.query, { random: "query" }) });
},
}
This approach does append the url only once but removes the checked state of the checkbox which i don't want
I want similar to below everytime i click checkbox, it must retain the state of the checkbox at the same time append to the url
www.foobar.com/?first=1&second=12&third=5
Here's what you should do. First of all, you should all your filters state in data()
data() {
return {
filter: {
first: this.$route.query.first || null,
second: this.$route.query.second || null,
third: this.$route.query.third || null
}
}
}
Then you set up a watcher that fires when any filter changes, obviusly you need to v-model the inputs in your <template> to the fields in data()
watch() {
filter: {
handler(newFilters) {
const q = complexToQueryString({
...this.filter,
})
const path = `${this.$route.path}?${q}`
this.$router.push(path)
}
}
}
The complexToQueryString function is a thing of mine which removes null values from the query and also works for filters that are arrays. I did this because my API reads null as String 'null'.
const complexToQueryString = (object, parentNode = null) => {
const query = Object.entries(object).map((item) => {
const key = parentNode ? `${parentNode}[${item[0]}]` : item[0]
const value = item[1]
if (Array.isArray(value)) {
return arrayToQueryString(value, key)
} else if (value instanceof Object) {
return complexToQueryString(value, key)
} else if (item[1] !== undefined) {
return [
Array.isArray(item[0]) ? `${key}[]` : key,
encodeURIComponent(item[1]),
].join('=')
}
return ''
})
.filter(empty => empty)
.join('&')
return query
}
Now it should work, if you change the filter value then the data.filter.first changes the value, which fires the watcher, which updates the URL.
The best thing about this aproach is that now you can copy & paste the URL and the filter is exactly the same and returns the same result.
Your approach is almost correct, except that on page request, router-level You should append all the query parameters to route params.
Then asign those params to data inside Your filter page, and mutate them, also updating the query like You're doing now. This way You'll have query updated, and checkboxes wont lose state as they will depend on data, rather than on params.
routes: [{
path: '/path',
component: Component,
props: (route) => ({
filter1: route.query.filter1,
filter2: route.query.filter2,
filter3: route.query.filter3
})
}]

How to separate in vue.js entered value and displayed value in input[type=text]?

Example:
<span class='prefix'>+{{ prefix }}</span>
<input type='tel' v-model='phone'>
What should be displayed
When phone === '790012345678', it is actually
prefix = '7'
phone = '90012345678'
And displayed accordingly
<span class='prefix'>+7</span>
<input type='tel' value='90012345678'>
When user removes value from input, prefix is removed too.
Problem (jsfiddle http://jsfiddle.net/4qqza69k/48/)
I use watcher for `phone`.
When user changes something inside `input` watcher must update value for `phone`, but this way it is triggered again and it receives updated (incorrect) value.
Scenarios:
Phone equals 7-100-200-30-40
prefix = +7, phone = 1002003040
Phone equals 7
prefix = +7, phone = ''
Phone equals 7123
prefix = +7, phone = 123
Phone is empty
prefix = '', phone = ''
Problem: how to exclude prefix from input without triggering updates?
I think you need to rewrite v-model into more explicit v-on + v-bind pair and listen to input for a phone number, while calculating prefix and the rest part separately:
new Vue({
el: '#app',
data: {
prefix: '',
phone: '', // full phone number
},
methods: {
handleInput: function(e) {
if (e.target.value === '') this.prefix = '';
if (this.prefix !== '') {
this.phone = this.prefix + e.target.value;
} else {
const v = e.target.value;
this.phone = v;
this.prefix = v.slice(0,1);
}
}
},
computed: {
withoutPrefix: function() {
return (this.prefix !== '') ? this.phone.slice(1) : ''
}
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<span class='prefix'>+{{ prefix }}</span>
<input type='tel' #input="handleInput" :value="withoutPrefix">
<p>Phone: {{ phone }}</p>
</div>
It does not exactly work cause I'm being a bit confused by your example, but I think one way is to use computed getter/setter instead.
data() {
return {
hiddenInputValue: ''
}
},
computed: {
inputValue: {
get() {
return this.hiddenInputValue;
},
set(val) {
// do something with the input value...
this.hiddenInputValue = // assign a modified version of the input value...
}
}
}
You should be able to do something with that, but check my comment. It's probably a better solution.

Only null or Array instances can be bound to a multi-select

I'm building a multi-step form in Aurelia where each page shows one question.
I use the same view for every question, with if statements determining what type of form field to show.
When I try to bind my question data to a multiple select element however, Aurelia throws errors and says "Only null or Array instances can be bound to a multi-select.".
What's really strange is that if the first question is a multiple select I don't get the error until I come to a non-multiselect question and then go back to the multiselect question.
I can solve this entire problem by setting activationStrategy: 'replace' for this route, but I really don't want that.
The important code follows:
import {inject} from 'aurelia-framework';
import {Router} from 'aurelia-router';
#inject(Router)
export class Form {
constructor (router) {
this.router = router;
this.active = 0;
this.field = null;
this.fields = [
{
type: 'text',
value: null
},
{
type: 'select',
value: [],
options: [
'foo',
'bar'
]
},
{
type: 'select',
value: [],
options: [
'foo',
'bar'
]
},
{
type: 'text',
value: null
},
];
}
activate (routeParams) {
this.active = routeParams.fieldIndex || 0;
this.active = parseInt(this.active);
this.field = this.fields[this.active];
}
prev () {
if (typeof this.fields[this.active - 1] !== 'undefined') {
this.router.navigateToRoute('form', {
fieldIndex: this.active - 1
});
return true;
}
else {
return false;
}
}
next () {
if (typeof this.fields[this.active + 1] !== 'undefined') {
this.router.navigateToRoute('form', {
fieldIndex: this.active + 1
});
return true;
}
else {
return false;
}
}
}
And the template:
<template>
<div class="select" if.bind="field.type == 'select'">
<select value.bind="field.value" multiple="multiple">
<option repeat.for="option of field.options" value.bind="option">${option}</option>
</select>
</div>
<div class="text" if.bind="field.type == 'text'">
<input type="text" value.bind="field.value">
</div>
<a click.delegate="prev()">Previous</a> | <a click.delegate="next()">Next</a>
</template>
But you'll probably want to check out the GistRun: https://gist.run/?id=4d7a0842929dc4086153e29e03afbb7a to get a better understanding.
Try setting the first question to a multiselect and you'll notice the error disappears (until you go back to it). You can also try activationStrategy in app.js like mentioned above.
Why is this happening and how can I solve it?
Also note that in my real app I'm actually using compose instead of ifs but have tried with both and both produce the same error. It almost seems as if the select values are bound before the if is evaluated, causing the error to show up because the text field type lacks the options array.
A little late but I wanted to give a suggestion -- for SELECT multi-selects, you should decouple the bound variable from the multi-selector to prevent those errors.
For example, if you in your custom elements that bind to 'selected', they should bind to:
<select multiple value.two-way="selectedDecoupled">
Then when the actual variable 'selected' changes, it only changes in the custom element if the bound value is an array:
selectedChanged( newV, oldV ){
if( typeof newV =='object' )
this.selectedDecoupled = newV;
else
this.selectedDecoupled = [];
$(this.SELECT).val(this.selectedDecoupled ).trigger('change');
}
Example of it in use with a custom select2 element:
https://github.com/codefreeze8/aurelia-select2
Ok so it turns out swapping the order of the HTML, and putting the select after the input solves this issue.
Jeremy Danyow explains it like this:
When Form.field changes, the bindings subscribing to that property's changes evaluate sequentially. Which means there's a period of time when the select AND the input are both on the page. The html input element coaleses null values to empty string which in turn causes field.value to be empty string, which makes the multi-select throw.
Very tricky to track down imo but I'm glad the Aurelia devs are so helpful over on Github.
Working Gist: https://gist.run/?id=3f88b2c31f27f0f435afe14e89b13d56