I would like to get JSON data of an api like github. Is this the right way?
Javascript:
var Github = window.Github = Ember.Application.create({
LOG_TRANSITIONS: true
});
Github.Adapter = DS.Adapter.extend({
find: function(store, type, id) {
//var url = type.url;
var url = "https://api.github.com/repos/emberjs/ember.js/commits";
//url = url.fmt(id);
jQuery.getJSON(url, function(data) {
// data is a hash of key/value pairs. If your server returns a
// root, simply do something like:
// store.push(type, id, data.person)
console.dir(data);
store.push(type, id, data);
});
}
});
Github.Commit = DS.Model.extend({
sha: DS.attr('string'),
url: DS.attr('string')
});
Github.Store = DS.Store.extend({
adapter: 'Github.Adapter'
});
Github.IndexRoute = Ember.Route.extend({
model: function() {
return Github.Store.find('commit');
}
});
HTML:
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0/handlebars.js"></script>
<script src="http://builds.emberjs.com/tags/v1.2.0/ember.js"></script>
<script src="http://builds.emberjs.com/tags/v1.0.0-beta.3/ember-data.js"></script>
<meta charset=utf-8 />
<title>JS Bin</title>
</head>
<body>
</body>
</html>
Here is the jsbin
But what is the problem? I don't understand how to interpret the assertion.
Found a solution, it's working but im not sure if this is the right way :)
JavaScript
var Github = window.Github = Ember.Application.create({
LOG_TRANSITIONS: true
});
Github.ApplicationSerializer = DS.RESTSerializer.extend({
extractArray: function(store, type, payload, id, requestType) {
payload.forEach(function(element, key){
element.id = element[type.idField];
});
var newPayload = {};
newPayload[Ember.String.pluralize(type.typeKey)] = payload;
return this._super(store, type, newPayload, id, requestType);
}
});
Github.ApplicationAdapter = DS.RESTAdapter.extend({
namespace: 'repos/emberjs/ember.js',
host: 'https://api.github.com'
});
Github.Commit = DS.Model.extend({
sha: DS.attr('string'),
url: DS.attr('string')
});
Github.Commit.idField = 'sha';
Github.IndexRoute = Ember.Route.extend({
setupController: function(){
var store = this.get('store');
store.find('commit');
}
});
http://jsbin.com/iPiNUrun/10/edit
Related
We've been banging our heads on how to optimize a lock-state downloading a large set of data with Ember-data/Rest-adapter. We're preloading an app with data from a REST API and one of the sets has ha weight of ~2M for some users. What we want to do is avoid the lock-state that the app runs into when extracting all these records.
In this example the interface is supposed to update i on each frame, but "hangs" as soon as the JSON is downloaded and being prepared. This is of-course related to the single-threaded execution, but there has to be some way of making this graceful?
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return [];
},
setupController: function(controller) {
var element = document.getElementById('counter');
var i = 0;
var l = function() {
element.innerHTML = i;
i++;
window.requestAnimationFrame(l);
}.bind(this);
l();
this.store.find('record').then(function(data){
console.log('loaded', data);
});
}
});
App.RecordModel = DS.Model.extend({
name: DS.attr('string'),
email: DS.attr('string'),
birthdate: DS.attr('date'),
created: DS.attr('date'),
});
App.RecordAdapter = DS.RESTAdapter.extend({
host: 'https://gist.githubusercontent.com/hussfelt/100fedf00009bdcbb962/raw/',
pathForType: function() {
return 'json_example.json';
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Ember Starter Kit</title>
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://builds.emberjs.com/tags/v1.10.0/ember-template-compiler.js"></script>
<script src="http://builds.emberjs.com/tags/v1.10.0/ember.debug.js"></script>
<script src="http://builds.emberjs.com/beta/ember-data.min.js"></script>
<script src="app.js"></script>
</head>
<body>
<div id="counter"></div>
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
</script>
</body>
</html>
The solution was to skip using the RESTAdapter to populate this set of data.
Instead we'd do a normal Ajax request with Ember.$, fetching the data - then loop through the data in chunks and use store.pushPayload to inject into the store.
Thanks to people in #emberjs at freenode for the ideas!
The below script could surely be optimized pushing more records each time instead of one at a time. But it solves the problem, and minimizes the lock-state.
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return [];
},
setupController: function(controller) {
var element = document.getElementById('counter');
var i = 0;
var l = function() {
element.innerHTML = i;
i++;
window.requestAnimationFrame(l);
}.bind(this);
l();
// Prebuild options object
var options = {
// Requesting url
url: 'https://gist.githubusercontent.com/hussfelt/100fedf00009bdcbb962/raw/json_example.json',
// Using GET
type: 'GET',
// This is a cross-domain request
crossDomain: true,
// On successful request
success: function(data) {
// Run the inception-loop
recordLoop(Ember.$.parseJSON(data));
},
};
// Trigger the request
Ember.$.ajax(options);
// Disable the normal find for records
//this.store.find('record').then(function(data){
// console.log('loaded', data);
//});
/**
* Will populate the store in each 60th of a second
* #param object data The data to populate with
* #return void
*/
var recordLoop = function(data) {
// Setup counters
var x, i = 0;
// Prebuild awesome object - to match push-payload
var records = {
records: []
};
// Loop through records, populate array and push to store
for (x = (data.records.length - 1), i = 0;
(x >= 0 && i <= 300); x--, i++) {
// Prepare object
records.records = [data.records[x]];
// Push to store
this.store.pushPayload('record', records);
// Remove the actual element from the data
data.records.splice(x, 1);
}
// Run again, if we have content
if (data.records.length > 0) {
window.setTimeout(function() {
recordLoop(data);
}, 1000 / 60);
}
}.bind(this);
}
});
App.RecordModel = DS.Model.extend({
name: DS.attr('string'),
email: DS.attr('string'),
birthdate: DS.attr('date'),
created: DS.attr('date')
});
App.RecordAdapter = DS.RESTAdapter.extend({
host: 'https://gist.githubusercontent.com/hussfelt/100fedf00009bdcbb962/raw/',
pathForType: function() {
return 'json_example.json';
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Ember Starter Kit</title>
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://builds.emberjs.com/tags/v1.10.0/ember-template-compiler.js"></script>
<script src="http://builds.emberjs.com/tags/v1.10.0/ember.debug.js"></script>
<script src="http://builds.emberjs.com/beta/ember-data.min.js"></script>
<script src="app.js"></script>
</head>
<body>
<div id="counter"></div>
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
</script>
</body>
</html>
Ember noob here. I'm basically trying to have multiple input fields for multiple parameters. As the user types into the fields, this sends off a request to a PHP script which returns the relevant JSON and displays it.
Ember 1.6.1 (latest version is a pain to learn as all of the docs are
out of date)
Handlebars 1.3.0
jQuery 1.11.1
Here's the code so far (not working for multiple).
index.html
<script type="text/x-handlebars" data-template-name="search">
{{view App.SearchTextField elementId="bedrooms" valueBinding=bedrooms upKeyAction="searchProperties" placeholder="Bedrooms"}}
{{view App.SearchTextField elementId="suburb" valueBinding=suburb upKeyAction="searchProperties" placeholder="Sydney"}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="search/results">
{{#each}}
<h1>{{bedrooms}} - {{street}} {{suburb}}</h1>
{{/each}}
</script>
apps.js
App = Ember.Application.create();
App.Router.map(function() {
this.resource('search', {path: '/'}, function(){
this.route('results', {path: '/search/:suburb/:bedrooms'});
});
});
App.SearchRoute = Ember.Route.extend({
actions: {
searchProperties: function(suburb, bedrooms) {
console.log(suburb);
this.transitionTo('search.results', suburb, bedrooms);
}
}
});
App.SearchResultsRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON('../test/data.php?suburb='+params.suburb+'&bedrooms='+params.bedrooms);
}
});
App.SearchTextField = Ember.TextField.extend({
keyUp: function (e) {
if (e.target.id == 'bedrooms') {
var bedrooms = e.target.value;
} else if (e.target.id == 'suburb') {
var suburb = e.target.value;
}
console.log(suburb + bedrooms);
this.sendAction('action', suburb, bedrooms);
}
});
After some playing around I got it to work using this (looking more jQuery than Ember, but hey it works)
App = Ember.Application.create();
App.Router.map(function() {
this.resource('search', {path: '/'}, function(){
this.route('results', {path: '/search/:suburb/:bedrooms'});
});
});
App.SearchRoute = Ember.Route.extend({
actions: {
searchProperties: function(data) {
this.transitionTo('search.results', data.suburb, data.bedrooms);
}
}
});
App.SearchResultsRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON('../test/data.php?suburb='+params.suburb+'&bedrooms='+params.bedrooms);
}
});
App.SearchTextField = Ember.TextField.extend({
keyUp: function (e) {
var data = {suburb:$('#suburb').val(), bedrooms:$('#bedrooms').val()};
this.sendAction('upKeyAction', data);
}
});
Is there a better way to do this?
You are kind of over complicating things IMO,
I'd prefer to observe for the value changes in the controller and act accordingly. Result in much reduced code, and in fact you are actually exploiting the features, the framework provides.
Sample implementation, may need to modify to fulfill your needs
App.SearchController = Ember.ObjectController.extend({
suburb : null,
bedrooms : null,
doSearch : function(){
var model = [{street: this.get('suburb'), bedrooms: this.get('bedrooms')}];
//var model = Ember.$.getJSON('../test/data.php?suburb='+this.get('suburb')+'&bedrooms='+this.get('bedrooms'));
this.transitionToRoute('search.results', model);
}.observes('suburb', 'bedrooms')
});
App.SearchRoute = Ember.Route.extend({
});
App.SearchResultsRoute = Ember.Route.extend({
});
App.SearchTextField = Ember.TextField.extend({});
FIDDLE
I'm using ember to display data received from my golang server. The data are in JSON form.
so I opened a websocket and tried to push the message received in the store but i got this error:
Uncaught TypeError: undefined is not a function
this is my app.js:
App = Ember.Application.create({
LOG_TRANSITIONS: true
})
/******************************* Post Template **************************************/
//Define a route for the template "post"
App.Router.map(function() {
this.route("post", { path: "/post" });
});
//Post Model
App.Post = DS.Model.extend({
name: DS.attr('string' ),
number: DS.attr('string')
});
DS.SocketAdapterMixin = Ember.Mixin.create({
uri: 'ws://localhost:8081/',
init: function(){
this.ws = new WebSocket(this.uri);
// callbacks
this.ws.onopen = function() {
console.log('Connection established /all');
};
this.ws.onclone = function() {
console.log('Connection closed /' + 'all');
};
this.ws.onmessage = function(data) {
this.get('store').load(App.Post, data)
console.log(data);
};
this._super();
},
initialize: function() {
console.log('SocketAdapterMixin::initialize');
this._super();
}
});
DS.SocketAdapter = DS.RESTAdapter.extend(DS.SocketAdapterMixin, {
init: function() {
this._super();
console.log('SocketAdapter');
}
});
App.ApplicationAdapter = DS.SocketAdapter.extend({});
// Use the adapter in the store
App.Store = DS.Store.extend({
revision: 13,
adapter: DS.SocketAdapter.create({})
});
and my index.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<title>Ember.js Example Application</title>
<script src="js/libs/jquery-1.10.2.js"></script>
<script src="js/libs/handlebars-1.1.2.js"></script>
<script src="js/libs/ember-1.5.1.js"></script>
<script src="js/libs/Ember_Data.js"></script>
<script src="js/app.js"></script>
<script src="js/router.js"></script>
<!-- <script src="js/models/model.js"></script> -->
</head>
<body>
<h1>Bonjour </h1>
<script type="text/x-handlebars">
Hello, {{firstName}} {{lastName}}<br/>
<nav>
{{#link-to 'post'}}Post{{/link-to}}
</nav>
<div>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>My Wrappers</h2>
<ul>
{{#each post in model}}
<li>{{post.number}}</li>
{{/each}}
</ul>
</script></p>
<script type="text/x-handlebars" data-template-name="post">
<h2>My Post</h2>
<ul>
<li> Zied</li>
<li> Farah</li>
</ul>
</script>
<script type="text/javascript">
</script>
</head>
<body>
</body>
</html>
I suggest that the problem is in this.get('store'), it prints undefined when i try to print its value.
You don't define the store in Ember Data since Ember Data 1.0 beta 1+.
App.Store = DS.Store.extend({
revision: 13,
adapter: DS.SocketAdapter.create({})
});
Just using this will suffice:
App.ApplicationAdapter = DS.SocketAdapter;
The store is passed in to the find functions, if you want you can use it then. Additionally you would be out of scope inside of onmessage, but that's beside the point.
Recommendation
Since your program is two fold I'd recommend creating your adapters/transport layer using dependency injection. Here's a rough draft
Transport
DS.SocketTransport = Ember.Object.extend({
uri: 'ws://localhost:8081/',
type: 'post',
ws: null,
store: null,
init: function(){
var uri = this.get('uri'),
type = this.get('type'),
store = this.get('store'),
ws = new WebSocket(uri);
// callbacks
ws.onopen = function() {
console.log('Connection established /all');
};
ws.onclone = function() {
console.log('Connection closed /' + 'all');
};
ws.onmessage = function(data) {
// if this is random post data, side load
store.load('post', data)
console.log(data);
};
this._super();
}
});
Web Socket Adapter
App.MyWsAdapter = DS.RESTAdapter.extend({
transport: null,
find: function(store, type, id) {
var transport = this.get('transport');
// Do your thing here
return new Ember.RSVP.Promise(function(resolve, reject){
// use the transport here to send a message/get a message, containing
// the json for the type and id mentioned above then use
//resolve(json);
});
},
});
Dependency Injection
App.initializer({
name: "transport",
after:['store'],
initialize: function (container, application) {
var store = container.lookup('store:main'),
postTransport = application.PostTransport.create({store:store, type:'post'});
register("my:postTranspot", postTransport);
application.PostAdapter = App.MyWsAdapter.create({
transport: postTransport
});
}
});
Presently I am going though every page and snippit of code in the Ember.js Guides and building a small sample app. Some I have gotten stuck on for a bit but solved. This one however befuddles me.
At http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/
It's also here but does not use the .get('songs") http://emberjs.com/guides/controllers/representing-a-single-model-with-objectcontroller/
App.SongsRoute = Ember.Route.extend({
setupController: function(controller, playlist) {
controller.set('model', playlist.get('songs'));
}
});
I don't know what playlist.get('songs') is referencing. I assume it's a model object array inner object but I am wrong obviously. But since the example code at their site does not have mock stub data to work from I am just guessing from all of my tests.
The code provided here has some commented out bits to see what I was testing.
<script type="text/x-handlebars" data-template-name="songs">
<h1>Playlist</h1>
<ul>
{{#each}}
<li>{{name}} by {{artist}}</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="playlist">
<h3>Playlist: </h3>
</script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0-rc.3/handlebars.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/ember.js/1.0.0-rc.6/ember.min.js"></script>
<script type="text/javascript">
window.App = Ember.Application.create();
App.Router.map(function () {
this.resource('songs');
this.resource('playlist');
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('songs');
}
});
// App.SongsRoute = Ember.Route.extend({
// setupController: function(controller, model) {
// controller.set('model', model);
// },
// model: function () {
// // return songs;
// return playlist.songs;
// }
// });
App.SongsRoute = Ember.Route.extend({
playlist: function() {
var playlist = { songs: [{fish: "fish"}, {fish: "fish"}] };
return playlist;
}.property(),
setupController: function(controller, playlist) {
controller.set('model', playlist.get('songs'));
}
});
App.PlaylistRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('model', model);
},
model: function () {
return playlist;
}
});
App.SongsController = Ember.ArrayController.extend();
var songs = {
duration: 777,
name: 'Ember',
artist: 'Jimmy Smith',
};
var playlist = {
songs: [
{
id: 1,
duration: 777,
name: 'Ember',
artist: 'Jimmy Smith',
},
{
id: 2,
duration: 888,
name: 'jQuery',
artist: 'Hyper Cat',
}
]
};
</script>
Unfortunately this link (http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/) is a little confusing. They are assuming that a model relationship is there, but they are not showing it.
They are assuming that there is something like this :
App.Playlist = DS.Model.extend({
name : DS.attr('string'),
songs : DS.hasMany('song',{async:true})
});
App.Song = DS.Model.extend({
name : DS.attr('string')
});
And then generally what you'd want to do is to pull the collection from the model in setupController, and then set that as content on a nested controller that has been needed by the main controller.
App.PlaylistRoute = Ember.Route.extend({
setupController : function(controller,model){
this._super(controller,model);
this.controllerFor('songs').set('content',model.get('songs'));
}
});
App.PlaylistController = Ember.ObjectController.extend({
needs : ['songs']
});
And then since you're using ArrayController for the collection, you have built in sorting if you define the sortProperties and sortAscending properties.
App.SongsController = Ember.ArrayController.extend({
sortProperties : ['name'],
sortAscending : true
});
Here's a JSBin showing the general idea, using the FixtureAdapter.
http://jsbin.com/ucanam/1073/edit
Given a view with a context like { id: 1, form_id: 5}, I want to create an {{action}} link to the form using the form_id.
My view code looks like:
<script type="text/x-handlebars" data-template-name="group">
{{action showForm form_id href=true}}
</script>
And the action in my router looks like:
showForm: function(router, event) {
var form_id = event.context;
router.transitionTo('root.form', { id: form_id });
},
I get an error that reads:
Uncaught Error: assertion failed: You must specify a target state for event 'showForm' in order to link to it in the current state 'root.index'.
I'm guessing that the problem is with the way I'm setting up the context for transitionTo, but I haven't been able to figure out the correct solution.
Here is the full code to reproduce the problem:
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="group">
{{action showForm form_id href=true}}
</script>
MyApp = Ember.Application.create({
autoinit: false
});
MyApp.router = Ember.Router.create({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
// Throws error:
// You must specify a target state for event 'showForm' in
// order to link to it in the current state 'root.index'
//
showForm: function(router, event) {
var form_id = event.context;
router.transitionTo('root.form', { id: form_id });
},
// Won't work because form deserialize finds id, not form_id
//showForm: Em.Route.transitionTo('root.form'),
// This won't work either
// showForm: Em.Route.transitionTo('root.form', { id: this.form_id }),
connectOutlets: function( router, context ){
var group = Em.Object.create({ id:1, form_id: 5 });
router.get( 'applicationController' ).connectOutlet( 'group', group );
}
}),
form: Ember.Route.extend({
route: '/form/:id',
serialize: function( router, context ){
return { id: context.id }
},
deserialize: function( router, context ){
var form = Em.Object.create({ id: 5, name: 'my form' });
return MyApp.Form.find( context.id );
},
connectOutlets: function( router, context ){
// left out for fiddle example
}
})
})
});
MyApp.ApplicationController = Ember.Controller.extend({});
MyApp.GroupController = Em.ObjectController.extend({});
MyApp.GroupView = Em.View.extend({ templateName: 'group' });
MyApp.initialize(MyApp.router);
And the cooresponding fiddle:
http://jsfiddle.net/jefflab/LJGCz/
I was able to come up with an ugly solution to this problem using a computed property as the context of my action. The key snippets are:
<script type="text/x-handlebars" data-template-name="group">
<a {{action showForm view.formHash href=true}}>go to form</a>
</script>
MyApp.GroupView = Em.View.extend({
templateName: 'group',
formHash: function() {
return { id: this.get('controller').get('form_id') };
}.property('form_id')
});
And the working fiddle is here:
http://jsfiddle.net/jefflab/pGv8u/
However, after talking to Tom Dale, it is clear that the "right way" to solve to solve this problem is by using materialized objects instead of id values. If you are using Ember data, this is a great use case for the "sideloading" belongsTo feature.