Observe non-ember globals - ember.js

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.

Related

knockout - unit testing computed observables

In knockout, i want to unit test the value of a computed observable that depends on another observable, using jasmine.
However, it doesn't work, as the value of the computed observable doesn't update when i change the other observable.
Here is my (simplified) view model:
function MarkersViewModel() {
var self = this;
self.name = ko.observable("chad");
self.computedName = ko.computed(function() {
return self.name();
});
Here is my jasmine spec:
describe("string", function() {
var view_model = new MarkersViewModel();
view_model.name = ko.observable("joe");
it("returns the whole array when there is no filter", function() {
expect(view_model.computedName()).toBe("joe");
});
});
When i run this, jasmine fails:
Expected 'chad' to be 'joe'.
Any idea on how i could implement that?
Thanks
You should not recreate observable, just set value:
describe("string", function() {
var view_model = new MarkersViewModel();
view_model.name("joe"); // <- here
it("returns the whole array when there is no filter", function() {
expect(view_model.computedName()).toBe("joe");
});
});
Your computed scoped original observable (with "chad" assigned in constructor function) and used it.
It may be useful to the same solution solution with QUnit :
test(" Test view_model.name ", function () {
var view_model = new MarkersViewModel();
view_model.name("joe");
equal(view_model.computedName(), "joe");
});
https://github.com/thedom85/Javascript_Knockout_QUnit_Example

View doesn't update after pushing new POJO ember

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.

Ember promise not resolved when I expect it to be

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.[]

Emberjs how can I make collection arrangedContent and searchResults work together?

I have a controller that observes a search field like so:
Scrolls.IndexController = Ember.ArrayController.extend({
searchResult: function() {
var that = this;
this.get('model').set('content', this.store.filter('scroll', function(item) {
var searchTerm = that.get('searchCard');
var regExp = new RegExp(searchTerm, 'i');
return regExp.test(item.get('name'));
}));
}.observes('searchCard')
});
Which works great, but once I add a method that overrides arrangedContent to limit the returned items, it stops re-rendering.
Scrolls.IndexController = Ember.ArrayController.extend({
arrangedContent: Ember.computed('content', function() {
var count = 0;
return this.get('content').filter(function() {
count++;
return count <= 3;
});
}),
searchResult: function() {
var that = this;
this.get('model').set('content', this.store.filter('scroll', function(item) {
var searchTerm = that.get('searchCard');
var regExp = new RegExp(searchTerm, 'i');
return regExp.test(item.get('name'));
}));
}.observes('searchCard')
});
How can I get make what I'm doing to behave nicely with each other?
I see a few things here that jump out to me. First one being, in the context of a controller, content and model are the same thing so in the observer, when you do:
this.get('model').set('content'
You're setting a property of 'content' on the model when I think you actually intend to set the content directly on the controller, like this:
this.set('content',
I also kind of wonder whether you really need to override the content and arrangedContent properties (not sure what the calling code looks like). I suspect that might cause some bugs later. Instead, I wonder if you could set it up like this:
Scrolls.IndexController = Ember.ArrayController.extend({
firstThreeSearchResults: function() {
var count = 0;
return this.get('searchResults').filter(function() {
count++;
return count <= 3;
});
}.property('searchResults'),
searchResults: function() {
var searchTerm = this.get('searchCard');
return this.store.filter('scroll', function(item) {
var regExp = new RegExp(searchTerm, 'i');
return regExp.test(item.get('name'));
});
}.property('searchCard')
});
Final possible problem is the use of the filter function called on the store. According to the docs, this function: "returns a live RecordArray that remains up to date as new records are loaded into the store or created locally." The problem being, though the filter might update as new results are added, it might not cause the computed property that looks for the first three results to update. That is, the binding on that computed property might not fire. One way to get around this would be to do something like this:
Scrolls.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find();
}
});
Scrolls.IndexController = Ember.ArrayController.extend({
firstThreeSearchResults: function() {
var count = 0;
return this.get('searchResults').filter(function() {
count++;
return count <= 3;
});
}.property('searchResults'),
searchResults: function() {
var searchTerm = this.get('searchCard');
return this.get('content').filter(function(item) {
var regExp = new RegExp(searchTerm, 'i');
return regExp.test(item.get('name'));
});
}.property('searchCard', 'content.length')
});

temporary suspension of ember observers

With Ember.js, I have the following scenario:
My Class has a property that require some manipulation at instanciation.
To achieve that, I'm calling a function that does the work from init. I also want to observe this propery change, so that if consumer will set new value at run time I will run my manipulation logic over the new value.
The problem is, that as part of the init flow, I'm setting myself the new value to the property after manipulation, and this invokes the trigger (as expected). I do not want this code to run twice.
Consider the following code. 'here' will be printed twice to the console.
var MyObj = Ember.Object.extend({
prop: null,
init: function init() {
this._super.apply(this, arguments);
this._applyProp();
},
_applyProp: function prop() {
console.log('here');
var prop = this.get('prop');
if (prop === 'Dan') {
prop = 'Hi' + prop;
}
this.set('prop', prop);
}.observes('prop')
});
MyObj.create({prop: 'Dan'});
Any advice will be really appreciated.
Having an observer that sets the property it is observing seems like a bad idea. Your particular example could be achieved using a computed property getter/setter:
var MyObj = Ember.Object.extend({
prop: function(key, value) {
if (value !== undefined) {
if (value == 'Dan') {
return 'Hi ' + value;
}
return value;
}
}.property(),
});
MyObj.create({prop: 'Dan'});
Would this be sufficient to cover your use cases?