Click handler for dynamic inserted content from axios - vuejs2

Im using displayLog function in my vue methods to render logs (in foreach loop) I got from axios.
And i need to bind some handler to my dynamic content, for example changeThisNote.
How can i bind it to my dynamic inserted content?
If I code '#click="changeThisNote' Vue doesn't render it as I need.
Thanks.
methods: {
displayLog: function(log) {
let str = '';
let space = ' ';
let date = log.created_at;...str = '<div class="note" #click="changeThisNote(' + log.id + ')"><div class="note__date">'
+ date + space + name
+ '</div><div class="note__body" id="note_' + log.id + '">' + log.msg + '</div><div></div></div>';
return str;
},
changeThisNote: function(log_id) {
// here I need hide note__body, insert textarea with its data for editing
}
}

You shouldn't be building strings of HTML and inserting them into the DOM. That's missing out on some of the best features of Vue.
What you should do instead is use data to drive your template.
For example, store an array of log data in your component and render that in your template. When your Axios request completes, the template will update automatically
export default {
data: () => ({ logs: [] }),
methods: {
async getLogs () {
const { data } = await axios.get("/logs") // just an example
this.logs = data.map(log => ({
...log,
editing: false // add an "editing" flag
}))
}
}
}
<div
v-for="log in logs"
:key="log.id"
class="note"
#click="log.editing = true"
>
<div class="note__date">
{{ log.created_at }}
{{ log.name }} <!-- πŸ‘ˆ not sure where "name" was meant to come from -->
</div>
<textarea v-if="log.editing" v-model="log.msg"></textarea>
<div v-else class="note__body" :id="`note_${log.id}">
{{ log.msg }}
</div>
</div>

the simplest way would be to create the html element like this:
// ...
methods: {
displayLog (log) {
const div = document.createElement('div')
div.classList.add('note')
div.addEventListener('click', this.changeThisNote(log.id))
div.innerHTML = '<div class="note__date">' + date + space + name + '</div><div class="note__body" id="note_'+log.id+'">' + log.msg + '</div><div></div>'
}
}
// ...

Related

Vue.js : Range slider with two handles

I want to create a vue js components where it contains a range slider of hours with two handles.
I use vue3 + vite.js
I tried this code to implement the components but when I drag one of handles I have an error
Code :
this is the template :
<template>
<div>
<input type="range" ref="rangeInput" v-model="rangeValue" #input="updateRange"/>
<div class="range-slider">
<div class="handle" :style="{left: leftHandle + '%'}" #mousedown="startHandleDrag(1)">
{{ formatHour(rangeValue[0]) }}
</div>
<div class="handle" :style="{left: rightHandle + '%'}" #mousedown="startHandleDrag(2)">
{{ formatHour(rangeValue[1]) }}
</div>
</div>
</div>
</template>
and this is the script :
<script>
export default {
data() {
return {
rangeValue: [8, 18],
handleDragging: 0
};
},
computed: {
leftHandle() {
return this.rangeValue[0];
},
rightHandle() {
return this.rangeValue[1];
}
},
methods: {
updateRange(event) {
const value = event.target.value;
const range = this.rangeValue;
if (this.handleDragging === 1) {
range[0] = value[0];
} else if (this.handleDragging === 2) {
range[1] = value[1];
} else {
range[0] = value[0];
range[1] = value[1];
}
this.rangeValue = range;
},
startHandleDrag(handle) {
this.handleDragging = handle;
document.addEventListener("mouseup", this.stopHandleDrag);
document.addEventListener("mousemove", this.updateRange);
},
stopHandleDrag() {
this.handleDragging = 0;
document.removeEventListener("mouseup", this.stopHandleDrag);
document.removeEventListener("mousemove", this.updateRange);
},
formatHour(value) {
return value + ":00";
}
}
};
</script>
Error :
any ideas to solve it !!!
In your startHandleDrag() and stopHandleDrag(), you bind updateRange() to the mousemove event:
document.addEventListener("mousemove", this.updateRange);
There are two issues with that:
The target of the mousemove event is the element under the cursor. This can be any element, and unless it happens to be an input, it will not have a value attribute (and if it does, it will not hold an array). If you really want to use the "mousemove" event, use the cursor coordinates like pageX or pageX.
You bind it as a function pointer (addEventListener("mousemove", this.updateRange)), and when called from the listener, this will refer to element.target. To avoid this, either use an arrow function (addEventListener("mousemove", (e) => this.updateRange(e))) or bind this (addEventListener("mousemove", this.updateRange.bind(this))).
I don't fully understand what you want to do with the handles, but my guess is that adding and removing listeners is a workaround, and you actually want to make them draggable? If so, have a look at the drag event. Hope that helps!

Get name model in vue js with help id input or name

Can i get model name if i now id input?
For examle
<input v-model="data.name" id="name_db">
I have in db value for data.name
Before vue i did this:
valuesFromDb.forEach(data=>{
if(data.fromdb==name_db)
$("#name_db").val(data.fromdb)
}
...
But it can't work with vueJS
I know i can do this:
data.name = data.fromdb
But i have many data in db and before vue i put data with help forloop.
Model and id have different names ​​and it will take a long time to manually iterate through all the data
Now i want get model name and put value to it
Somethinks like this:
var modelName = $("#name_db").getModelNameVue();
modelName=data.fromdb
If i do this, in input value change but in data dont
data(){
return{
mainPdf:{
left: 5,
bottom:5,
top:5,
right:5
}
}
}
<input v-model="mainPdf.left" id="left_margin">
<input v-model="mainPdf.bottom" id="bot_margin">
<input v-model="mainPdf.isMargin" id="right_margin">
<input v-model="mainPdf.isMargin" id="up_margin">
getFromdb(){
api.getFromdb(e=>{ // string=e
var string = "left_margin=0&bot_margin=1&right_margin=2&up_margin=3"
var rPlus = /\+/g;
$.each( string.split( "&" ), function( index, field ) {
$.each( string.split( "&" ), function( index, field ) {
var current = field.split( "=" );
if( current[ 1 ] && current[ 0 ]) {
var name = decodeURIComponent(current[0].replace(rPlus, "%20"));
var value = decodeURIComponent(current[1].replace(rPlus, "%20"));
$("#"+ name).val(value);
}
});
})
})
I can't dynamic-binding because i can't change name of properties in mainPdf, because i have entity with same fields(left,bottom,top,right) in my backend
==========i found solution
i used dispatchEvent
$("#" + NAME).prop("checked", true);
$("#"+ NAME")[0].dispatchEvent(new Event('change')); //or input
Using Vue.js and dynamic programming techniques, it's a piece of cake.
Vue.component('dynamic-binding', {
template: `
<div style="display: flex;">
<input
v-for="field in Object.keys(mainPdf)" :key="field"
v-model="mainPdf[field]"
>
</div>
`,
data() {
return {
mainPdf: {},
};
},
methods: {
fromDb(val = 'left_margin=0&bot_margin=1&right_margin=2&up_margin=3') {
const db = new URLSearchParams(val);
this.mainPdf = Object.fromEntries(db);
},
},
mounted() {
this.fromDb();
},
});
new Vue({ el: '#app' });
<div id="app">
<dynamic-binding></dynamic-binding>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

How to Add javascript paggination code in Vue js component

I'm trying to add pagination code in the Vue component and I've tried to add the code in the mounted hook to call the function but it doesn't work. I want to load the code after component loaded completely.Also, jQuery code doesn't load in Vue component. Do I need to change my code to pure javascript for that. Can you guide me how to fix the issue?
// Create a root instance for each block
var vueElements = document.getElementsByClassName('search-bento-block');
var count = vueElements.length;
const store = new Vuex.Store({
state: {
query: drupalSettings.bento.query ? drupalSettings.bento.query : '',
bentoComponents: []
},
mutations: {
add (state, payload) {
state.bentoComponents.push(payload)
}
},
getters: {
getComponents: state => {
return state.bentoComponents
}
}
})
// Loop through each block
for (var i = 0; i < count; i++) {
Vue.component('results', {
template: `
<div v-if="results && results.length > 0">
<div v-for="result in results">
<div class="search-result-item">
<div class="image-holder">
<img src="https://images.unsplash.com/photo-1517836477839-7072aaa8b121?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=750&q=80">
</div>
<div class="container-content">
<a v-bind:href="result.url">
<h3 v-html="result.title"></h3>
</a>
<p>Subjects: <span v-html="result.subjects"></span></p>
</div>
</div>
</div>
</div>
<div v-else>
<p>No results found.</p>
</div>
`,
props: ['results'],
})
new Vue({
el: vueElements[i],
store,
data: {
message: 'Hello There!',
results: [],
total: 0,
bentoSettings: [],
},
methods: {
addComponentToStore: function (type) {
this.$store.commit('add', type);
console.log("test");
console.log(this.results.length);
}
},
mounted: function() {
// console.log(this.$route.query.bentoq);
const id = this.$el.id;
this.bentoSettings = drupalSettings.pdb.configuration[id];
var bentoConfig = drupalSettings.pdb.configuration[id].clients[this.bentoSettings.bento_type] ? drupalSettings.pdb.configuration[id].clients[this.bentoSettings.bento_type].settings : [];
axios
.get('/api/search/' + this.bentoSettings.bento_type, {
params: {
query: this.$store.state.query,
plugin_id: this.bentoSettings.bento_type,
bento_limit: this.bentoSettings.bento_limit,
bento_config: bentoConfig,
}
})
.then(response => {
console.log(response);
this.results = response.data.records;
this.total = response.data.total;
this.addComponentToStore({
title: this.bentoSettings.example_field,
count: this.total
});
})
.catch(error => {
console.log(error.response);
})
}
});
}
// I'm trying to call following function in Vue component.
function baseThemePagination1() {
//Pagination
pageSize = 3;
var pageCount = $('.line-content').length / pageSize;
for (var i = 0; i < pageCount; i++) {
$('#pagin').append('<li><a href=\'#\'>' + (i + 1) + '</a></li> ');
}
$('#pagin li').first().find('a').addClass('current')
showPage = function(page) {
$('.line-content').hide();
$('.line-content').each(function(n) {
if (n >= pageSize * (page - 1) && n < pageSize * page)
$(this).show();
});
}
showPage(1);
$('#pagin li a').click(function() {
$('#pagin li a').removeClass('current');
$(this).addClass('current');
showPage(parseInt($(this).text()))
});
}
What you are trying to do is not the recommended way to use vue, direct DOM manipulation is one of the things that vue is made to avoid (although can be done). The Vue way would be to bind the value you want to a variable with v-model assuming it is an input and then create your pagination based on that.
If you insist on DOM manipulation then try ref="line-content" and then call it like so:
this.refs.line-content.
In terms of reacting to a page change click simply use a method in your methods section there is no reason to use jQuery for that.
See here for a simple explanation:
https://medium.com/#denny.headrick/pagination-in-vue-js-4bfce47e573b

How to pass a variable and instantiate a new api request from my NavBar.vue component file to my News.vue views file?

I'm making an API request from https://newsapi.org/ and am able to do so with the created() method upon initiation. I have a component named Navbar.vue that includes buttons I'd like to use, upon click, to make a new api request and pass in a news source variable for the api request (e.g. 'cnn', 'fox-news'). Even though I've registered my News.vue in my Navbar.vue component, it doesn't appear I can use the created method to begin another instantiation. Here's a screen recording as well: https://drive.google.com/file/d/173x9PxLs5S2pWMYcHuXon0CQfoLwXNMT/view
I've tried calling NewsVue.created(source)
Top-Headlines/src/Components/Navbar.vue:
<template>
<div>
<b-navbar toggleable="lg" type="light" variant="success">
<b-container>
<b-navbar-brand href="#">Top Headlines</b-navbar-brand>
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
<b-collapse id="nav-collapse" is-nav>
<b-button-group>
<b-button variant="danger" v-on:click="getNews('cnn')">CNN</b-button>
<b-button variant="info" v-on:click="getNews('espn')">ESPN</b-button>
<b-button variant="warning" v-on:click="getNews('nbc-news')">NBC News</b-button>
</b-button-group>
</b-collapse>
</b-container>
</b-navbar>
</div>
</template>
<script>
// import News from '../views/News';
import NewsVue from '../views/News.vue';
export default {
// components: {
// NewsVue,
// },
data() {
return {
meal: ''
}
},
methods: {
getNews(source) {
console.log(NewsVue);
NewsVue.created(source);
}
}
}
Top-Headlines/src/views/News.vue:
<template>
<div class="articles-container">
<template v-for="article in headlines">
<div :key="article.publishedAt" class="article-container">
<div class="article-source">
<a v-bind:href="article.url">
<h5>{{ article.title }}</h5>
</a>
</div>
</div>
</template>
</div>
</template>
<script>
// # is an alias to /src
"use strict";
export default {
name: "news",
data() {
return {
headlines: [],
search: "",
newsSource: ""
};
},
methods: {
getTopHeadlines(newsSource) {
console.log(newsSource);
let url = '';
if (newsSource !== "" && newsSource !== undefined) {
url =
"https://newsapi.org/v2/top-headlines?" +
"pageSize=10&" +
"sources="+newsSource+"&" +
"apiKey=ab07dee4fb7e4f198621ab4da0b1e5e9";
} else {
url =
"https://newsapi.org/v2/top-headlines?" +
"country=us&" +
"pageSize=10&" +
"apiKey=ab07dee4fb7e4f198621ab4da0b1e5e9";
}
var req = new Request(url);
fetch(req)
.then(response => response.json())
.then(json => {
this.headlines = json.articles;
});
}
},
created(newsSource) {
this.getTopHeadlines(newsSource);
}
};
</script>
I expect the page to reload with news source filtered headlines.
Error messages:
"TypeError: this.getTopHeadlines is not a function
at Object.created (webpack-internal:///./node_modules/cache-"
created is normaly called by the system and has this set to the component. It seems you are trying to call it directly. You can either set this yourself by using apply, or by simply passing it in.
EITHER WAY, DON'T NAME THE FUNCTION CREATED, as it is reserved for the Vue lifecycle.
NewsVue.created2(source, NewsVue);
To call a function created2 and set the this context.
NewsVue.created2.call(NewsVue, source);
// or
NewsVue.created2.apply(NewsVue, [source]);
Either way, the function created2 will be invoked with this set to NewsVue and 1 parameter source.
Use a watcher function, then set the data from the watcher.
BTW, NewsView should take newsSource as a property, and I don't even see that component in your template... Perhaps that's the root of your issue. You need something like <NewsView :newsSource='newsSource'/> in the template. Then move newsSource to props, and make the watcher immediate.
export default {
name: "news",
data() {
return {
headlines: [],
search: "",
newsSource: ""
};
},
watch: {
newsSource(value) {
const newsSource = value;
console.log(newsSource);
let url = '';
if (newsSource !== "" && newsSource !== undefined) {
url =
"https://newsapi.org/v2/top-headlines?" +
"pageSize=10&" +
"sources=" + newsSource + "&" +
"apiKey=ab07dee4fb7e4f198621ab4da0b1e5e9";
} else {
url =
"https://newsapi.org/v2/top-headlines?" +
"country=us&" +
"pageSize=10&" +
"apiKey=ab07dee4fb7e4f198621ab4da0b1e5e9";
}
var req = new Request(url);
fetch(req)
.then(response => response.json())
.then(json => {
this.headlines = json.articles;
});
}
},
};

Add event listeners in VueJS 2

I am trying to add event listeners to my viewmodel once VueJS is loaded. Adding event listeners works if I do not use VueJS, so I know the code is correct but they never attach in VueJS.
<div id="app">
<div name="pageContent" id="preview">
<section class="row">
<div class="columns medium-12">
<h1>This is the top content</h1>
<p>ashcbaubvdiuavduabd</p>
</div>
</section>
<section class="row">
<div class="columns medium-6">
<h1>This is left content</h1>
<p>ashcbaubvdiuavduabd</p>
</div>
<div class="columns medium-6">
<h1>This is the right content</h1>
<p>ashcbaubvdiuavduabd</p>
</div>
</section>
</div>
</div>
<script type="text/javascript">
let editorContainer = document.getElementById('preview')
let controls = document.getElementById('defaultControls')
let cmsEditor = new CmsEditor(editorContainer, controls)
var app = new Vue({
el: '#app',
data: {
editor: cmsEditor
},
mounted: function () {
// wire up our listeners
console.log('mounted')
document.oncontextmenu = function () { return false }
let rows = this.editor.EditorContainer.getElementsByTagName('section')
for (var i = 0; i < rows.length; i++) {
console.log('section ' + i + ' : ' + rows[i].innerHTML)
rows[i].addEventListener('mouseover', function () {
console.log('mouse over event')
this.editor.SetActiveRow(this)
})
rows[i].addEventListener('dblclick', function () {
this.editor.DisplayContextMenu(this)
})
}
},
methods: {
save: function () {
console.log('save')
this.editor.Save()
},
undo: function () {
console.log('undo')
this.editor.Undo()
}
}
})
</script>
Looks like you are creating the editor on elements that will be removed from the DOM. Vue uses the content of #app as it's template, compiles the template into a render function, then replaces the DOM with the results of the render function. Given that editor is created on DOM elements that are gone now, I expect the code would fail.
You probably want to move the creation of the editor into mounted, then set up your event listeners.
FWIW, I also think you have the this issue mentioned by the commenters.
I think it should be something like this:
mounted: function() {
let editorContainer = document.getElementById('preview');
let controls = document.getElementById('defaultControls');
this.editor = new CmsEditor(editorContainer, controls);
// wire up our listeners
console.log('mounted')
document.oncontextmenu = function () { return false; };
let rows = this.editor.EditorContainer.getElementsByTagName("section");
for (var i = 0; i < rows.length; i++) {
console.log("section " + i + " : " + rows[i].innerHTML);
rows[i].addEventListener('mouseover', () => {
console.log('mouse over event');
this.editor.SetActiveRow(this);
});
rows[i].addEventListener('dblclick', () => {
this.editor.DisplayContextMenu(this);
});
}
},