Im very new to JS and OJET. I'm using oracle jet to create a form. I need to create two select fields, the firts displays a client's name and the next one must change is values with the selected client's team members.
I have a JSON File with this format:
{
"clients": [
{
"id": "C01",
"name": "Client 1",
"manager": "Manager 1",
"team": [
{
"id": "C1MEM1",
"name": "member 1"
},
{
"id": "C1MEM2",
"name": "member 2"
},
{
"id": "C1MEM3",
"name": "member 3"
},
{
"id": "C1MEM4",
"name": "Member 4"
}
]
},
{
"id": "C02",
"name": "Client 2",
"manager": "Manager 2",
"team": [
{
"id": "C2MEM1",
"name": "member 1"
},
{
"id": "C2MEM2",
"name": "member 2"
},
{
"id": "C2MEM3",
"name": "member 3"
},
{
"id": "C2MEM4",
"name": "member 4"
}
]
}
I managed to create a select field with the clients name:
self.clientsListVal = ko.observableArray(['C01']);
self.clientsList = ko.observableArray();
$.getJSON("http://localhost:8000/js/json/clients.json").
then(function(data){
$.each(data["clients"],function(){
self.clientsList.push({
value: this.id,
label: this.name
});
});
});
Then I tried to get the next select fields this way, but it doesn't work :( :
self.memberList = ko.observableArray();
$.getJSON("http://localhost:8000/js/json/clients.json").
then(function(data){
$.each(data["clients"],function(){
if (this.id === self.clientsListVal ) {
$.each(this["team"], function(){
self.memberList.push({
value: this.id,
label: this.name
});
});
}
});
});
This is the HTML im using:
<div class="oj-applayout-content">
<div role="main" class="oj-hybrid-applayout-content">
<div class="oj-hybrid-padding">
<h3>Dashboard Content Area</h3>
<div>
<label for="clients">Clients</label>
<select id="clients"
data-bind="ojComponent:
{component: 'ojSelect',
options: clientsList,
value: clientsListVal,
rootAttributes: {style:'max-width:20em'}}">
</select>
<label for="select-value">Current selected value is</label>
<span id="select-value" data-bind="text: clientsListVal"></span>
<label for="members">Members</label>
<select id="members"
data-bind="ojComponent: {component: 'ojSelect',
options: memberList,
value: memberListVal,
rootAttributes: {style:'max-width:20em'}}">
</select>
</div>
</div>
</div>
Any help or hint? thank you!.
EDIT:
I think the problem is that self.clientsListVal is returning a function not the current selected value. I added console.log(self.clientsListVal) to the view model to see the current value.
If I change self.clientsListVal for a string:
if(this.id === 'C01'){}
I get the members of the client "C01".
I tried changing self.clientsListVal to $('#clients').val(), this is the id of the select input and i get undefined in the console.log.
How can I get the select field string value inside the viewmodel?
In Knockout, observables are functions -- so when you ask for the observable directly, like self.clientsListVal, you get the function definition. To get the underlying value, call the observable like a function: self.clientsListVal().
So your test becomes if (this.id === self.clientsListVal() ) {
Now you have another problem -- the observable holds an array, not an ID. The array may have a single ID element in it, but you have to reach into the array to get it.
Since you didn't show us how a value gets into clientsListVal, it's hard to say what you need to do. Is it bound to an input field where the user specifies a value? Is it populated from a data call? either way, do you ever need to have more than one ID in clientsListVal? If you only need to hold one ID at a time, change clientsListVal from an observableArray to a simple observable and your test will work.
If clientsListVal can hold multiple values, you'll need to loop over them. There are various ways to do this. You can get the underlying array by assigning the value of the observableArray to a variable: var clients = clientsListVal(). clients now holds the array, and you can use jQuery's $.each, the native Array.each, or some other way to loop over or map the array. Or you can use Knockout's built-in array utilities, like arrayForEach
if you don't want to change to a regular observable but expect the array to only have a single element, you can get at it like clientsListVal()[0] -- that's the 0th (first) element of the array. Watch out for empty arrays, tho.
Related
I have JSON file with this structure:
{
"launches": [{
"name": "First Name"
}],
"launches": [{
"name": "Second Name"
}],
"launches": [{
"name": "Third Name"
}],
"launches": [{
"name": "Fourth Name"
}]
}
I add the data like this:
$('#div-name').append(d.name);
When this is displayed on a webpage, it is placed in one div with no spaces. I can add a <p> tag to the append, but that still displays ALL the data and creates new divs to display it.
Basically, what I am trying to do is to create a div for each separate "name" value and display only one value per div.
First of all, your "json" is not a valid json, remmeber that in a json object, you can not have duplicated keys. You can easily use an online validator to help you with that... and after you fix it, let's assume that what you actually have is an array of objects like:
[
{"name": "Falcon 9"},
{"name": "Orion"},
{"name": "PSLV"},
{"name": "Soyuz"}
]
with this valid json, you can easily loop trough all the elements like (and take that you are using jQuery):
var json = [ ... ];
$(function() {
$.each(json, function(i, item){
$("p").append("<div>" + item.name + "</div>");
});
});
here's a live test: https://jsbin.com/bixadegeye/1/edit?html,css,js,output
A slightly different way of going about it:
var data = {
"names": [
"Falcon 9",
"Orion",
"PSLV",
"Soyuz"
]
};
$.each( data["names"], function( key, value ) {
$('#div-name').append(value+"<br />");
});
Not sure if the question makes sense so better to provide a code example.
{
"data": {
"whatever": [{
"id": "abcd12312",
"title": null,
"value": null,
"options": [
{
"text": "My text {{value}} one",
"value": "email#address.com"
},
{
"text": "My text [value] one",
"value": "email#address.com"
}
]
}]
}
}
So I was thinking can you do anything with Mustache or is there another way without having to write JavaScript to insert the value inside the text?
What I am trying to do is to allow the user to change the text to what ever they want but have a pointer to the value which will be dynamic based on what email address the entered on a previous page.
My problem is a bit complex, so i will try to explain it as detailed as possible.
I have a directive in a SPA that render their components based on a JSON data that i'm getting from an API. Based on the elements and their types (the JSON is an array of different objects) i'm rendering every object in an specific directive:
Objects Type 1: Renders in a Directive Type 1.
Objects Type 2: Renders in a Directive Type 2.
Objects Type 3: Renders in a Directive Type 3.
Directives Type 1-2-3 are contained in the parent directive and every directive has different controls (select, checkbox). This is a very simple Sketch:
And the "sub-directives":
I'm rendering my elements as follows (Container directive):
<div ng-repeat="element in elementList | customFilter:itemsType1">
<div class="line"></div>
<div class="form-group">
<directivetype1 itemdata="element" modeldata="data"></directivetype1>
</div>
</div>
<div ng-repeat="element in elementList | customFilter:itemsType2">
<div class="line"></div>
<div class="form-group">
<directivetype2 itemdata="element" modeldata="data"></directivetype2>
</div>
</div>
...
And this is the Directive 1 code:
<div class="container-fluid">
<div class="form-group">
<div class="col-xs-6 col-sm-3">
<div class="checkbox">
<label><input type="checkbox"/>{{itemdata.metadata.description}}</label>
</div>
</div>
<div class="col-xs-6 col-sm-3">
<label>Option</label>
<select class="form-control" ng-model="" ng-options="list.id as list.label for list in item.optionData"></select>
</div>
</div>
</div>
My problem goes when i try to attach the model to every element rendered, because of:
The model data comes from another API, in another structure.
I'm iterating the list of controls with ng-repeat, but, when i pass the model data to the sub-directive i'm passing all the possible data (as Array) and i'm not being capable of filter and know what object in that array belongs to an specific view element.
The data has the following structure:
View data:
[
{
"elementA": {
"metadata": {
"id": "001",
"subId": "016",
"description": "Element 1"
},
"optionData": [
{
"id": "5",
"label": "Option 1"
},
{
"id": "6",
"label": "Option 2"
},
{
"id": "7",
"label": "Option 3"
}
]
}
},
{
"elementB": {
"metadata": {
"id": "002",
"subId": "024",
"description": "Element 2"
},
"optionData": [
{
"id": "1",
"label": "Option 1"
},
{
"id": "2",
"label": "Option 2"
},
{
"id": "3",
"label": "Option 3"
}
]
}
}
]
Model data:
[
{
"metadata": {
"id": "002",
"subId": "024",
"description": "Element 2",
"selected": "1"
},
...(Some other data belonging to the model)
},
{
"metadata": {
"id": "001",
"subId": "016",
"description": "Element 1",
"selected": "5"
},
...(Some other data belonging to the model)
},
...
]
As you can see, the only way to correlate both models is with id and subId Fields in the metadata object (because the metadata itself can vary having more or less fields).
QUESTION
How can i filter my model object, based on the view object? My goal is to get the model object that correlates to the view object and pass it to the sub-directive for setting it as the model of the control that i'm rendering at that point.
EDIT:
As cmw pointed out, i've coded a function to correlate every model object with their respective view object, but that object is not reflected in the directive scope. itemdata and modeldata are passed to the directive using a bi-directional scope ('='). I think (but i'm not entirely sure) that, when i pass a function to modeldata the directive is not being capable of setting the returned object. The solution that i've coded based on the cmw answer is as follows:
Directive:
<directivetype1 itemdata="element" modeldata="getModelObject(data)"></directivetype1>
JS (coded in the Ctrl of the parent):
$scope.getModelObject = function(element){
var id = typeof element.metadata === 'undefined' ? null : element.metadata.id;
var subid = typeof element.metadata === 'undefined' ? null : element.metadata.subid;
var modelElement = null;
for (var i = 0; i < $scope.data.length; i += 1){
element = $scope.data[i];
if (modelElement.metadata.id === id && modelElement.metadata.subid === id) return element;
}
return null;
};
But when i try to work in the directive with modeldata i see "null" in FF/Chrome Console.
Any guideline to know what's happening?
Thanks.
EDIT 2:
I've added a version of my code here: http://plnkr.co/edit/xjp1l3PuWczdqYf5LP8q?p=preview. Sadly, in that Plunkr it works as expected but my code does not (i'm expecting to see the output of <h1>{{modeldata}}</h1>). I'm comparing the two versions to see any difference (note that i've included the same AngularJS version that i'm using in my project).
As you've pointed out, I believe the key is simply to make use of the id and subId properties on the meta object.
Something like this would probably work...
<div ng-repeat="element in elementList | customFilter:itemsType1">
<div class="line"></div>
<div class="form-group">
<directivetype1 itemdata="element" modeldata="modelDataFor(element)">
</directivetype1>
</div>
</div>
Then, in your controller, define a function like the following...
$scope.modelDataFor = function (element) {
var id = element.meta.id,
subId = element.meta.subId,
curr;
for (var i = 0; i < $scope.data.length; i += 1) {
curr = $scope.data[i];
if (curr.meta.id === id && curr.meta.subId === subId) {
return curr;
}
}
return null;
}
This seems like the most natural place to pluck out the relative data model object to pass into your nested directives.
I have created a plunker here: http://plnkr.co/qbWBFo that shows a form that I am auto filling based on some json (keys). When the user hits submit, I need to access all data that was filled in and create a json like shown below. Obviously if the div named "myform.rows" had static fields I would be able to call $scope.myform.rows. and get all data. Any suggestions? Thank you
UPDATE: The json that I want to get when a user hits submit after filling out all the form fields is something like this:
{
"Date Of Birth": {
"value": "19 May, 1990",
"tag": "a"
},
"Employer": {
"value": "Starbucks",
"tag": "b"
},
"First Name": {
"value": "Jane",
"tag": "a"
},
"Last Name":{
"value": "Doe",
"tag": "c"
},
"Middle Name": {
"value": "K.",
"tag": "c"
},
"Place Of Birth": {
"value": "Houston, Texas",
"tag": "d"
}
}
Following should work for you:
<input type="text" ng-model="formData[k]"/>
Where k is the key/property of the JSON.
I have created Plunkr here: http://plnkr.co/edit/hyBFpRr3OOtanuYsGibs
Use ng-model on your form controls. This will automatically bind to same variable in your scope.
Say you start an object in scope: $scope.myFormData={};
Then add to inputs ng-model;
<input ng-model="myFormData.name"/>
<input ng-model="myFormData.phone"/>
As user types the myFormData object will be automatically updated with whatever ng-model's that match that object
Then within submit method, you send that object to server.
DEMO
I'm trying to show all lists and the tasks associated with each list. In my controller I have:
$http.get('api/list/').success(function (data) {
$scope.lists = data;
$scope.tasks = data[0].Task;
});
This works for the first item but of course data[0].Task needs to be dynamic. The problem I'm having is that this is being called once for each list. I tried using a variable but it gets reset to it's original value. I've also tried using a callback but no luck. I'm not sure what I'm overlooking or if I'm going about it all wrong.
Your best bet is to wrap the http.get in a factory and let it return new representations of your Lists that have the tasks in them. This way you get new references and it won't overwrite your existing objects. Essentially, you want the http.get to return new List objects in its success resolution.
After that, the controller gets the promise resolution, takes the new list object, and binds it into something thats on the scope. This will filter through to the rest of the page and let you preserve existing lists/tasks for the life of the page.
Your GET api/list/ request you probably return something like this:
[
{
"id": 1,
"name": "List #1",
"tasks": [
{
"id": 1,
"name": "Task #1 on List #1"
},
{
"id": 2,
"name": "Task #2 on List #1"
},
{
"id": 3,
"name": "Task #3 on List #1"
}
]
},
{
"id": 2,
"name": "List #2",
"tasks": [
{
"id": 1,
"name": "Task #1 on List #2"
},
{
"id": 2,
"name": "Task #2 on List #2"
},
{
"id": 3,
"name": "Task #3 on List #2"
}
]
}
]
This is assuming that you always want to return the associated tasks in an api/list/ command.
You then only need to call this once every time you want to refresh all lists and all tasks.
You should have a single controller which is bound to a view, in which your $http.get is called. It should set $scope.lists = data on success.
In your view you simply need two nested ng-repeat tags. For example, you could use unordered lists:
<div ng-controller="ListsController">
<ul>
<li ng-repeat="list in lists">
<ul>
<li ng-repeat="task in list.tasks">
</li>
</ul>
</li>
</ul>
</div>
I haven't used angular but I'm pretty sure this is all you need to do. A single AJAX call will populate a <li> element for each list, with nested <li> elements for each task belonging to that list.
If each list has a task and you want a list of them you can do this:
$scope.tasks = data.map(function(obj){return obj.task;})
map creates an array based on what the function returns for each list.