How to implement a service layer using Ember.js - ember.js

I am curious what the best way is to implement a service layer using Ember.js.
A can not really find any suggestions about it in the Ember Guide.
For example I have some validation code that I could use in more than one controllers, and I don't want to copy&paste them into all of them.
Would you use plain javascript objects with methods, that implement the service logic, or is there a more ember-like way to do this?
Thanks.

There is built in support for service layer in Emberjs even not documented now. You can use service layer based on dependency injection.
See example below. Also there is public repository on GitHub with example
index.html
<!doctype html>
<html lang="en" data-framework="emberjs">
<head>
<meta charset="utf-8">
<title>ember_js_di_example</title>
<script src="./bower_components/jquery/dist/jquery.js"></script>
<script src="./bower_components/handlebars/handlebars.js"></script>
<script src="./bower_components/ember/ember.js"></script>
<script src="./index.js"></script>
</head>
<body>
<script type="text/x-handlebars" id="Home">
<div>
<div>My name is <b>{{me.name}}</b></div>
<div>My wife name is <b>{{me.wife.name}}</b></div>
<div>My wife name is <b>{{wife.name}}</b> (based on controller injection)</div>
<div>My children name is <b>{{me.children.name}}</b></div>
</div>
</script>
<script type="text/x-handlebars">
{{ outlet }}
</script>
index.js
var Services = Ember.Namespace.create();
// service class one
Services.Me = Ember.Object.extend({
name: function () {
return 'Jan'
}.property()
})
// service class two
Services.Wife = Ember.Object.extend({
name: function () {
return 'Sarka'
}.property()
})
// service class three
Services.Children = Ember.Object.extend({
name: function () {
return 'Ondra'
}.property()
})
App = Ember.Application.create({
LOG_TRANSITIONS: true,
ready: function () {
// Register service class - instance will be created on demand
this.register('service:me', Services.Me);
this.register('service:wife', Services.Wife);
// Register already instantiated service
this.register('service:children', Services.Children.create(), {instantiate: false});
// inject service to all routes, controllers, service will be accessible as me property
this.inject('route', 'me', 'service:me');
this.inject('controller', 'me', 'service:me');
// inject wife and children to me, will be accessible as wife and children property
this.inject('service:me', 'wife', 'service:wife');
this.inject('service:me', 'children', 'service:children');
// inject wife only to application controller, will be accessible as wife property on home controller
this.inject('controller:Home', 'wife', 'service:wife')
}
});
App.Router.map(function () {
this.resource('Home', {path: '/home'});
});

I think at the moment the best way to reuse controller code would be to use inheritence... that is, if you want common code in multiple controllers, then create a sort of abstract controller with the duplicate logic in it and then extend that for your other controllers. That's what I've done in the past and it's worked quite well for me.
I've had a similar issue with routers, though. You could always use a mixin as well.
Example:
App.MyAbstractController = Em.Controller.extend({
commonMethod: function() {
// something common to all controllers
}
})
App.ActualControllerOne = App.MyAbstractController.extend();
App.ActualControllerTwo = App.MyAbstractController.extend();

Related

Getting a weird persistance issue with ember data fragments and localstorage

Apologies if this isn't quite the right place (as opposed to either libraries own github issue page, but as I've not been able to determine exactly which library is not quite working correctly hard to log it specifically).
I'm using ember data fragments on my model (an array), and localstorage to save down my model. When calling rollback upon the saved model, it seems to reset the fragments back to their original state (i.e. no values), but it still maintains the fragment itself on the array, rather than dropping the item out of the array.
I've got a fiddle setup, click 'add' to add a model, click to view it's details, then click 'add' in there, followed by 'cancel'. You can see that the type + desc values drop out, but the element is still there.
If I switch out to using the Fixture adapter then it all works as expected, just not sure where to start even attempting to debug, I've stepped through many lines of _super calls, and what not trying to figure it out, but just get lost.
Note
This is a pseudo version of my actual app, and curiously enough when you navigate to the home page and then back to the details page, it seems to resolve the type/desc correctly, which it is not doing on my actual app, it still maintains the default values. However refreshing the page makes it work perfectly from then onwards.
Any help greatly appreciated!
<!DOCTYPE html>
<html>
<head>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="//builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>
<script src="//builds.emberjs.com/tags/v1.7.0/ember.js"></script>
<script src="//builds.emberjs.com/canary/ember-data.js"></script>
<script src="//raw.githubusercontent.com/lytics/ember-data.model-fragments/master/dist/ember-data.model-fragments.js"></script>
<script src="//raw.githubusercontent.com/kurko/ember-localstorage-adapter/master/localstorage_adapter.js"></script>
<script>
window.App = Ember.Application.create();
App.ApplicationStore = DS.Store.extend();
App.ApplicationSerializer = DS.LSSerializer.extend();
App.ApplicationAdapter = DS.LSAdapter.extend({
namespace: 'cars'
});
App.Car = DS.Model.extend({
make: DS.attr(),
model: DS.attr(),
features: DS.hasManyFragments('feature')
});
App.Feature = DS.ModelFragment.extend({
type: DS.attr(),
description: DS.attr()
});
App.Router.map(function () {
this.route('index', { path: '/' });
this.route('car', { path: '/car/:car_id'});
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('car');
},
actions : {
add: function(model) {
var car = this.store.createRecord('car', {
make: 'Dodge',
model: 'Viper',
features: []
});
car.save();
}
}
});
App.CarRoute = Ember.Route.extend({
actions: {
add: function(model) {
model.get('features').createFragment({
type: 'Something',
description: 'Some desc'
});
model.save(); //*/
},
cancel: function(model) {
model.rollback();
}
}
});
</script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<script type="text/x-handlebars" data-template-name="index">
{{#link-to 'index'}}Home{{/link-to}}
<ol>{{#each}}
<li>{{#link-to 'car' this}}{{name}} {{model}}{{/link-to}}</li>
{{else}}
<button {{action 'add' model}}>Add</button>
{{/each}}</ol>
</script>
<script type="text/x-handlebars" data-template-name="car">
{{#link-to 'index'}}Home{{/link-to}}
<dl>
<dt>Make</dt>
<dd>{{make}}
<dt>Model</dt>
<dd>{{model.model}}</dd>{{#each features}}
<dt>{{_view.contentIndex}}. {{type}}</dt>
<dd>{{description}}</dd>
{{/each}}
</dl>
<button {{action 'add' model}}>Add</button>
<button {{action 'cancel' model}}>Cancel</button>
</script>
</body>
</html>
I havent worked with data fragments but fragment is a model itself so the element/fragment is still there because you have created a record for it.
This record is stored in the ember store until you do something with it.
Rollback, via emberjs.com,does this - "If the model isDirty this function will discard any unsaved changes".
The model in this case seems to be the fragment. Rollback gets rid of the changes, which is what it is doing in your case, removing the type and desc values, but the record itself is still in the store.
If you want to get rid of the fragment altogether you would have to delete it. http://emberjs.com/guides/models/creating-and-deleting-records/

can you run an ember.js app inside an ember.js app

We have a theoretical need to run an ember widget inside of another ember widget. Is this possible to do? Would scoping the nested ember widget to a different dom element than its parent work?
The idea is that we might need to embed our ember widgets on customer websites that also run ember.
Thanks!
I'm not sure to understand you correctly but If your need is to have separate components that you can distribute and reuse across application, you sure can have them. Although I'm not sure I got you right as you seem to speak of 2 different problems (nesting widgets and distributing ember widgets as third parties for other ember applications...)
Here is a fiddle on how to make an external component and reuse it if you want more details. Let me know if this helps.
The component :
var GravatarImageComponent = Ember.Component.extend({
size: 200,
email: '',
gravatarUrl: function () {
var email = this.get('email'),
size = this.get('size');
return 'http://www.gravatar.com/avatar/' + CryptoJS.MD5(email) + '?s=' + size;
}.property('email', 'size')
});
Ember.Application.initializer({
name: "gravatar-image-component",
initialize: function(container, application) {
container.register('component:gravatar-image', GravatarImageComponent);
}
});
The HTML to bring it to life :
<script type="text/x-handlebars">
<ul class="example-gravatar">
<li>{{gravatar-image email="tomster#emberjs.com" size="200"}}</li>
<li>{{gravatar-image size="200"}}</li>
</ul>
</script>
<script type="text/x-handlebars" id="components/gravatar-image">
<img {{bind-attr src=gravatarUrl}}>
<div class="email-input">
{{input type="email" value=email placeholder="tomster#emberjs.com for instance"}}
</div>
</script>

Accessing a single record from a simple model

I'm starting simple, trying to display a single value from a simple model.
This answer to "accessing the model from the template" suggests that it's necessary to extend ObjectController. At this point, there's have no application logic, so it doesn't seem like a controller (or a view) is really needed yet.
Likewise, there are no routes yet, so it doesn't seem like anything should be needed beyond App.IndexRoute.
The single object in the dictionary fixture has a title property with the value Hello Ember. I'm expecting to see that text displayed between two hard-coded arrows. Instead, all I get is the arrows.
The Index.html is:
<!DOCTYPE html>
<html>
<head>
<title>Dictionary</title>
</head>
<body>
<!-- Main body of the application -->
<script type="text/x-handlebars">
<p>Title: -->{{title}}<--</p>
</script>
<!-- ... Ember.js and other JavaScript dependencies ... -->
<script src="js/libs/jquery-1.10.2.min.js"></script>
<script src="js/libs/handlebars-1.0.0.js"></script>
<script src="js/libs/ember.js"></script>
<script src="js/libs/ember-data.js"></script>
<script src="js/app/application.js"></script>
<script src="js/routers/router.js"></script>
<script src="js/models/dictionary_model.js"></script>
<script src="js/controllers/dictionary_controller.js"></script>
</body>
</html>
And then the JavaScript:
// application.js
window.App = Ember.Application.create();
App.ApplicationAdapter = DS.FixtureAdapter.extend();
// router.js
App.Router.map(function() {
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('dictionary', 0);
}
});
// dictionary_model.js
App.Dictionary = DS.Model.extend({
title: DS.attr("string")
});
App.Dictionary.FIXTURES = [
{
id: 0,
title: "Hello Ember"
}];
// dictionary_controller.js
App.DictionaryController = Ember.ObjectController.extend({
});
I'm not sure where you're reading in the documentation that's contradicting, please update your question with the contradicting statements so they can be fixed.
The controller really only need be defined if you need to add additional computed properties, actions, or other methods. In your case you are correct in that it needn't be defined.
That being said, the application template (or unnamed template as in your case) is the root of your ember app. Any child routes/resources will be rendered in the {{outlet}} located in the application template(examples below).
The index route is a route underneath the application route. Resources are considered routes that can have children and generally associated with a model.
All this comes up to the main problem you're seeing. You've returned your model from the index route, but you are attempting to use it in the application route's template.
Here's a simplified version of your code
Code
App = Ember.Application.create();
App.ApplicationAdapter= DS.FixtureAdapter;
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('dictionary', 0);
}
});
App.Dictionary = DS.Model.extend({
title: DS.attr("string")
});
App.Dictionary.FIXTURES = [
{
id: 0,
title: "Hello Ember"
}];
Templates
<script type="text/x-handlebars">
<h2>Application Template</h2>
Here we Are in the Application Template
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>Index Template</h2>
{{title}}
</script>
Example in action
http://emberjs.jsbin.com/OxIDiVU/443/edit

How do I test AngularJS code using Mocha?

Basically, I'm quite experienced with Mocha (written thousands of unit tests), and I'm quite new to AngularJS (written just my first project).
Now I am wondering how I might unit test all the AngularJS stuff using Mocha.
I know that Mocha runs in the browser, and I have already done this. But how do I structure and setup things?
I guess I need to:
Load AngularJS
Load Mocha
Load my tests
Within each of the tests, I need to load a controller, a service, ... to test. How do I do that? I am not using require.js or something like that, the files are just script files with basically the following content:
angular.controller('fooController', [ '$scope', function ($scope) {
// ...
}]);
How do I reference and instantiate that controller within a test?
The same holds true for services, directives, ...
Do I need to create mocks for $scope, $http & co. for myself, or is there some help?
Please note that I am aware that there is the Karma test runner (formerly known as Testacular), but I do not want to switch my test runner completely.
One way of doing that is to use Angular $injector in your tests:
myModule_test.js
suite('myModule', function(){
setup(function(){
var app = angular.module('myModule', []);
var injector = angular.injector(['myModule', 'ng']);
var service = injector.get('myService');
});
suite('myService', function(){
test('should return correct value', function(){
// perform test with an instance of service here
});
});
});
your html should look similar to this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>myModule tests</title>
<link rel="stylesheet" media="all" href="vendor/mocha.css">
</head>
<body>
<div id="mocha"><p>Index</p></div>
<div id="messages"></div>
<div id="fixtures"></div>
<script src="vendor/mocha.js"></script>
<script src="vendor/chai.js"></script>
<script src="angular.min.js"></script>
<script src="myModule.js"></script>
<script>mocha.setup('tdd')</script>
<script src="myModule_test.js"></script>
<script>mocha.run();</script>
</body>
</html>
If you're creating an angular service that doesn't have any dependencies and isn't necessarily angular specific, you can write your module in an angular-agnostic way, then either write a separate small angular wrapper for it, or test for the presence of angular, and conditionally create a service for it.
Here's an example of an approach that I use to create modules that can be used both in angular, the browser, and node modules, including for mocha tests:
(function(global) {
//define your reusable component
var Cheeseburger = {};
if (typeof angular != 'undefined') {
angular.module('Cheeseburger', [])
.value('Cheeseburger', Cheeseburger);
}
//node module
else if (typeof module != 'undefined' && module.exports) {
module.exports = Cheeseburger
}
//perhaps you'd like to use this with a namespace in the browser
else if (global.YourAppNamespace) {
global.YourAppNamespace.Cheeseburger = Cheeseburger
}
//or maybe you just want it to be global
else {
global.Cheeseburger = Cheeseburger
}
})(this);

Query server for data on bind/observe

I apologize if this has a painfully obvious answer but I am both a JS and Ember noob and I am having trouble finding a solution to what I think is a common scenario. Essentially, I have a multi-page app with html/css/js front end and a java back end with an exposed REST api. I have 1 app.js file that I include in all screens and multiple controllers, some of which only apply to individual screens.
EDIT: Forgot my question. My question is how do I delay the query to my server for my user data until my controller has an observer. Since the controller is present on multiple screens (which dont all need it) I do not want to blindly query on creation of the object since it would be wasteful. For now i have a hacky way of doing it where at the end of my inline script tag of a page I call the populate method. Below is what my code currently looks like.
Section of app.js:
App = Ember.Application.create();
User = Ember.Object.extend({
username: 'empty',
fullname: 'empty user'
});
App.UserDataSource = Ember.Object.extend({
fetchMyUser: function(callback) {
$.get('ncaa/user', function(data) {
callback(User.create({
username: data.username,
fullname: data.fullname}));
});
}
});
App.userDataSource = App.UserDataSource.create();
App.UserController = Ember.Object.extend({
content: null,
populate: function() {
var controller = this;
this.get('dataSource').fetchMyUser(function(data) {
controller.set('content', data);
});
}
});
App.userController = App.UserController.create({
dataSourceBinding: Ember.Binding.oneWay('App.userDataSource')
});
Ember.run.sync();
Section of index.html
<script type="text/x-handlebars">
Welcome, {{App.userController.content.fullname}}
</script>
....other code....
<script type="text/javascript">
$(document).ready(function() {
....other code....
App.userController.populate();
});
</script>
I am pretty sure my first steps will be modifying that handlebars template to extend Ember.View but would like to know what the community believes is the best practice. Also, is it wrong for me to try and put all of this in one app.js file? It would be ok to query on creation of my controller if it was only imported on screens that required the user to display.
The answer for my question did end up being in the Ember.View. Essentially what I do is override the init function of my view which adds the call to populate the necessary controller with data. The view is that instantiated via the handlebars template so no more unnecessary calls or hacky work around. Important changes below.
Index.html:
<script type="text/x-handlebars">
{{#view App.UserNameView }}
Welcome, {{fullName}}
{{/view}}
</script>
App.js:
App.UserNameView = Em.View.extend({
init: function() {
this._super();
App.userController.populate();
},
fullNameBinding: 'App.userController.content.fullname',
userNameBinding: 'App.userController.content.username'
});