`, it's bound to null and assigned to the `onClick` event
346 | prop of the mailbox list item.
347 |
348 | The repetition (Passing things again and again) rather violates DRY, but it's
349 | not hard to follow after an initial look through the code.
350 |
--------------------------------------------------------------------------------
/mailbox/img/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryolabs/react-examples/362fa371bbcefcec133800a7df7028b418d2dc73/mailbox/img/screenshot.png
--------------------------------------------------------------------------------
/mailbox/img/structure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryolabs/react-examples/362fa371bbcefcec133800a7df7028b418d2dc73/mailbox/img/structure.png
--------------------------------------------------------------------------------
/mailbox/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Email Client
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/mailbox/run_server.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | PY_VERSION=`python -c 'import sys; print("%i" % (sys.hexversion<0x03000000))'`
4 |
5 | if [ $PY_VERSION -eq 0 ]; then
6 | python -m http.server
7 | else
8 | python -m SimpleHTTPServer
9 | fi
10 |
--------------------------------------------------------------------------------
/mailbox/scripts.js:
--------------------------------------------------------------------------------
1 | var Email = React.createClass({
2 | render: function() {
3 | return (
4 |
5 |
6 | From
7 | {this.props.from}
8 |
9 | To
10 | {this.props.to}
11 |
12 | Subject
13 | {this.props.subject}
14 |
15 |
16 |
17 | );
18 | }
19 | });
20 |
21 | var EmailListItem = React.createClass({
22 | render: function() {
23 | return (
24 |
25 | {this.props.subject}
26 | {this.props.from}
27 | {this.props.to}
28 |
29 | );
30 | }
31 | });
32 |
33 | var EmailList = React.createClass({
34 | render: function() {
35 | var email_list = this.props.emails.map(function(mail) {
36 | return (
37 |
42 | );
43 | }.bind(this));
44 |
45 | return (
46 |
47 |
48 |
49 | Subject
50 | From
51 | To
52 |
53 |
54 |
55 | {email_list}
56 |
57 |
58 | );
59 | }
60 | });
61 |
62 | var NoneSelected = React.createClass({
63 | render: function() {
64 | return (
65 |
66 | No {this.props.text} selected.
67 |
68 | );
69 | }
70 | });
71 |
72 | var Mailbox = React.createClass({
73 | getInitialState: function(){
74 | return { email_id: null };
75 | },
76 |
77 | handleSelectEmail: function(id) {
78 | this.setState({ email_id: id });
79 | },
80 |
81 | render: function() {
82 | var email_id = this.state.email_id;
83 | if (email_id) {
84 | var mail = this.props.emails.filter(function(mail) {
85 | return mail.id == email_id;
86 | })[0];
87 | selected_email = ;
92 | } else {
93 | selected_email = ;
94 | }
95 |
96 | return (
97 |
98 |
100 |
101 | {selected_email}
102 |
103 |
104 | );
105 | }
106 | });
107 |
108 | var MailboxList = React.createClass({
109 | render: function() {
110 | var mailbox_list = this.props.mailboxes.map(function(mailbox) {
111 | return (
112 |
115 |
116 | {mailbox.emails.length}
117 |
118 | {mailbox.name}
119 |
120 | );
121 | }.bind(this));
122 |
123 | return (
124 |
125 |
126 | {mailbox_list}
127 |
128 |
129 | );
130 | }
131 | });
132 |
133 | var App = React.createClass({
134 | getInitialState: function(){
135 | return { mailbox_id: null };
136 | },
137 |
138 | handleSelectMailbox: function(id) {
139 | this.setState({ mailbox_id: id });
140 | },
141 |
142 | render: function() {
143 | var mailbox_id = this.state.mailbox_id;
144 | if (mailbox_id) {
145 | var mailbox = this.props.mailboxes.filter(function(mailbox) {
146 | return mailbox.id == mailbox_id;
147 | })[0];
148 | selected_mailbox = ;
150 | } else {
151 | selected_mailbox = ;
152 | }
153 |
154 | return (
155 |
156 |
158 |
159 |
160 |
161 | {selected_mailbox}
162 |
163 |
164 |
165 |
166 | );
167 | }
168 | });
169 |
170 | var fixtures = [
171 | {
172 | id: 1,
173 | name: "Inbox",
174 | emails: [
175 | {
176 | id: 1,
177 | from: "joe@tryolabs.com",
178 | to: "fernando@tryolabs.com",
179 | subject: "Meeting",
180 | body: "hi"
181 | },
182 | {
183 | id: 2,
184 | from: "newsbot@tryolabs.com",
185 | to: "fernando@tryolabs.com",
186 | subject: "News Digest",
187 | body: "Intro to React "
188 | }
189 | ]
190 | },
191 | {
192 | id: 2,
193 | name: "Spam",
194 | emails: [
195 | {
196 | id: 3,
197 | from: "nigerian.prince@gmail.com",
198 | to: "fernando@tryolabs.com",
199 | subject: "Obivous 419 scam",
200 | body: "You've won the prize!!!1!1!!!"
201 | }
202 | ]
203 | }
204 | ];
205 |
206 | React.render(
207 | ,
208 | document.body
209 | );
210 |
--------------------------------------------------------------------------------
/mailbox/style.css:
--------------------------------------------------------------------------------
1 | .mailboxes {
2 | margin: 25px auto;
3 | width: 120px;
4 | }
5 |
6 | .mailbox {
7 | margin-top: 25px;
8 | }
9 |
10 | .email-viewer {
11 | padding: 25px;
12 | }
13 |
14 | .none-selected {
15 | margin: 20px;
16 | padding: 20px;
17 | font-size: 1.2em;
18 | }
19 |
--------------------------------------------------------------------------------
/modal/README.md:
--------------------------------------------------------------------------------
1 | # Modal
2 |
3 | This is an example of creating a reusable modal component with React that
4 | supports CSS animations.
5 |
6 | It would be fairly easy to create a specific modal component that is mounted and
7 | unmounted by toggling a bit of state in the parent component. However, what we
8 | want is a little more complex:
9 |
10 | * We want a reusable modal component, that you just wrap around the modal's
11 | contents, add some props, and it works, so you don't have to create a new
12 | component for each modal in your app.
13 |
14 | * Animation support: We want to be able to write some CSS selectors that
15 | correspond to the various stages of the modal's lifecycle.
16 |
17 | ## Components
18 |
19 | First, we have to use React's
20 | [CSS Transition Group component](https://facebook.github.io/react/docs/animation.html):
21 |
22 | ```javascript
23 | var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
24 | ```
25 |
26 | Next, we create the modal. This component automates the following:
27 |
28 | * It determines whether or not it should be shown using a prop.
29 | * It wraps its contents in a CSS Transition Group.
30 |
31 | ```javascript
32 | var Modal = React.createClass({
33 | render: function() {
34 | if(this.props.isOpen){
35 | return (
36 |
37 |
38 | {this.props.children}
39 |
40 |
41 | );
42 | } else {
43 | return ;
44 | }
45 | }
46 | });
47 | ```
48 |
49 | Note how we use `this.props.children` to extract the component's body.
50 |
51 | Because React requires that the render function return a component, rather than
52 | returning `null` when the modal is not open, we return an empty transition
53 | group.
54 |
55 | Now, for this example, we'll create an `App` component to hold the state that
56 | tells React whether the modal is open, and a couple of methods to open and close
57 | it.
58 |
59 | ```javascript
60 | var App = React.createClass({
61 | getInitialState: function() {
62 | return { isModalOpen: false };
63 | },
64 |
65 | openModal: function() {
66 | this.setState({ isModalOpen: true });
67 | },
68 |
69 | closeModal: function() {
70 | this.setState({ isModalOpen: false });
71 | },
72 |
73 | render: function() {
74 | return (
75 |
76 |
App
77 |
Open modal
78 |
80 | My Modal
81 |
82 |
This is the modal's body.
83 |
84 | Close modal
85 |
86 |
87 | );
88 | }
89 | });
90 | ```
91 |
92 | As you can see in `render`, all we had to do was wrap the contents of the modal
93 | in the `Modal` component, pass the state that determines whether its open, and
94 | give the CSS transition a name (We'll use this later). Inside the body, we make
95 | insert a call to the `closeModal` method. There's no need to bind anything.
96 |
97 | Finally, we render the `App` component, and this concludes the JavaScript part
98 | of this example:
99 |
100 | ```javascript
101 | React.render(
102 | ,
103 | document.body
104 | );
105 | ```
106 |
107 | ## Style
108 |
109 | Most of the stylesheet is not important to this example, it's just giving the
110 | app and modal components a shape. The important part is the CSS animation
111 | classes that React will use.
112 |
113 | The `enter` selector sets the style for the component's initial state before
114 | animation begins, and `enter-active` sets the final state:
115 |
116 | ```css
117 | .modal-anim-enter {
118 | opacity: 0.00;
119 | transform: scale(0.7);
120 | transition: all 0.2s;
121 | }
122 |
123 | .modal-anim-enter.modal-anim-enter-active {
124 | opacity: 1;
125 | transform: scale(1);
126 | }
127 | ```
128 |
129 | `leave` and `leave-active` are the opposite:
130 |
131 | ```css
132 | .modal-anim-leave {
133 | opacity: 1;
134 | transform: scale(1);
135 | transition: all 0.2s;
136 | }
137 |
138 | .modal-anim-leave.modal-anim-leave-active {
139 | opacity: 0.00;
140 | transform: scale(0.7);
141 | }
142 | ```
143 |
--------------------------------------------------------------------------------
/modal/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Modal
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/modal/run_server.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | PY_VERSION=`python -c 'import sys; print("%i" % (sys.hexversion<0x03000000))'`
4 |
5 | if [ $PY_VERSION -eq 0 ]; then
6 | python -m http.server
7 | else
8 | python -m SimpleHTTPServer
9 | fi
10 |
--------------------------------------------------------------------------------
/modal/scripts.js:
--------------------------------------------------------------------------------
1 | var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
2 |
3 | var Modal = React.createClass({
4 | render: function() {
5 | if(this.props.isOpen){
6 | return (
7 |
8 |
9 | {this.props.children}
10 |
11 |
12 | );
13 | } else {
14 | return ;
15 | }
16 | }
17 | });
18 |
19 | var App = React.createClass({
20 | getInitialState: function() {
21 | return { isModalOpen: false };
22 | },
23 |
24 | openModal: function() {
25 | this.setState({ isModalOpen: true });
26 | },
27 |
28 | closeModal: function() {
29 | this.setState({ isModalOpen: false });
30 | },
31 |
32 | render: function() {
33 | return (
34 |
35 |
App
36 |
Open modal
37 |
39 | My Modal
40 |
41 |
This is the modal's body.
42 |
43 | Close modal
44 |
45 |
46 | );
47 | }
48 | });
49 |
50 | React.render(
51 | ,
52 | document.body
53 | );
54 |
--------------------------------------------------------------------------------
/modal/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
3 | margin: 0;
4 |
5 | min-height: 100vh;
6 |
7 | display: flex;
8 | align-items: center;
9 | }
10 |
11 | .app {
12 | width: 300px;
13 | margin: 0 auto;
14 | padding: 20px;
15 |
16 | border: 1px solid #ccc;
17 | border-radius: 4px;
18 |
19 | z-index: 0;
20 | }
21 |
22 | h1, h3 {
23 | text-align: center;
24 | margin: 0 0 20px 0;
25 | }
26 |
27 | .modal {
28 | width: 200px;
29 | height: 100px;
30 | padding: 20px;
31 |
32 | position: absolute;
33 | z-index: 1;
34 | top: 50%;
35 | left: 50%;
36 | margin: -70px 0 0 -120px;
37 |
38 | border: 1px solid #ccc;
39 | background: white;
40 | }
41 |
42 | button {
43 | display: block;
44 | margin: 0 auto;
45 | padding: 6px 12px;
46 |
47 | border: 1px solid #ccc;
48 | border-radius: 4px;
49 | background: white;
50 |
51 | color: #0066CC;
52 | font-weight: bold;
53 |
54 | font-size: 14px;
55 | line-height: 20px;
56 | }
57 |
58 | /* Animation */
59 |
60 | .modal-anim-enter {
61 | opacity: 0.00;
62 | transform: scale(0.7);
63 | transition: all 0.2s;
64 | }
65 |
66 | .modal-anim-enter.modal-anim-enter-active {
67 | opacity: 1;
68 | transform: scale(1);
69 | }
70 |
71 | .modal-anim-leave {
72 | opacity: 1;
73 | transform: scale(1);
74 | transition: all 0.2s;
75 | }
76 |
77 | .modal-anim-leave.modal-anim-leave-active {
78 | opacity: 0.00;
79 | transform: scale(0.7);
80 | }
81 |
--------------------------------------------------------------------------------
/nuclear-test/.gitignore:
--------------------------------------------------------------------------------
1 | build/main.js
2 | build/vendors.js
3 | node_modules/*
4 |
5 |
--------------------------------------------------------------------------------
/nuclear-test/build/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NuclearJs Test
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/nuclear-test/client/actions.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import reactor from './reactor';
4 | import axios from 'axios';
5 |
6 |
7 | var actionsNames = [
8 | 'CONTACT_SELECTED',
9 |
10 | 'CONTACTS_REQUESTED',
11 | 'CONTACTS_RECEIVED',
12 | 'CONTACTS_RECEIVE_FAILED',
13 |
14 | 'CONTACT_ADD_REQUESTED',
15 | 'CONTACT_ADDED',
16 | 'CONTACT_ADD_FAILED',
17 |
18 | 'CONTACT_DELETE_REQUESTED',
19 | 'CONTACT_DELETED',
20 | 'CONTACT_DELETE_FAILED',
21 |
22 | 'CONTACT_FAV_REQUESTED',
23 | 'CONTACT_FAVED',
24 | 'CONTACT_FAV_FAILED',
25 |
26 | 'CONTACT_UNFAV_REQUESTED',
27 | 'CONTACT_UNFAVED',
28 | 'CONTACT_UNFAV_FAILED',
29 |
30 | 'MESSAGE_DISMISSED'
31 | ];
32 |
33 |
34 | var Actions = {};
35 |
36 | for(let actionName of actionsNames) {
37 | Actions[actionName] = actionName;
38 | }
39 |
40 | function dispatch(action, params) {
41 | reactor.dispatch(action, params);
42 | }
43 |
44 |
45 | class ActionCreators {
46 | static getContacts() {
47 | axios.get('http://localhost:3000/contacts')
48 | .then(response => dispatch(Actions.CONTACTS_RECEIVED, response.data))
49 | .catch(error => dispatch(Actions.CONTACTS_RECEIVE_FAILED, error));
50 | dispatch(Actions.CONTACTS_REQUESTED);
51 | }
52 |
53 | static addContact(contact) {
54 | axios.post('http://localhost:3000/contacts', contact)
55 | .then(response => dispatch(Actions.CONTACT_ADDED, response.data))
56 | .catch(error => dispatch(Actions.CONTACT_ADD_FAILED, error));
57 | dispatch(Actions.CONTACT_ADD_REQUESTED, contact.toJS());
58 | }
59 |
60 | static deleteContact(contact) {
61 | axios.delete(`http://localhost:3000/contacts/${contact.get('id')}`)
62 | .then(response => dispatch(Actions.CONTACT_DELETED, contact.toJS()))
63 | .catch(error => dispatch(Actions.CONTACT_DELETE_FAILED, contact.toJS()));
64 | dispatch(Actions.CONTACT_DELETE_REQUESTED, contact.toJS());
65 | }
66 |
67 | static favContact(contact) {
68 | axios.patch(`http://localhost:3000/contacts/${contact.get('id')}`,
69 | {fav: true})
70 | .then(response => dispatch(Actions.CONTACT_FAVED, response.data))
71 | .catch(error => dispatch(Actions.CONTACT_FAV_FAILED, contact.toJS()));
72 | dispatch(Actions.CONTACT_FAV_REQUESTED, contact.toJS());
73 | }
74 |
75 | static unfavContact(contact) {
76 | axios.patch(`http://localhost:3000/contacts/${contact.get('id')}`,
77 | {fav: false})
78 | .then(response => dispatch(Actions.CONTACT_UNFAVED, response.data))
79 | .catch(error => dispatch(Actions.CONTACT_UNFAV_FAILED, contact.toJS()));
80 | dispatch(Actions.CONTACT_UNFAV_REQUESTED, contact.toJS());
81 | }
82 |
83 | static selectContact(contactId) {
84 | dispatch(Actions.CONTACT_SELECTED, contactId);
85 | }
86 |
87 | static dismissMessage(userAction) {
88 | dispatch(Actions.MESSAGE_DISMISSED, userAction);
89 | }
90 | };
91 |
92 |
93 | export {
94 | Actions,
95 | ActionCreators
96 | };
97 |
--------------------------------------------------------------------------------
/nuclear-test/client/components/app.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | import React from 'react';
4 | import Router from 'react-router';
5 | import Mixin from 'react-mixin';
6 | import cx from 'classnames';
7 |
8 | import reactor from '../reactor';
9 | import {messageGetter} from '../getters';
10 | import {ActionCreators} from '../actions';
11 |
12 | import 'bootstrap-sass-loader';
13 |
14 |
15 | class App extends React.Component {
16 | getDataBindings() {
17 | return {
18 | message: messageGetter
19 | };
20 | }
21 |
22 | handleCloseMessage() {
23 | ActionCreators.dismissMessage(true);
24 | clearTimeout(this.dismissedId);
25 | }
26 |
27 | render () {
28 | let contactsPageActive = this.isActive('contacts') || this.isActive('contact') || this.isActive('new-contact');
29 |
30 | let message;
31 | if(this.state.message) {
32 | message = (
33 |
38 |
40 | ×
41 |
42 | {this.state.message.get('message')}
43 |
44 | );
45 | if(this.state.message.get('timeout')) {
46 | this.dismissedId = setTimeout(
47 | () => ActionCreators.dismissMessage(false),
48 | this.state.message.get('timeout') * 1000);
49 | }
50 | }
51 | return (
52 |
53 |
54 | {message}
55 |
56 |
57 |
58 |
59 |
60 | NuclearJs Test
61 |
62 |
63 |
64 |
65 |
66 |
67 |
71 |
72 | Contacts
73 |
74 |
75 |
76 |
77 | Favorites
78 |
79 |
80 |
81 |
82 |
83 |
84 | );
85 | }
86 | }
87 |
88 | Mixin.onClass(App, Router.State);
89 | Mixin.onClass(App, reactor.ReactMixin);
90 |
91 | export default App;
92 |
--------------------------------------------------------------------------------
/nuclear-test/client/components/contact.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 | import {toImmutable} from 'nuclear-js';
5 | import cn from 'classnames';
6 |
7 |
8 | class Contact extends React.Component {
9 | render() {
10 | const defaultContact = {
11 | fav: false,
12 | name: '',
13 | email: '',
14 | address: '',
15 | phone: ''
16 | }
17 | let contact = this.props.contact || toImmutable(defaultContact);
18 |
19 | const dataFields = ['email', 'address', 'phone'];
20 |
21 | let rows = [];
22 | for(let field of dataFields) {
23 | rows.push({field} {contact.get(field)} );
24 | }
25 |
26 | return (
27 |
28 |
29 |
30 |
32 |
33 |
34 |
35 |
36 |
37 |
{contact.get('name')}
38 |
39 |
40 |
45 |
46 | );
47 | }
48 | }
49 |
50 |
51 | export default Contact;
52 |
--------------------------------------------------------------------------------
/nuclear-test/client/components/contacts.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 | import Router from 'react-router';
5 | import cn from 'classnames';
6 |
7 |
8 | class ContactListItem extends React.Component {
9 | render() {
10 | let contact = this.props.contact;
11 |
12 | return (
13 |
14 |
15 |
16 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 |
27 |
28 | {contact.get('name')}
29 |
30 |
31 |
32 |
33 | );
34 | }
35 |
36 | handleDelete() {
37 | this.props.onDelete(this.props.contact);
38 | }
39 |
40 | handleFav() {
41 | let contact = this.props.contact;
42 | this.props.onFav(contact, !contact.get('fav'));
43 | }
44 | }
45 |
46 |
47 | export default ContactListItem;
48 |
--------------------------------------------------------------------------------
/nuclear-test/client/components/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import App from './app';
4 | import ContactListItem from './contacts';
5 | import Contact from './contact';
6 | import List from './list';
7 |
8 |
9 | export {
10 | App,
11 | ContactListItem,
12 | Contact,
13 | List
14 | };
15 |
--------------------------------------------------------------------------------
/nuclear-test/client/components/list.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 |
5 |
6 | class List extends React.Component {
7 | render() {
8 | return (
9 |
10 | {this.props.items}
11 |
12 | );
13 | }
14 | }
15 |
16 |
17 | export default List;
18 |
--------------------------------------------------------------------------------
/nuclear-test/client/getters.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const contactsGetter = [
4 | ['contacts'],
5 | contacts => contacts.toList()
6 | ];
7 |
8 | const currentContactGetter = [
9 | ['contacts'],
10 | ['currentContact'],
11 | (contacts, id) => id ? contacts.get(id.toString()) : null
12 | ];
13 |
14 | const favoritesGetter = [
15 | ['contacts'],
16 | contacts => contacts.toList().filter(contact => contact.get('fav'))
17 | ];
18 |
19 | const messageGetter = [
20 | ['message'],
21 | message => message
22 | ];
23 |
24 |
25 | export {
26 | contactsGetter,
27 | currentContactGetter,
28 | favoritesGetter,
29 | messageGetter
30 | };
31 |
--------------------------------------------------------------------------------
/nuclear-test/client/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 | import Router from 'react-router';
5 |
6 | import routes from './routes';
7 |
8 | import './stylesheets/app.css';
9 |
10 |
11 | Router.run(routes, Router.HashLocation, (Root) => {
12 | React.render( , document.body);
13 | });
14 |
--------------------------------------------------------------------------------
/nuclear-test/client/pages/contact.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 | import Mixin from 'react-mixin';
5 |
6 | import {Contact} from '../components/';
7 | import reactor from '../reactor';
8 | import {currentContactGetter} from '../getters';
9 | import {ActionCreators} from '../actions';
10 |
11 |
12 | class ContactPage extends React.Component {
13 | getDataBindings() {
14 | return {
15 | contact: currentContactGetter
16 | };
17 | }
18 |
19 | static willTransitionTo(transition, params, query) {
20 | ActionCreators.selectContact(parseInt(params.contactId));
21 | ActionCreators.getContacts();
22 | }
23 |
24 | render() {
25 | return (
26 |
31 | );
32 | }
33 | }
34 |
35 | Mixin.onClass(ContactPage, reactor.ReactMixin);
36 |
37 |
38 | export default ContactPage;
39 |
--------------------------------------------------------------------------------
/nuclear-test/client/pages/contacts.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 | import Router from 'react-router';
5 | import Mixin from 'react-mixin';
6 | import cx from 'classnames';
7 |
8 | import {ContactListItem, List} from '../components/';
9 | import reactor from '../reactor';
10 | import {contactsGetter} from '../getters';
11 | import {ActionCreators} from '../actions';
12 |
13 |
14 | class ContactsPage extends React.Component {
15 | getDataBindings() {
16 | return {
17 | contacts: contactsGetter
18 | };
19 | }
20 |
21 | static willTransitionTo(transition, params, query) {
22 | ActionCreators.getContacts();
23 | }
24 |
25 | render() {
26 | let items = this.state.contacts.toArray().map(contact =>
27 | );
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | }
42 |
43 | handleDelete(contact) {
44 | ActionCreators.deleteContact(contact);
45 | }
46 |
47 | handleFav(contact, fav) {
48 | if (fav) {
49 | ActionCreators.favContact(contact);
50 | } else {
51 | ActionCreators.unfavContact(contact);
52 | }
53 |
54 | }
55 | }
56 |
57 | Mixin.onClass(ContactsPage, reactor.ReactMixin);
58 |
59 |
60 | export default ContactsPage;
61 |
--------------------------------------------------------------------------------
/nuclear-test/client/pages/favorites.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 | import Router from 'react-router';
5 | import Mixin from 'react-mixin';
6 | import cx from 'classnames';
7 |
8 | import {ContactListItem, List} from '../components/';
9 | import reactor from '../reactor';
10 | import {favoritesGetter} from '../getters';
11 | import {ActionCreators} from '../actions';
12 |
13 |
14 | class FavoritesPage extends React.Component {
15 | getDataBindings() {
16 | return {
17 | contacts: favoritesGetter
18 | };
19 | }
20 |
21 | static willTransitionTo(transition, params, query) {
22 | ActionCreators.getContacts();
23 | }
24 |
25 | render() {
26 | let items = this.state.contacts.toArray().map(contact =>
27 | );
29 | return (
30 |
31 |
32 |
33 | );
34 | }
35 |
36 | handleFav(contact, fav) {
37 | if (fav) {
38 | ActionCreators.favContact(contact);
39 | } else {
40 | ActionCreators.unfavContact(contact);
41 | }
42 |
43 | }
44 | }
45 |
46 | Mixin.onClass(FavoritesPage, reactor.ReactMixin);
47 |
48 |
49 | export default FavoritesPage;
50 |
--------------------------------------------------------------------------------
/nuclear-test/client/pages/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import ContactsPage from './contacts';
4 | import ContactPage from './contact';
5 | import NewContactPage from './newcontact';
6 | import FavoritesPage from './favorites';
7 |
8 | export {
9 | ContactsPage,
10 | ContactPage,
11 | NewContactPage,
12 | FavoritesPage
13 | };
14 |
--------------------------------------------------------------------------------
/nuclear-test/client/pages/newcontact.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 | import Addons from 'react/addons';
5 | import Router from 'react-router';
6 | import Mixin from 'react-mixin';
7 |
8 | import {ActionCreators} from '../actions';
9 |
10 |
11 | class NewContactPage extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | name: '',
16 | phone: '',
17 | email: '',
18 | address: ''
19 | };
20 | }
21 |
22 | render() {
23 | return (
24 |
57 | );
58 | }
59 |
60 | handleSubmit(e) {
61 | e.preventDefault();
62 | let contact = {
63 | name: this.state.name,
64 | fav: false,
65 | email: this.state.email,
66 | address: this.state.address,
67 | phone: this.state.phone
68 | };
69 | ActionCreators.addContact(contact);
70 | this.transitionTo('contacts');
71 | }
72 | }
73 |
74 | Mixin.onClass(NewContactPage, Addons.addons.LinkedStateMixin);
75 | Mixin.onClass(NewContactPage, Router.Navigation);
76 |
77 |
78 | export default NewContactPage;
79 |
--------------------------------------------------------------------------------
/nuclear-test/client/reactor.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import Nuclear from 'nuclear-js';
4 |
5 | import {ContactStore, CurrentContactStore, MessageStore} from './stores/';
6 |
7 |
8 | const reactor = new Nuclear.Reactor({debug: true});
9 |
10 | reactor.registerStores({
11 | contacts: new ContactStore(),
12 | currentContact: new CurrentContactStore(),
13 | message: new MessageStore()
14 | });
15 |
16 | export default reactor;
17 |
--------------------------------------------------------------------------------
/nuclear-test/client/routes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 | import Router from 'react-router';
5 |
6 | import {App} from './components/';
7 | import {ContactsPage, ContactPage, NewContactPage, FavoritesPage} from './pages/';
8 |
9 |
10 | // declare our routes and their hierarchy
11 | export default (
12 |
13 |
14 |
15 |
16 |
18 |
20 |
21 |
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/nuclear-test/client/stores/contacts.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import {Store, toImmutable} from 'nuclear-js';
4 |
5 | import {Actions} from '../actions';
6 |
7 |
8 | class ContactStore extends Store {
9 | getInitialState() {
10 | return toImmutable({});
11 | }
12 |
13 | initialize() {
14 | this.on(Actions.CONTACTS_RECEIVED, this.contactsReceived);
15 | this.on(Actions.CONTACT_ADDED, this.contactAdded);
16 | this.on(Actions.CONTACT_DELETED, this.contactDeleted);
17 | this.on(Actions.CONTACT_FAVED, this.contactFaved);
18 | this.on(Actions.CONTACT_UNFAVED, this.contactFaved);
19 | }
20 |
21 | contactsReceived(state, contacts) {
22 | let contactsMap = {};
23 | for(let contact of contacts) {
24 | contactsMap[contact.id] = contact;
25 | }
26 | return toImmutable(contactsMap);
27 | }
28 |
29 | contactAdded(state, contact) {
30 | let map = {};
31 | map[contact.id] = contact;
32 | return state.merge(toImmutable(map));
33 | }
34 |
35 | contactDeleted(state, contact) {
36 | return state.delete(contact.id);
37 | }
38 |
39 | contactFaved(state, contact) {
40 | let map = {};
41 | map[contact.id] = contact;
42 | return state.merge(toImmutable(map));
43 | }
44 | }
45 |
46 |
47 | class CurrentContactStore extends Store {
48 | getInitialState() {
49 | return null;
50 | }
51 |
52 | initialize() {
53 | this.on(Actions.CONTACT_SELECTED, this.contactSelected);
54 | }
55 |
56 | contactSelected(state, contactId) {
57 | return contactId;
58 | }
59 | }
60 |
61 |
62 | export {
63 | ContactStore,
64 | CurrentContactStore
65 | };
66 |
--------------------------------------------------------------------------------
/nuclear-test/client/stores/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import {ContactStore, CurrentContactStore} from './contacts';
4 | import MessageStore from './messages';
5 |
6 | export {
7 | ContactStore,
8 | CurrentContactStore,
9 | MessageStore
10 | };
11 |
--------------------------------------------------------------------------------
/nuclear-test/client/stores/messages.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import {Store, toImmutable} from 'nuclear-js';
4 |
5 | import {Actions} from '../actions';
6 |
7 |
8 | class MessageStore extends Store {
9 | getInitialState() {
10 | return null;
11 | }
12 |
13 | initialize() {
14 | this.on(Actions.CONTACTS_REQUESTED, this.contactsRequested);
15 | this.on(Actions.CONTACTS_RECEIVED, this.contactsReceived);
16 | this.on(Actions.CONTACTS_RECEIVE_FAILED, this.contactsReceiveFailed);
17 |
18 | this.on(Actions.CONTACT_ADD_REQUESTED, this.contactAddRequested);
19 | this.on(Actions.CONTACT_ADDED, this.contactAdded);
20 | this.on(Actions.CONTACT_ADD_FAILED, this.contactAddFailed);
21 |
22 | this.on(Actions.CONTACT_DELETE_REQUESTED, this.contactDeleteRequested);
23 | this.on(Actions.CONTACT_DELETED, this.contactDeleted);
24 | this.on(Actions.CONTACT_DELETE_FAILED, this.contactDeleteFailed);
25 |
26 | this.on(Actions.CONTACT_FAV_REQUESTED, this.contactFavRequested);
27 | this.on(Actions.CONTACT_FAVED, this.contactFaved);
28 | this.on(Actions.CONTACT_FAV_FAILED, this.contactFavFailed);
29 |
30 | this.on(Actions.CONTACT_UNFAV_REQUESTED, this.contactUnfavRequested);
31 | this.on(Actions.CONTACT_UNFAVED, this.contactUnfaved);
32 | this.on(Actions.CONTACT_UNFAV_FAILED, this.contactUnfavFailed);
33 |
34 | this.on(Actions.MESSAGE_DISMISSED, this.messageDismissed);
35 | }
36 |
37 | contactsRequested(state) {
38 | return toImmutable({
39 | type: 'info',
40 | message: 'Loading contacts...',
41 | });
42 | }
43 |
44 | contactsReceived(state, contacts) {
45 | return null;
46 | }
47 |
48 | contactsReceiveFailed(state, error) {
49 | return toImmutable({
50 | type: 'error',
51 | message: 'Fail to load contacts',
52 | timeout: 5
53 | });
54 | }
55 |
56 | contactAddRequested(state, contact) {
57 | return toImmutable({
58 | type: 'info',
59 | message: `Adding ${contact.name}`
60 | });
61 | }
62 |
63 | contactAdded(state, contact) {
64 | return toImmutable({
65 | type: 'success',
66 | message: 'Contact added',
67 | timeout: 5
68 | });
69 | }
70 |
71 | contactAddFailed(state, error) {
72 | return toImmutable({
73 | type: 'error',
74 | message: 'Failed to add contact',
75 | timeout: 5
76 | });
77 | }
78 |
79 | contactDeleteRequested(state, contact) {
80 | return toImmutable({
81 | type: 'info',
82 | message: `Deleting ${contact.name}`
83 | });
84 | }
85 |
86 | contactDeleted(state, contact) {
87 | return toImmutable({
88 | type: 'success',
89 | message: 'Contact deleted',
90 | timeout: 5
91 | });
92 | }
93 |
94 | contactDeleteFailed(state, error) {
95 | return toImmutable({
96 | type: 'error',
97 | message: 'Failed to delete contact',
98 | timeout: 5
99 | });
100 | }
101 |
102 | contactFavRequested(state, contact) {
103 | return toImmutable({
104 | type: 'info',
105 | message: `Faving ${contact.name}`
106 | });
107 | }
108 |
109 | contactFaved(state, contact) {
110 | return toImmutable({
111 | type: 'success',
112 | message: `${contact.name} is a favorite`,
113 | timeout: 5
114 | });
115 | }
116 |
117 | contactFavFailed(state, error) {
118 | return toImmutable({
119 | type: 'error',
120 | message: 'Failed to fav',
121 | timeout: 5
122 | });
123 | }
124 |
125 | contactUnfavRequested(state, contact) {
126 | return toImmutable({
127 | type: 'info',
128 | message: `Unfaving ${contact.name}`
129 | });
130 | }
131 |
132 | contactUnfaved(state, contact) {
133 | return toImmutable({
134 | type: 'success',
135 | message: `${contact.name} is not a favorite`,
136 | timeout: 5
137 | });
138 | }
139 |
140 | contactUnfavFailed(state, error) {
141 | return toImmutable({
142 | type: 'error',
143 | message: 'Failed to unfav',
144 | timeout: 5
145 | });
146 | }
147 |
148 | messageDismissed(state) {
149 | return null;
150 | }
151 | }
152 |
153 |
154 | export default MessageStore;
155 |
--------------------------------------------------------------------------------
/nuclear-test/client/stylesheets/app.css:
--------------------------------------------------------------------------------
1 | .icon {
2 | padding-right: 10px;
3 | }
4 |
5 | .messages {
6 | position: fixed;
7 | width: 40%;
8 | top: 10px;
9 | left: 30%;
10 | z-index: 9999;
11 | text-align: center;
12 | }
13 |
14 | .messages .alert {
15 | display: inline-block;
16 | }
17 |
18 | .messages .close {
19 | margin-left: 10px;
20 | }
21 |
--------------------------------------------------------------------------------
/nuclear-test/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "contacts": [
3 | {
4 | "id": 5,
5 | "fav": true,
6 | "name": "Chelsey Dietrich",
7 | "email": "Lucio_Hettinger@annie.ca",
8 | "address": "Skiles Walks Suite 351, Roscoeview",
9 | "phone": "(254)954-1289"
10 | },
11 | {
12 | "id": 6,
13 | "fav": true,
14 | "name": "Mrs. Dennis Schulist",
15 | "email": "Karley_Dach@jasper.info",
16 | "address": "Norberto Crossing Apt. 950, South Christy",
17 | "phone": "1-477-935-8478 x6430"
18 | },
19 | {
20 | "id": 8,
21 | "fav": false,
22 | "name": "Nicholas Runolfsdottir V",
23 | "email": "Sherwood@rosamond.me",
24 | "address": "Ellsworth Summit Suite 729, Aliyaview",
25 | "phone": "586.493.6943 x140"
26 | },
27 | {
28 | "id": 9,
29 | "fav": false,
30 | "name": "Glenna Reichert",
31 | "email": "Chaim_McDermott@dana.io",
32 | "address": "Dayna Park Suite 449, Bartholomebury",
33 | "phone": "(775)976-6794 x41206"
34 | },
35 | {
36 | "id": 10,
37 | "fav": true,
38 | "name": "Clementina DuBuque",
39 | "email": "Rey.Padberg@karina.biz",
40 | "address": "Kattie Turnpike Suite 198, Lebsackbury",
41 | "phone": "024-648-3804"
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/nuclear-test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nuclear-test",
3 | "version": "0.0.0",
4 | "description": "A nuclearjs test",
5 | "main": "client/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "$(npm bin)/webpack",
9 | "run": "sh run.sh"
10 | },
11 | "author": "Diego Gadola ",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "axios": "^0.5.4",
15 | "babel-core": "^5.6.15",
16 | "babel-loader": "^5.2.2",
17 | "bootstrap": "^3.3.5",
18 | "bootstrap-sass": "^3.3.5",
19 | "bootstrap-sass-loader": "^1.0.4",
20 | "bootstrap-webpack": "0.0.3",
21 | "classnames": "^2.1.3",
22 | "css-loader": "^0.15.1",
23 | "file-loader": "^0.8.4",
24 | "jquery": "^2.1.4",
25 | "json-server": "^0.7.25",
26 | "node-libs-browser": "^0.5.2",
27 | "node-sass": "^3.2.0",
28 | "nuclear-js": "^1.0.5",
29 | "react": "^0.13.3",
30 | "react-mixin": "^1.5.0",
31 | "react-router": "^0.13.3",
32 | "sass-loader": "^1.0.2",
33 | "style-loader": "^0.12.3",
34 | "url-loader": "^0.5.6",
35 | "webpack": "^1.10.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/nuclear-test/readme.md:
--------------------------------------------------------------------------------
1 | ```
2 | npm install
3 | npm run build
4 | npm run run
5 | ```
6 |
7 | Go to [http://localhost:8000][localhost8000] on your browser.
8 |
9 | [localhost8000]: http://localhost:8000 'localhost'
10 |
--------------------------------------------------------------------------------
/nuclear-test/run.sh:
--------------------------------------------------------------------------------
1 | echo 'Starting contacts API ...'
2 | $(npm bin)/json-server -w db.json &> /dev/null &
3 | export JS_PID=$!
4 | echo 'API started'
5 | echo 'Starting frontend server ...'
6 | cd build
7 | python -m SimpleHTTPServer &> /dev/null &
8 | export HTTP_PID=$!
9 | echo 'frontend started'
10 | trap 'kill -2 $JS_PID && kill -9 $HTTP_PID && echo API and frontend stopped.' SIGINT
11 | trap 'kill -9 $JS_PID && kill -9 $HTTP_PID && echo API and frontend stopped.' SIGKILL
12 | wait
13 |
--------------------------------------------------------------------------------
/nuclear-test/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var webpack = require('webpack');
4 | var path = require('path');
5 |
6 |
7 | var sassPaths = [
8 | 'client/stylesheets/',
9 | ]
10 | .map(function(m){ return path.resolve(__dirname, m); })
11 | .join('&includePaths[]=');
12 |
13 | module.exports = {
14 | context: __dirname + '/client',
15 | entry: {
16 | main: "./index.js",
17 | vendors: ['react', 'react-router', 'nuclear-js', 'jquery', 'classnames', 'react-mixin', 'axios']
18 | },
19 |
20 | output: {
21 | path: "./build",
22 | filename: "[name].js"
23 | },
24 |
25 | resolve: {
26 | modulesDirectories: [ 'node_modules' ],
27 | extensions: ['', '.js', '.jsx'],
28 | },
29 |
30 | plugins: [
31 | new webpack.optimize.DedupePlugin(),
32 | new webpack.optimize.CommonsChunkPlugin({
33 | name:'vendors'
34 | }),
35 | new webpack.ProvidePlugin({
36 | $: 'jquery',
37 | jQuery: 'jquery',
38 | 'window.jQuery': 'jquery'
39 | })
40 | ],
41 |
42 | module: {
43 | loaders: [
44 | { test: /\.css$/, loader: "style!css" },
45 | { test: /\.scss$/, loader: "style!css!sass?includePaths[]=" + sassPaths},
46 | { test: /\.jsx$|\.js$/, loader: 'babel'},
47 | { test: /\.woff$/, loader: "url-loader?limit=10000&mimetype=application/font-woff" },
48 | { test: /\.ttf$/, loader: "file-loader" },
49 | { test: /\.eot$/, loader: "file-loader" },
50 | { test: /\.svg$/, loader: "file-loader" }
51 | ]
52 | }
53 | }
54 |
--------------------------------------------------------------------------------