So I'm using a third party vue component called 'vue-tree-list' here's the link -> https://github.com/ParadeTo/vue-tree-list
in the component it has a computed property that basically analyzes the tree structure to find the right place for a new leaf/node to be inserted.
In my parent component I did this:
<template>
<div class="py-8 px-5" style="min-height: calc(100vh - (112px + 2.75rem))">
<div class="flex flex-col w-full">
<button class="cursor-pointer relative flex flex-row items-center h-10 focus:outline-none "
>
<span class="text-sm tracking-wide truncate ml-6">Add Node</span>
</button>
</div>
<VueTreeList
#click="onClick"
#change-name="onChangeName"
#delete-node="onDel"
#add-node="onClick"
ref="tree"
:model="data"
default-tree-node-name="New Depot"
default-leaf-node-name="New Driver"
v-bind:default-expanded="false"
>
<template v-slot:leafNameDisplay="slotProps">
<a class="text-orange-primary mr-4">
<span>{{ slotProps.model.name }}</span>
</a>
</template>
<span class="icon" slot="addTreeNodeIcon">📂</span>
<span class="icon" #click.stop="test()" slot="addLeafNodeIcon">+</span>
^INSTEAD OF CALLING THE DEFAULT EVENT 'add-child' WHICH IMMEDIATELY INSERTS A NODE I DIVERTED IT INSTEAD SINCE I WANT THE USER TO INPUT THEIR DATA BEFORE INSERTING INSIDE THE TREE
<span class="icon" slot="editNodeIcon">📃</span>
<span class="icon" slot="delNodeIcon">✂️</span>
<span class="icon" slot="leafNodeIcon">🍃</span>
<span class="icon" slot="treeNodeIcon">📂</span>
</VueTreeList>
<Modal ref="modal" :title="modalTitle" :size="modalSize" :height="modalHeight">
<div v-if="modalContent == 'new'">
<DriverLookUp />
<VehicleLookUp />
</div>
</Modal>
</div>
</template>
<script>
import { VueTreeList, Tree, TreeNode } from 'vue-tree-list'
import { DriverLookUp, VehicleLookUp } from '#/components/forms/depot'
export default { ONLY ADDED THE RELEVANT FUNCTIONS SINCE IT WOULD BE VERY LONG
components: {
VueTreeList, Modal, DriverLookUp, VehicleLookUp
},
test(){
this.$refs.tree.rootNode() <--- the computed method that I want to access
},
}...
The problem with this is that the computed property for some reason throws an error on missing properties which doesn't make sense since it has already been rendered. Is there a way to trigger a child components computed property?
https://github.com/ParadeTo/vue-tree-list/blob/master/src/VueTreeList.vue <--- here's the link of the child component that I'm working with
That component's computed prop walks up its parent tree until it finds a component with a prop named model, containing name of "root". It assumes all parents have this prop, and fails otherwise, leading to the error you observed.
A workaround is to declare that property in your component before reading the computed prop:
export default {
props: {
👇
model: {
type: Object,
default: () => ({ name: 'root' }),
},
},
methods: {
test() {
const rootNode = this.$refs.tree.rootNode
console.log({ rootNode })
},
}
}
demo
Related
There's an issue when using v-model to div tags. Apparently, div tags doesn't allow v-model and I've decided to create my comment section as a component. I needed to assign this div text area as is because of UI/UX reasons. textarea, input, etc tags, as to my knowledge, these tags are not compatible with contenteditable="true"; I need to expand the height of the input field as a user types in their comments. Below is the vue component that I imported in my parent view.
<!-- CommentSection.vue -->
<template>
<div id="chatId" contenteditable="true" placeholder="Leave a message" class="overflow-hidden block mx-4 text-left p-2.5 w-full text-sm text-gray-900 bg-white rounded-2xl border border-gray-300 focus:ring-blue-500 focus:border-blue-500"/>
</template>
<style>
#chatId[contenteditable="true"]:empty:not(:focus):before {
content: attr(placeholder)
}
</style>
On my view file, I imported it and used v-model into it, just like this.
<!--MainPage.vue-->
<template>
...
...
<CommentSection v-model="comment"/>
<button #click="submitPost()"> Submit </button>
...
...
...
</template>
<script>
import CommentSection from '#/components/CommentSection.vue'
export default{
name: 'MainPage',
data(){
return{
comment: '',
}
},
components: { CommentSection },
methods:{
submitPost(){
console.log(this.comment);
},
},
}
</script>
However, when I check my console, it gives me the value "null" or just nothing. Is there's a way to fix this? Or is it the way I implemented it that causes the problem.
EDIT: Here's the running code in codesandbox.
I solved your problem and the code is as follows. I hope I have helped
By adding # to the div tag, we can see the changes in the content of the tag in the change method. And in that method, use emit$ to share its value with other components
<!-- CommentSection.vue -->
<template>
<div id="chatId" #input="chanage" contenteditable="true" placeholder="Leave a message" class="overflow-hidden block mx-4 text-left p-2.5 w-full text-sm text-gray-900 bg-white rounded-2xl border border-gray-300 focus:ring-blue-500 focus:border-blue-500"/>
</template>
<script>
export default {
methods: {
chanage (e) {
this.$emit('value-div', e.target.textContent)
}
}
}
</script>
<style>
#chatId[contenteditable="true"]:empty:not(:focus):before {
content: attr(placeholder)
}
</style>
And here we have the props created by $emit, whose value we initialize in the comment variable. Actually, it has a function similar to v-model.
<!--MainPage.vue-->
<template>
...
...
<CommentSection #value-div="(value)=>comment = value"/>
<button #click="submitPost()"> Submit </button>
...
...
...
</template>
<script>
import CommentSection from '#/components/CommentSection.vue'
export default{
name: 'MainPage',
data(){
return{
comment: '',
}
},
components: { CommentSection },
methods:{
submitPost(){
console.log(this.comment);
},
},
}
</script>
I have created a component which basically generates a card which includes card title,card description and a button. Now when I map through an array on a different component to generate those cards I want the button to be different on different components. Like on home page the button should say Update, on other page the button should say Delete. How can I acheive that? Here is the component which generates card.
import React from 'react';
import { Card } from 'react-bootstrap';
const InventoryItem = ({ product }) => {
const { productName, productImage, productPrice, productQuantity, productSupplier, productDetails } = product;
return (
<div className='col-12 col-md-6 col-lg-6'>
<Card className='h-100 items-card d-block d-md-block d-lg-flex flex-row align-items-center border border-0'>
<div className='text-center card-image-container'>
<Card.Img variant="top" src={productImage} className='card-image img-fluid' />
</div>
<Card.Body className='card-details'>
<Card.Title>{productName}</Card.Title>
<Card.Text>{productDetails}</Card.Text>
<div className='d-lg-flex align-items-center justify-content-between mb-3'>
<p className='mb-0'>Price: ${productPrice}</p>
<p className='me-2 mb-0'>Stock: {productQuantity}</p>
</div>
<div className='d-flex align-items-center justify-content-between'>
<p className='mb-0'>Supplier:{productSupplier}</p>
<button className='btn btn-dark'>Update</button>
</div>
</Card.Body>
</Card>
</div>
);
};
export default InventoryItem;
Pass the button text as a prop to the component, so consuming code can speficy the text for the button.
Add it as a prop:
const InventoryItem = ({ product, buttonText }) => {
And use it in the button:
<button className='btn btn-dark'>{buttonText}</button>
Then when using the component, pass the prop:
<InventoryItem product={someProductObject} buttonText="Update" />
or with a conditional value:
<InventoryItem product={someProductObject} buttonText={someCondition ? "Update" : "Delete"} />
You can define a condition for the button basaed on the page you are:
window.location.href return the string href.
const MyButton= window.location.href=='Home'
? <button> HOME</button>
: <button> UPDATE</button>
Hope that answer your question,
Mauro
In my vue application, i am using slots for some block of contents. Now, i have to migrate my application into react. While exploring react, i got to know props.children will work similar as slot works.
But, i am not sure what will be the proper way to use this pattern in react.
Here is the sample of code in vue
<template>
<div class="badge-box">
<span :class="badgeClass" :style="badgeStyle">
<span v-if="shape !=='dot'" class="line-break">
<slot>
{{text}}
</slot>
</span>
</span>
<span v-if="shape ==='dot'" class="line-break" style="margin-left: 8px;">
<slot name="dotShape">
{{text}}
</slot>
</span>
</div>
</template>
<script>
export default {
name:'sample'
props: {
text: { type: string }
}
}
</script>
How to change this vue slot pattern into React using props.children?
There are several patterns in React that correlate closely with Vue slots.
props.children can be used, but only for default slot with no slotProps. For named slot additional props can be used. Default slot content <slot>{{text}}</slot> can be conditionally rendered when no children are provided:
let MyComp = props => (
...
<div class="default-slot">{{props.children ?? props.text}}</div>
...
<div class="named-slot">{{props.named ?? props.text}}</div>
...
)
and
<MyComp named={<p>Named content</p>}>
<p>Default content</p>
</MyComp>
Function as child and function as prop patterns serve the same purpose but allow to replace slots with slotProps. A child can pass parameters to parent scope through a callback:
let MyComp = props => (
...
<div class="default-slot">{{props.children?.('foo') ?? props.text}}</div>
...
<div class="named-slot">{{props.named?.('bar') ?? props.text}}</div>
...
)
and
<MyComp named={param => <p>Named content {{param}}</p>}>{
param => <p>Default content {{param}}</p>
}</MyComp>
Assuming you are using JSX/TSX in functional React
const Component ({text}) => {
return (
<div class="badge-box">
<span :class="badgeClass" :style="badgeStyle">
<span v-if="shape !=='dot'" class="line-break">
{{text}}
</span>
</span>
<span v-if="shape ==='dot'" class="line-break" style="margin-left: 8px;">
{{text}}
</span>
</div>
)
}
will do what you want. If you are using class component, put it in the render method.
I have course details component. In this component I have 2 child component, for player and for lessons. Now when I click on one of the lesson it need to load in player the suitable video. Can any one help me with some ideas how can I do this ? How we can in Angular on click from one component send commands to other component ?
this is parent component
<div class='course-details'>
<div>
<app-course-player [course]='course'></app-course-player>
</div>
<div>
<app-lesson-card class='w-full' [course]='course'></app-lesson-card>
</div>
</div>
player component
<div *ngFor='let courseLesson of course.lessons'>
<vg-player class='w-full video-size'>
<vg-overlay-play vgFor='dreamclass-video'></vg-overlay-play>
<vg-controls>
<vg-play-pause></vg-play-pause>
<vg-playback-button></vg-playback-button>
<vg-scrub-bar>
<vg-scrub-bar-current-time></vg-scrub-bar-current-time>
<vg-scrub-bar-buffering-time></vg-scrub-bar-buffering-time>
</vg-scrub-bar>
<vg-time-display vgProperty='left' vgFormat='mm:ss'></vg-time-display>
<vg-mute></vg-mute>
<vg-fullscreen></vg-fullscreen>
</vg-controls>
<video #myMedia
[vgMedia]='myMedia'
[poster]='"img-proxy/plain/" + courseLesson.coverUrl'
id='dreamclass-video'
[src]='courseLesson.videoUrl'>
</video>
</vg-player>
</div>
get data from here
#Input() course: ICourse;
lesson component
<div class='course-section w-full lg:w-auto border border-white-gray mt-4 lg:mt-0'>
<div class='flex items-center justify-between'>
<p class='text-primary text-2xl price-box font-medium'>Free</p>
</div>
<div class='theme-section border-t border-b border-white-gray'>
<div class='flex items-center justify-between top-margin' *ngFor='let lessonData of course.lessons'>
<div class='flex items-center'>
<img src='assets/images/sliders/lock.svg' class='mr-right' alt=''>
<p class='text-sm font-normal text-darkgray course-box-title'>{{lessonData.title}}</p>
</div>
<div><p
class='text-sm font-normal text-regulargray'>{{lessonData.duration * 1000 | date: 'mm:ss'}}</p>
</div>
</div>
</div>
<div>
<button *ngIf='!course?.enrolled' mat-raised-button class='enrol-butn' color='primary'
(click)='enrollCourse()'> Enroll
</button>
<button *ngIf='course?.enrolled' mat-raised-button class='enrol-butn text-white' color='accent'
(click)='enrollCourse()'> Enrolled
</button>
</div>
</div>
Another way is using a service.
Create a service:
ng g s MyServiceName --skip-Tests
Once it is created, check that has been decorated as providedIn root:
#Injectable({
providedIn: 'root',
})
export class MyServiceNameService {
....
In your service, declare all you want the components have in commun.
e.g:
private _courseSelected: ICourse
Inject the service in the constructor of your components (in course-player and in lesson-card):
...
import { MyServiceNameService } from '../../services/my-service-name.service';
...
constructor(
private myServiceNameService : MyServiceNameService ) {
}
...
Used directly the variables in the service from your components, or better, make methods in the service to modify the varaibles values.
e.g:
in service:
...
export class MyServiceNameService {
public get courseSelected() {
return this._courseSelected;
}
constructor(){}
public someFunctionThaCausesTheVideoSelectionChanges(newVideoUrl: string){
this._courseSelected = newVideoUrl;
}
...
in component:
const myCourseSelectede = this.myServiceNameService.courseSelected;
....
setNewCourse ( newCourse: string) {
this.myServiceNameService.someFunctionThaCausesTheVideoSelectionChanges(newCourse);
}
You can solve this using an Output event in the lesson-card component and using template ids in the parent component.
Something like:
<div class='course-details'>
<div>
<app-course-player #player [course]='course'></app-course-player>
</div>
<div>
<app-lesson-card class='w-full' (videoSelect)="player.play($event)" [course]='course'></app-lesson-card>
</div>
</div>
In the lesson-card component declare the Output Event:
export class LessonCardComponent {
#Output()videoSelect = new EventEmitter<string>('');
...
someFunctionThaCausesTheVideoSelectionChanges(){
videoSelect.emit('new video url');
}
}
In the course-player component, declare the play function
export class CoursePlayerComponent{
...
play(videoUrl: string){
...
}
}
I am trying to:
Make a tab be active and turn off all the other ones.
Currently I only have it working where it will turn off and on one at a time.
I a trying to figure out how I can do this.
I was thinking is there a way to pass in the parent component to the function and then be able to access the className property of all its children??
Currently I have:
import React from 'react';
import ReactDOM from 'react-dom';
export default class TestContainer extends React.Component{
setActive(event){
event.preventDefault();
//getting id from component
let isActive = event.target.id;
if(event.currentTarget.className === "list-group-item text-center active"){
event.currentTarget.className = "list-group-item text-center";
} else if(event.currentTarget.className === "list-group-item text-center") {
event.currentTarget.className = "list-group-item text-center active";
}
}
render(){
return (
<div className="col-lg-6 col-md-6 col-sm-6 col-xs-6 bhoechie-tab-container scroll-y">
<div className="col-lg-2 col-md-2 col-sm-2 col-xs-2 bhoechie-tab-menu">
<div className="list-group">
<a href="#" onClick={this.setActive} id="eyes" className="list-group-item text-center active">
<h4 className="glyphicon glyphicon-eye-close"></h4><br/>1
</a>
<a href="#" onClick={this.setActive} id="hair" className="list-group-item text-center">
<h4 className="glyphicon glyphicon-tint"></h4><br/>2
</a>
<a href="#" onClick={this.setActive} id="mouth" className="list-group-item text-center">
<h4 className="glyphicon glyphicon-minus"></h4><br/>3
</a>
<a href="#" onClick={this.setActive} id="clothing" className="list-group-item text-center">
<h4 className="glyphicon glyphicon-user"></h4><br/>4
</a>
<a href="#" onClick={this.setActive} id="props" className="list-group-item text-center">
<h4 className="glyphicon glyphicon-gift"></h4><br/>5
</a>
</div>
</div>
)}
By trying to change the state of your elements outside of the render() method, you're fighting against the strengths of React's rendering engine. Use state, either internal to the component or provided via props/context (preferably props), to determine which tab should be rendered with active in its className.
When you respond to a click, change the state and let React re-render the component based on the new state.
For the limited example provided here, I'd recommend storing the ID of the active element internally in the component's state.
export default class TestContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
activeTab: this.props.activeTab || 'eyes'
};
}
setActive(event) {
this.setState({ activeTab: event.target.id });
}
render() {
const { activeTab } = this.state;
const tabClassName = "list-group-item text-center";
const eyesTabClassName = tabClassName + (activeTab === 'eyes' ? ' active' : '');
...
<a href="#" onClick={this.setActive} id="eyes" className={eyesTabClassName}>
...
}
}
This is a rough, unoptimized solution, but it should get the idea across. When you call this.setState, React responds by marking the component as needing to be re-rendered. The render() method will automatically be called and thus all of your JSX will be re-evaluated and re-rendered. React will then look at the difference between what it just produced and what is already in the virtual DOM and merge in your changes.
This means that whichever tab was previously rendered with "active" in its className will be re-rendered. If it's no longer active, your render code would not have concatenated the active class into that tab's className. Further, whichever class is active will now have its className appended with "active".
TL;DR
Don't manipulate the DOM in your event handlers. Manipulate state and let React work its magic by re-rendering with your new state.