Skip to content

Instantly share code, notes, and snippets.

@mvark
Last active April 3, 2017 20:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mvark/7521891cffb39c2ead63 to your computer and use it in GitHub Desktop.
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
<!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>&nbsp;|&nbsp;<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