Skip to content

Instantly share code, notes, and snippets.

@RedRoserade
Created March 16, 2015 22:17
Show Gist options
  • Save RedRoserade/9db4297b75027c8843c3 to your computer and use it in GitHub Desktop.
Save RedRoserade/9db4297b75027c8843c3 to your computer and use it in GitHub Desktop.
ASP.NET MVC ModelState validation and Flux. Based heavily off http://fluxxor.com/guides/quick-start.html.
/*global React, _, $, fluxxor*/
(function () {
var constants = {
ADD_TODO: "ADD_TODO",
TOGGLE_TODO: "TOGGLE_TODO",
CLEAR_TODOS: "CLEAR_TODOS"
};
var TodoStore = Fluxxor.createStore({
initialize: function() {
this.todos = [];
this.validationState = {};
this.bindActions(
constants.ADD_TODO, this.onAddTodo,
constants.TOGGLE_TODO, this.onToggleTodo,
constants.CLEAR_TODOS, this.onClearTodos
);
},
validate() {
this.__debouncedValidate = this.__debouncedValidate || _.debounce(() => {
$.ajax({
url: '/api/validations/todo',
type: 'post',
data: JSON.stringify({
todos: this.todos
}),
contentType: 'application/json;charset=utf-8'
})
.done(() => {
console.log('on done');
console.log(this);
this.validationState = {};
this.emit("change");
})
.fail((err) => {
this.validationState = JSON.parse(err.responseText).ModelState;
this.emit('change');
});
}, 1000);
this.__debouncedValidate();
},
onAddTodo: function(payload) {
this.todos.push(payload);
this.validate();
this.emit('change');
},
onToggleTodo: function(payload) {
payload.todo.complete = !payload.todo.complete;
this.validate();
this.emit("change");
},
onClearTodos: function() {
this.todos = this.todos.filter(function(todo) {
return !todo.complete;
});
this.validate();
this.emit("change");
},
getState: function() {
return {
todos: this.todos,
validationState: this.validationState
};
},
getValidationState() {
return this.validationState
}
});
var actions = {
addTodo: function(text) {
this.dispatch(constants.ADD_TODO, {text: text});
},
toggleTodo: function(todo) {
this.dispatch(constants.TOGGLE_TODO, {todo: todo});
},
clearTodos: function() {
this.dispatch(constants.CLEAR_TODOS);
}
};
var stores = {
TodoStore: new TodoStore()
};
var flux = new Fluxxor.Flux(stores, actions);
flux.on("dispatch", function(type, payload) {
if (console && console.log) {
console.log("[Dispatch]", type, payload);
}
});
var FluxMixin = Fluxxor.FluxMixin(React),
StoreWatchMixin = Fluxxor.StoreWatchMixin;
var ModelStateMixin = function (storeName) {
var store = flux.store(storeName);
this.validationMessageFor = (propName) => {
var state = store.getValidationState();
console.log(state);
return state[`model.${propName}`];
};
};
var Application = React.createClass({
mixins: [FluxMixin, StoreWatchMixin("TodoStore"), ModelStateMixin("TodoStore")],
getInitialState: function() {
return { newTodoText: "" };
},
getStateFromFlux: function() {
var flux = this.getFlux();
return flux.store("TodoStore").getState();
},
render: function() {
return (
<div>
<ul>
{this.state.todos.map(function(todo, i) {
return (
<li key={i}>
<TodoItem todo={todo} />
{this.validationMessageFor(`todos[${i}].text`)}
</li>
);
})}
</ul>
<form onSubmit={this.onSubmitForm}>
<input type="text" size="30" placeholder="New Todo"
value={this.state.newTodoText}
onChange={this.handleTodoTextChange} />
<input type="submit" value="Add Todo" />
</form>
<button onClick={this.clearCompletedTodos}>Clear Completed</button>
</div>
);
},
handleTodoTextChange: function(e) {
this.setState({newTodoText: e.target.value});
},
onSubmitForm: function(e) {
e.preventDefault();
this.getFlux().actions.addTodo(this.state.newTodoText);
this.setState({newTodoText: ""});
},
clearCompletedTodos: function(e) {
this.getFlux().actions.clearTodos();
}
});
var TodoItem = React.createClass({
mixins: [FluxMixin],
propTypes: {
todo: React.PropTypes.object.isRequired
},
render: function() {
var style = {
textDecoration: this.props.todo.complete ? "line-through" : ""
};
return <span style={style} onClick={this.onClick}>{this.props.todo.text}</span>;
},
onClick: function() {
this.getFlux().actions.toggleTodo(this.props.todo);
}
});
React.render(<Application flux={flux} />, document.getElementById("container"));
}());
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='Content-type' content='text/html; charset=utf-8'>
<title>Basic Example with JSX and ES6 features</title>
<link rel="stylesheet" href="../shared/css/base.css" />
</head>
<body>
<div id="container">
should not see me.
</div>
<script src="./Scripts/react.js"></script>
<script src="./Scripts/JSXTransformer.js"></script>
<script src="./Scripts/fluxxor.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.2/underscore-min.js"></script>
<script type="text/jsx;harmony=true" src="./Components/form.jsx">
</script>
</body>
</html>
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace ReactFluxModelState.Models
{
public class Todo
{
public Todo()
{
todos = new HashSet<TodoItem>();
}
public ICollection<TodoItem> todos { get; set; }
}
public class TodoItem
{
[Required(AllowEmptyStrings = false), RegularExpression("stuff")]
public string text { get; set; }
}
}
using ReactFluxModelState.Models;
using System.Web.Http;
using System.Web.Http.Cors;
namespace ReactFluxModelState.Controllers.ValidationControllers
{
[RoutePrefix("api/validations/todo")]
public class TodoController : ApiController
{
[HttpPost, Route(""), EnableCors("*", "*", "*")]
public IHttpActionResult Validate(Todo model)
{
if (ModelState.IsValid)
{
return Ok();
}
return BadRequest(ModelState);
}
}
[RoutePrefix("api/validations/todoitem")]
public class TodoItemController : ApiController
{
[HttpPost, Route(""), EnableCors("*", "*", "*")]
public IHttpActionResult Validate(TodoItem model)
{
if (ModelState.IsValid)
{
return Ok();
}
return BadRequest(ModelState);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment