Ember.js views (unlimited number) - javascript

so I need to be able to support an (finitely) unlimited number of views in ember.
Basically, I'm creating a survey app. The survey can have an undefined number of steps, hence an undefined number of views/controllers. The number of steps is got from the server, along with the content and the handlebars template (there are only a finite number of possible content types for a step), and I need some way of being able to extend the survey for the different surveys. So basically one ember app handles the entire thing, I've managed to abstract my code for rendering the content types out enough that it doesn't matter for the ember step: i now just need to make it support an unlimited number of steps
Is it possible to have ember views and controllers, but instead of referencing them by name, reference them by array indice? Like normally you'd go App.TestView = …, and router.get('applicationController').connectOutlet('test'), but could I use App.AllView[0] = Ember.View.extend(); and router.get('applicationController').connectOutlet('test')[0] or something? Then I can have a for loop at the start, which grabs the survey data which contains the content types and number of steps, plug it in a view array, and we're away
There might be other ways to achieve this, so any solutions would be great. Creating a new ember app is not an option, the same app needs to service whatever is passed in as a survey object.

You could create an array of view classes that contains the different steps. You can add new views to the array using the handlebars template name.
App.surveyViews = [];
App.surveyControllers = [];
App.surveyViews['firstStep'] = Em.View.extend({ templateName: 'my-handlebars-template' });
App.surveyControllers['firstStep'] = Em.Controller.extend();
Your route can take in the step from the route and use that to look up the appropriate view class and controller to use for the connectOutlet.
step: Em.Route.extend({
route: '/:step',
connectOutlets: function(router, context) {
router.get('applicationController').connectOutlet({
viewClass: App.surveyViews[context.step],
controller: App.surveyControllers[context.step].create(),
context: {}
});
}
})
You can add any extra logic you need for the context, etc., but that would be the basic idea.

Related

Lodash & Angular: Transform JSON object into view model

I am trying to find the best way to transform a large JSON object into a view model. Previously, I had the model incorporated into the view, which is a bad practice. So, now I have the model being generated inside of a controller. I am using Lodash as a utility library.
My current design plan is to transform the JSON object into a "master" array that is accessible in the controller's scope. The JSON file is being served by Express. ModelService simply gets this file to make it available in the controller.
$scope.arr is the "master" array that I want to use in the view.
I also made the JSON data available for viewing at an external link since it is so large. Here it is.
(function() {
'use strict';
angular
.module('app')
.controller('ModelController', ModelController);
function ModelController($scope, ModelService, _) {
$scope.jsonData = ModelService.getAll();
$scope.getData = function() {
$scope.jsonData.$promise.then(function(data) {
$scope.all = data;
$scope.arr = [];
_.mapValues($scope.all.parent1.clusters, function(cluster) {
$scope.arr.push(cluster.name);
_.mapValues(cluster.subclusters, function(subcluster) {
$scope.arr.push(subcluster.name);
_.mapValues(subcluster.entities, function(entity) {
// map entities
})
});
});
});
};
$scope.getData();
}
})();
This code is just adding cluster and subcluster names to the array. I'd like the subclusters to be mapped to their parent cluster. The idea I have for doing this involves transforming each cluster element into its own array, and then adding the subclusters, and then transforming each subcluster into an array in order to map the entities to them. This seems tedious and inefficient. So, I am looking for a better way to achieve this.
It would be nice if I could add each cluster object to the array in one fell swoop without all the mapping and converting objects to arrays. Is that possible at all?
The wireframe view looks like this. The Flex Cluster Title is the name of the subcluster, and each number inside of them is an entity.
Firstly, I would move this processing into the service. It's easier to test, and keeps your view separated from your models (Controllers, are really more part of the "View" IMO when it comes to Angular especially if you're considering upgrading to Angular 2.0 in the future).
In Angular, I think the appropriate way to solve this, would be to use components (or directives) combined with ng-repeat.
The page template:
<!-- Page template, assume $ctrl is your controller, $ctrl.clusters is the data -->
<cluster ng-repeat = "cluster in $ctrl.clusters"
cluster-data="cluster" >
</cluster>
The cluster directive template:
<!-- Assume $ctrl is the controller for the cluster directive, $ctrl.cluster is the cluster object. -->
<div class="name">{{$ctrl.cluster}}</div>
<div class="subClusterNames"
ng-repeat="subCluster in $ctrl.cluster.subClusters>
{{subCluster.name}}
</div>
You might think that this is mapping your data too closely to the view, but as long as you use components to display your data (ie, don't put it all into one template) I think you'll be fine.

Angular JS Concept 'Separation Of Concerns' --> CRUD Screen

I have a two CRUD screens in AngularJS.
1) Separate html file for View, Add and Edit. View Controller, Add Controller and Edit Controllers also Separate. This structure creating more duplicate code in html and controller side.
2) Separate html file for View, Add. View Controller, Add Controller only Separate. During edit mode I'm using a Boolean in controller to find its in edit mode or not.
I'm new to AngularJS. Anybody clearly tell me pros and cons, which way is correct in AngularJS 'Separation Of Concerns' concept.
I'm not sure you're going to the good direction.
By separation of concern, what is intended is to split the code that is responsible for managing the view, and the code responsible to make calls to your server. Imagine you want to make CRUD around a person, I would do the following :
service :
angular.module('app').factory("personService", ["$http", function($http)]){
return {
create : create,
update: update,
remove : remove,
get: get
};
function create(person){
return http.post("person/create", person);
}
// other functions
}
Then, I would only have 1 controller for everything :
angular.module("app").controller("PersonController", ["personService", function(personService)]){
var self = this;
self.isUpdate = true; // Insert logic here
self.isCreate = false; // Insert logic here
init();
self.save = function(){
var promise = self.isCreate ?
personService.create(self.person)
:personService.update(self.person);
promise.then(function(result){
// Handle return of save;
});
};
function init(){
if (!self.isCreate){
personService.get(personId).then(function(result){
self.person = result.data.person;
});
}
}
}
And then I would have the following view :
<div ng-controller="personController as person">
<label>Name: </label>
<input type="text" ng-disabled="!person.isUpdate" ng-model="person.person.name" />
<button ng-click="person.isUpdate = !person.isUpdate;">Edit</button>
<!-- Edit : the code 'person.isUpdate = !person.isUpdate;' could also be into a controller's function (like the save function) -->
<button ng-click="person.save()" ng-if="person.isUpdate">Save</save>
</div>
I also recommend you to read this : https://github.com/johnpapa/angular-styleguide
Separation of concerns means that you have a well defined structure of your application: the data model in the application is decoupled from the business and presentation logic. It is the base of the MVC pattern, which defines the view, the controller and the model.
This separation makes the code maintainable and easy to test.
Follow this guidance how to architect your Angular application.
The model should:
Include the domain data;
Implement the management of the domain data (query, edit, delete, storing mechanism, REST implementation, http fetching);
Expose an API that makes possibly the model usage in controller or other service;
The model should not:
Provide the details on how the domain data is managed (all REST implementation, http calls should be encapsulated in the model);
Contain logic that transforms the model based on user interaction (it is controller's role);
Contain logic for displaying data to the user (this is the view’s job);
A controller should:
Contain the logic required to initialize the scope;
Contain the logic/behaviors required by the view to present data from the scope;
Contain the logic/behaviors required to update the scope based on user interaction;
A controller should not:
Contain logic that manipulates the DOM (that is the job of the view);
Contain logic that manages the persistence of data (that is the job of the model);
Manipulate data outside of the scope;
A view should:
Contain the logic and markup required to present data to the user
A view should not:
Contain complex logic (this is better placed in a controller);
Contain logic that creates, stores, or manipulates the domain model.
This guidance is taken from this awesome book, which I recommend for any starting AngularJS developer.
Related to your example, in my opinion you should:
Create a controller and a template for editing and adding; Depending on model's isNew property, you can apply editing or adding action;
Create a controller and a template for viewing the model;
Optionally, if you have a collection of models, you can create a new controller and view also.
However it depends on the amount of logic behind the model. If the model is trivial, probably you can implement everything in a single controller and view. But it's rare.

Backbone router, view & model cooperation

I have the View containing some button. When that View becomes activated it should take the some URL parameter (in my case -- site id) and set it to the button attribute "data-site_id". There is a router too in the app. But I don't know how to implement it with the best way. Till now I see 3 supposed solutions:
Extract site id from URL hash. URL is build by such a pattern:
"sites/edit/:id(/:tab)": "editSite",
the question is -- may I use here a router itself (and, if yes then how?) or can not, and should parse it with common JS way? Of course, router & view are two different objects and located in different files/scopes.
Save the site_id in model. But I'm not sure how to store it from router. I think I can create an instance of model, set it as variable under router scope and then treat it as usual, something like this:
(function(app, $, config, _) {
var Model = new app.modelName();
var Router = app.Router = Backbone.Router.extend({
routes: {
"": "start",
//....
"sites/edit/:id(/:tab)": "editSite",
//...
},
//....
editSite: function(id, tab){
Model.set('site_id', id);
}
//....
})(window.Application, jQuery, window.chatConfig, _);
after that I can extract the site id from model in any time I want
Assign a data-site_id attribute to the button just from the router. But this doesn't look as a best practice, right?
So what is your advice?
Your second suggested solution makes the most sense, you could then instantiate your view passing that model to the constructor.
See http://backbonejs.org/#View-constructor

In Vanilla JavaScript, how do I structure my app when having multiple Models/Views/Controllers?

I'm trying to get a practical grasp of MVC model implementation (not the conceptual understanding) in JavaScript.
As for the start, I thought it would be worth making an effort and try building a MVC app in plain JS. I've read dozens of articles and book chapters referring to MVC and its variations. Of course I googled lots of examples to see how it's done for real. The most understandable and with the proper meaning is in my opinion this one:
https://github.com/tastejs/todomvc/tree/master/examples/vanillajs
In the end, I was able to refactor my own app in the todomvc-vanillajs way.
However, there is one thing that still bothers me. All these apps and examples are very basic, so there is only one Model, View and Controller specified for the whole app.
What if I wanted to add more (equally complex) features to such app?
Should I add them one by one to my controller.js view.js and model.js files or whether should I stop developing spaghetti code and add new files instead, thus creating new models, controllers and views for each of the new feature individually?
It seems to me, that every feature should have its own view, controller and model, or at least, could have, depending on the subjective evaluation. But I'm not quite sure how such implementation should look at this situation in terms of code structure, namespacing etc.?
What if I want to imitate a scale by creating multiple views, models and controllers on every single functionality like e.g. handling an "add task to the list" or "delete the task" actions.
For the purpose of my dilemma, I've created my own MVC draft, which has two models, controllers and views. Whether such an approach would make sense? What happens when further developing my application, I quickly get to the point where I have dozens and more specific (coresponding) models, views and controllers.
Heres is the aforementioned fiddle.
;(function () {
'use strict';
/**
* #file ./App.js
*/
var App = {
Model : {},
Controller : {},
View : {}
};
console.log('start');
window.App = App;
})();
/* -------------Views-folder----------------------*/
/* -------------separate-file-----------------------*/
(function () {
'use strict';
/**
* #file Views/buildAdd.js
*/
var buildAdd = {
// render
// event
// pass the reference to event handler in Controller
};
App.View.buildAdd = buildAdd;
})(App);
/* -------------separate-file-----------------------*/
(function () {
'use strict';
/**
* #file Views/buildDelete.js
*/
var buildDelete = {
// render
// event
// pass the reference to event handler in Controller
};
App.View.buildDelete = buildDelete;
})(App);
/* -------------Controllers-folder----------------------*/
/* -------------separate-file-----------------------*/
(function () {
'use strict';
var addController = {
// handle the event and decide what the Model has to do
// handle the response from Model and tells the View how to update
};
App.Controller.addController = addController;
})(App);
/* -------------separate-file-----------------------*/
(function () {
'use strict';
var deleteController = {
// handle the event and decide what the Model has to do
// handle the response from Model and tells the View how to update
};
App.Controller.deleteController = deleteController;
})(App);
/* -------------Models-folder----------------------*/
/* -------------separate-file-----------------------*/
(function () {
'use strict';
var addModel = {
// send request
// get response
};
App.Model.addModel = addModel;
})(App);
/* -------------separate-file-----------------------*/
(function () {
'use strict';
var deleteModel = {
// send request
// get response
};
App.Model.deleteModel = deleteModel;
})(App);
/* -------------separate-file-----------------------*/
Thus, I found this question very similar to mine, but the provided answers are not entirely satisfactory, at least to me.
Check my implementation of so called Single Page Application framework. The whole thing is of 60 lines of code. It uses jQuery but can be implemented in VanilaJS.
Basic idea is simple - your app is just a collection of pages a.k.a. views
<section id="route1" src="content1.htm" />
<section id="route2" src="content2.htm" />
...
Sections id's define set of possible "routes"
SpAPP catches browser's navigate event and load requested view on the route.
And partial content1..N.htm files contain view markup, setup and controller functions.
Data model here is JS data received from server and stored in memory or in local storage.
As of MVC frameworks in general... You cannot bring joy to everyone and free of charge. That small SpAPP thing that can easily be understood and adjusted to particular project's needs is a way to go I think.
Looking at my experience in Ruby on Rails framework, I don't always need all three elements of MVC pattern. Sometimes you need a model for a database but it's only accessed internally, not by the client. Or sometimes you only need a generic helper class.
As a convention, the files are split, each one has its own controller, model and view, following a naming convention, maybe something like:
articles-view.html
articles-controller.js
articles-model.js
Views are split for each action in the controller:
articles-index.html
articles-show.html
articles-update.html
...
articles-controller.js
articles-model.js
Inside the controller, you will have the "actions", the functions for everything semantically related to an Article in a blog.
function ArticlesController() {
function index() { ... }
function create() { ... }
function edit() { ... }
...
function delete() { ... }
}
In models, you basically have the class / prototype itself, something that is built with the given data.
function Article() {
this.name = "";
this.author = "";
this.text = "";
this.dateCreated = "";
}
And finally, your views should have element with the same name used in the model.
If you have a basic CRUD system, for example, you can have just one controller and one model, but different views (one for listing all items, one for creating and editing, one for just one item, etc).
Taking examples from Rails and NodeJS, a way to write less code for the views is by using "partials". Common HTML structures can be saved on a file and imported into other HTML files as needed, such a form, the headers, the footer of a page and so on.
Example:
Instead of having a form on articles-create.html and another on articles-edit.html, you will have something like:
_articles-form.html <- this is your partial!
articles-create.html
articles-edit.html
"_articles-form.html" will be imported / appended into the create and edit pages.
Other common features can be consider as "Helpers". They are not a letter in "MVC", but often used. Like the Datepicker library, a simple validation function, a parser, etc. Something that can be used by everyone, not a specific feature of a class.
The project structure could be something like:
app/
app/controllers/
app/controllers/articles-controller.js
app/models/
app/models/articles-model.js
app/views/
app/views/articles/
app/views/articles/index.html
app/views/articles/create.html
app/views/articles/edit.html
app/views/articles/delete.html
app/views/articles/_form.html
Also, having a Manager functionality as you described above, will help you load all the data needed. Some function that maybe will read a json file, looking for the feature's name and parsing through the file's names, loading everything.
The manager would check if there is a model file, a controller file and a folder with N view files in it, containg the word "articles". The same would happen to "authors", "comments", "users" and so on.
I understand that you are proposing this question for study reasons and you took JS as a personal preference, so I´m not saying "don't try it" or something like that. But something to consider: the MVC pattern tackles applications that involves both client and server side. Unless your are developing on a full stack with NodeJS and MongoDB (or other similar technologies), HTML and Javascript are more on the View side of the application (or as helpers).
And if you are developing something like a library, you'll end up putting everything on a single file and minifying/uglifying it. Take JQuery as an example. Javascript developers often go with the Module pattern. They create an object, expose methods and variables that the other developer needs to know and that's it.
So, probably (but not for sure, you never know!), you won't see or work on many vanilla Javascript applications implementing MVC pattern.

How to inject state parameter automatically

Abstract
Hi, I'm using angular + ui-router in my project, I have huge amount of nested states and different views that in turn contain huge amount of different inputs, a user fills these inputs incrementally step by step.
The problem
Sometimes users require additional info that is located on the previous step, and browsers "back" button helps the user to sneak peek into that data, but as soon as the user presses it, the info he already entered is lost due to state transition, which is obviously a bad thing.
Strategy
In order to overcome described problem I have the following plan:
Associate each user's "navigation" (guess this is a proper term) with a random id
To prevent scope-inheritance-and-serialization issues, instead of putting viewmodel into $scope use ordinary javascript object that will be storing immediate values that are bound to UI.
Add watcher to look for changes on that "storage object"
As soon as the change spotted, serialize the object and persist it
Explanations
Why do we need a random parameter in URL?
We don't want to store all data in URL, since there might be quite some amount of data that wont fit into URL. So in order to provide the guarantees the URL won't break, we put only small random GUID/UUID into it that later allows obtaining the data associated with current "navigation" by this random GUID/UUID.
The storage
There are multitude of storage scenarios available out there: LocalStorage, IndexedDB, WebSQL, Session Storage, you name it, but due to their cross-tab, cross-browser, browser-specific nature it would be hard to manipulate and manage all of the data that gets into the storage. The implementation will be buggy / might require server-side support.
So the most elegant storage strategy for this scenario would be storing data in special window.name variable which is capable of storing data in-between requests. So the data is safe until you close your tab.
The Question
On behalf of everything written above, I have the root view called "view" that has a state parameter id (this is the random GUID/UUID)
$stateProvider.state('view', {
url: '/view/{id}',
controller: 'view',
templateUrl: 'views/view.html'
});
All of the other views derive from this view, is there way to make ui-sref directive to automatically inject a random GUID/UUID into id state parameter of my root view, instead of writing each time ui-sref's like:
<a ui-sref="view({id:guid()}).someNestedView({someNestedParam: getParam()})"
I would like to have something like:
<a ui-sref="view.someNestedView({someNestedParam: getParam()})"
The AOP and Decorator pattern are the answer. The comprehensive description could be found here:
Experiment: Decorating Directives by Jesus Rodriguez
Similar solution as described below, could be observed:
Changing the default behavior of $state.go() in ui.router to reload by default
How that would work? There is a link to working example
In this case, we do not solve from which source the random GUID comes from. Let's just have it in runtime:
var guidFromSomeSource = '70F81249-2487-47B8-9ADF-603F796FF999';
Now, we can inject an Decorator like this:
angular
.module('MyApp')
.config(function ($provide) {
$provide.decorator('$state', function ($delegate) {
// let's locally use 'state' name
var state = $delegate;
// let's extend this object with new function
// 'baseGo', which in fact, will keep the reference
// to the original 'go' function
state.baseGo = state.go;
// here comes our new 'go' decoration
var go = function (to, params, options) {
params = params || {};
// only in case of missing 'id'
// append our random/constant 'GUID'
if (angular.isUndefined(params.id)) {
params.id = guidFromSomeSource;
}
// return processing to the 'baseGo' - original
this.baseGo(to, params, options);
};
// assign new 'go', right now decorating the old 'go'
state.go = go;
return $delegate;
});
})
Code should be self explanatory, check it in action here

Categories

Resources