Skeleton loader component - javascript

I'm new to VueJS, and I'm coming to you to find out if what I've done is feasible or not.
Instead of having old data, while loading components, I prefer to display a preloader.
I liked the idea of a skeletons loader, rather than a simple spinner.
Right now I have a state in the store, which is null by default, I have a mutation to set the loading, and a getter.
To avoid visual bugs, from the router, with a beforeEach, I initialize the loading state to true, so that by default the components start loading !
Then, in my view, I import the Loader component, with its svg and style.
And I place it over the component that needs to be displayed, with a simple condition v-if="!getLoading" and v-if="getLoading".
The problem is that I feel like I'm tinkering with the blind, the beforeach and displaying this component with a condition?
I would be reassured if someone can give me some advice, or approve this method of doing!
Here is the code of a simple Loader component
<template>
<content-loader
:height="78"
:width="390"
:speed="4"
primaryColor="#f2f6fe"
secondaryColor="#e0eafa"
>
<rect x="9" y="20" rx="4" ry="4" width="142" height="13" />
<rect x="316.38" y="5.38" rx="5" ry="5" width="68" height="68" />
<rect x="9" y="46" rx="4" ry="4" width="75.26" height="13.26" />
</content-loader>
</template>
<script>
import { ContentLoader } from "vue-content-loader"
export default {
components: {
ContentLoader
}
}
</script>
The store code
const state = {
loading: null,
}
const mutations = {
SET_LOADING: (state, payload) => {
state.loading = payload;
},
}
const getters = {
getLoading(state) {
return state.loading;
}
}
Example of utilisation in my view : with condition
<div class="col-12 col-lg-4 col-xl-3 col-md-6" v-if="getLoading"> // the condition
<div class="card animate-up-2">
<div class="card-body">
// the component
<StatsLoader></StatsLoader>
</div>
</div>
</div>
<div class="col-12 col-lg-4 col-xl-3 col-md-6" v-if="!getLoading"> // the condition
<div class="card animate-up-2">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<h3 class="font-weight-bold text-uppercase">5 %</h3>
<div class="d-flex d-sm-block d-lg-flex align-items-end">
<p class="mb-0 mr-2 text-muted">API usage</p>
</div>
</div>
<div>
<div class="avatar avatar-lg">
<div class="avatar-title sred sbg-soft-red rounded">
<i class="fad fa-project-diagram"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

If that is a global loader being used on all the routes, you can wrap it in a component and use that component everywhere. Use named slots to manage your template.
An example of a Loader component:
Loader.vue
<template>
<div>
<slot name='loading' v-if='getLoading'>
<StatsLoader></StatsLoader>
</slot>
<slot name='content v-if='!getLoading'></slot>
</div>
</template>
<script>
import StatsLoader from '#/components/StatsLoader.vue';
export default {
name: 'Loader',
props: {
isLoading: {
type: Boolean,
default: null
}
},
computed: {
getLoading() {
return this.isLoading === null ? this.$store.state.loading : this.isLoading;
}
},
components: {
StatsLoader
}
}
</script>
Also, it would useful to register this component globally so you don't need to include it all the routes. You can do that using Vue.component in your main entry file.
import Loader from '#/components/Loader.vue';
Vue.component('Loader', Loader);
And you can rewrite your current component template like this:
<Loader>
<template v-slot:loader>
<!-- Use a different loader here for this page maybe -->
</template>
<template v-slot:content>
<div class="card animate-up-2">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<h3 class="font-weight-bold text-uppercase">5 %</h3>
<div class="d-flex d-sm-block d-lg-flex align-items-end">
<p class="mb-0 mr-2 text-muted">API usage</p>
</div>
</div>
<div>
<div class="avatar avatar-lg">
<div class="avatar-title sred sbg-soft-red rounded">
<i class="fad fa-project-diagram"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
</Loader>
Also you can use <Loader :is-loading='isPageLoading' /> in case you don't want to rely on the global loader.

Related

Vue.js iterate props array and manage additional state for that array

I have the prop that is array, iterate it in template and try to manage hover state with displaying additional element when hover is happened:
<template>
<div class="d-flex justify-center" style="height: 100%">
<div v-for="(item, i) in data" :key="i" class="flex-grow-1 d-flex flex-column" style="height: 100%">
<div class="px-2" style="height: 100%;">
<div class="d-flex flex-column-reverse justify-start align-end" style="height: 100%;">
<div class="bar"
:class="{'bar-active' : item.isActive}"
#mouseover="item.isHover = true"
#mouseleave="item.isHover = false"
:style="`height: ${getBarHeight(item.y)}%`"/>
<div class="container">
<div v-if="item.isHover || item.isActive" class="label fw-600 fs-12 tc-text-primary">{{getIncomeView(item)}}</div>
</div>
</div>
</div>
<div class="container">
<div class="label content-accent" style="white-space: nowrap;">{{item.x}}</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import BarChartModel from "./bar-chart-model";
#Component({
components: {
}
})
export default class OverviewChart extends Vue {
#Prop({default: () => []})
data!: BarChartModel[];
hovers: boolean[] = [];
...
}
</script>
The problem is coming when I try to pass the data as prop. Data ceases to be updated with item.isHover = true and hover feature doesn't work. I tried to keep the hover state in separate data array (out of the prop in hovers: boolean[] = []), but it also doesn't work (state array is changed, but if hasn't react on the v-if="hovers[i]". What I can do to track the hover state and display the additional element during hover and pass the data as prop?

Image not loading in REACT dangerouslySetInnerHTML

I'm new to React and when I fetch data from a database I don't get the image loaded properly.
import React, { Component } from 'react';
import { stripSlashes } from 'slashes';
import ReactTimeAgo from 'react-time-ago';
import { Link } from 'react-router-dom';
class Conversation extends Component {
render() {
return (
<li className="post__item pt-3">
<svg className="me-2 rounded" width="32" height="32" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: 32x32" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#007bff"></rect><text x="50%" y="50%" fill="#007bff" dy=".3em"></text></svg>
<div className="pb-3 mb-0 small lh-sm border-bottom w-100">
<Link to={`/community/${this.props.items.post_id}`}>
<div className="d-flex justify-content-between">
<strong className="text-gray-dark">{this.props.items.post_title}</strong>
</div>
</Link>
<Link to={`/member/${this.props.items.post_author.user_id}`}>
<span className="d-block">#{this.props.items.post_author.first_name}</span>
</Link>
<p className="mb-0 lh-sm text-muted">
<i className="fas fa-heart"></i>
<ReactTimeAgo date={new Date(this.props.items.post_date)} locale="nl-NL"/>
<div className="content" dangerouslySetInnerHTML={{ __html: stripSlashes(this.props.items.post_content) }}></div>
</p>
</div>
</li>
);
}
}
export default Conversation;
Output is:
<img src="https://domain.nl/wp-content/themes/weightz/inc/admin/img/emoticons/angry.svg">
I'm using dangerouslySetInnerHTML but only get the code but not the image itself. Does anyone know what I'm doing wrong?
I store the data in a different way in the database is the solution. Thanks #ChrisG for your look at this code

Vue Js open sketchpad in vue-js-modal

I am new with Vue and trying to open sketchpad inside vue-js-modal.
I am not sure what am I doing wrong here.
I am using VueModal and vue-signature-pad.
It works fine on the page but I could not make it run in the modal.
Here is the link to codesandbox
App.vue code is as follow
<template>
<div id="app">
<v-dialog />
<div class="container">
<div class="row">
<div class="col-12 mt-2">
<div class="col-6 mt-2">
<button class="btn btn-secondary" #click="showButtonsDialog">
Open Dialog
</button>
</div>
</div>
<div class="col-12 mt-2">
<VueSignaturePad
id="signature"
width="100%"
height="100%"
ref="signaturePad"
/>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "App",
methods: {
showButtonsDialog() {
this.$modal.show("dialog", {
title: "Some Title",
text:'<VueSignaturePad id="signature" width="100%" height="200px" ref="signaturePad"/>',
buttons: [
{
title: "CANCEL",
handler: () => {
this.$modal.hide("dialog");
}
}]
});
}
}
};
</script>
What am I doing wrong here?
Thank you
I think text only work with plain text, not Vue component. To include Vue component in your modal, you can declare it in template
<modal name="example"
width="80%"
height="80%">
<VueSignaturePad
id="signature"
width="100%"
height="100%"
ref="signaturePad"
/>
</modal>
and when you want to show it
showButtonsDialog() {
this.$modal.show("example")
}
Demo on Codesandbox

React & Typescript: Cannot read property '0' of undefined

I recently moved from .jsx to .tsx, and I'm having issues (before moving to typescript, was all working well). The first error is at line: "<Modal show={this.state.isOpen[key]} onClose={this.handleToggleModal.bind(__this,key)}>". I suspect there's something related to typescript, but I don't know what. Anybody can help me?
// TaskCard.tsx
//Dependencies
import * as React from 'react';
//Copmponents
import Modal, {ModalProps} from './Modal';
import Input from './Input';
import {IBoard} from './Board';
import PostComment from './PostComment';
export interface IComments{
body: string,
from: string,
date: string,
hour: string
}
export interface ITask{
board: string,
duedate: string,
tag: string,
tagClass: string,
tagText: string,
body: string,
risk: string,
responsable: string
comments: IComments[]
}
export interface TaskProps{
tasks: ITask[]
boards: IBoard[]
}
export interface TaskState{
isOpen: {}
}
class TaskCard extends React.Component<TaskProps, TaskState>{
constructor(props) {
super(props);
this.state = {
isOpen: props.isOpen
};
}
handleToggleModal(key) {
this.state.isOpen[key] = !this.state.isOpen[key];
this.setState(this.state.isOpen);
}
render(){
const { tasks, boards } = this.props;
let __this = this;
return(
tasks && tasks.map(
(tasks, key) =>
<div key={key} className="v-margin no-margin-top card-contour medium" onClick={this.handleToggleModal.bind(__this,key)}>
<div className="row middle-xs caption">
<div className="col-xs-6 no-padding">
<div className="row middle-xs">
<img className="img_icn no-padding-left" src="./assets/img/icn_calendar.svg" alt=""/>
<p className="txt-tertiary-color">{tasks.duedate}</p>
</div>
</div>
<div className="col-xs-6 no-padding">
<div className="row end-xs middle-xs">
<div className={tasks.tagClass + " tag tag_box center-align"}>
<p>{tasks.tag}</p>
<span className="tag_hint">{tasks.tagText}</span>
</div>
</div>
</div>
</div>
<div className="row middle-row v-padding">
<p>{tasks.body}</p>
</div>
<div className="row middle-xs caption">
<div className="col-xs-9 no-padding">
<div className="row middle-xs">
<img className="img_icn no-padding-left" src="./assets/img/icn_target.svg" alt=""/>
<p className="txt-tertiary-color">Riesgos: {tasks.risk}%</p>
</div>
</div>
<div className="col-xs-3 no-padding">
<div className="row middle-xs end-xs">
<img className="img_icn no-padding-left" src="./assets/img/icn_comments.svg" alt=""/>
<p className="txt-tertiary-color">{tasks.comments.length}</p>
</div>
</div>
</div>
<Modal show={this.state.isOpen[key]} onClose={this.handleToggleModal.bind(__this,key)}>
<div className="modal_container">
<section className="modal_section">
<div className="row middle-xs no-margin-left no-margin-right modal_section_title">
<object width="20px" height="20px" data="./assets/img/icn_objetive-blue.svg"></object>
<div className="col-xs start-xs">
<h4 className="semi-bold">Objetivo</h4>
</div>
</div>
<div className="col-xs-12 modal_section_content">
<p>{tasks.body}</p>
</div>
</section>
<section className="modal_section">
<div className="row middle-xs no-margin-left no-margin-right modal_section_title">
<object width="20px" height="20px" data="./assets/img/icn_target-blue.svg"></object>
<div className="col-xs start-xs">
<h4 className="semi-bold">Target</h4>
</div>
</div>
<div className="col-xs-12 modal_section_content">
<p>Riesgos / Problemas = {tasks.risk}%</p>
<div className="table v-margin">
<div className="row middle-xs">
<div className="col-xs">
<p className="table_head">Fecha de vencimiento</p>
<p className="table_body">{tasks.duedate}</p>
</div>
<div className="col-xs">
<p className="table_head">Tipo de objetivo</p>
<p className="table_body">{tasks.tagText}</p>
</div>
<div className="col-xs">
<p className="table_head">Responsable</p>
<p className="table_body">{tasks.responsable}</p>
</div>
<div className="col-xs-12 v-margin no-margin-bottom">
<p className="table_head">Estado de objetivo</p>
<div className="row middle-xs">
{
boards && boards.map(
(boards, board) =>
<div key={board} className="col-xs-3">
<Input name="state" type="radio" classNameCustom="input_radio"
checked={boards.id == tasks.board} value={boards.id} id={boards.id}/>
<label htmlFor={boards.id}>{boards.name}</label>
</div>
)
}
</div>
</div>
</div>
</div>
</div>
</section>
<section className="modal_section">
<div className="row middle-xs no-margin-left no-margin-right modal_section_title">
<object width="20px" height="20px" data="./assets/img/icn_activity-blue.svg"></object>
<div className="col-xs start-xs">
<h4 className="semi-bold">Actividad</h4>
</div>
</div>
<div className="col-xs-12 modal_section_content">
{
tasks.comments.length!=0 ?(
tasks.comments.map((comments, i) => {
return(
<div key={i} className="comment">
<div className="comment_box">
<p>{comments.body}</p>
</div>
<div className="comment_data row no-margin middle-xs">
<div className="col-xs start-xs">
<p>{comments.from}</p>
</div>
<div className="col-xs end-xs">
<p>{comments.date} a las {comments.hour} hs.</p>
</div>
</div>
</div>
)
})
):null
}
</div>
</section>
</div>
<PostComment />
</Modal>
</div>
)
)
}
}
export default TaskCard;
And here is the Modal component
//Modal.tsx file
//Dependencies
import * as React from 'react';
export interface ModalProps{
onClose: any,
show: boolean,
children: any
}
export default class Modal extends React.Component<ModalProps>{
handleStopPropagation = (e) =>{
e.stopPropagation();
}
render(){
const {onClose, show, children} = this.props;
// Render nothing if the "show" prop is false
if(!this.props.show) {
return null;
}
return (
<div className="modal_bkg" onClick={this.handleStopPropagation}>
<div className="modal_bkg_container row middle-xs center-xs">
<div className="modal card">
<div className="right-align modal_close">
<button className="btn btn_close" onClick={this.props.onClose}>x</button>
</div>
{this.props.children}
</div>
</div>
</div>
);
}
}
I'm unable to run the code at the moment, but as far as I can see the TaskCard.render function is using Array.map function, which is executing a callback function for every task item.
And there is a problem, because in the callback this is not the this you think it is :). According to documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map you can use map overload that accepts this.
I believe that if you do
return(tasks && tasks.map(
(tasks, key) => <div>put your component markup here</div>,
this)
);
inside TaskCard.render function you should see the error no more.
A friend helped me with this and now it's working. The first problem was the state "isOpen" at start always is undefined so, that's why runtime was throwing error "Cannot read property '0' of undefined". So now, "isOpen" is and empty object and the final constructor looks like this:
constructor(props) {
super(props);
this.state = {
isOpen: {}
};
}
After this, another error was "boards.map is not a function" and that was because I was passing the array "boards" as object. Changing for "board" was enought. So the final code was:
boards && boards.map(
(board, key) =>
<div key={key} className="col-xs-3">
<Input name="state" type="radio" classNameCustom="input_radio"
checked={board.id == task.board} value={board.id} id={board.id}/>
<label htmlFor={board.id}>{board.name}</label>
</div>
)
Anyway, thanks for trying to help me. Regards!

Expected onClick listener to be a function, instead got type string Unknown event handler property onclick. Did you mean `onClick`?

import React from 'react';
import './styles/index.css';
import ReactDOM from 'react-dom';
import AboutUs from './about.js';
import ContactForm from './contact.js';
import MainBody from './mainbody.js';
import Footer from './footer.js';
class Header extends React.Component
{
constructor(props)
{
super(props);
this.opengdMenu=this.opengdMenu.bind(this);
this.openDesktopAboutusForm=this.openDesktopAboutusForm.bind(this);
this.openDesktopContactForm=this.openDesktopContactForm.bind(this);
this.loadHomePage=this.loadHomePage.bind(this);
this.state={
isMenuOpen:false //state variable to change the state of menu from open to close and vice-versa.
};
}
opengdMenu()
{
this.setState({isMenuOpen:!this.state.isMenuOpen});
if(this.state.isMenuOpen===true)
{
var about_us = document.getElementById('about');
about_us.setAttribute("style","display:none");
}
}
openDesktopAboutusForm()
{
ReactDOM.render(<AboutUs />,document.getElementById('about'));
}
openDesktopContactForm()
{
ReactDOM.render(<ContactForm />,document.getElementById('about'));
}
loadHomePage()
{
this.render()
ReactDOM.render(<MainBody />, document.getElementById('mainbody'));
ReactDOM.render(<Footer />,document.getElementById('footer'));
}
render()
{
if(!this.state.isMenuOpen) //this part will render the header with the menu closed.
{
return(
<div>
<div className="row">
<div className="gd_header_section body_fixed_content hidden-xs hidden-sm">
<div className="gd_header_default_container" id="gd_header_default_container">
<img className="gd_header_logo_icon" onClick={this.loadHomePage} src={require('./images/GD_Logo_white.png')} alt="gd logo_icon_ desktop" />
<img className="gd_header_menu_icon" onClick={this.opengdMenu} src={require('./images/menu_icon.png')} alt="gd menu icon" />
<div className="gd_header_login">LOGIN</div>
</div>
</div>
</div>
<div className="row">
<div className="gd_small_header_section body_fixed_content visible-xs visible-sm">
<div className="gd_small_header_default_container" id="gd_small_header_default_container">
<img className="gd_small_header_logo_icon1" src={require('./images/GD_Logo_white.png')} alt="GD logo white" onClick={this.loadHomePage} />
<img className="gd_small_header_menu_icon" src={require('./images/menu_icon.png')} alt="small menu icon" onClick={this.opengdMenu} />
</div>
</div>
</div>
</div>
);
}
else //this part will render the header with the menu opened.
{
return(
<div className="row">
<div className=" col-md-12 col-xs-12 col-sm-12 gd_header_section body_fixed_content hidden-xs hidden-sm">
<div className="gd_header_collapsed_container" id="gd_header_collapsed_container">
<img className="gd_header_logo_icon" src={require('./images/GD_Logo_gray.png')} alt="gd gray logo" onClick={this.loadHomePage} />
<img className="gd_header_menu_close_icon" src={require('./images/cross_icon.png')} alt="cross icon" onClick={this.opengdMenu} />
<div className="godocto_header_menu_option_div" onClick={this.openDesktopAboutusForm}>
<span><a className="gd_header_menu_option"> ABOUT US </a></span>
</div>
<div className="godocto_header_menu_option_div" onClick={this.openDesktopContactForm}>
<span><a className="gd_header_menu_option">CONTACT US</a></span>
</div>
</div>
</div>
<div className="gd_small_header_collapsed_container col-xs-12 col-sm-12 col-md-12 visible-xs visible-sm" id="gd_small_header_collapsed_container">
<div className="gd_small_header_collpased_menu_division">
<img className="gd_small_header_logo_icon2" src={require('./images/GD_Logo_gray.png')} alt="gd small logo" onClick={this.loadHomePage} />
<img className="gd_small_header_menu_close_icon" src={require('./images/cross_icon.png')} alt="gd small menu mobile" onClick={this.opengdMenu} />
</div>
<div className="gd_small_header_menu_division">
<div className="gd_small_header_menu_option_division" onClick="openSmallDesktopAboutusForm()">
<span><a className="gd_small_header_menu_option">ABOUT US</a></span>
</div>
<div className="gd_small_header_menu_option_division" onClick="openSmallDesktopContactForm()">
<span><a className="gd_small_header_menu_option">CONTACT US</a></span>
</div>
</div>
</div>
</div>
);
}
}
}
export default Header
when i clicked on menu then it caught error or i click whether about or contact same error will come
in about us and contact component i render text simply but error was come in header component
there is only one state variable
The error you are having is on this part:
<div className="gd_small_header_menu_division">
<div className="gd_small_header_menu_option_division" onClick="openSmallDesktopAboutusForm()">
<span><a className="gd_small_header_menu_option">ABOUT US</a></span>
</div>
<div className="gd_small_header_menu_option_division" onClick="openSmallDesktopContactForm()">
<span><a className="gd_small_header_menu_option">CONTACT US</a></span>
</div>
</div>
take a look at this part onClick="openSmallDesktopAboutusForm()". You can write it like this onClick={openSmallDesktopAboutusForm()} just like what you did with your other methods.
As per the warning message, you can just change onclick on small caps, with onClick with the camel case.

Categories

Resources