$BROADCASTing from service to controller - javascript

I have a little concern here
This comes from a service named BetSlipFactory
removeSlip: function(slip) {
return betSlipSelectionRequest('/betSlip/removeSelection', {
game: slip.game,
pair: slip.pair,
line: slip.line
});
}
Then I have this function in the controller for that service
$scope.removeSlip = function(slip) {
$rootScope.$broadcast('betSlip:removeLines', slip);
BetSlipFactory.removeSlip(slip)
}
Next I have a controller in a different scope named LinesCtrl and I have this function here which calls a couple functions from the service BetSlipFactory which is like a kind of toggle function
$rootScope.$on('betSlip:removeLines', function(event, slip) {
if (slip) {
BetSlipFactory.remove(line, row, type);
};
});
$scope.addLineToBetSlip = function(line, row, type) {
var spreadSelected = (row.spreadSelected && type === 'spread'),
totalSelected = (row.totalSelected && type === 'total'),
moneyLineSelected = (row.moneyLineSelected && type === 'moneyline');
if (spreadSelected || totalSelected || moneyLineSelected) {
BetSlipFactory.remove(line, row, type);
}else {
BetSlipFactory.add(line, row, type);
}
};
And then the HTML:
<button ng-click="removeSlip(slip)"></button>
And:
<td ng-class="!row.moneyLineSelected ? 'lines-hover' : 'line-selected'">
<a ng-click="addLineToBetSlip(line, row, 'moneyline')">
<span ng-hide="row.noMoneyLine">{{:: row.moneyLine}}</span>
</a>
</td>
What I need: combine the scopes, when the function $scope.removeSlip(slip) is call, also I need to call $scope.addLineToBetSlip(line, row, type) and then that function should call BetSlipFactory.remove(line, row, type); as it is within that if statement.
When I call $scope.removeSlip(slip) I need to kill slip parameter, within the scope of BetSlipFactory everything works great.
I recorded a video for you to see what I am talking about, let me explain the video a little bit.
In the first 2 tries you might see that I am able to select and deselect and everything works great, but in the 3rd and 4th try, you see that I select a line, and then I go a call and removeSlip(slip) when I play the X on the right, and in order to deselect the line on the left I have to do it manually.

So I started a fiddle showing this process dumbed way down compared to the plnkr you started after. Here I am using two separate controllers and a service (factory) to manage the data. This can be done without using $rootScope or $broadcast. Hopefully you can take what I have done here and integrate it into all that code you posted on plnkr. Below you can see it is quite a simple process
the jsfiddle
HTML:
<div ng-app="TestApp">
<div id="colLeft" ng-controller="LeftController">
<div ng-repeat="bet in possibleBets">
<button ng-class="!bet.moneyLineSelected ? 'lines-hover' : 'line-selected'" ng-click="addLineToBetSlip(bet)">{{bet.name}}</button>
</div>
</div>
<div id="colRight" ng-controller="RightController">
Your Bets:<br>
<div ng-repeat="bet in bets">
Active bet: {{bet.name}} - <button ng-click="removeLineFromBetSlip(bet)">×</button>
</div>
</div>
</div>
CSS:
.lines-hover {
}
.line-selected {
background:yellow;
}
#colLeft {
width:65%;
background:#f00;
float:left;
}
#colRight {
width:35%;
background:gray;
float:left;
}
and finally the JS
var app = angular.module('TestApp',[]);
app.controller('LeftController', function($scope, BetSlipFactory)
{
// this data is the data from your DB
$scope.possibleBets = [
{name:'Bet 1',moneyLineSelected:false},
{name:'Bet 2',moneyLineSelected:false},
{name:'Bet 3',moneyLineSelected:false}
];
// now that I think about it, addLineToBetSlip is not a good name
// since it actually toggles the bet
$scope.addLineToBetSlip = function(bet)
{
bet.moneyLineSelected = !bet.moneyLineSelected; // toggle the moneyLineSelected boolean
(bet.moneyLineSelected) ? BetSlipFactory.add(bet) : BetSlipFactory.remove(bet); // add or remove the bet
};
});
app.controller('RightController', function($scope, BetSlipFactory)
{
$scope.bets = BetSlipFactory.getAllBets(); // link to all the active bets
// remove the bet from the factory
$scope.removeLineFromBetSlip = function(bet)
{
bet.moneyLineSelected = false;
BetSlipFactory.remove(bet);
};
});
app.service('BetSlipFactory', function()
{
//a place to keep active bets
var theBets = [];
return {
add: function(bet)
{
// actually add the bet to this local array
theBets.push(bet);
},
remove: function(bet)
{
// you should do error checking of the index before removing it
var index = theBets.indexOf(bet);
theBets.splice(index,1);
},
getAllBets: function()
{
//simply return all active bets
return theBets;
}
}
});
function log(msg)
{
console.log(msg);
}

Related

How to handle different content depending on selected element in the same sidebar

Imaging that we have animals table. Each row describes one animal, for example: ID, NAME, TYPE.
Depending on the selected row type, I want show is the sidebar content related to that animal and some user actions.
Content is completely different, it pulls from data from different APIs.
But the sidebar placed always in the same position, same size and styles.
Maybe I'll have common actions for each controller, like -> close sidebar.
If sidebar already opened and user switch to another one, sidebar should change immediately.
How should I design such login with angular ?
I got an idea to define one directive in html for sidebar. And set listener for selected row, after that compile dynamically sidebar directive for selected row, and insert into parent (main) sidebar.
Probably also I need to handle destroy of previous one.
I appreciate if anyone can tell is I'm going the right way... or should I change something ?
function dtSidebarDirective($compile, $mdUtil, $mdSidenav, $log, mxRegistry) {
return {
restrict: 'E',
templateUrl: 'app/components/sidebar/sidebar.html',
controller: function($scope) {
// used to replace sidebar data on the fly without recompile
$scope.refresh = function() { }
$scope.close = function(ev) {
$mdSidenav('right').close()
}
},
scope: true,
link: link
};
function link(scope, element) {
// used to detect switching between the same type of elements
var _activeType;
var _childDirective;
var _childScope;
var _childElement;
var _toggle = $mdUtil.debounce(function() {
$mdSidenav('right')
.toggle()
.then(function() {
scope.isOpen = $mdSidenav('right').isOpen();
$log.debug('toggle right is done');
});
});
var _init = function(type, data) {
// by default open diagram sidebar
switch(type) {
case 'shape':
_childDirective = $compile('<dt-dog-sidebar></dt-dog-sidebar>');
break;
case 'text':
_childDirective = $compile('<dt-cat-sidebar></dt-cat-sidebar>');
break;
default:
_childDirective = $compile('<dt-animal-sidebar></dt-diagram-sidebar>');
}
// initialize child sidebar : element & scope
_activeType = type;
_childScope = scope.$new();
_childScope.data = data;
_childElement = _childDirective(_childScope);
element.find('md-sidenav').append(_childElement);
};
var _isInitialized = function(type) {
var isDefined = angular.isDefined(_childDirective);
return type ? _activeType == type && isDefined : isDefined;
};
var _destroy = function() {
if(_isInitialized()) {
_childScope.$destroy();
_childElement.empty();
_childElement.remove();
}
};
function showSidebar(ev, type, data) {
// lets figure out does we open the same kind of sidebar
if(_isInitialized(type)) {
_childScope.data = data;
_childScope.refresh();
return;
}
// destroy since we gonna replace with new sidebar
_destroy();
_init(type, data);
}
function toggle() {
update();
_toggle();
}
function update(ev) {
// detect which sidebar should be shown now
}
scope.$on('sidebar:toggle', toggle);
scope.$on('sidebar:show', showSidebar);
scope.$on('sidebar:update', update);
}
I manage to get it work with recompiling each time I need to should different sidebar or call refresh of children scope.

callback still doesn't work

I wrote post several hours ago, where I have several interesting answers, but I don't uderstand everything and now I can't use that post. So I create one new.
I sill have problem with callback in this code:
<div id='temp'></div>
<div id='points'></div>
<div onClick ="play(1, 1)" id='click'>clickclickclick</div>
<script>
play(function (addpoints) {
document.getElementById('temp').innerHTML = addpoints;
}());
function play (choosecolor, randescolor) {
if (choosecolor == randescolor) {
if (document.getElementById('points').innerHTML === '') {
document.getElementById('points').innerHTML=100;
}
else {
var points = function (allpoints) {
var addpoints = parseInt(allpoints) + 100;
callback(addpoints);
document.getElementById('points').innerHTML=addpoints;
}
}
points(document.getElementById('points').innerHTML);
}
}
I uderstand that function callback doesn't send var addpoint with its value to second function, so in second function addpoints is undefined. But I don't understand why addpoints lose its value (but I can show it in div), what callback do and how correct it. Some user gave me advise that I should to choosecolor(addpoints); instead of callback(addpoints); but it still not work.
After some thoughts and research. i came accross that i was wrong somewhere. Here is the solution , pass the last parameter - which is the name of the function you want to call back. Now this will work as you want it to work.
Now here i mentioned the third parameter as name of the function i want to call after this function.
<div id='temp'></div>
<div id='points'></div>
<div onClick ="play(1, 1, newcallback)" id='click'>clickclickclick</div>
Here i declared a newcallback function which i will call after this method:
function newcallback(addpoints) {
document.getElementById('temp').innerHTML = addpoints; //undefined
});
function play (choosecolor, randescolor,callback) {
if (choosecolor == randescolor) {
if (document.getElementById('points').innerHTML === '') {
document.getElementById('points').innerHTML=100;
}
else {
var points = function (allpoints) {
var addpoints = parseInt(allpoints) + 100;
callback(addpoints);
document.getElementById('points').innerHTML=addpoints;
}
}
points(document.getElementById('points').innerHTML);
}
}
Here is the fiddle : JsFiddle

Get this._id of collection instance in template.foo.rendered

I try to include the rating module from Semantic UI (http://semantic-ui.com/modules/rating.html) under articles for users to be able to rate them. If user rates the article, the article id is stored into Meteor.user().profile.ratedItems.
If user goes away from the article to another one and then comes back to the first one the rating module should be rendered as read-only(so the user is not able to rate the same article again).
The problem is that I do not know how to check if article _id is stored in Meteor.user().profile.ratedItems in template.foo.rendered because this._id gives not article id but id of template.
In template.foo.events and template.foo.helpers I can check for that by the sentence _.contains(Meteor.user().profile.ratedItems,this._id) and it works OK everywhere but of cause not in template.foo.rendered. Now even if user rates an article more then one time the rating in db does not change. But I need to solve the "visual" issue.
So here is the code:
JS:
Template.foo.helpers({
rate: function () {
return Math.floor(this.rating);
},
state : function () {
if (Meteor.userId()) {
if (_.contains(Meteor.user().profile.ratedItems,this._id)) {
return "rated"
} else {return "unrated"}
} else {
return ""
}
},
statetext: function () {
if (Meteor.userId()) {
if (_.contains(Meteor.user().profile.ratedItems,this._id)) {
return "Overall rating:" }
else { return "Rate the article:"}
} else {
return "Overall rating:"
}
}
});
Template.foo.rendered = function() {
if (Meteor.userId()) {
if (_.contains(Meteor.user().profile.ratedItems,this._id)) {
$('.ui.rating').rating('disable');
} else {
$('.ui.rating').rating();
}
} else {
$('.ui.rating').rating('disable');
}
};
Template.foo.events({
'click .unrated': function () {
var addedRating = $('.unrated').rating('get rating');
var currentArticleId = this._id;
var newsum = this.rating_sum+addedRating;
var newcount = this.rating_count+1;
var newrating = newsum/newcount;
Schools.update(currentSchoolId,{$inc: {rating_count:1}});
Schools.update(currentSchoolId,{$inc: {rating_sum:addedRating}});
Schools.update(currentSchoolId,{$set: {rating:newrating}});
Meteor.users.update({_id:Meteor.userId()},{$push: {'profile.ratedItems':currentArticleId}});
$('.ui.rating').rating({"set rating":Math.floor(newrating)});
$('.ui.rating').rating('disable');
}
});
HTML:
<template name="schoolPage">
<div class="ui center aligned blue segment">
{{#if currentUser}}{{statetext}}{{else}}Overall rating:{{/if}}
<div class="ui tiny heart rating {{state}}" data-rating="{{rate}}" data-max-rating="5"></div>
</div>
</template>
I thought about using Session.set and Session.get but did not come to any solution yet.
Thank you for help in advance.
Instead of this, you can use Template.currentData inside the rendered callback.
See the docs at http://docs.meteor.com/#/full/template_currentdata.

knockout dirty flag code not working

Just started with knockout and need to implement page change warning. Following is the code snippet. I just need an alert pop up as warning if any change is made on the page.
function parseViewModel() {
var viewModel = JSON.parse(getState());
viewModel.checking = ko.observable(false);
viewModel.Slider = new ko.observable(100 - viewModel.Slider);
viewModel.CausalsList = buildHierarchy(viewModel.Causals);
viewModel.Causals["-1"] = "Total Marketing Budget";
viewModel.GeographiesList = ko.observableArray(gl);
viewModel.Geographies["0"] = "All Geographies";
viewModel.ProductsList = ko.observableArray(pl);
viewModel.Products["0"] = "All Products";
.
.
.
return viewModel;
}
function bindModel() {
model = parseViewModel();
ko.dirtyFlag = function (root, isInitiallyDirty) {
var result = function () { },
_initialState = ko.observable(ko.toJSON(root)),
_isInitiallyDirty = ko.observable(isInitiallyDirty);
result.isDirty = ko.computed(function () {
return _isInitiallyDirty() || _initialState() !== ko.toJSON(root);
});
result.reset = function () {
_initialState(ko.toJSON(root));
_isInitiallyDirty(false);
};
return result;
};
model.dirtyFlag = new ko.dirtyFlag(model);
model.isDirty.subscribe(function () {
alert("Page change warning!");
});
ko.applyBindings(model, $('#const').get(0));
ko.applyBindings(model, $('#buttonDiv').get(0));
}
Referred Ryan Niemeyer's blog. Unfortunately, it's not working anymore. Any insights please?
You would want to subscribe to model.dirtyFlag.isDirty in your case rather than model.isDirty.
One way to do is by using customBinding. I'm not that familiar with KO either but this might be something you're interested on.
Basically you would do is :-
ko.bindingHandlers.myFunction = {
update : function(){
//do something
}
}
http://knockoutjs.com/documentation/custom-bindings.html
And call it on your element using :-
<h1 data-bind="myFunction:{}"></h1>
Also, a jsfiddle to show how it works. (If you change the value of the First Name and focus out of it then the customBinding gets triggered. )
http://jsfiddle.net/3vuTk
Not sure if it's the best practice though.

How do you program game states in javascript?

You can use frames in Flash ActionScript to hold a welcome screen, game content, and a game over screen; you can create an enum of game states in Microsoft XNA and draw all graphics and text when you call that state in the Draw method. How would you do this in HTML5 and JavaScript? I have a feeling that this can be done by showing and hiding div's, but I'm not entirely sure how to do it.
<asp:DropDownList ID="DDL_SelectGame" runat="server" CssClass="BigText">
<asp:ListItem>MatchMe (Memory)</asp:ListItem>
<asp:ListItem>Royal Jewels (Bejeweled)</asp:ListItem>
<asp:ListItem>Tetris</asp:ListItem>
</asp:DropDownList>
<div id="Welcome" class="Welcome">
<asp:Button ID="BTN_StartGame" runat="server" CssClass="Button" />
</div>
<div id="Game" class="Game">
</div>
<div id="GameOver" class="GameOver">
<asp:Button ID="BTN-Replay" CssClass="Button" runat="server" />
</div>
The code above is an example of how I would set up a game page on my site (http://www.graphics-geek.com).
Here's a state machine I'm planning to use for future game development.
With something like this you should be able to implement the drawing of different states, and state changes in the callbacks. Either by show/hide divs, or drawing different buffers/graphics to a canvas.
To create states:
// initially hide the divs using css or js
state.add("welcome",
function () {
document.getElementById("Welcome").style.display = "block";
},
function () {
document.getElementById("Welcome").style.display = "none";
}
);
state.add("level01",
function () {
document.getElementById("Game").style.display = "block";
},
function () {
document.getElementById("Game").style.display = "none";
}
);
state.add("end",
function () {
document.getElementById("GameOver").style.display = "block";
}
);
The second function is optional (actually both are, but such a state would do nothing).
To switch states (assuming we have added 3 states; "welcome", "level01" and "end"):
state.enter("welcome");
//... at some point welcome changes state using following:
state.enter("level01");
//... at the end of level1:
state.enter("end");
Code:
var state = (function () {
"use strict";
var currentState = -1,
stateNames = [],
stateCallbacks = [];
return {
current: function () {
if (currentState >= 0) {
return stateNames[currentState];
}
},
add: function (name, onEnter, onExit) {
var index = stateNames.indexOf(name);
if (index !== -1) {
throw "State " + name + " already exist!";
}
stateCallbacks.push({
enterState: onEnter || false,
exitState: onExit || false
});
stateNames.push(name);
},
remove: function (name) {
var index = stateNames.indexOf(name);
if (index === -1) {
throw "State " + name + " not found!";
}
stateNames.splice(index, 1);
stateCallbacks.splice(index, 1);
},
enter: function (name) {
var index = stateNames.indexOf(name);
if (index === -1) {
throw "State " + name + " not found!";
}
if (stateCallbacks[currentState].exitState) {
stateCallbacks[currentState].exitState();
}
currentState = index;
if (stateCallbacks[index].enterState) {
stateCallbacks[index].enterState();
}
},
exit: function () {
if (currentState === -1) {
throw "Not currently in any state";
}
if (stateCallbacks[currentState].exitState) {
stateCallbacks[currentState].exitState();
}
currentState = -1;
}
};
}());
Additional comments:
The structure with the anonymous function wrapping the code is a module pattern for javascript from http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html. This is similar to a namespace in other languages.
For possibly better performance of lookups (implemented here as Array.indexOf(name)), you might want to add an object with string keys associated with index values, especially for large number of states (ie. a game with 100+ levels, or a different use case where states are switched more intensively). I've not benchmarked this though, so it is highly speculative. In theory an array search is n - 1 times slower than a hashtable lookup (which objects are likely implemented as).
Edit:
Fixed bug in code. state.add now checks for existing names.
Added disclaimer.
Added example of how to show/hide divs from callbacks.
Removed disclaimer, replaced code with improved version.

Categories