How to expose wrapped <input> in Vue? - vue-component

I'm trying to create a reusable styled input field in Vue. To make it styled (e.g. with an icon inside) I need to wrap it in another html-element.
Lets call the example below StyledInput
<div class="hasIcon">
<input />
<i class="someIcon"></i>
<div>
If I want to use StyledInput it might look like so:
<styled-input #keyup.enter="doSomething">
</styled-input>
But this would not work, due to the event listener being attached to the <div> instead of the <input>.
A workaround to that could be to emit all key-events from the input field:
<div class="hasIcon">
<input #keyup="$emit('keyup', $event) />
<i class="someIcon"></i>
<div>
But this will not scale well since it would have to be rewritten every time a developer uses an unmapped prop or event.
Is there a way to only make the inner element exposed to whomever uses it?

I'm not sure there is a Vue way to achieve this, because, as far as I'm aware there is no way to bind vue events dynamically, it is however possible to do this using vanilla javascript by passing all events as a prop then mapping them using addEventListener() to add your custom events:
Vue.component('my-input', {
template: "#my-input",
props: ['events'],
mounted() {
// get the input element
let input = document.getElementById('styled-input');
// map events
this.events.forEach((event) => {
let key = Object.keys(event);
input.addEventListener(key, event[key]);
});
}
})
Then you can just pass through all events as a prop like so:
<my-input :events="events"></my-input>
View Model:
var app = new Vue({
el: "#app",
data: {
events: [{
focus: () => {
console.log('focus')
}
}, {
keyup: (e) => {
console.log(e.which)
}
}]
}
})
Heres the JSFiddle: https://jsfiddle.net/h1dnk40v/
Of course, this means any developer would have to do things like map key codes etc, so you will lose some of the convenience Vue provides.
One thing I will just mention is that Vue components aren't necessarily intended to be infinitely reusable, they are supposed to provide specific functionality and encapsulate complex logic, so you would probably do better to implement the most likely use cases, and if the component doesn't fit you can extend it or write a new one for that particular event.

You can also use $attrs to pass props and events onto children elements:
<template>
<div>
<input v-bind="$attrs">
</div>
</template>
In Vue 3, you can specify a second script tag:
<script setup>
</script>
<script>
export default {
inheritAttrs: false,
};
</script>
https://vuejs.org/guide/components/attrs.html#disabling-attribute-inheritance

You could use slots to achieve this. If your <styled-input> template looks like this:
<div class="hasIcon">
<slot><input></slot>
<i class="someIcon"></i>
<div>
Then you can use it like this:
<styled-input>
<input #keyup.enter="doTheThing">
</styled-input>
Or, in cases where you don't care about the input events, like this:
<styled-input></styled-input>
and the default slot content (a bare <input>) will be used. You can use CSS to style the <input> inside the component, but you can't add custom properties or classes to it, so this approach may or may not fit your requirements.

Related

Vue3 Prop passing to data item to be able to manipulate values, not modelling data item actually, why?

I am using vue3 and wonder how to pass data the correct way.
My Component structure is one table (items loaded via pinia store): XTableComponent
The XTableComponent has a child: XModalComponent. In the rendered table I have a button in each row. #click on that stores the current item in a data item
XTableComponent:
<template>
...that mentioned table in each line a button with #click and the item in the iteration as param
<x-model-component v-if="currentItem" :item="currentItem ref="x-modal"></x-modal-component>
</template>
<script>
export default {
data: () => {
return {
currentItem: {},
itemListStore: useItemListStore()
}
},
computed: {
itemList() {
return this.itemListStore.list
}
methods: {
showModal(item){
this.currentItem = item
this.$refs['x-modal'].show()
}
}
}
</script>
My Child component looks a bit like this:
XModalComponent:
<template>
....
<input v-model:value="innerItem.something" type="text">
<button #click="save">save</button>
</template>
<script>
export default {
props: {
item: Object
},
data: () => {
return {
innerItem: {}
}
}
mounted() {
this.innerItem = item
},
methods: {
save() {
console.log(this.innerItem) //this does not show the manipulated value of `something`
}
}
}
</script>
Now, if I manipulate the input in my child component, and trigger a click event, the value does not get changed on my data item ...
what did I get wrong in vue3 with reactiveness, proxeis and passing props?
p.s. my code is kind of pseude code here, so please be fair with me on typos, or obvious parts
that are missing
p.p.s. I am used to vue2 quite well, so maybe I mix concepts. please tell me that too.
p.p.p.s. my table renders correctly, the modal looks fine. i double checked all names and typos.
So, as we figured out, the problem came from the way innerItem.something was bound to the input, and some confusion around the v-model directive.
As a recap, the v-model directive is short-hand for setting a prop on a component and listening to an event which updates the value.
In Vue 2, that was:
<child-component
:value="myValue"
#input="(nevValue) => myValue = newValue"
/>
which is equivalent to
<child-component v-model="myValue"/>
and it allows a variable to be changed by parent as well as child ("two-way binding"). Note that property name and event matches that of a HTML input element (the "value" attribute and the "input" event), probably because it represents the most familiar case, where a value is bound to an input:
<input type="text" v-model="myText"/>
However, to allow for multiple two-way bindings on a component, Vue 2 also introduced a second way, which allows to bind to any of the child components props, not just "value". This is the .sync modifier:
<child-component :childComponentProp.sync="myVar"/>
which is equivalent to:
<child-component
:childComponentProp="myVar"
#update:childComponentProp="(newValue) => myVar = newValue"
/>
In Vue 3, they decided to unify the two, dropping .sync and instead allowing to pass a prop name to v-model similar to how slot names are passed to the v-slot directive, i.e. v-model:childComponentProp="myVar", and similar as v-slot alone is equivalent to v-slot:default, v-model alone is equivalent to v-model:modelValue. So it is equivalent to:
<my-component
:modelValue="myValue"
#update:modelValue="(nevValue) => myValue = newValue"
/>
But the above only applies for Vue components. When using v-model on an HTML input element, it sill behaves like in Vue 2 and binds to the "value" attribute and the "input" event. It is still equivalent to:
<input :value="myValue" #input="(nevValue) => myValue = nevValue"/>
However, that behavior is a special case of plain v-model (i.e. without a prop name). And I think this is where the confusion comes from.
Using v-model:value explicitly binds to the #update:value event, i.e. this
<input v-model:value="innerItem.something" type="text">
is equivalent to:
<input type="text"
:value="innerItem.something"
#update:value="(newValue) => innerItem.something = newValue"
/>
but that event is not sent by a plain HTML element.
So long long story short, you have to use v-model= instead of v-model:value= when binding to a native input element.
Does that make sense? Hope it helps.

How to share data between components in VueJS

I have a fairly simple VueJS app, 3 components (Login, SelectSomething, DoStuff)
Login component is just a form for user and pass input while the second component needs to display some data obtained in the login progress.
How can I share data from one component to the others? So that when I route to second component I still have the data obtained in the Login one?
You can either use props or an event bus, where you'll be able to emit an event from a component and listen on another
vm.$on('test', function (msg) {
console.log(msg)
})
vm.$emit('test', 'hi')
// -> "hi"
In Vue.js components can communicate with each other using props or events. It all depends on the relation between your components.
Let's take this small example:
<template>
<h2>Parent Component</h2>
<child-component></child-component>
</template>
To send information from the parent to Child, you will need to use props:
<template>
<h2>Parent Component</h2>
<child-component :propsName="example"></child-component>
</template>
<script>
export default {
data(){
return{
example: 'Send this variable to the child'
}
}
}
</script>
To send information from the child to the parent, you will need to use events:
Child Component
<script>
...
this.$emit('example', this.variable);
</script>
Parent Component
<template>
<h2>Parent Component</h2>
<child-component #example="methodName"></child-component>
</template>
<script>
export default {
methods: {
methodName(variable){
...
}
}
}
</script>
Check the documentation of vue.js for more information about this subject. This is a very brief introduction.
Use this small plugin if you have a lot of nested components:
Vue.use(VueGlobalVariable, {
globals: {
user: new User('user1'),
obj:{},
config:Config,
....
},
});
Now you can use $user in any component without using props or other

vue.js reference div id on v-on:click

Using v-on:click I'd like to set a variable with the id of the div in Vue.JS - how do I reference this?
<div id="foo" v-on:click="select">...</div>
<script>
new Vue({
el: '#app',
data: {
},
methods: {
select: function(){
divID = this.id // ??
alert(divID)
}
}
})
</script>
You can extend your event handler with the event object $event. That should fit your needs:
<div id="foo" v-on:click="select($event)">...</div>
The event is passed on in javascript:
export default {
methods: {
select: function(event) {
targetId = event.currentTarget.id;
console.log(targetId); // returns 'foo'
}
}
}
As mentioned in the comments, `$event` is not strictly necessary, when using it as the only parameter. It's a nice reminder that this property is passed on, when writing it explicitly.
However, nobody will stop you from writing the short notation:
<div id="foo" #click="select">...</div>
Beware that the method will not receive the `$event` object when you add another parameter. You need to explicitly add it at the position you will handle it in the listener. Any parameter order will work:
<div id="foo" #click="select(bar, $event)">...</div>
To find more options of the v-on directive, you can look through the corresponding entry in the vue documentation:
Vue API Documentation - v-on
Inspired by #nirazul's answer, to retrieve data attributes:
HTML:
<ul>
<li
v-for="style in styles"
:key="style.id"
#click="doFilter"
data-filter-section="data_1"
:data-filter-size="style.size"
>
{{style.name}}
</li>
</ul>
JS:
export default {
methods: {
doFilter(e) {
let curTarget = e.currentTarget;
let curTargetData = curTarget.dataset;
if (curTargetData) {
console.log("Section: " + curTargetData.filterSection);
console.log("Size: " + curTargetData.filterSize);
}
}
}
}
Just to highlight another option than the selected answer for the same question, I have a delete button on a record and want to perform an action with the record's unique id (a number). I could do the selected answer as before:
<button :id="record.id" #click="del">×</button>
This leaves the unfortunate reality that my del function needs to pull the id attribute out of the javascript event, which is more about the API (the DOM) than my domain (my app). Also using a number as an element id isn't ideal and could cause a conflict if I do it more than once in a view. So here's something that's just as clear and avoids any future confusion:
<button #click="()=>del(record.id)">×</button>
methods: {
del(id) {
fetch(`/api/item/${id}`, {method:"DELETE"})
}
}
You see, now my del function takes the record id instead of an event, simplifying things.
Note that if you do this wrong, you will invoke your delete function immediately, which is not what you want. Don't do this:~~
<button #click="del(record.id)">×</button>
If you end up doing that, Vue will call the del function every time this html fragment is rendered. Using the anonymous function ()=>del(record.id) will return a function that's ready to be executed when the click event happens.
Actually #nirazul proved this is fine. Not sure what my issue was.

Rendering React Components to Specific Div Using Routing

I am using FlowRouter as a router for a Meteor/React application I am trying to create. I'm having a very hard time trying to get my react components to render in specific places. Does anyone know how to do this?
So on my landing page, when I click a button, I want to route to a secondary page. I have three different components that I want to render in certain parts of the page. I've been using ReactLayout.render(), but I can't seem to make sure components get rendered in certain areas. I thought document.getElementById would work
ReactLayout.render(LandingPage, document.getElementById("landing-page")
but it hasn't been.
The second parameter of ReactLayout.render expects an object. If you want to render several components into your LandingPage element, it might look something like this:
LandingPage = React.createClass({
render() {
return (
<div className="app-root">
<AppHeader />
<div className="container">
{this.props.testOne}
</div>
<div className="app-root">
{this.props.testTwo}
</div>
</div>
);
}
});
Then render using:
FlowRouter.route( '/testRedirect', {
name: 'test',
action() {
ReactLayout.render( Default, { testOne: <TestOneComponent />, testTwo: <TestTwoComponent /> } );
}
});

Magic mode ignoring updates to custom object

Please see this pen for a demo of the issue (based on the slideshow from the tutorial). When clicking on "next" and "prev" arrows, you'll notice that the imgIndex mustache updates correctly, but the expression mustaches such as <p>{{ curImageCaption() }}</p> do not recognize when their values are changing.
That is, the object is mutated such that the mustache value would change if the expressions were re-evaluated, but ractive doesn't seem to realize that. Is there any way to get this to work, barring writing adaptors? Am I misunderstanding how magic mode works? The interesting thing is that even if I explicitly call ractive.update() inside the event handlers, ractive still doesn't respond.
UPDATE WITH NEW INFO
After more fiddling, I came up with this hack that gets it working. The hack is to change, eg, <p>{{ curImageCaption() }}</p> to <p>{{ curImageCaption(imgIndex) }}</p> -- adding a simple primitive to the mustache expression which ractive understands how to watch correctly.
I think I see what's going on now, but having to explicitly add arguments to the mustache expression containing changing primitives defeats much of the purpose of having the separate domain object -- that is, now you are coding your domain object with ractive in mind, using changing primitives a sort of basic pub/sub mechanism for notifying ractive of changes.
Having to create a real pub/sub mechanism on my custom objects, which ractive then explicitly subscribes to, would be fine. The problem is, as I noted in the OP, even when ractive is notified of a change via ractive.update(), it still doesn't know it should recompute the mustaches unless I use the fake argument hack. So it's not clear what callback ractive should be registering to make everything work.
I don't understand the inner-working of ractive well enough to do this, but I suspect what's needed is the ability to directly work with the _deps stuff, and manually trigger recomputes for expressions. If this sounds right, an example of how to accomplish it would be appreciated.
UPDATE 2 -- A decent solution
Here is a proof of concept for a not-too-hacky workaround.
The idea is to use ECMA5 properties to decorate your custom domain object, providing properties that delegate to the existing methods you want to use but which don't work inside ractive templates. The properties, otoh, work just fine.
So instead of <p>{{ curImageCaption() }}</p> we simply write <p>{{ imageCaption }}</p>, and then we decorate our custom domain object like so:
Object.defineProperty(mySlideshow, "imageCaption", {
configurable: true,
get: function() { return this.curImageCaption() },
set: function() { }
});
This decoration, a bit verbose in my demo, can easily be slimmed down by creating a helper method which accepts an object mapping your new ractive-friendly property names to names of existing methods on your object, and takes care of the above boilerplate for you.
NOTE: One drawback of this method is that you do have to call ractive.update() manually in your event handlers. I'd like to know if there's a way of getting around that. And if there is not, how big of a performance hit does this cause? Does it defeat the whole purpose of ractive's surgical updates?
Update 3 -- A better decent solution?
This pen takes yet another approach, in which link our custom domain model with ractive via a generic dispatcher object (an object that implements notify()). I think this is my favorite of the approaches so far....
It's similar to the official ractive adaptors, but we are using DI to pass our unofficial ractive adapter to our domain object, rather than wrapping our object. At first glance it might seem we are "coding to ractive," but in fact this is only partially true. Even if we were using another framework, we'd need to use some notification mechanism to broadcast changes to our view model so that views could react to it. This DI approach seems to require less boilerplate than official ractive adaptors, though I don't understand them well enough to know this for sure. It is not as completely general a solution as the official adaptors either.
Code from pen for posterity
HTML
<div id='output'></div>
<script id='template' type='text/ractive'>
<div class='slideshow'>
<div class='main'>
<a class='prev' on-tap='prev'><span>«</span></a>
<div class='main-image' style='background-image: url({{ curImageSrc() }});'></div>
<a class='next' on-tap='next'><span>»</span></a>
</div>
<div class='caption'>
<p>{{ curImageCaption() }}</p>
<p>Image index: {{ imgIndex }} </p>
</div>
</div>
</script>
JS
// Fix JS modular arithmetic to always return positive numbers
function mod(m, n) { return ((m%n)+n)%n; }
function SlideshowViewModel(imageData) {
var self = this;
self.imgIndex = 0;
self.next = function() { self.setLegalIndex(self.imgIndex+1); }
self.prev = function() { self.setLegalIndex(self.imgIndex-1); }
self.curImage = function() { return imageData[self.imgIndex]; }
self.curImageSrc = function() { return self.curImage().src; }
self.curImageCaption = function() { return self.curImage().caption; }
self.setLegalIndex = function(newIndex) { self.imgIndex = mod(newIndex, imageData.length); }
}
var mySlideshow = new SlideshowViewModel(
[
{ src: imgPath('problem.gif'), caption: 'Trying to work out a problem after the 5th hour' },
{ src: imgPath('css.gif'), caption: 'Trying to fix someone else\'s CSS' },
{ src: imgPath('ie.gif'), caption: 'Testing interface on Internet Explorer' },
{ src: imgPath('w3c.gif'), caption: 'Trying to code to W3C standards' },
{ src: imgPath('build.gif'), caption: 'Visiting the guy that wrote the build scripts' },
{ src: imgPath('test.gif'), caption: 'I don\'t need to test that. What can possibly go wrong?' }
]
);
var ractive = new Ractive({
el: '#output',
template: '#template',
data: mySlideshow,
magic: true
});
ractive.on( 'next', function(event) {
ractive.data.next();
});
ractive.on( 'prev', function(event) {
ractive.data.prev();
});
function imgPath(name) { return 'http://learn.ractivejs.org/files/gifs/' + name; }
I'll try and explain what's going on under the hood before presenting a possible solution:
Wrapping objects in magic mode
In magic mode, when Ractive encounters an unwrapped data descriptor of an object, it wraps it by replacing it with an accessor descriptor - the get()/set() functions. (More info on MDN, for those interested.) So when you do self.imgIndex = 1, you're actually triggering the set() function, which knows how to notify all the dependants of the imgIndex property.
The key word here is 'encounters'. The only way Ractive knows that it needs to wrap imgIndex is if we do ractive.get('imgIndex'). This happens internally because we have an {{imgIndex}} mustache.
So that's why the index property updates.
Dependency tracking with computed values
Within an ordinary template, you can have what basically amount to computed values, using the get() method:
<p>{{ curImageCaption() }}</p>
ractive = new Ractive({
el: 'body',
template: template,
data: {
images: images,
imgIndex: 0,
curImageCaption: function () {
var imgIndex = this.get( 'imgIndex' );
return this.get( 'images' )[ imgIndex ].caption;
}
}
});
Here, because we're calling ractive.get() inside the curImageCaption function, Ractive knows that it needs to rerun the function each time either images or imgIndex changes.
What you're in effect asking is a reasonable question: why doesn't retrieving the value of self.imgIndex in magic mode work the same as doing ractive.get('imgIndex')?
The answer comes in two parts: Firstly, I hadn't thought of adding that feature, and secondly, it turns out it doesn't work! Or rather, it's extremely fragile. I changed magic mode so that the get() accessor captured the dependency the same way ractive.get() does - but self.imgIndex is only an accessor descriptor (as opposed to a data descriptor) if Ractive has already encountered it. So it worked when we had <p>Image index: {{ imgIndex }} </p> at the top of the template, but not when it's at the bottom!
Normally the prescription would be fairly simple: use ractive.get() to make the dependency on self.imgIndex explicit inside curImageSrc() and curImageCaption(). But because you're using a custom viewmodel object, that's not ideal because it effectively means hard-coding keypaths.
A solution - creating a custom adaptor
Here's what I'd recommend - making an adaptor that works with the custom viewmodel object:
Ractive.adaptors.slides = {
filter: function ( object ) {
return object instanceof SlideshowViewModel;
},
wrap: function ( ractive, slides, keypath, prefix ) {
var originalNext, originalPrev;
// intercept next() and prev()
originalNext = slides.next;
slides.next = function () {
originalNext.call( slides );
ractive.update( keypath );
};
originalPrev = slides.prev;
slides.prev = function () {
originalPrev.call( slides );
ractive.update( keypath );
};
return {
get: function () {
return {
current: slides.curImage(),
index: slides.imgIndex
};
},
teardown: function () {
slides.next = originalNext;
slides.prev = originalPrev;
}
};
}
};
var ractive = new Ractive({
el: '#output',
template: '#template',
data: mySlideshow,
adaptors: [ 'slides' ]
});
This is a very simple adaptor, and it could probably be improved, but you get the gist - we're intercepting calls to next() and prev(), and letting Ractive know (via ractive.update()) that it needs to do some dirty checking. Note that we're presenting a facade (via the get() method of the wrapper), so the template looks slightly different - see this pen.
Hope this helps.
Maybe this is an academic exercise, and I'm new to Ractive, but it seems the problem lies in the template not having a context to the current image.
EDITED: Use current Image as a context block instead of looping through collection.
<div class='slideshow'>
{{#curImage}}
<div class='main'>
<a class='prev' on-tap='prev'><span>«</span></a>
<div class='main-image' style='background-image: url({{ src }});'></div>
<a class='next' on-tap='next'><span>»</span></a>
</div>
<div class='caption'>
<p>{{ caption }}</p>
<p>Image index: {{ imgIndex }} </p>
</div>
</div>
...
function SlideshowViewModel(imageData) {
...
self.curImage = imageData[self.imgIndex]
...
self.setLegalIndex = function(newIndex) {
self.imgIndex = mod(newIndex,imageData.length);
self.curImage = imageData[self.imgIndex]
}
}
This is using your original pen with just the key modifications. Here is new pen.
I would still move the buttons into an outer part of the template so the display in the middle could be made into a partial:
<div class='main'>
<a class='prev' on-tap='prev'><span>«</span></a>
{{#current}}
{{>partial}}
{{/}}
{{/current}}
<a class='next' on-tap='next'><span>»</span></a>
</div>
and encapsulate in Ractive.extend, but if ViewModel works for you...

Resources