Main goal: Using .find() to access a model other than the one available in the current controller -in order to compare data from the current controller's model with a piece of data from a 'foreign' controller's model.
What triggers the comparison:
I have a button inside a template with {{ action "isResponse"}}. This template's controller has an isResponse : function() {...}
The problem I have: The action is fired every time I click the button, but App.Answer.find() only returns content after the 2nd click. I'm wondering if this is because the Answer model hasn't loaded, but am unsure how to properly set up an observer for isLoaded in my example (if that is even the issue)
So how come App.Answer.find() returns empty the first time it's called??
App.ChoiceController = Ember.ObjectController.extend({
chosen: false,
isResponse: function() {
// successfully returns what I want from this controller's model
var questionId = this.get('question.id')
// gets DS.RecordArray of the model i'd like to compare with
var answers = App.Answer.find()
// filter to get a result that matches this.get('question.id')
var answer = answers.filter(function(ans) {
// returns all entries that match
if(ans.get('question.id') == questionId) { return true }
}, 'answers.isLoaded'); // this observer doesn't seem to hurt or help
// get the final value I need
var choice = answer.mapProperty('choice.id')
// if choice array is not empty, (should only have 1 element anyways)
if(!choice) {
this.set('chosen', choice[0]);
} else {
this.set('chosen', false);
}
}
})
Here are the models involved. Both include DS.belongsTo attributes
App.Choice = DS.Model.extend({
"question" : DS.belongsTo('App.Question')
})
App.Answer = DS.Model.extend({
"question" : DS.belongsTo('App.Question')
"choice" : DS.belongsTo('App.Choice')
})
App.Question = DS.Model.extend({
})
EDIT
Here is jsfiddle showing the behavior. Make sure to open your browser console to notice that each button requires 2 clicks for action isResponse to function properly. http://jsfiddle.net/iceking1624/QMBwe/
After reading your comment I've retought a solution to your problem and one possible way might be that you can define a AnswerController of type ArrayController (since it's for a collection of answers) and then setup this controller in your ApplicationRoute's setupController hook.
Main goal: Using .find() to access a model other than the one available in the current controller -in order to compare data from the current controller's model with a piece of data from a 'foreign' controller's model.
Later on you can then require access to the AnswerController's data using the needs API with needs:['answers'] from inside whatever controller that needs access to the answers collection, and finally have access to the data with this.get('controllers.answer'). You can find here more info on the needs API.
See here a possible solution that works correctly, displaying the right choice already on the 1st click:
App.AnswerController = Ember.ArrayController.extend({});
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controllerFor('answer').set('content', App.Answer.find());
}
});
App.ChoiceController = Ember.ObjectController.extend({
needs: ['answer'],
chosen: false,
isResponse: function() {
var questionId = this.get('question.id');
var answers = this.get('controllers.answer');
var answer = answers.content.filter(function(ans) {
if(ans.get('question.id') == questionId) { return true }
}
var choice = answer.mapProperty('choice.id');
if(!choice) {
this.set('chosen', choice[0]);
} else {
this.set('chosen', false);
}
}
});
And here a working fiddle.
Hope it helps.
Related
I'm currently developing my first Backbone single page app project and I'm facing an issue.
Basically I have a menu (html select input element) implemented as a View. Its value is used to control pretty much every other data requests since it specifies which kind of data to show in the other Views.
Right now I handle the DOM event and trigger a global event so that every model can catch it and keep track internally of the new value. That's because that value is then needed when requesting new data. But this doesn't look like a good solution because A) I end up writing the same function (event handler) in every model and B) I get several models with the same variable.
var Metrics = Backbone.Collection.extend({
url: "dummy-metrics.json",
model: MetricsItem,
initialize: function () {
this.metric = undefined;
},
setMetric: function (metric) {
this.metric = metric;
globalEvents.trigger("metric:change", this.get(metric));
}
});
var GlobalComplexity = Backbone.Collection.extend({
url: function () {
var url = "http://asd/global.json?metric=" + this.metric;
return url;
}, //"dummy-global.json",
model: GlobalComplexyItem,
initialize: function () {
this.metric = undefined;
this.listenTo(globalEvents, "metric:change", this.updateMetric);
},
updateMetric: function (metric) {
this.metric = metric.get("id");
this.fetch({ reset: true });
}
});
All my other Collections are structured like GlobalComplexity.
What's the cleanest way to solve this problem?
Thank you very much.
Define a global parametersManager. Export an instance (singleton) then require it when you need it.
On "globalupdate" you update the parametersManager then trigger "update" for all your model/collections so they'll look what are the current parameters in the parametersManager.
I am trying to implement a search system here and I am having some trouble with store.filter()
First of all I can't find any good documentation on the store.filter() method aside from here: http://emberjs.com/guides/models/frequently-asked-questions/
So I am using the example provided on that page as a guideline.
This is what I have in my code
App.ApplicationController = Ember.ObjectController.extend({
isDropdown: false,
actions: {
handle_search: function() {
var search_text = this.get('search_text');
if (search_text) {
this.set('isDropdown', true);
return this.store.filter('procedure', {proc_name: search_text}, function(procedure) {
console.log(procedure);
});
}
}
}
});
but when I log what is being returned, it's returning basically every single model. Instead of no results or a limited amount of results.
On top of that procedure it self is not the model's object, it's some other thing.
So my question is how do I get the actual model with the fields and how can I ensure that the store is actually filtering the results?
You just need to pass in a function that returns true/false for including the record
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.filter('color', function(item){
return item.get('color')=='red';
});
}
});
http://emberjs.jsbin.com/OxIDiVU/647/edit
If you want to make a call back to the server at the same time (find by query) you include the optional query param, the below example will call /colors?color=green
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.filter('color', {color:'green'}, function(item){
return item.get('color')=='red';
});
}
});
/**
Takes a type and filter function, and returns a live RecordArray that
remains up to date as new records are loaded into the store or created
locally.
The callback function takes a materialized record, and returns true
if the record should be included in the filter and false if it should
not.
The filter function is called once on all records for the type when
it is created, and then once on each newly loaded or created record.
If any of a record's properties change, or if it changes state, the
filter function will be invoked again to determine whether it should
still be in the array.
Optionally you can pass a query which will be triggered at first. The
results returned by the server could then appear in the filter if they
match the filter function.
Example
```javascript
store.filter('post', {unread: true}, function(post) {
return post.get('unread');
}).then(function(unreadPosts) {
unreadPosts.get('length'); // 5
var unreadPost = unreadPosts.objectAt(0);
unreadPost.set('unread', false);
unreadPosts.get('length'); // 4
});
```
#method filter
#param {String or subclass of DS.Model} type
#param {Object} query optional query
#param {Function} filter
#return {DS.PromiseArray}
*/
I want to return the child elements (notes) of a given draft (parent element) as a computed property from my Ember.DocumentController.
In this case I want to return all the notes that belong to the editableDraft property.
Or is there a better way of doing it?
App.DocumentController = Ember.ObjectController.extend({
editableDraft: function() {
var editDrafts = this.get('model.drafts').filterBy("editable", true);
var draft = editDrafts.length ? editDrafts[0] : null;
return draft;
}.property('model.drafts.#each.editable'),
editableNotes: function() {
var eDraft = this.get("editableDraft"); // want to return notes of editableDraft
return eDraft.get("notes");
}.property('model.drafts.#each.editable')
});
See test app in the jsbin!
Two problems. One, in the document template, here:
{{render 'editableDraftNotes' notes}}
Render does some weird thing with replacing your controller and model with the provided arguments. Not what you need in this case. Try this:
{{partial 'editableDraftNotes'}}
Two, in the editableNotes property. You should listen to changes on editableDraft. Also, because Ember.Data returns promises, you must chain your gets (this.get("A.B") instead of this.get("A").get("B")). Try this:
editableNotes: function() {
return this.get("editableDraft.notes");
}.property('editableDraft')
Working jsbin here.
In a Kendo app using the Kendo MVVM framework: I have a "global" viewModel which is information that is common to all parts of the app - e.g. the UserState, which has a property isLoggedIn.
Many different Views and ViewModels access the userState object (from what I can see, 1 View is bound to 1 ViewModel in Kendo).
For example, my home page might show the Login button if they are not authenticated. Then all the other screens behave differently once you are logged in, so each ViewModel needs to reference the UserState object. However, if any of them change it then all other Views should update as I used a Kendo Observable object. This does not seem to work.
I set up a simple example here to illustrate the problem: http://jsfiddle.net/rodneyjoyce/uz7ph/11
var app = new kendo.mobile.Application();
userState = (function ()
{
var userStateViewModel = kendo.observable({
isLoggedIn: false
});
function loginUser()
{
userStateViewModel.set("isLoggedIn", true);
alert('Logged in');
};
return {
userStateViewModel: userStateViewModel,
loginUser: loginUser
}
})();
var viewModel1 = kendo.observable({
label: 'ViewModel1',
isLoggedInVM1: function() {
return userState.userStateViewModel.get("isLoggedIn");
},
logIn: function ()
{
//when calling LoginUser from here, the binding is not updated, even though the value is changed (true)
userState.loginUser();
alert('After Login viewModel1.isLoggedInVM1() = ' + viewModel1.isLoggedInVM1() + ' but the binding has not updated');
}
});
alert('Value onLoad = ' + viewModel1.isLoggedInVM1());
//If you uncomment this and call LoginUser from here then afterwards the binding changes to true, but not if you call it from within ViewModel1
//userState.loginUser();
kendo.bind($("#testForm"), viewModel1);
When I call userState.loginUser() to change the value of isLoggedIn in userStateViewModel it does not update. Run and click on the button to see the problem - the binding does not reflect the updated value (but the alert box does). Any help appreciated, thank you.
Note: This is en extension of an earlier question which got me a bit further.
The problem is that userState is a simple object, not an ObservableObject. Because of this, the change event of the userStateViewmodel observable does not trigger the change event for viewmodel1 and the view doesn't update.
You can remedy this by making userState a property of viewModel1, so it is wrapped in an observable (or you could wrap your return object in the IIFE in an observable):
var viewModel1 = kendo.observable({
label: 'ViewModel1',
userState: userState,
isLoggedInVM1: function() {
return userState.userStateViewModel.get("isLoggedIn");
},
logIn: function ()
{
userState.loginUser();
}
});
Take a look at this demo; try commenting the userState property and you'll see the difference.
I have the following controller in ExtJs:
Ext.define('FileBrowser.controller.BrowserController', {
extend: 'Ext.app.Controller',
views: ['browser.tree_dir', 'browser.grid_file'],
stores: ['store_dir', 'store_file'],
init: function () {
this.control({
'window > tree_dir': {
itemclick: {
fn: function (view, record, item, index, event) {
if (record.isLeaf() == false) {
Ext.getStore('store_file').load({
params: {
dir: record.data.id
}
});
var parentOfCurrentFiles = record.data.id
nodeId = record.data.id;
htmlId = item.id;
var grid_view = this.getView('browser.grid_file');
var grid_view_v = grid_view.getView();
grid_view_v.refresh();
}
}
}
}
});
},
onPanelRendered: function () {
console.log('The panel was rendered');
}
});
If you notice under 'itemclick' I am trying to refresh one of my views, my approach is not working. Can anyone explain to me how I can refresh the view? Thank you.
Replace var grid_view= this.getView('browser.grid_file'); with var grid_view= this.getView('browser.grid_file').create(); to get a real instance (as I already told you, getView() only return the view config, not a instance!) or if you have already created that grid and only one instance exist use the xtype along with a component query to receive it var grid_view=Ext.ComponentQuery('grid_file')[0]
Now to the refresh()
Basically you never need to call this method cause your grid is bound to a store and any change made on this store is directly reflected to your grid.
I would also recommend you to store view instances when creating them instead of using queries or directly use the ref property and let ExtJS do the work for you. The last one will the best solution you I guess... Take a look at ref's within the API examples and give it a try.
So what you are trying to do is, load the store and have the data reflect once you refresh the grid_view...?
In that case, you haven't done a setStore() to the grid, or if you have done that elsewhere, you are't doing a setData() to the store. Also you should call the refresh on the grid.