Create a gist now

Instantly share code, notes, and snippets.

React/Fluxxor in Rails Example
:javascript
$(document).ready(function() {
window.loadIngredientSuggestionsEditor(#{@ingredients});
});
%h1 Edit ingredient suggestions
#js-ingredient-suggestions-editor
/** @jsx React.DOM */
window.loadIngredientSuggestionsEditor = function(ingredients) {
/* DEFINE THE FLUXXOR STORE */
var fluxIngredientSuggestionsStore = {};
fluxIngredientSuggestionsStore.constants = {
UPDATE_INGREDIENT: "UPDATE_INGREDIENT",
DELETE_INGREDIENT: "DELETE_INGREDIENT",
};
fluxIngredientSuggestionsStore.store = Fluxxor.createStore({
initialize: function(options) {
/* We'll have ingredients */
this.ingredients = options.ingredients || [];
/* Those ingredients can be updated and deleted */
this.bindActions(fluxIngredientSuggestionsStore.constants.UPDATE_INGREDIENT, this.onUpdateIngredient, fluxIngredientSuggestionsStore.constants.DELETE_INGREDIENT, this.onDeleteIngredient);
},
getState: function() {
/* If someone asks the store what the ingredients are, show them */
return {
ingredients: this.ingredients,
};
},
onUpdateIngredient: function(payload) {
/* Update the model if an ingredient is renamed */
payload.ingredient.item = payload.new_name;
this.emit("change")
},
onDeleteIngredient: function(payload) {
/* Update the model if an ingredient is deleted */
this.ingredients = this.ingredients.filter(function(ingredient) {
return ingredient.id != payload.ingredient.id
});
this.emit("change");
}
});
fluxIngredientSuggestionsStore.actions = {
updateIngredient: function(ingredient, new_name) {
/* First, update the model by calling the function above */
this.dispatch(fluxIngredientSuggestionsStore.constants.UPDATE_INGREDIENT, {
ingredient: ingredient,
new_name: new_name
});
/* Then, update the server and show a success message */
$.ajax({
type: "PUT",
url: "/ingredient_suggestions/" + ingredient.id,
data: {
item: new_name
},
success: function() {
$.growl.notice({
title: "Ingredient suggestion updated",
});
},
failure: function() {
$.growl.error({
title: "Error updating ingredient suggestion",
});
}
});
},
deleteIngredient: function(ingredient) {
/* First, update the model by calling the function above */
this.dispatch(fluxIngredientSuggestionsStore.constants.DELETE_INGREDIENT, {
ingredient: ingredient
});
/* Then, delete it on the server and show a success message */
$.ajax({
type: "DELETE",
url: "/ingredient_suggestions/" + ingredient.id,
success: function(data) {
$.growl.notice({
title: "Ingredient suggestion deleted",
});
}.bind(this),
failure: function() {
$.growl.error({
title: "Error deleting ingredient suggestion",
});
}
});
}
};
/* Initalize the Fluxxor store when needed */
fluxIngredientSuggestionsStore.init = function(ingredients) {
var tempStore = {
IngredientSuggestionsStore: new fluxIngredientSuggestionsStore.store({
ingredients: ingredients
})
};
fluxIngredientSuggestionsStore.flux = new Fluxxor.Flux(tempStore, fluxIngredientSuggestionsStore.actions);
}
/* DEFINE REACT COMPONENTS */
var IngredientSuggestionsEditor = React.createClass({
/* Update this component when the Fluxxor store is updated */
mixins: [FluxMixin, StoreWatchMixin("IngredientSuggestionsStore")],
/* Get the ingredients list from the store */
getStateFromFlux: function() {
var flux = this.getFlux();
return {
ingredients: flux.store("IngredientSuggestionsStore").getState().ingredients
};
},
/* Show each ingredient when the IngredientSuggestion component */
render: function() {
var props = this.props;
var ingredients = this.state.ingredients.map(function (ingredient) {
return <IngredientSuggestion ingredient={ingredient} key={ingredient.id} flux={props.flux} />
});
return (
<div>
{ingredients}
</div>
);
}
});
var IngredientSuggestion = React.createClass({
/* We need this mixin since we are calling Flux store actions from this component */
mixins: [FluxMixin],
/* We'll track two things for each ingredient, whether the user has changed its name and whether they have saved the update to the server. */
getInitialState: function() {
return {changed: false, updated: false};
},
render: function() {
return (
<div>
<a href="#" onClick={this.handleDelete}><i className="fa fa-times"></i></a>
<input onChange={this.handleChange} ref="ingredient" defaultValue={this.props.ingredient.item}/>
{/* Show the Update and Cancel buttons only if the user has changed the ingredient name */
this.state.changed ?
<span>
<a href="#" onClick={this.handleUpdate}>Update</a>
<a href="#" onClick={this.handleCancelChange}>Cancel</a>
</span>
:
""
}
</div>
)
},
handleChange: function() {
/* If the user changed the ingredient name, set the 'changed' state to true */
if ($(this.refs.ingredient.getDOMNode()).val() != this.props.ingredient.item) {
this.setState({changed: true});
} else {
this.setState({changed: false});
}
},
handleUpdate: function(e) {
/* Update the ingredient name in the Fluxxor store */
e.preventDefault();
this.getFlux().actions.updateIngredient(this.props.ingredient, $(this.refs.ingredient.getDOMNode()).val());
this.setState({changed: false, updated: true});
},
handleDelete: function(e) {
/* Delete the ingredient from the Fluxxor store */
e.preventDefault();
if (confirm("Delete " + this.props.ingredient.item + "?")) {
this.getFlux().actions.deleteIngredient(this.props.ingredient);
}
},
handleCancelChange: function(e) {
e.preventDefault();
$(this.refs.ingredient.getDOMNode()).val(this.props.ingredient.item);
this.setState({changed: false});
}
});
/* LOAD FLUXXOR STORE AND RENDER REACT COMPONENTS TO THE PAGE */
fluxIngredientSuggestionsStore.init(ingredients);
React.renderComponent(<IngredientSuggestionsEditor flux={fluxIngredientSuggestionsStore.flux} />,
document.getElementById('js-ingredient-suggestions-editor'));
}
class IngredientSuggestionsController < ApplicationController
def index
@ingredients = IngredientSuggestion.select("id, item").order("item ASC").to_json
end
def update
ingredient = IngredientSuggestion.find(params[:id])
ingredient.item = params[:item]
ingredient.save!
render :nothing => true, :status => 200
end
def destroy
ingredient = IngredientSuggestion.find(params[:id])
ingredient.destroy
render :nothing => true, :status => 200
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment