mizzao / meteor-sharejs

Meteor smart package for transparently adding ShareJS editors to an app
MIT License
225 stars 53 forks source link

share-js with React #63

Open hennoxlane opened 8 years ago

hennoxlane commented 8 years ago

I'm trying to use this template with ReactJS, by wrapping it in the blazetoreact package and rendering it that way.

I've got a documents collection, and am passing the mongo ID to docid. Here's the component I'm using shareJS in:

const ShareJS = BlazeToReact('_sharejsText'); //need the underscore here, or the package isn't found
import Documents from '../../collections/Documents.js';

const App = React.createClass({
  mixins: [ReactMeteorData],
  getMeteorData() {
    let handle = Meteor.subscribe("documents");
    return {
      documentsLoading: ! handle.ready(),
      documents: Documents.find().fetch(),
    };
  },
  createDocument() {
    Documents.insert({
      title: "untitled"
    });
  },
  render: function() {
    if (!this.data.documentsLoading) {
      return (
          <ShareJS docid={this.data.documents[0]._id} id="editor" />
      );
    }
    return <div onClick={this.createDocument}>create document</div>;
  }
});

export default App;

The result is that once I click a button (other component) to insert a new document, I'm seeing a textarea rendered, with '....loading' in it. And that's it. No functionality.

Troubleshooting: I can see the ops collection being created in my mongo, but there's no sign of the docs collection.

I've been able to setup shareJS with blaze just fine, no issues there: a click on a button creates a new Document, set the id to docid on a Session variable & presto, I can see the docs collection getting created in mongo & I'm good to go.

So far, that seems to be the one thing that's different: I'm not using Session in React.

So I'm wondering: does this package depend on usage of Session? Or is there another way to trigger the creation of the shareJS docs collection?

If there's no way to use this in Meteor + React, I'll use the plugin as is in a Blaze template, but I'd rather stick to a uniform codebase, if I can.

update: I've tried setting the document on a session variables and passing that through the docid prop, but no luck so far. This is most likely something to do with data-context not begin set correctly, I guess?

mizzao commented 8 years ago

If you look at the code, it does make use of the Blaze APIs, so I don't think you can just drop it in React like that.

vincentracine commented 8 years ago

Here a copy and paste from my AceEditor React component.

// AceEditor.js
AceEditor = React.createClass({

    getInitialState(){
        return {
            doc: null,
            editor: null
        }
    },

    componentDidMount(){
        // Get Ace Editor from DOM
        this.state.editor = ace.edit("editor");

        // Set Ace Editor behaviour
        this.setTheme('ace/theme/chrome');
        this.state.editor.getSession().setMode("ace/mode/python");
        this.state.editor.setFontSize(14);
        this.state.editor.setShowPrintMargin(false);
        this.state.editor.getSession().setUseWrapMode(true);
        this.state.editor.$blockScrolling = Infinity;
        this.state.editor.resize();

        this.onChange();
    },

    shouldComponentUpdate(nextProps, nextState) {
        return nextProps.docid !== this.props.docid;
    },

    componentDidUpdate(){
        this.onChange();
    },

    componentWillUnmount(){

        // Disconnect from ShareJS
        this.disconnect();

        // Clean up Ace memory to avoid memory leaks
        this.state.editor.destroy();

    },

    // ------- ShareJS

    onChange(){

        // Doesn't have a document opened but wants to connect
        if(!this.isConnected() && this.props.docid){
            this.connect(this.props.docid);
        }

        // Has a document opened but wants to open a new document
        if(this.isConnected() && this.props.docid){
            this.disconnect();
            this.connect(this.props.docid);
        }

        // Has a document opened but wants to close it
        if(this.isConnected() && !this.props.docid){
            this.disconnect();
        }

    },
    connect(documentId){
        let self = this;

        if(this.isConnected()){
            throw new Error('Already connected to ShareJS');
        }

        // Open the document
        sharejs.open(documentId, 'text', function(error, doc){
            if(error) {
                console.error("Connection error:", error);
            }else{
                // Update state
                self.setState({ doc: doc });

                // Check we are connected
                if(self.isConnected()){
                    // Attach ace editor to document
                    doc.attach_ace(self.state.editor);
                    console.log('Opened document [',documentId,']');
                }else{
                    console.error("Document was opened but closed right away");
                }
            }
        });
    },
    disconnect(){
        if(this.isConnected()){
            let name = this.state.doc.name;
            this.state.doc.close();
            if(this.state.doc.state === 'closed'){
                console.log('Closed document [',name,']');
            }else{
                console.error('Failed to close document [',name,']');
            }
            this.state.doc.detach_ace();
        }
    },
    isConnected(){
        return this.state.doc != null && this.state.doc.state === 'open';
    },

    // ------- End of ShareJS

    // ------- Editor State

    setTheme(theme){
        this.state.editor.setTheme(theme);
    },
    getAceInstance(){
        return this.state.editor;
    },
    getText(){
        return this.state.editor ? this.state.editor.getValue() : null;
    },

    // ------- End of Editor State

    render() {
        return (
            <div id='editor' ref="editor" data-doc={this.props.docid} className="shareJSAce"></div>
        )
    }
});

And you can use it like this:

EditorPage = React.createClass({

    mixins: [ReactMeteorData],

    getMeteorData() {
        return {
            currentUser: Meteor.user(),
            document: Documents.findOne({_id: this.props.docId})
        }
    },

    ready(){
        return this.data.document;
    },

    render(){
        return (
            <Row class="container-fluid">
                <div className="col-xs-12">
                    {this.ready() ? (
                        <AceEditor ref="editor" docid={this.props.docId}/>
                    ) : null}
                </div>
            </Row>
        )
    }
});