I am looking for a best approach to convert a static form to an angular dynamic form. I am not sure how to bind multiple values to the same answer.
The static page is available at: https://jsfiddle.net/hvuq5h46/
<div ng-repeat="i in items">
<select ng-model="i.answer" ng-options="o.id as o.title for o in i.answersAvailable" ng-visible="y.TYPE = 'SINGLE'"></select>
<input type="checkbox" ng-model="i.answer" ng-visible="y.TYPE = 'MULTIPLE'" />
</div>
The JSON file
[
{
"id": 1,
"title": "Are you a student?",
"type": "SINGLE",
"answersAvailable": [
{
"id": 1,
"title": "Yes"
},
{
"id": 2,
"title": "No"
}
],
"answer": [
1
]
},
{
"id": 2,
"title": "Would you like to be an astronaut?",
"type": "SINGLE",
"answersAvailable": [
{
"id": 4,
"title": "Yes"
},
{
"id": 5,
"title": "No"
},
{
"id": 6,
"title": "I am not sure"
}
],
"answer": [
4
]
},
{
"id": 3,
"title": "What is your favourite planet?",
"type": "MULTIPLE",
"answersAvailable": [
{
"id": 7,
"title": "Earth"
},
{
"id": 8,
"title": "Mars"
},
{
"id": 9,
"title": "Jupiter"
}
],
"answer": [
7,
8
]
}
]
Things would be much simpler if you can use a multiple select, but I understand it might be difficult for user to interact (consider something like md-select, which transforms multiple select into a list of checkbox for you)
Multiple select:
<select multiple
ng-model="i.answer"
ng-options="o.id as o.title for o in i.answersAvailable"
ng-if="i.type == 'MULTIPLE'"></select>
Anyway it is completely ok to use HTML checkbox. To do that we would need to bind checkbox model into the data as usual, and then update the answer array simultaneously.
ng-model="o.selected"
ng-change="updateAnswer(i)"
Also, we'll need to copy existing data to model during init.
ng-init="initMultiple(i)"
Working code:
angular.module('test', []).controller('Test', Test);
function Test($scope) {
$scope.items = [{
"id": 1,
"title": "Are you a student?",
"type": "SINGLE",
"answersAvailable": [{
"id": 1,
"title": "Yes"
},
{
"id": 2,
"title": "No"
}
],
"answer": [
1
]
},
{
"id": 2,
"title": "Would you like to be an astronaut?",
"type": "SINGLE",
"answersAvailable": [{
"id": 4,
"title": "Yes"
},
{
"id": 5,
"title": "No"
},
{
"id": 6,
"title": "I am not sure"
}
],
"answer": [
4
]
},
{
"id": 3,
"title": "What is your favourite planet?",
"type": "MULTIPLE",
"answersAvailable": [{
"id": 7,
"title": "Earth"
},
{
"id": 8,
"title": "Mars"
},
{
"id": 9,
"title": "Jupiter"
}
],
"answer": [
7,
8
]
}
]
$scope.initMultiple = function(item) {
item.answersAvailable.forEach(function(option) {
option.selected = item.answer.indexOf(option.id) != -1;
});
}
$scope.updateAnswer = function(item) {
item.answer = item.answersAvailable.filter(function(option) {
return option.selected;
})
.map(function(option) {
return option.id;
});
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app='test' ng-controller='Test'>
<div ng-repeat="i in items">
<select ng-model="i.answer[0]"
ng-options="o.id as o.title for o in i.answersAvailable"
ng-if="i.type == 'SINGLE'"></select>
<label ng-repeat="o in i.answersAvailable"
ng-if="i.type == 'MULTIPLE'"
ng-init="initMultiple(i)">
<input type="checkbox"
ng-model="o.selected"
ng-change="updateAnswer(i)" /> {{o.title}}
</label>
<div>{{i.answer}}</div>
</div>
</div>
Based on my experience, I will make a separation as two Angular models (or usually called services) for the form questions and another one which will collect the answers and eventually will be passed to the backend for further processing. This will provide me a flexibility to maintain both logic and presentation.
var myModule = angular.module('myModule', []);
myModule.factory('QuestionsFormService', function() {
var _question1;
var _question2;
var _question3;
function init(data){
//questions initiation
}
return init;
});
var myModule = angular.module('myModule', []);
myModule.factory('FormDataService', function() {
var _dataAnswer = {}
function init(){
//data initialization
}
function insertData(key, value){
_dataAnswer[key] = value
}
return init;
});
From the example of service models above, you need to make these available to your presentation through the Angular controller with Dependency Injection.
myModule.controller("MyCtrl", function($scope, FormDataService, QuestionsFormService) {
$scope.form_questions = QuestionsFormService.init();
$scope.form_answers = FormDataService.init()
//further logic to make these available on your view on your convenience
});
What you write on the HTML page as an Angular view is already close enough. You only need to change the binding to two models as I propose above. Thank you.
Related
I am using Typescript in an Angular/Ionic project. I have an array of users that contain an array of skills. I have to filter users based on their online status as well as skills.
[
{
"id": 1,
"name": "Vikram Shah",
"online_status": "Online",
"skills": [{
"id": 2,
"title": "CSS"
},
{
"id": 3,
"title": "JavaScript"
},
{
"id": 4,
"title": "Python"
}
]
},
{
"id": 1,
"name": "Abhay Singh",
"online_status": "Online",
"skills": [{
"id": 1,
"title": "HTML"
},
{
"id": 2,
"title": "CSS"
},
{
"id": 3,
"title": "JavaScript"
},
{
"id": 4,
"title": "Python"
}
]
},
{
"id": 1,
"name": "Test Oberoi",
"online_status": "Online",
"skills": [{
"id": 1,
"title": "HTML"
},
{
"id": 2,
"title": "CSS"
},
{
"id": 3,
"title": "JavaScript"
},
{
"id": 4,
"title": "Python"
}
]
}
]
This is how all skills look like
this.skill_types = [
{"id":8,"title":"Cleaner", checked:false},
{"id":7,"title":"Painter", checked:false},
{"id":6,"title":"Plumber", checked:false},
{"id":5,"title":"Carpenter", checked:false},
{"id":4,"title":"Advisor", checked:false},
{"id":3,"title":"Team Leader", checked:false},
{"id":2,"title":"Management", checked:false},
{"id":1,"title":"Administrator", checked:false}
];
This array contains the IDs of skills that I want to filter
filterArr = [1, 3, 6];
This solution is almost working as expected. It is filtering well based on two criteria together.But not sure how to add condition for second filtering. The second filter should apply only if filterArr is not empty.
return this.items = this.items.filter((thisUser) => {
return thisUser.online_status.toLowerCase().indexOf(onlineStatus.toLowerCase()) > -1 &&
thisUser.skills.some(c => this.filterArr.includes(c.id))
});
The issue I am facing with code above is When there is no skill selected in the filter criteria, I would like to display all users. But it is not working that way. The logic here is to not apply any filter when the size of selected skills (filter condition) is greater than zero. So I tried this way....which looks similar to the way above...but this makes everything worse.
let filteredByStatus = [];
filteredByStatus = this.items.filter((thisUser) => {
return thisUser.online_status.toLowerCase().indexOf(onlineStatus.toLowerCase()) > -1
});
//Condition can be applied if filtering is separated
let filteredBySkills = [];
filteredBySkills = this.items.filter((thisUser) => {
return thisUser.skills.some(c => this.filterArr.includes(c.id))
});
//Expecting to join results from multiple filters
return this.items = filteredByStatus.concat(filteredBySkills);
But this is not working at all. Not sure what wrong is there. I am looking for a solution that enables to join arrays of similar objects without duplicating them.
Don't think you need to join arrays for your filtering. You can use something like rxjs filter.
return from(this.items)
.pipe(
filter(user => {
return user.online_status.toLowerCase().indexOf(onlineStatus.toLowerCase()) > -1
&& user.skills.some(c => filterArr.includes(c.id));
})
);
Or if you like to split it up you can just change it to like:
return from(this.items)
.pipe(
filter(user => user.online_status.toLowerCase().indexOf(onlineStatus.toLowerCase()) > -1),
filter(user => user.skills.some(c => filterArr.includes(c.id)))
);
Stackblitz: https://stackblitz.com/edit/angular-pk3w8b
You can tweak your condition a bit and place !this.filterArr.length in your condition (in terms of OR condition AND with user status) to make your whole condition gets true so that user gets filter.
I trying to create multiple menu category using JDL. In one CategoryItem have parentID.
I already created JDL like:
microservice * with Category
entity CategoryItem{
name String required
}
relationship ManyToOne{
CategoryItem{parrent} to CategoryItem
}
service * with serviceClass
paginate CategoryItem with pagination
If the client calls a method findAll. Can I return JSON format like that:
[
{
"id": 1,
"name": "car",
"CategoryItem": [
{
"id": 2,
"name": "red car"
},
{
"id": 3,
"name": "blue car"
}
]
},
{
"id": 4,
"name": "bike",
"CategoryItem": []
}
]
Here in {{msg}} I can display all jason data with Selected:true or false..
but What I need is I don't want to display all the datas, when I click Save button I want to display questions.id,option.id and selected:true or false below the textbox
we will get all the json data from $scope.questions.
Home.html
<div ng-repeat="question in filteredQuestions">
<div class="label label-warning">Question {{currentPage}} of {{totalItems}}.</div>
<div class="row">
<div class="row">
<h3>{{currentPage}}. <span ng-bind-html="question.Name"></span></h3>
</div>
</div>
<div class="row text-left options">
<div class="col-md-6" ng-repeat="option in question.Options" style="float:right;">
<div class="option">
<label class="" for="{{option.Id}}">
<h4> <input id="{{option.Id}}" type="checkbox" ng-model="option.Selected" ng-change="onSelect(question, option);" />
{{option.Name}}</h4>
</label>
</div>
</div>
</div>
<div class="center"><button ng-click="save()">Save</button></div>
<div class="center"><textarea rows="5" cols="50">{{msg}}</textarea></div>
</div>
controllers.js
var HomeController = function ($scope, $http, helper) {
/*$scope.names = response.data;
$scope.detectChange=function(){
$scope.msg = 'Data sent: '+ JSON.stringify($scope.filteredQuestions);
}*/
$scope.save = function() {
$scope.msg = 'Data sent: '+ JSON.stringify($scope.questions);
}
$scope.quizName = 'data/csharp.js';
$scope.loadQuiz = function (file) {
$http.get(file)
.then(function (res) {
$scope.quiz = res.data.quiz;
$scope.config = helper.extend({}, $scope.defaultConfig, res.data.config);
$scope.questions = $scope.config.shuffleQuestions ? helper.shuffle(res.data.questions) : res.data.questions;
$scope.totalItems = $scope.questions.length;
$scope.itemsPerPage = $scope.config.pageSize;
$scope.currentPage = 1;
$scope.mode = 'quiz';
$scope.$watch('currentPage + itemsPerPage', function () {
var begin = (($scope.currentPage - 1) * $scope.itemsPerPage),
end = begin + $scope.itemsPerPage;
$scope.filteredQuestions = $scope.questions.slice(begin, end);
});
});
}
$scope.loadQuiz($scope.quizName);
}
HomeController.$inject = ['$scope', '$http', 'helperService'];
csharp.js
{
"quiz": {
"Id": 2,
"name": "C# and .Net Framework",
"description": "C# and .Net Quiz (contains C#, .Net Framework, Linq, etc.)",
"paragraph": "In 2015 Microsoft released ASP.NET 5.ASP.NET 5 is a significant redesign of ASP.NET.ASP.NET, MVC, and Web Pages are now merged into a single framework named MVC 6.It includes the following features:Linux support OSX support Node.js supportA ngularJS supportTag ,HelpersView, ComponentsWeb ,APIGruntJS ,supportBower, supportNo ,Visual BasicNo Web Forms"
},
"config": {
"shuffleQuestions": true,
"showPager": false,
"allowBack": true,
"autoMove": false
},
"questions": [{
"Id": 1010,
"Name": "Which of the following assemblies can be stored in Global Assembly Cache?",
"QuestionTypeId": 1,
"Options": [{
"Id": 1055,
"QuestionId": 1010,
"Name": "Private Assemblies"
}, {
"Id": 1056,
"QuestionId": 1010,
"Name": "Friend Assemblies"
}, {
"Id": 1057,
"QuestionId": 1010,
"Name": "Public Assemblies"
}, {
"Id": 1058,
"QuestionId": 1010,
"Name": "Shared Assemblies"
}]
}, {
"Id": 1019,
"Name": "Which of the following does NOT represent Integer?",
"QuestionTypeId": 1,
"Options": [{
"Id": 1055,
"QuestionId": 1010,
"Name": "Char"
}, {
"Id": 1056,
"QuestionId": 1010,
"Name": "Byte"
}, {
"Id": 1057,
"QuestionId": 1010,
"Name": "Short"
}, {
"Id": 1058,
"QuestionId": 1010,
"Name": "Long"
}]
}]
}
This is my answer of above code..here Displaying all the data
Data sent: [{"Id":1013,"Name":"Which of the following is NOT an Arithmetic operator in C#.NET?","QuestionTypeId":1,"Options":[{"Id":1055,"QuestionId":1010,"Name":"** (Double Star)","$$hashKey":"00X","Selected":false},{"Id":1057,"QuestionId":1010,"Name":"+ (Plus)","$$hashKey":"00Y","Selected":false},
"$$hashKey":"00C"}]
but I want to display the all the question id's and corresponding option id's and if it is selected,selected:true otherwise false in the format of above output
If you want to change what is presented to the user after saving you should change this function:
$scope.save = function() {
$scope.msg = 'Data sent: '+ JSON.stringify($scope.questions);
}
to something like:
$scope.save = function() {
$scope.msg = 'Question IDs:';
$scope.questions.forEach(function(el){
$scope.msg += el.Id + ',';
}
}
This for example will join all the ids of the array that conains all the questions.
To add options and selections you have to ng-model the value to some variable that you will use inside your controller.
I think you should check a little guide to angularjs two-way data binding
I've this JSON:
{
"success": true,
"return": {
"totalItem": 7,
"totalPages": 1,
"pageSize": 7,
"items": {
"phones": [
"(48) 9999-9999"
],
"users": {
"manager": {
"id_user": "5819",
"name": "Síndico Ilhas Belas",
"user": "sindico.teste#domain.com",
"photo": "https://domain.amazonaws.com/files/upload/2015/07/25/5819.55b32d90e69ab3.05638898_873f970f8d259.jpg"
},
"employees": [
{
"id_user": "2",
"name": "José Perez",
"user": "pepe#domain.com",
"photo": "https://.amazonaws.com/files/upload/2013/10/07/2.52523a0451c3c5.59697102_7a4188d3.jpg",
"groups": [
{
"id_group": "33",
"name": "Portaria"
}
],
"work_details": {
"work_schedule": "8-12hs e 14-18hs",
"work_activities": "Supervisionar os trabalhos de conservação"
}
},
{
"id_user": "15142",
"name": "Marcos Rojas",
"user": "rojas#c.com",
"photo": "http://www.domain.com/themes/intra/img/sf_ele.gif",
"groups": [
{
"id_group": "589",
"name": "Zeladoria"
}
],
"work_details": {
"work_schedule": "8h 12h",
"work_activities": "Zeladoria"
}
},
{
"id_user": "18833",
"name": "Portaria",
"user": "teste#domain.com",
"photo": "http://www.domain.com/themes/intra/img/sf_ele.gif",
"groups": [
{
"id_group": "33",
"name": "Portaria"
}
],
"work_details": {
"work_schedule": "8hs por dia. 8-12hs e 14-18hs",
"work_activities": "Supervisionar os trabalhos de conservação"
}
}
],
"boardMembers": [
{
"id_user": "8189",
"name": "Ana Maria",
"user": "8189",
"photo": "http://www.domain.com/themes/intra/img/sf_ela.gif",
"groups": [
{
"id_group": "722",
"name": "Subsíndico"
}
]
},
{
"id_user": "11442",
"name": "Luciana Zath",
"user": "lzath#mail.com",
"photo": "http://www.domain.com/themes/intra/img/sf_ela.gif",
"groups": [
{
"id_group": "1456",
"name": "Conselho fiscal"
}
]
}
]
}
}
}
}
Because manager is only one record, i can't get it with ng-repeat in this code
<div class="card" ng-repeat="(manager, name) in items">
<pre>{{name}}</pre>
</div>
JSON returns all data, but i need return only manager data, for example:
Name, User (email) and photo
And controller:
// Controller of about.
appControllers.controller('aboutCtrl', function($scope, $mdBottomSheet, $mdToast, $mdDialog, About) {
About.get(function(data) {
$scope.items = data.return.items;
console.log(data);
})
}); // End of about controller.
employees and boardMembers works fine with ng-repeat, but single record for manager, not
appControllers.controller('aboutCtrl', function($scope, $mdBottomSheet, $mdToast,
$mdDialog, About) {
About.get(function(data) {
$scope.items = data.return.items;
$scope.manager = data.return.items.users.manager;
console.log(data);
})
}); // End of about controller.
in html:
<p>{{manager.name}}</p>
<p>{{manager.user}}</p>
If manager is always a single object just do:
Manager: {{items.users.manager.name}}
Then repeat over the employees array
<div ng-repeat="employee in items.users.employees">
Employee: {{employee.name}}
You can only iterate through a list of items using ng-repeat if items are an array. In your JSON, items are an object, so you will not get manager name. Just use {{items.users.manager.name}} to get manager name. On the other hand you could use ng-repeat to iterate over items.users.employees, because it's employees property is an array.
<div class="card" ng-repeat="employee in items.users.employees">
<pre>{{employee.name}}</pre>
</div>
You can access it with
{{items.users.manager}}
I have an application that I am constructing with a friend and we have a very big problem: I have a very big json with some nested arrays an objects, I am using a filter with an ng-model="search" the filter (search) works great with the top level array which is named sports, but once I try to search through the leagues array, the filter returns nothing. I saw in another question that I can search(filter) based on nested properties which is exactly what I want. I tried to follow the example answer on that question but I am having a problem trying to solve this. Someone else says: that is not possible with this kind of matching that you are trying because you want to filter through a matching sport.name and all of his matching and not non matching leagues or a non matching sport.name and only his matching leagues.
json
[
{
"name": "Cricket",
"leagues": []
},
{
"name": "NBA Games",
"leagues": [
{
"name": "NBA",
"sport": {
"id": 8,
"name": "NBA Earliest"
},
"lineType": "G",
"priority": [
1,
3
],
"part": "0"
}
]
},
{
"name": "COLLEGE Basketball",
"leagues": [
{
"name": "College - NCAA BASKETBALL",
"sport": {
"id": 24,
"name": "College Basketball"
},
"lineType": "G",
"priority": [
0,
4
],
"part": "0"
},
{
"name": "NCAA BASKETBALL ADDED GAMES",
"sport": {
"id": 24,
"name": "College Basketball"
},
"lineType": "G",
"priority": [
1,
4
],
"part": "0"
},
...
my html
<input type="search" ng-model="search">
<div ng-repeat="sport in sports | filter: query">
<!-- searching (filtering) great -->
<div>{{sport.name}}</div>
</div>
<div ng-repeat="league in sport.leagues | filter: query">
<!-- here is not filtering at all -->
{{league.name}}
</div>
</div>
can I do it this way I am trying or how do I implement a custom filter for this ?