Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return VTree from main #25

Merged
merged 12 commits into from
Mar 16, 2016
41 changes: 41 additions & 0 deletions ElmAppWrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use strict';
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can use the same wrapper for both platforms all the time, yeah. There was a reason why we didn't do this straight in the beginning, though: usually bigger React Native apps have differences between iOS and Android versions. There are components that only work on iOS and vice versa. So in the long run, we should figure out a way to support this, maybe simply by having a MainAndroid.elm and MainIos.elm, which would be compiled separately to e.g. elm.android.js and elm.ios.js.

var React = require('react-native');
var Elm = require('./elm');
var {
AppRegistry,
StyleSheet,
Text,
View,
Image,
} = React;


function componentFactory() {
return React.createClass({
componentWillMount() {
Elm.embedReact(Elm.Main, this);
},
getInitialState() {
return {
_elmVTree: React.createElement(View, {}, []),
};
},
render() {
var vtree = this.state._elmVTree;
console.log('react app rendering vtree: ', vtree);
return React.createElement(View, {style: styles.container},
vtree
);
},
})
}

var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});

module.exports = componentFactory;
79 changes: 45 additions & 34 deletions Main.elm
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import ReactNative.NativeApp as NativeApp
import ReactNative.Style as Style exposing ( defaultTransform )


-- "main"
port viewTree : Signal Json.Encode.Value
port viewTree =
NativeApp.start { model = model, view = view, update = update, init = init }
app =
NativeApp.start { model = model, view = view, update = update }

main =
app

-- port tasks : Signal (Task.Task Never ())
-- port tasks =
-- app.tasks

type alias Model = Int

Expand All @@ -24,24 +28,33 @@ model = 9000
view : Signal.Address Action -> Model -> RN.VTree
view address count =
RN.view
[ Style.alignItems "center"
[ RN.style [ Style.alignItems "center" ]
]
[ RN.image
[ Style.height 64
, Style.width 64
, Style.marginBottom 30
[ RN.style
[ Style.height 64
, Style.width 64
, Style.marginBottom 30
]
, RN.imageSource "https://raw.githubusercontent.com/futurice/spiceprogram/master/assets/img/logo/chilicorn_no_text-128.png"
]
[
]
"https://raw.githubusercontent.com/futurice/spiceprogram/master/assets/img/logo/chilicorn_no_text-128.png"

, RN.text
[ Style.textAlign "center"
, Style.marginBottom 30
[ RN.style
[ Style.textAlign "center"
, Style.marginBottom 30
]
]
[ RN.string ("Counter: " ++ toString count)
]
Nothing
("Counter: " ++ toString count)
, RN.view
[ Style.width 80
, Style.flexDirection "row"
, Style.justifyContent "space-between"
[ RN.style
[ Style.width 80
, Style.flexDirection "row"
, Style.justifyContent "space-between"
]
]
[ button address Decrement "#d33" "-"
, button address Increment "#3d3" "+"
Expand All @@ -62,22 +75,20 @@ update action model =
button : Signal.Address Action -> Action -> String -> String -> RN.VTree
button address action color content =
RN.text
[ Style.color "white"
, Style.textAlign "center"
, Style.backgroundColor color
, Style.paddingTop 5
, Style.paddingBottom 5
, Style.width 30
, Style.fontWeight "bold"
, Style.shadowColor "#000"
, Style.shadowOpacity 0.25
, Style.shadowOffset 1 1
, Style.shadowRadius 5
, Style.transform { defaultTransform | rotate = Just "10deg" }
[ RN.style
[ Style.color "white"
, Style.textAlign "center"
, Style.backgroundColor color
, Style.paddingTop 5
, Style.paddingBottom 5
, Style.width 30
, Style.fontWeight "bold"
, Style.shadowColor "#000"
, Style.shadowOpacity 0.25
, Style.shadowOffset 1 1
, Style.shadowRadius 5
, Style.transform { defaultTransform | rotate = Just "10deg" }
]
, RN.onPress address action
]
(Just <| RN.onPress address action)
content


-- for the first vtree
port init : Signal ()
[ RN.string content ]
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ No.

## Get it running


### Caution: Experimental software!

The newest version of Elm Native UI depends on

- [modified Elm compiler](https://github.com/NoRedInk/elm-compiler/tree/elm-native-ui) ([ZIP](https://github.com/NoRedInk/elm-compiler/archive/elm-native-ui.zip)) &mdash; Must be on your `PATH` before the standard elm-compiler. You will need to [build the compiler from source](https://github.com/NoRedInk/elm-compiler/tree/elm-native-ui#build-from-source--contribute) yourself for now.

- [modified elm-core](https://github.com/yusefnapora/core/tree/elm-native-ui) ([ZIP](https://github.com/yusefnapora/core/archive/elm-native-ui.zip)) &mdash; Must replace the `elm-stuff/packages/elm-lang/core/3.0.0` directory in your project.

The modified compiler will allow our React Native "Virtual Tree", or `VTree` for short, to pass through `main`, just like `Html` from elm-html does.

The modified elm-core adds a function to enable rendering for the VTree type.

_If you clone the repositories yourself, make sure you checkout the elm-native-ui branch on each of them before using._


### Actually getting it running

Install React Native following [their guide](https://facebook.github.io/react-native/docs/getting-started.html#content). Check that you can create a new project with `react-native init AwesomeProject` and try running it on a real or virtual device.

Once that's out of the way, clone this repository and in the directory:
Expand Down
91 changes: 75 additions & 16 deletions ReactNative/Native/ReactNative.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,95 @@ Elm.Native.ReactNative.make = function(localRuntime) {
return localRuntime.Native.ReactNative.values;
}

var List = Elm.Native.List.make(localRuntime);
var Json = Elm.Native.Json.make(localRuntime);
var Signal = Elm.Native.Signal.make(localRuntime);
var React = require('react-native');

var prepareReset = true;
var eventHandlerCount = 0;
localRuntime.ports._ReactNativeEventHandlers = {};

function on(decoder, createMessage) {
if(prepareReset){
eventHandlerCount = 0;
localRuntime.ports._ReactNativeEventHandlers = {};
}

function nativeEventHandler(decoder, createMessage) {
function eventHandler(event) {
var value = A2(Json.runDecoderValue, decoder, event);
if (value.ctor === 'Ok') {
Signal.sendMessage(createMessage(value._0));
}
}
localRuntime.ports._ReactNativeEventHandlers[++eventHandlerCount] = eventHandler;
prepareReset = false;
return eventHandlerCount;
return eventHandler;
}

function vtreeToReactElement(vtree) {
switch (vtree.ctor) {
case 'VString':
{
return vtree._0;
}
case 'VNode':
{
var tagName = vtree._0;
var propertyList = vtree._1;
var childNodes = vtree._2;

var reactClass = React[tagName];
var props = propertyListToObject(propertyList);
var children = List.toArray(childNodes).map(vtreeToReactElement);

var args = [reactClass, props].concat(children);
return React.createElement.apply(null, args);
}
default:
throw new Error("I don't know how to render a VTree of type '" + vtree.ctor + "'\n" +
"If you've recently added a new type of VTree, you must add a new case to\n" +
"the switch statement in Native.ReactNative.vtreeToReactElement");
}
}

Elm.Native.ReactNative.prepareResetHandlers = function () {
prepareReset = true;
function propertyToObject(property) {
if (property.ctor !== 'JsonProperty' &&
property.ctor !== 'NativeProperty') {
throw new Error("I don't know how to handle a Property of type '" + property.ctor + "'\n" +
"If you've recently added a new type of Property, you must edit the\n" +
"function Native.ReactNative.propertyToObject");
}

return {
key: property._0,
value: property._1,
};
}

function propertyListToObject(list)
{
var object = {};
while (list.ctor !== '[]')
{
var entry = propertyToObject(list._0);
object[entry.key] = entry.value;
list = list._1;
}
return object;
}

function render(vtree) {
return vtreeToReactElement(vtree);
}

function setReactVTree(reactElement, vtree) {
var newState = Object.assign({},
reactElement.state,
{_elmVTree: vtreeToReactElement(vtree)}
);

reactElement.setState(newState);
}

function updateAndReplace(containerElement, oldVTree, newVTree) {
setReactVTree(containerElement, newVTree);
}


localRuntime.Native.ReactNative.values = {
on: F2(on),
render: render,
updateAndReplace: updateAndReplace,
nativeEventHandler: F2(nativeEventHandler),
};
return localRuntime.Native.ReactNative.values;
};
5 changes: 1 addition & 4 deletions ReactNative/NativeApp.elm
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ type alias Config model action =
{ model : model
, view : Signal.Address action -> model -> RN.VTree
, update : action -> model -> model
, init : Signal ()
}


start : Config model action -> Signal Json.Encode.Value
start : Config model action -> Signal RN.VTree
start config =
let
actions =
Expand All @@ -23,7 +22,6 @@ start config =
merged =
Signal.mergeMany
[ Signal.map ConfigAction actions.signal
, Signal.map (always Init) config.init
]

address =
Expand All @@ -50,4 +48,3 @@ start config =
in
model
|> Signal.map (config.view address)
|> Signal.map RN.encode
Loading