Changing the attribute of each element in a v-for array - javascript

I am attempting to figure things out with Vue way of writing javascript.
Considering this scenario:
at .vue template
<button v-on:click="biggerFont()" class="btn btn-s btn-default" type="button" name="button">A</button>
<div v-for="(tweet, index) in tweets">
<div class="each_tweet">
<textarea v-on:keyup="typing(index)"
v-model="tweet.content"
placeholder="What's happening now">{{ tweet.content }}</textarea>
</div>
</div>
at .vue <script>
export default {
methods: {
biggerFont: function() {
//can I somehow use this.tweets.style.fontSize to influence all the fontSize?
document.getElementsByClassName("each_tweet").style.fontSize = "xx-large"
}
}
}
My question is:
how do I go about changing the font-size of each value of the textarea in tweets? Is there a default Vue way of influencing these fontSize?
I tried and failed using document.getElementsByClassName.style.fontSize and it does not seems to be the Vue way. Thank you!

I believe the Vue way of doing this is using class and style bindings. For example:
<textarea v-bind:class="{ big: enlarge}">{{item}}</textarea>
https://jsfiddle.net/e064m859/

document.getElementsByClassName method returns a nodeList so you have to access a DOM element using its index.
export default {
methods: {
biggerFont: function() {
var tweets=document.getElementsByClassName("each_tweet");
for(var i=0;i<tweets.length;i++){
tweets[i].style.fontSize = "xx-large";
}
}
}
}

Related

Can you use functions from an imported JavaScript library such as Change Case directly in a Vue component's template?

I understand how to import and use Change Case within the <script></script> element of a Vue component, which is just the standard Javascript import covered in the Change Case Github page. However, I would like to use the Change Case functions directly in the template if possible.
Currently, it is my understanding that for dynamic content in the template, in this case generated by v-for running through an array, I must render the return value of a intermediary method from the component's methods section which applies the Change Case function. A method is required for each case type (e.g. camelCase, snakeCase, etc.) I want to render, in this instance one (capitalCase). For example:
// ...
<div
v-for="location in locations"
:key="location.name"
>
<input
type="checkbox"
:id="`select-${location.name}`"
:value="capitalCaseLocationName(location.name)"
v-model="locationsInput"
/>
<label :for="`select-${location.name}`">
{{ capitalCaseLocationName(location.name) }}
</label>
</div>
// ...
methods: {
capitalCaseLocationName(name) {
return capitalCase(name)
}
},
// ...
It would be preferable to somehow import Change Case into the template logic so I could write it like this (no intermediary methods needed):
// ...
<div
v-for="location in locations"
:key="location.name"
>
<input
type="checkbox"
:id="`select-${location.name}`"
:value="capitalCase(location.name)"
v-model="locationsInput"
/>
<label :for="`select-${location.name}`">
{{ capitalCase(location.name) }}
</label>
</div>
// ...
Any chance of that being possible?
As long as you register the imported function as a method you should be able to use it directly in the template.
According to the code, you use Options API, so something like this should do the trick:
import {capitalCase} from "change-case";
...
methods: {
capitalCase,
myOtherMethod () => {...}
}
...
And in the <template>:
<input
type="checkbox"
:id="`select-${location.name}`"
:value="capitalCase(location.name)"
v-model="locationsInput"
/>
The functions need to be defined and passed to the template, that is why even console.log won't work from a template.
You already have an answer with an example, but here's another thing you could do that might make things easier.
You can create a helper like this:
template-helpers.js
export function capitalCase(str) {
return str.split(" ").map(wrd => wrd[0].toUpperCase() + wrd.slice(1)).join(" ")
}
export default {
capitalCase
}
this would make it so that you could use it in a composition/setup like this
import templateHelpers from "../utils/template-helpers.js";
setup(){
return{
...templateHelpers
}
}
in an options API component you could just include it like this
import templateHelpers from "../utils/template-helpers.js";
// ...
methods: {
...templateHelpers,
// other methods
}
// ...
Example
by exporting functions in export default you can destructure them by using methods: { ...templateHelpers
the downside is that it would all the methods every time, but it would make for a more convenient solution. Alternatively, you can pick and chose, since the functions are also exported
import {capitalCase} from "../utils/template-helpers.js";
// ...
methods: {
capitalCase,
// other methods
}
// ...
Vue does have a way to add global definitions, but it's discouraged. This would be done by assigning it to config.globalProperties
https://vuejs.org/api/application.html#app-config-globalproperties
app.config.globalProperties.capitalCase = (str) => {
return str.split(" ").map(wrd => wrd[0].toUpperCase() + wrd.slice(1)).join(" ")

apply function from methods to reverse string in paragraph in vue.js

Dears, I have tried to apply function to reverse string in paragraph text in vue.js,
I have created function to reverse words in methods called (reverseword) and added it card using :rule="reverseword()",but it does not work. your support is highly appreciated
Code:
<div class="post-box">
<span class="post-viwes">{{viwes}}</span>
<h3 class="post-title">{{title}}</h3>
<span class="post-date">{{date}}</span>
<p class="post-content">{{content}}</p>
<div class="row">
<div class = "col-sm-6 text-right">
<span class="post-author">{{author}} </span>
</div>
<div class = "col-sm-6 text-right" :rules="reverseword()">
<span class="post-category" >{{category.toUpperCase()}}</span>
</div>
</div>
)
</div>
</template>
<script>
export default {
props:["viwes","title","date","content","author","category"],
name:"posts",
methods: {
reverseWord: function () {
this.category = this.category.split('').reverse().join('')
}
}};
</script>```
Your reverseWord method attempts to mutate a prop (category).
You can't mutate a prop, because the mutation would be overwritten by the first update from the parent.
If/when you do want/need to change a prop value, you have do it in the parent component which will then pass the change down to the child component, through the props binding.
Updating the parent from child can be done by
either using $emit
or by using a store, external to both the child and the parent.
If, in fact, you don't want to mutate category, you just need to be able to use its reverse inside the template, create a computed property:
computed: {
reversedCategory() {
return this.category.split('').reverse().join('');
}
}
Use it in template as you would use a normal property:
<div class = "col-sm-6 text-right" :rules="reversedCategory">
<span class="post-category" >{{category.toUpperCase()}}</span>
</div>
The computed is reactive. Meaning every time category changes, reverseCategory will update accordingly.

Template Literals - Changing the Inner HTML of an element (onInput event)

So I´ve never used Template Literals before, but I need to work with a template now that seemingly includes a form with Template Literals
This is one of my inputs:
<input
type="number"
className="mf-input "
min="1900"
max="2099"
step="1"
id="curyear"
name="currentyear"
placeholder="${ parent.decodeEntities(`Current Year`) } "
onInput=${parent.handleChange}
aria-invalid=${validation.errors['currentyear'] ? 'true' : 'false'}
ref=${el => parent.activateValidation({"message":"This field is required.","minLength":1900,"maxLength":2099,"type":"by_character_length","required":false,"expression":"null"}, el)}
/>
<${validation.ErrorMessage}
errors=${validation.errors}
name="currentyear"
as=${html`<span className="mf-error-message"></span>`}
/>
What I am trying to do is, in the onInput method, instead of handling the validation, I also want to change the innerHTML of an element:
<h2 class="elementor-heading-title elementor-size-default" id="curyeartext">Current Year</h2>
I´ve been trying to do it for hours, but I can't get it to work.
EDIT: turns out, they are template literals and not reactJS
Avoid setting innerHTML inside React, use state instead. This is because React will overwrite your modified DOM when it re-renders, if the html is in a node that React is controlling.
export default function MyReactComponent() {
var [ currentInput, setCurrentInput ] = React.useState();
return <>
<input
type="number"
className="mf-input"
min="1900"
max="2099"
step="1"
name="currentyear"
onInput={(e) => setCurrentInput(e.target.value)}
value={currentInput}
/>
<h2 class="elementor-heading-title elementor-size-default" id="curyeartext">
{currentInput}
</h2>
</>;
}
However, if you have a situation where it is unavoidable, you can tell React to ignore a node by only specifying a ref on it - i.e. no other props or child JSX:
export default function MyReactComponent() {
return <div ref={divRef => {
divRef.innerHTML = "Hello <b>world!</b>";
}} />
}
This technique is typically used when integrating non-React specific JS libraries into React, as you can do whatever you want with the contents of that DOM node.
I managed to do this by adding a function above the return:
function myfunction() {
document.querySelector('#curyeartext').innerHTML = document.querySelector('#curyear').value;
document.querySelector('#lastyear').innerHTML = document.querySelector('#curyear').value-1;
document.querySelector('#yearbefore').innerHTML = document.querySelector('#curyear').value-2;
}
And call it in the onInput event handler
onInput=${myfunction}

Vue - dynamically create divs that use e.g v-on:click

I have something like that:
<div v-if="dataIsLoaded" v-for="(line, index) in json.data" v-on:dblclick="edit(index)" v-html="test(index)">
</div>
and test(index) returns html-ish string:
<div id=${index} v-on:click="remove(index)"></div>
How can I make it work?
My goal is:
If dataIsLoaded == true, then foreach (line, index) in json.data perform
test(index) and return its output to that container and display it as html.
Meanwhile output from test(index) can have different functions/events associated with them defined via string (as I showed above).
Why are you using v-html here? v-html should really only be needed in very specific and limited situations; I don't think it applies here. The string <div id=${index} v-on:click="remove(index)"></div> is not plain HTML, it's a Vue template string, so it won't work. v-html is also susceptible to XSS attacks; all in all I say avoid it like the plague.
Do something like this instead:
<template v-if="dataIsLoaded">
<div v-for="(line, index) in json.data" #dblclick="edit(index)">
<!-- Type1 -->
<div v-if="line.type === 'Type1'" :id="index" #click="remove(index)"></div>
<!-- Type2 -->
<div v-else-if="line.type === 'Type2'">...</div>
</div>
</template>
I've added the type property on each line object as a discriminator to determine which template should be used for the line.
Also I've hoisted the v-if="dataIsLoaded" into a <template> above the div because v-if is evaluated for each div generated by the v-for and the condition doesn't depend on the children so it needn't be repeated for each child (a minor optimization).
If you don't like the idea of having lots of v-if and v-else-if (a sort of "switch" statement in the template) then you can use <component :is="..."> instead:
<div v-for="(line, index) in json.data" #dblclick="edit(index)">
<component :is="line.type" :id="index"/>
</div>
import Type1 from './type1.vue'
import Type2 from './type2.vue'
export default {
components: {
Type1,
Type2
}
}
It's not possible with v-html, as the doc states
Updates the element’s innerHTML. Note that the contents are inserted
as plain HTML - they will not be compiled as Vue templates
A more component-thinking solution is to create a component(s) with needed behaviour and paste them as a results of v-for:
// componentWithOnClick.js
<script>
export default {
name: 'ComponentWithOnClick',
methods: {
remove() {
//
}
}
</script>
<template>
<div id=${index} v-on:click="remove(index)"></div>
</template>
and then use it in the parent file:
//app.js
import ComponentWithOnClick from './ComponentWithOnClick.vue'
{
components: {
ComponentWithOnClick
}
//
<div v-if="dataIsLoaded" v-for="(line, index) in json.data" v-on:dblclick="edit(index)">
<ComponentWithOnClick></ComponentWithOnClick>
</div>

Async loading a template in a Knockout component

I'm pretty experienced with Knockout but this is my first time using components so I'm really hoping I'm missing something obvious! I'll try and simplify my use case a little to explain my issue.
I have a HTML and JS file called Index. Index.html has the data-bind for the component and Index.js has the ko.components.register call.
Index.html
<div data-bind="component: { name: CurrentComponent }"></div>
Index.js
var vm = require("SectionViewModel");
var CurrentComponent = ko.observable("section");
ko.components.register("section", {
viewModel: vm.SectionViewModel,
template: "<h3>Loading...</h3>"
});
ko.applyBindings();
I then have another HTML and JS file - Section.html and SectionViewModel.js. As you can see above, SectionViewModel is what I specify as the view model for the component.
Section.html
<div>
<span data-bind="text: Section().Name"></span>
</div>
SectionViewModel.js
var SectionViewModel = (function() {
function SectionViewModel() {
this.Section = ko.observable();
$.get("http://apiurl").done(function (data) {
this.Section(new SectionModel(data.Model)); // my data used by the view model
ko.components.get("dashboard", function() {
component.template[0] = data.View; // my html from the api
});
});
}
return SectionViewModel;
});
exports.SectionViewModel = SectionViewModel;
As part of the constructor in SectionViewModel, I make a call to my API to get all the data needed to populate my view model. This API call also returns the HTML I need to use in my template (which is basically being read from Section.html).
Obviously this constructor isn't called until I've called applyBindings, so when I get into the success handler for my API call, the template on my component is already set to my default text.
What I need to know is, is it possible for me to update this template? I've tried the following in my success handler as shown above:
ko.components.get("section", function(component) {
component.template[0] = dataFromApi.Html;
});
This does indeed replace my default text with the html returned from my API (as seen in debug tools), but this update isn't reflected in the browser.
So, basically after all that, all I'm really asking is, is there a way to update the content of your components template after binding?
I know an option to solve the above you might think of is to require the template, but I've really simplified the above and in it's full implementation, I'm not able to do this, hence why the HTML is returned by the API.
Any help greatly appreciated! I do have a working solution currently, but I really don't like the way I've had to structure the JS code to get it working so a solution to the above would be the ideal.
Thanks.
You can use a template binding inside your componente.
The normal use of the template bindign is like this:
<div data-bind="template: { name: tmplName, data: tmplData }"></div>
You can make both tmplData and tmplName observables, so you can update the bound data, and change the template. The tmplName is the id of an element whose content will be used as template. If you use this syntax you need an element with the required id, so, in your succes handler you can use something like jQuery to create a new element with the appropriate id, and then update the tmplname, so that the template content gets updated.
*THIS WILL NOT WORK:
Another option is to use the template binding in a different way:
<div data-bind="template: { nodes: tmplNodes, data: tmplData }"></div>
In this case you can supply directly the nodes to the template. I.e. make a tmplNodes observable, which is initialized with your <h3>Loading...</h3> element. And then change it to hold the nodes received from the server.
because nodesdoesn't support observables:
nodes — directly pass an array of DOM nodes to use as a template. This should be a non-observable array and note that the elements will be removed from their current parent if they have one. This option is ignored if you have also passed a nonempty value for name.
So you need to use the first option: create a new element, add it to the document DOM with a known id, and use that id as the template name. DEMO:
// Simulate service that return HTML
var dynTemplNumber = 0;
var getHtml = function() {
var deferred = $.Deferred();
var html =
'<div class="c"> \
<h3>Dynamic template ' + dynTemplNumber++ + '</h3> \
Name: <span data-bind="text: name"/> \
</div>';
setTimeout(deferred.resolve, 2000, html);
return deferred.promise();
};
var Vm = function() {
self = this;
self.tmplIdx = 0;
self.tmplName = ko.observable('tmplA');
self.tmplData = ko.observable({ name: 'Helmut', surname: 'Kaufmann'});
self.tmplNames = ko.observableArray(['tmplA','tmplB']);
self.loading = ko.observable(false);
self.createNewTemplate = function() {
// simulate AJAX call to service
self.loading(true);
getHtml().then(function(html) {
var tmplName = 'tmpl' + tmplIdx++;
var $new = $('<div>');
$new.attr('id',tmplName);
$new.html(html);
$('#tmplContainer').append($new);
self.tmplNames.push(tmplName);
self.loading(false);
self.tmplName(tmplName);
});
};
return self;
};
ko.applyBindings(Vm(), byName);
div.container { border: solid 1px black; margin: 20px 0;}
div {padding: 5px; }
.a { background-color: #FEE;}
.b { background-color: #EFE;}
.c { background-color: #EEF;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="byName" class="container">
Select template by name:
<select data-bind="{options: tmplNames, value: tmplName}"></select>
<input type="button" value="Add template"
data-bind="click: createNewTemplate"/>
<span data-bind="visible: loading">Loading new template...</span>
<div data-bind="template: {name: tmplName, data: tmplData}"></div>
</div>
<div id="tmplContainer" style="display:none">
<div id="tmplA">
<div class="a">
<h3>Template A</h3>
<span data-bind="text: name"></span> <span data-bind="text: surname"></span>
</div>
</div>
<div id="tmplB">
<div class="b">
<h3>Template B</h3>
Name: <span data-bind="text: name"/>
</div>
</div>
</div>
component.template[0] = $(data)[0]
I know this is old, but I found it trying to do the same, and the approcah helped me come up with this in my case, the template seems to be an element, not just raw html

Categories