Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial upload

  • Loading branch information...
commit 908451d51e9a96763a914252bf3bab8bffa94b8e 0 parents
@zachdunn zachdunn authored
1  .gitignore
@@ -0,0 +1 @@
+.DS_Store
13 README.md
@@ -0,0 +1,13 @@
+# Drag-n-Drop Recipe Builder
+
+** Upcoming tutorial **
+
+A drag-n-drop powered recipe builder that uses AJAX to pull combinations from a database. Pretty much mirror's the mechanics behind the popular Doodle God game.
+
+## Getting Started
+
+Since this is a preliminary build, you'll have to do a little more legwork to get it up and running. Run the install.php script after plugging in your database info to get started. You can use the `add-recipe.php` script to add recipes. Only two ingredients ("Water Bottle" and "Newspaper") are included to start.
+
+The `recipes-builder.php` file loads the builder itself. Results will show up once you've plugged in some recipes to the database.
+
+** More Documentation Coming Soon**
45 add-recipe.php
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Recipe Manager</title>
+ <link rel="stylesheet" href="css/style.css" />
+ <link rel="stylesheet" href="css/add-form.css" />
+</head>
+<body>
+
+ <h1>Add a Question</h1>
+
+ <form method="post" action="ajax/add-recipe.php">
+
+ <fieldset>
+ <label for="recipe_title">Title</label>
+ <input type="text" name="recipe_title"/>
+ </fieldset>
+
+ <fieldset>
+ <label for="recipe_desc">Description</label>
+ <textarea name="recipe_desc" rows="6"></textarea>
+ </fieldset>
+
+ <fieldset>
+ <label for="recipe_image">Image URL</label>
+ <input type="text" name="recipe_image"/>
+ </fieldset>
+
+ <fieldset>
+ <label for="ingredient_1">Ingredient 1</label>
+ <input type="text" name="ingredient_1"/>
+ </fieldset>
+
+ <fieldset>
+ <label for="ingredient_2">Ingredient 2</label>
+ <input type="text" name="ingredient_2"/>
+ </fieldset>
+
+ <input type="submit" value="Save this recipe"/>
+
+ </form>
+
+</body>
+</html>
31 ajax/add-recipe.php
@@ -0,0 +1,31 @@
+<?php
+
+ /*
+ AJAX form handler for adding recipes
+ TO-DO: Call via jQuery AJAX instead of form action
+ */
+
+ //Error reporting (Debug only)
+ error_reporting(E_ALL);
+ ini_set("display_errors", 1);
+
+ //Prevent direct script access
+ /*if (!defined('BASEPATH') &&
+ strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest')
+ exit('No direct script access allowed.');*/
+
+ require_once '../includes/recipes.php';
+ $recipes = new Recipes();
+
+ //Make array of ingredients & implode
+
+ if($recipes->add_recipe($_POST)) :
+ $return['success'] = true;
+ echo 'Success';
+ else :
+ $return['success'] = false;
+ echo 'Failure';
+ endif;
+
+ //Strip slashes and convert to JSON
+ //echo stripslashes(json_encode($return));
36 ajax/check-recipe.php
@@ -0,0 +1,36 @@
+<?php
+ /*
+ Checks for combinations
+ and displays results via AJAX
+ */
+
+ //Error reporting (Debug only)
+ error_reporting(E_ALL);
+ ini_set("display_errors", 1);
+
+ //Prevent direct script access
+ if (!defined('BASEPATH') &&
+ strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest')
+ exit('No direct script access allowed.');
+
+ //Instantiate Recipes class
+ require_once '../includes/recipes.php';
+ $recipes = new Recipes();
+
+ //Make array of ingredients & implode
+ $ingredients = array();
+ array_push($ingredients, $_POST['ingredient1']);
+ array_push($ingredients, $_POST['ingredient2']);
+ $ingredients = implode(':', $ingredients);
+
+ //Check for combinations and return results
+ if($result = $recipes->combine_ingredients($ingredients)) :
+ $return['message'] = "You found a combination!";
+ $return['recipe'] = $result;
+ else :
+ $return['message'] = "Those items don't go together";
+ $return['recipe'] = false;
+ endif;
+
+ //Strip slashes and convert to JSON
+ echo stripslashes(json_encode($return));
7 css/add-form.css
@@ -0,0 +1,7 @@
+fieldset{
+ border:none;
+}
+
+label{float:left; width:130px;clear:left;text-align:right; padding-right:15px; font-size:.85em;}
+
+input[type="text"], textarea{border-radius:3px; border:none; font-size:.93em; padding: 3px 5px; outline:none; clear:right; width:300px;}
78 css/style.css
@@ -0,0 +1,78 @@
+/*
+ Recipe Builder
+ Fall 2010
+ Zach Dunn / One Mighty Roar (onemightyroar.com)
+*/
+
+body{
+ font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;
+ background: #a3da62;
+ margin:0; padding:0;
+}
+
+#header{
+ background:#252525;
+ display:block; margin-bottom:20px;
+ border-bottom:20px #b4e778 solid;
+}
+
+ #header h1{color:#efefef; margin:0; padding: 15px 20px; font-weight:normal; font-size:1.2em; text-transform:uppercase;}
+
+#wrapper{
+ margin:0 auto;
+ width:960px;
+}
+
+ #ingredients{
+ display:block; clear:both; list-style:none; padding-left:0;
+ }
+ #ingredients li{
+ display:inline; float:left;
+ width:50px; height:50px;
+ background:#FFF;
+ margin-right:10px;
+ }
+
+ .dropzone{
+ background:#fff;
+ width:300px;
+ height:300px;
+ -webkit-box-shadow: 0 1px 4px rgba(0,0,0,.2);
+ }
+ .dropzone img{width:280px; height:280px; margin:10px; background:#EFEFEF;}
+
+ #box1{float:left;}
+ #box2{float:right;}
+
+ #result{
+ display:block; clear:both;
+ width:690px; height:250px;
+ margin:30px auto 0 auto;
+ }
+ #result img{float:left; margin-right:10px;}
+ #result .desc{padding:15px 0 0 0;}
+
+ .desc{}
+ .desc span{font-size:.8em; text-transform:uppercase; color:#70a432; letter-spacing:1px; font-weight:bold;}
+ .desc h2{margin:0; padding-bottom:0; font-size:1.5em;}
+ .desc h2 + p{margin-top:0;}
+ .desc p{color:#526d33; font-size:.8em; line-height:1.4em;}
+#footer{
+ text-align:center;
+ clear:both;
+}
+ #footer p{font-size:.70em;}
+ #footer img{
+ opacity:.6;
+ -webkit-transition:all .5s ease;
+ -moz-transition:all .5s ease;
+ -o-transition:all .5s ease;
+ transition:all .5s ease;
+ }
+ #footer img:hover{opacity:1.0}
+
+/* UI Styles */
+.ui-state-hover{background:#FCCE02;}
+
+/* Helper Classes */
+.clear{clear:both; display:block; width:100%;}
BIN  images/ingredients/bottled-water.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  images/ingredients/drag-here-default.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  images/ingredients/newspaper.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  images/ingredients/thumbs/bottled-water.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  images/ingredients/thumbs/newspaper.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  images/result-circle-bg.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
259 includes/recipes.php
@@ -0,0 +1,259 @@
+<?php
+
+class Recipes
+{
+
+ private $recipes;
+ private $json_result;
+
+ //Fill this in with your own information
+ private $table_name = 'bi_recipes';
+
+ function __construct()
+ {
+
+ //Database constants
+ define('DB_HOST', 'localhost');
+ define('DB_NAME', 'omr_buildinternet');
+ define('DB_USER', 'root');
+ define('DB_PASSWORD', 'root');
+
+ //Get recipes from the database
+ $this->recipes = $this->get_recipes();
+
+ //Make a JSON version
+ $this->json_result = stripslashes(json_encode($this->recipes));
+ }
+
+ private function connect()
+ {
+
+ $mysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
+
+ //Connection error? Stop everything.
+ if ($mysqli->connect_errno)
+ die('Connection Error: ' . $mysqli->connect_errno);
+
+ //All good? Send back the mysqli object
+ return $mysqli;
+ }
+
+ public function database_exists()
+ {
+ /*
+ Check if the recipes database exists
+ */
+
+ $mysqli = $this->connect();
+
+ if($stmt = $mysqli->prepare("
+ SELECT COUNT(*)
+ FROM information_schema.TABLES
+ WHERE table_schema = ?
+ AND table_name = ?"))
+ {
+
+ //Workaround since bind_param doesn't accept constants
+ $db_name = DB_NAME;
+
+ $stmt->bind_param('ss', $db_name, $this->table_name);
+ $stmt->execute();
+ $stmt->bind_result($table_count);
+ $stmt->fetch();
+ $stmt->close();
+
+ }else{
+ die('Error: Could not prepare statement');
+ }
+
+ $table_exists = ($table_count >= 1) ? true : false;
+
+ if ($table_exists) :
+ return true;
+ else :
+ return false;
+ endif;
+ }
+
+ public function install()
+ {
+ /*
+ Makes table for recipe information
+ */
+
+ $mysqli = $this->connect();
+
+ $ingredients_setup = "
+ CREATE TABLE `".$this->table_name."` (
+ `recipe_id` int(11) NOT NULL AUTO_INCREMENT,
+ `recipe_title` varchar(200) DEFAULT NULL,
+ `recipe_image` text,
+ `recipe_desc` text,
+ `ingredients` varchar(128) DEFAULT NULL,
+ PRIMARY KEY (`recipe_id`)
+ ) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
+ ";
+
+ if($q = $mysqli->query($ingredients_setup)) :
+ echo 'Created Table: ' . $this->table_name;
+ else:
+ die('Error: Could not create table');
+ endif;
+
+ }
+
+ private function get_recipes()
+ {
+ $mysqli = $this->connect();
+
+ //Dump the recipes in db to a local variable
+ if($q = $mysqli->query("SELECT * FROM $this->table_name") ) :
+ while($row = $q->fetch_object()) {
+ $row->ingredients = json_decode($row->ingredients, true); //Ingredients are stored as JSON
+ $data[] = $row;
+ }
+ //We're done with the db
+ $mysqli->close();
+ return $data;
+ else :
+ //No Results
+ $mysqli->close();
+ return false;
+ endif;
+ }
+
+ public function combine_ingredients($ingredients)
+ {
+ $ingredients = explode(':', $ingredients);
+
+ foreach ($this->recipes as $recipe) :
+
+ if( !$this->array_diff_once($ingredients, $recipe->ingredients) ) :
+ //You found a combination
+ return $recipe;
+ endif;
+
+ endforeach;
+
+ //No recipe found, it was a bad combination
+ return false;
+ }
+
+ public function make_json_file()
+ {
+ //Save a recipes.json file
+ $json_file = fopen('recipes.json', 'w');
+ fwrite($json_file, $this->json_result);
+ fclose($json_file);
+ }
+
+ /*
+ Form Handling for "Add Recipe" page
+ */
+
+ public function add_recipe($_POST)
+ {
+ $mysqli = $this->connect();
+
+ //Make sure everything is formatted correctly.
+ $recipe = $this->sanitize_data($_POST);
+
+ if ($stmt = $mysqli->prepare("INSERT ? (recipe_title, recipe_image, recipe_desc, ingredients) VALUES (?, ?, ?, ?)"))
+ {
+ //Once the query is prepared, set the parameters
+ $stmt->bind_param('sssss', $this->table_name, $recipe['title'], $recipe['image'], $recipe['desc'], $recipe['ingredients']);
+ $stmt->execute();
+ $stmt->close();
+ }else{
+ die('Could not prepare SQL statement.');
+ }
+
+ //We're done with the db
+ $mysqli->close();
+
+ return true;
+ }
+
+ private function sanitize_data($_POST)
+ {
+ //Get and sanitize the post fields
+ if (isset($_POST['recipe_title']) && !empty($_POST['recipe_title']) ) :
+ $recipe['title'] = filter_var($_POST['recipe_title'], FILTER_SANITIZE_STRING);
+ endif;
+
+ if (isset($_POST['recipe_desc']) && !empty($_POST['recipe_desc']) ) :
+ $recipe['desc'] = filter_var($_POST['recipe_desc'], FILTER_SANITIZE_STRING);
+ endif;
+
+ if (isset($_POST['recipe_image']) && !empty($_POST['recipe_image']) ) :
+ $recipe['image'] = filter_var($_POST['recipe_image'], FILTER_SANITIZE_URL);
+ endif;
+
+ $ingredients = array();
+
+ if (isset($_POST['ingredient_1']) && !empty($_POST['ingredient_1']) ) :
+ $ingredients['1'] = filter_var($_POST['ingredient_1'], FILTER_SANITIZE_STRING);
+ endif;
+
+ if (isset($_POST['ingredient_2']) && !empty($_POST['ingredient_2']) ) :
+ $ingredients['2'] = filter_var($_POST['ingredient_2'], FILTER_SANITIZE_STRING);
+ endif;
+
+ //Encode ingredients array to JSON for DB storage
+ $recipe['ingredients'] = json_encode($ingredients);
+
+ return $recipe;
+ }
+
+ /*
+ Helper Functions
+ */
+
+ private function array_diff_once()
+ {
+ /*
+ Modified array_diff() to evaluate each entry once (Prevents false positives from 50% matches)
+ VIA: http://www.php.net/manual/en/function.array-diff.php#75731
+ */
+
+ if(($args = func_num_args()) < 2)
+ return false;
+ $arr1 = func_get_arg(0);
+ $arr2 = func_get_arg(1);
+ if(!is_array($arr1) || !is_array($arr2))
+ return false;
+ foreach($arr2 as $remove){
+ foreach($arr1 as $k=>$v){
+ if((string)$v === (string)$remove){ //NOTE: if you need the diff to be STRICT, remove both the '(string)'s
+ unset($arr1[$k]);
+ break; //That's pretty much the only difference from the real array_diff :P
+ }
+ }
+ }
+ //Handle more than 2 arguments
+ $c = $args;
+ while($c > 2){
+ $c--;
+ $arr1 = array_diff_once($arr1, func_get_arg($args-$c+1));
+ }
+ return $arr1;
+ }
+
+ /*
+ Debugging Functions Only
+ */
+
+ public function show_recipes()
+ {
+ //Debugging function
+ echo '<pre>';
+ echo var_dump($this->recipes);
+ echo '</pre>';
+ }
+
+ public function json_output()
+ {
+ print_r($this->json_result);
+ }
+
+}
8 install.php
@@ -0,0 +1,8 @@
+<?php
+
+require_once 'includes/recipes.php';
+$recipes = new Recipes();
+
+if ( !$recipes->database_exists() ) $recipes->install();
+
+echo 'Everything is good to go';
71 js/recipe-assets.js
@@ -0,0 +1,71 @@
+//Define default paths
+var results_path = 'images/';
+var ingredients_path = 'images/ingredients/';
+var thumb_path = 'images/ingredients/thumbs/';
+
+$(document).ready(function() {
+
+ $('#result').hide();
+
+ $('.dropzone').droppable({
+ hoverClass: "ui-state-hover",
+ tolerance: "intersect",
+ accept: "#ingredients li",
+ drop: function(event, ui){
+
+ //Get ingredient information
+ var file_name = ui.draggable.find('img').attr("rel");
+ var ingredient_name = ui.draggable.find('img').attr('alt');
+
+ //Update hidden field
+ if ($(this).attr('id') == "box1"){
+ $('#ingredient1').attr("value", ingredient_name);
+ }else{
+ $('#ingredient2').attr("value", ingredient_name);
+ }
+
+ //Update image for ingredient box
+ $(this).find('img').attr('src', ingredients_path + file_name + '.jpg').attr('alt', ingredient_name);
+
+ //Serialize POST data for AJAX call
+ var data_string = $('#combo-form').serialize();
+
+ //Check the current combination, return as JSON
+ $.post('ajax/check-recipe.php', data_string,
+ function(data) {
+ update_results(data.recipe);
+ }, 'json' );
+
+ return false;
+ }
+ });
+
+ $('#ingredients li').draggable({
+ containment: 'body',
+ revert: 'invalid',
+ helper: 'clone',
+ zIndex: 2000,
+ scroll: false,
+ });
+
+});
+
+function update_results(recipe) {
+
+ if (recipe == false) {
+ console.log('No result found');
+ $('#result').hide();
+ }else{
+ console.log(recipe);
+ $('#result').show();
+
+ if (recipe.recipe_image == null){
+ $('#result').find('img').attr('src', results_path + '/result-circle-bg.jpg');
+ }else{
+ $('#result').find('img').attr('src', recipe.recipe_image);
+ }
+
+ $('#result').find('h2').text(recipe.recipe_title);
+ $('#result').find('p').text(recipe.recipe_desc);
+ }
+}
11 json-generator.php
@@ -0,0 +1,11 @@
+<?php
+
+ /*
+ Output recipes as a JSON file
+ */
+
+ require_once 'includes/recipes.php';
+ $recipes = new Recipes();
+
+ $recipes->json_output();
+ //$recipes->make_json_file();
19 recipe-structure.sql
@@ -0,0 +1,19 @@
+#
+# Encoding: Unicode (UTF-8)
+#
+
+
+DROP TABLE bi_recipes;
+
+
+CREATE TABLE `bi_recipes` (
+ `recipe_id` int(11) NOT NULL AUTO_INCREMENT,
+ `recipe_title` varchar(200) DEFAULT NULL,
+ `recipe_image` text,
+ `recipe_desc` text,
+ `ingredients` varchar(128) DEFAULT NULL,
+ PRIMARY KEY (`recipe_id`)
+) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
+
+
+
74 recipes-builder.php
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Recipe Builder</title>
+
+ <link rel="stylesheet" type="text/css" href="css/style.css"/>
+
+ <!-- jQuery UI -->
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+ <script type="text/javascript" src="http://jqueryui.com/ui/jquery.ui.core.js"></script>
+ <script type="text/javascript" src="http://jqueryui.com/ui/jquery.ui.widget.js"></script>
+ <script type="text/javascript" src="http://jqueryui.com/ui/jquery.ui.mouse.js"></script>
+ <script type="text/javascript" src="http://jqueryui.com/ui/jquery.ui.draggable.js"></script>
+ <script type="text/javascript" src="http://jqueryui.com/ui/jquery.ui.droppable.js"></script>
+
+ <script type"text/javascript" src="js/recipe-assets.js"></script>
+
+</head>
+<body>
+
+ <div id="header">
+ <h1>Drag and Drop Recipe</h1>
+ </div>
+
+ <div id="wrapper">
+
+ <p id="debug"></p>
+
+ <ul id="ingredients">
+ <li><img src="images/ingredients/thumbs/bottled-water.png" alt="Bottled Water" rel="bottled-water"/></li>
+ <li><img src="images/ingredients/thumbs/newspaper.png" alt="Newspaper" rel="newspaper"/></li>
+ </ul>
+
+ <div class="clear">&nbsp;</div>
+
+ <div id="box1" class="dropzone">
+ <img src="images/ingredients/drag-here-default.jpg" alt="First Component"/>
+ </div>
+
+ <div id="box2" class="dropzone">
+ <img src="images/ingredients/drag-here-default.jpg" alt="Second Component"/>
+ </div>
+
+ <form action="ajax/check-recipe.php" method="post" id="combo-form">
+ <input name="ingredient1" id="ingredient1" type="hidden" value=""/>
+ <input name="ingredient2" id="ingredient2" type="hidden" value=""/>
+ </form>
+
+ <div class="clear">&nbsp;</div>
+
+ <!--
+ The result div only shows when a combination has been found.
+ -->
+ <div id="result">
+
+ <img src="images/result-circle-bg.jpg" title="No match found">
+ <div class="desc">
+ <span class="prefix">You Made</span>
+ <h2>Nothing Yet!</h2>
+ <p>Drag and drop ingredients to put discover new combinations.</p>
+ </div><!-- .desc -->
+
+ </div><!-- #result -->
+
+ <div class="clear">&nbsp;</div>
+ </div>
+
+ <div id="footer">
+ <p>Drag and Drop with AJAX, JSON, jQuery UI. Tutorial by <a href="http://buildinternet.com">Build Internet</a>.</p>
+ </div>
+
+</body>
+</html>
Please sign in to comment.
Something went wrong with that request. Please try again.