I've always read that Ember works just fine with POJOs in place of Ember Data, but now that I'm giving it a shot, I'm having a little trouble.
I'm creating an app using NW.js and LinvoDB for the database. Fetching from the DB is easy and works great:
// route/index
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
var gui = require('nw.gui');
var linvoDB = require('linvodb3');
linvoDB.defaults.store = {db: require('medeadown')};
linvoDB.dbPath = gui.App.dataPath;
var File = new linvoDB('file', {} );
var Tags = new linvoDB('tag', {});
var media = new Promise(function(resolve, reject) {
var query = File.find().sort({ctime: -1}).live();
File.on('liveQueryUpdate', function() {
resolve(query.res);
});
});
var tags = new Promise(function(resolve, reject) {
var query = Tags.find().sort({name: 1}).live();
Tags.on('liveQueryUpdate', function() {
resolve(query.res);
});
});
return Ember.RSVP.hash({
media: media,
tags: tags
});
}
});
I have a simple event set up to create a tag, save it and push it into the model:
//controllers/index
actions: {
viewMedia: function(media) {
this.transitionToRoute('media', media)
},
addTag: function() {
var linvoDB = require('linvodb3');
var gui = require('nw.gui');
linvoDB.defaults.store = {db: require('medeadown')};
linvoDB.dbPath = gui.App.dataPath;
var Tag = new linvoDB('tag', {});
var tag = new Tag();
tag.name = this.get('tagName');
tag.save();
this.get('model.tags').push(tag);
}
}
I can verify that the tag object is being inserted correctly in the the tag array in the model, but the view isn't updating. From what I've read, that's cause I'm not using Ember.Object.
How are you suppose to do this with POJOs or do I have to use Ember.Objects? Thanks.
Ember provides it's own array implementation in Ember.Array/Ember.MutableArray that adds a lot of nifty things, like being properly observable in the Ember ecosystem. The Ember.MutableArray in particular (or rather Ember.MutableEnumerable if you want to go deep) has a method called pushObject(obj) that Push the object onto the end of the array. and also notifies any subscribers.
Since Ember also is nice enough to add these to the regular Arrays prototype to make it easy for people to get going, you should be able to simply do this.get('model.tags').pushObject(tag); in your code.
Related
I'm trying to port a 'DateInputView' TextField extension with a bound helper to HTMLBars. It appears that 'call()' has been removed from 'Handlebars.helpers.view' so the helper doesn't work anymore. I've tried several syntax changes based on forums I've read but nothing works. The field is a datepicker which switches to the native calendar picker when using a mobile device. I see an example where the bound helper is integrated into the view helper code, so uses one js file rather than two, so am trying to go that route now. Code is below. If anyone who knows to to rework it for HTMLBars please let me know.
// ../templates/avail/navigation.hbs
{{date-input id="checkin" valueBinding="controllers.avail.arrival" class="form-control date-picker"}}
// ../views/date-input.js
var DateInputView = Ember.TextField.extend({
didInsertElement: function(){
Ember.run.scheduleOnce('afterRender', this, this._setupDateInput);
},
_setupDateInput: function(){
var _this = this;
var type = Ember.$(this.get('element')).attr('type');
// Set up Date Picker for object
if( type === "input" ) {
Ember.$(this.get('element')).datepicker({
format: "yyyy-mm-dd",
autoclose: true
});
}
}
});
export default DateInputView;
// ../helpers/date-input.js
import DateInputView from '../views/date-input';
export default Ember.Handlebars.makeBoundHelper(function(options) {
Ember.assert('You can only pass attributes to the `input` helper, not arguments', arguments.length < 2);
var hash = options.hash,
types = options.hashTypes,
inputType = hash.type,
onEvent = hash.on;
delete hash.type;
delete hash.on;
hash.type = "input";
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
hash.type = "date";
}
hash.onEvent = onEvent || 'enter';
return Ember.Handlebars.helpers.view.call(this, DateInputView, options);
});
I wound up re-working this viewHelper as a Component.
I have a custom component that expects data and not a promise, but I am unsure if they way that I am obtaining the data is the right way.
Is this the right way to do it?
component hbs
{{x-dropdown content=salutations valuePath="id" labelPath="description" action="selectSalutation"}}
Doesn't work
controller (this is the way I expect things to work
import Ember from 'ember';
export default Ember.Controller.extend({
bindSalutations: function() {
var self = this;
this.store.find('salutation').then(function(data) {
self.set('salutations', data);
});
}.on('init'),
components/x-dropdown.js
import Ember from 'ember';
export default Ember.Component.extend({
list: function() {
var content = this.get('content');
var valuePath = this.get('valuePath');
var labelPath = this.get('labelPath');
return content.map(function(item) {
return {
key: item[labelPath],
value: item[valuePath],
};
});
}.property('content'),
This works
controller
bindSalutations: function() {
var self = this;
this.store.find('salutation').then(function(data) {
self.set('salutations', data.get('content')); // pass the content instead of just the data
});
}.on('init'),
component
...
list: function() {
var content = this.get('content');
var valuePath = this.get('valuePath');
var labelPath = this.get('labelPath');
return content.map(function(item) {
return {
key: item._data[labelPath], // access through the _data attribute
value: item._data[valuePath],
};
});
}.property('content'),
Ember Data returns a Proxy Promise. This means you can use the promise as if it were a collection or model itself, as long as you aren't dependent on the property being completely populated when you use it. If you really want the promise resolved, you should probably be setting it up in the route.
If you want it on your controller, you can be lazy and do it like so:
Controller
salutations: function() {
this.store.find('salutation');
}.property(),
Component
...
list: function() {
var content = this.get('content'),
valuePath = this.get('valuePath'),
labelPath = this.get('labelPath');
return content.map(function(item) {
return {
key: item.get(labelPath),
value: item.get(valuePath),
};
});
}.property('content.[]'),
Template
{{x-dropdown content=salutations valuePath="id" labelPath="description" action="selectSalutation"}}
The real trick is to watch if the collection is changing. Hence you'll see I changed the property argument to content.[]
I have a view:
App.PhotoUploadView = Ember.View.extend({
images: [],
didInsertElement: function() {
var that = this;
var product = this.get('controller').get('model');
var upimages = product.get('upimages');
//this.set('images', []);
upimages.then(function(images) {
images.forEach(function(image, indexI) {
var imageObject = new Object();
imageObject.link = App.appConf.apiPaths.images + image.get('link');
that.get('images').pushObject(imageObject);
});
console.log(that.get('images'))
});
}
});
So what I'm doing here is define a images array initially empty, and fill it with objects obtained by manipulating a little the model's children...
{{#each product in model}}
{{view App.PhotoUploadView}}
{{/each}}
In the App I have many PhotoUploadView inserted at the same time; The thing is that instead of having a different images array for every instance of the PhotoUploadView, I get that every instance has a images array that contains all the images, like if the array is shared between instances;
If I remove comment to this.set('images', []); in the didInsertElement function, then everything works; so the question is: the images array is shared between the PhotoUploadView instances? Or am I missing something...?
Ember sees that as a static property and it applies to all instances of that view. If you set it as undefined up front, then define it on init (or whenever it's needed) it should fix the issue.
App.PhotoUploadView = Ember.View.extend({
init: function(){
this._super();
this.set('images', []);
}
images: undefined,
didInsertElement: function() {
var that = this;
var product = this.get('controller').get('model');
var upimages = product.get('upimages');
//this.set('images', []);
upimages.then(function(images) {
images.forEach(function(image, indexI) {
var imageObject = new Object();
imageObject.link = App.appConf.apiPaths.images + image.get('link');
that.get('images').pushObject(imageObject);
});
console.log(that.get('images'))
});
}
});
I would like to use my emberdata as data for creating objects within d3. I try to convert the items from the controllers model into new javascript objects and giving this new array to d3 data. here is the code
App.GraphicsView = Ember.View.extend( {
didInsertElement: function() {
var svg = d3.select("#svg");
var data = this.get('controller').get('model').get('content');
var newData = [];
for(var i = 0; i < data.length; i++) {
newData.push(data[i]);
}
var graphics = svg.selectAll("rect")
.data(newData)
.enter()
.append("rect");
graphics.attr("x", function(d, i) {
return d.get('x');
})
}
but the data variable ist not realy an array so I cann't iterate over it
Ember has lots of in-built iterators (Check out the Ember.Enumerable docs). In your case however, simply calling toArray should suffice.
App.GraphicsView = Ember.View.extend({
didInsertElement: function() {
var svg = d3.select("#svg");
var data = this.get('controller.content');
var newData = data.toArray();
var graphics = svg.selectAll("rect")
.data(newData)
.enter()
.append("rect");
graphics.attr("x", function(d, i) {
return d.get('x');
})
}
})
EDIT:
Here is a working version of your fiddle http://jsfiddle.net/PkT8x/156/
A couple of things, firstly, you had incompatible versions of Ember and Ember-Data in the fiddle.
Secondly, the FixtureAdapter simulates a remote response by default therefore on didInsertElement the App.Graphic collection was actually empty and it is not until the next runloop that the array was populated with the objects, to fix this, I made Ember re-calculate the d3 object whenever the controller's length changes.
App.GraphicsView = Ember.View.extend({
graphicsObserver: function() {
var svg = d3.select("#svg");
var data = this.get('controller.content');
var newData = data.toArray();
var graphics = svg.selectAll("rect")
.data(newData)
.enter()
.append("rect");
graphics.attr("x", function(d, i) {
return d.get('x');
})
}.observes("controller.length")
})
Here is another example : http://jsfiddle.net/2UPLp/16/light/
Requires:
d3.v2.min.js
handlebars-1.0.0.beta.6.js
ember-1.0.pre.min.js
Here is another approach
https://github.com/sabithpocker/ember-appkit-d3-sample
This particular method is generating paths with d3 and binding it to
the svg using Ember. This will result in paths responding to data
changes in the app. The example does not use d3 to render the svg,
instead it is using d3 API to calculate the path and use it in svg.
I want a computed property to observe a non-ember global: a specific key in localStorage. Is this possible? The following does not seem to cut it:
someProperty:function(){
//some functionality
}.property('localStorage.someKey')
Is it possible to do what I'm trying to do directly?
In general, you can observe regular JavaScript objects just fine. You just need to use Ember.get and Ember.set to modify them:
var pojo = {};
var MyObject = Ember.Object.extend({
bigEyeballs: function() {
var O_O = this.get('pojo.O_O');
if (O_O) { return O_O.toUpperCase(); }
}.property('pojo.O_O')
});
var obj = MyObject.create({ pojo: pojo });
console.log(obj.get('bigEyeballs'));
Ember.set(pojo, 'O_O', "wat");
console.log(obj.get('bigEyeballs'));
You can see this working in this JSBin.
Local Storage is a bit of a different matter, as it's not really a normal JavaScript object. You can create a small Ember wrapper around local storage, and use that for observation:
var LocalStorage = Ember.Object.extend({
unknownProperty: function(key) {
return localStorage[key];
},
setUnknownProperty: function(key, value) {
localStorage[key] = value;
this.notifyPropertyChange(key);
return value;
}
});
var storage = new LocalStorage();
var MyObject = Ember.Object.extend({
bigEyeballs: function() {
var O_O = this.get('pojo.O_O');
if (O_O) { return O_O.toUpperCase(); }
}.property('pojo.O_O')
});
var obj = MyObject.create({ pojo: storage });
console.log(obj.get('bigEyeballs'));
Ember.set(storage, 'O_O', "wat");
console.log(obj.get('bigEyeballs'));
You can see this live on JSBin.
In both cases, the important thing is that you will have to use Ember-aware setting and getting in order to observe these properties.