Why am I getting "Error: 'undefined' is not a function (evaluating 'items.split(',')')" here? - angularjs

I'm getting the following error while typing into the field filtered by 'completeList'. Why is this happening?
JavaScript
angular.module('myApp', ['timer'])
.controller('AppCtrl',['$scope', function($scope){
$scope.gameOn = true;
$scope.leaders = true;
$scope.myScore = true;
}])
.filter('completeList', function() {
return function(items) {
var list = [];
if(items) {
list = items.split(',');
var last = list[list.length - 1];
if(items.charAt(items.length - 1) != ',' || last.length === 0)
list.pop();
}
return list;
};
});
HTML
<div ng-show="gameOn" ng-controller="LabelCtrl" class="row marketing">
<div class="col-lg-4">
<h4>Enter comma-separated labels for this image</h4>
<form role="form" class="form-inline" >
<input ng-list ng-model="labels" placeholder="Enter labels" class="form-control" type="text" >
<button class="form-control" class="btn btn-xs btn-success">Submit</button>
</form>
</div>
<div class="col-lg-2">
<h4>Labels</h4>
<div>
<ol>
<li ng-repeat="label in labels track by $index | completeList">
{{ label }}
</li>
</ol>
</div>
</div>

The good news is that you only have a minor Angular syntax error. It is actually mentioned in the documentation:
Filters should be applied to the expression, before specifying a
tracking expression.
...
For example: 'item in items | filter:searchText track by item.id' is a pattern that might be used to apply a filter to items in conjunction with a tracking expression.
Given this knowledge, just change your ngRepeat line to the following, it does work exactly as you intended and works perfectly on my side:
<li ng-repeat="label in labels | completeList track by $index">

I do not know what the data structure of labels so here is my best stab at it. Filters are applied onto the instance of your iteration of your loop. It looks like you may be trying to apply the filter to the entire collection instead of that index of the loop. The filter is applied to label not labels. In this case you cannot split it. Again I dont know your data structure so I am kind of guessing here. It would be helpful if you could reveal what labels is.
Thanks,
Jordan

Related

AngularJS filter is requiring two clicks before it filters results

I am a beginner at angular. I am pretty certain I am doing this the completely incorrect way but because I finally have it "somewhat working" as it works on the second click I am stuck going in this direction and can't seem to figure out another way to do it.
The filter sorts on the second click because it is initialing as "undefined" before the first click and sets it based on that I believe.
In my html:
<div class="col-xs-12 col-sm-4 location-list" ng-repeat="key in careerlist.location">
<div class="locations" ng-click="careerlist.criteriaMatch()">{{key}}
</div>
</div>
<div class="col-xs-12">
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-4 job-container" ng-repeat="job in careerlist.career | filter : searchText | filter: selectExperience | filter: careerlist.criteria.name">
<h2>
{{job.title}}
</h2>
<h3>
{{job.location}}
</h3>
<div class="job-description" ng-bind-html="job.description | limitHtml : 200">
</div>
<br><br>
<button>Read More</button>
</div>
<br><br>
</div>
</div>
In my controller:
cl.criteriaMatch = function( criteria ) {
jQuery(document).on('click', '.locations', function(){
cl.criteria = {};
console.log("just the element text " + jQuery(this).text());
cl.criteria.name = jQuery(this).text();
return function( criteria ) {
return criteria.name === criteria.name;
};
});
};
Use ng-click instead of jQuery#on('click'). Angular does not know that the filter should be updated.
Update
#Makoto points out that the click is bound twice. It very much looks like you should just remove the jQuery binding altogether. I would even go so far as suggesting removing jQuery from you project.

How to target specific field in Firebase / AngularFire when more than 1 level deep

I have an ng-repeat which looks like this:
<div ng-repeat="field in fields">
{{field.label}}
<div ng-repeat="choice in field.choices track by $index">
#{{$index+1}} <input type="text" ng-model="choice.value" ng-change="fields.$save(field)">
<a ng-click="field.choices.$remove(choice)">Remove</a>
</div>
</div>
As you can see, it is 2 levels deep it has choices inside of each field, I can't figure out how to remove a specific choice on click, I tried field.choices.$remove(choice) but it doesn't do anything, when I did fields.$remove(field) it removes the entire field, and when I did fields.$remove(choice) or field.$remove(field.choices) it doesn't do anything either.
Here is how fields is initiated:
var fieldsRef = firebase.child('Fields');
$scope.fields = $firebaseArray(fieldsRef);
$scope.fields is a $firebaseArray. But the choices property within a field is not. Therefore, you cannot call field.choices.$remove().
The solution is for the ng-click to call a function in your controller to remove the choice from the field, then call $scope.fields.$save(field).
<a ng-click="$scope.removeChoiceFromField(choice, field)">Remove</a>
Controller:
$scope.removeChoiceFromField = function(choice, field) {
var i = field.choices.indexOf(choice);
if(i != -1) {
field.choices.splice(i, 1);
$scope.fields.$save(field);
}
};

ng-show if filter and inner if statements are valid

I have an ng-repeat being created like so:
<div class="items">
<!-- WANT TO HIDE THIS ENTIRE AREA IF THERE IS NO VALID ITEMS -->
<b>Items</b>
<span ng-repeat="item in items | orderBy:'priority'" itemid="{{item.$id}}" ng-if="!item.groupID || item.groupID == 'misc'">
{{item.title}}
</span>
</div>
As you can see I have an ng-if which checks if the item has a null item.groupid or is in the misc category.
Many times there is no items that match all of these criteria and in that case I want to hide the outer div <div class="items">
I can't figure out how to hide it because I can't do an ng-show on the inner elements part of the loop.
You can use a filter, and assign the result of the filter to a variable. Use that variable to decide if the enclosing div should be shown or not.
Here's a plunkr showing how it works:
<div ng-show="filteredItems.length > 0">
Test
<div ng-repeat="item in items | filter:hasGroupFilter(group) as filteredItems">
{{ item.name }} - {{ item.group }}
</div>
</div>
And in the controller:
$scope.hasGroupFilter = function(group) {
return function(item) {
return !item.group || item.group === group;
};
};
I think the cleanest way to do that is to pre filter the list, and add a condition on your parent div.items instead of using ng-if in each one of the span in the ng-repeat.
Here is a working plunker
You should filter the list in your controller and just add the condition on the parent div
<div class="items" ng-if="filteredItems.length">
<b>Items</b>
<span ng-repeat="item in filteredItems | orderBy:'priority'" itemid="{{item.$id}}">
{{item.title}}
</span>
</div>
You could potentially create a function that has access to a public variable on the outer scope. It would look something along the lines of this:
Inside your JS file
$scope.NoValidItems = true; //assume no valid items code also might look different based on how you format your angular
$scope.CheckItems = function(item){
var check = false;
if(item.groupID && item.groupID !== 'misc'){
check = true;
$scope.NoValidItems = false;
}else
check = false;
return check;
};
HTML
<div class="items" ng-if="!NoValidItems ">
<!-- WANT TO HIDE THIS ENTIRE AREA IF THERE IS NO VALID ITEMS -->
<b>Items</b>
<span ng-repeat="item in items | orderBy:'priority'" itemid="{{item.$id}}" ng-if="!CheckItems(item)">
{{item.title}}
</span>
</div>
This should work, and the if statement might not be exact, but you get the drift. Pretty much if there is one that is valid it will show, but if there is not a valid item do not show. Valid being meeting your conditional criteria. Hopefully this helps, and I explained it alright

How do I only add complete items to this list

I'm using an input field to accept comma-separated values then dynamically generating list items for each comma-separated value. However, I don't want items added to the list until they have a comma after them. For example, if I enter "Dog, Cat, Fish" in the input field, I don't want the following as I'm typing "Cat"
Dog
Ca
I want "Cat" to be added only once I've put a comma after it to indicate it is a complete entry.
Here's the code I'm using to achieve this.
<input ng-list ng-model="labels" placeholder="Enter labels">
<div>
<ul>
<li ng-repeat="label in labels track by $index">
{{ label }}
</li>
</ul>
</div>
Link to running example
What's the angular way to achieve this?
One way to do this would be to use custom filters to filter the ng-repeat items instead of relying on the ng-list directive.
See the associated PLUNKER of the code below.
HTML
<input ng-model="labels" placeholder="Enter labels" />
<div>
<ul>
<li ng-repeat="label in labels | completeList">
{{ label }}
</li>
</ul>
</div>
JAVASCRIPT
filter('completeList', function() {
return function(items) {
var list = [];
if(items) {
list = items.split(',');
var last = list[list.length - 1];
if(items.charAt(items.length - 1) != ',' || last.length === 0)
list.pop();
}
return list;
};
});

duplicates in a repeater are not allowed angular

I'm trying to create a form like below, this using ng-repeat directive in angular and it whenever I created a new row complains
"Duplicates in a repeater are not allowed.".
While I understand the solution for this is by putting "track by $index", however it causes another issue, which clicking delete on one row deletes the value of other field. So I suspect that track by index is OK for static text but not input form. So how to use ng-repeat correctly for my case?
My jsfiddle : demo.
Edit : I do aware that json array of object will solve my issue ( because for object angular create $$hashKey ) and already implemented this for most of my other module. But I am actually expecting some fix that can be done without really change my json array of string. Sorry for not being clear.
My current code :
HTML
<div class="row-fluid spacer10">
<a ng-click="addAKA()" class="btn btn-primary spacer5 left30"><i class="icon-plus icon-white"></i> Add New Alias</a>
</div>
<div class="row-fluid spacer10"></div>
<div class="row-fluid spacer5" ng-repeat="item in aliasList track by $index">
<input type="text" class="span6 left30" ng-model="item">
<button class="btn btn-danger" ng-click="deleteAKA($index)">delete</button>
<BR/>
</div>
Javascript
$scope.addAKA = function ()
{
if($scope.aliasList == null)
{
$scope.aliasList = [];
}
$scope.aliasList.push("");
$scope.aliasjson = JSON.stringify($scope.aliasList);
}
$scope.deleteAKA = function (idx)
{
var aka_to_delete = $scope.aliasList[idx];
$scope.aliasList.splice(idx, 1);
$scope.aliasjson = JSON.stringify($scope.aliasList);
}
I would guess this is caused when there are more than one empty strings in the list.
If this is the case, it is caused because any two empty strings are equals in JS and Angular repeater does not allow duplicate values (as clearly stated in the message). This is a valid decision as they have to relate an object in the list with its DOM tree to minimize DOM manipulation.
A solution would be to insert simple objects containing the string in the model:
$scope.addAKA = function () {
...
$scope.aliasList.push({value:""});
...
};
And adjust your template:
<input type="text" class="span6 left30" ng-model="item.value">
Since all new objects are different, your problem should be solved.
See a fiddle where a filter is implemented to transform the model back to a list of strings.
When you type in a new created input, your list stays the same. Angular on any list change will update the view (ng-repeat) and remove all new stored text. Therefore we need to add ng-change to update our list on any input change
Add ng-change="change(i, $index) to your item and it should work
HTML
<div ng-controller='ctrl'>
<ol>
<li ng-repeat='i in list track by $index'>
<input type='text' ng-model='i' ng-change="change(i, $index)"></input>
<button ng-click='deleteItem($index)'>Delete</button>
</li>
</ol>
<button ng-click='addItem()'>Add</button>
<div>ITEM: {{list | json}}</div>
</div>
Javascript
angular.module("app", []).controller("ctrl", function ($scope) {
$scope.list = ["one","two"];
$scope.addItem = function ()
{
$scope.list.push("");
};
$scope.deleteItem = function (idx)
{
var item_to_delete = $scope.list[idx];
$scope.list.splice(idx, 1);
};
$scope.change = function (item, idx)
{
$scope.list[idx] = item;
};
});
See fixed Demo in DEMO
Yes, pushing more than one empty string will result in ng-repeat complaining.
In addition, you can also try:
if ($scope.aliasList.indexOf(VALUE_TO_ADD) === -1) {
...
}

Resources