Last active
April 3, 2017 20:29
-
-
Save mvark/7521891cffb39c2ead63 to your computer and use it in GitHub Desktop.
Single Page Application (SPA) to track food expiry dates shows how to implement CRUD functionality through Azure Mobile Services HTTP OData REST calls, without writing any server-side code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta http-equiv="content-type" content="text/html; charset=UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title>Food Tracker</title> | |
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"> | |
<link href='http://cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.1.1/fullcalendar.min.css' rel='stylesheet' /> | |
<link href='http://cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.1.1/fullcalendar.print.css' media='print' rel='stylesheet' /> | |
<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/themes/smoothness/jquery-ui.css"> | |
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.0.1/css/toastr.min.css" media="screen" rel="stylesheet" type="text/css" /> | |
<style> | |
body { } | |
#calendar { } | |
.clickable { | |
cursor: pointer; | |
} | |
.btn { | |
background-color: #e0eaf1; | |
border-bottom: 1px solid #b3cee1; | |
border-right: 1px solid #b3cee1; | |
color: #3e6d8e; | |
display: inline-block; | |
font-size: 90%; | |
line-height: 1.4; | |
margin: 2px 2px 2px 0; | |
padding: 3px 4px 3px 4px; | |
text-decoration: none; | |
white-space: nowrap; | |
} | |
</style> | |
</head> | |
<body> | |
<nav class="navbar navbar-default" role="navigation"> | |
<div class="container-fluid"> | |
<div class="navbar-header"> | |
<a class="navbar-brand" href="#">Food Tracker</a> | |
</div> | |
</div> | |
</nav> | |
<div class="container-fluid"> | |
<div class="row"> | |
<div class="col-md-6 col-sm-12 col-xs-12"> | |
<div id="alert"></div> | |
<div id='loading'> | |
loading... | |
</div> | |
<div id='calendar'></div> | |
</div> | |
<div class="col-md-6"> | |
<ul id="myTab" class="nav nav-tabs" role="tablist"> | |
<li class="active"><a href="#viewTab" data-id="viewTab" role="tab" data-toggle="tab">View</a></li> | |
<li><a href="#addTab" data-id="addTab" role="tab" data-toggle="tab">Add</a></li> | |
<li><a href="#editTab" data-id="editTab" role="tab" data-toggle="tab">Edit</a></li> | |
<li><a href="#searchTab" data-id="searchTab" role="tab" data-toggle="tab">Search</a></li> | |
</ul> | |
<div class="tab-content"> | |
<div class="tab-pane active" id="viewTab"> | |
<p><div id="tracker"></div></p> | |
</div> | |
<div class="tab-pane" id="addTab"> | |
<div id="addForm"> | |
<form> | |
<div><br /> | |
<label for="item">Item:</label> | |
<input type="text" name="item" id="item" autofocus="autofocus"><br /> | |
<label for="bestBefore">Best before:</label> | |
<input name="bestBefore" id="bestBefore" type="text" class="date-picker"><br /> | |
<button id="submit" class="fc-button fc-state-default fc-corner-left fc-corner-right">Save</button> | |
</div> | |
</form> | |
<div id="status"></div> | |
</div> | |
</div> | |
<div class="tab-pane" id="editTab"> | |
<br /><h6>Click on a specific item in the list on the View tab or an item in the calendar, to edit</h6> | |
<div id="editForm"> | |
<form> | |
<div> | |
<label for="item">Item:</label> | |
<input type="text" id="eitem"><br /> | |
<label for="bestBefore">Best before:</label> | |
<input name="ebestBefore" id="ebestBefore" type="text" class="date-picker"><br /> | |
<input type="hidden" id="itemId" value=""> | |
<button id="update" class="fc-button fc-state-default fc-corner-left fc-corner-right">Save</button> | |
</div> | |
</form> | |
</div> | |
</div> | |
<div class="tab-pane" id="searchTab"> | |
<div id="searchForm"> | |
<div><br /> | |
<input type="text" id="keyword"> | |
<button id="search" class="fc-button fc-state-default fc-corner-left fc-corner-right">Search</button> | |
<div id="results"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script> | |
<script type="text/javascript" src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script> | |
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.1/moment.min.js"></script> | |
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.1.1/fullcalendar.min.js"></script> | |
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.0.1/js/toastr.min.js"></script> | |
<script> | |
"use strict"; | |
var xhr = new XMLHttpRequest(); | |
var $item = $('#item'); | |
var $bestBefore = $('#bestBefore'); | |
var dataSet; | |
$(document).ready(function () { | |
initialize(); | |
$(".date-picker").on("change", function () { | |
//jump to a specific month | |
}); | |
$('#calendar').fullCalendar({ | |
defaultDate: new Date(), | |
loading: function (bool) { | |
if (bool) $('#loading').show(); | |
else $('#loading').hide(); | |
}, | |
eventRender: function (event, element) { | |
element.css('cursor', 'pointer'); //on hovering over events in calendar, hand pointer should appear not cursor | |
}, | |
eventClick: function (calEvent, jsEvent, view) { | |
$('#itemId').val(calEvent.id); | |
$('#eitem').val(calEvent.title); | |
$('#ebestBefore').val(calEvent.start.format()); | |
$(this).css('border-color', 'green'); | |
showElem('#editForm'); | |
$('#myTab li:eq(2) a').tab('show'); | |
}, | |
dayClick: function (date, jsEvent, view) { | |
$('#bestBefore').val(date.format()); | |
$(this).css('background-color', 'cyan'); | |
$('#myTab li:eq(1) a').tab('show'); | |
$('#item').focus(); | |
}, | |
events: function (start, end, timezone, callback) { | |
$.ajax({ | |
url: "http://example.azure-mobile.net/tables/food?$filter=bestbefore gt '" + start.toISOString() + "' and bestbefore lt '" + end.toISOString() + "'&$orderby=bestbefore", | |
dataType: 'json', | |
beforeSend: setHeader, | |
success: function (data) { | |
var events = []; | |
$.each(data, function (i) { | |
var bbdate = data[i].bestbefore.split("T"); | |
events.push({ | |
"id": data[i].id, | |
"title": data[i].item, | |
"start": bbdate[0] | |
}); | |
}); | |
callback(events); | |
$('#tracker').empty(); | |
$('#myTab #viewTab').tab('show'); | |
listResults(events, "#tracker"); | |
}, | |
error: function () { toastr.error('Operation failed! Please retry'); } | |
}); | |
} | |
}); | |
}); | |
//SEARCH | |
function searchItem(keyword) { | |
$.getJSON("http://example.azure-mobile.net/tables/food?$filter=substringof('" + keyword + "',item)&$orderby=bestbefore", function (data) { | |
var events = []; | |
$.each(data, function (i) { | |
var bbdate = data[i].bestbefore.split("T"); | |
events.push({ | |
"id": data[i].id, | |
"title": data[i].item, | |
"start": bbdate[0] | |
}); | |
}); | |
listResults(events, "#results") | |
}); | |
} | |
//CREATE | |
/* POST our newly entered data to the server | |
********************************************/ | |
function restPost(food) { | |
$.ajax({ | |
url: 'https://example.azure-mobile.net/tables/food', | |
type: 'POST', | |
datatype: 'json', | |
beforeSend: setHeader, | |
data: food, | |
success: function (data) { | |
toastr.success('Added ' + data.item); | |
}, | |
error: function () { toastr.error('Operation failed! Please retry'); } | |
}); | |
} | |
function listResults(events, container) { | |
var results = ""; | |
for (var i = 0; i < events.length; i++) { | |
var stuff = '[{ "id":"' + events[i].id + '", "title":"' + events[i].title + '" , "start":"' + events[i].start + '" }]'; | |
results += "<li><a data-stuff='" + stuff + "' class='items clickable btn' data-editid='" + events[i].id + "' >Edit</a> | <a class='del clickable btn' data-deleteitem='" + events[i].title + "' data-deleteid='" + events[i].id + "' >Delete</a> | " + events[i].title + " X " + events[i].start + "</li>" | |
} | |
if (results == "") { | |
$(container).html("Nothing to show :-("); | |
} | |
else { | |
$(container).html(results); | |
$(".items").bind('click', function () { | |
var foodItem = $(this).data('stuff'); | |
getItem.apply(this, foodItem); | |
showElem('#editForm'); | |
$('#myTab li:eq(2) a').tab('show'); | |
}); | |
$(".del").bind('click', function () { | |
var delId = $(this).data("deleteid"); | |
var delItem = $(this).data("deleteitem"); | |
if (confirm('Are you sure you want to delete the record?')) { | |
deleteItem(delId, delItem); | |
} | |
}); | |
} | |
} | |
//UPDATE | |
function restPatch(food) { | |
$.ajax({ | |
url: 'https://example.azure-mobile.net/tables/food/' + food.id, | |
type: 'PATCH', | |
datatype: 'json', | |
beforeSend: setHeader, | |
data: food, | |
success: function (data) { | |
toastr.success('Edited ' + data.item); | |
}, | |
error: function () { toastr.error('Operation failed! Please retry'); } | |
}); | |
} | |
//DELETE | |
function deleteItem(delId, delItem) { | |
$.ajax({ | |
url: 'https://example.azure-mobile.net/tables/food/' + delId, | |
type: 'DELETE', | |
success: function (result) { | |
toastr.success('Deleted ' + delItem); | |
$('#calendar').fullCalendar('refetchEvents'); | |
}, | |
error: function () { toastr.error('Operation failed! Please retry'); } | |
}); | |
} | |
$('#search').on('click', function (e) { | |
searchItem($('#keyword').val()); | |
}); | |
$('#submit').on('click', function (e) { | |
e.preventDefault(); | |
var food = { | |
item: $item.val(), | |
bestBefore: $bestBefore.val() | |
}; | |
restPost(food); | |
$('#calendar').fullCalendar('refetchEvents'); | |
$('#myTab li:eq(0) a').tab('show'); | |
resetForm('#editForm'); | |
}); | |
$('#update').on('click', function (e) { | |
e.preventDefault(); | |
var food = { | |
id: $('#itemId').val(), | |
item: $('#eitem').val(), | |
bestBefore: $('#ebestBefore').val() | |
}; | |
restPatch(food); | |
$('#calendar').fullCalendar('refetchEvents'); | |
hideElem('#editForm'); | |
$('#myTab li:eq(0) a').tab('show'); | |
resetForm('#editForm'); | |
}); | |
$.ajaxSetup({ | |
headers: { | |
'X-ZUMO-APPLICATION': '--paste your APPLICATION KEY here--' | |
} | |
}); | |
function getItem(foodItem) { | |
$('#itemId').val(foodItem.id); | |
$('#eitem').val(foodItem.title); | |
$('#ebestBefore').val(foodItem.start); | |
} | |
/* Used for authorization, to access the JSON data in the Azure Mobile Service | |
******************************************************************************/ | |
function setHeader(xhr) { | |
xhr.setRequestHeader('X-ZUMO-APPLICATION', '--paste your APPLICATION KEY here--'); | |
} | |
function initialize() { | |
hideElem('#editForm'); | |
$.datepicker.setDefaults({ dateFormat: 'yy-mm-dd' }); | |
$(".date-picker").datepicker(); | |
toastr.options.timeOut = 1500; // 1.5s | |
toastr.options.closeButton = true; | |
toastr.options.positionClass = "toast-top-right"; | |
} | |
function showElem(elem) { | |
$(elem).show(); | |
} | |
function hideElem(elem) { | |
$(elem).hide(); | |
} | |
function resetForm(form) { | |
$(form).find("input[type=text]").val(""); | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment