I'm working in Angular, and I have checkboxes that switch the Boolean (to signify task completion). I also have two arrays, one that holds a list of objects and another to hold the first key values once completed and cleared.
$scope.taskList = [{
complete: false,
foo: "1",
bar: "2",
baz: "3"
}];
$scope.completedTasks = [];
The below function works properly to clear tasks when only one or two are checked; however, when the number of items checked grows, only a portion of checked items will clear; if you keep invoking the function, eventually all tasks will clear, but I can't figure out how to clear the entire list in one fell swoop.
$scope.clearComplete = function() {
for (var i = 0; i < $scope.taskList.length; i++) {
if ($scope.taskList[i].complete == true) {
$scope.completedTasks.push($scope.taskList[i].foo);
$scope.taskList.splice(i, 1);
}
}
console.log($scope.completedTasks);
return $scope.taskList;
};
I'm at a loss for how to correct the logic and am hoping that some fresh eyes will be able to help me out.
For this project, I'm trying to keep dependencies low, without the help of other helper libraries like Underscore and Lodash.
first of all if you are doing taskList.splic then loop backwards.
otherwise you can do like this:
var incompleteTasks = [];
$scope.tasks.forEach(function(task){
if(task.complete) {
$scope.completedTasks.push(task.foo);
} else {
incompleteTasks.push(task);
}
});
$scope.tasks = incompleteTasks;
so, you will iterating exactly once to your array, and make it happen with very cleaner approach.
You don't need any external libraries to use forEach, or filter. And I would approach it in two steps for readability:
$scope.clearComplete = function() {
//push completed tasks to completedTasks
$scope.taskList.forEach(function(t) {
if(t.complete) {
$scope.completedTasks.push(t.foo);
}
});
//then filter the task list
$scope.taskList = $scope.taskList.filter(function(t) {
return !t.complete;
});
console.log($scope.completedTasks);
return $scope.taskList;
};
Related
I'm reading and watching Tutorials about Meteor since 1-2 Weeks. I've learned about how to structure a meteor app regarding server and client side code, accounts, security etc.
What i could not figure out:
Where do i put the calculation logic properly?
For example:
A user puts data in a form and the data is saved in the database. Depending on this input data i want to do several calculations by putting the data through lets say a chaining of around 20 Methods, and finally display some results.
At the moment i have all of these Methods inside the file where the Template.displayResults.helper is.
When i put them in another file they don't get recognized, i think because of the wrapper Meteor puts around.
Example: I have a collection of DIY projects and each of the projects has a field with an array of utilities that are neccessary for the project.
Projects = new Mongo.Collection('projects');
/*
exampleProject = {
"name": "Kitchen table",
"utilities": ["Hammer", "Glue"]
}
*/
I want to display all possible DIY projects depending on the utilities the user has checked.
The UI has a group of checkboxes via the user can select a bunch of utilities he wants to use.
These values are saved in a collection.
Utilities = new Mongo.Collection('utilities');
/*
exampleUtility = {
"name": "Hammer",
"checked": true
}
*/
Then i want to calculate the possible Projects...
Template.displayResults.helpers({
projectsPossible: function () {
var utilitiesCheckedDB = Utilities.find({
checked: true
}).fetch();
var projectsAll = Projects.find().fetch();
return projectsPossible(utilitiesCheckedDB, projectsAll);
}
});
// Returns an array of all possible projects depending on the selected utilities
function projectsPossible(utilitiesCheckedDB, projectsAll) {
var result = [];
_.each(projectsAll, function (project) {
if (project.utilities.length === _.intersection(project.utilities, checkedCheckboxesList(utilitiesCheckedDB)).length) {
result.push(project);
}
});
return result;
}
// Returns an array of all checked utilities in the current checkbox database
function checkedCheckboxesList(checkedCheckboxesDB) {
var result = [];
_.each(checkedCheckboxesDB, function (checkbox) {
result.push(checkbox.name);
});
return result;
}
The question is: There are more methods like "projectsPossible" and "checkedCheckboxesList". Where do i put these methods to get a good structure?
Thanks in advance!
Vin
If you want to register global helpers, just use Template.registerHelper(name, function), for instance:
Template.registerHelper('projectsPossible', function() {
var utilitiesCheckedDB = Utilities.find({
checked: true
}).fetch();
var projectsAll = Projects.find().fetch();
return projectsPossible(utilitiesCheckedDB, projectsAll);
});
If you want to make the functions projectsPossible(utilitiesCheckedDB, projectsAll) or checkedCheckboxesList(checkedCheckboxesDB) accessible from other (client) files, you can make them global. For example:
projectsPossible = function(utilitiesCheckedDB, projectsAll) {
var result = [];
_.each(projectsAll, function(project) {
if (project.utilities.length === _.intersection(project.utilities, checkedCheckboxesList(utilitiesCheckedDB)).length) {
result.push(project);
}
});
return result;
};
You can make model classes, using the transform option for collections. For an ES5 example, see the docs: http://docs.meteor.com/#/full/mongo_collection
Also, you have to make that model class or function global by not using var.
(function() {
foo = function foo() {
alert("fooh")
}
})()
In the above example, without foo =, the foo function would only be visible inside its own file because of the wrapper.
I need some help with a nested for loop in protractor and converting/understanding promises correctly. In ‘test’ below the functionality works with all values, but as soon as I try to put in the nested for loops things go south. Any chance someone has a clean suggestion on this? I have tried the forEach which some indicate handle the promise issue inherently, but I seem to get the same results.
My Test data looks like:
objectPage.chartValues = {
[['chart','ChartID01'],['title','TitleText01'],['Name01','value01'],['Name02','Value02']],
[[‘chart','ChartID02'],['title','TitleText02'],['Name01','value01'],['Name02','Value02']],
[[‘chart','ChartID03'],['title','TitleText03'],['Name01','value01'], [‘Name02’,'Value02'],['Name03','Value03']]
}
it ('test', function (){
for (chartNumber = 0; chartNumber < objectPage.chartValues.length; chartNumber++) {
for (chartEntry = 1; chartEntry < ObjectPage.chartValues[chartNumber].length; chartEntry++) {
//for readability of next call pulled out here
chart = objectPage.chartValues[chartNumber][0][1];
name = objectPage.chartValues[chartNumber][chartEntry][0];
value = objectPage.chartValues[chartNumber][chartEntry][1];
pageObject.getbackgroundcolor(chart, name).then(function (color) {
expect(pageObject.getElementFromTable(chart, name, color).getText())
.toEqual(value);
});
}
}
});
//function calls in pageobject the call for get background is straight forward.
this.getbackgroundcolor = function (chartName, valueName) {
return element(by.id(chartName)).element(by.cssContainingText('.dxc-item', valueName)).element(by.tagName('rect')).getAttribute('fill');
//get element is very similar.
this.getElementFromTable = function(chartName, valueName, colorname) {
some searching stuff..
return element(by.css(‘tspan'));
My results seem to indicate the look executes, but not returning from the actual expect. Finally trying to find a value for an item with background color of null. I know this is not true as I have run all values individually and in sequence without issue. Hopefully I avoided cut and past/generalization errors.
Thank you.
Update:
it('Verify Charts on page ', function () {
myChartlength = objectPage.chartValues.length;
for (chartNumber = 0; chartNumber < myChartlength; chartNumber++) {
(function (chartNumber) {
myEntrylength = objectPage.chartValues[chartNumber].length;
chartValues = objectPage.chartValues[chartNumber];
for (chartEntry = 2; chartEntry < myEntrylength; chartEntry++) {
(function (chartEntry) {
//pulled these out for readablility of next call.
chart = chartValues[0][1];
name = chartValues[chartEntry][0];
value = chartValues[chartEntry][1];
console.log('chart: ' + chart + ', name: ' + name + ', value: ' + value);
page.getbackgroundcolor(chart, name).then(function (color) {
expect(objectPage.getElementFromTable(chart, name, color).getText()).toEqual(value);
});
})(chartEntry);
};
})(chartNumber);
};
});
Yeah, if I'm understanding your question correctly, your problem is async. It's firing through the loops before any promises are returned.
To loop tests, the best solution I've found is to use an IIFE (Instantly Invoked Function Expression). In which, you create your loop, create the iife, and pass in the index.
Here's a basic example:
describe('to loop tests', function() {
var data = ['1', '2', '3'];
for(var i = 0; i < data.length; i++) {
// create your iife
(function(i) {
it('pass in the index to an iife', function() {
console.log('i is: ' + i);
expect(data[i]).toBe(true);
});
})(i); // pass in index
}
});
This works great for data driving tests from say a data file, or whatever. And if you need multiple loops, like in your example code, you'll just make multiple iifes.
You shouldn't use for loops with protractor or you will have a bad time.
Due to the asynchronous nature of Protractor, if you need loops, I see async's map https://github.com/caolan/async as one good and clean solution.
Other option is to use ES5's map when you need loops in Protractor, such as:
[1,3,5,7].map(function(index,key){
expect(element.all(by.css('.pages-list')).get(index).isDisplayed()).toBeFalsy()
})
In your case, I see that you need a for loops to produce array, that you later can map over it.
You can have this array with function, that uses for loops inside and returns the needed array to a callback.
Simple example with one for loop
function returnIndexes(callback){
var exitArray = [];
for (var i = 0; i < someArray.length; i++) {
if(someArray[i].length > 12){
exitArray.push(someArray[i]);
}
if(i==someArray.length-1){
callback(exitArray);
}
}
I have a multiselect dependency where I display cities and their areas respectively.
The problem is that, both for loops in the function getArea only get called upon the second select of the cities option element.
Note: In debugger it works fine. I think its a scope problem and I tried using the foreach function but to no avail.
I have indicated below the line where the issue occurs.
//Global Variables
var allAreas = new Array();
$(document).ready(function() {
getCities();
$("#sel-city").bind("change", getAreas);
});
//get Cities on reload
function getCities() {
$.getJSON("cities.json", function(json) {
var citySelect = $("#sel-city");
for (var i in json) {
$("#sel-city")
.append($('<option>', {value: json[i].id})
.text(json[i].name));
}
});
}
function getAreas() {
var parentID = $(this).val();
console.log(parentID);
if (allAreas.length == 0) {
$.getJSON("areas.json", function(json) {
for (var i in json) {
allAreas.push(json[i]);
}
});
}
$('option', $("#sel-area")).remove();
var areasByParentID = new Array();
//Loop here
for (var i in allAreas) {
if (allAreas[i].city_id == parentID) {
areasByParentID.push(allAreas[i]);
}
}
console.log(areasByParentID);
//Loop here
for (var k in areasByParentID) {
$("#sel-area")
.append($('<option>', {value: areasByParentID[k].id})
.text(areasByParentID[k].name));
}
}
With the information you gave us and without sample data everything looks fine. But you have some minor mistakes which could lead to bad results. I decided to have a look on it, make some corrections and get it workin correctly with some sample data.
What you should take care of?
use for loops instead of for-in for arrays
you should avoid the new Array() operation
you should cache jquery variables to reduce siteload
for the areas to be set correctly on siteload you should just define init functions
What here could cause an error?
var a = [];
a[5] = 5;
for (var x in a) {
// Shows only the explicitly set index of "5", and ignores 0-4
}
The use of the for-in statement is to enumerate over object properties and will even inherited properties. Depending on your data it could give wrong results as shown in my example.
here is a jsfiddle with sample data.
I have a class called Room, with an array containing all the Player entities as one of its properties,
players = [];
In the Room class is a method that only returns the players who actually competed in the round.
// below method is called by the room's timer
var getPlayersWhoFinished = function() {
playersWhoFinished = [];
for (i = 0; i < players.length; i++) {
if (players[i].isFinished()) {
playersWhoFinished.push(players[i]);
};
};
return playersWhoFinished;
}
So I know that I could just leave the above in the Room class as is, but I already have three other functions with more complex mapping, in an already large class (300+ lines).
I don't understand how to encapsulate these sort of methods into other classes, as they're so closely related to the Room class and all the the Room reference to be sent to the appropiate users.
Modifying the above code and slotting it into Player class would sort of make sense to me, but the only way I can think of getting this to work is using a static method and sending the room object to it.
// players is now a member of the Player class
Player.getPlayersWhoFinished = function(room, players) {
playersWhoFinished = [];
for (i = 0; i < players; i++) {
if (players[i].getRoom() == room) {
playersWhoFinished.push(players[i]);
}
}
return playersWhoFinished;
}
Anyway, this seems cumbersome and inefficent to me. I've really been struggling to figure out how to make my Room class lithe as possible.
Consider splitting logic into Objects and Collections. It is similar to what backbone offers (Model, Collection).
As collections logic is usually specific to objects it contains, but have some shared functionality as well (simple iterations, filters and so on), you can create generic Collection, and then through Inheritance add more methods in order to fit your needs of that specific Object it stores.
So you would have your room:
function Room() {
// ..
this.players = new PlayerCollection();
// ..
}
For collection I've added some 'bonus' methods, so it would look like:
function Collection() {
this.list = [ ];
this.length = 0;
}
// adds item
Collection.prototype.add = function(item) {
this.list.push(item);
this.length = this.list.length;
}
// removes by index
Collection.prototype.remove = function(index) {
this.list.splice(index, 1);
this.length = this.list.length;
}
// finds one item based on filter function, and triggers callback with item and index
Collection.prototype.findOne = function(fn, callback) {
for(var i = 0, len = this.list.length; i < len; ++i) {
var result = fn(this.list[i]);
if (result) {
return callback(this.list[i], i);
}
}
return callback(null, -1);
}
// filters off
Collection.prototype.filter = function(fn) {
return this.list.filter(fn);
}
Then you would define PlayerCollection, that will have extra method just to filter off players who is finished:
function PlayerCollection() {
Collection.call(this);
}
// some inheritance here
PlayerCollection.prototype = Object.create(Collection.prototype);
PlayerCollection.prototype.constructor = PlayerCollection;
// returns list of finished players
PlayerCollection.prototype.finished = function() {
return Collection.prototype.filter.call(this, function(player) {
return player.isFinished();
});
}
You still can reuse that filter method, as it helps to create some bespoke queries.
Your room logic would look like:
var room = new Room();
// add some players to room
// ...
var finishedPlayers = room.players.finished(); // what you need
It looks clear and straight forward, as well keeps all collection logic away from Room, so you can simply reuse it all over your game code. And improving in one place - would improve it as a whole.
Dividing logic into abstracts like that, helps to scale your code and separate dependencies.
Bear in mind Browser support for filter and if you need -IE8, then get shim from here.
The getPlayersWhoFinished() method belongs to Room, which is the object that should track players. You also are performing a search in O(n) complexity every time you need to find finished players, which can be improved.
You could have a callback mean to be called each time a player finishes:
Player.prototype.onFinished = function() {
this.getRoom().addFinished(this);
}
And then manage a private array in Room containing all the finished players:
function Room() {
this._finishedPlayers = [];
}
Room.prototype.addFinished = function(player) {
this._finishedPlayers.push(player);
}
Room.prototype.getPlayersWhoFinished = function() {
return this._finishedPlayers;
}
As a side note, you should always declare variables with var, or else you will get them declared in the global scope (i.e. usually the window object).
If I have an array of objects is there any way possible for the item to splice itself out of the array that contains it?
For example: If a bad guy dies he will splice himself out of the array of active enemies.
I probably sound crazy but that ability would simplify my code dramatically, so I hope for something cool =)
The way you would do it is as follows:
var game_state = { active_enemies: [] };
function Enemy() {
// Various enemy-specific things go here
}
Enemy.prototype.remove = function() {
// NOTE: indexOf is not supported in all browsers (IE < 8 most importantly)
// You will probably either want to use a shim like es5-shim.js
// or a utility belt like Underscore.js
var i = game_state.active_enemies.indexOf(this);
game_state.active_enemies.splice(i, 1);
}
See:
Es5-Shim
Underscore.js
Notta bene: There are a couple of issues here with this manner of handling game state. Make sure you are consistent (i.e. don't have enemies remove themselves from the list of active enemies, but heroes remove enemies from the map). It will also make things more difficult to comprehend as the code gets more complex (your Enemy not only is an in-game enemy, but also a map state manager, but it's probably not the only map state manager. When you want to make changes to how you manage map state, you want to make sure that code is structured in such a way that you only need to change it in one place [preferably]).
Assuming the bad guy knows what list he's in, why not?
BadGuy.prototype.die = function()
{
activeEnemies.splice(activeEnemies.indexOf(this), 1);
}
By the way, for older browsers to use indexOf on Arrays, you'll need to add it manually.
You kind of want to avoid circular references
I would suggest creating an object/class that represents the active enemies list. Create methods on that instance for adding/removing a given item from the list - abstracting the inner workings of the data structure from the outside world. If the active enemies list is global (e.g. there's only one of them), then you can just reference it directly to call the remove function when you die. If it's not global, then you'll have to give each item a reference to the list so it can call the function to remove itself.
You can also use an object and instead of splice, delete the enemy:
var activeEnemies = {};
function Enemy() {
this.id = Enemy.getId(); // function to return unique id
activeEnemies[this.id] = this;
// ....
}
Enemy.getId = (function() {
var count = 0;
return function() {
return 'enemyNumber' + count++;
}
}());
Enemy.prototype.exterminate = function() {
// do tidy up
delete activeEnemies[this.id];
}
Enemy.prototype.showId = function() {
console.log(this.id);
}
Enemy.prototype.showEnemies = function() {
var enemyList = [];
for (var enemy in activeEnemies) {
if (activeEnemies.hasOwnProperty(enemy)) {
enemyList.push(enemy);
}
}
return enemyList.join('\n');
}
var e0 = new Enemy();
var e1 = new Enemy();
console.log( Enemy.prototype.showEnemies() ); // enemyNumber0
// enemyNumber1
e0.exterminate();
console.log( Enemy.prototype.showEnemies() ); // enemyNumber1