Protractor: creating an array from nested elements - angularjs

I'm trying to get elements in my DOM and return them as an array. They are nested. I want protractor to return something like this:
{
category_title: "cat1",
items: {item_title: "item1"}, {item_title: "item2"}
},
{
category_title: "cat2",
items: {item_title: "item1"}, {item_title: "item2"}
}
DOM:
<div ng-repeat="category in categories">
<div id="category-title">{{ category.title }} </div>
<div ng-repeat="item in category.items">
<div id="item-title">{{ item.title }} </div>
</div>
</div>
This is what I'm trying in Protractor:
return element.all(by.repeater("category in categories")).map(function(elm) {
var category_title = elm.element(by.id('category-title')).getText(),
var t = {
category_title: category_title,
dimensions: {}
};
// After this part, I'm lost...
return elm.all(by.repeater("item in category.items")).map(function(elm) {
return {
category_title: category_title,
dimension: elm.element(by.id('item-title')).getText(),
}
});
});

This seems to work, but I'm not 100% sure it's correct
return element.all(by.repeater("category in categories")).map(function(elm) {
return {
category_title: elm.element(by.id('category-title')).getText(),
items: elm.all(by.id("item-title")).getText()
}
})

Related

How can I make square-connect work with angularjs?

Basing myself on the example provided by the SquareUp documentation (https://github.com/square/connect-api-examples.git). I am trying to integrate squareup to process payments with CC but I do not know what happens.
the view:
<div class="bg-light lter b-b wrapper-md">
<h1 class="m-n font-thin h3"></h1>
</div>
<div class="wrapper-md" >
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading font-bold">CC info</div>
<div class="panel-body">
<div class="no-boot" ng-controller="PaymentController" ng-cloak>
<div id="successNotification" ng-show="isPaymentSuccess">
Card Charged Succesfully!!
</div>
<form novalidate id="payment-form" ng-hide="isPaymentSuccess">
<div id="card-errors" ng-repeat="error in card_errors">
<li>{{error.message}}</li>
</div>
<div>
<label>Card Number</label>
<div ng-model="data.card.card_number" id="sq-card-number"></div>
</div>
<div>
<label>CVV</label>
<div ng-model="data.card.cvv" id="sq-cvv"></div>
</div>
<div>
<label>Expiration Date</label>
<div ng-model="data.card.expiration_date" id="sq-expiration-date"></div>
</div>
<div>
<label>Postal Code</label>
<div ng-model="data.card.postal_code" id="sq-postal-code"></div>
</div>
<div>
<input ng-click="submitForm()" ng-disabled="isProcessing" type="submit" id="submit" value="Buy Now" class="btn btn-primary">
</div>
</form>
<button id="sq-apple-pay" class="button-apple-pay-block" ng-show="supportApplePay"></button>
</div>
</div>
</div>
</div>
</div>
</div>
the controller:
'use strict';
/* Controllers */
// signin controller
app.controller('PaymentController', ['$scope', '$http', function($scope, $http) {
//for showing #successNotification div
$scope.isPaymentSuccess = false;
//for disabling payment button
$scope.isProcessing = false;
//for disabling apple pay button
$scope.supportApplePay = false;
$scope.data = {
product_id: "001",
user: {},
card: {},
products: {
"001": {
"name": "Paper Origami 1:10,000 scale model (11 inch)",
"value":"1.0",
},
"002": {
"name": "Plastic 1:5000 scale model (22 inch)",
"value":"49.0",
},
"003": {
"name": "Metal & Concrete 1:1000 scale replica (9 feet)",
"value":"5000.0",
}
}
};
$scope.submitForm = function(){
console.log($scope.data)
$scope.isProcessing = true;
$scope.paymentForm.requestCardNonce();
return false
}
var cardNumber = $scope.data.card.card_number = 5409889944179029;
var cvv = $scope.data.card.cvv = 111;
var expirationDate = $scope.data.card.expirationDate = '02/21';
var postalCode = $scope.data.card.postalCode = 3311;
$scope.paymentForm = new SqPaymentForm({
applicationId: 'sandbox-sq0idp-IsHp4BXhhVus21G5JPyYpw',
locationId: 'CBASECJCvmqtoIL1fn3iReEjQRcgAQ',
inputClass: 'sq-input',
inputStyles: [
{
fontSize: '14px',
padding: '7px 12px',
backgroundColor: "transparent"
}
],
cardNumber: {
elementId: 'sq-card-number',
placeholder: '5409889944179029',
value: '5409889944179029'
},
cvv: {
elementId: 'sq-cvv',
placeholder: '111',
value: '111'
},
expirationDate: {
elementId: 'sq-expiration-date',
placeholder: '04/21',
value: '04/21'
},
postalCode: {
elementId: 'sq-postal-code',
placeholder: '33114',
value: '33114'
},
applePay: {
elementId: 'sq-apple-pay'
},
// cardNumber:''+cardNumber,
// cvv:''+cvv,
// expirationDate:''+expirationDate,
// postalCode:''+postalCode,
callbacks: {
cardNonceResponseReceived: function(errors, nonce, cardData) {
if (errors){
$scope.card_errors = errors
$scope.isProcessing = false;
$scope.$apply(); // required since this is not an angular function
}else{
$scope.card_errors = []
$scope.chargeCardWithNonce(nonce);
}
},
unsupportedBrowserDetected: function() {
// Alert the buyer
},
methodsSupported: function (methods) {
console.log(methods);
$scope.supportApplePay = true
$scope.$apply(); // required since this is not an angular function
},
createPaymentRequest: function () {
var product = $scope.data.products[$scope.data.product_id];
return {
requestShippingAddress: true,
currencyCode: "USD",
countryCode: "US",
total: {
label: product["name"],
amount: product["value"],
pending: false,
}
};
},
// Fill in these cases to respond to various events that can occur while a
// buyer is using the payment form.
inputEventReceived: function(inputEvent) {
switch (inputEvent.eventType) {
case 'focusClassAdded':
// Handle as desired
break;
case 'focusClassRemoved':
// Handle as desired
break;
case 'errorClassAdded':
// Handle as desired
break;
case 'errorClassRemoved':
// Handle as desired
break;
case 'cardBrandChanged':
// Handle as desired
break;
case 'postalCodeChanged':
// Handle as desired
break;
}
}
}
});
$scope.chargeCardWithNonce = function(nonce) {
alert("no");
var url = "libs/php_payment/process-card.php";
var data = {
nonce: nonce,
product_id: $scope.data.product_id,
name: $scope.data.user.name,
email: $scope.data.user.email,
street_address_1: $scope.data.user.street_address_1,
street_address_2: $scope.data.user.street_address_2,
city: $scope.data.user.city,
state: $scope.data.user.state,
zip: $scope.data.user.zip
};
$http.post(url, data).success(function(data, status) {
if (data.status == 400){
// display server side card processing errors
$scope.isPaymentSuccess = false;
$scope.card_errors = []
for (var i =0; i < data.errors.length; i++){
$scope.card_errors.push({message: data.errors[i].detail})
}
}else if (data.status == 200) {
$scope.isPaymentSuccess = true;
}
$scope.isProcessing = false;
}).error(function(){
$scope.isPaymentSuccess = false;
$scope.isProcessing = false;
$scope.card_errors = [{message: "Processing error, please try again!"}];
})
}
//build payment form after controller loads
var init = function () {
$scope.paymentForm.build()
};
init();
}]);
error: "Error: [$rootScope:inprog] $digest already in progress
I haven't done angular in a while, but I'm betting that your issue is in:
methodsSupported: function (methods) {
console.log(methods);
$scope.supportApplePay = true
$scope.$apply(); // required since this is not an angular function
},
You are calling $apply() after a non-asyc call, generally you apply new data that you got asynchronously. See Angular Docs

Vuejs checkbox indeterminate status

I need little help. I have world regions list with countries inside:
{
'North American Countries' : {
'countries' : {
'us' : { 'name' : 'United States' } ,
'ca' : { 'name': 'Canada' }
.
.
.
.
}
},
'European Countries' : {
......
}
}
HTML:
<ul v-for="(regionName, region) in regions">
<li>
<label>{{ regionName }}</label>
<input type="checkbox" #change="toggleGroupActivation(regionName)">
</li>
<li v-for="country in region.countries">
<div>
<label for="country-{{ country.code }}">{{ country.name }}</label>
<input id="country-{{ country.code }}" type="checkbox" :disabled="!country.available" v-model="country.activated" #change="toggleCountryActivation(regionName, country)">
</div>
</li>
</ul>
And I try to build the list with checkboxes, where you can select countries. If check whole region's checkbox, automatically checked all countries in it region. If are checked only few countries in region(not all), need to display indeterminate checkbox status by region checkbox. How to handle it?
The usual solution to the Select All checkbox is to use a computed with a setter. When the box is checked, all the sub-boxes are checked (via the set function). When a sub-box changes, the Select All box value is re-evaluated (in the get function).
Here, we have a twist: if the sub-boxes are mixed, the Select All box should indicate that somehow. The approach is still to use a computed, but instead of just true and false values, it can return a third value.
There's no built-in way of representing a third value in a checkbox; I've chosen to replace it with a yin-yang emoji.
const rawData = {
'North American Countries': {
'countries': {
'us': {
'name': 'United States'
},
'ca': {
'name': 'Canada'
}
}
},
'European Countries': {
countries: {}
}
};
const countryComponent = Vue.extend({
template: '#country-template',
props: ['country', 'activated'],
data: () => ({ available: true })
});
const regionComponent = Vue.extend({
template: '#region-template',
props: ['region-name', 'region'],
data: function () {
const result = {
countriesActivated: {}
};
for (const c of Object.keys(this.region.countries)) {
result.countriesActivated[c] = { activated: true };
}
return result;
},
components: {
'country-c': countryComponent
},
computed: {
activated: {
get: function() {
let trueCount = 0;
let falseCount = 0;
for (const cName of Object.keys(this.countriesActivated)) {
if (this.countriesActivated[cName]) {
++trueCount;
} else {
++falseCount;
}
}
if (trueCount === 0) {
return false;
}
if (falseCount === 0) {
return true;
}
return 'mixed';
},
set: function(newValue) {
for (const cName of Object.keys(this.countriesActivated)) {
this.countriesActivated[cName] = newValue;
}
}
}
}
});
new Vue({
el: 'body',
data: {
regions: rawData
},
components: {
'region-c': regionComponent
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<template id="region-template">
<li>
<label>{{ regionName }}</label>
<input v-if="activated !== 'mixed'" type="checkbox" v-model="activated">
<span v-else>☯</span>
<ul>
<country-c v-for="(countryName, country) in region.countries" :country="country" :activated.sync="countriesActivated[countryName]"></country-c>
</ul>
</li>
</template>
<template id="country-template">
<li>
<label for="country-{{ country.code }}">{{ country.name }}</label>
<input id="country-{{ country.code }}" type="checkbox" :disabled="!available" v-model="activated">
</li>
</template>
<ul>
<region-c v-for="(regionName, region) in regions" :region-name="regionName" :region="region" :countriesActivated=""></region-c>
</ul>

How to "connect" 2 variables in Angular by some value

I have 2 variables in controller:
$scope.first = [
{ id="11", nameF="aaaa1" },
{ id="12", nameF="bbbb1" }
]
$scope.second = [
{ id="21", nameS="aaaa2", idFirst="11" },
{ id="22", nameS="bbbb2", idFirst="12" },
{ id="23", nameS="cccc2", idFirst="12" }
]
In a template I have ngRepeat for variable second:
<div ng-repeat="item in second>
<div>{{item.nameS}}</div>
<div>{{item.idFirst}}</div>
</div>
For every item.idFirst I would like to write out matching nameF instead. What is the best practice to achieve that? I can't seem to figure out a simple way to do it, but suppose there has to be one. Thanx!
You can use custom filter if you don't want to create one single object holding the expected structure.
HTML :
<div ng-repeat="item in second">
<div>{{item.nameS}}</div>
<div>{{item.idFirst | getMatchName:first}}</div>
</div>
JS:
.filter('getMatchName', function() {
return function(strName, arrFirst) {
arrFirst.forEach(function(val, key) {
if (val.id == strName) {
strName = val.nameF;
}
})
return strName;
}
})
Here is working plunker
If I understand correctly,
$scope.getNameF = function(idFirst){
for(var i = 0; i < $scope.first.length; i++){
if($scope.first[i].id === idFirst){
return $scope.first[i].nameF;
}
}
return undefined;
}
<div ng-repeat="item in second">
<div>{{item.nameS}}</div>
<div>{{getNameF(item.idFirst)}}</div>
</div>
EDIT
Also you can prepare data before rendering:
for(var i = 0; i < $scope.second.length; i++){
$scope.second[i].nameF = getNameF($scope.second[i].idFirst);
}
<div ng-repeat="item in second">
<div>{{item.nameS}}</div>
<div>{{item.nameF}}</div>
</div>
You can think of your template logic a bit like code, calling values from other variables: yourValue[another.value].nameF
Change your code to this and it should work
<div ng-repeat="item in second>
<div>{{item.nameS}}</div>
<div>{{first[item.idFirst].nameF}}</div>
</div>

v-for and computed properties calling methods

I'm trying to update some properties on a VueJS object list, but I'm having some glitches.
Here's the HTML:
<div id="el">
<p>Total Chats: {{ chats.length }} ({{ unreadCount }} unread)</p>
<button v-on:click="__addChat(true)">Add a Chat</button>
<button v-on:click="__makeAllRead">Read All</button>
<pre>{{ $data | json }}</pre>
<ul>
<li v-for="chat in chats">
<p>
Message {{ chat.id }}
<span v-if="chat.unread"><strong>(UNREAD)</strong></span>
</p>
</li>
</ul>
</div>
And the Vue code:
var vm = new Vue({
el: '#el',
data: {
nextID : 2,
chats: {
'6fc5gh4j3kl_FIRST': {
id : 1,
unread : true,
body : Date(),
}
},
},
methods: {
__addChat: function (unread) {
var chat = {
id : this.nextID++,
unread : unread,
body : Date(),
};
this.chats[this.__makeHash()] = chat;
console.log(this.chats);
},
__makeAllRead : function() {
console.log(Object.keys(this.chats));
for ( var key in Object.keys(this.chats) ) {
// if any tests are invalid
if ( this.chats.hasOwnProperty(key) ) {
this.chats[key] = true;
}
}
},
__countUnread : function() {
var unread = 0;
for ( var key in Object.keys(this.chats) ) {
// if any tests are invalid
if ( this.chats.hasOwnProperty(key) && this.chats[key].unread ) {
unread++;
}
}
return unread;
},
__makeHash: function() {
return '6fc5gh4j3kl1AZ0' + Math.floor((Math.random() * 100) + 1);
},
},
ready: function() {
this.__addChat(false);
},
computed: {
unreadCount: function () {
console.log('counting...');
return this.countUnread();
}
}
});
Here's a Fiddle: http://jsfiddle.net/522aw2n5/7/
Things I cannot understand/fix on this code:
1) A computed property using a method to update the count
2) It displays only the object manually created, not the dynamically ones.
3) I cannot make all messages read by clicking a button.
This is Vue 1.0.0 RC2.
Could you, please, tell me what Am I doing wrong?
Answered on VueJS's Gitter
HTML
<div id="el">
<p>Total Chats: {{ totalChats }} ({{ unreadCount }} unread)</p>
<button v-on:click="__addChat(true)">Add a Chat</button>
<button v-on:click="__makeAllRead">Read All</button>
<pre>{{ $data | json }}</pre>
<ul>
<li v-for="chat in chats">
<p>
{{ chat.id }}
<span v-if="chat.unread"><strong>(UNREAD)</strong></span>
</p>
</li>
</ul>
</div>
Vue Code
var vm = new Vue({
el: '#el',
data: {
nextID : 2,
chats: {
'6fc5gh4j3kl_FIRST': {
id : 1,
unread : true,
body : Date(),
}
},
},
methods: {
__addChat: function (unread) {
var chat = {
id : this.nextID++,
unread : unread,
body : Date(),
};
this.$set('chats.' + this.__makeHash(), chat);
},
__makeAllRead : function() {
console.log(Object.keys(this.chats));
for ( var key in this.chats ) {
// if any tests are invalid
if ( this.chats.hasOwnProperty(key) ) {
this.chats[key].unread = false;
}
}
},
__makeHash: function() {
return 'fc5gh4j3kl1AZ0' + Math.floor((Math.random() * 100) + 1);
},
},
ready: function() {
this.__addChat(false);
},
computed: {
totalChats: function() {
var size = 0, key;
for (key in this.chats) {
if (this.chats.hasOwnProperty(key)) size++;
}
return size;
},
unreadCount: function () {
var unread = 0;
for ( var key in this.chats ) {
// if any tests are invalid
if ( this.chats.hasOwnProperty(key) && this.chats[key].unread ) {
unread++;
}
}
return unread;
}
}
});

Angular-xeditable: Need a checklist that displays checked items

I would like to use a check list and show the user the boxes she has checked.
I am using this framework: http://vitalets.github.io/angular-xeditable/#checklist . See his example 'Checklist' versus his example 'Select multiple'. However, I do not want to display a link with a comma separated string, i.e., join(', '). I would like each selection to appear beneath the previous, in an ordered list or similar.
Pretty much copied from his examples, here are the guts of my controller:
$scope.userFeeds = {
feeds: {}
};
$scope.feedSource = [
{ id: 1, value: 'All MD' },
{ id: 2, value: 'All DE' },
{ id: 3, value: 'All DC' }
];
$scope.updateFeed = function (feedSource, option) {
$scope.userFeeds.feeds = [];
angular.forEach(option, function (v) {
var feedObj = $filter('filter')($scope.feedSource, { id: v });
$scope.userFeeds.feeds.push(feedObj[0]);
});
return $scope.userFeeds.feeds.length ? '' : 'Not set';
};
And here is my html:
<div ng-show="eventsForm.$visible"><h4>Select one or more feeds</h4>
<span editable-select="feedSource"
e-multiple
e-ng-options="feed.id as feed.value for feed in feedSource"
onbeforesave="updateFeed(feedSource, $data)">
</span>
</div>
<div ng-show="!eventsForm.$visible"><h4>Selected Source Feed(s)</h4>
<ul>
<li ng-repeat="feed in userFeeds.feeds">
{{ feed.value || 'empty' }}
</li>
<div ng-hide="userFeeds.feeds.length">No items found</div>
</ul>
</div>
My problem is - display works with editable-select and e-multiple, but not with editable-checklist. Swap it out and nothing is returned.
To workaround, I have tried dynamic html as in here With ng-bind-html-unsafe removed, how do I inject HTML? but I have considerable difficulties getting the page to react to a changed scope.
My goal is to allow a user to select from a checklist and then to display the checked items.
Try this fiddle: http://jsfiddle.net/mr0rotnv/15/
Your onbeforesave will need to return false, instead of empty string, to stop conflict with the model update from xEditable. (Example has onbeforesave and model binding working on the same variable)
return $scope.userFeeds.feeds.length ? false : 'Not set';
If you require to start in edit mode add the attribute shown="true" to the surrounding form element.
Code for completeness:
Controller:
$scope.userFeeds = {
feeds: []
};
$scope.feedSource = [
{ id: 1, value: 'All MD' },
{ id: 2, value: 'All DE' },
{ id: 3, value: 'All DC' }
];
$scope.updateFeed = function (feedSource, option) {
$scope.userFeeds.feeds = [];
angular.forEach(option, function (v) {
var feedObj = $filter('filter')($scope.feedSource, { id: v });
if (feedObj.length) { // stop nulls being added.
$scope.userFeeds.feeds.push(feedObj[0]);
}
});
return $scope.userFeeds.feeds.length ? false : 'Not set';
};
Html:
<div ng-show="editableForm.$visible">
<h4>Select one or more feeds</h4>
<span editable-checklist="feedSource"
e-ng-options="feed.id as feed.value for feed in feedSource"
onbeforesave="updateFeed(feedSource, $data)">
</span>
</div>
<div ng-show="!editableForm.$visible">
<h4>Selected Source Feed(s)</h4>
<ul>
<li ng-repeat="feed in userFeeds.feeds">{{ feed.value || 'empty' }}</li>
<div ng-hide="userFeeds.feeds.length">No items found</div>
</ul>
</div>
Css:
(Used to give the "edit view" a list appearance)
.editable-input label {display:block;}
Also there is the option of using a filter if you do not need to do any validation or start in edit mode.
Controller:
$scope.user = { status: [2, 3] };
$scope.statuses = [
{ value: 1, text: 'status1' },
{ value: 2, text: 'status2' },
{ value: 3, text: 'status3' }
];
$scope.filterStatus = function (obj) {
return $scope.user.status.indexOf(obj.value) > -1;
};
HTML:
<a href="#" editable-checklist="user.status" e-ng-options="s.value as s.text for s in statuses">
<ol>
<li ng-repeat="s in statuses | filter: filterStatus">{{ s.text }}</li>
</ol>
</a>

Resources