I'm pretty sure I'm doing this 100% wrong, but I have three buttons that toggle separate functions. I'm trying to determine the best way to write the functions in my controller.
The buttons:
<button ng-class="{selected:vm.selectedOption==='1'}" ng-click="vm.selectOption('1')">1</button>
<button ng-class="{selected:vm.selectedOption==='2'}" ng-click="vm.selectOption('2')">2</button>
<button ng-class="{selected:vm.selectedOption==='3'}" ng-click="vm.selectOption('3')">3</button>
My methods:
vm.selectOption = function(1) {
// function
};
vm.selectOption = function(2) {
// function
};
vm.selectOption = function(3) {
// function
};
or
vm.selectOption = function(option) {
if (vm.selectedOption==='1') {
// function
};
if (vm.selectedOption==='2') {
// function
};
if (vm.selectedOption==='3') {
// function
};
};
or I imagine there is a more correct way to do this that I'm not seeing.
You are passing the option to select to the function so:
vm.selectOption = function(option) {
vm.selectedOption = option;
}
If you are looking to do something specific based on the option you just set:
vm.selectOption = function(option) {
vm.selectedOption = option;
switch(option) {
case '1':
// do something
break;
case '2':
// do something else
break;
case '3':
// do something completely different
break;
}
}
Related
What is the better way to update state in this context inside a reducer?
case DELETE_INTEREST:
let deleteInterests = state.user.interests;
let index = deleteInterests.findIndex(i => i == action.payload);
deleteInterests.splice(index, 1);
return { ...state, user: { ...state.user, interests: deleteInterests } };
ESLint doesn't like let statements inside case blocks inside a reducer, getting:
eslint: no-case-declaration - unexpected lexical declaration in case
block
ESLint doesn't like let statements inside case blocks inside a
reducer, Why?
This is discouraged because it results in the variable being in scope outside of your current case. By using a block you limit the scope of the variable to that block.
Use {} to create the block scope with case, like this:
case DELETE_INTEREST: {
let .....
return (...)
}
Check this snippet:
function withOutBraces() {
switch(1){
case 1:
let a=10;
console.log('case 1', a);
case 2:
console.log('case 2', a)
}
}
function withBraces() {
switch(1){
case 1: {
let a=10;
console.log('case 1', a);
}
case 2: {
console.log('case 2', a)
}
}
}
console.log('========First Case ============')
withOutBraces()
console.log('========Second Case ============')
withBraces();
For deleting the element from array, use array.filter, because splice will do the changes in original array. Write it like this:
case DELETE_INTEREST:
let deleteInterests = state.user.interests;
let newData = deleteInterests.filter(i => i !== action.payload);
return { ...state, user: { ...state.user, interests: newData } };
try to encapsulate the inside the case with {}
like this look simple example
case EnumCartAction.DELETE_ITEM: {
const filterItems = state.cart.filter((item) => item._id !== action.payload)
return {
...state,
cart: filterItems
}
}
An easy fix is to use brackets {} to encapsulate your case code.
If you're using return you might need to add break in some cases.
Our web application has various html forms that each contain a list of form fields. Using Protractor, I'm looking for a way to retrieve the list of form fields: the field label, input type (textbox, select, radio, etc...), and input control (for setting the value later on). I then want to populate certain values for the fields in the form dynamically.
Here is the definition of the form field labels and the values I want to set:
this.fields = {
'Listing Agent': 1,
'Property Class': 1,
'Property Type': 2,
'Transaction Type': 'Sale',
'Ownership Terms': 'Sole Ownership',
'Listing Agreement': 'Yes',
'Display Listing on Internet': 'Yes',
'Display Address on Internet': 'Yes',
'Allow Automated Valuation on Internet': 'Yes',
'Allow Public Comment/Reviews on Internet': 'Yes'
};
I then retrieve the elements that match those field names by label text:
this.elements = form.find.allElements(this.fields);
When calling that method it retrieves the correct elements, but then I'm having trouble with setting the input type for each field. Checking the input type of a field returns a promise, not the actual value, so I can't figure out how to retrieve the input type for each element and then return an array of all of the elements.
this.find = {
allElements: function (fields) {
var items = [];
for (var key in fields) {
var el = element(by.cssContainingText('.sheet-grid-row', key));
this.getElementType(el).then(function (type) {
var item = {
type: type,
label: key,
isRequired: false,// TODO: el.getAttribute('class').indexOf('is-required-field') > -1
input: this.getElementInput(el, type)
};
items.push(item);
});
}
return items;// TODO: Doesn't work, of course...
},
getElementType: function (el) {
var deferred = protractor.promise.defer();
el.element(by.css('select')).isPresent().then(function (exists) {
if (exists)
deferred.fulfill(self.inputTypes.select);
else {
el.element(by.css('input[type="text"]')).isPresent().then(function (exists) {
if (exists)
deferred.fulfill(self.inputTypes.textbox);
else {
el.element(by.css('input[type="radio"]')).isPresent().then(function (exists) {
if (exists)
deferred.fulfill(self.inputTypes.textbox);
else
deferred.fulfill(self.inputTypes.unknown);
});
}
});
}
});
return deferred.promise;
},
getElementInput: function (el, type) {
switch (type) {
case self.inputTypes.select:
return new SelectWrapper(el.element(by.css('select')));
break;
case self.inputTypes.textbox:
return el.element(by.css('input[type="text"]'));
break;
case self.inputTypes.radio:
return el.element(by.css('input[type="radio"]'));
break;
}
return null;
}
};
At this point, I wish I could just get the native DOM elements and not deal with the promises at all. Is there a way to accomplish what I'm after?
You are trying to do asynchronous things element.isPresent() inside a synchronous loop for. You will want your allElements function to look more like this:
this.find = {
allElements: function (fields) {
var self = this;
var items = [];
var getItems = function(inputArray, currentIndex, outputArray) {
outputArray = outputArray || [];
currentIndex = currentIndex || 0;
var key = inputArray[currentIndex];
if (key) {
var el = element(by.cssContainingText('.sheet-grid-row', key));
return self.getElementType(el).then(function(type) {
var item = {
type: type,
label: key,
isRequired: false,// TODO: el.getAttribute('class').indexOf('is-required-field') > -1
input: self.getElementInput(el, type)
};
outputArray.push(item);
}).then(function() {
return getItems(inputArray, currentIndex + 1, outputArray);
});
} else {
return Promise.resolve(outputArray);
}
};
return getItems(Object.keys(fields));
},
getElementType: function (el) {
var self = this;
return el.element(by.css('select')).isPresent().then(function(exists) {
if (exists) {
return self.inputTypes.select;
} else {
return el.element(by.css('input[type="text"]')).isPresent().then(function(exists) {
if (exists) {
return self.inputTypes.textbox;
} else {
return el.element(by.css('input[type="radio"]')).isPresent().then(function(exists) {
if (exists) {
return self.inputTypes.radio;
} else {
return self.inputTypes.unknown;
}
});
}
});
}
});
},
getElementInput: function (el, type) {
switch (type) {
case self.inputTypes.select:
return new SelectWrapper(el.element(by.css('select')));
break;
case self.inputTypes.textbox:
return el.element(by.css('input[type="text"]'));
break;
case self.inputTypes.radio:
return el.element(by.css('input[type="radio"]'));
break;
}
return null;
}
};
Keep in mind that the output of find.allElements is now a promise, as it should be, so its usage will be something like:
this.find.allElements(this.fields).then(function(items) {
console.log('this is an array of my ' + items.length + ' items');
});
In my angular factory I have 3 functions that return the names of some codes, depending on their type: eat, feel and source. This is one of the functions
getFeelingCode: function(feelingCode) {
var feelTag=$filter('filter')(constant.feeling_code, {code: feelingCode}, function(a, b) {
return a === b;
});
if(feelTag[0])
return feelTag[0].label;
else
return feelingCode;
}
The 3 functions bassically do the same thing, and I would like to convert them into one function, that would look like this:
vApp.factory('BgsFactory', function(constant, $filter) {
return {
getCode: function(Code, StringCode) {
var cst;
switch(StringCode) {
case "eating_code": cst = constant.eating_code; break;
case "feeling_code": cst = constant.feeling_code; break;
case "source_code": cst = constant.source_code; break;
}
var Tag=$filter('filter')(cst, {code: Code}, function(a, b) {
return a === b;
});
if(Tag[0])
return Tag[0].label;
else
return Code;
}
In my view I'm calling them like that
<td>{{getSourceCode(item.tags.source)}}</td>
<td>{{getEatingCode(item.tags.eat_code)}}</td>
<td>{{getFeelingCode(item.tags.feeling_code)}}</td>
But I don't know how to set them in my controller. I tried something like that, but it fails saying that the functions don't exist.
$scope.getEatingCode = BgsFactory.getCode("eating_code");
$scope.getFeelingCode = BgsFactory.getCode("feeling_code");
$scope.getSourceCode = BgsFactory.getCode("source_code");
How should I pass the params?
Thank you!
If I wasn't get is wrong, would be like this
$scope.getEatingCode = function(code) { return BgsFactory.getCode(code, "eating_code"); };
$scope.getFeelingCode = function(code) { return BgsFactory.getCode(code, "feeling_code"); };
$scope.getSourceCode = function(code) { return BgsFactory.getCode(code, "source_code"); };
I'm using $filter to iterate through an array and fetch a specific value
Below is my code:
var selected = $filter('filter')($scope.folders, {url: el.selected[0] });
This code is working, but I got a problem when the url contain an accent and space like so :
/Users/Me/project/products/Poste à souder
In that case the string comparaison isn't working anymore.
What is the cleaner way to solve this situation ?
That true. As a francophone, I've often encounter encoding/decoding issues with angularjs.
The source code of the default filter is as follow
function filterFilter()
{
return function(array, expression, comparator)
{
if (!isArrayLike(array))
{
if (array == null)
{
return array;
}
else
{
throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
}
}
var expressionType = getTypeForFilter(expression);
var predicateFn;
var matchAgainstAnyProp;
switch (expressionType)
{
case 'function':
predicateFn = expression;
break;
case 'boolean':
case 'null':
case 'number':
case 'string':
matchAgainstAnyProp = true;
//jshint -W086
case 'object':
//jshint +W086
predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
break;
default:
return array;
}
return Array.prototype.filter.call(array, predicateFn);
};
}
and the predicate generator stand as follow: it generate the default comparator if the provided one is not a function
function createPredicateFn(expression, comparator, matchAgainstAnyProp)
{
var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
var predicateFn;
if (comparator === true)
{
comparator = equals;
}
else if (!isFunction(comparator))
{
comparator = function(actual, expected)
{
if (isUndefined(actual))
{
// No substring matching against `undefined`
return false;
}
if ((actual === null) || (expected === null))
{
// No substring matching against `null`; only match against `null`
return actual === expected;
}
if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual)))
{
// Should not compare primitives against objects, unless they have custom `toString` method
return false;
}
actual = lowercase('' + actual);
expected = lowercase('' + expected);
return actual.indexOf(expected) !== -1;
};
}
predicateFn = function(item)
{
if (shouldMatchPrimitives && !isObject(item))
{
return deepCompare(item, expression.$, comparator, false);
}
return deepCompare(item, expression, comparator, matchAgainstAnyProp);
};
return predicateFn;
}
Too much speech. You have the choice:
Provide a comparator to your filter see the doc
but remember that you can't define inline function in angular template
you can define a function in that scope, but it will only be available in that scope
You can write your own filter
.filter('myCustomFilter', function()
{
return function(input, criteria)
{
... // your logic here
return ...// the filtered values
};
})
Maybe it's best to write your own filter:
app.filter("customFilter", function () {
//the filter will accept an input array, the key you want to look for and the value that the key should have
return function (array, key, value) {
return array.filter(function(x){
return (x.hasOwnProperty(key) && (x[key] === value));
});
};
});
And use it in your controller like:
$scope.filtered = $filter("customFilter")($scope.folders, "url", "/Users/Me/project/products/Poste à souder");
Check out a working demo here.
I'm trying to construct a translated message by looping over an array of objects and then adding a new "message" property to that object containing the translated string. I see the correct message output while inside $translate.then(); but when I assign the message to the object it is undefined. What is the correct way to resolve the promise returned from $translate.then() and assign it to the "message" property?
//items.controller.js
function getItems() {
return itemsFactory.getItems()
.then(function (response) {
vm.items = initItemsList(response.activities);
});
}
function initItemsList(itemsList) {
for (var i = 0; i < itemsList.length; i++){
var activityType = itemsList[i].activityType;
switch (activityType){
case "HISTORY": {
var itemName = itemsList[i].item.itemName;
var itemVersion = itemsList[i].item.itemVersion;
$translate('activity.'+activityType, { itemname: itemName, itemversion: itemVersion }).then(function(content){
vm.itemContent = content;
console.log(vm.itemContent); // correct message displayed.
});
break;
}
default: {
break;
}
}
itemsList[i].message = vm.itemContent; // undefined
}
return itemsList;
}
// translation.json
"activity : {
"HISTORY" : "History for {{ itemname }} {{ itemversion }}."
}
Promises are always resolved asynchronously. So the statement
itemsList[i].message = vm.itemContent;
, which is executed right after the switch, is executed before the callback passed to the $translate promise. Just move the statement to the callback:
$translate('activity.'+activityType, { itemname: itemName, itemversion: itemVersion }).then(function(content){
vm.itemContent = content;
console.log(vm.itemContent);
itemsList[i].message = vm.itemContent;
});
As #Vegar correctly states, the code inside then is executed after the assignment so moving the assignment inside then function will take care of the problem. However, your itemsList will be returned from the function before all the translations are done so you will need to return a promise that resolves when all translations are done:
function initItemsList(itemsList) {
var allTranslations = [];
for (var i = 0; i < itemsList.length; i++){
var activityType = itemsList[i].activityType;
switch (activityType){
case "HISTORY": {
var itemName = itemsList[i].item.itemName;
var itemVersion = itemsList[i].item.itemVersion;
allTranslations.push($translate('activity.'+activityType, { itemname: itemName, itemversion: itemVersion }).then(function(content){
vm.itemContent = content;
itemsList[i].message = vm.itemContent;
}));
break;
}
default: {
break;
}
}
}
return $q.all(allTranslations);
}
The caller of your function will have to do like:
initItemList(itemList).then(function(translatedList){
//Do stuff with translated list
});