SPFx React FileViewer for Sharepoint Custom List - reactjs

I am new to SPFx and React. The requirement is to read a Sharepoint Custom List and render the list columns with custom look and feel. This is achieved and read all the attachments (more than one, for a single list item).
How to open all the attachments with React FileViewer (more than one) in browser?
public render(): React.ReactElement<ICirDetailsProps> {
this._renderListAsync();
return(
<div className="${ styles.cirDetails }">
<div className="${ styles.container }">Circulars<br/>
<div id="cirDetails" className="${ styles.details}"></div>
</div>
</div>
);
}
attachments.forEach((afile: any) => {
let fileUrl = hostURL + afile.ServerRelativeUrl;
let fileExt = this.getFileExtension(afile.ServerRelativeUrl);
attFiles += `<FileViewer fileType='${fileExt}' filePath='${afile.ServerRelativeUrl}' /><br/>`;
});
const listContainer: Element = document.querySelector("#cirDetails") as HTMLElement;
listContainer.innerHTML=html;
It shows no error, but the attachments are not opened within browser. I could see the React FileViewer tags are rendered as HTML Content using F12. What is the mistake am I doing here?

Sample demo:
import * as React from 'react';
import styles from './MyReactFileViewer.module.scss';
import { IMyReactFileViewerProps } from './IMyReactFileViewerProps';
import { escape } from '#microsoft/sp-lodash-subset';
import FileViewer from 'react-file-viewer';
import * as CSS from 'csstype';
import { sp } from "#pnp/sp";
import { IAttachmentInfo } from "#pnp/sp/attachments";
import { IItem } from "#pnp/sp/items/types";
import "#pnp/sp/webs";
import "#pnp/sp/lists/web";
import "#pnp/sp/items";
import "#pnp/sp/attachments";
interface IPnpstate {
attachments: any[]
}
export default class MyReactFileViewer extends React.Component<IMyReactFileViewerProps, IPnpstate> {
constructor(props: IMyReactFileViewerProps, state: IPnpstate) {
super(props);
this.state = {
attachments: []
};
}
public componentDidMount() {
let item = sp.web.lists.getByTitle("MyList").items.getById(18);
// get all the attachments
item.attachmentFiles.get().then((files:IAttachmentInfo[]) => {
var attachs=[];
files.map(file=>{
var fileType=file.FileName.split('.').pop();
var fileUrl=file.ServerRelativeUrl;
attachs.push({"fileType":fileType,"fileUrl":fileUrl});
})
console.log(attachs);
this.setState({attachments:attachs});
});
}
public render(): React.ReactElement<IMyReactFileViewerProps> {
return (
<div className={styles.myReactFileViewer}>
<div className={styles.container}>
<div className={styles.row}>
<div className={styles.column}>
<span className={styles.title}>Welcome to SharePoint!</span>
<p className={styles.subTitle}>Customize SharePoint experiences using Web Parts.</p>
<p className={styles.description}>{escape(this.props.description)}</p>
<a href="https://aka.ms/spfx" className={styles.button}>
<span className={styles.label}>Learn more</span>
</a>
{(this.state.attachments || []).map((item, index) => (
<div key={item.ID}>
<FileViewer
fileType={item.fileType}
filePath={item.fileUrl}
/>
</div>
))}
</div>
</div>
</div>
</div>
);
}
}

I used IFrame tag, based on the file types, the iframe renders differently.

Related

How to load a specific photo with dynamic URL with react.js

I have a component where a list of pictures is rendered and it works perfectly fine :
import { Component} from 'react'
import Header from '../Home/Header'
import Footer from '../Home/Footer'
import PhotoItems from './objet'
class Photos1930 extends Component {
render() {
return (
<div>
<Header />
<h2 className='titre bloc'>Photos 1930</h2>
<div className='bloc bloc__photo'>
{PhotoItems.map((val, key) => {
let id = val.id
let url = val.url
let lienImage = "/galerie/:" + (val.id)
return <div key={id}>
<a href={lienImage}>
<img className='photo' alt='Photo Charles-Quint' src={url}></img>
</a>
</div>
})}
</div>
<Footer />
</div>
)
}
}
export default Photos1930
I want to create an other component where i can load a specific picture when user click on a picture from the precedent list. I use the same logic but for some reason the picture doesn't load. I don't have any errors in my console but on my page i just have the standard icon for image with my alt.
All the pictures are on public folder.
I just don't understand why is it working on one component but not on the other one.
import { Component } from 'react'
import Header from '../Home/Header'
import Footer from '../Home/Footer'
import PhotoItems from './objet'
const url = window.location.pathname
const justId = parseInt((url.split(':')[1]))
function specificId(photo) {
return photo.id === (justId)
}
let justUrl = (PhotoItems.find(specificId).url)
console.log(justUrl)
class PickPhoto extends Component {
render() {
return (
<div>
<Header />
<div>
<h1>{justId}</h1>
<img className="bigPhoto" alt="Charles-Quint" src={justUrl}></img>
</div>
<Footer />
</div>
)
}
}
export default PickPhoto
EDIT1 : Here's my github repo : https://github.com/FranMori/CharlesQuint
and here's my netlify link : https://stoic-bohr-810e13.netlify.app/
You can click on "Galerie Photos" and then click on any picture to see the problem.
in your repo, this.justUrl is undefined. You need to add justUrl in the component's state and update it dynamically inside componentDidMount like below. I also added a / in src={/${this.state.justUrl}}
import { Component } from 'react'
import Header from '../Home/Header'
import Footer from '../Home/Footer'
import PhotoItems from './objet'
class PickPhoto extends Component {
constructor() {
super()
this.state = { justUrl: "" };
}
componentDidMount() {
const url = window.location.pathname
const justId = parseInt((url.split(':')[1]))
function specificId(photo) {
return photo.id === justId
}
let justUrl = (PhotoItems.find(specificId).url)
console.log(justUrl)
this.setState({justUrl})
}
render() {
return (
<div>
<Header />
<div>
<h1>{this.justId}</h1>
<img className="bigPhoto" alt="Charles-Quint" src={`/${this.state.justUrl}`}></img>
</div>
<Footer />
</div>
)
}
}
export default PickPhoto

Fetching and displaying data with React

i am new to react and have been trying this for a long time without any success. Maybe someone could look through and determine the mistate i am making. here is my code.
I have the App.js file with a route to services page where i want to fecth and display data divs with services from a mysql database with react and JSX.
import React, { Component } from "react";
import { BrowserRouter } from "react-router-dom";
import Footer from "./Footer";
import Header from "./Header";
import axios from "axios";
class Services extends React.Component {
constructor(props) {
super(props);
this.state = {
services: [],
};
}
componentDidMount() {
axios
.get("http://localhost:5000/services")
.then((response) => this.setState({ services: response.data }));
}
render() {
var { services } = this.state;
return (
<div>
<div className="row">
<BrowserRouter>
<Header></Header>
</BrowserRouter>
<div className="Services">
<br></br>
<br></br>
<br></br>
</div>
<div className="row">
<h1 className="text-center title">Our Services</h1>
<p className="p-3 text-left">
Quickly get help for your issue by selcting the service that
matches your issues. You can always describe your issue if it does
not match any of our services and you only pay when the issue has
been resolved.
</p>
<hr></hr>
{services}
</div>
</div>
<BrowserRouter>
<Footer></Footer>
</BrowserRouter>
</div>
);
}
}
export default Services;
import React from "react";
import { BrowserRouter } from "react-router-dom";
import axios from "axios";
class Services extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
componentDidMount() {
axios.get("https://jsonplaceholder.typicode.com/posts").then(response => {
console.log(response);
this.setState({ data: response.data });
});
}
render() {
let services_data = [];
this.state.data.forEach((res, i) => {
services_data.push(
<ul key={res.i}>
<li>{res.id}</li>
<li>{res.title}</li>
<li>{res.body}</li>
</ul>
);
});
return (
<div>
<div className="row">
<BrowserRouter>Header</BrowserRouter>
<div className="Services" />
<div className="row">
<h1 className="text-center title">Our Services</h1>
<p className="p-3 text-left">
Quickly get help for your issue by selcting the service that
matches your issues. You can always describe your issue if it does
not match any of our services and you only pay when the issue has
been resolved.
</p>
<hr />
{services_data}
</div>
</div>
<BrowserRouter>Footer</BrowserRouter>
</div>
);
}
}
export default Services;
The above code is working fine and split the method like this.

How to call props of a sidebar Component? - Leads to TypeError: Cannot read property 'map' of undefined

I am adding a props of sidebar Component to my template.
I am passing {...this.props} to Sidebar.
But it still leads to TypeError: Cannot read property 'map' of undefined in my Menu file.
My PostTemplateDetails file that I wish to add the Sidebar component:
import React from 'react'
import Sidebar from '../Sidebar'
import { Link } from 'gatsby'
import moment from 'moment'
import './style.scss'
class PostTemplateDetails extends React.Component {
render() {
const { subtitle, author } = this.props.data.site.siteMetadata
const post = this.props.data.markdownRemark
const tags = post.fields.tagSlugs
const tagsBlock = (
<div className="post-single__tags">
<ul className="post-single__tags-list">
{tags &&
tags.map((tag, i) => (
<li className="post-single__tags-list-item" key={tag}>
<Link to={tag} className="post-single__tags-list-item-link">
{post.frontmatter.tags[i]}
</Link>
</li>
))}
</ul>
</div>
)
return (
<div>
<Sidebar {...this.props} />
<div className="content">
<div className="content__inner">
<div className="post-single">
<div className="post-single__inner">
<h1 className="post-single__title">{post.frontmatter.title}</h1>
<div
className="post-single__body"
/* eslint-disable-next-line react/no-danger */
dangerouslySetInnerHTML={{ __html: post.html }}
/>
<div className="post-single__date">
<em>
Published {moment(post.frontmatter.date).format('D MMM YYYY')}
</em>
</div>
</div>
<div className="post-single__footer">
{tagsBlock}
<hr />
<p className="post-single__footer-text">
{subtitle}
<a
href={`https://twitter.com/${author.twitter}`}
target="_blank"
rel="noopener noreferrer"
>
<br /> <strong>{author.name}</strong> on Twitter
</a>
</p>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default PostTemplateDetails
My Sidebar component file:
import React from 'react'
import get from 'lodash/get'
import { Link } from 'gatsby'
import Menu from '../Menu'
import Links from '../Links'
import profilePic from '../../pages/photo.jpg'
import './style.scss'
class Sidebar extends React.Component {
render() {
const { location } = this.props
const {
author,
subtitle,
copyright,
menu,
} = this.props.data.site.siteMetadata
const isHomePage = get(location, 'pathname', '/') === '/'
/* eslint-disable jsx-a11y/img-redundant-alt */
const authorBlock = (
<div>
<Link to="/">
<img
src={profilePic}
className="sidebar__author-photo"
width="75"
height="75"
alt={author.name}
/>
</Link>
{isHomePage ? (
<h1 className="sidebar__author-title">
<Link className="sidebar__author-title-link" to="/">
{author.name}
</Link>
</h1>
) : (
<h2 className="sidebar__author-title">
<Link className="sidebar__author-title-link" to="/">
{author.name}
</Link>
</h2>
)}
<p className="sidebar__author-subtitle">{subtitle}</p>
</div>
)
/* eslint-enable jsx-a11y/img-redundant-alt */
return (
<div className="sidebar">
<div className="sidebar__inner">
<div className="sidebar__author">{authorBlock}</div>
<div>
<Menu data={menu} />
<Links data={author} />
<p className="sidebar__copyright">{copyright}</p>
</div>
</div>
</div>
)
}
}
export default Sidebar
My Menu component file, which is added in the Sidebar component file - this is where the error seems to be residing.
import React from 'react'
import { Link } from 'gatsby'
import './style.scss'
class Menu extends React.Component {
render() {
const menu = this.props.data
const menuBlock = (
<ul className="menu__list">
{menu.map(item => (
<li className="menu__list-item" key={item.path}>
<Link
to={item.path}
className="menu__list-item-link"
activeClassName="menu__list-item-link menu__list-item-link--active"
>
{item.label}
</Link>
</li>
))}
</ul>
)
return <nav className="menu">{menuBlock}</nav>
}
}
export default Menu
I am not sure why this is not working, since adding in my PAGETemplateDetails file seem to be working fine:
import React from 'react'
import Sidebar from '../Sidebar'
import './style.scss'
class PageTemplateDetails extends React.Component {
render() {
const page = this.props.data.markdownRemark
return (
<div>
<Sidebar {...this.props} />
<div className="content">
<div className="content__inner">
<div className="page">
<h1 className="page__title">{page.frontmatter.title}</h1>
<div
className="page__body"
/* eslint-disable-next-line react/no-danger */
dangerouslySetInnerHTML={{ __html: page.html }}
/>
</div>
</div>
</div>
</div>
)
}
}
export default PageTemplateDetails
SiteMetadata.menu is queried on the Post template File:
import React from 'react'
import Helmet from 'react-helmet'
import { graphql } from 'gatsby'
import Layout from '../components/Layout'
import PostTemplateDetails from '../components/PostTemplateDetails'
class PostTemplate extends React.Component {
render() {
const { title, subtitle } = this.props.data.site.siteMetadata
const post = this.props.data.markdownRemark
const { title: postTitle, description: postDescription } = post.frontmatter
const description = postDescription !== null ? postDescription : subtitle
return (
<Layout>
<div>
<Helmet>
<title>{`${postTitle} - ${title}`}</title>
<meta name="description" content={description} />
</Helmet>
<PostTemplateDetails {...this.props} />
</div>
</Layout>
)
}
}
export default PostTemplate
export const pageQuery = graphql`
query PostBySlug($slug: String!) {
site {
siteMetadata {
title
subtitle
copyright
author {
name
twitter
}
disqusShortname
url
}
}
markdownRemark(fields: { slug: { eq: $slug } }) {
id
html
fields {
tagSlugs
}
frontmatter {
title
tags
date
description
}
}
}
`
Not sure if this is relevant but this is the post file:
import React from 'react'
import { Link } from 'gatsby'
import moment from 'moment'
import './style.scss'
class Post extends React.Component {
render() {
const {
title,
date,
category,
description,
} = this.props.data.node.frontmatter
const { slug, categorySlug } = this.props.data.node.fields
return (
<div className="post">
<div className="post__meta">
<time
className="post__meta-time"
dateTime={moment(date).format('MMMM D, YYYY')}
>
{moment(date).format('MMMM YYYY')}
</time>
<span className="post__meta-divider" />
<span className="post__meta-category" key={categorySlug}>
<Link to={categorySlug} className="post__meta-category-link">
{category}
</Link>
</span>
</div>
<h2 className="post__title">
<Link className="post__title-link" to={slug}>
{title}
</Link>
</h2>
<p className="post__description">{description}</p>
<Link className="post__readmore" to={slug}>
Read
</Link>
</div>
)
}
}
export default Post
You're passing the property data as "menu" <Menu data={menu} />
In the Menu component, you don't have the menu property, you have this.props.data, which is equal to menu value, as defined in the Sidebar component. Probably there's no such property "menu" on this.props.data
So your code should be const data = this.props or const menu = this.props.data if you want to keep the variable name.
Thank you everyone, it was a query problem as ksav suspected. The Menu and Sidebar components were fine. I failed to query the menu information in my sitemetadata, and was able to fix it by going to my Post Template File and querying the menu, like so:
export const pageQuery = graphql`
query PostBySlug($slug: String!) {
site {
siteMetadata {
title
subtitle
copyright
menu {
label
path
}
author {
name
twitter
}
disqusShortname
url
}
}
markdownRemark(fields: { slug: { eq: $slug } }) {
id
html
fields {
tagSlugs
}
frontmatter {
title
tags
date
description
}
}
}
`
Thank you everyone, I'm struggling but I got there.

How to add a skeleton load for generated items

I'm working on a slider of items similar to Netflix but with movies, using the info recovered from the tmdb API.
I would like to add a skeleton load for each dynamically generated item using map to improve the user experience.
First of all for the skeleton loading I got a lot of inspiration from this code available on pen code that I adapted afterwards.
https://codepen.io/mxbck/pen/EvmLVp
I try a solution first here but it does not work, I voluntarily remove code that did not relate to my problem for clarity:
Slider component
import React, {PureComponent , Component } from "react";
import style from './Caroussel.css';
import MovieItem from "../components/MovieItem";
import MovieItemContainer from '../components/MovieItem/MovieItemContainer';
class Slider extends Component {
constructor(props){
super(props);
this.handleOnLeftArrowClick = this.leftArrowClick.bind(this);
this.handleOnRightArrowClick = this.rightArrowClick.bind(this);
this.state = {
sliderItems: [],
}
}
componentDidMount() {
this.updateSliderState();
this.setState({
totalItems: this.props.movieList.length,
sliderItem: this.props.movieList
})
}
componentWillMount(){
if(typeof(window) !== 'undefined') {
$(window).on('resize', this.updateSliderState.bind(this))
}
}
render(){
const { sliderItem} = this.state;
const sliderClass = cx ({
sliderMask:true,
moving
})
return(
<div className="wrapper">
<div className={style.slider}>
<div className={sliderClass} ref="slider">
{this.state.sliderItem ?
sliderItem.map((element, index) => (
<MovieItemContainer>
<MovieItem
key={index}
title={element.title}
id={element.id}
release_date={element.release_date}
url={element.backdrop_path}
/>
</MovieItemContainer>
))
:
sliderItem.map((element, index) => {
null
})
}
</div>
{
click &&
<div className={style.leftArrow} ref="leftArrow">
<IosArrowBack onClick{this.handleOnLeftArrowClick} color="black" />
</div>
}
<div className={style.rightArrow} ref="rightArrow">
<IosArrowForward onClick={this.handleOnRightArrowClick} color="black" />
</div>
</div>
</div>
);
}
}
export default Slider;
MovieItem Container component
import React, {Component} from "react";
import '../../../style/card.scss';
class MovieItemContainer extends React.Component {
render() {
return (
<div className="card">
{this.props.children}
</div>
);
}
}
export default MovieItemContainer;
MovieItem component
import React from 'react';
import Moment from 'react-moment';
import style from './MovieItem.css';
import {Link} from 'react-router';
const MovieItem = ({ url, title, release_date, id }) => {
let link ='https://image.tmdb.org/t/p/w300/'+url;
const text_truncate = (str, length, ending) => {
if (length == null) {
length = 100;
}
if (ending == null) {
ending = '...';
}
if (str.length > length) {
return str.substring(0, length - ending.length) + ending;
} else {
return str;
}
};
return (
<div className={style.sliderItem}>
<div style={{ borderBottomLeftRadius: 8, borderBottomRightRadius:8 }} className={style.sliderItemInner}>
<img style={{ borderRadius: 8 }} className={style.cover} src={link} />
<div className={style.shadow}></div>
<div className={style.titles}>
<span className={style.title}>
<Link className={style.title} to={`film/${id}`}>
{text_truncate(title,18)}
</Link>
</span>
<span className={style.release_date}>
<Moment format="YYYY">
{release_date}
</Moment>
</span>
</div>
</div>
</div>
)
}
export default MovieItem;
The MovieItem component only makes the item a movie with a background image, the title of the movie and the year of release.
In the example above, the loading is done for only one card, I would just adapt this code for each of the generated items (10 items).
I thank you in advance for your help and your answers.

React JS thumbnail gallery

I am building a component which is a gallery that consists of a main gallery image and a list of thumbnails under the main image. This component will be used across multiple pages so each page will have it own thumbnails/images. I have worked out how to get the correct images into the gallery depending on the page using redux and a store. However I cant figure out the functionality in getting the main image to change when the corresponding thumbnail is clicked, any ideas or suggestion how I could approach this?
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Gallery extends Component {
render() {
let img = this.props.bouq.map((el, index) => {
return(
<img src={"/images/" + el + ".jpg"} alt="." key={index}/>
);
})
return(
<section className="gallery">
<div className="mainImage">
<img src='/images/bouquets.jpg' alt="."/>
</div>
<div className="thumbnails">
{img}
</div>
</section>
);
}
}
const mapStateToProps = state => {
return {
bouq: state.bouquets
};
}
export default connect(mapStateToProps)(Gallery);
You can leverage the access to every individual element inside the map, onClick on an element, you can update you mainImage in your redux store.
You'll also need to create an action that updates the mainImage (depending on how you manage your redux actions)
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { updateMainImage } from './actions'
class Gallery extends Component {
updateMainImage = tumbnail => () => {
this.props.dispatch(updateMainImage(thumbnail))
}
render() {
const { mainImg, bouq } = this.props
return (
<section className="gallery">
<div className="mainImage">
<img src={`/images/${mainImage}.jpg`} alt={mainImage} />
</div>
<div className="thumbnails">
{bouq.map(thum => (
<img
key={thumb}
src={`/images/${thumb}.jpg`}
alt={thumb}
onClick={this.updateMainImage(thumb)}
/>
))}
</div>
</section>
)
}
}
const mapStateToProps = state => {
return {
bouq: state.bouquets,
mainImage: state.mainImage,
}
}
export default connect(mapStateToProps)(Gallery)

Resources