i am working in project where using model to show PDFTron webviewer. i am using model popup to load webviewer inside it when select pdf file from list.
first time it loads pdf document but when "clr-modal" open second time webviewer not load document.
//PdfviewerComponent html.
<div class="page">
<div #viewer class="viewer"></div>
</div>
export class PdfviewerComponent implements OnInit, AfterViewInit, OnChanges {
#ViewChild('viewer', { static: true }) viewer: ElementRef;
#Input() hash: string;
wvInstance: any;
constructor() { }
ngOnInit(): void { }
ngAfterViewInit(): void {
console.log('AfterViewInit called');
WebViewer({
path: '../assets/lib'
}, this.viewer.nativeElement).then(instance => {
this.wvInstance = instance;
const idtoken = localStorage.getItem('id_token');
const options = {
customHeaders: { Authorization: 'Bearer ' + idtoken }
};
const hash = this.hash.split(' ')[0];
const url = `/api/project/readfile/${hash}`;
this.wvInstance.loadDocument(url, options);
});
}
}
//popup in main component
<clr-modal [(clrModalOpen)]="openModel" >
<h3 class="modal-title">{{currentFileName}}</h3>
<div class="modal-body">
<app-pdfviewer [hash]="currentFileHash"></app-pdfviewer>
</div>
</clr-modal>
How does your popup modal work? Does it add "display:none" to hide the DOM element or does it remove the element from the DOM and add it back later?
If it removes the DOM element and add it back, there shouldn't be a problem (beside slower performance due to having to reload everything each time) as long as the WebViewer get re initialized.
If the popup is using "display:none", that is a problem. A lot of browsers have issues when using "display:none" with iframes (which WebViewer uses). If possible try using "visibility:hidde", "opacity:0", or "height: 0px" to hide the modal instead.
Related
image of the detail view with console to see the console.log()
I'm having troubles making the Tour Of Heroes Angular tutorial work, i'm in the 6 step of the tutorial, getting the data from a server but instead of getting the data from a simulated data server i have a api with nodejs express and mysql.
The problem cames when i try to show the detail of the hero (fetching one by id), all seems to work but the information don't show on the view.
template:
<div *ngIf="hero">
<h2>{{ hero.name }} Details</h2>
<div>id: {{hero.id}}</div>
<div>
<label for="name">Hero name: </label>
<input id="name" [(ngModel)]="hero.name" placeholder="name">
</div>
<button type="button" (click)="goBack()">go back</button>
</div>
component:
ngOnInit(): void {
this.getHero();
}
getHero(){
const id = Number(this.route.snapshot.paramMap.get("id"));
this.heroService.getHero(id).subscribe(hero => {
this.hero = hero;
console.log("hero", hero)
})
}
service:
private heroesUrl = 'http://localhost:3300/api/';
constructor(private MessageService: MessageService, private http: HttpClient) {
}
private log(message: string) {
this.MessageService.add(`HeroService: ${message}`);
}
getHeroes(): Observable<Hero[]>{
this.log('HeroService: fetched heroes');
return this.http.get<Hero[]>(this.heroesUrl);
}
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}${id}`;
return this.http.get<Hero>(url);
}
I don't know what's the problem, im learning angular but the observable is well suscribed, in the attached image you can see in the console that at least the api is working.
you received an array with an unique element, see the [``] in console. So
Or in subscribe your write hero[0]
this.heroService.getHero(id).subscribe(hero => {
this.hero = hero[0];
})
Or in your service return the first element of the array. For this use rxjs/operator map
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}${id}`;
return this.http.get<Hero[]>(url).pipe(
map((res:Hero[])=>res[0])
);
}
See that although you say to Angular that getHero return an Observable<Hero> really you got an Observable<Hero[]>. Yes, when we indicate the return of a function this not make "magically" we get the result, only help us to write the code and the editor advise us about it
I am trying to build dynamic content from a SharePoint list using SPFX. I'd like to use jQuery to build an accordion view of the data. The issue is that I can't even seem to get the element once the page is rendered.
In my code I am requiring a file called ota.js with the following code:
console.log('Start');
function otaExpand(){
console.log('otaExpand Function Called');
let spListContainer = document.getElementById('spListContainer');
console.log(spListContainer);
}
window.addEventListener("load", otaExpand());
In my ts file this is my render method:
public render(): void {
this.domElement.innerHTML = `
<div>
<div id="spListContainer">TEST</div>
</div>
`;
//this._renderListAsync();
//($('.accordion', this.domElement) as any).accordion();
}
When I review the console, I get my messages, but the element itself comes back as null.
console.log
I am using SharePoint 2019 on premise with the following configuration.
+-- #microsoft/generator-sharepoint#1.10.0
+-- gulp-cli#2.3.0
`-- yo#2.0.6
node --version
v8.17.0
I should also mention I am using TypeScript with no JavaScript framework.
Does anyone know why I can't access this element from my js file?
Thanks!
My overall goal is to call list data and apply an accordion style to it (https://jqueryui.com/accordion), but I can't even get passed capturing the element to change it.
I've tried calling my code from a js file as well as trying to put the code directly in the html. Neither worked.
OK, I finally figured out what I was doing wrong. I was calling my jQuery in the render() method rather than in _renderList where this.domElement actually makes sense.
Here's my code in case anyone wants to avoid the pain I put myself through. This allows you to specify a list in the site and you just need to add the fields you want to display.
import { Version } from '#microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneChoiceGroupOption,
IPropertyPaneConfiguration,
PropertyPaneChoiceGroup,
PropertyPaneCustomField,
PropertyPaneTextField
} from '#microsoft/sp-webpart-base';
import { escape } from '#microsoft/sp-lodash-subset';
import styles from './GetSpListItemsWebPart.module.scss';
import * as strings from 'GetSpListItemsWebPartStrings';
import {
SPHttpClient,
SPHttpClientResponse
} from '#microsoft/sp-http';
import * as jQuery from 'jquery';
import 'jqueryui';
import { SPComponentLoader } from '#microsoft/sp-loader';
import PropertyPane from '#microsoft/sp-webpart-base/lib/propertyPane/propertyPane/PropertyPane';
export interface IGetSpListItemsWebPartProps {
title: string;
description: string;
listField: string;
}
export interface ISPLists {
value: ISPList[];
}
export interface ISPList {
ID: string;
Title: string;
Website: {
Description : string,
Url : string
};
Description : string;
}
export default class GetSpListItemsWebPart extends BaseClientSideWebPart<IGetSpListItemsWebPartProps> {
private _getListData(): Promise<ISPLists> {
return this.context.spHttpClient.get(this.context.pageContext.web.absoluteUrl + "/_api/web/lists/GetByTitle('" + this.properties.listField + "')/Items",SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});
}
private _renderListAsync(): void {
this._getListData()
.then((response) => {
this._renderList(response.value);
})
.catch(() => {});
}
private _renderList(items: ISPList[]): void {
let listData = `
<h1>${this.properties.title}</h1>
<h2>${this.properties.description}</h2>
<div class="accordion">
`;
items.forEach((item: ISPList) => {
let Description : string;
item.Description ? Description = item.Description : Description = "";
listData += `
<h3> ${item.Title}</h3>
<div>
<table>
<tr>
<td>OTA URL</td>
<td>${item.Website.Description}</td>
</tr>
<tr>
<td>Description</td>
<td>${Description}</td>
</tr>
</table>
</div>
`;
});
listData += '</div>';
this.domElement.innerHTML = listData;
const accordionOptions: JQueryUI.AccordionOptions = {
animate: true,
collapsible: true,
icons: {
header: 'ui-icon-circle-arrow-e',
activeHeader: 'ui-icon-circle-arrow-s'
}
};
jQuery('.accordion', this.domElement).accordion(accordionOptions);
}
public render(): void {
this._renderListAsync();
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('title',{
label: strings.TitleFieldLabel
}),
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
}),
PropertyPaneTextField('listField', {
label: strings.ListFieldLabel
})
]
}
]
}
]
};
}
public constructor() {
super();
SPComponentLoader.loadCss('//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css');
}
}
Your code from the "ota.js" file is probably called before your HTML is initialized (i.e. before the "render()" function is executed). To make sure this is the case, you could add log to the "render()" function to see when it's called.
In other words, "window.load" event happens long before "render()" function is called. This is how web parts are loaded - dynamically after full load of the page. Or "window.load" does not happen at all - web parts may be loaded by the user when using the page designer, i.e. without page reload.
To fix the issue, you should get the element after it's created, i.e. after the "render()" function creates the element you are trying to get.
I am trying to implement Stripe payments in my Next.js app as described in the guide here: https://stripe.com/docs/payments/quickstart
The guide tells me that in order to use Stripe Elements for my checkout form, I need to know payment intent. It says:
Create PaymentIntent as soon as the page loads
The issue is - our website will not have a separate payments page, the payment form will be displayed inside the modal, which is loaded on every page of the website. That means, I would have to fetch the payment intent for any user who ever visits any page on our website, whether they're planning to purchase the course or not, just so that I could display the payment form inside the modal. That doesn't seem right to me.
Can you give me some advice, let me know if there's a better way to handle this?
Another issue is that this guide tells me that I should pass the fetched payment intent clientSecret as an option to <Elements/> wrapper.
And if I hover on <Elements/> wrapper in my VSCdoe, it tells me:
[...] Render an Elements provider at the root of your React app so that it is available everywhere you need it. [...]
So, does that mean I have to put <Elements/> wrapper into my _app.tsx file? And that means I'd have to fetch the payment intent clientSecret inside of the _app.tsx? So that my app would fetch payment intent secret any time any user ever loads any page on my website?
Again, this seems pretty weird, wouldn't it slow things down, add extra requests and loading time to all my pages, and create a whole bunch of payment intents that are never used?
Render the payment form in a modal in Layout.js and wrap the
entire project in the Layout component
place this code in _app.js
import React, { useEffect, useState } from "react"
import { loadStripe } from "#stripe/stripe-js"
import { Elements } from "#stripe/react-stripe-js"
import Layout from "../components/Layout"
import PaymentModalForm from "../components/PaymentModalForm"
const promise = loadStripe("pk_test_....")
// replace pk_test_... with your publishable key
const API_URL = "http://localhost:8000"
// replace API_URL with your backend server url
const App = ({ Component, pageProps }) => {
const [secret, setSecret] = useState(null)
useEffect(() => {
const fetchSecret = async () => {
const response = await fetch(`${API_URL}/create_intent`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
items: [{ id: 'adidas boost', quantity: 2}]
})
})
const { client_secret } = await response.json()
setSecret(clientSecret)
}
fetchSecret()
}, [])
const options = {
clientSecret: secret,
appearance: { theme: "stripe"}
}
return (
{secret && (
<Elements stripe={promise} options={options}>
<Layout>
<Component {...pageProps} />
</Layout>
</Elements>
)}
)
}
export default App
Then in your Layout.js, fill in this code
import PaymentModalForm from "../components/PaymentModalForm"
import React, { useEffect, useState } from "react"
const Layout = ({ children }) => {
const [showModal, setShowModal] = useState(false)
const handleClick = () => {
if (showModal) {
setShowModal(false)
} else {
setShowModal(true)
}
}
return (
<div>
<div className="container">
{children}
<button onClick={handleClick}>Show Payment Modal</button>
</div>
{showModal ? (
<div className="modal fade">
<div className="modal-dialog">
<div className="modal-content">
<PaymentModalForm />
</div>
</div>
</div>
) : ( null )}
</div>
)
}
export default Layout
There's more work to be done in PaymentModalForm.js
so i got this React project - there is an admin page and i can add new vacations (with Reactstrap Modal).
the Vacations are added to the database and are shwon in a child's component called AdminVacations.
the array of the Vacations is loaded at the child's component.
this is the code to add Vacation (in admin component)
async Add (){
let NewVac = {
name:this.state.name,
price:this.state.price,
start:this.state.start,
end:this.state.end,
image:this.state.file,
description:this.state.description
}
let r = await fetch(`${apiUrl}/addvacation`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(NewVac)
});
let jsonData= await r.json();
if (r.status === 200){
this.toggle();
}
}
i also got edit and delete buttons - which found in the child's child component.
the problem is that i need to show the changes on the screen(Live).
i manage to do it with the edit and delete button (because they are found in the same component)
but the add button is not there.
how can i make the Vacations state be reloaded again in the child's component from his father component?
import React, { Component } from 'react';
import apiUrl from './apiUrl';
import AdminVacation from './AdminVacation';
import AdminNavbar from './AdminNavbar'
class AdminVacations extends Component {
state={
Vacations:[],
userid:''
}
render() {
return (
<div className="container">
<div class="row">
{this.state.Vacations.map(data=> <AdminVacation key={data.id} data={data} RefreshFunc={this.RefreshFunc.bind(this)} />)}
</div>
</div>
)
}
async componentDidMount(){
let response = await fetch(`${apiUrl}/allvacations`);
let data = await response.json();
this.setState({ Vacations:data })
}
RefreshFunc(){
console.log("something Happened")
this.componentDidMount()
}
}
export default AdminVacations;
(basically i want to run componentDidMount in the Child's component (from Admin) again every time i add a Vacation - and not just when i edit it or delte it.
the Edit and Delete are found in AdminVacation Component.
You can use the componentDidUpdate lifecycle method in the child to check for any updates to the props. In a nutshell, it works like this. The following code checks if the current props and previous props, with respect to location are different, and if so, it performs the actions inside the if block.
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
window.scrollTo(0, 0); // reset scroll on route change
}
}
I'm still a beginner in reactJS (using nodeJS backend) and I have to create a website to manage my collections. I don't know if what I'm going to ask you is feasible, but it probably is.
So I'm using a react component, react-photo-gallery. It's a component where you can use url links and it mixes them together to create a beautiful gallery.
https://github.com/neptunian/react-photo-gallery
I'm using nodeJS to get the information from the database, where I get the urls of all the pictures. For example I have a collection of cards, and an url of the image which represents the collection. What I want to do is get the link of the picture that I'm clicking on so I can use it in another component.
import React from 'react';
import { render } from 'react-dom';
import Gallery from 'react-photo-gallery';
import Photo from './Photo';
class PhotoGallery extends React.Component {
constructor(props){
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
urlImages: []
};
}
async componentDidMount() {
var getUrlImages = 'http://localhost:3004';
const response = await fetch(getUrlImages+"/getUrlImages");
const newList = await response.json();
this.setState(previousState => ({
...previousState,
urlImages: newList,
}));
}
galleryPhotos() {
if(this.state.urlImages) {
return this.state.urlImages.map(function(urlimage) {
return { src: urlimage.urlimage, width: 2, height: 2 }
})
}
}
onClick() {
alert(this.galleryPhotos().value);
}
render() {
return (
<Gallery axis={"xy"} photos={this.galleryPhotos()} onClick={this.onClick}/>
)
}
}
const photos = [];
export default PhotoGallery;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Basically what I want to do is get the source link of the picture in the onClick function. Is that possible?
Thanks in advance!
Check the onClick event.
onClick(event) {
alert(event.target.src)
}
The DEMO
The onClick event of the Gallery component has a number of arguments:
the event
an object containing the selected index and the original photo object
You can use this in your onClick handler:
onClick(e, obj) {
const src = obj.photo.src
// do whatever you need with the src (setState, etc)
}