Angular JS dynamic JSON building - angularjs

I want to start with a base JSON, ie [], and provide a tree like structure with options to edit node (change or extend existing keys/values), add sibling (create new entry), and add child (extend the json to become a map, ie add { "field1" : "value1", "field2" : "value2"} to "data".
Seems like the best way to do this is to bind a json scope value to a tree structure. I am about to build one myself, but thought I would check to see if it's been done already....
This sort of feature would allow someone closer to the business to define and refine the data model, as well as make simple edits. Think the php myadmin interface, or the django admin page.

This fiddle will give you a headstart. It was actually referenced from this question. It does not deal with object parameters, just nodes in the tree, but adding these should be pretty straightforward from a controller standpoint. The real challenge is in developing a good-looking view. Here is the controller part (just to make SO happy):
angular.module("myApp", []).controller("TreeController", ['$scope', function($scope) {
$scope.delete = function(data) {
data.nodes = [];
};
$scope.add = function(data) {
var post = data.nodes.length + 1;
var newName = data.name + '-' + post;
data.nodes.push({name: newName,nodes: []});
};
$scope.tree = [{name: "Node", nodes: []}];
}]);

Check it out json-tree angularjs directive.

This looks like a good implementation of what you're looking for.

Related

Multiple filters on a collection with angular-meteor

I'm following the Meteor To-Do App tutorial with Angular integration and am learning about filtering collections. I've been able to implement a simple filter on a collection for the app I'm working on by following the principles in the tutorial, but now I'm stuck trying to figure out how to add multiple queries to the filter.
In the example, you can view incomplete tasks by toggling a checkbox. This is implemented in the controller by watching $scope.hideCompleted for changes and passing it as a Mongo query to filter the Meteor collection.
Watcher
$scope.$watch('hideCompleted', function() {
if ($scope.hideCompleted)
$scope.query = {checked: {$ne: true}};
else
$scope.query = {};
});
Collection filter
$scope.tasks = $meteor.collection(function() {
return Tasks.find($scope.getReactively('query'), {sort: {createdAt: -1}})
});
How do I make the query support multiple filters? For example, say I've chosen to extend the example and have ranked each to-do item by priority. I then would have an an input field for the user to filter the collection by priority, whose value is bound to $scope.priority. Now, if I wanted to filter the to-do list by incomplete and priority=$scope.priority tasks, I understand the Mongo query would need to be something along the lines of Tasks.find({ $and: [{ checked: {$ne: true} },{ priority: $scope.priority }]},{ sort: { createdAt: -1 } }).
In my app, I've been able to make two watchers properly track changes to two scope variables, analogous to my example with $scope.hideCompleted and $scope.priority, but I don't know how to take the next step to merge the queries when filtering the collection. I've also tinkered around a little with this package, since I'll eventually hope to be able to filter and sort by many criteria, but I didn't get too far with it before switching to the concepts I've described here.
I'd appreciate any help with this. Thank you!
This would be my approach:
$meteor.autorun($scope, function() {
// uncomment subscribe after you've got to that point
// $scope.$meteorSubscribe('yourSubscription').then(function() {
$scope.tasks = $scope.$meteorCollection(function() {
return Tasks.find({
checked: $scope.getReactively('model.hideCompleted'),
priority: $scope.getReactively('model.priority')
}, { sort: { createdAt: -1 } });
});
// });
});
A couple of things here:
Once you have removed autopublish you can uncomment the $scope.$meteorSubscribe method and replace "yourSubscription" with the name of your actual subscription.
$meteor.autorun will fire every time any getReactively variable changes.
$scope.$meteorSubscribe and $scope.$meteorCollection are favored as they will remove the subscriptions and object/collection when the scope is destroyed.
If you have any issues then perhaps I can setup a demo for you to look at.
Well, I guess I was a lot closer than I had expected, so I'll answer my question and share what I did to implement multiple filters with regards to the hypothetical extension of the to-do app.
I made hideCompleted and priority scope variables into properties of a scope object model, and used a single watcher with the argument true at the end to check for object equality (for any changes to model or its properties). I then generated $scope.query by stitching together "sub-queries." I've added the code below.
This seems to be working fine for now, but I'm not sure if it's the best solution, so I will continue experimenting, and I will update my answer if I find anything better. I'd be interested in any other approaches, though!
Watcher
var initQuery=true;
var subQueries=[];
$scope.$watch('model', function() {
if (!initQuery){
subQueries=[];
if ($scope.model.hideCompleted)
subQueries.push({checked: {$ne: true}});
if ($scope.model.priority)
subQueries.push({priority: $scope.model.priority});
$scope.query = { $and: subQueries};
} else {
initQuery = false;
$scope.query = {};
}
}, true);
Filter Collections (unchanged)
$scope.tasks = $meteor.collection(function() {
return Tasks.find($scope.getReactively('query'), {sort: {createdAt: -1}})
});

How to prepare arrays for insertion into Firebase database?

I have a question about adding arrays to Firebase using AngularFire. Let's start with a quick example. What I tend to do when my users on the front end create a list is something like this:
angular.module("app", ["firebase"])
.controller("createListCtrl", function($scope, $firebaseArray) {
console.log("controller loaded");
$scope.newList = [];
$scope.addItemToList = function(itemlist) {
console.log(itemlist);
$scope.newList.push({
"item": itemlist,
"done": false
});
}
$scope.sendToDb = function() {
var ref = new Firebase("https://xxxxxx.firebaseio.com");
var list = $firebaseArray(ref);
list.$add({
"list": $scope.newList
}).then(function(ref) {
var id = ref.key();
console.log("added record with id " + id);
console.log(list.$indexFor(id)); // returns location in the array
})
}
Ok all nice and dandy and it all works great but I then I read this article:
https://www.firebase.com/blog/2014-04-28-best-practices-arrays-in-firebase.html
And I heard more people say to avoid arrays and I see the problem with array in Firebase, but what is the alternative, the article says this structure:
{foo: {counter: 1}, bar: {counter: 1}, baz: {counter: 1}};
Is that really a better structure? I think it gets messy and I don't even know how I would achieve this structure starting with something like this:$scope.newList = {};. Is it really a problem doing it with an array. Are arrays really evil in Firebase? Thanks in advance for an explanation or a better alternative.
edit
This is how the list is stored in Firebase, which does not seem very good:
---uniqueID
---list
---0
---done:false
---item:"item1"
---1
---done:false
---item:"item2"
---2
---done:false
---item:"item3"
The $firebaseArray class, which you're already using, provides a mapping between Firebase's ordered collections (which use push ids for their keys) and AngularJS's array (which use regular arrays).
So in your controller's constructor instead of creating a local array for itemList, create a two-way synchronized $firebaseArray:
$scope.newList = $firebaseArray(new Firebase("https://xxxxxx.firebaseio.com"));
The blog post you're referring to served as the basis for quite a few changes to AngularFire since then. I highly recommend that you work through the AngularFire development guide. It will take at most a few hours and will answer many more questions than just this one (which is covered in the section on synchronized arrays).
Update
Thanks for the update. I now get what you're trying to do. So you initially want to keep the list of items client-side only, and then all at once save it to Firebase.
In that case, I'd write sendToDb like this:
$scope.sendToDb = function () {
var ref = new Firebase("https://xxxxxx.firebaseio.com");
var listRef = ref.push();
$scope.newList.forEach(function(item) {
var itemRef = listRef.push({ item: item.item, done: item.done });
console.log('Added item with key: '+itemRef.key());
});
}
This uses the regular Firebase JavaScript SDK. But since AngularFire is built on top of that, they will co-exist without problems.
So instead of pushing the array in one go, I simply loop over the items in it and push each of them.
Working fiddle here: https://jsfiddle.net/frankvanpuffelen/vnh5dbwq/11/

Angular Resource - Default Model Structure

So I'm using this Rest API with ngResource to do get, query, post and update requests. What I'm looking for, is a way to define the structure for each entity.
For example, assuming we have:
module.factory('app.entity.item', function($resource) {
return $resource('http://xmpl.io/items/:itemId', { itemId: '#id' });
});
I want to instantiate it in a controller like:
module.controller('AddItemCtrl', ['app.entity.item', function(Item) {
$scope.item = new Item();
});
and bind it to the respective form in my template.
The actual problem that I have run into, is that I have to deal with 1:m tables.
An example of the entity structure would be:
{
"name": "",
"categories": [],
"list": [
{
"value": "",
"list": [
{
"value": "",
"list": [
{
"value": ""
}
]
}
]
}
]
}
(A more thorough example in the fiddle below)
Now the first two fields are obviously not the problem. It is the third one. The list. Each one of these lists can have a variable number of items.
I am currently using ngRepeat and an add(type, context) method, which adds a new set of fields to the scope (value field in this example and child lists for the first two levels), which will appear in UI by ngRepeat so the user can fill it up and submit it to the service.
First off, I have to define the structure, so the UI would not be empty when the page loads.
module.controller('AddItemCtrl', ['app.entity.item', function(Item) {
$scope.item = new Item({
"name": "",
"categories": [],
"list": [
{
"value": "",
"list": [
{
"value": "",
"list": [
{
"value": ""
}
]
}
]
}
]
});
});
But that is redundant. I have to do it everywhere!
Another issue is that when the item.$save is called, the model is emptied (perhaps re-instantiated?) and the fields inside the list property (managed by the ngRepeat directive) are gone.
So I'm wondering, what would you do under such circumstances.
Is there a way to define the entity (resource) structure?
SAMPLE: http://jsfiddle.net/g15sqd5s/3/
trying to give simple answer - for simple structures I would use something like
module.factory('Item', function($resource) {
var resource = $resource('http://xmpl.io/items/:itemId', { itemId: '#id' },
// you can also define transformRequest here:
{ transformRequest: function(data) {
// data can be transformed here
return angular.toJson(data);
}});
return angular.extend(resource.prototype,
{
name: null,
categories: []
});
});
but then be aware of need to 'flatten' the object.
and for the more complex model I would check restangular
similar topic is also discussed here:
How can I extend the constructor of an AngularJS resource ($resource)?
I would go ahead and revise my model structure in the backend in the first place - the models on the client side should merely follow the ones already defined, rather than being re-defined in a transform block. So, to answer your question, the "default" model structure comes from the server. What you get in your $resource objects has the structure of what your server returns.
To start off, is it really ok to invoke $save on the Item model when the user has populated some values? What we want to save are obviously the lists associated with an item, not the item itself. A separate resource defined in the backend, say items/<item_id>/list, may be a cleaner solution. It may not scale very well, as you'll have to make a separate GET request for each item to fetch its list, but that's the proper RESTful way to do it.
Extending this approach to the example in your fiddle, I imagine a routing scheme like buildings/<building_id>/floors/<floor_id>/units/<unit_id> would be a proper solution. Making a GET request to buildings/ should yield you a list of buildings; each building in the array returned should be an instance of a Building model, which has the proper URL set so the user can perform a single POST and update only the building name, instead of sending back the whole structure back to the server. Applying this recursively to the nested resources should give you a clean and concise way to deal with model changes.
Regarding the UI part - I would go ahead and define three directives for buildings, floors and units, and let each one manage an array with the respective resources, also taking care for the UI bindings to the model values.
So how could a Building model look like?
var BuildingResource = $resource('/buildings/:id', { id: '#id' });
Invoking BuildingResource.query() should yield an array of existing buildings. Adding a new building could look like this:
var newBuilding = new BuildingResource();
newBuilding.$save().then(function(building) {
$scope.buildings.push(building);
}, function(errData) {
//Handle error here...
});
It should be easy to extend this pattern for the rest of the resources - note that what the server needs to return for every building is just the name and the id; knowing the id is sufficient to construct an URL (and a $resource object, respectively) to fetch the needed child resources (in this case, floors).

Multiple resources in one view

I want to display a list of images and their respective comments. Like:
Image url | Format | Comments
http://example.com/img.jpg | 1280x420 | [Comment 1], [Comment 2] ...show all ...show all
http://example.com/img2.jpg | 630x590 | [Comment 1], [Comment 2] ...show all
I have two resouces: /images and /comments/{image_id}
What is the recommended way to fetch the comments for each image to be able to display them on the same row? Does Marionette have a helper for this?
In my opinion, these look like a good place to use relational models. Backbone doesn't support these out of the box, so you'll need a plugin. Have a look at Backbone-Relational or supermodel.js. These projects provide better forms of model nesting than the default implementation. From there, use nested composite views to render the collections.
From what I know Marionette does not have such helper. I think you can use something simple like:
var ImageComments = Backbone.Collection.extend({
initialize: function(models, options) {
options || (options = {});
this.imageId = options.imageId;
Backbone.Collection.prototype.initialize.apply(this, arguments);
},
urlRoot: function() {
return 'comments/' + this.imageId;
}
});
var id = 1,
image = new Image({ id: id }),
comments = new ImageComments(null, { imageId: id });
$.when(image.fetch(), comments.fetch()).done(function() {
// .. do your things with image & comments
});
This describes simple case, if that's commonly used in your application you might want to implement your own fetch method (e.g. for image, that will also fetch comments) or use plugins like Backbone-relational or Backbone-associations
You can use nested composite views.
http://davidsulc.com/blog/2013/02/03/tutorial-nested-views-using-backbone-marionettes-compositeview/
You can also do old fashioned in template loops for the comments
http://www.headspring.com/an-underscore-templates-primer/

Show layout in Marionette.Application inside controller

I am trying to structure my code with MVC flow within my application. I am trying to show created layouts in my marionette app instance within my marionette.controller as below..
Can anyone please tell me is it a proper way to show or change layouts within controller is proper way or not? And if not then what's the proper approach for that.
My Controller
define([ 'marionette', 'app', 'index_view' ], function( Marionette, App, IndexView ) {
console.log("Inside...ViewFlow Controller.");
var ViewFlow_Controller = Marionette.Controller.extend({
loadIndex : function() {
console.log("Inside...Load Index Method.");
App.main.show( new IndexView() );
}
});
return new ViewFlow_Controller();
});
where my IndexView is like this
define(['app', 'helper', 'templates'],
function (App, Helper, templates){
console.log("Inside...Index View.");
App.Page_Index = (function(){
var Page_Index = {};
var _pageName = 'IndexPage';
var _pageLayout = Helper.newPageLayout({
name:_pageName,
panelView: Helper.newPanelView(),
headerView: Helper.newHeaderView({name:_pageName, title:'Welcome to the Index Page'}),
contentView: Helper.newContentView({name:_pageName, template: templates.content_index}),
footerView: Helper.newFooterView({name:_pageName, title:'IndexPage Footer'})
});
return Page_Index;
})();
return App.Page_Index;
});
My helper returns me App_Layout instance.
But it's not working, it's giving me an error
Uncaught TypeError:object is not a function viewflow_controller.js:12
Please help me out.
You can find the code here if you want to refer to the complete code or contribute.
Thanks in advance.
The code on GitHub seems to contain only empty files (aside from the libraries), so I'm going to assume Helper returns a layout instance (which you seem to have indicated, saying it returned an App_Layout instance).
It looks like you're using layouts wrong. The way to use layouts is basically:
Create a layout instance with regions (e.g.) panelRegion and contentRegion
Create view instances that will be displayed in the layout (e.g.) panelViewInstance and contentViewInstance
Write a handler to show your views when the layout itself is shown.
The handler should look like this:
myLayout.on("show", function(){
myLayout.panelRegion.show(panelViewInstance);
myLayout.contentRegionshow(contentViewInstance);
});
Then, show that layout in one of your app's regions:
MyApp.mainRegion.show(myLayout);
The documentation on layouts is here: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.layout.md
You can learn more on using layouts and structuring your code in my book on Marionette.

Resources