Angular JS game - Creating and destroying sub-controllers with methods - javascript

I'm creating an AngularJS and HTML5-based game that consists of users finding and clicking on birds. I want birds to appear at random places on the screen. If you click a bird - or if you wait more than 5 seconds - the bird automatically disappears.
I'm new to Angular, but I thought I would approach this problem by...
Creating a controller that is responsible for managing game
sessions (time limit, difficulty, etc.)
Using that
controller to create Bird objects (sub-controllers?) at a regular
interval.
Putting logic into each of the Bird objects so
that they automatically destroy themselves after 5 seconds or if
they are clicked.
Here's the main controller that created for part 1 of my problem:
myGameModule.controller( 'BirdActivityCtrl', function BirdActivityCtrl($scope) {
$scope.difficulty = 'Easy';
$scope.reward = 250;
$scope.spawn_interval = 1000;
$scope.status = 'starting';
$scope.birds_required = 30;
$scope.birds_clicked = 0;
$scope.time_left = 60;
$scope.start = function(){
location = '#/birds_in_progress';
$scope.status = 'in_progress';
}
$scope.cancel = function(){
location = '#/cafeteria';
}
});
Specifically, I am asking for help with Parts 2 and 3 of my question (mentioned above). I know that Angular has strict conventions for separating DOM elements from controllers. What is the correct way to spawn bird objects (which will be tied to DIVs on the page) and to destroy them after 5 seconds? Thank you for reading. Any help will be greatly appreciated!

Your DOM-related work should be in directives. Any directive can have it's own controller, it will be your BirdController. For any new bird instance (div with directive) new controller and scope will be created.
I suggest you put all your birds in some data structire in a service accessible from any part of your app with DI. Then you can simply use ng-repeat for you birds!
Something like this pseudocode should work:
game.factory('BirdStorage', ['in', 'ject', 'ables', function(){
var birds = [];
return {
addBird : function(){
birds.push({...})
},
deleteBird : function(id){
...
},
...
}
}]);
game.directive('bird', ['in', 'ject', 'ables', function(){
return {
restrict: 'EA',
template: '<div>...</div>',
replace: true,
scope: {
...
}
controller: function($scope, $element, $attrs){
...
}
link: function(){
...
}
}
}]);
Then you can use it in HTML like an element:
<ul>
<li data-ng-repeat="bird in birds">
<bird attrs=...>
</li>
</ul>
birds will come from a service you defined earlier.
And a logic to remove bird will come to BirdStorage. Just create a timeout that will delete specified bird:
setTimeout(function(){
this.deleteBird(id);
}, 5000)

Related

Update background image without refresh angularjs

Hey everyone I am currently building a mobile app for my company and I have a angularjs function that is currently adding a css class based on whether it's day or night. It does what it's supposed to do perfectly by putting a day or night image based on whether it's day or night.
The issue I am running into is that when testing the app right before the clock let's say goes from 1:59 to 2 it doesn't change the background image until you refresh the page. I want it to automatically change the background image without having to refresh and can't seem to find a way to do it. I will link the code here!
Any help is appreciated as it has me completely stumped...
Here is what I have in my html.
<div class="dashboard-welcome" ng-class="{'hide-homescreen-image-landscape'
: isLandscape, 'homescreenImageDay' : isDay, 'homescreenImageNight' :
isNight }">
Here is where the function is being called
angular.module('starter').controller('homeCtrl', ['$scope', '$interval',
'jsBridge', 'authService', 'tierService', '$window', fnc]);
function fnc($scope, $interval, jsBridge, authService, tierService, $window)
{
$scope.$on('$ionicView.afterEnter', function (event, viewData) {
setOrientation();
$scope.isDay = !isDayTime(1000);
$scope.isNight = isDayTime(1000);
});
Here is where the function is instantiated. Basically checking what time it is.
var isDayTime = function () {
var h = new Date().getHours();
if (h >= 14) {
return true;
} else {
return false;
}
}
I can't supply all the code since this application is thousands of lines long but this is the working function as of now. Just need it to switch background images without refreshing using angularjs...
Assuming that inside your div you are using an background-image: url("...")
I suggest you set up an object to hold a single $scope.isDay value instead of doing the calculation twice.
Also use the $interval service to check every nth millisecond to update your $scope.isDay value.
The code below works fine in dynamically changing a background image on a page using its CSS class.
HTML:
<div ng-class="data.isDay ? 'back1' : 'back2'"></div>
JS:
var exampleApp = angular.module('exampleApp', []);
exampleApp.controller('exampleController', function($scope, $interval) {
$scope.data = {
isDay: true,
classVal: {
one: 'text-danger',
two: 'text-success'
}
};
$interval(function() {
$scope.toggleImage();
}, 600);
$scope.toggleImage = function() {
$scope.data.isDay = ($scope.data.isDay ? false : true)
};
$scope.toggleImage();
});
Here is also a plnkr demo

angularjs: setting a scope variable from directive

First what I want:
I have a series of thumbnails. When I click on one, I want that specific thumbnail to be shown in a bigger div, with its description. (For later: should be animated).
Now I have the directive and the controller, but I don't know how to set the appropriate variable!
So some code:
First HTML: here is the root .jade file for this section. I have a directive called product.
section
.detail-view(ng-show="vm.showDetail")
.prod-desc(ng-bind="vm.detailPic")
.prod-img(ng-bind="vm.detailDesc")
product.col-xs-12.product_tile(ng-repeat="item in vm.products", item="::item")
As you can see, the product directive is part of an ng-repeat; for this reason, the div I want to show the resized image is outside the iteration (.detail-view).
The product directive:
'use strict';
ProductDirective.$inject = ['ShopCart', '$animate', 'User'];
function ProductDirective(ShopCart, $animate, User) {
return {
restrict: 'E',
scope: {
item: '='
},
template: require('./product.html'),
link: link
};
function link(scope, element) {
scope.toggleDetails = toggleDetails;
}
function toggleDetails() {
if (!scope.isSelected && isBlurred()) return;
scope.vm.detailPic = scope.item.photo;
scope.vm.detailDesc = scope.item.prod_description;
scope.vm.isSelected = !scope.isSelected;
scope.showDetail = !scope.showDetail;
var action = scope.isSelected ?
}
}
Now the div I want to update with the image in big is outside the iteration - and hence outside the scope of the directive. How can I set the value of showDetail, showDesc and showPic?
As I am using controllerAs with value vm, I thought I could just do scope.vm.detailPic = scope.item.photo;, as in other solutions I have seen that when setting a property on a root object, it would be propagated...but I get
Cannot set property 'detailPic' of undefined
For now, what works (but looks a bit odd to me) is this, in toggleDetails()
scope.$parent.$parent.vm.detailPic = scope.item.photo;
scope.$parent.$parent.vm.detailDesc = scope.item.prod_description;
scope.$parent.$parent.vm.showDetail = !scope.$parent.$parent.vm.showDetail

How to repurpose my directive here to work in 2 different scopes?

http://plnkr.co/edit/39FGMocKB5GtQWnI1TFw?p=preview
I have sidebar which contains a list of tags, when you click on a tag I use the TagDetailsFactory to send a tag into the scope of the view controller.
Everything works great except for when you hover over a tag in the TagDetailsFactory scope.
The tagDetails template does not show up, however if you hover over the same tag in the sidebar scope, the tagDetails shows up in both. This is wrong.
Hovering over a tag in the sidebar should only show the tag details for that tag and the same for the tags inside the view scope.
Hovering over a tag in the view scope, doesn't display it's details
Hovering over a tag inside of the sidebar scope, should only show details for it's tag, and not the tag in the view scope, like it does here:
Steps:
- The first tags Array is in the cnt controller
- When you click on a tag, it gets stored in the TagDetailsFactory service
- I then broadcast an event to the view controller to then call the getTagDetails function in TagDetailsFactory to retrieve the saved tags and store them into the viewTags array in the view controller.
// Code goes here
angular.module('app', [])
.directive('tagDetails', function() {
return {
restrict: "E",
link: function($scope, el, attrs) {
// console.debug($scope, attrs);
},
scope:{
tag:'='
},
template: '<div ng-show="tag.showDetails">{{tag.details}}</div>'
};
})
.factory('TagDetailsFactory', function() {
var savedTags = [];
var saveTagDetails = function(tag) {
savedTags.push(tag);
}
var getTagDetails = function() {
return savedTags;
}
return {
saveTagDetails : saveTagDetails,
getTagDetails : getTagDetails
};
})
.controller('sidebar', function($scope,
$rootScope,
TagDetailsFactory) {
$scope.tags = [];
for(var i = 0; i < 10; i++) {
$scope.tags.push(
{ name: 'Foo Bar ' + i, details: 'Details' + i }
);
}
$scope.showTagDetails = function(t) {
t.showDetails = true;
}
$scope.leaveTag = function(t) {
t.showDetails = false;
}
$scope.sendTag = function(t) {
TagDetailsFactory.saveTagDetails(t);
$rootScope.$broadcast('updateView');
}
})
.controller('view', function($scope,
$rootScope,
TagDetailsFactory) {
$scope.viewTags = [];
$scope.$on('updateView', function() {
$scope.viewTags = TagDetailsFactory.getTagDetails();
});
$scope.showTagDetails = function(v) {
v.showDetails = true;
}
$scope.leaveTag = function(v) {
v.showDetails = false;
}
});
Do I have to create a 2nd directive here? To be the template for the tag details in the view scope? Or can my current tagDetails directive be repurposed somehow in an Angular way?
I forked your Plunker with a working copy, and I'll explain the changes I made.
You have two issues with the code here. The first is a simple typo, which is causing your header to not reference the correct function for mouseover. Your functions are calling showTagDetailsView(v)and leaveTagView(v), but they are named showTagDetails and leaveTag on the controller.
The second issue is with the way that the items are added to the savedTags[]. In JavaScript, objects are passed by reference. when you call savedTags.push(tag);, you are pushing a reference to the same object into the new array. Any changes made to the object in one array will be reflected in the other array.
Instead, what you want is a separate copy of the object in the savedTags[]. This can be accomplished by using angular.copy. Note that I also reset tag.showDetails = false; before making the copy, else the new copy will have it set to true, and the details will be showing the instant the copy appears, even though you are hovering over the other element when you click it.
var saveTagDetails = function(tag) {
tag.showDetails = false;
savedTags.push(angular.copy(tag));
}
Just a side note, you might also have an issue with CSS here, as hovering seems to change the position of the lists, and in some cases the hover actually causes the tag to move itself out of the hover, causing a bounce effect.

Two boards sharing same slider (jsxGraph), posible?

Like titles states I need two boards sharing the same slider. Lets say
var s = board3.create('slider',[[-10,-5],[-5,-5],[-11,-11,5]]);
and then another board (board4) to have the same slider meaning it reacts on on the slide of slider s
Is this possible? How to do it?
Two boards can be connected with the command "board.addChild()". In your case, there are the two boards "board3" and "board4". Whenever board3 is updated, board4 should also receive a board update event call. This can be achieved by the call of "board3.addChild(board4)":
var board3 = JXG.JSXGraph.initBoard('jxgbox3', {axis:true, boundingbox: [-15,10,15,-10], keepaspectratio:false}),
board4 = JXG.JSXGraph.initBoard('jxgbox4', {axis:true, boundingbox: [-15,10,15,-10], keepaspectratio:false});
board3.addChild(board4);
// slider s in board3 and
// point p in board4 which reacts to slider s
var s = board3.create('slider', [[-10,-5],[-5,-5],[-11,-11,5]]),
p = board4.create('point', [function() {
return s.Value();
},
function() {
return 0.5 * s.Value();
}]);

How to make a child directive aware of all of its parents in angular?

This is a slightly long one.
How can I have child directives that are aware of all of their ancestors, not just their immediate parent?
The reason I'm asking is that I want to have a Raphael paper directive that provides a reference to a Raphael paper to all its children. Also, I'm trying to have a "rl-shape" directive that can group different shapes together so they can be translated, transformed, etc. together all at once. I'd also like this "rl-shape" directive to be able to appear inside of itself, allowing for arbitrary trees of shapes.
There might be a completely different, better way to do this. If so, please correct me.
Here's the code I have so far:
<!doctype html>
<html xmlns:ng="http://angularjs.org" ng-app="myApp">
<head>
<title>Test</title>
<script src="js/underscore.js"></script>
<script src="js/raphael-min.js"></script>
</head>
<body>
<rl-paper>
<rl-shape name="myShape">
<rl-circle name="inner" cx="0" cy="0" r="50"></rl-circle>
<rl-circle name="outer" cx="0" cy="0" r="100"></rl-circle>
</rl-shape>
</rl-paper>
<p>
<button ng-click="myShape.translate(0, -10)">Move shape up</button>
<button ng-click="myShape.translate(0, 10)">Move shape down</button>
</p>
<script src="js/angular.min.js"></script>
<script>
var myApp = angular.module("myApp", []);
function Shape(children) {
this.translate = function(dx, dy) {
_.each(children, function(c) { c.translate(dx, dy); });
};
}
myApp.directive("rlPaper", function() {
return {
restrict: "E",
controller: function($element) {
this.paper = new Raphael($element[0], 220, 220);
this.paper.setViewBox(-110, -110, 220, 220);
}
};
});
myApp.directive("rlShape", function () {
return {
restrict: "E",
require: ["^rlPaper"],
controller: function($scope, $element, $attrs, $transclude) {
this.children = [];
},
link: function(scope, element, attrs, ctrls) {
// How can the link function of the rlShape directive access its
// own controller? If I could figure that out, I could do
// something like the following:
var shapeCtrl = undefined; // Don't know how to get this
var shape = Shape(shapeCtrl.children);
scope[attrs.name] = shape;
}
};
});
myApp.directive("rlCircle", function() {
return {
restrict: "E",
require: ["^rlPaper", "?^rlShape"],
link: function(scope, element, attrs, ctrls) {
var paperCtrl = ctrls[0];
var shapeCtrl = ctrls[1];
var circle = paperCtrl.paper.circle(attrs.cx, attrs.cy, attrs.r);
scope[attrs.name] = circle;
if ( shapeCtrl ) {
shapeCtrl.children.push(circle);
}
}
};
});
</script>
</body>
</html>
(You have quite a few questions in your question. You'll get better, and likely faster, answers, if you post separate questions for each, with a minimalistic fiddle or plunker demonstrating the problem/question.)
How can I have child directives that are aware of all of their ancestors, not just their immediate parent?
If a directive does not create an isolate scope (in the sample code you provided, none of your directives are doing this), then the directive has access to all ancestors' $scopes via normal JavaScript prototypal inheritance.
So if you define your data on your directives' $scopes, the child directives will be able to access that data directly:
E.g., if you have this code in a parent directive's controller function:
$scope.paper = new Raphael($element[0], 220, 220);
Then child directives can access it (in their controller or link functions):
var paper = $scope.paper; // prototypal inheritance will find it
I want to have a Raphael paper directive that provides a reference to a Raphael paper to all its children.
So, in the rlPaper directive, define a "Raphael paper" object on that directive's $scope (not on this). Do this in the directive's controller function, not the link function, because the link function will run too late. (Fiddle showing when directive's controller and link functions run, with two nested directives.)
How can the link function of the rlShape directive access its own controller?
There is a way to do this, but I think the better way is to put your data and functions on the directive's $scope, which is shared by the directive's link and controller functions.
In the rlCircle directive, you don't have to require other parent controllers if you instead put your data onto the $scope objects.

Categories