Dynamically Creating Menu Structure using Knockout - javascript

I have a requirement to display menus and submenus but I have to populate the structure using a service response.
Below is the sample response I am getting from the service.
{name: "Item A",
url: "url of Item A",
title: "sometitle",
children: [{
name: "Child1 of A",
url: "url of Child1 of A",
title: "sometitle",
children: [{
name: "Grandchild of A",
url: "url of Grandchild of A",
title: "sometitle",
children: [{
name: "Grand Grand child of A",
url: "url of Grand Grand child of A",
title: "sometitle",
children: []
}]
}]
}]
},
{name: "Item B",
url: "url of Item B",
title: "sometitle",
children: []
},
{
name: "Item C",
url: "url of Item C",
title: "sometitle",
children: []
}
All the parent nodes Item A, Item B, Item c are displayed as menu buttons,with children as submenus. I have no information on the depth of the children.
Service can have any number of parents and any level of children nodes.
So,at runtime I have to create the menu structure.
Can i achieve the same using pure javascript ui/li tags? I want to create ui, li tags dynamically ,using a recursive function, which gives me a similar tree structure as shown in the response, Any pointer?
Thanks
Tani

I've faced this requirement myself, I'll present my solution:
I defined a Knockout template to render one item. This will be used to render the first 'tier' of items in your tree-like datastructure. The 'magic' is that the template will render the item's children, using the template itself. So the idea is basically a 'recursive template'. (Sorry if my explanation is a bit vague, the code below should make it clear.)
The template could look like this (suppose you store the service response in an array called items):
<script type="text/html" id="treeItem">
<li class="item">
<ul data-bind="template: { name: 'treeItem', foreach: children }"></ul> // Magic is in this line
</li>
</script>
Consume the template for the first tier of items:
<ul data-bind="template: { name: 'treeItem', foreach: items }"></ul>
Here is a Fiddle demonstrating the solution: http://jsfiddle.net/xsjxc8jd/
Edit: And it didn't even require observables :)

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. 😁

Angular ng-options isn't set by the model

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.

I'm trying to print an array using a loop in Javascript in Angular JS

I'm trying to print out each array item from a property in an object:
{
position:"Finance Office Assistant",
employer:"Washtenaw County Finance Department",
location:"Ann Arbor, MI",
start_date:"2012",
current: false,
end_date:"2012",
duties: [
"Item 1",
"Item 2",
"Item 3"
]
},
This object is in an array, with several other objects. I'm trying to create a function that loops through all of the objects and prints out the duties array items in an unordered list with the exact number of list items and array items.
Here is the function I'm trying to write to do the task
$scope.dutyList = function() {
var arrayLength = $scope.duties.length;
while (arrayLength > 0) {
console.log("dutyList run")
document.write("<li> {{ dutyList }} </li>");
--arrayLength;
}
}
You don't need a function to handle displaying data like this. Angular's ngRepeat is for this. To access the second level of of your data set you can nest two repeats in your unordered list. The first one (in a div) repeats the first layer of your data, and exposes the second layer, which repeat in the <li> tag:
<ul>
<div ng-repeat="d in data">
<li ng-repeat="duty in d.duties">{{duty}}</li>
</div>
</ul>
Plunker
As a general rule, you don't want to write directly to the document when using a framework like Angular. Instead, you should use the built in templating system and directives to render your page.
I'm making some assumptions about the end goal here, but, assuming the array of objects can be accessed by the controller and attached to the scope, you could print your unordered list of duties for each position using something like the following.
angular.module('myApp', []).controller('myCtrl', function($scope){
$scope.foo = [{
position:"Finance Office Assistant",
employer:"Washtenaw County Finance Department",
location:"Ann Arbor, MI",
start_date:"2012",
current: false,
end_date:"2012",
duties: [
"Item 1",
"Item 2",
"Item 3"
]
}, {
position:"Another Position",
employer:"Another Employer",
location:"Ann Arbor, MI",
start_date:"2012",
current: false,
end_date:"2012",
duties: [
"2nd Object, Item 1",
"2nd Object, Item 2",
"2nd Object, Item 3"
]
}];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='myApp' ng-controller='myCtrl'>
<div ng-repeat='bar in foo'>
<h1>{{ bar.position }}</h1>
<ul>
<li ng-repeat='duty in bar.duties'>
{{ duty }}
</li>
</ul>
</div>
</div>

KendoUI ObservableArray children being clobbered by KendoUI treeView

Backstory
I'm using AngularJS/KendoUI and using angular-kendo-ui as a the 'bridge' between the two. I'm using Kendo for it's treeview component and this piece is a client requirement.
What I need to accomplish is
1. draw the tree menu from data provided by a service
2. periodically check each element in this data, and update a 'disabled' prop
3. redraw elements in the treeview based on the results from the above step.
Assumptions
1. If I want to be able to update the kendo tree view, then I need to use Kendo's observeables
2. I may be using Kendo's ObservableArray incorrectly here.
The problem
If I create a new ObservableArray like so
var things = new kendo.data.ObservableArray([{
text: "Parent 1",
items: [{text: "Child 1"}, {text: "Child 2"}, {text: "Child 3"}]
}])
things be logged to the console and the structure is intact.
However, once the treeview is instantiated with this data, further logging of things to the console show that the children (items) are no longer present. It is very hard to iterate over and update children if they don't exist!
Plunker here http://plnkr.co/edit/qJpIvK?p=info, if you view the 'script.js' file and open the console, you should be able to see my issue.
here is the code
HTML
<div ng-app="MyApp">
<div ng-controller="TreeController">
<div kendo-tree-view k-options="thingsOptions"></div>
</div>
</div>
JS
var app = angular.module("MyApp", ["kendo.directives"]);
app.controller('TreeController', function($scope, $timeout) {
var things = new kendo.data.ObservableArray([{
text: "Parent 1",
items: [{
text: "Child 1"
}, {
text: "Child 2"
}, {
text: "Child 3"
}]
}, {
text: "Parent 2",
items: [{
text: "Child 1"
}, {
text: "Child 2"
}, {
text: "Child 3"
}]
}]);
// should have 3 items
console.log('preTreeView init', things[1].items);
$scope.thingsOptions = {
dataSource: things
};
$timeout(function() {
// the 3 items expected are gone, why?
console.log('postTreeView init', things[1].items);
}, 1000);
});
Is this a misuse of ObservableArray and if so, what is the correct approach?
Internally, the TreeView widget turns your ObservableArray into a kendo.data.HierarchicalDataSource http://docs.telerik.com/kendo-ui/api/framework/hierarchicaldatasource which moves each of the children into DataSource objects of their own.
You can navigate them afterward like this:
var treeViewWidget = $(".k-treeview").data("kendoTreeView");
var dataSource = treeViewWidget.dataSource; // this is a HierachicalDataSource
var parents = dataSource.data(); // Parent1 and Parent2
var parent1 = parents[0];
var doesParent1HaveChildren = parent1.hasChildren; // true
var childrenDataSource = parent1.children; // this is a HierarchicalDataSource
var child1 = childrenDataSource.data()[0]; // this is {text: "Child 1"}

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