├── .gitignore ├── readme.md ├── resources ├── logoutcallback.html ├── ngopenfb.js ├── oauthcallback.html └── openfb.js └── server ├── package.json ├── readme.md ├── routes └── sessions.js └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | server/node_modules 3 | npm-debug.log 4 | conference -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Ionic Tutorial 2 | 3 | In this tutorial, you learn how to build a native-like mobile application with Ionic and AngularJS. You build a Conference application that allows the attendees of the conference to browse through the list of sessions, and share information on Facebook. 4 | 5 | Follow the step-by-step instructions available here: http://ccoenraets.github.io/ionic-tutorial/ 6 | -------------------------------------------------------------------------------- /resources/logoutcallback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/ngopenfb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Angular wrapper for the OpenFB library 3 | * Allows you to use OpenFB "the Angular way": 4 | * - As an Angular service instead of a global object 5 | * - Using promises instead of callbacks 6 | * @author Christophe Coenraets @ccoenraets 7 | * @version 0.5 8 | */ 9 | angular.module('ngOpenFB', []) 10 | 11 | .factory('ngFB', function ($q, $window) { 12 | 13 | function init(params) { 14 | return $window.openFB.init(params); 15 | } 16 | 17 | function login(options) { 18 | var deferred = $q.defer(); 19 | $window.openFB.login(function(result) { 20 | if (result.status === "connected") { 21 | deferred.resolve(result); 22 | } else { 23 | deferred.reject(result); 24 | } 25 | }, options); 26 | return deferred.promise; 27 | } 28 | 29 | function logout() { 30 | var deferred = $q.defer(); 31 | $window.openFB.logout(function() { 32 | deferred.resolve(); 33 | }); 34 | return deferred.promise; 35 | } 36 | 37 | function api(obj) { 38 | var deferred = $q.defer(); 39 | obj.success = function(result) { 40 | deferred.resolve(result); 41 | }; 42 | obj.error = function(error) { 43 | deferred.reject(error); 44 | }; 45 | $window.openFB.api(obj); 46 | return deferred.promise; 47 | } 48 | 49 | function revokePermissions() { 50 | var deferred = $q.defer(); 51 | $window.openFB.revokePermissions( 52 | function() { 53 | deferred.resolve(); 54 | }, 55 | function() { 56 | deferred.reject(); 57 | } 58 | ); 59 | return deferred.promise; 60 | } 61 | 62 | function getLoginStatus() { 63 | var deferred = $q.defer(); 64 | $window.openFB.getLoginStatus( 65 | function(result) { 66 | deferred.resolve(result); 67 | } 68 | ); 69 | return deferred.promise; 70 | } 71 | 72 | return { 73 | init: init, 74 | login: login, 75 | logout: logout, 76 | revokePermissions: revokePermissions, 77 | api: api, 78 | getLoginStatus: getLoginStatus 79 | }; 80 | 81 | }); -------------------------------------------------------------------------------- /resources/oauthcallback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /resources/openfb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * OpenFB is a micro-library that lets you integrate your JavaScript application with Facebook. 3 | * OpenFB works for both BROWSER-BASED apps and CORDOVA/PHONEGAP apps. 4 | * This library has no dependency: You don't need (and shouldn't use) the Facebook SDK with this library. Whe running in 5 | * Cordova, you also don't need the Facebook Cordova plugin. There is also no dependency on jQuery. 6 | * OpenFB allows you to login to Facebook and execute any Facebook Graph API request. 7 | * @author Christophe Coenraets @ccoenraets 8 | * @version 0.5 9 | */ 10 | var openFB = (function () { 11 | 12 | var loginURL = 'https://www.facebook.com/dialog/oauth', 13 | 14 | logoutURL = 'https://www.facebook.com/logout.php', 15 | 16 | // By default we store fbtoken in sessionStorage. This can be overridden in init() 17 | tokenStore = window.sessionStorage, 18 | 19 | // The Facebook App Id. Required. Set using init(). 20 | fbAppId, 21 | 22 | context = window.location.pathname.substring(0, window.location.pathname.lastIndexOf("/")), 23 | 24 | baseURL = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '') + context, 25 | 26 | // Default OAuth redirect URL. Can be overriden in init() 27 | oauthRedirectURL = baseURL + '/oauthcallback.html', 28 | 29 | // Default Cordova OAuth redirect URL. Can be overriden in init() 30 | cordovaOAuthRedirectURL = "https://www.facebook.com/connect/login_success.html", 31 | 32 | // Default Logout redirect URL. Can be overriden in init() 33 | logoutRedirectURL = baseURL + '/logoutcallback.html', 34 | 35 | // Because the OAuth login spans multiple processes, we need to keep the login callback function as a variable 36 | // inside the module instead of keeping it local within the login function. 37 | loginCallback, 38 | 39 | // Indicates if the app is running inside Cordova 40 | runningInCordova, 41 | 42 | // Used in the exit event handler to identify if the login has already been processed elsewhere (in the oauthCallback function) 43 | loginProcessed; 44 | 45 | // MAKE SURE YOU INCLUDE IN YOUR index.html, OTHERWISE runningInCordova will always by false. 46 | // You don't need to (and should not) add the actual cordova.js file to your file system: it will be added automatically 47 | // by the Cordova build process 48 | document.addEventListener("deviceready", function () { 49 | runningInCordova = true; 50 | }, false); 51 | 52 | /** 53 | * Initialize the OpenFB module. You must use this function and initialize the module with an appId before you can 54 | * use any other function. 55 | * @param params - init paramters 56 | * appId: (Required) The id of the Facebook app, 57 | * tokenStore: (optional) The store used to save the Facebook token. If not provided, we use sessionStorage. 58 | * loginURL: (optional) The OAuth login URL. Defaults to https://www.facebook.com/dialog/oauth. 59 | * logoutURL: (optional) The logout URL. Defaults to https://www.facebook.com/logout.php. 60 | * oauthRedirectURL: (optional) The OAuth redirect URL. Defaults to [baseURL]/oauthcallback.html. 61 | * logoutRedirectURL: (optional) The logout redirect URL. Defaults to [baseURL]/logoutcallback.html. 62 | * accessToken: (optional) An already authenticated access token. 63 | */ 64 | function init(params) { 65 | 66 | if (params.appId) { 67 | fbAppId = params.appId; 68 | } else { 69 | throw 'appId parameter not set in init()'; 70 | } 71 | 72 | if (params.tokenStore) { 73 | tokenStore = params.tokenStore; 74 | } 75 | 76 | if (params.accessToken) { 77 | tokenStore.fbAccessToken = params.accessToken; 78 | } 79 | 80 | loginURL = params.loginURL || loginURL; 81 | logoutURL = params.logoutURL || logoutURL; 82 | oauthRedirectURL = params.oauthRedirectURL || oauthRedirectURL; 83 | cordovaOAuthRedirectURL = params.cordovaOAuthRedirectURL || cordovaOAuthRedirectURL; 84 | logoutRedirectURL = params.logoutRedirectURL || logoutRedirectURL; 85 | 86 | } 87 | 88 | /** 89 | * Checks if the user has logged in with openFB and currently has a session api token. 90 | * @param callback the function that receives the loginstatus 91 | */ 92 | function getLoginStatus(callback) { 93 | var token = tokenStore.fbAccessToken, 94 | loginStatus = {}; 95 | if (token) { 96 | loginStatus.status = 'connected'; 97 | loginStatus.authResponse = {accessToken: token}; 98 | } else { 99 | loginStatus.status = 'unknown'; 100 | } 101 | if (callback) callback(loginStatus); 102 | } 103 | 104 | /** 105 | * Login to Facebook using OAuth. If running in a Browser, the OAuth workflow happens in a a popup window. 106 | * If running in Cordova container, it happens using the In-App Browser. Don't forget to install the In-App Browser 107 | * plugin in your Cordova project: cordova plugins add org.apache.cordova.inappbrowser. 108 | * 109 | * @param callback - Callback function to invoke when the login process succeeds 110 | * @param options - options.scope: The set of Facebook permissions requested 111 | * @returns {*} 112 | */ 113 | function login(callback, options) { 114 | 115 | var loginWindow, 116 | startTime, 117 | scope = '', 118 | redirectURL = runningInCordova ? cordovaOAuthRedirectURL : oauthRedirectURL; 119 | 120 | if (!fbAppId) { 121 | return callback({status: 'unknown', error: 'Facebook App Id not set.'}); 122 | } 123 | 124 | // Inappbrowser load start handler: Used when running in Cordova only 125 | function loginWindow_loadStartHandler(event) { 126 | var url = event.url; 127 | if (url.indexOf("access_token=") > 0 || url.indexOf("error=") > 0) { 128 | // When we get the access token fast, the login window (inappbrowser) is still opening with animation 129 | // in the Cordova app, and trying to close it while it's animating generates an exception. Wait a little... 130 | var timeout = 600 - (new Date().getTime() - startTime); 131 | setTimeout(function () { 132 | loginWindow.close(); 133 | }, timeout > 0 ? timeout : 0); 134 | oauthCallback(url); 135 | } 136 | } 137 | 138 | // Inappbrowser exit handler: Used when running in Cordova only 139 | function loginWindow_exitHandler() { 140 | console.log('exit and remove listeners'); 141 | // Handle the situation where the user closes the login window manually before completing the login process 142 | if (loginCallback && !loginProcessed) loginCallback({status: 'user_cancelled'}); 143 | loginWindow.removeEventListener('loadstop', loginWindow_loadStopHandler); 144 | loginWindow.removeEventListener('exit', loginWindow_exitHandler); 145 | loginWindow = null; 146 | console.log('done removing listeners'); 147 | } 148 | 149 | if (options && options.scope) { 150 | scope = options.scope; 151 | } 152 | 153 | loginCallback = callback; 154 | loginProcessed = false; 155 | 156 | startTime = new Date().getTime(); 157 | loginWindow = window.open(loginURL + '?client_id=' + fbAppId + '&redirect_uri=' + redirectURL + 158 | '&response_type=token&scope=' + scope, '_blank', 'location=no,clearcache=yes'); 159 | 160 | // If the app is running in Cordova, listen to URL changes in the InAppBrowser until we get a URL with an access_token or an error 161 | if (runningInCordova) { 162 | loginWindow.addEventListener('loadstart', loginWindow_loadStartHandler); 163 | loginWindow.addEventListener('exit', loginWindow_exitHandler); 164 | } 165 | // Note: if the app is running in the browser the loginWindow dialog will call back by invoking the 166 | // oauthCallback() function. See oauthcallback.html for details. 167 | 168 | } 169 | 170 | /** 171 | * Called either by oauthcallback.html (when the app is running the browser) or by the loginWindow loadstart event 172 | * handler defined in the login() function (when the app is running in the Cordova/PhoneGap container). 173 | * @param url - The oautchRedictURL called by Facebook with the access_token in the querystring at the ned of the 174 | * OAuth workflow. 175 | */ 176 | function oauthCallback(url) { 177 | // Parse the OAuth data received from Facebook 178 | var queryString, 179 | obj; 180 | 181 | loginProcessed = true; 182 | if (url.indexOf("access_token=") > 0) { 183 | queryString = url.substr(url.indexOf('#') + 1); 184 | obj = parseQueryString(queryString); 185 | tokenStore.fbAccessToken = obj['access_token']; 186 | if (loginCallback) loginCallback({status: 'connected', authResponse: {accessToken: obj['access_token']}}); 187 | } else if (url.indexOf("error=") > 0) { 188 | queryString = url.substring(url.indexOf('?') + 1, url.indexOf('#')); 189 | obj = parseQueryString(queryString); 190 | if (loginCallback) loginCallback({status: 'not_authorized', error: obj.error}); 191 | } else { 192 | if (loginCallback) loginCallback({status: 'not_authorized'}); 193 | } 194 | } 195 | 196 | /** 197 | * Logout from Facebook, and remove the token. 198 | * IMPORTANT: For the Facebook logout to work, the logoutRedirectURL must be on the domain specified in "Site URL" in your Facebook App Settings 199 | * 200 | */ 201 | function logout(callback) { 202 | var logoutWindow, 203 | token = tokenStore.fbAccessToken; 204 | 205 | /* Remove token. Will fail silently if does not exist */ 206 | tokenStore.removeItem('fbtoken'); 207 | 208 | if (token) { 209 | logoutWindow = window.open(logoutURL + '?access_token=' + token + '&next=' + logoutRedirectURL, '_blank', 'location=no,clearcache=yes'); 210 | if (runningInCordova) { 211 | setTimeout(function() { 212 | logoutWindow.close(); 213 | }, 700); 214 | } 215 | } 216 | 217 | if (callback) { 218 | callback(); 219 | } 220 | 221 | } 222 | 223 | /** 224 | * Lets you make any Facebook Graph API request. 225 | * @param obj - Request configuration object. Can include: 226 | * method: HTTP method: GET, POST, etc. Optional - Default is 'GET' 227 | * path: path in the Facebook graph: /me, /me.friends, etc. - Required 228 | * params: queryString parameters as a map - Optional 229 | * success: callback function when operation succeeds - Optional 230 | * error: callback function when operation fails - Optional 231 | */ 232 | function api(obj) { 233 | 234 | var method = obj.method || 'GET', 235 | params = obj.params || {}, 236 | xhr = new XMLHttpRequest(), 237 | url; 238 | 239 | params['access_token'] = tokenStore.fbAccessToken; 240 | 241 | url = 'https://graph.facebook.com' + obj.path + '?' + toQueryString(params); 242 | 243 | xhr.onreadystatechange = function () { 244 | if (xhr.readyState === 4) { 245 | if (xhr.status === 200) { 246 | if (obj.success) obj.success(JSON.parse(xhr.responseText)); 247 | } else { 248 | var error = xhr.responseText ? JSON.parse(xhr.responseText).error : {message: 'An error has occurred'}; 249 | if (obj.error) obj.error(error); 250 | } 251 | } 252 | }; 253 | 254 | xhr.open(method, url, true); 255 | xhr.send(); 256 | } 257 | 258 | /** 259 | * Helper function to de-authorize the app 260 | * @param success 261 | * @param error 262 | * @returns {*} 263 | */ 264 | function revokePermissions(success, error) { 265 | return api({method: 'DELETE', 266 | path: '/me/permissions', 267 | success: function () { 268 | success(); 269 | }, 270 | error: error}); 271 | } 272 | 273 | function parseQueryString(queryString) { 274 | var qs = decodeURIComponent(queryString), 275 | obj = {}, 276 | params = qs.split('&'); 277 | params.forEach(function (param) { 278 | var splitter = param.split('='); 279 | obj[splitter[0]] = splitter[1]; 280 | }); 281 | return obj; 282 | } 283 | 284 | function toQueryString(obj) { 285 | var parts = []; 286 | for (var i in obj) { 287 | if (obj.hasOwnProperty(i)) { 288 | parts.push(encodeURIComponent(i) + "=" + encodeURIComponent(obj[i])); 289 | } 290 | } 291 | return parts.join("&"); 292 | } 293 | 294 | // The public API 295 | return { 296 | init: init, 297 | login: login, 298 | logout: logout, 299 | revokePermissions: revokePermissions, 300 | api: api, 301 | oauthCallback: oauthCallback, 302 | getLoginStatus: getLoginStatus 303 | } 304 | 305 | }()); 306 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "0.1.0", 4 | "description": "Ionic Tutorial", 5 | "main": "server.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/ccoenraets/ionic-tutorial" 9 | }, 10 | "author": "Christophe Coenraets", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/ccoenraets/ionic-tutorial/issues" 14 | }, 15 | "homepage": "https://github.com/ccoenraets/ionic-tutorial", 16 | "dependencies": { 17 | "body-parser": "^1.3.1", 18 | "express": "^4.4.5", 19 | "method-override": "^2.0.2" 20 | }, 21 | "scripts": { 22 | "start": "node server.js" 23 | }, 24 | "keywords": [ 25 | "Ionic", 26 | "Tutorial" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /server/readme.md: -------------------------------------------------------------------------------- 1 | # Conference server -------------------------------------------------------------------------------- /server/routes/sessions.js: -------------------------------------------------------------------------------- 1 | var sessions = [ 2 | {id:0 , title:"Introduction to Ionic", speaker:"CHRISTOPHE COENRAETS", time:"9:40am", room:"Ballroom A", description: "In this session, you'll learn how to build a native-like mobile application using the Ionic Framework, AngularJS, and Cordova."}, 3 | {id:1 , title:"AngularJS in 50 Minutes", speaker:"LISA SMITH", time:"10:10am", room:"Ballroom B", description: "In this session, you'll learn everything you need to know to start building next-gen JavaScript applications using AngularJS."}, 4 | {id:2 , title:"Contributing to Apache Cordova", speaker:"JOHN SMITH", time:"11:10am", room:"Ballroom A", description: "In this session, John will tell you all you need to know to start contributing to Apache Cordova and become an Open Source Rock Star."}, 5 | {id:3 , title:"Mobile Performance Techniques", speaker:"JESSICA WONG", time:"3:10Pm", room:"Ballroom B", description: "In this session, you will learn performance techniques to speed up your mobile application."}, 6 | {id:4 , title:"Building Modular Applications", speaker:"LAURA TAYLOR", time:"2:00pm", room:"Ballroom A", description: "Join Laura to learn different approaches to build modular JavaScript applications."} 7 | ]; 8 | 9 | exports.findAll = function (req, res, next) { 10 | res.send(sessions); 11 | }; 12 | 13 | exports.findById = function (req, res, next) { 14 | var id = req.params.id; 15 | res.send(sessions[id]); 16 | }; -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | bodyParser = require('body-parser'), 3 | methodOverride = require('method-override'), 4 | sessions = require('./routes/sessions'), 5 | app = express(); 6 | 7 | app.use(bodyParser.json()); 8 | app.use(bodyParser.urlencoded({ 9 | extended: true 10 | })); 11 | app.use(methodOverride()); // simulate DELETE and PUT 12 | 13 | // CORS (Cross-Origin Resource Sharing) headers to support Cross-site HTTP requests 14 | app.all('*', function(req, res, next) { 15 | res.header("Access-Control-Allow-Origin", "*"); 16 | res.header("Access-Control-Allow-Headers", "X-Requested-With"); 17 | next(); 18 | }); 19 | 20 | app.get('/sessions', sessions.findAll); 21 | app.get('/sessions/:id', sessions.findById); 22 | 23 | app.set('port', process.env.PORT || 5000); 24 | 25 | app.listen(app.get('port'), function () { 26 | console.log('Express server listening on port ' + app.get('port')); 27 | }); 28 | --------------------------------------------------------------------------------