creating a dynamic form that builds a multi dimensional array - javascript

I am a php developer that is new to angular and javascript in general but finding it really powerful and fast for creating interactive UIs
I want to create a form for a user to create an object called a program, a program has some basic info like title and description and it can have many weeks. I want their to be an 'add week' button that when pressed displays a group of form fields related to weeks, if pushed again it shows another group of form fields to fill in the second weeks information.
edit1: specifically, how I am adding the objects to scope.program with the addWeeks method.
secondly when I console.log the $scope.program it just looks very messy a lot of arrays within objects within objects. it just dosnt look like a clean array of data but maybe thats just because I am not used to javascript and or json? Each week is going to have up to 7 days obviously and each day can have numerous events so it just seems to me like it is going to be quite messy but maybe I should just have faith :p
finally how the addProgram method is creating the json object to be sent to the server
when the form is submitted it should post a json object that looks something like this
program {
title: 'name of programme',
desc: 'description of programme',
weeks: [
{
item1: 'foo',
item2: 'more foo'
},
{
item1: 'foo2',
item2: 'more foo 2'
}
]
]
}
here is a codepen of what I am doing right now but I am not sure it is the best or even an ok way to do it, particularly how I am appending the arrays/objects in teh addWeek method.
there are going to be many more layers to the form and the object it is posting(days, sessions, excersises etc) so I want to get the basics of doing this right before adding all of that.
html
<div ng-app="trainercompare">
<div ng-controller="programsController">
<input type="text" placeholder="Program Title" ng-model="program.title"></br>
<input type="text" placeholder="Program Focus" ng-model="program.focus"></br>
<input type="text" placeholder="Program Description" ng-model="program.desc"></br>
<button ng-click="addWeek()"> add week</button>
<div ng-repeat="week in program.weeks">
<input type="text" placeholder="Name the week" ng-model="week.name">
<input type="text" placeholder="Describe It" ng-model="week.desc">
{{ week.name }}</br>
{{ week.desc }}</br>
</div>
<button ng-click="addProgram()"> add program</button>
</div>
</div>
app.js
var myModule = angular.module("trainercompare", ['ui.bootstrap']);
function programsController($scope, $http) {
$scope.program = {
weeks: [{
}]
};
$scope.addWeek = function() {
$scope.program.weeks.push(
{
}
);
};
function isDefined(x) {
var undefined;
return x !== undefined;
}
$scope.addProgram = function() {
var program = {
title: $scope.program.title,
focus: $scope.program.focus,
desc: $scope.program.desc,
weeks: []
};
angular.forEach($scope.program.weeks, function(week, index){
var weekinfo = {
name: week.name,
desc: week.desc
};
program.weeks.push(weekinfo);
});
$http.post('/programs', program).success(function(data, status) {
if(isDefined(data.errors)) {
console.log(data.errors);
}
if(isDefined(data.success)) {
console.log(data.success);
}
});
};
}

Looks to me like you've got a good grasp on it. The addWeek code looks correct. The extra data you see when you console.log your model is some of Angular's internal stuff to track bindings. When you post that to your server it should be cleaned up by Angular.
Angular has a JSON function that removes all of the hash values and other 'angular' things from your JSON. That's why they start with a $ so it knows to remove them.
This happens automatically when you use $http, it's in the documentation here:
If the data property of the request configuration object contains an object, serialize it into JSON format.
Since Angular will clean up the hashes and things, you don't need to "rebuild" the model when you're posting it... just set data to $scope.program and remove 70% of the code in $scope.addProgram.
To learn more specifically how Angular cleans up the JSON, look at this answer: Quick Way to "Un-Angularize" a JS Object

Related

Can I make an attribute appear only once in a vue v-for

I have an array of people with associated teams. I want to display all the people in the record, but I only want to display their team name once.
Meaning, if the v-for loop has encountered this particular team name, it should put it in a new temporary array to signify that it should be unique, then when it encounters that team name again, checks it through that temporary array and prevent it from showing again.
Sample HTML Code:
<div id="a-list">
<div v-for="person in people">{{person.Name}}, {{person.Team}}</div>
</div>
Sample Vue Code:
var crew = new Vue({
el: "#a-list",
data: {
people:
[ { "Name": "Richard","Team":"DMS"}, { "Name": "Mark","Team":"VV"}, { "Name": "Steve","Team":"VV"}, {"Name":"Koji","Team":"MZ"}, {"Name":"Jamie","Team":"VV"} ]
}
});
Expected Output:
Richard, DMS
Mark, VV
Steve,
Koji, MZ
Jaimie,
Is this possible to do directly from the v-for loop and not in the JS file?
Edited to show more data that are not sequential
Update: As Fabio has pointed out, the above scenario wouldn't make much sense unless the order of the team is arranged sequentially in the output first. So his answer is correct.
This could be a solution:
<div id="a-list">
<div v-for="(person,index) in people"> {{person.Name}}, {{ ((index == 0) || person.Team != people[index-1].Team) ? person.Team : '' }}</div>
</div>

AngularJS - Get printed value from scope inside an attribute?

I'm currently working on an AngularJS project and I got stuck in this specific requirement.
We have a service that has all the data, DataFactoryService. Then, I have a controller called DataFactoryController that is making the magic and then plot it in the view.
<div ng-repeat = "list in collection">
{{list.name}}
...
</div>
Now, we have a requirement that pass multiple data into one element. I thought an "ng-repeat" would do, but we need to have it inside an element attribute.
The scenarios are:
At one of the pages, we have multiple lists with multiple data.
Each data has a unique code or ID that should be passed when we do an execution or button click.
There are instances that we're passing multiple data.
Something like this (if we have 3 items in a list or lists, so we're passing the 3 item codes of the list):
<a href = "#" class = "btn btn-primary" data-factory = "code1;code2;code3;">
Submit
</a>
<a href = "#" class = "btn btn-default" data-factory = "code1;code2;code3;">
Cancel
</a>
In the example above, code1,code2,code3 came from the list data. I tried several approach like "ng-repeat", "angular.each", array, "ng-model" but I got no success.
From all I've tried, I knew that "ng-model" is the most possible way to resolve my problem but I didn't know where to start. the code below didn't work though.
<span ng-model = "dataFactorySet.code">{{list.code}}</span>
{{dataFactorySet.code}}
The data is coming from the service, then being called in the controller, and being plot on the HTML page.
// Controller
$scope.list = dataFactoryService.getAllServices();
The data on the list are being loaded upon initialization and hoping to have the data tags initialized as well together with the list data.
The unique code(s) is/are part of the $scope.list.
// Sample JSON structure
[
{ // list level
name: 'My Docs',
debug: false,
contents: [ // list contents level
{
code: 'AHDV3128',
text: 'Directory of documents',
...
},
{
code: 'AHDV3155',
text: 'Directory of pictures',
...
},
],
....
},
{ // list level
name: 'My Features',
debug: false,
contents: [ // list contents level
{
code: 'AHGE5161',
text: 'Directory of documents',
...
},
{
code: 'AHGE1727',
text: 'Directory of pictures',
...
},
],
....
}
]
How can I do this?
PLUNKER -> http://plnkr.co/edit/Hb6bNi7hHbcFa9RtoaMU?p=preview
The solution for this particular problem could be writing 2 functions which will return the baseId and code with respect to the list in loop.
I would suggest to do it like below
Submit
Cancel
//inside your controller write the methods -
$scope.getDataFactory = function(list){
var factory = list.map( (a) => a.code );
factory = factory.join(";");
return factory;
}
$scope.getDataBase= function(list){
var base= list.map( (a) => a.baseId);
base= base.join(";");
return base;
}
Let me know if you see any issue in doing this. This will definitely solve your problem.
You don't really have to pass multiple data from UI if you are using Angular.
Two-way data binding is like blessing which is provided by Angular.
check your updated plunker here [http://plnkr.co/edit/mTzAIiMmiVzQfSkHGgoU?p=preview]1
What I have done here :
I assumed that there must be some unique id (I added Id in the list) in the list.
Pass that Id on click (ng-click) of Submit button.
You already have list in your controller and got the Id which item has been clicked, so you can easily fetch all the data of that Id from the list.
Hope this will help you... cheers.
So basing from Ashvin777's post. I came up with this solution in the Controller.
$scope.getFactoryData = function(list) {
var listData = list.contents;
listData = listData.map(function(i,j) {
return i.code;
});
return listData.join(';');
}

Group results in autocompleted dropdown [Meteor]

I try to do a dropdown list in my app. First of all I use a Meteor, so that's specific kind of app ofc :)
Second thing is that I use sebdah/meteor-autocompletion package, because I want my results to be sorted in specific way and limited.
The last thing I need is to group my results.
For example: If I have 2 products named "blah" I want to get only 1 "blag" in my dropdown "autocompletion" list.
Some code:
HTML:
<template name="InvoicesEditInsertInsertForm">
<input id="descriptionautocomplete" type="text" name="description" value="" class="form-control" autofocus="autofocus" placeholder="New Item...">
</template>
JS:
Template.InvoicesEditInsertInsertForm.rendered = function() {
AutoCompletion.init("input#descriptionautocomplete");
};
Template.InvoicesEditInsertInsertForm.events({
'keyup input#descriptionautocomplete': function () {
AutoCompletion.autocomplete({
element: 'input#descriptionautocomplete', // DOM identifier for the element
collection: InvoicesItem, // MeteorJS collection object
field: 'description', // Document field name to search for
limit: 5, // Max number of elements to show
sort: { modifiedAt: -1 },
}); // Sort object to filter results with
},
});
I need to use function that could group my "description" here.
I tried to do it in helper and I get it on my screen, but to be honest I don't know how to put that into my dropdown :(
try: function() {
var item= InvoicesItem.find({},{sort:{modifiedAt:-1}}).fetch();
var descriptions={};
_.each(item,function(row){
var description = row.description;
if(descriptions[description]==null)
descriptions[description]={description:description};
});
return _.values(descriptions);
},
I don't think you can do what you want with that package. If you have a look at the current limitations of the package documentation, you can see other potential solutions to your problem.
You can do addtional filtering as follows:
filter: { 'gender': 'female' }});
but I don't think this will allow you to demand only unique options.
The code you wrote above for try won't do anything. Autocomplete doesn't take a field called try.

Remove Quotes from json_encoded string value

I am working on a project to implement jTable into a PHP framwework class.
It is going quite well. Now I am stuck at a problem where I would like to be able to make custom input fieds on the edit dialog.
We are already using the select2 plugin, now I want to implement this on the edit dialogs of jTable. As far as I understood it is possible to add custom fields to the edit dialog like this:
Name: {
title: 'Name',
width: '20%',
input: function (data) {
if (data.record) {
return '<input type="text" name="Name" style="width:200px" value="' + data.record.Name + '" />';
} else {
return '<input type="text" name="Name" style="width:200px" value="enter your name here" />';
}
}
}
Notice the code above is in JavaScript.
Basically what I do is that I build this javascript in php array and via json_encode I send it to the client.
My problem here is that when I do
$column_array['name']['input'] = function (data) {if ....and so on}
I get on the javascript side
input: "function (data) {... so on"
Please notice the double quotes, it is a string and not a function anymore.
What I would need is to remove this 2 double quotes after the input. (well that's my idea so far)
OR if any one got some experience with jTable] and knows a better way to implement custom fields like select2, chosen, jQuery multiselect, elfinder and stuff like that.
I can give tomorrow some code if needed, cause I am not at work anymore today.
Based on this concept:
// Create a function that takes two arguments and returns the sum of those arguments
var fun = new Function("a", "b", "return a + b");
// Call the function
fun(2, 6);
Output: 8
you may change your JSON a bit in to
Name: {
title: 'Name',
width: '20%',
input: { name: "input",
param: "data",
body: "if (data.record) {
return '<input type=\".... "
}
....
then you may define a function and execute it
var d = new Function(Name.input.name, Name.input.param, Name.input.body);
d(otherdata);
this will omit the awful eval(...) stuff. Caveat: it's still not a method of the given object.

I have two divs with the same ng-controller in AngularJS, how can I make them share information?

I have two different div tags in my html code referencing the same controller in AngularJS. What I suspect is that since these divs aren't nested they each have their own instance of the controller, thus the data is different in both.
<div ng-controller="AlertCtrl">
<ul>
<li ng-repeat="alert in alerts">
<div class="span4">{{alert.msg}}</div>
</li>
</ul>
</div>
<div ng-controller="AlertCtrl">
<form ng-submit="addAlert()">
<button type="submit" class="btn">Add Alert</button>
</form>
</div>
I know this could easily be fixed by including the button in the first div but I feel this is a really clean and simple example to convey what I am trying to achieve. If we were to push the button and add another object to our alerts array the change will not be reflected in the first div.
function AlertCtrl($scope) {
$scope.alerts = [{
type: 'error',
msg: 'Oh snap! Change a few things up and try submitting again.'
}, {
type: 'success',
msg: 'Well done! You successfully read this important alert message.'
}];
$scope.addAlert = function() {
$scope.alerts.push({
type: 'sucess',
msg: "Another alert!"
});
};
}
This is a very common question. Seems that the best way is to create a service/value and share between then.
mod.service('yourService', function() {
this.sharedInfo= 'default value';
});
function AlertCtrl($scope, yourService) {
$scope.changeSomething = function() {
yourService.sharedInfo = 'another value from one of the controllers';
}
$scope.getValue = function() {
return yourService.sharedInfo;
}
}
<div ng-controller="AlertCtrl">{{getValue()}}</div>
<div ng-controller="AlertCtrl">{{getValue()}}</div>
If I understand the question correctly, you want to sync two html areas with the same controller, keeping data synced.
since these divs aren't nested they each have their own instance of the controller, thus the data is different in both
This isn't true, if you declare the controllers with the same alias (I'm using more recente angular version):
<div ng-controller="AlertCtrl as instance">
{{instance.someVar}}
</div>
<div ng-controller="AlertCtrl as instance">
{{instance.someVar}} (this will be the same as above)
</div>
However, if you WANT them to be different and comunicate each other, you will have to declare different aliases:
<div ng-controller="AlertCtrl as instance1">
{{instance1.someVar}}
</div>
<div ng-controller="AlertCtrl as instance2">
{{instance2.someVar}} (this will not necessarily be the same as above)
</div>
Then you can use services or broadcasts to comunicate between them (the second should be avoided, tough).

Categories