minutemailer / react-popup

React popup component
http://minutemailer.github.io/react-popup/
MIT License
208 stars 71 forks source link

Can't get the popup to even show #24

Closed minisemi closed 7 years ago

minisemi commented 7 years ago

I love how your demo looks, but I can't get the popup to even show in my own code. I have tried to add an onClick and EventListener to a button accordingly. In the EventListener I didn't know what you were referring to by "target.offsetWidth" in you example though, so I just changed it to a "2". Any thoughts?

import React, { Component } from 'react';
import ReactDom from 'react-dom';
import Popup from 'react-popup';
import {Button} from 'react-bootstrap';

class ExampleComponent extends Component {

    constructor (props) {
        super(props);

        this.state = {
            events: [],
        };

        this.handleSelectEvent = this.handleSelectEvent.bind(this)
    }

 componentDidMount(){

        const trigger = document.getElementById('trigger');

        trigger.addEventListener('click', function (e) {
            e.preventDefault();

            Popup.create({
                content: 'This popup will be displayed right above this button.',
                buttons: {
                    right: ['ok']
                },
                noOverlay: true, // Make it look like a tooltip
                position: (box) => {
                    const bodyRect      = document.body.getBoundingClientRect();
                    const btnRect       = trigger.getBoundingClientRect();
                    const btnOffsetTop  = btnRect.top - bodyRect.top;
                    const btnOffsetLeft = btnRect.left - bodyRect.left;
                    const scroll        = document.documentElement.scrollTop || document.body.scrollTop;

                    box.style.top  = (btnOffsetTop - box.offsetHeight - 10) - scroll + 'px';
                    box.style.left = (btnOffsetLeft + 1 - (box.offsetWidth / 2)) + 'px';
                    box.style.margin = 0;
                    box.style.opacity = 1;
                }
            });
        });
    }

  handleSelectEvent() {

        Popup.create({
            className: 'prompt'
        });
        Popup.alert('Hello, look at me');

        let mySpecialPopup = Popup.register({
            title: 'I am special',
            content: 'Since I am special you might need me again later. Save me!',
            buttons: {
                left: ['cancel'],
                right: ['ok']
            }
        });

        Popup.queue(mySpecialPopup);
    }

    render() {
        return (
            <div>
                <Popup
                    className="mm-popup"
                    btnClass="mm-popup__btn"
                    closeBtn={true}
                    closeHtml={null}
                    defaultOk="Ok"
                    defaultCancel="Cancel"
                    wildClasses={false}/>
                <Button id='trigger' onClick={this.handleSelectEvent}/>
            </div>

        );
    }
}

export default ExampleComponent;
tbleckert commented 7 years ago

You are almost correct and it's completely my fault, I realize I need to update the docs.

Since the popup component rely on position and the fact that it's supposed to be a global component, you need to mount it globally as well. Meaning as far up the DOM tree you can. Nesting it inside other components can affect the positioning and cause other problems related to duplication.

For example:

<body>
    <div id="popupContainer"></div>
</body>
import React from 'react';
import ReactDom from 'react-dom';
import Popup from 'react-popup';

ReactDom.render(
    <Popup />,
    document.getElementById('popupContainer')
);

Yo should only mount it once. Multiple popups at the same time is not supported and I'm not sure it every will because that would require drag and other features to be useful.

minisemi commented 7 years ago

Thanks for your quick response! Hmm, ok I see what you mean. I now put the div in the index.html (as far up the DOM tree as possible) as following:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
    <link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Philosopher" />

    <!--
      Notice the use of %PUBLIC_URL% in the tag above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>Bookster</title>
  </head>
  <body>
    <div id="root"></div>
    <div id="popupContainer"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start`.
      To create a production bundle, use `npm run build`.
    -->
  </body>
</html>

And I mounted the Popup in the index.js as following:

import React from 'react';
import ReactDOM from 'react-dom';
import Popup from 'react-popup';
import './static/index.css';
import AppRoutes from './components/AppRoutes'

const Root = () => {
    return (
        <AppRoutes/>
    )
}

ReactDOM.render(
  <Root />,
  document.getElementById('root')
);

ReactDOM.render(
    <Popup />,
    document.getElementById('popupContainer')
);

And in the component currently mounted, I changed the code to the following. But I still can't get it to show. Is there something else I'm missing that you can spot? I'm coding in React and ES6, so maybe it has something to do with your comment about CommonJS in the guide?

import React, { Component } from 'react';
import ReactDom from 'react-dom';
import Popup from 'react-popup';
import {Button} from 'react-bootstrap';

class ExampleComponent extends Component {

    constructor (props) {
        super(props);

        this.state = {
            events: [],
        };

        this.handleSelectEvent = this.handleSelectEvent.bind(this)
    }

  handleSelectEvent() {
        Popup.alert('Hello, look at me');
    }

    render() {
        return (
            <div>
                <Button id='trigger' onClick={this.handleSelectEvent}/>
            </div>
        );
    }
}

export default ExampleComponent;
tbleckert commented 7 years ago

Looks good now. It is also ES6 compatible so it should work. What error messages do you get? If you're not getting any error messages then there's probably something with the styling of the popup. Yet again, I realize there's nothing in the docs about basic styling. Also, some styling will be moved inside the component in the coming realize.

Until then, you can see example css in the pages branch. Basically what you need is:

.mm-popup {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 1000;
    overflow: auto;
    display: none;
    background: rgba(0, 0, 0, .1);
}

.mm-popup--visible {
    display: block;
}
minisemi commented 7 years ago

I am not receiving any error messages. I didn't have the style sheet though, so I created an identical one to your popup.example.css and imported it to the index.js listed in my previous comment. Then I set the classname for the Popup as following:

ReactDOM.render(
    <Popup
        className="mm-popup"
        btnClass="mm-popup__btn"
        closeBtn={true}
        closeHtml={null}
        defaultOk="Ok"
        defaultCancel="Cancel"
        wildClasses={false}/>,
    document.getElementById('popupContainer')
);

However, It is still not showing anything when I press the button in ExampleComponent in my previous comment. I inserted a console.log(Popup) in the button's onClick to make sure it enters the function, which prints:

function Component(props) {
            _classCallCheck(this, Component);

            var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Component).call(this, props));

            initialState.closeO…

I'm wondering if it has to do anything with my reference to Popup in " Popup.alert('Hello, look at me');" which refers to the Popup in "import Popup from 'react-popup'. Shouldn't my Popup refer to the Popup element in index.js somehow (which is now the second child to body after root, as the following div <div id="popupContainer"><div data-reactroot="" class="mm-popup"></div></div>), rather than the module? Or do I need to create or register a Popup before I alert?

Again, I really appreciate that you take time to help me with your Popup component. Hopefully this will help others in my position as well.

minisemi commented 7 years ago

When I do the Popup.alert('Hello, look at me'); it doesn't even change the div popupContainer's display property to block; it remains set to none. Hence the alert isn't changing the Popup's class to visible. However, when I manually set the div's property to block in the chrome's dev tools the screen goes transparent grey (as to fade out in favor for the popup window), but without a popup merging. This is (I guess) because the Popup isn't receiving the text from my alert call, which makes me believe it's the way I refer to the Popup in my ExampleComponent that is the problem. Any thoughts?

tbleckert commented 7 years ago

Is your CSS actually applied to the page? You say that you are importing it in the index.js but how do you load it after that? How is your build step looking? Sounds to me that the CSS is not applied at all.

minisemi commented 7 years ago

I just import it at the top like this:

import React from 'react';
import ReactDOM from 'react-dom';
import Popup from 'react-popup';
import './static/index.css';
import AppRoutes from './components/AppRoutes'
import './static/PopUp.css'

const Root = () => {
    return (
        <AppRoutes/>
    )
}

window.onload = () => {
ReactDOM.render(
  <Root />,
  document.getElementById('root')
);

ReactDOM.render(
    <Popup
        className="mm-popup"
        btnClass="mm-popup__btn"
        closeBtn={true}
        closeHtml={null}
        defaultOk="Ok"
        defaultCancel="Cancel"
        wildClasses={false}/>,
    document.getElementById('popupContainer')
);}

But according to Chrome's dev tools the Popup div looks like this, with the class mm-popup:

<div id="popupContainer"><div data-reactroot="" class="mm-popup"></div></div>

And the devtools also tell me that the div has the following attributes thanks to that class:

.mm-popup {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 1000;
    overflow: auto;
    display: none;
    background: rgba(0, 0, 0, .1);
}

So I'm not sure how it would get those attributes if I loaded the CSS wrongfully through import './static/PopUp.css' in index.js, if you don't include them in the module?

tbleckert commented 7 years ago

That's what I was afraid of. We don't work with CSS in JS, so no inline styles or css modules. So you link it in exactly as you do with your bootstrap css. Why? That's a big topic, but if you're interested I'm happy to chat about it somewhere else.

Maybe we'll support as an option to the way we prefer to work with CSS, but that will not happen in a while I'm afraid.

minisemi commented 7 years ago

Oh ok, I see! So how and where do you suggest I load it to the Popup element? You mention I should do how I link bootstrap, but that is just a link in my index.html like this: <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">.

tbleckert commented 7 years ago

Yeah, what you do there is you use link to fetch a file containing css. So you put the popup css in a file and do the same thing. Normally you would use SASS/LESS along with a build step that compile it to CSS and preferably apply an autoprefixer after that (depending on what your browser requirements are).

If you look at the bigger picture, like how you are planning to use this component you will basically realize one thing. You either realize that you will be using plain css on other parts of the site/app, in that case I would recommend you setup a proper CSS structure with build steps. Or you will realize you do not like the idea of using CSS like this and won't do it on any other part of your site/app, in that case I would recommend you fork this and implement inline styles or how you prefer to work with CSS.

minisemi commented 7 years ago

Ok, so now I put the link there with the Bootstrap as shown below, but it is still not showing. It is a CSS file so should it really need those build steps you mentioned? Because the linked Bootstrap CSS file doesn't go through any build steps. I have never used either SASS or LESS and I'm not exactly a pro at CSS haha, so I have no idea how I would go about doing that or why I would need to build a CSS file (or what it even means by doing so, since I have always just imported them individually for each component). So if that is something you think I need as the final step to get it to show, could you perhaps give me the sample code for doing so please? :)

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
    <link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Philosopher" />
    <link rel="stylesheet" type="text/css" href="../src/static/PopUp.css"/>

    <!--
      Notice the use of %PUBLIC_URL% in the tag above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>Bookster</title>
  </head>
  <body>
    <div id="root"></div>
    <div id="popupContainer"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start`.
      To create a production bundle, use `npm run build`.
    -->
  </body>
</html>
tbleckert commented 7 years ago

You are on track. And no, for the sake of this simple demo or a simple site/small app you do probably not need a build step for compiling CSS.

I'm guessing that src/static is not a publicly available directory. I'm not sure what framework you are using but you're obviously using something with some kind of templating. To get it running you should be able to place the PopUp.css next to your favicon.ico and then use:

<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%/PopUp.css"/>
minisemi commented 7 years ago

I did that as well but still can't get it to work :/

image

tbleckert commented 7 years ago

And you can see that the CSS is actually loaded when you start the server and visit this page?

minisemi commented 7 years ago

Well it has it linked right there in the laundromat1 file according to the dev tools, as <link rel="stylesheet" type="text/css" href="/PopUp.css"/>. But apart from that I'm not sure how to check if it is actually loaded.

image

minisemi commented 7 years ago

But when I click the button which is executing Popup.alert('Hello, look at me'); it doesn't change the div's class in the left hand side of the picture (<div id="popupContainer"><div data-reactroot="" class="mm-popup"></div></div>) from .mm-popup to .mm-popup--visible. So It feels like it doesn't find that div from my component.

image

minisemi commented 7 years ago

This is my component

// @flow
import React, { Component } from 'react';
import ReactDom from 'react-dom';
import BigCalendar from 'react-big-calendar';
import moment from 'moment';
import 'react-big-calendar/lib/css/react-big-calendar.css';
import '../static/BookingCalender.css'
import {getCalenderEvents, bookEvent} from '../utils/bookster-api'
import {Button} from 'react-bootstrap';
import Popup from 'react-popup';
import Auth from '../Auth'

// Setup the localizer by providing the moment (or globalize) Object
// to the correct localizer.
BigCalendar.momentLocalizer(moment); // or globalizeLocalizer

class BookingCalender extends Component {

    constructor (props) {
        super(props);

        this.state = {
            events: [],
        };
        this.handleSelectEvent = this.handleSelectEvent.bind(this)
    }

    componentDidMount(){
        if (this.props.bookingId!==undefined) {
            getCalenderEvents(this.props.bookingId).then((events) => {
                for (var i =0; i<events.length;i++){
                    events[i].start=new Date(events[i].start);
                    events[i].end=new Date(events[i].end);
                }
                this.setState({events: events})
            })
        }

    }

    componentWillReceiveProps(nextProps){
        if (nextProps.bookingId !== this.props.bookingId) {
            getCalenderEvents(this.props.bookingId).then((events) => {
                for (var i =0; i<events.length;i++){
                    events[i].start=new Date(events[i].start);
                    events[i].end=new Date(events[i].end);
                }
                this.setState({events:events})
            })
        }
    }

    handleSelectEvent() {
        Popup.alert('Hello, look at me');
    }

    render() {
        return (
            <div>
                <Button onClick={this.handleSelectEvent}/>
                <BigCalendar
                    selectable
                    events={this.state.events}
                    onSelectEvent={this.handleSelectEvent}
                    defaultView='week'
                    defaultDate={new Date(2017, 4, 15)}
                />
            </div>

        );
    }

export default BookingCalender
tbleckert commented 7 years ago

Ok nice, the CSS is clearly loaded now. Everything looks fine too me. Could you possible share your project with me (I of course understand if you can't) or can you make a small test case that fails?

minisemi commented 7 years ago

Yeah of course, it is just a project I am doing for a course in school. It's public on my github. The server uses a MySQL server to fetch the data though, but the schema for the database is in the top folder of the project. The button which is has an onClick with the Popup.alert is in a BookingCalender.js component. You can get there by signing up, logging in, searching for "Soccer" for example and choosing a search result. The button is to the top right of the calender, just right of the text "agenda".

tbleckert commented 7 years ago

Thanks! I'll try it out and get back to you

minisemi commented 7 years ago

Awesome! Thanks and good luck :)

tbleckert commented 7 years ago

Found the problem. In src/components/Prompt where you register your plugins you are also calling the plugin, trying to display it. This happens before the actual component is mounted and causes an error. Not sure why no errors is logged but removing the code at lines 79-81 in Prompt.js will fix the problem.

minisemi commented 7 years ago

Oh, I didn't even know I used that component. I just created it before and forgot about it lol. Well, now it works. Again, thanks a lot for your help!

andymic commented 7 years ago

Hey @tbleckert I read through this post and still cannot get my popup to show.

{% load render_bundle from webpack_loader %}
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
</head>
<body class="hidden-sn mdb-skin">
    <div id="popupContainer"></div>
    <div id="container">
    </div>
    {% render_bundle 'main' %}
</body>
</html>
-----------------------------------------------------------
Popup.create({
    title: null,
    content: 'Hello, look at me',
    className: 'alert',
    buttons: {
        right: ['ok']
    }
});
ReactDOM.render(<Popup /> ,document.getElementById('popupContainer'));

Alike the comments above, my DOM shows an empty mm-popup element. Any thoughts on what else might cause the issue? Do I need to include my own styles? - Thanks

tbleckert commented 7 years ago

Might just be because you're calling it before it's mounted. But yes, you need some basic styles first (https://github.com/minutemailer/react-popup#basic-styling).