Is there a difference between .add() in AngularJS vs. DOM? - dom

Is there a difference between the .add() method in AngularJS and DOM? I'm using .add() in an AngularJS application and I'm running into *issues pushing data to a firebase. When I go to look at the documentation to do some research I found nothing in the AngularJS docs about this method. I found that .add() in DOM is for adding an option to a select (eg Object_of_Select.add(option, before);). But this doesn't really translate IMO to how I'm using it in AngularJS.
*The Issue I'm experiencing:
I have no problem with Firebase and persisting data, its super straightforward and awesome. I run into problems however, when I take an object (in this example "campaign") and push objects into a nested array of objects ("tactics"). The second I add local data, I can no longer "add" that data to the Firebase. No errors, just nothing.
So if anyone could point me in the right direction it would be super.
TEMPLATE
<!-- TITLE THE CAMPAIGN -->
<h3>{{campaign.name}}</h3>
<input type="text" placeholder="Campaign Title" ng-model="campaign.name"/>
<!-- LOOP THROUGH TACTICS -->
<div class="well well-lg" ng-repeat="foo in campaign.tactics">
<h4>{{foo.name}} - {{foo.type}}</h4>
</div>
<!-- SELECT TACTIC TYPE (Loaded from separate Firebase instance)-->
<select ng-model="tacticSelect">
<option ng-repeat="tactic in tactics" value="{{tactic.name}}">{{tactic.name}} ({{tactic.type}})</option>
</select>
<!-- PUSH NEW TACTICS INTO TACTIC LOOP -->
<button class="btn" ng-click="campaign.tactics.push({'name': tacticSelect, 'type': 'email'})"><span class="glyphicon glyphicon-plus" ></span> Add Tactic</button>
<!-- SAVE CAMPAIGN TO CAMPAIGNS FIREBASE -->
<button class="btn btn-success" ng-click="campaigns.add(campaign)"></span> Save</button><!-- ng-click="campaigns.add(campaign)"-->
CONTROLLER
function scopeAssignments($scope, angularFireCollection, $location){
$scope.tactics = angularFireCollection(fbTactics);
$scope.campaigns = angularFireCollection(fbCampaigns);
// SAMPLE DATA SET
$scope.campaign = {"tactics" : []};
}
DESIRED DATA STRUCTURE (I'm trying to create campaigns that can have multiple tactics with in a single campaign.)
$scope.campaign = {
"name" : "Sample Campaign",
"tactics" : [{
"name" : "First Tactic",
"type" : "Email"
}]
};

add is neither an Angular method nor a DOM method. It's a method provided by an angularFireCollection. angularFireCollection synchronizes data explicitly, which means that calling $scope.campaigns.tactics.push is not sufficient to send that data to Firebase.
You essentially have two options. You can use the angularFire service which will automatically synchronize data whenever it changes locally.
CONTROLLER
function scopeAssignments($scope, angularFire){
$scope.tactics = angularFire(fbTactics);
$scope.campaigns = angularFire(fbCampaigns);
$scope.campaign = $scope.campaigns[campaignID];
$scope.campaign.tactics = [];
}
If you want to stick with angularFireCollection, you have to explicitly notify the collection when you want some data sent to the Firebase server. You can do this, for example, by:
TEMPLATE
<!-- PUSH NEW TACTICS INTO TACTIC LOOP -->
<button class="btn" ng-click="addTactic(campaign, tacticSelect)">
<span class="glyphicon glyphicon-plus"></span> Add Tactic
</button>
CONTROLLER
$scope.addTactic = function(campaign, tactic) {
$scope.campaign.tactics.push({'name': tacticSelect, 'type': 'email'});
$scope.campaigns.update($scope.campaign);
}

Related

Passing multiple data points to an action? VUEX

So I have a form that collects 3 data points that looks like this:
<form class="" >
<table>
<tr>
<td>Maximum Temperature</td>
<td><input class="setSettings" v-model="settings.maxTMP" type="number" max="30" min="5"></td>
</tr>
<tr>
<td>Minimum Humidity</td>
<td><input class="setSettings" v-model="settings.minHUM" type="number" max="95" min="20"></td>
</tr>
<tr>
<td>Maximum CO2</td>
<td><input class="setSettings" v-model="settings.maxCO2" type="number" min="200" max="5000" step="10"></td>
</tr>
</table>
<button type="button" v-on:click="whatSettings(settings)">Update Settings</button>
</form>
My data in the script tag is stored in the components local data, not the store, ike this:
data: function() {
return {
settings: {
maxTMP: "",
minHUM: "",
maxCO2: ""
}
}
}
There's no reason to put it through a mutation and commit it to the store as the purpose for the action is merely to send the data via post request to receiving api.
My methods for the component look like this:
methods: {
...mapActions({
setSettings: 'setSettings'
}),
whatSettings(settings){
let foo = settings.maxTMP;
let bar = settings.minHUM;
let uhm = settings.maxCO2;
console.log(foo);
console.log(bar);
console.log(uhm);
this.setSettings(foo,bar,uhm)
}
},
And that action from the store is write like this:
setSettings(foo,bar,uhm) {
console.log("Set these settings");
console.log(foo);
console.log(bar);
console.log(uhm);
},
Please forgive all the console.log()'s. I have it like this cause I was trying to test out different combinations of things to figure out where it goes wrong. Right now when I click the Update Settings button the console prints the whatSettings() correctly so I know that foo, bar, uhm are the correct value as they are passed into the setSettings() action. The problem is in the action's logs. "Set these settings" and foo are printed correctly followed by a single undefined, not two. So I'm not sure exactly what's happening with bar & uhm. When I rearrange the order it's always the first that gets printed.
Is it a problem with multiple arguments being passed to the action? Ideally I would just like it to look like this directly in the button tag for neatness but that didn't work so I tried trouble shooting like this:
v-on:click="this.setSettings(settings.maxTMP, settings.minHUM, settings.maxCO2)"
Thanks for reading and I appreciate the help!
Well, after a little more looking around I found this and it fixed my problem: Axios post request with multiple parameters in vuex action
correct button tag:
<button type="button"
v-on:click="setSettings({
foo: settings.maxTMP,
bar: settings.minHUM,
uhm: settings.maxCO2
})"
>Update Settings</button>
correct vuex store action:
setSettings({commit}, {foo, bar, uhm}) {
console.log("Set these settings");
console.log(foo);
console.log(bar);
console.log(uhm);
},
Not entirely sure why the {commit} needs to be present, but it does otherwise I just get 3 undefined results in the log. But it works now!

protractor : element stored in a variable is changing if the result of the locator change

I have a list of invitations and want to see if the first pending one has been accepted after some trigger.
<div class="invitation">
<h1>A</h1>
<div class="PENDING">PENDING</div>
</div>
<div class="invitation">
<h1>B</h1>
<div class="PENDING">PENDING</div>
</div>
some code inside specs to store first pending invitation with h1 A
// get first element with css .PENDING
let pendingTxt = element.all(by.css('.PENDING')).get(0);
// get its parent : div.invitation with h1 A
let invitation = pendingTxt.element(by.xpath('ancestor::div'))
Here some script update invitation to status ACCEPTED
<div class="invitation">
<h1>A</h1>
<div class="ACCEPTED">ACCEPTED</div>
</div>
<div class="invitation">
<h1>B</h1>
<div class="PENDING">PENDING</div>
</div>
But if I log invitation now it will return invitation with h1 B.
Which means that invitation has been updated according to pendingText locator. I'm not sure to understand why.
But i can't find way to properly store the original invitation. I tried to clone it with no success.
The variable pendingTxt holds a Promise for an ElementFinder which will locate an element every time it is resolved. You are not storing an element, by the mechanism to locate it.
So to store an element, you first have to resolve the Promise to a WebElement:
protractor.promise.fulfilled(element.all(by.css('.PENDING')).get(0))
.then(status => {
expect(status.getText()).toEqual('PENDING');
// accept the invitation
expect(status.getText()).toEqual('ACCEPTED');
})
Or to a list of web elements:
element.all(by.css('.PENDING')).then(statuses => {
expect(statuses[0].getText()).toEqual('PENDING');
// accept the invitation
expect(statuses[0].getText()).toEqual('ACCEPTED');
})

Trying to think about how to build a multi step form in angular 2

I am trying to build a small, 3 step form. It would be something similar to this:
The way I did this in react was by using redux to track form completion and rendering the form body markup based on the step number (0, 1, 2).
In angular 2, what would be a good way to do this? Here's what I am attempting at the moment, and I'm still working on it. Is my approach fine? Is there a better way to do it?
I have a parent component <app-form> and I will be nesting inside it <app-form-header> and <app-form-body>.
<app-form>
<app-header [step]="step"></app-header>
<app-body [formData]="formData"></app-body>
</app-form>
In <app-form> component I have a step: number and formData: Array<FormData>. The step is just a index for each object in formData. This will be passed down to the header. formData will be responsible the form data from user. Each time the form input is valid, user can click Next to execute nextStep() to increment the index. Each step has an associated template markup.
Is there a better way to do something like this?
don't overdo it, if it is a simple form you don't need to use the router or a service to pass data between the steps.
something like this will do:
<div class="nav">
</div>
<div id="step1" *ngIf="step === 1">
<form></form>
</div>
<div id="step2" *ngIf="step === 2">
<form></form>
</div>
<div id="step3" *ngIf="step === 3">
<form></form>
</div>
It's still a small template, and you kan keep all of the form and all the data in one component, and if you want to you can replace the ngIf with something that switches css-classes on the step1,2,3 -divs and animate them as the user moves to the next step
If you want to keep things extensible, you could try something like this:
<sign-up>
<create-account
[model]="model"
[hidden]="model.createAccount.finished">
</create-account>
<social-profiles
[model]="model"
[hidden]="model.socialProfiles.finished">
</social-profiles>
<personal-details
[model]="model"
[hidden]="model.personalDetails.finished">
</personal-details>
</sign-up>
export class SignUpVm {
createAccount: CreateAccountVm; //Contains your fields & finished bool
socialProfiles: SocialProfilesVm; //Contains your fields & finished bool
personalDetails: PersonalDetailsVm; //Contains your fields & finished bool
//Store index here if you want, although I don't think you need it
}
#Component({})
export class SignUp {
model = new SignUpVm(); //from sign_up_vm.ts (e.g)
}
//Copy this for personalDetails & createAccount
#Component({})
export class SocialProfiles {
#Input() model: SignUpVm;
}

infinite loop when trying to retrieve data from controller in view using angular $resource.Query() method

I'm building a small sails.js + angular.js app.
Here is a fiddle that roughly shows what my code looks like: http://jsfiddle.net/WEk3F/
index: function(req, res, next) {
Food.find({}, function foundFoods(err, foods) {
if (err) return next(err);
var data = {
name1: "test1",
name2: "test2"
}
res.view(
'food/index', {
foods: data
});
});
},
<div ng-app="myApp">
<div ng-controller="FoodController">
<ul>
<li ng-repeat="food in foods">
{{food.name}}
</li>
</ul>
<form>
<label for="name">Name:</label>
<input name="name" ng-model="editableFood.name" />
</form>
</div>
</div>
My problem is, that whenever i try to retrieve the data from my controller, i don't get those 2 items but instead it renders more and more items and just doesn't stop. even the page gets slow and unresponsive and almost freezes.
When i say
$scope.foods = [{"name": "test1"},{"name": "test2"}];
instead of
$scope.foods = Food.query();
it works. but i want the data to be coming from the backend via the controller.
the other methods (add, update etc) of the angular.js $resource module work fine for me.
/food maps to the index action of my FoodController and just returns some fixed test data
i found out what the problem was.
the angular.js $resource should only be used in a restful way, so the GET request to my food/index should return an array of objects.
in my case this wasn't the case. instead i used the index action of my controller to render a view and the result was therefor html.
so my options are to define a new route, that the $resource takes for the Query() command or define a new action that i use for the view/html stuff and use the index action again for pure restful response.
thx #sam hunter for pointing me to the right direction
the infinite loop that i received is explained here: One dimensional array of strings being parsed to 2d by angular resource
i love stack overflow

Passing selected value of a select to form action

I have a menu that I want to differ based on which account is currently selected in the system.
I have a page that allows a user to select an account from an html select. When the user submits the form from the account selection page I want to call the menu method on my controller passing in the selected value so my url looks correct.
Here is the existing template from the page that allows a user to select an account:
#helper.form(action = routes.Accounts.menu {
<table>
<tr>
<td><select id="accountNames">
#accountNames.map { name =>
<option value="#name">#name</option>
}
</select></td>
</tr>
<tr>
<td>
<p>
<input type="submit" value="Choose">
</p>
</td>
</tr>
</table>
}
From my routes file:
GET /account/:accountName/menu controllers.Accounts.menu(accountName: String)
How do I reference the selected value from my select (id="accountNames") and pass it into my form action?
Actually I think you're on the wrong side for doing that.
If the form's action has to change over the use of your 'select', it has to be done using JS.
So when the form is submitted (event submit) you have to update the url.
This can be done easily using javascriptRoutes.
So you have to do several things:
1/ create the javascriptRouter (assuming your add it in Application.scala)
def javascriptRoutes = Action {
Ok(
Routes.javascriptRouter("playRoutes")(
//accounts
controllers.routes.javascript.Accounts.menu
)
).as("text/javascript")
}
2/ define it in your routes file
# Javascript routing
GET /assets/javascripts/routes controllers.Application.javascriptRoutes
3/ add the related javascript file import in your views, let say in main.scala.html
<script type="text/javascript" src="#routes.Application.javascriptRoutes"></script>
4/ add a submit handler to your form that does that before executing the default behavior
$("form").submit(function () {
//this computes the correct URL giving a parameter which is the value of the selected option
var newURl = playRoutes.controllers.Accounts.menu($("#accountNames").val()).url
$(this).attr("action", newUrl);
})
Notice how we've used playRoutes both in the controller (1) and the JS call (4).