Does Knockout.mapping make ALL nested objects observable? - object

I am trying to map all possible nested objects of a JSON object so that each and every one is becomes an observable. I was under the impression that the use of ko.mapping.fromJS would result in all objects and their objects becoming observable. However, I am not seeing that happen.
If you look at the JSFiddle and code below you will see that the span initially displays the value "Test". My intention is for the button click to update the viewModel with the contents of stuff2, which should change the span's value to "Test2". However, the button click does not update anything.
http://jsfiddle.net/Eves/L5sgW/38/
HTML:
<p> <span>Name:</span>
<span data-bind="text: IntroData.Name"></span>
<button id="update" data-bind="click: Update">Update!</button>
</p>
JS:
var ViewModel = function (data) {
var me = this;
ko.mapping.fromJS(data, {}, me);
me.Update = function () {
ko.mapping.fromJS(stuff2, {}, windows.viewModel);
};
return me;
};
var stuff = {
IntroData: {
Name: 'Test'
}
};
var stuff2 = {
IntroData: {
Name: 'Test2'
}
};
window.viewModel = ko.mapping.fromJS(new ViewModel(stuff));
ko.applyBindings(window.viewModel);
Is it just that I have to make use of mapping options to have the nested objects be made observable? If so, what if the JSON object is so vast and complex (this one obviously isn't)? Can some recursive functionality be used to loop through each object's nested objects to make them all observable?

Modifying the Update function as below will work.
me.Update = function () {
ko.mapping.fromJS(stuff2, {}, windows.viewModel);
};

Related

React children are mutating parent state through props...I am unclear as to the cause

I have a React component that is passing an object to a child element containing an input field to be modified via a passed function. The input field is changing the state of the child element but somehow, without invoking the passed function, the props object is being modified.
this is the render function of the parent class:
render: function() {
const newTriggerClass = this.state.item.triggerID === ""
? " new-trigger"
: "";
return (
<div className={newTriggerClass}>
<div className="triggers-detail">
<section className="trigger-info group">
<TriggerInfoEntry
item={this.state.item}
triggerState={this.state.editTriggerState}
columnHeaders={this.props.columnHeaders}
hideDetail={this.hideDetail}
editAction={this.props.editAction}
editToggle={this.editToggle}
_onChange={this._onInfoChange}
createAction={this.props.createAction}
deleteAction={this.props.deleteAction}/>
</section>
<section className="trigger-actions group">
<TriggerActionEntry
item={this.state.item}
actionState={this.state.editActionState}
editToggle={this.editToggle}
editAction={this.editTriggerAction}
actionIndex={this.state.selectedActionIndex}/>
</section>
</div>
<div className={"modal-overlay" + newTriggerClass}>
</div>
</div>
);
},
and the child TriggerActionEntry is setting it's own state.item to a clone of props.item.
propTypes: {
item: React.PropTypes.object.isRequired,
editAction: React.PropTypes.func.isRequired,
editToggle: React.PropTypes.func.isRequired,
actionState: React.PropTypes.bool
},
getInitialState: function() {
return {
item: assign({testy: true}, this.props.item),
actionIndex: 0,
deleteState: false,
editState: false
};
},
when I change the state of the child component (state.item) through an input, the parent state gets changed! I am not sure of the cause. Nothing that I have read online alludes to this behavior. Any help is appreciated.
UPDATE:
#garrettmaring The code I use to change state is only designed to work on the child state which is set in the getInitialState as a clone of this.props.item through assign which is my alias for object-assign which is an Object.assign ponyfill. The form to change item is actually one more child down. Here is the render of TriggerActionEntry
<div>
<TriggerActionForm
action={this.state.item.actions[this.state.actionIndex]}
index={this.state.actionIndex}
addNewAction={this.addNewAction}
editTriggerAction={this.editTriggerAction}
deleteTriggerAction={this.deleteTriggerAction}
editState={this.state.editState}
deleteState={this.props.actionState}
toggleEdit={this.toggleEdit}
activateEdit={this.activateEdit}/>
</div>
and here is the _onChange function of the grandchild TriggerActionForm
_onChange: function (e) {
e.preventDefault();
let newAction = assign({}, this.state.action);
const keyname = e.currentTarget.dataset.keyname;
if (keyname === "minDelay") {
newAction[keyname] = e.currentTarget.value;
} else {
newAction.args[keyname] = e.currentTarget.value;
}
this.setState({action: newAction});
if (!this.props.editState) {
this.props.activateEdit();
}
},
even when I unmount that component, the TriggerActionEntry state.item is modified, and shows up in the modified state when the component is mounted again...
UPDATE 2: OK, the problem is that neither Object.assign nor object-assign deeply clones objects. The classic! The actions were objects that were nested within item and rather than being cloned, another reference to the nested object was being created. Thanks for the help folks.
Im currently working on the same thing . The parent pass the data as a props to the children function. (im using hook instead of class). The purpose of the child component to process the data given from the parent and export as pdf or excel. Imagine the data pass in as an object that consists multiple array of objects . Each child object have an element of proj id which i would like to remove. The moment the data pass in , i have this
enter image description here
i set state with the data pass in from parent. When i gone throught some process and remove the the projid. When i try to access with any elements in the parent function. the whole thing gone broken.

text() returning children text as well

http://jsfiddle.net/Lc5gdvge/
In the fiddle above, I've showcased how text(), returns elements text and childrens' as well. How do I avoid this and only make it return "outerdiv"?
NOTE: Just click the blue container to call function.
$(document).ready(function() {
$('.outerdiv').click(function() {
var htmlstring = $(this).text();
alert(htmlstring);
})
});
NOTE 2: This has to be without the use of ID selector.
To get the text of only the .outerDiv and not the children elements, use the following code :
$(this).clone().children().remove().end().text();
What this does is, it clones the element, removes all the children tags, and goes back to the selected tag and gets the text.
The final code should look like :
$(document).ready(function () {
$('.outerdiv').click(function () {
var htmlstring = $(this).clone().children().remove().end().text();
alert(htmlstring);
});
});

Dynamically publish attributes in Polymer

Is it possible to publish new Polymer component attributes at runtime?
<polymer-element name="dynamic-attributes">
<template></template>
<script>
Polymer('dynamic-attributes', {
ready: function(){
var attributes = new Thing().getAttributes();
this.publishAttributes(attributes); // imaginary method
},
});
</script>
</polymer-element>
More info...
I'm writing a component lib that wraps Seriously.js.
Here is an example of the ideal implementation.
<seriously-graph linear>
<seriously-source>
<img src="images/pencils.jpg">
</seriously-source>
<seriously-effect type="pixelate" pixelSize="{{pixelSize}}"></seriously-effect>
<seriously-effect type="blur" amount=".5"></seriously-effect>
<seriously-target width="411" height="425"></seriously-target>
</seriously-graph>
I have a work-around that uses MutationObserver to detect attribute changes. It works, but then I have to find a solution for serialization and make property getters/setters. It feels like I'm re-inventing the wheel (Polymer), and was hoping there was a built-in method of doing this.
Here's a Gist that does what you're looking for. Basically, it dynamically creates a concrete <polymer-element> for an effect the first time it's used, and then swaps the generic instance with a dynamically created concrete one, copying any binding declarations in the process. The source would be:
<seriously-effect type="blur" amount="{{someAmount}}"></seriously-effect>
But with Inspect Element, you'd see:
<seriously-effect-blur amount="{{someAmount}}"></seriously-effect-blur>
Yes, it is possible by assigning an attributes object to publish node. See https://www.polymer-project.org/docs/polymer/polymer.html#attributes for details.
Your method must return an object of attributes:
Polymer('dynamic-attributes', {
publish: (new Thing()).getAttributes() || {}
});
Maybe you could make a global array and use it as attribute?
<polymer-element name="app-globals" attributes="values">
<script>
(function() {
var values = {};
Polymer({
ready: function() {
this.values = values;
for (var i = 0; i < this.attributes.length; ++i) {
var attr = this.attributes[i];
values[attr.nodeName] = attr.value;
}
}
});
})();
</script>
</polymer-element>
<app-globals id='myDynamicFunctions' someFunction someOtherFunction></app-globals>
<script>
//then define it
document.$.myDynamicFunctions.values.someFunction = function(){...}
</script>
I am not quite sure what you are looking for, this might give you a hint...

Knockout mapping plugin does not handle hierarchical data properly

I have hierarchical JSON structure that differs after every new JSON from the server side. Given my template, this does not adequately show model update.
After troubleshooting, I noticed the mapping plugin does not correctly map child elements(or perhaps I am doing it incorrectly)
I can also track the memory keeps growing for every update in the datamodel.
Any help will be greatly appreciated.
This simple test is up on JSFiddle http://jsfiddle.net/Bru5a/1/
Here is my view
<div id="view">
The behavior is different depending on the order you load the model.
First
Second
Third
<span data-bind="text: name"></span>
<div data-bind="if: $data.child">
<b data-bind="text: child.name"></b>
<div data-bind="if: child.sub">
<b data-bind="text: child.sub.name"></b>
</div>
</div>
</div>
Here is my Javascript:
var BaseModel = function(om) {
ko.mapping.fromJS(om, {}, this);
};
var resourceModel = null;
function applyJson(json) {
try {
if(resourceModel){
ko.mapping.fromJS(json, {} ,resourceModel);
} else {
resourceModel = new BaseModel(json);
ko.applyBindings(resourceModel, $("#view")[0]);
}
} catch(e) {
alert(e);
}
}
function loadFirst() {
var json = { "name" : "1",
"child" : {
"name": "Child One"
}
};
applyJson(json);
}
function loadSecond() {
var json = { "name" : "2" };
applyJson(json);
}
function loadThird() {
var json = { "name" : "3",
"child" : {
"name": "Child Three",
"sub" : {
"name" : "Third Sub Child"
}
}
};
applyJson(json);
}
$(document).ready(function () {
$("#second").click(function(){
loadSecond();
});
$("#third").click(function(){
loadThird();
});
$("#first").click(function(){
loadFirst();
});
});​
What is happening in your case, is that knockout is behaving as designed.
To see what is happening, add the following line inside your outer div
<div data-bind="text: ko.toJSON($root)"></div>
You can see what is being bound in your view model.
To understand what is being displayed, Watch what is happening the to the viewmodel as you select the different links. As you update your model, you will see that once a property has been created, it remains there. This is by design of how the mapper works.
Next, you have to remember that ko.applyBindings is only being called ONE time. Therefore, the binding are only being applied one time to the properties that are in existence at the time the applyBindings is called. When you add properties to your viewmodel later, they will not automatically be bound to the data-bindings.
To make this example work, you will need to re-think your view model so that all the properties are present at the time you call apply bindings.
EDIT
I've edited your fiddle to show what I was talking about at http://jsfiddle.net/photo_tom/Bru5a/5/

When to call YUI destroy?

When should destroy be called? Does it ever get called automatically by YUI lifecycle? Does the page unload cause the YUI lifecycle to call destroy on all objects created during the page processing? I have been working under the assumption that I need to make all my own calls to destroy but that gets hairy when ajax calls replace sections of code that I had progressively enhanced. For example:
<div id="replaceMe">
<table>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
</table>
<script>
YUI().use('my-lib', function(Y) {
Y.mypackage.enhanceTable("replaceMe");
});
<script>
</div>
The my-lib module basically adds a click handler and mouseover for each row:
YUI.add('my-lib', function(Y) {
function EnhancedTable(config) {
EnhancedTable.superclass.constructor.apply(this, arguments);
}
EnhancedTable.NAME = "enhanced-table";
EnhancedTable.ATTRS = {
containerId : {},
onClickHandler : {},
onMouseoverHandler : {},
onMouseoutHandler : {}
};
Y.extend(EnhancedTable, Y.Base, {
_click : function(e) {
//... submit action
},
destructor : function() {
var onClickHandler = this.get("onClickHandler"),
onMouseoverHandler = this.get("onMouseoverHandler"),
onMouseoutHandler = this.get("onMouseoutHandler");
onClickHandler && onClickHandler.detach();
onMouseoverHandler && onMouseoverHandler.detach();
onMouseoutHandler && onMouseoutHandler.detach();
},
initializer : function(config) {
var container = Y.one("[id=" + this.get("containerId") + "]");
this.set("container", container);
this.set("onMouseoverHandler", container.delegate("mouseover",
this._mouseover, "tr", this ));
this.set("onMouseoutHandler", container.delegate("mouseout",
this._mouseout, "tr", this ));
this.set("onClickHandler", container.delegate("click",
this._click, "tr", this ));
},
_mouseout : function(e) {
e.currentTarget.removeClass("indicated");
},
_mouseover : function(e) {
e.currentTarget.addClass("indicated");
}
});
Y.namespace("mypackage");
Y.mypackage.enhanceTable = function(containerId) {
var enhancedTable new EnhancedTable({containerId:containerId});
};
}, '0.0.1', {
requires : [ 'base', 'node' ]
});
The click handler would submit a request back to my application that would change the page. Do I need to remember all the enhancedTable objects and have an onunload handler call the destroy method of each? Or does the YUI framework take care of this?
The last part of this quesiton is, I also have code outside of this that replaces the whole table by replacing the content of the <div id="replaceMe">. In doing so, the script would get re-run and augment the new <table> with a new EnhancedTable. Do I need to remember the old table, and destroy it before the new table clobbers it?
Instead of setting handlers as attributes I'd store them all in an array like this:
this._handlers = [
container.delegate("mouseover", this._mouseover, "tr", this ),
container.delegate("mouseout", this._mouseout, "tr", this ),
container.delegate("click", this._click, "tr", this )
];
Then add a destructor method that does the following
destructor : function() {
new Y.EventTarget(this._handlers).detach();
}
It accomplishes the same thing but with way less work on your part!
Ideally instead of running this against each table you'd attach all your delegates to #replaceMe so that it wouldn't need to be recreated each time you changed the content, no matter where that happened from.
YUI won't automatically call .destroy() for you on unload, it will clean up DOM subs though. The above is extra credit that's really only necessary if you are going to be destroying the object yourself.

Resources