Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

diff and patching nodes with contentEditable='inherit' #176

Open
RGBboy opened this issue Jan 22, 2015 · 18 comments
Open

diff and patching nodes with contentEditable='inherit' #176

RGBboy opened this issue Jan 22, 2015 · 18 comments

Comments

@RGBboy
Copy link
Contributor

RGBboy commented Jan 22, 2015

If the contentEditable property is not specified on a node to update and the existing node has it, virtual-dom tries to set it to a blank string. This throws the following error in Chrome and Firefox:

Uncaught SyntaxError: Failed to set the 'contentEditable' property on 'HTMLElement': The value provided ('') is not one of 'true', 'false', 'plaintext-only', or 'inherit'.

Here is my setup that produces the error:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="script.js"></script>
  </head>
  <body>
    <div id="App"></div>
  </body>
</html>
var h = require('virtual-dom/h'),
    diff = require('virtual-dom/diff'),
    patch = require('virtual-dom/patch'),
    createElement = require('virtual-dom/create-element'),
    virtualize = require('vdom-virtualize'),
    document = global.document,
    element = document.getElementById('App'),
    tree = virtualize(element),
    newTree = h('div'),
    patches = diff(tree, newTree);

element = patch(element, patches);

I also put together a requirebin to show the problem: http://requirebin.com/?gist=d96693e487580d462d4f

When vdom-virtualize creates the tree it finds the contentEditable property is set to inherit. Then when the new tree is created contentEditable is not specified so a patch is created to remove it. This then causes the error because contentEditable must be set to one of true, false, plaintext-only, or inherit. inherit is the default value.

There are a couple of solutions to this. Either when a node is created, if contentEditable is undefined it is set to inherit or when nodes are diffed if one node has contentEditable set to inherit and the other undefined then the diff is nothing.

@Raynos
Copy link
Collaborator

Raynos commented Jan 22, 2015

Sounds like a virtualize bug.

@RGBboy
Copy link
Contributor Author

RGBboy commented Jan 22, 2015

This gist exhibits the same problem without virtualize: http://requirebin.com/?gist=d96693e487580d462d4f

var h = require('virtual-dom/h'),
    diff = require('virtual-dom/diff'),
    patch = require('virtual-dom/patch'),
    createElement = require('virtual-dom/create-element'),
    domify = require('domify'),
    html = '<div id="App">Before</div>',
    element = document.body.appendChild(domify(html)),
    oldTree = h('div', { id: 'App', contentEditable: 'inherit' }, 'Before'),
    newTree = h('div', { id: 'App' }, 'After'),
    patches = diff(oldTree, newTree);

element = patch(element, patches);

@neonstalwart
Copy link
Collaborator

@RGBboy does it work if you explicitly provide the new value for contentEditable? virtual-dom tends to prefer you be explicit rather than assume what it should do when a property is missing between 2 trees.

@RGBboy
Copy link
Contributor Author

RGBboy commented Jan 22, 2015

@neonstalwart it works if you do something like the following:

var h = require('virtual-dom/h'),
    diff = require('virtual-dom/diff'),
    patch = require('virtual-dom/patch'),
    createElement = require('virtual-dom/create-element'),
    domify = require('domify'),
    html = '<div id="App">Before</div>',
    element = document.body.appendChild(domify(html)),
    oldTree = h('div', { id: 'App', contentEditable: 'inherit' }, 'Before'),
    newTree = h('div', { id: 'App', contentEditable: true }, 'After'),
    patches = diff(oldTree, newTree);

element = patch(element, patches);

The issue is that the following two nodes will map to a dom element with contentEditable set to inherit:

var nodeA = h('div');
var nodeB = h('div', { contentEditable: 'inherit' });

Anyone trying to convert existing dom elements back to vnodes will get a contentEditable: 'inherit' property unless you explicitly ignore contentEditable on elements with contentEditable: 'inherit'.

@Raynos
Copy link
Collaborator

Raynos commented Feb 14, 2015

This is a bug in virtual-dom.

Virtual-dom will set a property to '' if it finds undefined. The DOM does not allow setting this field to ''

@Matt-Esch we need to either:

  • do a safe set (in a try catch)
  • have a white list of known properties that throw and do a safe set
  • have a white list of known properties that throw and have a default value different from ''

@matthewp
Copy link

Wouldn't the try/catch option mean it would happen on every redraw? If so I think one of the whitelist options is a better way to go.

@Zolmeister
Copy link
Contributor

@alexmingoia
Copy link

Why can't we just call removeAttribute? Why set attribute that are specified as being removed by a patch operation as ""?

@bitinn
Copy link
Contributor

bitinn commented May 7, 2015

I have tried using html-to-vdom with my PE approach and it works without throwing error mentioned. So htmlparser2 definitely do not add contentEditable: inherit on dom like browser's dom parser.

There can be 2 solutions to this:

  1. Try to get vdom-virtualize use a special case to ignore browser default behaviour.
  2. Or as mentioned by Raynos, virtual-dom protects user from making this mistake.

@bitinn
Copy link
Contributor

bitinn commented May 7, 2015

I have a pull request ready for vdom-virtualize, it works now, looks like contentEditable is the only edge case at the moment. marcelklehr/vdom-virtualize#22

You can try it using bitinn/vdom-virtualize#special-case

@alexmingoia
Copy link

@bitinn I do not think that is the proper way to solve this because the bug occurs without using vdom-virtualize as demonstrated by the previously posted requirebin example. node here in apply-properties.js is a DOM node, yet virtual-dom does not actually detect if the DOM node property being modified is legal to modify or not. It should only be modifying attributes (not all properties) and only in a manner valid according to the browser's DOM.

I think the proper solution is that virtual-dom must keep a whitelist of which DOM node properties are attributes and which aren't, and unfortunately, which modifications are legal in some cases.

@alexmingoia
Copy link

Sure it is not the simplest solution, but I think it is the proper one. We can't escape not dealing with the DOM's intricacies when working with it - as ugly as it is. The try/catch approach is not proper either because it just sweeps these issues under the rug and dings performance.

This is the same approach React uses for the same reasons. See this list: https://github.com/facebook/react/blob/0.13-stable/src/browser/ui/dom/HTMLDOMPropertyConfig.js

@bitinn
Copy link
Contributor

bitinn commented May 7, 2015

@alexmingoia yeah I would also prefer a fix from virtual-dom end, but I just want to see what else is broken and try to get my PE approach working :)

@egasimus
Copy link

+1 for this issue. What shall we do about it?

@bitinn
Copy link
Contributor

bitinn commented Jun 21, 2015

@egasimus Both vdom-virtualize and vdom-parser should have workaround this problem, how did you run into it?

(edited: sorry for the garbled message, office 365 doesn't work too well with github email reply it seems.)

@egasimus
Copy link

egasimus commented Jul 3, 2015

@bitinn I'm not using either of those (at least not directly -- I don't know if they're actually the underlying components of the workflow centered around h, diff, patch and create-element). I just tried toggling contenteditable in such a template and was faced with this error.

thSoft added a commit to thSoft/virtual-dom-1 that referenced this issue Nov 4, 2015
@josdejong
Copy link

Andy news on this bug? I encountered it too:

Uncaught SyntaxError: Failed to set the 'contentEditable' property on 'HTMLElement': The 
value provided ('null') is not one of 'true', 'false', 'plaintext-only', or 'inherit'.

@josdejong
Copy link

For others encountering the same issue, I found a workaround. What I did is change a DOM structure like this:

<div>
  <div contentEditable="true">B</div>
</div>

to something like this:

<div>
  <div>A</div>
  <div contentEditable="true">B</div>
</div>

Which gives trouble since in some cases virtual-dom tries to set contentEditable of the div A to "null".

To work around this, you have to make sure all siblings of the div's having a contentEditable attribute always have contentEditable specified too, like:

<div>
  <div contentEditable="false">A</div>
  <div contentEditable="true">B</div>
</div>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants