Skip to content

Commit

Permalink
Merge branch 'feat/flux-firebase'
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcin Kumorek committed Mar 1, 2015
2 parents 40f0134 + dc901fe commit 334c53c
Show file tree
Hide file tree
Showing 20 changed files with 458 additions and 163 deletions.
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -12,6 +12,7 @@
"url": "https://github.com/qmmr/react-movies.git"
},
"dependencies": {
"firebase": "^2.2.1",
"flux": "^2.0.1",
"react": "^0.12.2",
"superagent": "^0.21.0"
Expand Down Expand Up @@ -42,6 +43,7 @@
"karma-sinon-chai": "^0.3.0",
"lodash": "^3.3.0",
"mocha": "^2.1.0",
"object-assign": "^2.0.0",
"pretty-hrtime": "^1.0.0",
"require-dir": "^0.1.0",
"sinon": "^1.12.2",
Expand Down
Expand Up @@ -10,6 +10,8 @@ import {
OMDB_ERROR
} from '../constants/actionTypes'

import { CHILD_ADDED, CHILD_REMOVED } from '../constants/firebaseTypes'

export default function createActionCreators(appDispatcher) {
return {
addFavoriteMovie(data) {
Expand Down Expand Up @@ -42,6 +44,14 @@ export default function createActionCreators(appDispatcher) {

handleOMDBError(data) {
appDispatcher.handleServerAction({ type: OMDB_ERROR, data })
},

childAdded(data) {
appDispatcher.handleServerAction({ type: CHILD_ADDED, data })
},

childRemoved(data) {
appDispatcher.handleServerAction({ type: CHILD_REMOVED, data })
}
}
}
16 changes: 7 additions & 9 deletions src/app.js
@@ -1,21 +1,19 @@
import React from 'react'
import App from './components/App.jsx'
import createActionCreators from './utils/createActionCreators'
import createFirebaseService from './utils/createFirebaseService'
import omdbService from './services/omdbService'
import createActionCreators from './actions/createActionCreators'
import getFirebaseService from './services/firebaseService'
import getOMDBService from './services/omdbService'
import AppDispatcher from './dispatcher/AppDispatcher'
import MoviesStore from './stores/MoviesStore'

var appDispatcher = new AppDispatcher()
var actions = createActionCreators(appDispatcher)
var favoriteMoviesFBSvc = createFirebaseService('https://favorite-movies.firebaseio.com/favorite-movies')
var viewContext = {
moviesStore: new MoviesStore(appDispatcher, favoriteMoviesFBSvc, omdbService(actions)),
actions
}
var firebaseService = getFirebaseService(actions)
var omdbService = getOMDBService(actions)
var moviesStore = new MoviesStore(appDispatcher, firebaseService, omdbService)

React.render(
React.withContext(viewContext, function() {
React.withContext({ moviesStore, actions }, function() {
return React.createFactory(App)(null)
}),
document.querySelector('#container')
Expand Down
5 changes: 1 addition & 4 deletions src/components/FavoriteMovies.jsx
Expand Up @@ -46,14 +46,11 @@ export default React.createClass({
)
},

_getSpinner() {
return <span>Loading...</span>
},

_getTableRows() {
return (
this.state.movies.map((movie, idx) => {
let { Title, Director, Actors, Year, firebaseKey, imdbID, imdbRating, imdbVotes } = movie

return (
<tr key={ idx }>
<td><a href={ 'http://www.imdb.com/title/' + imdbID } target='_blank'>{ Title }</a></td>
Expand Down
40 changes: 25 additions & 15 deletions src/components/MovieInfo.jsx
Expand Up @@ -10,7 +10,7 @@ export default React.createClass({
getInitialState() {
return {
movie: null,
loading: false
queryInProgress: false
}
},

Expand All @@ -23,7 +23,29 @@ export default React.createClass({
},

_getLoader() {
return this.state.loading ? <div className='alert alert-info'>Loading...</div> : null
let preloaderHTML = `
<rect x="0" y="10" width="4" height="10" fill="#333" opacity="0.2">
<animate attributeName="opacity" attributeType="XML" values="0.2; 1; .2" begin="0s" dur="0.6s" repeatCount="indefinite" />
<animate attributeName="height" attributeType="XML" values="10; 20; 10" begin="0s" dur="0.6s" repeatCount="indefinite" />
<animate attributeName="y" attributeType="XML" values="10; 5; 10" begin="0s" dur="0.6s" repeatCount="indefinite" />
</rect>
<rect x="8" y="10" width="4" height="10" fill="#333" opacity="0.2">
<animate attributeName="opacity" attributeType="XML" values="0.2; 1; .2" begin="0.15s" dur="0.6s" repeatCount="indefinite" />
<animate attributeName="height" attributeType="XML" values="10; 20; 10" begin="0.15s" dur="0.6s" repeatCount="indefinite" />
<animate attributeName="y" attributeType="XML" values="10; 5; 10" begin="0.15s" dur="0.6s" repeatCount="indefinite" />
</rect>
<rect x="16" y="10" width="4" height="10" fill="#333" opacity="0.2">
<animate attributeName="opacity" attributeType="XML" values="0.2; 1; .2" begin="0.3s" dur="0.6s" repeatCount="indefinite" />
<animate attributeName="height" attributeType="XML" values="10; 20; 10" begin="0.3s" dur="0.6s" repeatCount="indefinite" />
<animate attributeName="y" attributeType="XML" values="10; 5; 10" begin="0.3s" dur="0.6s" repeatCount="indefinite" />
</rect>`

return this.state.queryInProgress ? (
<div className="preloader">
<h3>Searching for movie...</h3>
<svg dangerouslySetInnerHTML={{ __html: preloaderHTML }}></svg>
</div>
) : null
},

_getMovieInfo() {
Expand Down Expand Up @@ -66,22 +88,10 @@ export default React.createClass({
this.actions.addFavoriteMovie(this.state.movie)
},

_addToWatchLaterMovies(e) {
e.preventDefault()
console.log('MARCIN :: _addToWatchLaterMovies ::')
this.actions.addWatchLaterMovie(this.state.movie)
},

_addToHateMovies(e) {
e.preventDefault()
console.log('MARCIN :: _addToHateMovies ::')
this.actions.addHateMovie(this.state.movie)
},

_onMoviesStoreChange() {
this.setState({
movie: this.store.getFoundMovie(),
loading: this.store.isLoading()
queryInProgress: this.store.isQueryInProgress()
})
}

Expand Down
10 changes: 5 additions & 5 deletions src/components/Search.jsx
Expand Up @@ -10,7 +10,7 @@ export default React.createClass({
getInitialState() {
return {
query: '',
loading: this.store.isLoading()
queryInProgress: this.store.isQueryInProgress()
}
},

Expand All @@ -21,7 +21,7 @@ export default React.createClass({
submitHandler(e) {
e.preventDefault()

if (!this.state.loading) {
if (!this.state.queryInProgress) {
this.actions.queryMovie(this.state.query.trim())
this.setState({ query: '' })
}
Expand All @@ -37,13 +37,13 @@ export default React.createClass({
type='text'
className='form-control'
placeholder='Search for a movie...'
disabled={ this.state.loading && 'disabled' }
disabled={ this.state.queryInProgress && 'disabled' }
onChange={ this._updateQuery }
value={ this.state.query } />
<span className='input-group-btn'>
<button
className='btn btn-primary'
disabled={ this.state.loading && 'disabled' }
disabled={ this.state.queryInProgress && 'disabled' }
type='submit'>
Search
</button>
Expand All @@ -61,7 +61,7 @@ export default React.createClass({
},

_onMoviesStoreChange() {
this.setState({ loading: this.store.isLoading() })
this.setState({ queryInProgress: this.store.isQueryInProgress() })
}

})
11 changes: 11 additions & 0 deletions src/constants/constants.js
@@ -0,0 +1,11 @@
const ROOT_FIREBASE_URL = 'https://favorite-movies.firebaseio.com'
const FAVORITE_MOVIES = 'favorite-movies'
const OMDB_URL = 'http://www.omdbapi.com/'
const MOVIE_NOT_FOUND = 'Movie not found!'

export default {
ROOT_FIREBASE_URL,
FAVORITE_MOVIES,
OMDB_URL,
MOVIE_NOT_FOUND
}
7 changes: 7 additions & 0 deletions src/constants/firebaseTypes.js
@@ -0,0 +1,7 @@
const CHILD_ADDED = 'child_added'
const CHILD_REMOVED = 'child_removed'

export default {
CHILD_ADDED,
CHILD_REMOVED
}
6 changes: 6 additions & 0 deletions src/constants/sourceTypes.js
@@ -0,0 +1,6 @@
import keyMirror from 'react/lib/keyMirror'

export default keyMirror({
VIEW_ACTION: null,
SERVER_ACTION: null
})
16 changes: 16 additions & 0 deletions src/css/main.css
@@ -1,3 +1,19 @@
.search {
margin-bottom: 1em;
}

.preloader{
margin: 2em auto;
text-align: center;
width: 100%;
}

.preloader svg {
width: 20px;
height: 30px;
}

svg path,
svg rect{
fill: #158CBA;
}
4 changes: 1 addition & 3 deletions src/dispatcher/AppDispatcher.js
@@ -1,7 +1,5 @@
import { Dispatcher } from 'flux'

const VIEW_ACTION = 'VIEW_ACTION'
const SERVER_ACTION = 'SERVER_ACTION'
import { VIEW_ACTION, SERVER_ACTION } from '../constants/sourceTypes'

export default class AppDispatcher extends Dispatcher {
constructor() {
Expand Down
2 changes: 0 additions & 2 deletions src/htdocs/index.html
Expand Up @@ -6,8 +6,6 @@
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<link rel="stylesheet" href="css/bootstrap-lumen.min.css">
<link rel="stylesheet" href="css/main.css">
<!-- Firebase -->
<script src="https://cdn.firebase.com/js/client/2.2.0/firebase.js"></script>
</head>
<body>
<div id="container" class="container"></div>
Expand Down
66 changes: 66 additions & 0 deletions src/services/firebaseService.js
@@ -0,0 +1,66 @@
import Firebase from 'firebase'
import { ROOT_FIREBASE_URL, FAVORITE_MOVIES } from '../constants/constants'
import { CHILD_ADDED } from '../constants/firebaseTypes'

function firebaseService(actions) {
var rootFirebaseRef = new Firebase(ROOT_FIREBASE_URL)
var favoriteMoviesFirebaseRef = rootFirebaseRef.child(FAVORITE_MOVIES)

return {

getRootFirebaseRef() {
return rootFirebaseRef
},

getFavoriteMoviesFirebaseRef() {
return favoriteMoviesFirebaseRef
},

addToFavoriteMovies(movie) {
return favoriteMoviesFirebaseRef.push(movie, (err) => {
if (err) {
console.error(err.message)
}
})
},

removeFromFavoriteMovies(key) {
let movieRef = favoriteMoviesFirebaseRef.child(key)
let errorHandler = (err) => {
if (err) {
console.error(err)
} else {
console.log(`movie ${ key } was removed`)
actions.childRemoved(key)
}
}

movieRef.remove(errorHandler)
},

on(type) {
switch(type) {
case CHILD_ADDED:
favoriteMoviesFirebaseRef.on(CHILD_ADDED, function childAdded(childSnapshot, prevChildKey) {
let movie = childSnapshot.val()
movie.firebaseKey = childSnapshot.key()
// TODO: how to postpone the actions while dealing with firebase?
setTimeout(() => actions.childAdded(movie), 0)
})
break;
}
}

}
}

export default firebaseService
// _listenToFavoriteMoviesSvc() {

// }

// _removeFromFavoriteMovies(key) {
// this._favoriteMovies = this._favoriteMovies.filter((movie) => movie.firebaseKey !== key)
// // FIXME: should send another action to the system?
// this.emitChange()
// }
9 changes: 4 additions & 5 deletions src/services/omdbService.js
@@ -1,10 +1,8 @@
import { OMDB_URL } from '../constants/constants'
import request from 'superagent'
import parseJSON from '../utils/parseJSON'

export default function createOmdbService(actions) {
const OMDB_URL = 'http://www.omdbapi.com/'
const MOVIE_NOT_FOUND = 'Movie not found!'

export default function omdbService(actions) {
var handleResponse = (resp) => {
let data = parseJSON(resp.text)

Expand All @@ -26,7 +24,8 @@ export default function createOmdbService(actions) {
return {

queryMovie(title) {
return request.get(OMDB_URL)
return request
.get(OMDB_URL)
.query({ t: title, plot: 'short', r: 'json' })
.accept('application/json')
.on('error', handleOMDBError)
Expand Down

0 comments on commit 334c53c

Please sign in to comment.