Summary

The purpose of the #propifyyourlife app is to present resources and images of yoga poses that promote knowledge and awareness of poses, as well as the promotion of the yoga community as a whole. More specifically, I wish to present alternative forms of practicing yoga to express my deep conviction that yoga is for every body, regardless of ability. Additionally, by building and maintaining this application, I am furthering my own knowledge of yoga and website development.

This initial verson of the app is a collection bin of blog posts, images, and videos of individual yoga poses. Future development will include tagging these web resources as it relates to the health benefits they provide.

Takeaways

The Code

Directives

Pose List View

  • Installs mouse event handlers onto the view for the Pose List
  • View The Code
    angular.module('YogaPoseApp').controller('PoseListCtrl', ['$scope', '$location', 'poseListService', function ($scope, $location, appService ) {
    	// DATA INIT
    	$scope.poses = [];
    	console.log('PoseListCtrl()');
    
    	$scope.status = "Initiate";
    	$scope.filtering = false;
    
    	$scope.activeTags = "";
    	$scope.tags = {};
    
    	$scope.isReady = false;
    
    	$scope.defaultImage = $scope.activePoseImg = 'http://placehold.it/350x350';
    	
    	// GET ALL POSES
    	appService.getPoses().then(function(response) {
    		console.log(response);
    		$scope.poseList = response.data;
    		$scope.isReady = true;
    	}, function(response) {
    		$scope.status = "Error getting poses";
    		console.log('Error fetching poses', response);
    	});
    
    	// LOAD TAGS
    	// Specifically, we load the pose types. Every pose belongs to at least one of these Pose Types.
    	// The user can filter the pose list with these specific tags
    	appService.loadPoseTypes().then(function(response) {
    		angular.forEach(response.data, function(data) {
    			$scope.tags[data.ID] = {ID: data.ID, tagName: data.tagName, filterOn: false};
    		})
    	}, function(response) {
    		$scope.status = "Error getting tags";
    		console.log('Error fetching tags', response);
    	});
    
    
    	// Custom filter to filter the poses by pose type.
    	$scope.filterList = function(pose) {
    		if(!$scope.filtering) 
    			return true;
    		else {
    			return $scope.activeTags.replace(/\s*,\s*/g, ',').split(',').every(function(cat) {
    				return pose.tags.some(function(objTag){
    					return objTag.ID.indexOf(cat) !== -1;
    				});
    			});
    		}
    	};
    
    	// Manages the ActiveTags (string) scope variable
    	$scope.toggleTag = function(tag) {
    		if($scope.activeTags.indexOf(tag) >= 0)
    			$scope.activeTags = $scope.activeTags.replace(tag+',', '');
    		else
    			$scope.activeTags += tag + ',';
    	}
    
    	// Changes the View.
    	$scope.go = function(path) {
    		$location.path(path);
    	}
    }]);
    

yogaLinkFeed

  • Serves as a view for the LinkList model.
  • View The Code
    angular.module('YogaPoseApp').directive('yogaLinkFeed', ['$compile', function($compile) {
    	console.log('yogaInstafeed()');
    	var tpl = "

yogaInstaFeed

  • Serves as a view for the Yogagram model.
  • View The Code
    angular.module('YogaPoseApp').directive('yogaInstaFeed', ['$compile', function($compile) {
    	console.log('yogaInstafeed()');
    	var tpl = "
    " + "

    From Instagram

    " + "
      " + "
    • " + "" + "" + "" + "
    • " + "
    " + "
    "; return { restrict: 'EC', replace: true, template: tpl, scope: false, link: function($scope, $el, attrs, $rootScope ) { $scope.$watch(function() { return $scope.pose; }, function(newVal) { if(newVal) $el.append(""); }); } } }]);

yogaVideoList

  • Serves as a view for the VideoList model.
  • View The Code
    angular.module('YogaPoseApp').directive('yogaVideoList', ['$compile', function($compile) {
    	console.log('yogaVideoList()');
    	var rootTpl = "

    {{videoListStatus}}

    " + "
    " + "
    {{title}}
    " + "
    " + "
    {{video.title}}
    " + "

    {{video.title}}

    " + "
    {{video.duration}}
    " + "
    " + "
    " + "
    "; return { restrict: 'AC', replace: true, template: rootTpl, scope: false, link: function($scope, $el, attrs, $rootScope ) { var videoTpl = "
    " + "
    {{title}}
    " + "
    " + "
    {{video.title}}
    " + "

    {{video.title}}

    " + "
    {{video.duration}}
    " + "
    " + "
    "; $scope.$watch(function(scope) { return scope.videoList; }, function(newVal, oldVal) { if(newVal.length && false) { var newItem = newVal[newVal.length-1]; var html = videoTpl; html = html.replace('%title%', newItem.title, 'g'); html = html.replace('%img%', newItem.img, 'g'); html = html.replace('%locator%', newItem.locator, 'g'); html = html.replace('%duration%', newItem.duration, 'g'); $el.append($compile(html)($scope)); } }, true); } } }]);

yogaMediaViewer

  • Element to facilitate the display of each pose asset.
  • View The Code
    angular.module('YogaPoseApp').directive('yogaMediaViewer', function() {
    	return {
    		restrict: 'AC',
    		/*scope: {
    			pose: '=',
    			mediaObj: '='
    		},*/
    		scope: false,
    		replace: true,
    		template: '
    ', link: function($scope, $el, $attrs) { //scope.watch mediaObj var contentTpl= { youtube: function(obj) { console.log(obj); var html = '

    '+obj.title+'

    '; if(obj.tags && obj.tags.length) { html += '

    Tags: '; for(var i = 0; i < obj.tags.length; i++) { html += '' + obj.tags[i].tagName + ''; if(i < obj.tags.length - 1) html += ', '; } html += '

    '; } html += '
    ' return html; }, instagram: function(obj) { var html, user, type, media, date, caption; var titleEL, mediaRow, captionRow; type = obj.type; user = obj.user, media = obj.media, date = obj.date caption = obj.caption; titleEL = "
    "; titleEL += "

    "+user.name+"

    Published:"+date+"
    "; if(type == "video") { console.log('showVideo'); mediaRow = "
    "; } else { mediaRow = "
    "; } captionRow = "

    Caption

    "+caption+"

    "; if(obj.myTags && obj.myTags.length) { captionRow += "

    Internal Tags:

    "; for(var i = 0; i < obj.myTags.length; i++) { captionRow += '' + obj.myTags[i].tagName + ''; if(i < obj.myTags.length - 1) captionRow += ', '; } captionRow += '

    '; } return titleEL + mediaRow + captionRow; } } $scope.$watch('mediaObj', function(newVal, oldVal) { if(newVal) { var content = ''; if((typeof newVal) == "string") { if(newVal.match(/\.(?:jpg|gif|png)$/)) { content = ''; } else if(newVal.length == 11) { content = contentTpl.youtube(newVal); } } else if((typeof newVal) == "object") { if((newVal.link && newVal.link.indexOf('instagram') > -1) || newVal.asset_type == 4) content = contentTpl.instagram(newVal); else if(newVal.locator) content = contentTpl.youtube(newVal); } else { content = ""+newVal+""; } $el.find('.media-content').html(content); console.log('showing', newVal); $scope.$emit('newMedia', newVal) } }, true); } } });

Services

PoseListService

  • Supports the Pose List View by getting the list of poses, and the types of poses to facilitate filtering.
  • View The Code
    angular.module('YogaPoseApp').service('poseListService', ['$http', function($http, $q) {
    	var poseData = false;
    	return ({
    		getPoses: function() {
    			var request;
    
    			if(!poseData) {
    				request = $http.get('./api/getAllPoses', {cache: true});
    				return (request.then(function(response) {
    			    		return response.data;
    			    	}, function(response) {
    			    		if(!angular.isObject(response.data) || !response.data.message)
    			    			return ($q.reject("An uknown error has occured"));
    
    			    		return ($q.reject(response.data));
    			    	}));		
    			}
    		},
    		loadPoseTypes: function () {
    			var request =  $http.get('./api/loadTags?query=poseTypes', {cache: true});
    			return (request.then(function(response) {
    			    		return response.data;
    			    	}, function(response) {
    			    		if(!angular.isObject(response.data) || !response.data.message)
    			    			return ($q.reject("An uknown error has occured"));
    
    			    		return ($q.reject(response.data));
    			    	})
    			);
    		}
    	});
    }])
    

SinglePoseService

  • Supports the Single Pose View by getting Pose info, getting it’s assets, then going out to the web to find more data about those assets.
  • View The Code
    angular.module('YogaPoseApp').service('singleoseService', ['$http', function ($http, $q) {
    	return ({
    		getPose: getPose,
    		getAssets: getAssets,
    		getInstagrams: getInstagrams,
    		getSingleInstagram: getSingleInstagram,
    		getVideoInfo: getVideoInfo,
    		getPageInfo: function(url) {
    
    		var request = $http.get('./api/getPageInfo?url='+url, {cache: true});
    		return (request.then(handleSuccess, handleError));
    		},
    	});
    
    
    	function getPose(name) {
    		var request, requestObj;
    		if(Number.isInteger(name)) {
    			requestObj = {'ID': name};
    		}
    		else
    			requestObj = {'sanskritName': name};
    		request = $http.get('./api/getPose/'+name, {cache: true});
    		return (request.then(handleSuccess, handleError));		
    	}
    
    	function getAssets(poseID) {
    		var request = $http.get('./api/getAssets/'+poseID, {cache: true});
    		return (request.then(handleSuccess, handleError));
    	}
    
    
    	
    	function getInstagrams(tag) {
    		var base = 'https://api.instagram.com/v1/tags/' + tag + '/media/recent?access_token=';
    		var token = '211521125.467ede5.87ba7f407a0c40b191db7899a75b116c';
    		var end = "&callback=JSON_CALLBACK";
    
    		var request =  $http.jsonp(base + token + end);
    		return (request.then(handleSuccess, handleError));
    	};
    	function getSingleInstagram(shortcode) {
    		var base = 'https://api.instagram.com/v1/media/shortcode/'+shortcode+'?access_token=';
    		var token = '211521125.467ede5.87ba7f407a0c40b191db7899a75b116c';
    		var end = "&callback=JSON_CALLBACK";
    		var request =  $http.jsonp(base + token + end);
    		return (request.then(handleSuccess, handleError));
    	};
    
    	function getVideoInfo(videoID) {
    		var key = "AIzaSyBVBe0KqROzVUDK31iOC2Tjpc4ViSVeUVI";
    		var url = "https://www.googleapis.com/youtube/v3/videos?id="+videoID+"&key="+key+"&part=snippet,contentDetails";
    
    		return $http.get(url, {cache: true});
    	}
    
    	function handleSuccess(response) {
    		return response.data;
    	}
    	function handleError(response) {
    		if(!angular.isObject(response.data) || !response.data.message)
    			return ($q.reject("An uknown error has occured"));
    
    		return ($q.reject(response.data));
    	}
    }]);
    			

Controllers

Pose List Ctrl

  • Initializes retrieval of data from the service.
  • Key Functions:
    • filterList() : A custom Filter

      $scope.filterList = function(pose) {
      	if(!$scope.filtering) 
      		return true;
      	else {
      		return $scope.activeTags.replace(/\s*,\s*/g, ',').split(',').every(function(cat) {
      			return pose.tags.some(function(objTag){
      				return objTag.ID.indexOf(cat) !== -1;
      			});
      		});
      	}
      };
      					
    • toggleTag(): Manages the $scope.activeTags variable

      // Manages the ActiveTags (string) scope variable
      $scope.toggleTag = function(tag) {
      	if($scope.activeTags.indexOf(tag) >= 0)
      		$scope.activeTags = $scope.activeTags.replace(tag+',', '');
      	else
      		$scope.activeTags += tag + ',';
      }
      
      					

Single Pose View

  • Initializes retrival of data for a single pose. Once the data is returned from the service, the controller retrives any assets saved for that pose.
  • Controls the models used in the Single Pose View. Models include
    • LinkList
    • Yogagrams (Instagram posts)
    • VideoList
  • View The Code
    angular.module('YogaPoseApp').controller('SinglePoseCtrl', ['$scope', '$routeParams', 'singlePoseService', '$q', '$rootScope', function($scope, $routeParams, appService, $q, $rootScope) {
    	console.log('SinglePoseCtrl()');
    	var poseName = $routeParams.sanskritName;
    	
    	var camelize = function (str) {
    	  return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(letter, index) {
    	    return index == 0 ? letter.toLowerCase() : letter.toUpperCase();
    	  }).replace(/\s+/g, '');
    	}
    
    	$scope.pose = null;
    	$scope.viewportStatus = 'Init';
    
    	$scope.videoList = [];
    	$scope.videoListStatus = "Videos";
    
    	$scope.linkList = [];
    	$scope.LinkListStatus = false;
    	$scope.imgAssetList = [];
    	$scope.yogagramList = [];
    	$scope.instagrams = [];
    
    	$scope.mediaObj = null;
    
    	$scope.myTags = [];
    	/*
    	$scope.loadTags = function(query) {
    		console.log(query);
    		return appService.loadTags(query);
    	};
    	$scope.saveTags = function() {
    		console.log($scope.myTags);
    		appService.saveTags({tags: $scope.myTags, poseID: $scope.pose.ID}).then(function(response) {
    			console.log('success saving tags');
    		}, function(response) {
    			console.log("an error has occured while saving tags", response);
    		})
    	}
    	*/
    
    	
    	appService.getPose(poseName).then(function(response) {
    		var poseID, tagName;
    		
    		$scope.pose = response.data;
    		
    		poseID = $scope.pose['ID'];
    		tagName = camelize($scope.pose['sanskritName']);
    
    		getAssets(poseID);
    
    		$rootScope.pageTitle = $scope.pose['sanskritName'] + ' | ' + $scope.pose['englishName'];
    
    	}, function(response) {
    		$scope.status = "Error getting pose";
    		console.log('Error fetching pose: ' + poseName, response);
    	});
    
    	$scope.getVideoList = function() { return $scope.videoList; };
    
    	$scope.view = function(asset) {
    		$scope.mediaObj = asset;
    		return false;
    	}
    
    	$scope.getPageInfo = function(asset) {
    		var url = asset.locator, myAsset = asset;
    		appService.getPageInfo(url).then(function(response) {
    			for(var attr in response.data) {
    				myAsset[attr] = response.data[attr];
    			}
    			$scope.linkList.push(myAsset);
    			$scope.LinkListStatus = false;
    
    
    		}, function(response) {
    			$scope.status = "Error getting page";
    			console.log('Error fetching page: ' + url, response);
    		});
    	}
    
    
    
    
    	var getAssets = function(poseID) {
    		$scope.videoListStatus = "Videos Loading...";
    		$scope.LinkListStatus = "Links Loading...";
    		appService.getAssets(poseID).then(function(response) {
    			var data = response.data;
    			for(var i = 0; i < data.length; i++) {
    				switch(data[i].fk_type) {
    					case '1':
    						//image
    						$scope.imgAssetList.push(data[i])
    						break;
    					case '2':
    						//video
    						pushVideoItem({locator: data[i].locator, tags: (data[i].tags || []),  ID: data[i].ID, notes: data[i].notes});
    						break;
    					case '3':
    						$scope.getPageInfo(data[i]);
    						break;
    					case '4':
    						pushYogagram({locator: data[i].locator, tags: (data[i].tags || []),  ID: data[i].ID, notes: data[i].notes});
    						break;
    					default:
    						break;
    				}
    
    			}
    
    			if(!$scope.videoList.length) {
    				$scope.videoListStatus = "";
    			}
    			$scope.$broadcast('yogaAssetsLoaded');
    				
    
    		}, function(response) {
    			$scope.status = "Error getting pose asset";
    			console.log('Error getting pose assets', poseID, response);
    		});
    
    	};	
    	var getInstagrams = function(tag) {
    		/*var deferred = $q.defer();
    		deferred.notify();
    
    		*/
    		appService.getInstagrams(tag).then(function(response) {
    			var data = response.data;
    			console.log(data);
    			//$scope.instagrams = data;
    		}, function(response) {
    			$scope.status = "Error getting pose asset";
    			console.log('Error getting pose assets', tag, response);
    		});
    
    
    	};
    	var pushVideoItem = function(vidObj) {
    		var obj = angular.copy(vidObj);
    		var response = appService.getVideoInfo(obj.locator).then(function(response) {
    			var data = response.data;
    
    			obj.duration= (function(t) {
    				var t = t.replace("PT",'').replace('M',':').replace('S', '');
    				var explode = t.split(":");
    				if(!explode[1] || explode[1] == "")
    					explode[1] = "00";
    				return explode[0]+":"+explode[1].replace(/(^|[^0-9])([0-9]{1})($|[^0-9])/, '0$2');
    			})(data.items[0].contentDetails.duration);
    			obj.title = data.items[0].snippet.title;
    			obj.img = data.items[0].snippet.thumbnails.default.url;
    			//console.log('pushing', obj);
    			obj.asset_type = 2,
    			$scope.videoList.push(obj);
    		}, function(response) {
    			$scope.status = "Error getting video info";
    			console.log('Error getting video info', videoID, response);
    		});
    	}
    	var pushYogagram = function(yogObj) {
    		var myYogObj = angular.copy(yogObj);
    		var response = appService.getSingleInstagram(myYogObj.locator).then(function(response) {
    
    			var obj, user, type, media, date, date_str, caption;
    			var data = response.data;
    			if(response.meta.code == 200) {
    				type = data.type;
    				user = {
    					name: data.user.username,
    					pic: data.user.profile_picture
    				};
    
    				media = {
    					obj: data[type+'s'],
    					link: data.link
    				};
    
    				date = new Date(data.created_time * 1000);
    				date = date.toString('MMMM d, yyyy');
    
    				caption = data.caption.text.parseInstagramHashtag();
    
    				yogagram = {
    					type: type,
    					user: user,
    					media: media,
    					caption: caption,
    					myTags: myYogObj.tags,
    					notes: (myYogObj.notes == "-1") ? false : myYogObj.notes,
    					ID: myYogObj.ID,
    					date: date,
    					asset_type: 4
    				};
    
    				$scope.yogagramList.push(yogagram);
    			}
    			else
    				console.log('no instagram data', data);
    
    		}, function(response) {
    			$scope.status = "Error getting yogagram";
    			console.log('Error getting yogagram', yogObj, response);
    		});
    	}
    	
    }]);