Angular ng-options isn't set by the model - javascript

I have an angular view where the user can pick an option in a select. (The data of the select comes from an API, so it's JSON object).
Here is one of my select (don't look at my object's structure) :
<select ng-options="team.logo as team.logo.title for team in teams"
ng-model="live.metadata.teams.home.infos"
class="form-control">
</select>
In my controller, I have an empty model which is set well when the client selects a new option.
The value stored in the model by the select is an JSON object with id, title, etc.
In this view, the client can create a team and all works fine. The problem appears when the client wants to edit one of its teams.
I have exactly the same select with ng-options (in another state, [brother of the previous view] "edit" for example), but this time the model isn't empty (it contains a JSON object of a recently created team with exactly the same structure). So I want the select takes the value of the model, to show the name in the select for example.
But nothing appears. The select seems to be disconnected from the model, but when you pick up another option in the select it changes the model well so the ng-options and the ng-model sync well.
I don't know where it can come from...
(I guess it's not a problem of child scopes, I tested it and nothing changed).
Here's an example of object given by the API :
{
logo: {
nid: "71",
title: "Team Name 1",
type: "team",
logo: {
src: "imageURL",
alt: ""
}
}
},
{
logo: {
nid: "72",
title: "Team Name 2",
type: "team",
logo: {
src: "imageURL",
alt: ""
}
}
}

The problem is that by default angular watches the model by reference. To overcome this track by can be used. It didn't work for you because of nested objects (see select as and track by documentation), to fix this I've selected "logos" into another array.
Here's working demo https://jsfiddle.net/1a7s9xqb/

Have a look at my test
angular.module('app', []).controller('test', test);
test.$inject = ['$scope'];
function test($scope) {
$scope.teams = [{
logo: {
nid: "71",
title: "Team Name 1",
type: "team",
logo: {
src: "imageURL",
alt: ""
}
}
},
{
logo: {
nid: "72",
title: "Team Name 2",
type: "team",
logo: {
src: "imageURL",
alt: ""
}
}
}];
$scope.selectedTeam = $scope.teams[0].logo;
$scope.selectedTeam2 = { //logo
nid: "71",
title: "Team Name 1",
type: "team",
logo: {
src: "imageURL",
alt: ""
}
};
};
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="test" >
<select ng-options="team.logo as team.logo.title for team in teams"
ng-model="selectedTeam"
class="form-control">
</select>
</div>
In your scenario you have to set the logo as the model.
And the logo has to be the same reference as in teams.

Related

Unflattening line items in inputData when rendered by function

I have dynamic children input fields that need to be rendered in a function, but when they are, then they are not included in inputData properly/not under the parent input field's key. When the children are included directly in the inputFields, it works as expected, but I can't use a function within the children array with Zapier.
Here is the inputData currently, when the line items are rendered in a function, the LI_ denotes that it is a child input key -
"inputData": {
"supplier": "1",
"LI_budget": 1,
"LI_tax": 1,
"company": "1",
"currency": "1",
"LI_price": "1",
"LI_description": "1"
}
I'm expecting ("parent" is the inputField parent key here):
"inputData": {
"supplier": "1",
"parent": [{
"LI_budget": 1,
"LI_tax": 1,
"LI_price": "1",
"LI_description": "1"
}],
"company": "1",
"currency": "1",
}
This is the function I'm using to pull in the parent and children input fields:
const getLineItems = async (z, bundle) => {
let lineItem = {
key: 'parent',
children: [{
key: 'LI_description',
label: 'Description',
required: true
},
{
key: 'LI_budget',
required: true,
label: 'Budget',
dynamic: 'budget.id'
},
{
key: 'LI_price',
required: true,
type: 'number',
label: 'Unit price',
helpText: 'Example: 50.25'
},
{
key: 'LI_tax',
required: true,
label: 'Tax Rate',
dynamic: 'tax_rate.id'
},
]
}
return [lineItem];
};
There are dynamic fields generated in the getLineItems function that I took out to simplify. TIA
Caleb here from Zapier Platform Support. This is a tough one! We have a pretty long-standing issue report on our platform for supporting custom fields with parent keys (it boils down to a chicken vs the egg problem that really makes my head spin when I read the discussion on the issue). Your inputFields function is spot-on, it's just a matter of properly storing it in the bundle on our part.
I think we could cobble together a workaround to unflatten it. Before I do that though, could you give this a test in the editor and submit actual line items from a previous step to this step? I'm not sure what the inputData looks like (e.g. if multiple items are split like 1,2,3 or in some other fashion). If you want to iterate on this, it might be better to switch over to our public developer Slack (http://zpr.io/ttvdr); then we can post the results here for the next person to run into this. 😁

How to make linked(dynamic?) select fields in oracle jet?

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.

AngularJS : Guidance to implement a factory, service, directive or something else

I'm very new to angularjs and need some advice about the angular way to implement something very simple. In my $scope I need to set some field defaults, and these defaults are needed multiple times in my controller.
I want to be able to refactor these defaults out to a common place, to thin out the controller and allow for code reuse, but not sure if this should be a factory, directive or service.
Heres an example of the defaults:
$scope.skills = [{
description: '',
years: "1",
level: "0",
years_values: [
{ id: "1", description: "1" },
{ id: "2", description: "2" },
{ id: "3", description: "3+" }],
level_values: [
{ id: "0", description: "Starter"},
{ id: "1", description: "Intermediate"},
{ id: "2", description: "Advanced"} ]
}]
Here's an example of where I'd like to call the "new function":
skillSuccess = (resp)->
Loader.hide();
$rootScope.current_user = resp;
#TODO replace this repetition
$scope.skills = [{
description: '',
.... etc
My questions are:
Should I use a factory/directive/service, (or something else) for
this refactoring?
How do I ensure that the function gets called
initially so that the default values are available for the fields
when the page loads?
Should I use a factory/directive/service, (or something else) for this
refactoring?
I'd suggest you to create a constant because looks like you have defaults data which has initially has some value and that will going to be change by the user from the front-end. So you could place that in angular constant, then that constant will be accessed by the factory/service. And Factory/service will do the needful manipulation from its function. To make available constant in your service/factory you need to inject constant name in your service.
By looking at your current requirement you shouldn't be take consideration of directive component.
Constant
app.constant('defaults', [{
description: '',
years: "1",
level: "0",
years_values: [
{ id: "1", description: "1" },
{ id: "2", description: "2" },
{ id: "3", description: "3+" }],
level_values: [
{ id: "0", description: "Starter"},
{ id: "1", description: "Intermediate"},
{ id: "2", description: "Advanced"} ]
}]);
Service
app.service('dataService', function(defaults){
var dataService = this;
dataService.defaults = defaults;
dataService.defaults = angular.copy(defaults) //will return same copy every-time
dataService.getDefaults = function(){
return dataService.defaults;
}
//other method will lie here
})
How do I ensure that the function gets called initially so that the default values are available for the fields when the page loads?
You could simply get that defaults by consuming getDefaults method of your service, then stored that retrieved defaults and use those for manipulation.
If you want the defaults copy to instantiated every-time then use angular.copy(defaults) which will give you the copy of defaults.
Controller
app.controller('myCtrl', function($scope, dataService){
$scope.defaults = dataService.getDefaults(); //this will have defaults
//...other stuff here...
});
Should I use a factory/directive/service, (or something else) for this refactoring?
A controller should be used to set the scope, but the default values should be stored as a constant and returned by a factory. A factory pattern is preferred here because it is a singleton.
angular.module('myApp')
.factory('skillsFactory', function (defaultSkills) {
var service = {};
service.getDefaults = function () {
return defaultSkills;
};
return service;
})
.constant('defaultSkills', [{
description: '',
years: "1",
level: "0",
years_values: [{
id: "1",
description: "1"
}, {
id: "2",
description: "2"
}, {
id: "3",
description: "3+"
}],
level_values: [{
id: "0",
description: "Starter"
}, {
id: "1",
description: "Intermediate"
}, {
id: "2",
description: "Advanced"
}]
}]);
How do I ensure that the function gets called initially so that the default values are available for the fields when the page loads?
In your controller, call $scope.skills = skillsFactory.getDefaults();
angular.module('myApp')
.controller('skillsCtrl', function ($scope, skillsFactory) {
$scope.skills = skillsFactory.getDefaults();
});

Using an input element to reference a json

bit of background: when a user clicks an element, it loads a form retrieved by an ajax call. I save a copy of the json in the controller, and when they change a value, it is to update the local json.
Let's say the json is something like this:
{
name: "questions!",
id: "0",
questions: [{
id: "1",
text: "ninjas or pirates?",
type: "radio",
answers: [{
id: "1",
text: "ninjas"
} , {
id: "2",
text: "pirates"
}],
}, {
id: "3",
text: "why?",
type: "text"
answers: []
}]
}
the element returned is a jquery-ized version of the input that was selected.
While I could iterate through each field and check, I can't help but feel like there is a better way to do it and reference it more directly. How would this be done?

KendoUI TreeView Dynamic JSON

I'm attempting to use the KendoUI by Telerik and get a treeview to bind to dynamic JSON from a generic handler.
In my generic handler, I'm using Newtonsoft.Json to convert a List to my JSON results, which works just great and even works with a different KendoUI control (charts).
Here is what I have as far as the javascript to build the treeview:
var treeSource = new kendo.data.DataSource({
transport: {
read: {
url: "Services/CategoryHandler.ashx",
dataType: "json",
contentType: "application/json; charset=utf-8",
type: "GET"
}
}
});
$("#treeview").kendoTreeView({
dataSource: treeSource
});
Here is a shortened example of the returned JSON:
[
{
"text":"Node 1",
"expanded":true,
"items":null
},
{
"text":"Node 2",
"expanded":true,
"items":null
}
]
"items" will be sub collections in the tree.
When I add the items directly to the datasource such as:
var treeview = $("#treeview").kendoTreeView({
dataSource: [
{ text: "Item 1", expanded: true, items: [
{ text: "Item 1.1" },
{ text: "Item 1.2" },
{ text: "Item 1.3" }
] },
{ text: "Item 2", items: [
{ text: "Item 2.1" },
{ text: "Item 2.2" },
{ text: "Item 2.3" }
] },
{ text: "Item 3" }
]
})
It works just fine. It just does not work when I call a service which writes out the JSON, and what I mean by it does not work, is no data shows up, it is blank.
Any thoughts to what I might be missing or guidance to how I can verify my data is even being returned from the service and even filling my DataSource properly?
Thanks
IMPORTANT As November 8th, 2012 KendoUI already supports it.
The Kendo TreeView does not support binding to a data source yet. The good news is that this is in the plans and will be implemented soon (next release).
It works for me with a trick. I am using the dynamic ViewBag with Json serialized at the controller and therefore, nodes are being drawn great.
My issue is that the events don't seem to work ok. For instance I want to catch the onDrop and rise an alert to show the actual values of such node, and instead it displays the text for ALL nodes. This is driving me crazy by the way.
This is my code, hope can help someone.
function onDrop(e) {
alert(treeView.text(e.sourceNode));
}
A Template must be assigned in order to work:
template: "<span rel='#= item.Id #'> #=item.text #</span>",

Categories

Resources