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.
Related
I found this example from Ryan Niemeyer and started to manipulate it into the way I write my own code, but then it stopped working. Can anybody tell me why?
Alternative 1 is my variant
Alternative 2 is based upon Ryans solution and does work (just comment/uncomment the Applybindings).
Why doesn´t Alternative 1 work?
My issue is with the filteredRows:
self.filteredRows = ko.dependentObservable(function() {
//build a quick index from the flags array to avoid looping for each item
var statusIndex = {};
ko.utils.arrayForEach(this.flags(), function(flag) {
statusIndex[flag] = true;
});
//return a filtered list
var result = ko.utils.arrayFilter(this.Textbatches(), function(text) {
//check for a matching genré
return ko.utils.arrayFirst(text.genre(), function(genre) {
return statusIndex[genre];
});
return false;
});
console.log("result", result);
return result;
});
I want to filter my Textbatches on the genre-attribute (string in db and the data collected from the db is a string and not an array/object)
Fiddle here: http://jsfiddle.net/gsey786h/6/
You have various problems, most of them can be simply fixed with checking your browser's JavaScript console and reading the exceptions...
So here is the list what you need to fix:
You have miss-typed Textbatches in the declaration so the correct is self.Textbatches = ko.observableArray();
You have scoping problem in filteredRows with the this. So if you are using self you should stick to it and use that:
this.flags() should be self.flags()
this.Textbatches() should be self.Textbatches()
Your genre property has to be an array if you want to use it in ko.utils.arrayFirst
Finally your Textbatch takes individual parameters but you are calling it with an object, so you need to change it to look like:
Textbatch = function(data) {
var self = this;
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
self.statuses = ko.observableArray(data.status);
self.genre = ko.observableArray(data.genre);
self.createdBy = ko.observable(data.createdBy);
};
or you can of course change the calling places to use individual arguments instead of an object.
Here is a working JSFiddle containing all the above mentioned fixes.
UPDATED: See jsfiddle link below
UPDATED Again - See Update 2
I have the following HTML:
<button type="button" data-who="Appellant" data-bind="click: showLetter, hasFlag: { value: DeterminationLettersGenerated, flag: Enum_AppealParties.Appellee, enableIfTrue: true }">View</button>
Within the showLetter function I would like to do something like this:
self.showLetter = function (model, event) {
var flagValue = $(event.target).data("bind").flag;
...
}
And by sibling, I mean siblings to the actual click event that is bound. I just need to get whatever will get me Enum_AppealParties.Appellee.
I have tried numerous combinations of ko.toJS, ko.toJSON, $.parseJSON and JSON.stringify. They always return me a string of the following with quotes or escaped quotes around it:
click: showLetter, hasFlag: { value: DeterminationLettersGenerated, flag: Enum_AppealParties.Appellee, enableIfTrue: true }
What I NEED is the above string converted to JSON so at worst I would need to do the following in code:
self.showLetter = function (model, event) {
var magicObject = SomeAwesomeAnserHere();
var flagValue = magicValue.hasFlag.flag;
...
}
UPDATE:
Re the request to see a repo of it, check out this link Fiddle
Just click on the View button within and some Alert messages will appear. The one that says "Should say Object" says it is a string. Not sure if the combinations I mention above are the way to go or what. Just want to get to each piece of the data-bind elements.
UPDATE 2:
I know KO has to be doing what I am trying to accomplish, right? So after some digging around in the KO code, I see where it is turning the data-bind string into a usable object (in this case a function.) I am close to getting it to be useful within my own bindings/functions. This does not work 100% yet. But perhaps with someone smarter than me tinkering with it...
This code is within a KO.click event like the self.showLetter above:
var rewrittenBindings = ko.expressionRewriting.preProcessBindings($(event.target).data("bind"), null);
var functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
var almost = new Function("$context", "$element", functionBody);
To access sibling bindings, you need to define a custom binding. Defining such a binding that simply wraps the click binding is pretty simple:
ko.bindingHandlers.clickFlag = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
ko.applyBindingAccessorsToNode(element, {
click: function() {
return function(model, event) {
valueAccessor().call(this, model, event, allBindings.get('hasFlag'));
}
}
}, bindingContext);
}
}
http://jsfiddle.net/mbest/9mkw067h/85/
Why not just append it to the click handler?
<button type="button" data-who="Appellant" data-bind="click: function() {showLetter($data,Enum_AppealParties.Appellee);}">View</button>
http://jsfiddle.net/9mkw067h/86/
I agree with the previous posters, though. This should be part of the model.
In this this similar-ish question, which ultimately fizzled out without a good answer:
Knockout how to get data-bind keys and value observables using element?
it became fairly clear the only way to access this info was via parsing the data-bind attribute. Here's an updated version of your fiddle showing how to parse a nested bind statement to get what you need:
http://jsfiddle.net/9mkw067h/83/
This is the code that does the parse:
self.showLetter = function (model, event) {
var binding_info = {}
var binding_attr = $(event.target).attr("data-bind")
var indent = false, indent_key = "";
$(binding_attr.split(",")).each(
function(idx, binding) {
var parts = binding.split(":")
var key = parts[0].trim()
var val = parts[1].trim()
if (val.indexOf("{") != -1) {
binding_info[key] = {}
indent = true
indent_key = key
}
if (indent == true) {
binding_info[indent_key][key] = val.replace("{", "").replace("}", "").trim()
}
else {
binding_info[key] = val
}
if (val.indexOf("}") != -1) {
indent = false
indent_key = ""
}
}
)
console.log(binding_info.hasFlag.flag)
}
At the end of that, binding_info has what you're after.
Update:
The linked question above is slightly different, in that it starts from another view model and a given DOM element, and it says, can I get the bindings for that DOM element? It rules out a custom binding. However, in this instance, custom bindings are already in use, so Michael Best's post below provides a neater answer without custom parsing code and proves my assertion incorrect that custom parsing is the only way to do it!
I want to filter a Backbone collection using _.without method.
It returns correct results (only completed Todos) in this form of invocation:
return this.without.apply(this, this.active());
but not in this one:
return _.without(this.models, this.active());
In latter statement it returns array containing ALL models from collection.
Can't I use Underscore methods directly but only through Backbone's this context?
How to make 2nd statement work?
todos.js
var app = app || {};
var Todos = Backbone.Collection.extend({
model: app.Todo,
active: function() {
return this.filter(function(todo) {
return todo.get('completed') === false;
});
},
completed: function() {
return this.without.apply(this, this.active());
// return _.without(this.models, this.active()); <--- Problem is here
}
});
app.Todos = new Todos();
ADDED LATER:
Since _.without method does not accept array as a second parameter _.difference is more suitable for my task.
return _.difference(this.models, this.active());
The problem is that you use Undersore's without method improperly. It expects scalar value for 2nd parameter, whereas you pass array.
Actually you don't need _.without at all.
Don't try to reuse your active method in completed method. This is a bad practice. completed method has to be implemented by the same approach as active.
So the code should look like this:
var Todos = Backbone.Collection.extend({
model: app.Todo,
active: function() {
return this.filter(function(todo) {
return todo.get('completed') === false;
});
},
completed: function() {
return this.filter(function(todo) {
return todo.get('completed') === true;
});
}
});
UPDATE:
Q: So why the first invocation (which uses apply) works?
A: Because apply method converts this.active() result, which is array, to list of values, which is exactly expected by _.without method. So this invocation works unlike the second one which is not equivalent to the first.
However, as mentioned above, such type of code reusing is strongly unrecommended since it obscures the code logic, apart from the double array processing overhead.
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.
Sure this is a very easy question to answer but is there an easy way to determine if any property of a knockout view model has changed?
Use extenders:
ko.extenders.trackChange = function (target, track) {
if (track) {
target.isDirty = ko.observable(false);
target.originalValue = target();
target.setOriginalValue = function(startingValue) {
target.originalValue = startingValue;
};
target.subscribe(function (newValue) {
// use != not !== so numbers will equate naturally
target.isDirty(newValue != target.originalValue);
});
}
return target;
};
Then:
self.MyProperty= ko.observable("Property Value").extend({ trackChange: true });
Now you can inspect like this:
self.MyProperty.isDirty()
You can also write some generic viewModel traversing to see if anything's changed:
self.isDirty = ko.computed(function () {
for (key in self) {
if (self.hasOwnProperty(key) && ko.isObservable(self[key]) && typeof self[key].isDirty === 'function' && self[key].isDirty()) {
return true;
}
}
});
... and then just check at the viewModel level
self.isDirty()
You can subscribe to the properties that you want to monitor:
myViewModel.personName.subscribe(function(newValue) {
alert("The person's new name is " + newValue);
});
This will alert when personName changes.
Ok, so you want to know when anything changes in your model...
var viewModel = … // define your viewModel
var changeLog = new Array();
function catchChanges(property, value){
changeLog.push({property: property, value: value});
viewModel.isDirty = true;
}
function initialiseViewModel()
{
// loop through all the properties in the model
for (var property in viewModel) {
if (viewModel.hasOwnProperty(property)) {
// if they're observable
if(viewModel[property].subscribe){
// subscribe to changes
viewModel[property].subscribe(function(value) {
catchChanges(property, value);
});
}
}
}
viewModel.isDirty = false;
}
function resetViewModel() {
changeLog = new Array();
viewModel.isDirty = false;
}
(haven't tested it - but you should get the idea)
Consider using Knockout-Validation plug-in
It implements the following:
yourProperty.isModified() - Checks if the user modified the value.
yourProperty.originalValue - So you can check if the value really changed.
Along with other validation stuff which comes in handy!
Cheers
You might use the plugin below for this:
https://github.com/ZiadJ/knockoutjs-reactor
The code for example will allow you to keep track of all changes within any viewModel:
ko.watch(someViewModel, { depth: -1 }, function(parents, child) {
alert('New value is: ' + child());
});
PS: As of now this will not work with subscribables nested within an array but a new version that supports it is on the way.
Update: The sample code was upgraded to work with v1.2b which adds support for array items and subscribable-in-subscribable properties.
I had the same problem, i needed to observe any change on the viewModel, in order to send the data back to the server,
If anyone still intersted, i did some research and this is the best solution iv'e managed to assemble:
function GlobalObserver(viewModel, callback) {
var self = this;
viewModel.allChangesObserver = ko.computed(function() {
self.viewModelRaw = ko.mapping.toJS(viewModel);
});
viewModel.allChangesObserver.subscribe(function() {
callback(self.viewModelRaw);
});
self.dispose = function() {
if (viewModel.allChangesObserver)
viewModel.allChangesObserver.dispose();
delete viewModel.allChangesObserver;
};
};
in order to use this 'global observer':
function updateEntireViewModel() {
var rawViewModel = Ajax_GetItemEntity(); //fetch the json object..
//enter validation code here, to ensure entity is correct.
if (koGlobalObserver)
koGlobalObserver.dispose(); //If already observing the older ViewModel, stop doing that!
var viewModel = ko.mapping.fromJS(rawViewModel);
koGlobalObserver = new GlobalObserver(viewModel, Ajax_Submit);
ko.applyBindings(viewModel [ ,optional dom element]);
}
Note that the callback given (in this case 'Ajax_Submit') will be fired on ANY change that occurs on the view model, so i think it's really recommended to make some sort of delay mechanism to send the entity only when the user finished to edit the properties:
var _entitiesUpdateTimers = {};
function Ajax_Submit(entity) {
var key = entity.ID; //or whatever uniquely related to the current view model..
if (typeof _entitiesUpdateTimers[key] !== 'undefined')
clearTimeout(_entitiesUpdateTimers[key]);
_entitiesUpdateTimers[key] =
setTimeout(function() { SendEntityFunction(entity); }, 500);
}
I'm new to JavaScript and the knockout framework, (only yestarday i started to work with this wonderfull framework), so don't get mad at me if i did something wrong.. (-:
Hope this helps!
I've adapted #Brett Green code and extended it so that we can have AcceptChanges, marking the model as not dirty plus having a nicer way of marking models as trackables. Here is the code:
var viewModel = {
name: ko.observable()
};
ko.track(viewModel);
http://jsfiddle.net/david_freire/3HZEu/2/
I did this by taking a snapshot of the view model when the page loads, and then later comparing that snapshot to the current view model. I didn't care what properties changed, only if any changed.
Take a snapshot:
var originalViewModel = JSON.stringify(ko.toJS(viewModel));
Compare later:
if(originalViewModel != JSON.stringify(ko.toJS(viewModel))){
// Something has changed, but we don't know what
}
Consider a view model as follows
function myViewModel(){
var that = this;
that.Name = ko.observable();
that.OldState = ko.observable();
that.NewState = ko.observable();
that.dirtyCalcultions - ko.computed(function(){
// Code to execute when state of an observable changes.
});
}
After you Bind your Data you can store the state using ko.toJS(myViewModel) function.
myViewModel.Name("test");
myViewModel.OldState(ko.toJS(myViewModel));
You can declare a variable inside your view model as a computed observable like
that.dirtyCalculations = ko.computed(function () {});
This computed function will be entered when there is change to any of the other observables inside the view model.
Then you can compare the two view model states as:
that.dirtyCalculations = ko.computed(function () {
that.NewState(that);
//Compare old state to new state
if(that.OldState().Name == that.NewState().Name()){
// View model states are same.
}
else{
// View model states are different.
}
});
**Note: This computed observable function is also executed the first time when the view model is initialized. **
Hope this helps !
Cheers!!
I like Brett Green's solution. As someone pointed out, the isDirty comparison doesn't work with Date objects. I solved it by extending the subscribe method like this:
observable.subscribe(function (newValue) {
observable.isDirty(newValue != observable.originalValue);
if (newValue instanceof Date) {
observable.isDirty(newValue.getTime() != observable.originalValue.getTime());
}
});