I got a question about Backbone, how is it possible to set all attributes of a model to empty?
unsetmodel.unset(attribute, [options])
Remove an attribute by deleting it from the internal attributes hash. Fires a "change" event unless silent is passed as an option.
But this is only meant for unsetting individual properties one by one.
Anyone an idea?
Gretz,
From Backbone site:
clearmodel.clear([options])
Removes all attributes from the model, including the id attribute.
Fires a "change" event unless silent is passed as an option.
So I would do something like:
myModel.clear();
If you want to keep the attributes, why not iterate through all of them and set them manually?
$.each(this.model.attributes, function(index, value){
// set them manually to undefined
});
I know this is an old post, but I recently came across a similar issue - mainly, that if you do unset one-by-one, you get multiple change events, with the model in an intermediate state for each one. To allow this to happen with the appropriate change events fired afterwards, you would have to unset them silently one-by-one, then manually fire change events for each one after the unsets. However, if you look at the Backbone code, you'll see that the unset method is really just a call to set, with {unset:true} in the options. So you should be able to do this instead:
model.set({ attr1: undefined, attr2: undefined, attr3: undefined }, { unset: true })
I haven't tried it in practice, but it should definitely work in theory. You would get a series of change events for each attribute, after all of the unsets have completed. This approach is going a little outside the recommended path, since it uses unexposed logic from the Backbone source, but since this particular code hasn't changed in a few years (and actually appeared to be supported as a set option before that), it should be safe to use and continue using.
There isn't a built-in method to set all properties undefined, while keeping the attributes keys. Good news is that you can easily build one yourself with a underscore one-liner:
Backbone.Model.prototype.clearValues = function(options) {
this.set(_.object(_.keys(this.attributes), []), options);
}
All models will then have a clearValues method:
var model = new Model({
id:1,
foo:'foo',
bar:'bar'
});
model.clearValues();
console.log(model.toJSON()); //-> {id: undefined, foo: undefined, bar: undefined}
Related
Instructions:
make this code work without modifying snippet it in any way.
Use only plain old JavaScript and no third-party libraries.
Write new code that enables the code below to work properly.
Hint: Feel free to extend native objects... even though it's typically a bad practice.
// Start with an object, any object
var myObject = {};
// Register an event on your object using
// an `on` method
myObject.on('myEvent', function(data) {
// Log the data passed to the callback
console.log(data);
});
// Trigger the event using a `trigger` method.
// Include some data when you trigger the event.
myObject.trigger('myEvent', {
company: 'ABC Corp',
location: 'WTC Bangalore, IN',
website: 'http://abc.co'
});
// Register a different event
myObject.on('yourEvent', function() {
console.log('yourEvent fired');
});
// Trigger the new event
myObject.trigger('yourEvent');
// Trigger all existing events using a special
// "star" identifier.
myObject.trigger('*');
// Remove one event by name
myObject.off('myEvent');
// Since we've removed the event, this should
// do nothing
myObject.trigger('myEvent');
// Remove all existing events
myObject.off();
// Since we've removed all events, this should
// do nothing
myObject.trigger('*');
Everything else went well. I'm unable to get "arguments" while implementing myObject.trigger("*"); unable to read arguments object / parameters while implementing "*" and hence throw undefined.
My JSFiddle
Disclaimer
I obviously dont know what school you go to or anything, but please don't fool yourself trying to fool your teachers. With a few simple questions they'll know if you understand the material or not, and if you show up with a good answer but no knowledge to back it up, they will know what's up. I'm not accusing you of this, just a friendly word of advice of someone who has had good connections with his teachers after graduating last year ;)
So, how do we do this? Basically, you will have to add some functionality to the prototype of object, at least if you want this to affect all objects made afterwards. You can always create your own class and add the function to that prototype if you only want that class to have this functionality.
We need 3 functions added to the prototype, on, off and trigger of course. On top of that we add one extra property called events, initially an empty object.
You can look at the raw code for all these in the jsfiddle, I will only go through the structure and logic of the code here.
events will hold all the handlers (functions) associated with each event. When adding an event for the first time, we add a eventName property to the events object, the value for this property is initially an empty array.
on will find (or create) the array linked to eventName in events, and push the function into the array (note we do not call the function at this time, we simply store the reference to the function in the array).
off will iterate the array of eventName, and if it finds the same function (note the ===), remove it from the array.
trigger will iterate the array of eventName and call each function. Note that the function is called with the this keyword in the function set to the object, and with the same parameters as the trigger function was called (except eventName, the first parameter, which is filtered out). Yes that means you can pass as many parameters as you want to trigger(), and they will all be passed to each handler.
I won't go into detail what things like splice, slice, ===, arguments and apply do exactly, I'm sure you can find more and better information about that elsewhere on the world wide interwebs.
There's a lot more you can do for this, like making the events object invisible through some nice uses of scoping, but that wasn't part of the question so I didn't bother with that.
If you have any more questions after looking through this, feel free to ask. I also didn't test it extensively so if you find any bugs, let me know.
EDIT: I didn't read through the comments at first, but I now also added support for the '*' wildcard. Basically the functions now check for the wildcard and will iterate all eventNames on the event object when removing or triggering. You can also remove all functions for an event by not giving a function or by giving the same wildcard, but with an eventName.
EDIT2: had some bugs running the teacher's code, realized I forgot to check for hasOwnProperty while iterating. Look that one up, it's very important when working with prototypes!
I now put in the teacher's code in my jsfiddle, to show you that it works :)
jsfiddle with own code
jsfiddle with teacher code
EDIT3 - about the 'undefined' log.
The teacher's code calls .trigger 5 times, and you should see 4 console logs and as far as I can tell, they are all correct.Let me run through each trigger, and the subsequent console logs.
You add a handler to myEvent, which logs the first parameter
You trigger myEvent, with parameter => The parameter (the object), is
logged.
You add a handler to yourEvent, which logs a hardcoded
string.
You trigger yourEvent, no parameter => The hardcoded string is logged'
You trigger * with no parameter, all handlers run => undefined is logged, since no parameters were given, data in myEvent's handler is undefined. The hardcoded string is also logged
You remove the myEvent handler, trigger myEvent and confirm no functions are called
You remove all event handlers, trigger * and confirm no functions are called from any events.
I honestly don't know what you expected to happen on step 5, since you give no parameter, the data is assigned undefined, that's intended behaviour.
If you want to merge the data given in step 2 so it remains on the object, then instruct so in your handler. (for example, iterate all properties of data and add them to this, then log this). Right now you simply pass it data, it gets logged, and then thrown away. You can also add a parameter in step 5, and then all handlers will receive it (including the yourEvent handlers, but that one doesn't assign nor use it).
document.getElementById("myBtn").addEventListener("click", displayDate);
Say I have an object similar to the following:
Cars = {
"car_one": {
data: {
make: "Ford",
model: "Festiva"
},
img: "car_one.jpg"
},
"car_two": {
data: {
make: "Chevy",
model: "Pinto",
color: "Green"
},
img: "car_two.jpg"
}
...and so on...
}
I am hoping to find a way to monitor just the data property (and all sub-properties) for changes, then be able to setup a function to fire.
I have been looking at Object.watch() but testing seems to show that I would need to have it manually setup to check each property of Cars.{some_car}.data which (as shown in my example) isn't a constant set. I guess I'm hoping there's a less complex method that I'm just not aware of.
Depending on how much control you have over how its called, you could make an accessor function. The function could take an object as an argument, and set that object as the current value if the argument is set. For your use you can add a trigger of some type here. If no argument is passed it would return the current value.
Thats the type of API that knockout uses to great effect.
In modern browsers, getters and setters make this easy. For older values you could have an accessing property and then another property that serves as the holder for the data.
Unfortunately, as of today the easiest way would probably be to loop over the properties in data and call Object.watch for each of them. However, we might have Object.observe at the rescue eventually. If you are looking for a cross-browser Obect.watch implementation, have a look here.
Other alternatives would be to wrap your data in an observable object that allows to modify the data through a generic set method that would fire events when some properties are modified or to have some property watching task that would execute at a specific interval or triggered by specific actions that would check the object for changes. However, these alternatives are not any simpler.
Backbone models .get() and .set() routines are fine if your values are simple attributes. I have inherited an app however where the values are also JSON objects.
i.e. model.get("key") returns
{start:
{top:5, bottom:6},
end:{...}
}
etc. Setting any of these values using .set() is a PITA because you have to pull out the old value, make the change you want, and punt it back in again. i.e. if I want bottom = 7, you can't just do .set("start.bottom",7). I need to do something like:
var tempValue = model.get("start");
tempValue.bottom = 7;
model.set("start",tempValue)
Models have an attributes property, where you can do model.attributes.start.bottom = 7. Now this presumably doesn't fire any event handlers, because looking at the Backbone code, that is done in set.
So two questions:
If I don't need the change handlers to fire, is there anything wrong
with setting attributes directly.
Is there anyway of firing the
change handlers manually after setting attributes?
The Backbone changelog says - "The Model#change method has been removed, as delayed attribute changes are no longer available" -but I'm not entirely sure why this would be. It sounds useful.
Related to this: I'm also trying to parse a HTML form into the model. I've been trying to use ModelBinder, but it can't handle nested JSON. e.g. <input type="text" name="start.top">
Edit:
I've just realised you can do model.set({}) with an object. e.g. model.set({start :{top:7}}); but it's a bit clunky
If I don't need the change handlers to fire, is there anything wrong with setting attributes directly.
I think that this may answer your question:
Backbone.js get and set nested object attribute
Is there anyway of firing the change handlers manually after setting attributes?
The set method (without silent option) triggers 2 events: "change" and "change:attributeName".
If you need it, you can trigger them manually by invoking:
model.trigger("change change:attributeName")
I know it's not possible to bind to all DOM events and I know you can bind to multiple events by supplying a space-separated list.
But is it possible to bind to all custom events (preferably filtered by a wildcard pattern like 'abc*' or name-space)?
Edit:
To clarify, I have created some custom widgets that respond to some custom events. For example, they all handle an event called stepReset and resets their internal models.
After I've written these, I realized events don't bubble down, so the call $(body).trigger('stepReset') basically does nothing. As a result, I am considering adding an umbrella event handler on all widgets' parent elements to propagate all relevant events down.
(I know this is not an elegant solution, but I forgot to tag elements with handlers with a common class, so there's no easy way to use select them all.)
With regards to your upcoming edit, you can retrieve all bound events by accessing the object's data:
var boundEvents = $.data(document, 'events');
From here, you can iterate over the resulting object and check each property for your chosen wildcard character, or iterate over that property's array elements and check the namespace property of each.
For instance,
$.each(boundEvents, function () {
if (this.indexOf("*")) // Checks each event name for an asterisk *
alert(this);
// alerts the namespace of the first handler bound to this event name
alert(this[0].namespace);
});
If I understood you correctly, you can iterate over the special events object to get a list of custom events (including those specified in the jQuery source code). Here's an ES5 example, you will need to adapt it yourself for older browsers or use a polyfill for Object.keys:
var evts = Object.keys(jQuery.event.special).join(" ");
$("#myDiv").on(evts, function (e) {
// your code here
});
I'm still learning the proper usage of Knockout and I've found myself quickly getting away from ever typing ko.observable when setting up my viewmodel and instead just defining an object literal and passing it through the mapping plugin with something like
var viewModel = ko.mapping.fromJS(data);
or at the very least, something along the lines of stuffing all of my data into an attribute on the viewModel like so
var viewModel = {
... events etc ... ,
"data": ko.mapping.fromJS(data)
}
To be honest, the main reason I've been doing this is to get around having to type ko.observable and ko.observableArray repetitively. I'm just trying to figure out if this is a good approach and if there are any downsides to dropping the specific var x = ko.observable() declaration all together. Also, I'm doing this all on load, not in response to any ajax call etc, which from what I can tell, is what the mapping plugin was designed for.
In your work with knockout, do you still declare the observables manually, one by one, or have you gone with the mapping.fromJS method that I use? Are there any specific downsides to using the mapping plugin so frequently like this?
Edit:
Specific Example
In this article, Steve sets up his viewModel by doing
var initialData = [ { ... } , { ... } ]; // json from the serializer
var viewModel = {
gifts : ko.observableArray(initialData)
};
Normally, I'd just use ko.mapping.fromJS for this situation as well, specifically to make sure the objects within the array are turned into observables as well. Looking at what he did, my approach seems like its overkill and adds a bit of unnecessary overhead.
After using Knockout for a little longer, I've noticed that the mapping plugin has some additional options that give you much more fine grained control over the mapping process.
Control type and amount of properties generated
There are several ways to accomplish this, and I'll go over some, but the end result is that you end up with a lighter result from the mapping plugin because everything isn't observable.
Basically you leave everything that you don't think will change, as a normal property and only make observables out of the specific items that you want to observe.
Make mapping omit certain properties
You can make the mapping plugin omit properties entirely from the end result by specifying things like ignore or include. Both of these accomplish the same thing, just in opposite ways.
Note: Samples are from the knockout.js mapping plugin documentation, comments added by me
Mapping Plugin Argument: include
The following snippet will omit all properties from the source object other than those passed in via the include argument.
// specify the specific properties to include as observables in the end result
var mapping = {
// only include these two properties
'include': ["propertyToInclude", "alsoIncludeThis"]
}
// viewModel will now only contain the two properties listed above,
// and they will be observable
var viewModel = ko.mapping.fromJS(data, mapping);
Mapping Plugin Argument: ignore
If you want to only omit certain properties from the source object, use the ignore argument as shown below. It will make observables from all properties in the source object except for the specified properties.
// specify the specific properties to omit from the result,
// all others will be made observable
var mapping = {
// only ignore these two properties
'ignore': ["propertyToIgnore", "alsoIgnoreThis"]
}
// viewModel will now omit the two properties listed above,
// everything else will be included and they will be an observable
var viewModel = ko.mapping.fromJS(data, mapping);
Control what properties are or are not made observable
If you need to include properties but you don't think that they will need to be made observable (for whatever reason), the mapping plugin has something that can help.
Mapping Plugin Argument: copy
If you want the mapping plugin to simply copy the plain properties and not make them observable, use this argument, as shown below.
// tell the mapping plugin to handle all other properties normally,
// but to simply copy this property instead of making it observable
var mapping = {
'copy': ["propertyToCopy"]
}
var viewModel = ko.mapping.fromJS(data, mapping);
Gain complete control over the mapping process
If you want to have 100% control over what is created in the mapping process, including the ability to put closures and subscriptions in your objects, then you want to use the "create" option.
plain result with calculated properties
Here is an example where I was mapping data from an ajax call to an object with a results property. I didn't want anything observable and I just wanted a simple generated property that would be made of the other simple properties on the object. Maybe not the most compelling example but it demonstrates the functionality.
var searchMappingConfig = {
// specific configuration for mapping the results property
"results": {
// specific function to use to create the items in the results array
"create": function (options) {
// return a new function so we can have the proper scope/value for "this", below
return new function () {
// instead of mapping like we normally would: ko.mapping.fromJS(options.data, {}, this);
// map via extend, this will just copy the properties from the returned json element to "this"
// we'll do this for a more light weight vm since every last property will just be a plain old property instead of observable
$.extend(this, options.data);
// all this to add a vehicle title to each item
this.vehicleTitle = this.Year + "<br />" + this.Make + " " + this.Model;
}, this);
};
}
}
}
subscriptions and closures and mapping, oh my
Another situation is if you want closures and subscriptions in your result. This example is too long to be included in its entirety but its for a vehicle make/model hierarchy. I wanted all the models (children) for a given make (parent) to be un-enabled if the model was un-enabled and I wanted this to be done with a subscription.
// here we are specifying the way that items in the make array are created,
// since makes has a child array (Models), we will specify the way that
// items are created for that as well
var makesModelsMappingConfig = {
// function that has the configuration for creating makes
"create": function (options) {
// return a new function so we can have the proper
// scope/value for "this", below
return new function () {
// Note: we have a parent / child relationship here, makes have models. In the
// UI we are selecting makes and then using that to allow the user to select
// models. Because of this, there is going to be some special logic in here
// so that all the child models under a given make, will automatically
// unselect if the user unselects the parent make.
// make the selected property a private variable so it can be closure'd over
var makeIsSelected = ko.protectedComputed(false);
// expose our property so we can bind in the UI
this.isSelected = makeIsSelected;
// ... misc other properties and events ...
// now that we've described/configured how to create the makes,
// describe/configure how to create the models under the makes
ko.mapping.fromJS(options.data, {
// specific configuration for the "Models" property
"Models": {
// function that has the configuration for creating items
// under the Models property
"create": function (model) {
// we'll create the isSelected as a local variable so
// that we can flip it in the subscription below,
// otherwise we wouldnt have access to flip it
var isSelected = ko.protectedComputed(false);
// subscribe to the parents "IsSelected" property so
// the models can select/unselect themselves
parentIsSelected.current.subscribe(function (value) {
// set the protected computed to the same
// value as its parent, note that this
// is just protected, not the actual value
isSelected(value);
});
// this object literal is what makes up each item
// in the Models observable array
return {
// here we're returning our local variable so
// we can easily modify it in our subscription
"isSelected": isSelected,
// ... misc properties to expose
// under the item in the Model array ...
};
}
}
}, this);
};
}
};
All in all, what I've found is that you rarely need 100% of an object that you'd pass to the plugin and you rarely need 100% of it to be observable. Dig in with the mapping configuration options and create all sorts of complex and simple objects. The idea is to only get everything you need, nothing more or less.
My suggestion to you would the same another questioned I just answered at https://stackoverflow.com/questions/7499133/mapping-deeply-hierarchical-objects-to-custom-classes-using-knockout-mapping-plug.
Your reasoning for using mapping plug-in is reasonable and the one that I use. Why type more code than you have to?
In my experience with knockout (all of 4 months), I've found that the less I do manually and let the knockout routines do their thing, the better my apps seem to run. My suggestion is try the simplest approach first. If it doesn't meet your needs, look at how the simple approach is doing it's "thing" and determine what has to change to meet your needs.
Allen, my recent learning experience with Knockout.js has been similar to yours. We work with a deep hierarchical object graph from the server and I have defined explicit instantiable view model functions which preserve the basic structure of it.
I began by defining each property explicitly as an observable on the relevant view model, but that quickly got out of hand. Also, a major reason for switching to using the mapping plugin was that we have to do frequent Ajax posts of the graph back to the server where it is merged with the persisted version, then validated on the server in such a way that numerous properties can change and collections be modified, and a new instance returned as the Ajax result where it has to be re-merged with the client representation. That became seriously difficult, and the mapping plugin helped big time by allowing the specification of identifiers for resolving adds / deletes / updates and to remap an updated graph onto the original.
It also helped in the original graph creation through the use of the "create" option for sub view models. In each view model constructor I receive a reference to the parent view model plus the data with which to construct the child view model, then create further mapping options to create grandchildren from the passed-in child data.
The only (slight) downside I recently found, as detailed in this question, is that when doing ko.mapping.toJSON it doesn't hook into any toJSON overrides you may have defined on the prototypes of your view models in order to exclude properties from serialization. I have been able to get around that by specifying ignore options in the unmapping, as recommended by Ryan Niemeyer in that post.
So in summary, I'll definitely be sticking with the mapping plugin. Knockout.js rules.
A simpler but help-full add-on could be knockout-data-projections
Currently, it does not handle js to viewmodel mappings, but it handles quite well view model to JS mappings.