Get component in event handler like config functions do - javascript

What's the correct approach to accessing a component in an event handler? Or, what should I be doing instead?
I have a UI section that is essentially a div-button. When the user clicks it, then the "button" is replaced with an input. When the user is finished inputting, it goes back to a "button".
Because I'm porting this from Backbone, I was using jQuery to flip visibility on the two pieces and set focus on the input. After reading about how config functions in components are given the component, I was wondering if I should be given the component's DOM element, or take another approach entirely (maybe conditional within m() functions?).
{
controller: function () {
this.clickPlus = () => {
$('#newActivityPlusIcon').hide()
$('#newActivityPlaceholder').css('background-color', 'lightgray')
const $newActivityInput = $('#newActivityInput')
$newActivityInput.show()
$newActivityInput.focus()
}
this.keyUpInput = (event) => {
//If ESC key pressed
if (event.keyCode === 27) {
const $newActivityInput = $('#newActivityInput');
$newActivityInput.hide()
$newActivityInput.val('')
$('#newActivityPlaceholder').css('background-color', '')
$('#newActivityPlusIcon').show()
}
}
},
view: (ctrl) => {
return m('section', {id: 'newActivity'}, [
m('article', {id: 'newActivityPlaceholder', class: 'activityBox', onclick: ctrl.clickPlus}, [
m('span', {id: 'newActivityPlusIcon'}, '+'),
m('input', {id: 'newActivityInput', placeholder: 'type name', onkeyup: ctrl.keyUpInput}),
])
])
}
}

The crucial practical difference between Mithril & Backbone components is that Mithril views are written in Javascript. Some of the old best practices of web MVC are turned on their head as a result of the opportunities this opens up:
You can easily express conditional logic in views themselves. As Bryce shows, ternary operations ( condition ? true : false ) are a great way of making a view self-complete: in other words, the view function can express all possible states for the DOM, and everything it needs to do.
You can also define event handlers and element bindings in the view itself, meaning you no longer need the controller to be aware of the structure of the view, or define view logic. This enables you to write controllers whose sole function is to define the state and holistic actions of a component.
The special config property is only necessary if you absolutely need to pass a reference to a DOM element to the controller or another component - which is rarely the case - or to trigger special logic like animation the first time the element is rendered. In this case, it isn't necessary.
In the code below, I've managed to isolate 2 properties, input and active, and one action, cancel, that resets those properties. These describe all the state the component needs. The rest is purely the view's concern.
The view decides how to read from and write to these properties as dictated by the concerns of the DOM API. For example, how to read an input's value is a concern of the input itself: likewise, determining that the keyCode 27 signifies 'esc' is DOM logic and can be kept separate from the controller logic of changing state.
Separating these concerns becomes useful when UX and business requirements change: as an example, I've taken the arbitrary decision to make clicking off or tabbing away from the input also clear the input and loose active state. This doesn't require adding an extra action in the controller - we bind the same cancel function to the onblur event handler.
Other small tweaks:
Expressing the view as an arrow function looses the return keyword, which IMO makes it easier to read the view as a single declarative statement.
We can loose the IDs, since we no longer need to reference the view from the controller: all our state bindings are made directly in the view itself.
Static attributes like class and placeholder can be expressed directly in the selector string. This makes it clear that these attributes are always the same: now the DOM attributes object is only invoked for dynamic code - functions and conditional properties.
Array brackets to group children are redundant: you can simply insert the children directly one after the other.
https://jsbin.com/xopinoy/edit?js,output
{
controller: function () {
this.active = m.prop( false )
this.input = m.prop( '' )
this.cancel = () => {
this.active( false )
this.input( '' )
}
},
view: ctrl =>
m( 'section',
m( 'article.activityBox', {
onclick: event =>
ctrl.active(true),
style : {
background :
ctrl.active()
? 'lightgrey'
: ''
}
},
ctrl.active()
? m( 'input[placeholder="type name"]', {
value: ctrl.input(),
oninput: event =>
ctrl.input( event.target.value ),
onkeyup: event => {
if( event.keyCode === 27 )
ctrl.cancel()
},
onblur: ctrl.cancel
} )
: m( 'span', '+' )
)
)
}

Personally, I would expect to go with a conditional which means that the DOM only contains the element that's currently pertinent.
A rough JSBin example of how I think I'd structure this: http://jsbin.com/razisicelo/edit?html,js,output
The use of ctrl.inputValue is just illustrative.

Related

How to communicate between Web Components (native UI)?

I'm trying to use native web components for one of my UI project and for this project, I'm not using any frameworks or libraries like Polymer etc. I would like to know is there any best way or other way to communicate between two web components like we do in angularjs/angular (like the message bus concept).
Currently in UI web-components, I'm using dispatchevent for publishing data and for receiving data, I'm using addeventlistener.
For example, there are 2 web-components, ChatForm and ChatHistory.
// chatform webcomponent on submit text, publish chattext data
this.dispatchEvent(new CustomEvent('chatText', {detail: chattext}));
// chathistory webcomponent, receive chattext data and append it to chat list
this.chatFormEle.addEventListener('chatText', (v) => {console.log(v.detail);});
Please let me know what other ways work for this purpose. Any good library like postaljs etc. that can easily integrate with native UI web components.
If you look at Web Components as being like built in components like <div> and <audio> then you can answer your own question. The components do not talk to each other.
Once you start allowing components to talk directly to each other then you don't really have components you have a system that is tied together and you can not use Component A without Component B. This is tied too tightly together.
Instead, inside the parent code that owns the two components, you add code that allows you to receive events from component A and call functions or set parameters in Component B, and the other way around.
Having said that there are two exceptions to this rule with built in components:
The <label> tag: It uses the for attribute to take in an ID of another component and, if set and valid, then it passes focus on to the other component when you click on the <label>
The <form> tag: This looks for form elements that are children to gather the data needed to post the form.
But both of these are still not TIED to anything. The <label> is told the recipient of the focus event and only passes it along if the ID is set and valid or to the first form element as a child. And the <form> element does not care what child elements exist or how many it just goes through all of its descendants finding elements that are form elements and grabs their value property.
But as a general rule you should avoid having one sibling component talk directly to another sibling. The methods of cross communications in the two examples above are probably the only exceptions.
Instead your parent code should listen for events and call functions or set properties.
Yes, you can wrap that functionality in an new, parent, component, but please save yourself a ton of grief and avoid spaghetti code.
As a general rule I never allow siblings elements to talk to each other and the only way they can talk to their parents is through events. Parents can talk directly to their children through attributes, properties and functions. But it should be avoided in all other conditions.
Working example
In your parent code (html/css) you should subscribe to events emited by <chat-form> and send event data to <chat-history> by execute its methods (add in below example)
// WEB COMPONENT 1: chat-form
customElements.define('chat-form', class extends HTMLElement {
connectedCallback() {
this.innerHTML = `Form<br><input id="msg" value="abc"/>
<button id="btn">send</button>`;
btn.onclick = () => {
// alternative to below code
// use this.onsend() or non recommended eval(this.getAttribute('onsend'))
this.dispatchEvent(new CustomEvent('send',{detail: {message: msg.value} }))
msg.value = '';
}
}
})
// WEB COMPONENT 2: chat-history
customElements.define('chat-history', class extends HTMLElement {
add(msg) {
let s = ""
this.messages = [...(this.messages || []), msg];
for (let m of this.messages) s += `<li>${m}</li>`
this.innerHTML = `<div><br>History<ul>${s}</ul></div>`
}
})
// -----------------
// PARENT CODE
// (e.g. in index.html which use above two WebComponents)
// Parent must just subscribe chat-form send event, and when
// receive message then it shoud give it to chat-history add method
// -----------------
myChatForm.addEventListener('send', e => {
myChatHistory.add(e.detail.message)
});
body {background: white}
<h3>Hello!</h3>
<chat-form id="myChatForm"></chat-form>
<div>Type something</div>
<chat-history id="myChatHistory"></chat-history>
+1 for both other answers, Events are the best because then Components are loosly
coupled
Also see: https://pm.dartus.fr/blog/a-complete-guide-on-shadow-dom-and-event-propagation/
Note that in the detail of a Custom Event you can send anything you want.
Event driven function execution:
So I use (psuedo code):
Elements that define a Solitaire/Freecell game:
-> game Element
-> pile Element
-> slot Element
-> card element
-> pile Element
-> slot Element
-> empty
When a card (dragged by the user) needs to be moved to another pile,
it sends an Event (bubbling up the DOM to the game element)
//triggered by .dragend Event
card.say(___FINDSLOT___, {
id,
reply: slot => card.move(slot)
});
Note: reply is a function definition
Because all piles where told to listen for ___FINDSLOT___ Events at the game element ...
pile.on(game, ___FINDSLOT___, evt => {
let foundslot = pile.free(evt.detail.id);
if (foundslot.length) evt.detail.reply(foundslot[0]);
});
Only the one pile matching the evt.detail.id responds:
!!! by executing the function card sent in evt.detail.reply
And getting technical: The function executes in pile scope!
(the above code is pseudo code!)
Why?!
Might seem complex;
The important part is that the pile element is NOT coupled to the .move() method in the card element.
The only coupling is the name of the Event: ___FINDSLOT___ !!!
That means card is always in control, and the same Event(Name) can be used for:
Where can a card go to?
What is the best location?
Which card in the river pile makes a Full-House?
...
In my E-lements code pile isn't coupled to evt.detail.id either,
CustomEvents only send functions
.say() and .on() are my custom methods (on every element) for dispatchEvent and addEventListener
I now have a handfull of E-lements that can be used to create any card game
No need for any libraries, write your own 'Message Bus'
My element.on() method is only a few lines of code wrapped around the addEventListener function, so they can easily be removed:
$Element_addEventListener(
name,
func,
options = {}
) {
let BigBrotherFunc = evt => { // wrap every Listener function
if (evt.detail && evt.detail.reply) {
el.warn(`can catch ALL replies '${evt.type}' here`, evt);
}
func(evt);
}
el.addEventListener(name, BigBrotherFunc, options);
return [name, () => el.removeEventListener(name, BigBrotherFunc)];
},
on(
//!! no parameter defintions, because function uses ...arguments
) {
let args = [...arguments]; // get arguments array
let target = el; // default target is current element
if (args[0] instanceof HTMLElement) target = args.shift(); // if first element is another element, take it out the args array
args[0] = ___eventName(args[0]) || args[0]; // proces eventNR
$Element_ListenersArray.push(target.$Element_addEventListener(...args));
},
.say( ) is a oneliner:
say(
eventNR,
detail, //todo some default something here ??
options = {
detail,
bubbles: 1, // event bubbles UP the DOM
composed: 1, // !!! required so Event bubbles through the shadowDOM boundaries
}
) {
el.dispatchEvent(new CustomEvent(___eventName(eventNR) || eventNR, options));
},
Custom Events is the best solution if you want to deal with loosely coupled custom elements.
On the contrary if one custom element know the other by its reference, it can invoke its custom property or method:
//in chatForm element
chatHistory.attachedForm = this
chatHistory.addMessage( message )
chatHistory.api.addMessage( message )
In the last example above communication is done through a dedecated object exposed via the api property.
You could also use a mix of Events (in one way) and Methods (in the other way) depending on how custom elements are linked.
Lastly in some situations where messages are basic, you could communicate (string) data via HTML attributes:
chatHistory.setAttributes( 'chat', 'active' )
chatHistory.dataset.username = `$(this.name)`
I faced the same issue and as I couldn't find any fitting library I decided to write one on my own.
So here you go: https://www.npmjs.com/package/seawasp
SeaWasp is a WebRTC data layer which allows communication between components (or frameworks etc).
You simply import it, register a connection (aka tentacle ;) ) and you can send and receive messages.
I'm actively working on it so if you have any feedback /needed features, just tell me :).
For the case where the parent and child know about each other, like in a toaster example.
<toaster-host>
<toast-msg show-for='5s'>Success</toast-msg>
</toaster-host>
Lots of options but for:
Parent passing data to the child -> attributes or observedAttributes for primitives. If complex objects need to be passed either expose a function or a property aka domProperty that can be set. If a domProperty needs to react to being updated, it can be wrapped in a proxy.
Child passing data to parent -> can use events, or can query for the parent using .closest('toaster-host') and call a function or set a property. I prefer to query and call a function. Typescript helps with this type of approach.
In cases like the toaster example, the toaster-host and the toast-item will always be used together, so the argument about loose coupling is academic at best. They are different elements mainly because they have different jobs. If you wanted to swap out implementations of the toast-msg you could do that when you define the custom element, or even by changing the import statement to point to a different file.

Javascript - Mithril nested components

I am building a dashboard using mithril components. The dashboard consist of generic widget components which nest more specific components such as twitter widgets, sales widgets etc. The generic widget is agnostic of the nested component.
Now I have a requirement to compliment the generic widget from the nested component. Eg, the generic widget has a toolbar with common operations with delete & refresh, I want to add inner component specific operations such as change date range in case of sale widget, change account in case of twitter widget.
Straight forward approach to this is decompose the inner component is to 2 subcompoents a toolbar and the content, eg sales_toolbar_component & sales_content_component. Both these subcomponnets needs the same data loaded via ajax, hence it will lead to duplication. An approach is to follow Hybrid architecture (http://mithril.js.org/components.html#hybrid-architecture) where a top level component will do the ajax calls and pass data at constructor of each sub component. However in this case the parent component is generic is unaware of child data requirements
Another approach is for one sub-component to do the ajax call and notify the sibling via observer pattern, this seems quite a bit of internal coupling/dependency
Another consideration was to create a component that can have multiple views & for the parent to render each view as required using the same controller instance.Something like:
//create a component whose controller and view functions receive some arguments
var component = m.component(MyComponent, {name: "world"}, "this is a test")
var ctrl = new component.controller() // logs "world", "this is a test"
m.component(MyComponent.toolbar(ctrl));
m.component(MyComponent.content(ctrl));
None of these seems complete, is there a reference pattern I can consider?
The memoization pattern might suit you. Memoization involves wrapping a function - in this case the AJAX request - such that the first call with any given input triggers the underlying function, but its return value is stored in an internal cache; subsequent calls with the same input retrieve the cached value without touching the underlying function. Here's a simple implementation1 and a memoized AJAX requesting function that wraps Mithril's m.request:
function memoize( fn ){
var cache = {}
return function memoized( input ){
if( !( input in cache ) )
cache[ input ] = fn( input )
return cache[ input ]
}
}
var get = memoize( function( url ){
return m.request( { method : 'GET', url : url } )
} )
This way whichever components executes first will make the request; the next component (which will execute immediately afterwards) will retrieve the same value.
Regarding the idea of having a multi-view component, this isn't really any different in practical terms from your original proposal. Consider the following code:
var Wrapper = {
controller : function(){
// AJAX or whatnot
},
view : function( ctrl ){
return m( '.Wrapper',
m( Toolbar, ctrl )
m( Content, ctrl )
)
}
}
m( Wrapper, { name : 'world' }, 'This is a test' )
Here, I used the reference Wrapper instead of MyComponent, and the Toolbar and Content are just components as originally proposed. The wrapper isn't generic, but neither was MyComponent. I find that trying to reduce individual units of Mithril code to components where possible - even if you'd rather have less components in an ideal world - generally makes code much easier to maintain, because whereas you may end up with many context-specific modules instead of a few highly configurable generic modules, all of these context-specific modules are generic in the way they're called, which is much more useful in terms of code predictability.
Having said that, it would be possible to refine your idea of a pattern for passing one controller to multiple views. Again, I would reduce this pattern to component form, so that we can deal with the complications internally but expose an interface that's consistent across Mithril:
var MultiView = {
controller : function( controller ){
return new controller()
},
view : function( ctrl ){
var views = [].slice.call( arguments, 2 )
return m( 'div',
view.map( function( view ){
return view( ctrl )
} )
)
}
}
m( MultiView, function controller(){ /* AJAX or whatnot */ }, Toolbar.view, Content.view )
1 This memoization function will work for any function which accepts a single string argument, which is perfect for AJAX requests. If you ever want a more comprehensive memoization solution, check out funes.

How to ignore Angular $watch event when I myself have caused it?

I have a custom UI element with link to ngModel:
scope:
{
ngModel : "="
}
There are two ways how the attached model might change:
it is changed from outside - in this case I want to update UI of my custom element
it is changed from inside - in this case I want to ignore the changes because my UI is already up-to-date
I have a watch:
$scope.$watch("ngModel", function(newValue){
// here I have complex logic to traverse newValue
// to see if it matches my current UI state
// if it matches, then I return
// if it does not match, then I sync my UI to the newValue
});
and I have a function which pushes current UI state to the model:
function pushSelectionToModel() {
// some code
$scope.ngModel = newState;
// some code
}
Everything works fine, but in cases when user is scrolling through my UI directive fast, ngModel watch is being triggered each time. My code to detect if newValue matches my current UI state is fairly complex, thus causing performance issues.
A natural solution seems to be somehow to ignore the $watch in case when I have just called pushSelectionToModel. I cannot just disable the watch in pushSelectionToModel before update and enable after that because the watch is executed later, after pushSelectionToModel has exited.
How do I tell Angular that for some particular model assignment operation I don't want to trigger some particular $watch?
Here is the relevant simplified Plunkr example
Essentially I want to prevent updateUi() from being called twice when I click Apply button. But the example is simplified, in reality I can't directly assign or compare innerName and ngModel values because in my actual code the values differ because of some transformations.
I've sovled a similar problem, by adding the following statement:
$scope.$watch('ngModel', function(newValue){
if($scope.innerName !== newValue){
// now execute code
http://plnkr.co/edit/r9sQax4VNqBraimQi9pz
but its more of an workaround...

Working with current values of properties of the ViewModel with Knockout-Kendo.js

I recently found this great component -> Knockout-Kendo.js.
I use it to handle some behaviors with kendoComboBox.
The synchronization with the viewmodel works perfectly.
I want to listen changes of the control to execute some actions based on the current selected value.
I don't see any property that I can bind in the 'data-bind' attribute to listen changes but I know that internally, the knockout-kendo component listen changes and this is how the viewmodel is able to by sync with the control.
If I try to listen the valueChange event of the control, the problem is my eventhandler is catched before the viewmodel and while running in my eventhandler, I just have the previous value of the control using the viewmodel.
Look at this binding configuration in the component. What I understand is I'm able to use 'enabled', 'search', 'data', 'value' and any other exposed properties of the telerik control. What would be nice would be to define in data-bind attribute a property 'change' with an eventhandler linked in my viewmodel and be sure my eventhandler would be called after the internal eventhandler of the knockout-kendo component.
createBinding({
name: "kendoAutoComplete",
events: {
change: VALUE,
open: {
writeTo: ISOPEN,
value: true
},
close: {
writeTo: ISOPEN,
value: false
}
},
watch: {
enabled: ENABLE,
search: [SEARCH, CLOSE],
data: function(value) {
ko.kendo.setDataSource(this, value);
},
value: VALUE
}
});
I know I can try to modify the order of bind of events to be sure my eventhandler must be called after the synchronization of the viewmodel but I think it's a very bad practice.
Anybody have an idea how I can solve this problem with elegance?
You haven't mentioned why you want to do this. I can imagine two reasons:
To trigger some UI behavior/logic directly;
To trigger business logic (which may in turn trigger UI changes of course);
For people landing at this question with the latter case, here's an alternative solution. (This answer may not be a straight up answer to the OP's question, but seems useful enough to post it here.)
Suppose you have this basic view model:
var ViewModel = function() {
var self = this;
self.kendoObservable = ko.observable("Some text")
};
There are two ways you can indirectly respond to changes by Kendo. First, for simple cases, there's computed observables:
// Option 1, add this to ViewModel
self.dependentObservable = ko.computed(function() {
return self.kendoObservable() === "" ? "Empty" : "Not empty"; // example
});
This dependentObservable will be modified each time the kendoObservable changes. Basic stuff.
If you want to do something more complex when kendoObservable changes, e.g. do an AJAX call or whatnot, you may need a manual subscription:
// Option 2, add this to ViewModel
self.kendoObservable.subscribe(function(newValue) {
// Possibly do an AJAX call here or whatnot. Example:
alert("The new value is: " + newValue);
});
This will allow you to fire some complex logic each time the kendoObservable changes. AFAIK you need to check yourself whether the newValue is actually a changed value, at least in some versions of KO.

Four Backbone.js Model questions

I'm using a Backbone.js to keep track of the state in a visualization application.
The model has attributes such as:
indicatorX : "income"
indicatorY : "emissions"
indicatorXScale : "lin"
indicatorYScale : "log"
year : 1980
layout : {leftPanel : {[...]}, rightPanel : {[...]}}
1. What is a good way of handling "dependent attributes" in a Backbone.js model?
For example, when changing the indicatorX attribute, I'd also like the model to update the indicatorXScale attribute.
2. How can I handle "lingering" model attributes? Example:
The model contains this:
indicatorX : "income"
indicatorXScale : "log"
If only indicatorX is set on the model, the scale should be set to the default:
model.set( {indicatorX : "emissions"} )
if("indicatorX" in changedAttrs){
indicatorXScale = dataSource[indicatorX].defaultScale
}
What if however the user wants to override the default scale which is "lin" in the case of the "emissions" indicator?
model.set( {indicatorX : "emissions", indicatorXScale : log} )
As the model attribute indicatorXScale already is set to "log", the changed attribute is not recorded. How can I then make sure that the defaultScale is not loaded in this case - but instead the one passed to the model?
3. Is it a good idea to let the model use an extra attribute "action" to describe changes in the model?
In this way controllers can listen for one attribute instead of specifying handlers for combinations of attributes. These are the alternatives:
Alt 1. Controller has handlers for specific attributes:
this.model.bind("change:year", this.render);
this.model.bind("change:layout", this.updateLayout);
Alt 2. Controller has handler for model change and render() figures out what to do:
this.model.bind("change", this.render);
render() {
var changedAttributes = this.model.changedAttributes
if (a,b && c in changedAttributes) x()
if (a,d in changedAttributes) y()
}
Alt 3. Let model describe what a combination of attribute changes signify:
this.model.bind("change:action", this.render);
render() {
var changedAttributes = this.model.changedAttributes
var action = this.model.get("action")
if (action == gui_changeIndicator) x()
if (action == gui_opacity) y()
}
4. Are there any pitfalls to watch out for when using objects as attributes in a Backbone.js model?
Is it for example expensive to perform isEqual on the layout state that I try to keep in my model? Also, when setting the model, objects are passed by reference, so it better be a new object for the comparison to work?
1. What is a good way of handling "dependent attributes" in a Backbone.js model? For example, when changing the indicatorX attribute, I'd also like the model to update the indicatorXScale attribute.
IMHO, extend the model and bind into the change events. For example:
MyModel = Backbone.Model.extend({
initialize: function() {
this.bind('change:width', this.updateArea);
this.bind('change:height', this.updateArea);
this.updateArea();
},
updateArea: function () {
this.area = this.get('width') * this.get('height');
}
});
var model = new MyModel({height: 10, width: 10});
console.log(model.area); //100
model.set({width: 15});
console.log(model.area); //150
This is pretty basic, but change events are called per key and as a whole 'change'.. so you can bind into certain changes and update as necessary. If it's a large model with lots of keys that are updated intermittently this is definitely the way to go. If it's just those two keys.. well.. you could probably just bind to the regular ol' change event once.
2. How can I handle "lingering" model attributes?
Override the set method and add in some of your own code. Example:
MyModel = Backbone.Model.extend({
constructor: function (obj) {
this.touched = {}; //Set before the prototype constructor for anything being set
Backbone.Model.prototype.constructor.call(this, obj);
//set after for only things that the program has updated.
return this;
},
set: function(attributes, options) {
if(attributes.keyIWantToListenFor !== undefined && !this.touched.keyIWantToListenFor) {
console.log("keyIWantToListenFor was set, let's fire off the right methods");
this.touched.keyIWantToListenFor = true;
}
Backbone.Model.prototype.set.call(this, attributes, options);
return this;
}
});
var model = new MyModel({height: 10, width: 10});
model.set({keyIWantToListenFor: 15});
This keeps absolute "has the key been set at all" on the model. It may not be quite specific enough for your needs, but it does work.. so feel free to use and hack away at it.
3. Is it a good idea to let the model use an extra attribute "action" to describe changes in the model?
The way that the Backbone folks have it set up is that, as you already know, change:key is specifically for the change event on a certain key. By relying on a change:action you're kind of adding 'gotcha!'s to your code. I don't see how the other two methods are any better than the first, especially considering now you have logic thrown into an event listener to determine what to fire off.. instead of just attaching that code directly to the appropriate listeners. Given a choice, I'd stick with the first one - it is a clear "This key has updated, so we're going to do X". Not a "something has updated so let's go figure out what it is!" and potentially have to go through a dozen if statements or switches.
4. Are there any pitfalls to watch out for when using objects as attributes in a Backbone.js model?
Well, isEqual performs a deep comparison.. so you're running the risk of doing all of that comparison code plus the risk of recursion. So, yes, that could certainly be a pitfall if you're doing it a number of times.
The object by reference is certainly an issue - I've got a nice little hole in the wall where I've put my head through a few times wondering why something changed in a completely unrelated.. oh wait..
To remedy this a bit, you could override the get method to, in cases where it is returning an object, return something like $.extend(true, {}, this.get(key));
Also, you don't really know what exactly changed in the object based on plain Backbone. So, if you're doing lots of 'stuff' on a change (rebuilding a view, etc), you're potentially going to run into performance issues even if all you did was add another attribute to that object and it isn't used for any of said changes. (i.e. set({layout: layoutObj}) vs set({layoutPageTitle: 'blah'}) which may only update the title.. instead of causing the entire view to reload).
Otherwise, at least in the app that I'm working on, we've had no real issues with objects in backbone. They sync fairly well, and it's certainly better than .get('layout.leftPanel[0]') and having some magical translation to make that work. Just be careful of the reference part.
Hope that helps at least a little!

Categories