Get diff on rerender in React - dom

Is there a way to get the diff that React is getting of the DOM/Components? I can imagine trying to store changes as they happen so the user can 'undo' the changes [and am personally interested in the availability of the diff that is underlying the rerender (perhaps it would have the components that have changed?)].
EDIT: The undo feature was just an example. I am just interested in whether the above is possible to extract from React, since it is supposedly doing a diff of new and old tree.

I'm trying to store changes as they happen so the user can 'undo' the changes
My proposition how to accomplish this:
React render result should be based only on states. If you give two times identical state, then DOM should be identical. Possibly child components should be stateless.
componentWillUpdate method is run after changes in props and states.
You can copy and save state after any state change.
And if user 'undo' changes then set old saved state as current state.
And DOM should be identical to previous state.

Use Redux in combination with redux-undo if you want undo/redo functionality.

To answer your question: Probably, but you shouldn't do it like that
Imagine if you had the diff of the DOM, then you would have to parse the DOM and figure out which dom changes mapped to different parts of props and state.
Thats messy.
var SomeComponent = React.createClass({
onChange: function(newData){
currentState = _.clone(this.state.currentState)
previousChanges = this.state.previousChanges.concat([currentState])
this.setState({
currentState: newData,
previousChanges: previousChanges
})
},
undo: function(){
previousChanges = _.clone(this.state.previousChanges)
currentState = previousChanges.pop() // removes last item and returns it
this.setState({
currentState: currentState,
previousChanges: previousChanges
})
},
render: function(){
<div>
<SomeAwesomeThing
currentState={this.state.currentState}
onChange={this.onChange}
onUndo={this.undo}
/>
</div>
}
})
You basically have to keep a pointer to the current data, and to a list of changes, where each change can easily be reverted to.

Related

How do I properly update parent component from child form using props

I have a parent and child components
ShowreelApp.vue and Showreel.vue. I am passing properties over to the child when I create it.
<showreel-component
v-for="showreel in showreels"
v-bind="showreel"
:key="showreel.id"
#update="update"
#delete="del"
></showreel-component>
I then use the props to populate an update form in each child showreel.
<div class="showreel-input">
<label>Heading</label>
<input type="text" :value="myHeading" >
</div>
In the child form I am emiting an update event to the parent component to update the specific showreels data.
update(event) {
this.$emit('update', this.id, this.heading, this.subheading, this.youtubeid);
}
When the user clicks on the update button on the child Showreels components form I would like to send the data to the parent.
However I cannot bind the data to the child's input field as that causes the warning about mutating the property as I should not be updating the value from the child form.
One option I thought of was creating duplicates of each value in the child ... Updating them to the passed in props when its created , binding them to the form inputs... and then passing these values over to my parent using emit when updating.
But then these seems a bit convoluted to me and now I am thinking I have gone about this the wrong way. Any suggestions would be appreciated.
Mutating props from a child is usually not a very good practice but if you need it, as of version 2.3+ there is a .sync modifier. For more reference see Sync modifier
Another solution is to use event emitting or VueX. I would personally recommend Vuex but if you have relatively small application go with either sync or event emitting as you have described.
I should have mentioned that each showreel component has its own form fields and update button.
The solution I have come up with is to create a "sync" event in the parent and emit to it from the child
sync(id, property,value){
var currentShowreel = this.showreels.find(showreel => showreel.id === id);
currentShowreel[property] = value
},
The sync event calls a method in the parent which takes in and ID proprty name and property value.
This generic method is called from the child element inputs with a method
#keyup="sync('heading', $event.target.value)"
This calls a method in the child which in turn updates the parents data
},sync(property, value){
this.$emit('sync', this.id, property, value);
}
Then when the user updates I simply emit the update event to the parent. The parent grabs the particular showreel items data and updates based on the latest values.
It's working but not sure this is the right way of doing this.

Updating Angular 6 Form value in case corresponding dom native element value is modified by external library

I am experimenting cobrowsing with Angular 6 Forms.In co-browsing an opensource library togetherJS updates the DOM value of corresponding element for remote party.I have updated togetherJS related code in index.html(two line one in head and another in body) I have observed using ControlValueAccessor Form type DOM value of corresponding element is getting updated but FormControl value is not getting updated in view.
My question is how changes done by an external library on DOM elements can be reflected into angular 6 form control's element value in view.
One can get the code from below link:
https://github.com/srigaurav1986/Angular-Forms.git
How to reproduce:
1.Download code from above link.
2.Install dependencies using "npm install"
3.Run "ng serve -o"
4.open in browser "http://localhost:4200/controlvalueaccessor"
5.Click on "Start TogetherJs"
6.Copy the popped link in another browser window.
7.Update the "Name" field
We can see DOM field value is also getting updated on remote side but after pressing "Submit" button we can see FormControl value remains unaltered on remote side but changed on other side.
I tried using manually detecting changes using application.tick,markforcheck() and detectchanges() apis but no luck.Is there a way, where we can listen to some event on DOM element change and subscribe to it and also update the Formcontrol parameter values in such case.
The answer to this question lies in angular(6) property that it works on shadow DOM and only listen to the changes happening within angular zone , when the third party library like TogetherJS updates DOM corresponding changes doesn't effect angular components as they are not subscribe to actual DOM native element.
In order to resolve this issue we did following :
Register one call back in Form class constructer to capture DOM “change” events triggered from Co-Browsing library i.e. happening outside angular zone as mentioned below:
Code Snippet
constructor(private formBuilder: FormBuilder,private elementRef: ElementRef,private _renderer: Renderer,private cdr:ChangeDetectorRef,private app:ApplicationRef,private zone:NgZone) {
zone.runOutsideAngular(() =>{
window.document.addEventListener('change', this.change.bind(this));
})
}
Define the eventHandler to perform below actions :
Run in Angular context using this.zone.run()
Use ElementRef to get the component’s template selector.
Run queryselector on input elements and compare with event’s outerHTML to check which element has changed in component.
Set the Formcontrol’s value for matching element.
PS: Here customerForm is ControlValueAccesor FormGroup type of instance. In your case it can be your form. We can generalize form( In case reactive) key traversal as mentioned in another SO post
Angular 2: Iterate over reactive form controls
Code Snippet:
change(event){
event.preventDefault();
console.log("DOM value changed" ,event);
console.log("component value", this.elementRef.nativeElement);
this.zone.run(() => { console.log('Do change detection here');
//this.cdr.detectChanges();
if(this.elementRef.nativeElement.querySelectorAll('input')[0].outerHTML === event.target.outerHTML)
{
console.log('Inside value updation');
//this.customerForm.controls.name.value=event.target.value;
this.customerForm.controls['name'].setValue(event.target.value);
}
});
setTimeout(() =>{
this.cdr.markForCheck();
})
}
This will set the respective values of elements(obviously via traversing loop) changed in component and validation should not fail as it’s happening in current context.
The core idea of above details is how to capture change events happening outside angular zone and updating angular application accordingly.
PS : I shall update the full code in github for other's perusal.

Angular2 ControlGroup valueChanges on initial bind

I have a form with some <input type="text"> widgets and I have noticed that ControlGroup.valueChanges is being called upon initial databind when using [ngFormModel] and ngControl.
This means that the user thinks that the form has been changed upon initial load.
Is this normal or should I be using a different observable to track changes made the by the user?
I am using Angular2 RC3 and the following version import for forms:
import {ControlGroup, Validators, FormBuilder} from '#angular/common';
I think that's just how it works, however if you just want to track if changes are made by user, you should employ ControlGroup.dirty or formControl.dirty with the changes Observable.
ControlGroup.valueChanges.subscribe(() => {
if(ControlGroup.dirty){
console.log('This change is made by User.');
}
else {
console.log('This change is Automated. before any User interaction.');
}
})

How to handle UI animation events with knockout

So right now I have a table that displays some values and I have an indicator for conflicts. When the user clicks the indicator a new div appears with some animation to list all the conflicts.
Here is my HTML:
<span data-bind="if: hasConflict, click: $parent.selectProperty" class="conflictWarn"><i style="color: darkorange; cursor:pointer;" class="icon-warning-sign"></i></span>
The data might look something like this:
{
name:Property 1,
id: 1,
hasConflicts: no,
name:Property 2,
id: 2,
hasConflicts: yes,
conflicts: {
name: conflict1,
name: conflict2
}
name:Property 3,
id: 3,
hasConflicts: yes,
conflicts: {
name: conflicta,
name: conflictb
}
So the first table is going to look like this:
Property 1
Property 2 !
Property 3 !
Where ! is a conflict indicator. Clicking on the ! would display the conflicts div and also display conflict1 and conflict2 or conflicta and conflictb depending on which was clicked.
Here is the model we are working with. It's a bit complex because of the mapping for the properties from signalr. the "selectProperty" and "selectedProperty" was our way of saying which one to display conflicts for, but I'm not convinced this is the best way to do it.
function ItemViewModel() {
var self = this;
self.name = ko.observable("");
self.itemType = ko.observable("");
self.propertiesArray = ko.observableArray([]);
self.properties = ko.mapping.fromJS({});
self.selectedPropertyName = ko.observable("");
self.getItem = function (name) {
$.connection.mainHub.server.getItem(name).then(function (item) {
ko.mapping.fromJS(item.properties, self.properties);
self.propertiesArray(item.propertiesArray);
self.itemType(item.itemType.name);
self.name(item.name);
});
self.selectProperty = function (a, b) {
self.selectedPropertyName(a);
};
};
}
Originally the click event directly called a javascript function that did all the animation, but my coworker thought that might violate best practices for separating data and viewmodel in MVVM. Does it? Should we leave it calling the viewmodel function of "selectProperty" which allows us to pass context for the "conflicts popup" div? If so, do I just call the javascript function to do the animation from within the selectProperty function?
p.s. I've edited this about 800 times so I apologize if it's impossible to follow.
update I have the bindings working now, so I really just want to know what is best practice when it comes to UI animations and Knockout. Change the viewmodel from the javascript function or call the javascript function from the viewmodel function?
Regarding UI animations in my opinion it is best practice to implement custom bindings. This way code is encapsulated and it is easy to find where it is used. Check Animated transitions example on knockout website.
i'm going to extends Thomas answer with one point, custom bindings don't work when you want to animate the rendering / unrendering of the 'if' or 'with' bindings. an animation binding that tries to run at the same time as an 'if' or 'with' won't be able to complete the animation before the other binding alters the DOM, possibly removing the elements being animated from the page. there is no way to defer the binding processing until after the event completes.
for these cases animations should be implemented via the 'afterAdd' and 'beforeRemove' callbacks of the 'foreach' binding when the desire is to animate an element being added and removed from the page. 'if' and 'with' bindings can be rewritten as 'foreach' with little effort, as 'foreach' can take a single argument instead of a list. i really wish the animation tutorial would be extended to include this workaround.

delete item from a dojo.store.jsonrest

I started with this tutorial http://dojotoolkit.org/documentation/tutorials/1.6/store_driven_tree/
after setting up my ServerSide Restfull Service everything is working so far. I made a contextmenu for the tree by:
<ul dojoType="dijit.Menu" id="contextMenu" style="display: none;">
<li dojoType="dijit.MenuItem" iconClass="dijitEditorIcon dijitEditorIconDelete" onclick="pages.remove(tn.item.id);">delete page</li>
</ul>
<script type="dojo/connect">
var menu = dijit.byId("contextMenu");
menu.bindDomNode(this.domNode);
dojo.connect(menu, "_openMyself", this, function(e){
// get a hold of, and log out, the tree node that was the source of this open event
tn = dijit.getEnclosingWidget(e.target);
// contrived condition: disable all menu items except the "New Page" item
dojo.forEach(menu.getChildren(), function(child){
if(child.label != "Neue Seite")
{
child.set('disabled', typeof tn.item == 'undefined');
}
});
});
</script>
Now I know on wich node the user made the right click for the contextmenu and delete it with "pages.remove(tn.item.id);" from the Database. To notify the tree I´m overriding the remove function:
remove: function(objectId){
this.onDelete({id: objectId});
return dojo.store.JsonRest.prototype.remove.apply(this, arguments);
}
Everything works as expected but if im now doing some other things with the items in the tree like drag n drop an item to the root i was deleting a child before. The tree isn't showing it correctly anymore. I think the remove method of the store only sends the DELETE query to the Server but don't removes the item from the store. How can i get the array of the items in store to check and maybe to delete items?
The dijit.Tree is a presentation of an underlying dojo.data model, and any changes that you want to make to the tree really need to be done to the underlying data store. See the description here: http://dojotoolkit.org/reference-guide/dijit/Tree.html#dijit-tree So, instead of overriding the remove function, you should instead use the dojo.data API to modify the store, and then rerender the tree to reflect the changes. The best source for looking at the various methods available is in the dojo nightly files. Specifically, the dojo.data files are here: http://archive.dojotoolkit.org/nightly/dojotoolkit/dojo/data/
var item = tree.fetchItemByIdentity("selectedItem"); //find the item you want to remove
store.deleteItem(item); //delete the item from the data store
store.save(); //save the change made to the store