diff --git a/README.md b/README.md index 3986040..0b173ba 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ -purescript-react -================ +# purescript-react -[![Maintainer: paf31](https://img.shields.io/badge/maintainer-paf31-lightgrey.svg)](http://github.com/paf31) ![React: 0.13.3](https://img.shields.io/badge/react-0.13.3-lightgrey.svg) +[![Maintainer: paf31](https://img.shields.io/badge/maintainer-paf31-lightgrey.svg)](http://github.com/paf31) ![React: 0.14](https://img.shields.io/badge/react-0.14-lightgrey.svg) Low-level React Bindings for PureScript. @@ -9,71 +8,10 @@ For a more high-level set of bindings, you might like to look at `purescript-the - [Module Documentation](docs/) -## Dynamic children - -There are two ways that child elements can be passed to components: -- The first way is to spread the child element array when passing them to React's `createElement` function. The [React.DOM](docs/React/DOM.md) and [React.DOM.SVG](docs/React/DOM/SVG.md) spread the child element array. -- The second way is to pass the child element array to `createElement` without spreading. This is useful when dealing with [dynamic children](https://facebook.github.io/react/docs/multiple-components.html#dynamic-children). In this case a `key` property should be assigned direclty to each child. The [React.DOM.Dynamic](docs/React/DOM/Dynamic.md) and [React.DOM.SVG.Dynamic](docs/React/DOM/SVG/Dynamic.md) pass the child element array without spreading. - -Note that this module provides functions `createElement` and `createElementDynamic` to handle the two cases above for component classes. - -## Building - -The library and example can be built with Pulp as follows: - - pulp dep update - pulp build - - pulp test -r cat > example/index.js - open example/index.html +``` +bower install purescript-react +``` ## Example -```purescript -module Main where - -import Prelude - -import Control.Monad.Eff - -import Data.Maybe.Unsafe (fromJust) -import Data.Nullable (toMaybe) - -import DOM (DOM()) -import DOM.HTML (window) -import DOM.HTML.Document (body) -import DOM.HTML.Types (htmlElementToElement) -import DOM.HTML.Window (document) - -import DOM.Node.Types (Element()) - -import React - -import qualified React.DOM as D -import qualified React.DOM.Props as P - -incrementCounter ctx e = do - val <- readState ctx - writeState ctx (val + 1) - -counter = createClass $ spec 0 \ctx -> do - val <- readState ctx - return $ D.p [ P.className "Counter" - , P.onClick (incrementCounter ctx) - ] - [ D.text (show val) - , D.text " Click me to increment!" - ] - -main = container >>= render ui - where - ui :: ReactElement - ui = D.div [] [ createFactory counter {} ] - - container :: forall eff. Eff (dom :: DOM | eff) Element - container = do - win <- window - doc <- document win - elm <- fromJust <$> toMaybe <$> body doc - return $ htmlElementToElement elm -``` +Please refer to [purescript-react-example](https://github.com/purescript-contrib/purescript-react) diff --git a/bower.json b/bower.json index 85aa2d2..93e66b0 100644 --- a/bower.json +++ b/bower.json @@ -1,29 +1,23 @@ { "name": "purescript-react", - "version": "0.0.5", "homepage": "https://github.com/purescript-contrib/purescript-react", - "description": "Purescript bindings for React.js", + "description": "PureScript bindings for react", + "main": "", "keywords": [ "purescript", "react" ], "license": "MIT", "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" + "*", + "!src/**/*" ], "repository": { "type": "git", "url": "git://github.com/purescript-contrib/purescript-react.git" }, "dependencies": { - "purescript-dom": "~0.2.6" - }, - "devDependencies": { - "purescript-console": "~0.1.1", - "react": "~0.13.3" + "purescript-eff": "~0.1.2", + "purescript-unsafe-coerce": "~0.1.0" } } diff --git a/docs/React.md b/docs/React.md index 08ab7bb..b8a73b9 100644 --- a/docs/React.md +++ b/docs/React.md @@ -18,6 +18,14 @@ data ReactElement :: * A virtual DOM node, or component. +#### `ReactComponent` + +``` purescript +data ReactComponent :: * +``` + +A mounted react component + #### `ReactThis` ``` purescript @@ -302,6 +310,14 @@ createClass :: forall props state eff. ReactSpec props state eff -> ReactClass p Create a React class from a specification. +#### `createClassStateless` + +``` purescript +createClassStateless :: forall props. (props -> ReactElement) -> ReactClass props +``` + +Create a stateless React class. + #### `handle` ``` purescript @@ -350,20 +366,4 @@ createFactory :: forall props. ReactClass props -> props -> ReactElement Create a factory from a React class. -#### `render` - -``` purescript -render :: forall eff. ReactElement -> Element -> Eff (dom :: DOM | eff) ReactElement -``` - -Render a React element in a document element. - -#### `renderToString` - -``` purescript -renderToString :: ReactElement -> String -``` - -Render a React element as a string. - diff --git a/example/.gitignore b/example/.gitignore deleted file mode 100644 index 012a3cd..0000000 --- a/example/.gitignore +++ /dev/null @@ -1 +0,0 @@ -index.js diff --git a/example/index.html b/example/index.html deleted file mode 100644 index 3c2916c..0000000 --- a/example/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - purescript-react example - - - - - - diff --git a/package.json b/package.json new file mode 100644 index 0000000..d6126cc --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "name": "purescript-react", + "files": [], + "peerDependencies": { + "react": "^0.14.3" + } +} diff --git a/src/React.js b/src/React.js index c35c5bc..2d42dd1 100644 --- a/src/React.js +++ b/src/React.js @@ -3,21 +3,25 @@ // module React -exports.getProps = function(ctx) { - return function() { - return ctx.props; - }; -}; +var React = require('react'); -exports.getRefs = function(ctx) { - return function() { - return ctx.refs; - }; -}; +function getProps(this_) { + return function(){ + return this_.props; + }; +} +exports.getProps = getProps; + +function getRefs(this_) { + return function(){ + return this_.refs; + }; +} +exports.getRefs = getRefs; -exports.getChildren = function(ctx) { - return function() { - var children = ctx.props.children; +function getChildren(this_) { + return function(){ + var children = this_.props.children; var result = []; @@ -27,115 +31,94 @@ exports.getChildren = function(ctx) { return result; }; -}; - -exports.writeState = function(ctx) { - return function(state) { - return function() { - ctx.replaceState({ - state: state - }); - return function() { - return state; - } - }; - }; -}; - -exports.readState = function(ctx) { - return function() { - return ctx.state.state; +} +exports.getChildren = getChildren; + +function writeState(this_) { + return function(state){ + return function(){ + this_.setState({ + state: state + }); + return state; }; -}; + }; +} +exports.writeState = writeState; -exports.createClass = function(ss) { - var result = {}; - for (var s in ss) { - if (ss.hasOwnProperty(s)) { - if (s === "displayName") { - result[s] = ss[s]; - } - else if (s === "componentWillReceiveProps") { - result[s] = (function(impl) { - return function(nextProps) { - return impl(this)(nextProps)(); - } - })(ss[s]); - } - else if (s === "shouldComponentUpdate") { - result[s] = (function(impl) { - return function(nextProps, nextState) { - return impl(this)(nextProps)(nextState.state)(); - } - })(ss[s]); - } - else if (s === "componentWillUpdate") { - result[s] = (function(impl) { - return function(nextProps, nextState) { - return impl(this)(nextProps)(nextState.state)(); - } - })(ss[s]); - } - else if (s === "componentDidUpdate") { - result[s] = (function(impl) { - return function(prevProps, prevState) { - return impl(this)(prevProps)(prevState.state)(); - } - })(ss[s]); - } - else { - result[s] = (function(impl) { - return function() { - return impl(this)(); - } - })(ss[s]); - } - } +function readState(this_) { + return function(){ + return this_.state.state; + }; +} +exports.readState = readState; + +function createClass(spec) { + var result = { + displayName: spec.displayName, + render: function(){ + return spec.render(this)(); + }, + getInitialState: function(){ + return { + state: spec.getInitialState(this)() + }; + }, + componentWillMount: function(){ + return spec.componentWillMount(this)(); + }, + componentDidMount: function(){ + return spec.componentDidMount(this)(); + }, + componentWillReceiveProps: function(nextProps){ + return spec.componentWillReceiveProps(this)(nextProps)(); + }, + shouldComponentUpdate: function(nextProps, nextState){ + return spec.shouldComponentUpdate(this)(nextProps)(nextState.state)(); + }, + componentWillUpdate: function(nextProps, nextState){ + return spec.componentWillUpdate(this)(nextProps)(nextState.state)(); + }, + componentDidUpdate: function(prevProps, prevState){ + return spec.componentDidUpdate(this)(prevProps)(prevState.state)(); + }, + componentWillUnmount: function(){ + return spec.componentWillUnmount(this)(); } - result.getInitialState = function() { - return { - state: ss.getInitialState(this)() - }; - }; - return React.createClass(result); -}; + }; -exports.handle = function(f) { - return function(e) { - return f(e)(); - }; + return React.createClass(result); +} +exports.createClass = createClass; + +function handle(f) { + return function(e){ + return f(e)(); + }; }; +exports.handle = handle; -function createElement(value) { - return function(props) { +function createElement(class_) { + return function(props){ return function(children){ - return React.createElement.apply(React, [value, props].concat(children)); + return React.createElement.apply(React, [class_, props].concat(children)); }; }; -}; +} exports.createElement = createElement; exports.createElementTagName = createElement; -function createElementDynamic(value) { +function createElementDynamic(class_) { return function(props) { return function(children){ - return React.createElement(value, props, children); + return React.createElement(class_, props, children); }; }; }; exports.createElementDynamic = createElementDynamic; exports.createElementTagNameDynamic = createElementDynamic; -exports.createFactory = function(clazz) { - return React.createFactory(clazz); -}; - -exports.render = function(element) { - return function(container) { - return function() { - return React.render(element, container); - } - }; -}; - -exports.renderToString = React.renderToString; +function createFactory(class_) { + return React.createFactory(class_); +} +exports.createFactory = createFactory; diff --git a/src/React.purs b/src/React.purs index 506706d..a5c63fb 100644 --- a/src/React.purs +++ b/src/React.purs @@ -2,6 +2,7 @@ module React ( ReactElement() + , ReactComponent() , ReactThis() , TagName() @@ -51,29 +52,29 @@ module React , handle , createClass + , createClassStateless , createElement , createElementDynamic , createElementTagName , createElementTagNameDynamic , createFactory - - , render - , renderToString ) where import Prelude (Unit(), ($), bind, pure, return, unit) -import DOM (DOM()) -import DOM.Node.Types (Element()) - import Control.Monad.Eff (Eff()) +import Unsafe.Coerce (unsafeCoerce) + -- | Name of a tag. type TagName = String -- | A virtual DOM node, or component. foreign import data ReactElement :: * +-- | A mounted react component +foreign import data ReactComponent :: * + -- | A reference to a component, essentially React's `this`. foreign import data ReactThis :: * -> * -> * @@ -291,6 +292,10 @@ transformState ctx f = do -- | Create a React class from a specification. foreign import createClass :: forall props state eff. ReactSpec props state eff -> ReactClass props +-- | Create a stateless React class. +createClassStateless :: forall props. (props -> ReactElement) -> ReactClass props +createClassStateless = unsafeCoerce + -- | Create an event handler. foreign import handle :: forall eff ev props state result. (ev -> EventHandlerContext eff props state result) -> EventHandler ev @@ -308,9 +313,3 @@ foreign import createElementTagNameDynamic :: forall props. TagName -> props -> -- | Create a factory from a React class. foreign import createFactory :: forall props. ReactClass props -> props -> ReactElement - --- | Render a React element in a document element. -foreign import render :: forall eff. ReactElement -> Element -> Eff (dom :: DOM | eff) ReactElement - --- | Render a React element as a string. -foreign import renderToString :: ReactElement -> String diff --git a/src/React/DOM/Props.js b/src/React/DOM/Props.js index f2a5eef..4edff94 100644 --- a/src/React/DOM/Props.js +++ b/src/React/DOM/Props.js @@ -3,43 +3,47 @@ // module React.DOM.Props -exports.unsafeMkProps = function(key) { - return function(value) { - var result = {}; - result[key] = value; - return result; - }; -}; +var React = require('react'); -exports.unsafeUnfoldProps = function(key) { - return function(value) { - var result = {}; - var props = {}; - props[key] = result; +function unsafeMkProps(key) { + return function(value){ + var result = {}; + result[key] = value; + return result; + }; +} +exports.unsafeMkProps = unsafeMkProps; - for (var subprop in value) { - if (value.hasOwnProperty(subprop)) { - result[subprop] = value[subprop]; - } - } +function unsafeUnfoldProps(key) { + return function(value){ + var result = {}; + var props = {}; + props[key] = result; - return props; - }; -}; + for (var subprop in value) { + if (value.hasOwnProperty(subprop)) { + result[subprop] = value[subprop]; + } + } + + return props; + }; +} +exports.unsafeUnfoldProps = unsafeUnfoldProps; function unsafeFromPropsArray(props) { - var result = {}; + var result = {}; - for (var i = 0, len = props.length; i < len; i++) { - var prop = props[i]; + for (var i = 0, len = props.length; i < len; i++) { + var prop = props[i]; - for (var key in prop) { - if (prop.hasOwnProperty(key)) { - result[key] = prop[key]; - } - } + for (var key in prop) { + if (prop.hasOwnProperty(key)) { + result[key] = prop[key]; + } } + } - return result; + return result; }; exports.unsafeFromPropsArray = unsafeFromPropsArray; diff --git a/test/Container.purs b/test/Container.purs deleted file mode 100644 index 3fbfb3d..0000000 --- a/test/Container.purs +++ /dev/null @@ -1,20 +0,0 @@ -module Test.Container where - -import Prelude - -import React - -import qualified React.DOM as D -import qualified React.DOM.Props as P - -container :: forall props. ReactClass props -container = createClass $ spec unit \ctx -> do - children <- getChildren ctx - - let ui = D.div [ P.style { borderColor: "red" - , borderWidth: 2 - , padding: 10 - } - ] children - - return ui diff --git a/test/Main.js b/test/Main.js deleted file mode 100644 index d78c8de..0000000 --- a/test/Main.js +++ /dev/null @@ -1,12 +0,0 @@ -/* global exports */ -"use strict"; - -// module Test.Main - -exports.interval = function(ms) { - return function(action) { - return function() { - return setInterval(action, ms); - } - } -}; diff --git a/test/Main.purs b/test/Main.purs deleted file mode 100644 index 32c266e..0000000 --- a/test/Main.purs +++ /dev/null @@ -1,79 +0,0 @@ -module Test.Main where - -import Prelude - -import Control.Monad.Eff -import Control.Monad.Eff.Console - -import Data.Maybe.Unsafe (fromJust) -import Data.Nullable (toMaybe) - -import DOM (DOM()) -import DOM.HTML (window) -import DOM.HTML.Document (body) -import DOM.HTML.Types (htmlElementToElement) -import DOM.HTML.Window (document) - -import DOM.Node.Types (Element()) - -import React - -import qualified React.DOM as D -import qualified React.DOM.Props as P - -import Test.Container (container) - -foreign import interval :: forall eff a. - Int -> - Eff eff a -> - Eff eff Unit - -hello :: forall props. ReactClass { name :: String | props } -hello = createClass $ spec unit \ctx -> do - props <- getProps ctx - return $ D.h1 [ P.className "Hello" - , P.style { background: "lightgray" } - ] - [ D.text "Hello, " - , D.text props.name - ] - -counter :: forall props. ReactClass props -counter = createClass counterSpec - where - counterSpec = (spec 0 render) - { componentDidMount = \ctx -> - interval 1000 $ do - val <- readState ctx - print val - } - - render ctx = do - val <- readState ctx - return $ D.button [ P.className "Counter" - , P.onClick \_ -> do - val <- readState ctx - writeState ctx (val + 1) - ] - [ D.text (show val) - , D.text " Click me to increment!" - ] - -main :: forall eff. Eff (dom :: DOM | eff) Unit -main = void (body' >>= render ui) - where - ui :: ReactElement - ui = D.div' [ createFactory hello { name: "World" } - , createFactory counter unit - , createElement container unit - [ D.p [] [ D.text "This is line one" ] - , D.p [] [ D.text "This is line two" ] - ] - ] - - body' :: Eff (dom :: DOM | eff) Element - body' = do - win <- window - doc <- document win - elm <- fromJust <$> toMaybe <$> body doc - return $ htmlElementToElement elm