Work-around the lock-state in ember-data when downloading large sets of data - ember.js

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>

Related

Ember #each content doesn't update when replacing model

I have the following index.html:
<!DOCTYPE html>
<html>
<body>
<script type="text/x-handlebars" id="index">
<ul>
{{#each todo in todos}}
<li>{{todo}}</li>
{{/each}}
</ul>
<button {{action 'generate'}}/>Generate a to-do</buton>
</script>
<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.6.1.js"></script>
<script src="js/app.js"></script>
</body>
</html>
And app.js:
App = Ember.Application.create();
App.Router.map(function() {});
App.IndexRoute = Ember.Route.extend({
model: function() {
return {todos: ['To-do 1', 'To-do 2']};
},
});
// This is a function I cannot change, because I don't own it.
// So I'm forced to get the updated model as the result of this.
// Here is some dummy-but-working implementation, for simulation purpose:
function generate(todolist) {
var n = todolist.todos.length + 1;
todolist.todos.push("To-do " + n);
return todolist;
}
App.IndexController = Ember.ObjectController.extend({
actions: {
generate: function() {
var oldToDoList = this.get('model');
var newToDoList = generate(oldToDoList);
this.set('model', newToDoList);
console.log(this.get('model').todos);
},
},
});
When I click on the generate button, I effectively see the growing to-dos array in console, but UI doesn't update.
Shouldn't #each content update automatically when completely replacing controller's model, or am I missing something?
your generate method doesn't actually generate a new array, so Ember won't notice that you've changed the property (because it's a reference to the same array). In your particular instance you should just use pushObject and Ember will know you're modifying the same array.
function generate(todolist) {
var n = todolist.todos.length + 1;
todolist.todos.pushObject("To-do " + n);
return todolist;
}

ember cannot put into the store from an adapter [duplicate]

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
});
}
});

Ember data with RESTless JSON Api like github?

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

How to observe adding element to array

I want to observe adding element to array.
below is test program.
<!-- library load -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script>!window.jQuery && document.write(unescape('%3Cscript src="js/libs/jquery-1.6.1.min.js"%3E%3C/script%3E'))</script>
<script src="http://cloud.github.com/downloads/emberjs/ember.js/ember-0.9.5.min.js"></script>
<script type="text/x-handlebars">
{{#each App.ArrayController.array}}
{{foo}}
{{/each}}
<button onclick="App.ArrayController.addElement();">add</button>
</script>
<script type="text/javascript">
var App = Em.Application.create();
App.ArrayController = Em.Object.create({
array: [{foo:1}, {foo:2}, {foo:3}],
addElement: function() {
this.array.pushObject({foo:4});
},
elementAdded: function() {
alert('ok'); // not invoked...
}.observes('array')
})
</script>
But when call addElement, elementAdded is not invoked...
How do I observe adding element?
use observes('array.#each') instead. jsfiddle code is here
You can use Ember.ArrayController and overwrite the arrayDidChange function
And optionaly call other methods from ther.
<!-- library load -->
<script type="text/x-handlebars">
{{#each App.ArrayController.array}}
{{foo}}
{{/each}}
<button onclick="App.ArrayController.addElement();">add</button>
</script>
<script type="text/javascript">
var App = Em.Application.create();
App.arrayController = Ember.ArrayController.create({
content: [{foo:1}, {foo:2}, {foo:3}],
addElement: function() {
console.log(this);
var array = this.get('content')
array.pushObject({foo:4});
// this.set('array', array);
},
elementAdded: function() {
console.log('ok'); // not invoked...
}.observes('array'),
arrayDidChange: function(item, idx, removedCnt, addedCnt) {
this.elementAdded();
this._super(item, idx, removedCnt, addedCnt);
}
});
</script>
And you can use Observers
Check out this fiddle to see how to know exactly what object has been added or removed from the array using ArrayController.

How to detect if a view is ready?

I have the following html and js code snippets. Basically, I'm trying out Ember's select element. The problem is that I can't detect when the select element is ready to access.
HTML:
<!DOCTYPE html>
<html>
<head>
<title></title>
<link href='lib/uniform/css/uniform.default.css' rel='stylesheet'/>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript" src="../lib/ember.min.js"></script>
<script type="text/javascript" src='lib/uniform/jquery.uniform.js'></script>
<script type="text/javascript" src="Form.js"></script>
</head>
<body>
<script type="text/x-handlebars">
</script>
<script type="text/x-handlebars">
{{#view contentBinding="FormExample.selectValues" valueBinding="type" tagName="select"}}
{{#each content}}
<option {{bindAttr value="fullName"}}>{{fullName}}</option>
{{/each}}
{{/view}}
</script>
</body>
</html>
JS:
FormExample = Ember.Application.create({
ready: function()
{
this._super();
// $("select").uniform(); // doesn't work
$(document).ready( function(){
console.log( $("select") );
//$("select").uniform(); // doesn't work
});
}
});
FormExample.Person = Ember.Object.extend({
id: null,
firstName: null,
lastName: null,
fullName: function()
{
return this.get('firstName') + " " + this.get('lastName');
}.property('firstName','lastName').cacheable()
})
FormExample.selectValues = Ember.ArrayController.create({
content: [
FormExample.Person.create({id:1, firstName: 'a', lastName:'a'}),
FormExample.Person.create({id:2, firstName: 'b', lastName:'b'}),
FormExample.Person.create({id:3, firstName: 'c', lastName:'c'})
],
// test for auto binding
add: function()
{
this.pushObject( FormExample.Person.create({id:4, firstName: 'd', lastName: 'd'}) );
}
});
Output: []
I found it..
Changes to HTML:
instead of using view and create option manually, use the following code
{{view FormExample.select
contentBinding="FormExample.selectOptions"
selectionBinding="FormExample.selectedOption.person"
optionLabelPath="content.fullName"
optionValuePath="content.id"}}
Changes to JS:
FormExample.select = Ember.Select.extend({
didInsertElement: function()
{
$("select").uniform();
}
});