I have an array of tasks. They have titles and and labels.
function Task(taskTitle, taskType) {
this.title = taskTitle;
this.type = taskType;
}
$scope.tasks = [];
I end up declaring a bunch of tasks with different types and adding them to the array
In my html, I show a column of cards, filtered by type of task:
<div ng-model="tasks">
<div class="card" ng-repeat="abc in tasks track by $index" ng-show="abc.type==0">
<p> {{ abc.title }} </p>
</div>
</div>
I want to bind the first card displayed in this filtered view to some other div. I'll be processing an inbox, so I'll whittle this list of cards down to zero. Each time I 'process' a card and remove it from the list, I need the data to refresh.
<div ng-model="firstCardInFilteredArray">
<h4>Title of first card:</h4>
<p> This should be the title of the first card! </p>
</div>
My intuition was to do something like this (in javascript):
// pseudo-code!
$scope.inboxTasks = [];
for (i=0; i<tasks.length(); i++) {
if (tasks[i].type == 0) {
inboxTasks.append(tasks[i]);
}
}
and somehow run that function again any time the page changes. But that seems ridiculous, and not within the spirit of Angular.
Is there a simple way in pure javascript or with Angular that I can accomplish this conditional binding?
You can filter your ng-repeat: https://docs.angularjs.org/api/ng/filter/filter
<div ng-model="tasks">
<div class="card" ng-repeat="abc in filteredData = (tasks | filter: {type==0}) track by $index">
<p> {{ abc.title }} </p>
</div>
</div>
Additionally, by saving the filtered data in a separate list you can display the next task like this:
<div>
<h4>Title of first card:</h4>
<p> filteredData[0].title </p>
</div>
Your data will automatically update as you "process" tasks.
The other answers helped point me in the right direction, but here's how I got it to work:
HTML
<input ng-model="inboxEditTitle" />
JS
$scope.filteredArray = [];
$scope.$watch('tasks',function(){
$scope.filteredArray = filterFilter($scope.tasks, {type:0});
$scope.inboxEditTitle = $scope.filteredArray[0].title;
},true); // the 'true' keyword is the kicker
Setting the third argument of $watch to true means that any changes to any data in my tasks array triggers the watch function. This is what's known as an equality watch, which is apparently more computationally intensive, but is what I need.
This SO question and answer has helpful commentary on a similar problem, and a great fiddle as well.
More on different $watch functionality in Angular
To update inboxTasks, you could use $watchCollection:
$scope.inboxTasks = [];
$scope.$watchCollection('tasks', function(newTasks, oldTasks)
{
for (i=0; i<newTasks.length(); i++)
{
if(newTasks[i].type == 0)
{
$scope.inboxTasks.append(tasks[i]);
}
}
});
Related
I successfully added data to array using push method normally but failed to do so inside a pop up which opens up for a particular Id .
Here is my code:
<div class="form-group">
<label class="col-sm-3" for="pwd">Speciality:</label>
<div class="col-sm-4">
<input type="text" class="form-control" ng-model="spec" id="usr">
<button type="submit" ng-click="addSpeciality()">Add </button>
</div>
<div class="col-sm-5">
<ul>
<li ng-repeat="spec in speciality">
{{ spec }}
<button ng-click="removeSpeciality($index)">Remove</button>
</li>
</ul>
</div>
</div>
Controller code:
$scope.speciality=[];
$scope.addSpeciality = function(){
$scope.speciality.push($scope.spec);
$scope.spec = '';
};
$scope.removeSpeciality = function(index) {
$scope.speciality.splice(index, 1);
};
This is point when you need a Factory. Forget about storing any data in controllers. Really - FORGET! The only proper way to share data across application is to define Factory you like and inject it separately in every place you gonna work with that data.
You should not store data in $scope. $scope itself is an instance bind to DOM element. So only way you access any data from any level of deepness is using $parent what is a great mistake.
Your controller should $inject that Factory and call related methods when you need to change anything in data. Never pass business logic in controllers. They are like Strategy design pattern - place where you only tell what business logic should take place when you got trigger (click for example):
SpecialityFactory.addSpeciality = function () {
SpecialityFactory.specialities.push({});
}
SpecialityFactory.removeSpeciality = function (idx) {
SpecialityFactory.specialities.splice(idx, 1);
}
So it will be much easier to share business logic across your controllers:
// PageController
PageController.prototype.addSpeciality = function () {
SpecialityFactory.addSpeciality();
};
PageController.$inject = ['SpecialityFactory'];
// ModalController
ModalController.prototype.removeSpeciality = function (idx) {
SpecialityFactory.removeSpeciality(idx);
};
ModalController.$inject = ['SpecialityFactory'];
Well, first of all #Apperion is right, read his answer
but to get your code working try to add the controller to your wrapper HTML element
<div class="form-group" ng-controller="ExampleController">
and
with this array your ng-repeat won't work this way, you need to use this way:
<li ng-repeat="spec in speciality track by $index">
I am using knockout and Ryan Niemeyers ko.sortable.
My aim is to move items from one container to another. This works nice.
However, due to other circumstances I want the observableArrays object to be drag´n´dropped to be ko.computed.
I have a basic array containing all my elements
self.Textbatches = ko.observableArray();
then I have a filter version
self.TextsTypeOne = ko.computed(function(){
return ko.utils.arrayFilter(self.Textbatches(),function(t){
return t.type() === '1';
});
});
I have another filtered list with the texts dragging:
self.allocationableTextbatches = ko.computed(function(){
return ko.utils.arrayFilter(self.Textbatches(),function(t){
return t.type() === null;
});
});
Then I create my two containers with ´self.TextsTypeOne´ and ´self.allocationableTextbatches´:
<div id="allocationable-texts" class="menupanel">
<h4 class="select">Text(s) to allocate:</h4>
<div class="tb-table">
<div class="tb-list" data-bind="sortable:{template:'textbatchTmpl',data:$root.allocationableTextbatches,allowDrop:true}"></div>
</div>
</div>
<div id="TypeOne-texts" class="menupanel">
<h4 class="select">Text(s) on scene:</h4>
<div class="tb-table">
<div class="tb-list" data-bind="sortable:{template:'textbatchTmpl',data:$root.TextsTypeOne,allowDrop:true}"></div>
</div>
</div>
where
<script id="textbatchTmpl" type="text/html"><div class="textbatch" data-bind="click: $root.selectedText">
<span class="tb-title" data-bind="text: title"></span>
I can easily add buttons to my template setting type to 'null' and '1' respectively, and make it work that way. However I want this to be a drag and drop feature (as well) and the ko.sortable works on the jquery-ui objects. Thus it needs ´ordinary´ ´ko.observableArrays´ and not ´ko.computed´s as it is 'splice´-ing the observableArray, and dragging and dropping results in an error "TypeError: k.splice is not a function".
I have tried to make ´self.allocationableTextbatches´ and ´self.TextsTypeOne´ writable computed, but to no avail.
Any help and suggestions is highly appreciable!
Thanks in advance!
I would like to get the result of value conventer that filters an array in my view in order to display the number of results found.
<div repeat.for="d of documents|docfilter:query:categories">
<doc-template d.bind="d"></doc-template>
</div>
I neither want to move this logic to my controller (to keep it clean), nor to add crutches like returning some data from the value controller.
What I want:
So, basically I would like something like angular offers:
Like shown here:
ng-repeat="item in filteredItems = (items | filter:keyword)"
or here: ng-repeat="item in items | filter:keyword as filteredItems"
What I get:
Unfortunately, in Aurelia:
d of filteredDocuments = documents|docfilter:query:categories
actually means d of filteredDocuments = documents |docfilter:query:categories, and if I add brackets or as, it won't run (fails with a parser error).
So,
Is there a clean way of getting data out of data-filter in view?
Best regards, Alexander
UPD 1: when I spoke about returning some data from the value controller I meant this:
export class DocfilterValueConverter {
toView(docs, query, categories, objectToPassCount) {
...
objectToPassCount.count = result.length;
...
});
});
UPD 2. Actually, I was wrong about this: d of filteredDocuments = documents |docfilter:query:categories. It does not solve the issue but what this code does is :
1) filteredDocuments = documents |docfilter:query:categories on init
2) d of filteredDocuments which is a repeat over the filtered at the very beginning array
Assuming you have an outer-element, you can stuff the filtered items into an ad-hoc property like this:
<!-- assign the filtered items to the div's "items" property: -->
<div ref="myDiv" items.bind="documents | docfilter : query : categories">
<!-- use the filtered items: -->
<div repeat.for="d of myDiv.items">
<doc-template d.bind="d"></doc-template>
</div>
</div>
I know this isn't exactly what you're looking for but it will do the job. I'm looking into whether it would be helpfull to add a let binding command- something like this: <div let.foo="some binding expression">
Edit
Here's something a bit nicer:
https://gist.run/?id=1847b233d0bfa14e0c6c4df1d7952597
<template>
<ul with.bind="myArray | filter">
<li repeat.for="item of $this">${item}</li>
</ul>
</template>
I have a list of checkboxes for people, and I need to trigger an event that will display information about each person selected in another area of the view. I am getting the event to run in my controller and updating the array of staff information. However, the view is not updated with this information. I think this is probably some kind of scope issue, but cannot find anything that works. I have tried adding a $watch, my code seems to think that is already running. I have also tried adding a directive, but nothing in there seems to make this work any better. I am very, very new to Angular and do not know where to look for help on this.
My view includes the following:
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<fieldset>
<p data-ng-repeat="person in staffCtrl.persons">
<input type="checkbox" name="selectedPersons" value="{{ physician.StaffNumber }}" data-ng-model="person.isSelected"
data-ng-checked="isSelected(person.StaffNumber)" data-ng-change="staffCtrl.toggleSelection(person.StaffNumber)" />
{{ person.LastName }}, {{ person.FirstName }}<br />
</p>
</fieldset>
</div>
<div data-ng-controller="staffController as staffCtrl">
# of items: <span data-ng-bind="staffCtrl.infoList.length"></span>
<ul>
<li data-ng-repeat="info in staffCtrl.infoList">
<span data-ng-bind="info.staffInfoItem1"></span>
</li>
</ul>
</div>
My controller includes the following:
function getStaffInfo(staffId, date) {
staffService.getStaffInfoById(staffId)
.then(success)
.catch(failed);
function success(data) {
if (!self.infoList.length > 0) {
self.infoList = [];
}
var staffItems = { staffId: staffNumber, info: data };
self.infoList.push(staffItems);
}
function failed(err) {
self.errorMessage = err;
}
}
self.toggleSelection = function toggleSelection(staffId) {
var idx = self.selectedStaff.indexOf(staffId);
// is currently selected
if (idx >= 0) {
self.selectedStaff.splice(idx, 1);
removeInfoForStaff(staffId);
} else {
self.selectedStaff.push(staffId);
getStaffInfo(staffId);
}
};
Thanks in advance!!
In the code you posted, there are two main problems. One in the template, and one in the controller logic.
Your template is the following :
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<!-- ngRepeat where you select the persons -->
</div>
<div data-ng-controller="staffController as staffCtrl">
<!-- ngRepeat where you show persons info -->
</div>
Here, you declared twice the controller, therefore, you have two instances of it. When you select the persons, you are storing the info in the data structures of the first instance. But the part of the view that displays the infos is working with other instances of the data structures, that are undefined or empty. The controller should be declared on a parent element of the two divs.
The second mistake is the following :
if (!self.infoList.length > 0) {
self.infoList = [];
}
You probably meant :
if (!self.infoList) {
self.infoList = [];
}
which could be rewrited as :
self.infoList = self.infoList || [];
I have an array of users, I want to have my ng-repeat ordered by last name when first loaded. After a new user is added have the ng-repeat ordered by dated added then last name. Essentially I want the newest users pushed to the top of the ng-repeat.
<th ng-click="menuFilter('lastName', 1);">
<div ng-class='{"menuSort":sortColumn==1}'>Name <span ng-show="share.orderByField == 'lastName'">
</div>
</th>
<tr ng-repeat="user in users | orderBy:orderByField:reverseSort"></tr>
In my JS...
_this.sortColumn = 1;
_this.orderByField = 'lastName';
_this.reverseSort = false;
_this.menuFilter = function(section, column) {
_this.orderByField = section;
_this.reverseSort = !_this.reverseSort;
_this.sortColumn = column;
};
//my attempt to reset the order by created at date
if( _this.isRefreshing ) {
_this.orderByField = ['createdAt', 'lastName'];
}
Basically this code is not doing anything. I think I am missing a step in the HTML.
Thanks!
I think this is easiest done by sorting the array in pure javascript and then using a ng-repeat without the orderBy attribute.
HTML:
<div ng-repeat="user in users">{{user}}</div>
<input type="text" ng-model="name"></input>
<button ng-click="addName()">Add name</button>
JS:
$scope.users = ["Rusty", "Shackleford", "Dale", "Gribble"];
$scope.users.sort();
$scope.addName = function() {
$scope.users.unshift($scope.name);
}
JSFiddle: http://jsfiddle.net/asWF9/2/
This answer may help to sort your array: https://stackoverflow.com/a/6712080/3675149
Try using "unshift" instead of 'push' to add an item into the array. The unshift in js enables us to insert an item to the top of an array.