currently I am working on app for managing workouts. I would like to sort workouts by date. My record of one workout looks like that:
"title": "Running through the park",
"type": "Running",
"distance": "10",
"duration": [
{
"hours": "2"
},
{
"minutes": "40"
},
{
"seconds": "44"
}
],
"note": "Running in NY park."
I would like to sort workouts by the day. For example:
09.10
running
basketball
07.10
push-ups
football
Currently it looks like that: workouts-main screen
My html file for that component is simple :
<ion-list>
<ion-item *ngFor="let workout of workouts" (click)="workoutSelected($event,workout)">
{{workout.title}}
</ion-item>
What's the best way to do that?
As I stumbled recently on similar issue and couldn't find fully satisfactory answer I would like to share my solution.
.ts
objectEntries: any[];
groupedObjectEntries: any[];
ionViewDidLoad() {
// put in the line below your custom function for getting data into objectEntries object
.....
this.groupObjectEntries(this.objectEntries);
}
groupObjectEntries(objectEntries){
var currentDate: any;
var currentObjectEntries = [];
var output = [];
// sort descending
let sortedObjectEntries =objectEntries.sort(function (a, b) {
var key1 = new Date(a.date);
var key2 = new Date(b.date);
if (key1 < key2) {
return 1;
} else if (key1 == key2) {
return 0;
} else {
return -1;
}
});
sortedObjectEntries.forEach((item, index) => {
// conversion from JSON format
var itemDate = new (item.date);
itemDate.setHours(0,0,0,0);
if(itemDate != currentDate){
currentDate = itemDate;
let newGroup = {
groupDate: currentDate,
objectEntries: []
};
currentObjectEntries = newGroup.objectEntries;
output.push(newGroup);
}
currentObjectEntries.push(item);
});
this.groupedObjectEntries = output;
}
.html
<ion-content>
<ion-item-group *ngFor="let group of groupedObjectEntries">
<ion-item-divider sticky>
<ion-item color="gray" ><h4>{{ group.groupDate | date:'fullDate' }} </h4> </ion-item>
</ion-item-divider>
<button ion-item *ngFor="let item of group.objectEntries" (click)="itemSelected(item)">
<h3>{{ item.field1 }} </h3>
<p>{{ item.field2 }}</p>
</button>
</ion-item-group>
</ion-content>
Related
I want to introduce a search option in my mat-select DropDown.I went through a lot of similar working options, but those are not working for me because of the object array I am passing to the Dropdown. any help is appreciated. thank you.
My Code
HTML File
<mat-form-field>
<mat-select (selectionChange)="getSubtier($event.value)">
<input (keyup)="onKey($event.target.value)">
<mat-option>None</mat-option>
<mat-option *ngFor="let state of selectedStates" [value]="state">{{state.name}}</mat-option>
</mat-select>
</mat-form-field>
TS File
states: string[] = [
{
toptier_agency_id: 15,
create_date: 1517428376464,
update_date: 1560547998012,
toptier_code: "013",
abbreviation: "DOC",
name: "Department of Commerce",
website: "https://www.commerce.gov/",
mapped_org_name: "COMMERCE, DEPARTMENT OF",
display_yn: "Y"
},
{
toptier_agency_id: 16,
create_date: 1517428376787,
update_date: 1560547999157,
toptier_code: "014",
abbreviation: "DOI",
name: "Department of the Interior",
website: "https://www.doi.gov/",
mapped_org_name: "INTERIOR, DEPARTMENT OF THE",
display_yn: "Y"
}];
selectedValue: string;
selectedStates = [];
ngOnInit() {
this.selectedStates = this.states;
}
onKey(value) {
this.selectedStates = this.search(value);
}
getSubtier(value) {
console.log(value);
}
search(value: string) {
// this.selectedStates=[]
// let filter = value.toLowerCase();
// this.selectedStates = this.selectedStates['name'].filter((unit) => unit.label.indexOf(value) > -1);
}
StackBliz Demo
Modify search functuon. Use filter and includes
Try like this:
onKey(value) {
this.selectedStates = this.search(value);
}
search(value: string) {
let filter = this.states.filter(item =>
item.name.toLowerCase().includes(value.toLowerCase())
);
return [...filter];
}
Working Demo
Try the following:
introduce filter with an isVisible property
search(value: string) {
return this.selectedStates.map(k => ({...k, isVisible:(k.name.toLowerCase().indexOf(value.toLowerCase()) > -1)}))
}
add a getter for the filtered state
get filteredState() {
return this.selectedStates.filter(k=> k.isVisible === undefined || k.isVisible)
}
then in your html replace selectedStates with filteredState
<mat-option *ngFor="let state of filteredState" [value]="state">{{state.name}}</mat-option>
TS Note: that you should replace states: string[] with states: any[] or with the object you are using
I'm making a website where you can make quizzes and answer them. My issue is that when I try to answer my quiz, to see whether the answer is correct or incorrect, the result I get is not exactly what I want.
I'm creating my html page by loading a json and assigning a radio button to every answer. The structure is that a quiz can have many questions, and questions can have many answers.
HTML:
<table class="pageTable" align="center">
<!-- Quiz title -->
<div *ngFor="let quiz of quizToDisplay"><br/>
Quiz title: {{quiz.title}} <br/> by {{quiz.owner}}<br/><br/>
<!-- Quiz questions -->
<div *ngFor="let quizQuestions of quiz.questions" align="center">
<div class="Question-panel-title" style="padding: 10px; word-wrap: break-word;">
Question: {{quizQuestions.questionText}}
</div>
<!-- Quiz answers -->
<div class="Question-panel-content" style="padding: 5px; word-wrap: break-word;">
<div *ngIf="quizQuestions.types == 'Multiple-choice'" >
<div *ngFor="let quizAnswers of quizQuestions.answers; let i=index">
<input type="radio" id="rawr" name="{{quizQuestions.questionText}}" value="{{quizAnswers.correctAnswer}}" [disabled]="submitted">
{{i + 1}}: {{quizAnswers.answerText}}
</div>
<div *ngIf="submitted == true">
Your answer is {{correctAnswerMultipleChoice}}
</div>
</div>
<div *ngIf="quizQuestions.types == 'Checkboxes'">
<div *ngFor="let quizAnswers of quizQuestions.answers; let i=index">
<input type="checkbox" name="checkboxGroup" value="{{quizAnswers.correctAnswer}}" [disabled]="submitted" (click)="handleCheckboxAnswer(i, quizAnswers.correctAnswer)">
{{i + 1}}: {{quizAnswers.answerText}}
</div>
<div *ngIf="submitted == true">
Your answer is {{correctAnswerCheckbox}}
</div>
</div>
</div><br/>
</div>
<input type="submit" value="Submit" (click)="submitAnswer()">
<input type="submit" value="View Statistics">
<div *ngIf="quiz.owner == currentUser">
<input type="submit" value="Delete Quiz" (click)="deleteQuiz(quiz.id)">
</div>
</div>
Code:
export class QuizComponent implements OnInit, OnDestroy {
service: any;
data: any;
quizToDisplay: any;
currentUser: string;
correctAnswerMultipleChoice: string = 'Incorrect';
CheckboxesValues: string[] = [];
correctAnswerCheckbox: string = 'Incorrect';
submitted: boolean = false;
answersArray: any[] = [];
constructor(private router:Router, private quizObserverService:QuizObserverService, private socketService:SocketService, private elementRef:ElementRef){
this.currentUser = localStorage.getItem('user');
}
ngOnInit() {
this.service = this.quizObserverService.getQuiz(this.router.url).subscribe(data => { //only gets JSON upon page load
this.quizToDisplay = data;
})
}
ngOnDestroy() {
this.service.unsubscribe();
}
deleteQuiz(id: string){
this.socketService.socket.emit('deleteQuiz', JSON.stringify(id));
this.router.navigateByUrl('/home');
}
handleMultiplechoiceAnswer(){
let rawr = this.elementRef.nativeElement.querySelectorAll('#rawr');
for(let i = 0; i < this.quizToDisplay[0].questions.length; i++){
if(this.quizToDisplay[0].questions[i].types == "Multiple-choice"){
for(let j = 0; j < this.quizToDisplay[0].questions[i].answers.length; j++){
this.answersArray.push(this.quizToDisplay[0].questions[i].answers[j]);
}
}
}
if(this.handleMultiplechoiceAnswerCorrect(rawr)){
return this.handleMultiplechoiceAnswerCorrect(rawr);
}
else
{
return this.handleMultiplechoiceAnswerIncorrect(rawr);
}
}
handleMultiplechoiceAnswerCorrect(rawr: any){
for(let k = 0; k < rawr.length; k++){
if(this.answersArray[k].correctAnswer == rawr[k].value && rawr[k].value == "Correct" && rawr[k].checked == true){
return "Correct";
}
}
}
handleMultiplechoiceAnswerIncorrect(rawr: any){
return "Incorrect";
}
handleCheckboxAnswer(index: number, correct: string) {
this.CheckboxesValues[index] = correct;
}
submitAnswer(){
this.submitted = true;
this.correctAnswerMultipleChoice = this.handleMultiplechoiceAnswer();
}
When I try to answer my quiz, this is what the result looks like:
Image
As you can see, even though the second question is answered wrong, it still says that it is correct, because the first question is answered correctly. The method I'm using to determine whether the quiz is correct or false is the handleMultiplechoiceAnswer() method, so I think something is wrong in that method, however I can't pick my finger on it. Thanks for the help.
EDIT:
Very sorry, I forgot to put an example of my json structure. Here it is:
{
"id": "32bec4d6-b5fd-4360-bede-9c902abd95de",
"title": "random quiz",
"owner": "mohemohe",
"questions": [
{
"questionText": "Choose numbers above 10",
"answers": [
{
"answerText": "9",
"correctAnswer": "Incorrect"
},
{
"answerText": "11",
"correctAnswer": "Correct"
}
],
"types": "Multiple-choice"
},
{
"questionText": "Which website is this?",
"answers": [
{
"answerText": "stackoverflow",
"correctAnswer": "Correct"
},
{
"answerText": "google",
"correctAnswer": "Incorrect"
}
],
"types": "Multiple-choice"
}
],
"access": "Public"
}
EDIT 2:
Managed to make a plunker example: https://plnkr.co/edit/CoV1AQtVtbbS2kNYK7FR
The solve:
I think that the reason why this is always returning true / 'Correct' is because of the if statement evaluating it:
if(this.answersArray[k].correctAnswer == rawr[k].value && rawr[k].value == "Correct" && rawr[k].checked == true){...}
If we break this down...
this.answersArray[k].correctAnswer == rawr[k].value
rawr[k].value == "Correct"
rawr[k].checked == true
Instantly you can see that you are expecting rawr[k].value to equal both correctAnswer AND 'Correct'. Because the result is always returning true, this means that this.answersArray[k].correctAnswer == 'Correct'.
So this essentially negates the first 2 conditions in your if statement - so essentially your if statement becomes... if(rawr[k].checked == true).
Just FYI there is absolutely no need for even checking rawr[k].checked == true if you swap querySelectorAll('#rawr'); to querySelectorAll('#rawr:checked');
The following is just general feedback:
I think that you really need to consider re-working this entire script. It is very hard to read, highly un-optomised and needs to be refactored into a more robust approach.
First I would recommend creating a variable to store the currentQuestion so that you don't have to do quizToDisplay[0] every single time.
var currentQuestion: any;
ngOnInit(){
....
this.quizToDisplay = data;
this.currentQuestion = this.quizToDisplay[0];
}
Or at the very least... this.quizToDisplay = data[0]
Go and check out es6 array methods.
for(let i = 0; i < this.quizToDisplay[0].questions.length; i++){
if(this.quizToDisplay[0].questions[i].types == "Multiple-choice"){
for(let j = 0; j < this.quizToDisplay[0].questions[i].answers.length; j++){
this.answersArray.push(this.quizToDisplay[0].questions[i].answers[j]);
}
}
}
Can be transmutted into the following which is much easier to read:
this.quizToDisplay[0].questions.forEach((questionGroup, i)=>{
if(questionGroup.types == "Multiple-choice"){
questionGroup.forEach((question, ii)=>{
this.answersArray.push(question.answers[ii]);
});
}
});
Why not just include the feedback string in the json for each question? That way you can do something like the following and mitigate any future requirements that the feedback string is unique for a particular question:
<div *ngIf="submitted == true">
<p *ngIf="correct" class="correct-colour">{{quizAnswers.correctFeedback}}</p>
<p *ngIf="!correct" class="incorrect-colour">{{quizAnswers.incorrectFeedback}}</p>
</div>
I think that the naming conventions need to be improved. for example: quizQuestions.answers should be quizQuestions.options.
Don't forget that as you are using an object, you have the ability to assign new object properties "on the fly". For example you could do:
this.quizToDisplay[0]['answeredCorrectly'] = true;
I would strongly recommend creating a click event on your <input> so that you can trap the selected options more effectively. Using the above methodology you could...
// .html
<input ... (click)="optionClicked(option)">
// .ts
optionClicked(_option: Object){
quizQuestions['clicked'] = !quizQuestions['clicked'] || true;
}
If you ever want future code review, try codereview.stackexchange.com
{
"id": "32bec4d6-b5fd-4360-bede-9c902abd95de",
"title": "random quiz",
"owner": "mohemohe",
"questions": [
{
"questionText": "Choose numbers above 10",
"answers": [
{
"answerText": "9",
"correctAnswer": "Incorrect"
},
{
"answerText": "11",
"correctAnswer": "Correct"
}
],
"types": "Multiple-choice"
},
{
"questionText": "Which website is this?",
"answers": [
{
"answerText": "stackoverflow",
"correctAnswer": "Correct"
},
{
"answerText": "google",
"correctAnswer": "Incorrect"
}
],
"types": "Multiple-choice"
}
],
"access": "Public"
}
I have a list of movies and need to group them in both c# (or angular is also acceptable) and css very similary to the image provided here underneath. Any ideas on how to wire the html and c# and how to use the .groupBy() or something similar please ?
This is what I've got so far:
HTML (a list of all my movies in alphabetical order):
<div class="movs">
<movies-collection movies="::vm.sortedMovies" order-by="name"></movies-collection>
</div>
Typescript:
static id = "MoviesController";
static $inject = _.union(MainBaseController.$baseInject, [
"sortedMovies"
]);
static init = _.merge({
sortedMovies: ["allMovies", (movies: Array<Models.IGov>) => {
return _.sortBy(movies, "content.name");
}]
All my movies are already sorted alphabteically I just need to with the help of css structure them similarly to this image
I would create a filter that adds a "$first" property to the movie. If it is the first in a sorted list that starts with the character, then $first would be true. Bind to $first in your view when you show the character in uppercase.
The following demonstrates this idea:
var app = angular.module('app',[]);
app.controller('ctrl', function($scope) {
$scope.movies = [
{ title: 'The Godfather' },
{ title: 'Fargo' },
{ title: 'Sniper' },
{ title: 'Terminator'},
{ title: 'Click'},
{ title: 'Cake' },
{ title: 'Frozen' },
{ title: 'Casino Jack' },
{ title: 'Superman' },
{ title: 'The Matrix' }
];
});
app.filter('applyFirst', function() {
return function (movies) {
for(var i = 0; i < movies.length; ++i) {
if (i == 0)
movies[i].$first = true;
else {
if (movies[i].title.toLowerCase()[0] != movies[i-1].title.toLowerCase()[0]) {
movies[i].$first = true;
}
else {
movies[i].$first = false;
}
}
}
return movies;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-beta.1/angular.js"></script>
<div ng-app = "app" ng-controller="ctrl">
<div ng-repeat="movie in movies | orderBy:'title' | applyFirst">
<h1 ng-if="movie.$first">{{ movie.title[0] | uppercase }}</h1>
{{ movie.title }}
</div>
</div>
It's not possible in css, your code must split the array of movies into an array of letters, each with an array of movies.
You can use reduce for that:
var groupedMovies = movies.reduce((lettersArray, movie, idx, arr) => {
var firstLetter = movie[0].toUpperCase();
if (!lettersArray[firstLetter]) {
lettersArray[firstLetter] = [movie];
}
else {
lettersArray[firstLetter].push(movie);
}
return lettersArray;
}, []);
The result will look something like this:
[ T: [ 'The Avengers', 'Tower Quest', 'ThunderFist', 'Transformers' ],
U: [ 'Untamed Bengal Tiger', 'Untamed Giant Panda' ],
V: [ 'Victorious' ] ]
This way you can do a loop on the letters array, and in each do another loop for each movie.
The best practice for that would be to create a directive for a grouped movies, it will receive the letter and the inner array of movies in that letter.
I have a select that looks like this
<select
class="form-control"
ng-model="vm.transaction.location_from"
ng-options="l.name for l in vm.locations">
</select>
with vm.locations sourcing from the following JSON:
[
{
"id": "c0d916d7-caea-42f9-a87f-a3a1f318f35e",
"name": "Location 1"
},
{
"id": "d8a299a3-7f4b-4d32-884f-efe25af3b4d2",
"name": "Location 2"
}
]
Further, I have another select that looks like:
<select
class="form-control"
ng-model="vm.transaction.item"
ng-options="i.name for i in vm.items">
</select>
with vm.items sourcing from the following JSON:
[
{
"id": "9f582e58-45dd-4341-97a6-82fe637d769e",
"name": "20oz Soft Drink Cup",
"locations": [
{
"inventory_id": "9d5aa667-4a64-4317-a890-9b9291799b11",
"location_id": "c0d916d7-caea-42f9-a87f-a3a1f318f35e"
},
{
"inventory_id": "9d5aa667-4a64-4317-a890-9b9291799b11",
"location_id": "d8a299a3-7f4b-4d32-884f-efe25af3b4d2"
}
],
}
]
I want to, on change of the ng-mode="vm.transaction.item" select, have the ng-model="vm.transaction.location_from" be filtered to only show values that match from the locations array. I know I can use a | filter: { }, but I'm not sure what that filter should look like.
Hope this is your expected results.
Below are two options I tried ... demo | http://embed.plnkr.co/689OQztgu8F800YjBB2L/
Ref : underscorejs | angular-filter | everything-about-custom-filters-in-angular-js
// 1. filter items collection by location
angular.module('demo').filter('withLocation', function () {
return function (items, selectedLocation) {
function isLocationInLocations (elem) { return selectedLocation && elem.location_id === selectedLocation.id; }
function itemHasLocation (elm){ return (elm.locations && elm.locations.filter(isLocationInLocations).length > 0); }
return items.filter(itemHasLocation);
}});
// 2. filter function to check if option can be rendered ....
vm._filters.selectableItems = function(selectedLocation) {
return function(item) {
var locationsHasLocation = function(elem) { return selectedLocation && elem.location_id === selectedLocation.id; }
return (item.locations && item.locations.filter(locationsHasLocation).length > 0);
}
}
var app = angular.module("Test", []);
app.controller("Ctrl1", function($scope) {
$scope.location_fromArr =
[{
"id": "9f582e58-45dd-4341-97a6-82fe637d769e",
"name": "20oz Soft Drink Cup",
"locations": [{
"inventory_id": "9d5aa667-4a64-4317-a890-9b9291799b11",
"location_id": "c0d916d7-caea-42f9-a87f-a3a1f318f35e"
},{
"inventory_id": "9d5aa667-4a64-4317-a890-9b9291799b11",
"location_id": "d8a299a3-7f4b-4d32-884f-efe25af3b4d2"
}],
}];
$scope.itemArr =
[{
"id": "c0d916d7-caea-42f9-a87f-a3a1f318f35e",
"name": "Location 1"
},{
"id": "d8a299a3-7f4b-4d32-884f-efe25af3b4d2",
"name": "Location 2"
}];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="Test" ng-controller="Ctrl1">
Item
<select
class="form-control"
ng-model="item"
ng-options="i.name for i in itemArr">
</select>
Location
<select
class="form-control"
ng-model="location_from"
ng-options="l.name for l in location_fromArr | filter:{l.id: location_from.location_id}">
</select>
</div>
One way to do this is to supply a filter function to filter the locations. Something like:
vm.filterFun = function(selectedLocations) {
return function (location) {
var n;
if (!selectedLocations) {
return true;
}
for(n=0;n<selectedLocations.length;n += 1) {
if (selectedLocations[n].location_id === location.id) {
return true;
}
}
return false;
}
}
This is actually a function returning a filter function, based on the item selected.
Then in your select you apply the filter with:
<select
class="form-control"
ng-model="vm.transaction.location_from"
ng-options="l as l.name for l in vm.locations | filter:vm.filterFun(vm.transaction.item.locations)">
</select>
See plunker here.
I would forego angular filters and use the getterSetter option of ngModelOptions.
It could look something like this:
var selectedItem, selectedLocation;
var items = [];
var locations = [];
vm._items = items; // Static, always allow all items to be selected.
vm.locations = function () {
// Return differing results based on selectedItem.locations.
};
vm._transaction = {
location: function (v) {
/**
* If v is null, it is not present in the selectedItem.locations array.
* The extra check will ensure that we don't persist a filtered out location when
* selecting another item.
*/
return (v || v === null) ? (selectedLocation = v) : selectedLocation;
},
item: function (v) {
return v ? (selectedItem = v) : selectedItem;
}
};
Here's a plunker demonstrating the behaviour.
Not as simple/straight-forward as a filter, but I would bet (at least in the case of a piped filter) that you'd possibly see a slight performance gain going with this approach.
I do not have numbers to back up the above statement, and it usually boils down to the size of your dataset anyway. Grain of salt.
If you need it to function the other way around, you could write up a secondary filter like such:
function superFilter2 (arr) {
// If no location is selected, we can safely return the entire set.
if (!selectedLocation) {
return arr;
}
// Grab the current location ID.
var id = selectedLocation.id;
// Return the items that are present in the selected location.
return arr.filter(function (item) {
return item.locations.map(function (l) {
return l.location_id;
}).indexOf(id);
});
}
With that and the filter in the supplied plunker, there are some similarities that could be moved into higher order functions. Eventually with some functional sauce you could probably end up with a single god function that would work both ways.
you can do this:
<select
class="form-control"
ng-model="vm.transaction.item"
ng-change="itemCahngedFn()"
ng-options="i.name for i in vm.items">
</select>
var itemChangedFn = function(){
var filtredItems = [];
angular.forEach(vm.locations, function(item){
if(item.name == vm.transaction.item){
filtredItems .push(item.location);
}
});
vm.locations= filtredItems ;
}
i think filter:{ id : item.locations[0].location_id } should do the trick.
here is the jsfiddle
how do you think?
so I have a json object and also a edit button. I am wondering how to disable that edit button if the value is not equal to Peter so that the user cannot edit it.
angular.module('app')
.factory('WebApi', function () {
//Dummy Data
var name = [{
value: "Peter",
text: "Peter"
}, {
value: "John",
text: "John"
}, {
value: "Lucy",
text: "Lucy",
}, {
value: "Hawk",
text: "Hawk"
}];
var tempData = [];
//Display 100 item
for (var i = 0; i < 100; i++) {
var selectedName = name[Math.floor((Math.random() * name.length))];
tempData.push({
name: selectedName.text
})
};
constants.js
angular.module('app')
.factory('Constants', function () {
return {
status: {
Peter: 'Peter'
}
};
});
button
<ion-list can-swipe="listCanSwipe">
<ion-item ng-repeat="data in tempData"
item="data">
Name: {{data.name}}
<ion-option-button class="button-calm"
ng-click="edit(data)">
Edit
</ion-option-button>
</ion-item>
</ion-list>
Use ng-disabled:
Examlpe:-
<ion-option-button ng-disabled="name.value!='peter'" class="button-calm"ng-click="edit(data)">
Edit
</ion-option-button>
Use ng-disabled like following
<ion-option-button ng-disabled="data.name!='peter'" class="button-calm"ng-click="edit(data)">
Edit
</ion-option-button>
For reference - https://docs.angularjs.org/api/ng/directive/ngDisabled
In Html
ng-disabled="someFunction()"
In your JS
$scope.someFunction = function() {
// return true or false.
};