RecipePad

In this example below you will see how to do a RecipePad with some HTML / CSS and Javascript

CRUD functionality in react.js, writing to local storage.

Thumbnail
This awesome code was written by 0damian, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright 0damian ©

Technologies

  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>RecipePad</title>
  
  
  
      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  <div id="app"></div>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.min.js'></script>

  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/0damian/recipepad-LRdwWv */
body {
  background: #e5e5e5;
  font-family: "Roboto Slab", sans-serif;
}
body .container {
  box-sizing: border-box;
  width: 66.66666666%;
  margin: 2em auto;
  padding: 2em;
  position: relative;
  background: #f5f5f5;
}
body .container h1 {
  display: table;
  margin: auto;
  padding-bottom: 0.25em;
  border-bottom: 0.1em solid black;
}
body h2 {
  cursor: pointer;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
body div.form-container {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.6);
}
body form {
  padding: 2em;
  border: 0.1em solid black;
  position: fixed;
  top: 10%;
  left: 25%;
  width: 50%;
  z-index: 1;
  background: #f5f5f5;
}
body form label, body form input {
  display: block;
}
body form input, body form textarea {
  margin: auto auto 2em;
  line-height: 2;
  padding: auto 0.5em;
  width: 100%;
  font-size: 0.8em;
}
body button {
  background: #e5e5e5;
  border: 2px solid black;
  width: auto;
  font-size: 1em;
  margin-right: 1em;
}
body button:hover {
  border: 2px solid blue;
  cursor: pointer;
  background: blue;
  color: #e5e5e5;
}
body button.delete:hover {
  border: 2px solid red;
  background: red;
}
body button.new {
  margin-top: 1em;
}

@media screen and (max-width: 48em) {
  body {
    padding: 1em;
  }
  body .container {
    width: 95%;
  }
  body form {
    left: 2.5%;
    width: 80%;
  }
}


/*Downloaded from https://www.codeseek.co/0damian/recipepad-LRdwWv */
var RecipesContainer = React.createClass({
  displayName: 'RecipesContainer',

  getInitialState: function getInitialState() {
    var availableData = storage ? JSON.parse(storage.getItem('_0damian_data')) : seedData;
    return {
      data: availableData,
      formVisibility: 0
    };
  },
  handleRecipeSubmit: function handleRecipeSubmit(data) {
    var newId = this.state.data.length + 1;
    var newRecipe = {
      id: newId,
      active: 1,
      title: data.title,
      ingredients: data.ingredients,
      instructions: data.instructions
    };
    var newData = this.state.data;
    newData.push(newRecipe);
    if (storage) storage.setItem('_0damian_data', JSON.stringify(newData));
    this.setState({
      data: newData,
      formVisibility: 0
    });
  },
  handleRecipeTransfer: function handleRecipeTransfer(newData) {
    var unwrappedData = newData.data.data;
    for (var i = 0; i < this.state.data.length; i++) {
      if (this.state.data[i].id == unwrappedData.id.replace(/f/, '')) {
        var editData = this.state.data;
        editData[i].title = unwrappedData.title;
        editData[i].ingredients = unwrappedData.ingredients;
        editData[i].instructions = unwrappedData.instructions;
        if (storage) storage.setItem('_0damian_data', JSON.stringify(editData));
        this.setState({
          data: editData
        });
      }
    }
  },
  handleRecipeCancel: function handleRecipeCancel(data) {
    this.setState({
      formVisibility: 0
    });
  },
  handleRecipeDelete: function handleRecipeDelete(data) {
    for (var j = 0; j < this.state.data.length; j++) {
      if (this.state.data[j].id == data.data.id) {
        var newData = this.state.data;
        newData[j].active = 0;
        if (storage) storage.setItem('_0damian_data', JSON.stringify(newData));
        this.setState({
          data: newData
        });
      }
    }
  },
  toggleForm: function toggleForm(e) {
    e.preventDefault();
    if (this.state.formVisibility) {
      this.setState({
        formVisibility: 0
      });
    } else {
      this.setState({
        formVisibility: 1
      });
    }
  },
  render: function render() {
    return React.createElement(
      'main',
      { className: 'container' },
      React.createElement(
        'h1',
        null,
        'RecipePad'
      ),
      React.createElement(RecipeList, {
        data: this.state.data,
        onRecipeTransfer: this.handleRecipeTransfer,
        onRecipeDelete: this.handleRecipeDelete
      }),
      React.createElement(
        'button',
        {
          className: 'new',
          onClick: this.toggleForm
        },
        String.fromCharCode(43) + ' New Recipe'
      ),
      React.createElement(RecipeForm, {
        title: 'Add your recipe title',
        ingredients: 'Ingredients, separated, by, commas',
        instructions: 'Add your preparation instructions here',
        display: this.state.formVisibility ? { display: 'block' } : { display: 'none' },
        onRecipeSubmit: this.handleRecipeSubmit,
        onRecipeCancel: this.handleRecipeCancel
      })
    );
  }
});

var RecipeList = React.createClass({
  displayName: 'RecipeList',

  handleRecipeTransfer: function handleRecipeTransfer(data) {
    this.props.onRecipeTransfer({
      data: data
    });
  },
  handleRecipeCancel: function handleRecipeCancel(data) {
    this.props.onRecipeCancel({
      data: data
    });
  },
  handleRecipeDelete: function handleRecipeDelete(data) {
    this.props.onRecipeDelete({
      data: data
    });
  },
  render: function render() {
    var recipeNodes = this.props.data.map(function (recipe) {
      if (!recipe.active) return;
      return React.createElement(
        'div',
        { className: 'recipe' },
        React.createElement(Recipe, {
          title: recipe.title,
          ingredients: recipe.ingredients,
          instructions: recipe.instructions,
          key: recipe.id,
          id: recipe.id,
          onRecipeTransfer: this.handleRecipeTransfer,
          onRecipeCancel: this.handleRecipeCancel,
          onRecipeDelete: this.handleRecipeDelete
        })
      );
    }, this);
    return React.createElement(
      'div',
      { className: 'list' },
      recipeNodes
    );
  }
});

var Recipe = React.createClass({
  displayName: 'Recipe',

  getInitialState: function getInitialState() {
    return {
      formVisibility: 0,
      ingredientsVisibility: 0,
      active: 1
    };
  },
  toggleIngredients: function toggleIngredients(e) {
    e.preventDefault();
    if (this.state.ingredientsVisibility) {
      this.setState({
        ingredientsVisibility: 0
      });
    } else {
      this.setState({
        ingredientsVisibility: 1
      });
    }
  },
  toggleForm: function toggleForm(e) {
    e.preventDefault();
    if (this.state.formVisibility) {
      this.setState({
        formVisibility: 0
      });
    } else {
      this.setState({
        formVisibility: 1
      });
    }
  },
  handleRecipeTransfer: function handleRecipeTransfer(data) {
    this.setState({
      formVisibility: 0
    }), this.props.onRecipeTransfer({
      data: data
    });
  },
  handleRecipeCancel: function handleRecipeCancel(data) {
    if (data.cancel && data.source != 'f0') {
      this.setState({
        formVisibility: 0
      });
    } else {
      this.props.onCancelTransfer({
        data: data
      });
    }
  },

  handleRecipeDelete: function handleRecipeDelete(e) {
    e.preventDefault();
    this.props.onRecipeDelete({
      id: this.props.id
    });
  },
  render: function render() {
    var ingredientNodes = this.props.ingredients.map(function (ingredient) {
      return React.createElement(
        'li',
        null,
        ingredient
      );
    });
    var ingredientsList = String(this.props.ingredients).replace(/\,/g, ', ');
    return React.createElement(
      'div',
      { className: 'recipe' },
      React.createElement(
        'h2',
        {
          onClick: this.toggleIngredients
        },
        this.props.title
      ),
      React.createElement(
        'div',
        {
          className: 'recipe-body',
          style: this.state.ingredientsVisibility ? { display: 'block' } : { display: 'none' }
        },
        React.createElement(
          'ul',
          null,
          ingredientNodes
        ),
        React.createElement(
          'p',
          null,
          this.props.instructions
        )
      ),
      React.createElement(
        'button',
        {
          onClick: this.toggleForm
        },
        'Edit'
      ),
      React.createElement(
        'button',
        {
          className: 'delete',
          onClick: this.handleRecipeDelete
        },
        'Delete'
      ),
      React.createElement(RecipeForm, {
        title: this.props.title,
        ingredients: ingredientsList,
        instructions: this.props.instructions,
        key: 'f' + this.props.id,
        id: 'f' + this.props.id,
        display: this.state.formVisibility ? { display: 'block' } : { display: 'none' },
        onRecipeSubmit: this.handleRecipeTransfer,
        onRecipeCancel: this.handleRecipeCancel
      })
    );
  }
});

var RecipeForm = React.createClass({
  displayName: 'RecipeForm',

  getInitialState: function getInitialState() {
    return {
      title: '',
      ingredients: '',
      instructions: ''
    };
  },
  handleTitleChange: function handleTitleChange(e) {
    e.preventDefault();
    this.setState({
      title: e.target.value
    });
  },
  handleIngredientsChange: function handleIngredientsChange(e) {
    e.preventDefault();
    this.setState({
      ingredients: e.target.value
    });
  },
  handleInstructionsChange: function handleInstructionsChange(e) {
    e.preventDefault();
    this.setState({
      instructions: e.target.value
    });
  },
  handleRecipeSubmit: function handleRecipeSubmit(e) {
    e.preventDefault();
    var title = this.state.title.trim();
    var ingredients = this.state.ingredients.trim().split(',');
    var instructions = this.state.instructions.trim().split(',');
    this.setState({
      title: '',
      ingredients: '',
      instructions: ''
    });
    this.refs.titleInput.value = '';
    this.refs.ingredientsInput.value = '';
    this.refs.instructionsInput.value = '';
    this.props.onRecipeSubmit({
      id: this.props.id,
      title: title,
      ingredients: ingredients,
      instructions: instructions
    });
  },
  handleRecipeCancel: function handleRecipeCancel(e) {
    e.preventDefault();
    this.setState({
      title: '',
      ingredients: '',
      instructions: ''
    });
    this.refs.titleInput.value = '';
    this.refs.ingredientsInput.value = '';
    this.refs.instructionsInput.value = '';
    this.props.onRecipeCancel({
      source: this.props.id,
      cancel: 1
    });
  },
  render: function render() {
    return React.createElement(
      'div',
      {
        className: 'form-container',
        style: this.props.display
      },
      React.createElement(
        'form',
        { style: this.props.display },
        React.createElement(
          'label',
          { 'for': 'title' },
          'Title'
        ),
        React.createElement('input', {
          type: 'text',
          name: 'title',
          ref: 'titleInput',
          placeholder: this.props.title,
          onChange: this.handleTitleChange
        }),
        React.createElement(
          'label',
          { 'for': 'ingredients' },
          'Ingredients'
        ),
        React.createElement('input', {
          type: 'text',
          name: 'ingredients',
          ref: 'ingredientsInput',
          placeholder: this.props.ingredients,
          onChange: this.handleIngredientsChange
        }),
        React.createElement(
          'label',
          { 'for': 'instructions' },
          'Instructions'
        ),
        React.createElement('textarea', {
          name: 'instructions',
          rows: '3',
          ref: 'instructionsInput',
          placeholder: this.props.instructions,
          onChange: this.handleInstructionsChange
        }),
        React.createElement(
          'button',
          {
            type: 'submit',
            onClick: this.handleRecipeSubmit,
            disabled: !this.state.title.length || !this.state.ingredients.length || !this.state.instructions.length
          },
          'Save'
        ),
        React.createElement(
          'button',
          {
            type: 'submit',
            onClick: this.handleRecipeCancel
          },
          'Cancel'
        )
      )
    );
  }
});

var seedData = [{ id: 1,
  active: 1,
  title: "Four Cheese Rigatoni",
  ingredients: ['Rigatoni', 'Double Cream', 'Mozzarella', 'Cheddar', 'Parmesan', 'Roquefort', 'Pepper'],
  instructions: "Boil the pasta to taste. Meanwhile, chop / grate the cheese. Drain the pasta, add the cream, then the cheese and finally some pepper."
}, { id: 2,
  active: 1,
  title: "Mushroom Omelette",
  ingredients: ['Eggs', 'Milk', 'Mixed Herbs', 'Mushrooms', 'Cheese', 'Pepper'],
  instructions: "Slice the mushrooms and fry. Meanwhile, beat the eggs in a cup then add milk and stir. Add the mix to the mushrooms and season with herbs, cook gently until fluffy." }, { id: 3,
  active: 1,
  title: "Pesto & Avocado Sandwich",
  ingredients: ['Toast', 'Avocado', 'Pesto', 'Tomato', "Goat's Cheese", 'Pepper', 'Rocket'],
  instructions: "Put the toast under the grill. Chop the vegetables and cheese. Add a thin coat of pesto to the ungrilled side of the toast, grill for one minute then put the vegetables  and cheese on top." }];

if (localStorage && !localStorage.getItem('_0damian_data')) {
  var storage = localStorage;
  storage.setItem('_0damian_data', JSON.stringify(seedData));
} else if (localStorage && !!localStorage.getItem('_0damian_data')) {
  storage = localStorage;
} else storage = 0;

ReactDOM.render(React.createElement(RecipesContainer, null), document.getElementById('app'));

Comments