diff --git a/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee index 4cfaead078826..58e08ace4eb6f 100644 --- a/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee @@ -1,6 +1,6 @@ #= require_tree ../utils -{ matches, getData, setData, stopEverything, formElements } = Rails +{ matches, getData, setData, stopEverything, formElements, isContentEditable } = Rails Rails.handleDisabledElement = (e) -> element = this @@ -14,6 +14,9 @@ Rails.enableElement = (e) -> else element = e + if isContentEditable(element) + return + if matches(element, Rails.linkDisableSelector) enableLinkElement(element) else if matches(element, Rails.buttonDisableSelector) or matches(element, Rails.formEnableSelector) @@ -24,6 +27,10 @@ Rails.enableElement = (e) -> # Unified function to disable an element (link, button and form) Rails.disableElement = (e) -> element = if e instanceof Event then e.target else e + + if isContentEditable(element) + return + if matches(element, Rails.linkDisableSelector) disableLinkElement(element) else if matches(element, Rails.buttonDisableSelector) or matches(element, Rails.formDisableSelector) diff --git a/actionview/app/assets/javascripts/rails-ujs/features/method.coffee b/actionview/app/assets/javascripts/rails-ujs/features/method.coffee index d04d9414dda3d..9239fe8e59b1f 100644 --- a/actionview/app/assets/javascripts/rails-ujs/features/method.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/features/method.coffee @@ -1,6 +1,7 @@ #= require_tree ../utils { stopEverything } = Rails +{ isContentEditable } = Rails # Handles "data-method" on links such as: # Delete @@ -9,6 +10,9 @@ Rails.handleMethod = (e) -> method = link.getAttribute('data-method') return unless method + if isContentEditable(this) + return + href = Rails.href(link) csrfToken = Rails.csrfToken() csrfParam = Rails.csrfParam() diff --git a/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee index d1aeef56c7383..d392ddaf0f11b 100644 --- a/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee @@ -4,7 +4,8 @@ matches, getData, setData fire, stopEverything ajax, isCrossDomain - serializeElement + serializeElement, + isContentEditable } = Rails # Checks "data-remote" if true to handle the request through a XHR request. @@ -21,6 +22,10 @@ Rails.handleRemote = (e) -> fire(element, 'ajax:stopped') return false + if isContentEditable(element) + fire(element, 'ajax:stopped') + return false + withCredentials = element.getAttribute('data-with-credentials') dataType = element.getAttribute('data-type') or 'script' diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee index a760f272997f1..0311cd16c8704 100644 --- a/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee @@ -29,6 +29,18 @@ Rails.setData = (element, key, value) -> element[expando] ?= {} element[expando][key] = value +Rails.isContentEditable = (element) -> + isEditable = false + loop + if(element.isContentEditable) + isEditable = true + break + + element = element.parentElement + break unless(element) + + return isEditable + # a wrapper for document.querySelectorAll # returns an Array Rails.$ = (selector) -> diff --git a/actionview/test/ujs/public/test/data-disable-with.js b/actionview/test/ujs/public/test/data-disable-with.js index 10b8870171ea8..a34ff7ad44e34 100644 --- a/actionview/test/ujs/public/test/data-disable-with.js +++ b/actionview/test/ujs/public/test/data-disable-with.js @@ -37,6 +37,10 @@ module('data-disable-with', { 'data-url': '/echo', 'data-disable-with': 'clicking...' })) + + $('#qunit-fixture').append($('
', { + id: 'edit-div', 'contenteditable': 'true' + })) }, teardown: function() { $(document).unbind('iframe:loaded') @@ -432,3 +436,21 @@ asyncTest('button[data-remote][data-disable-with] re-enables when `ajax:error` e start() }, 30) }) + +asyncTest('form button with "data-disable-with" attribute and contenteditable is not modified', 6, function() { + var form = $('form[data-remote]'), button = $('') + + var contenteditable_div = $('#qunit-fixture').find('div') + form.append(button) + contenteditable_div.append(form) + + App.checkEnabledState(button, 'Submit') + + setTimeout(function() { + App.checkEnabledState(button, 'Submit') + start() + }, 13) + form.triggerNative('submit') + + App.checkEnabledState(button, 'Submit') +}) diff --git a/actionview/test/ujs/public/test/data-method.js b/actionview/test/ujs/public/test/data-method.js index 47d940c577386..3e0150807c586 100644 --- a/actionview/test/ujs/public/test/data-method.js +++ b/actionview/test/ujs/public/test/data-method.js @@ -5,6 +5,10 @@ module('data-method', { $('#qunit-fixture').append($('', { href: '/echo', 'data-method': 'delete', text: 'destroy!' })) + + $('#qunit-fixture').append($('
', { + id: 'edit-div', 'contenteditable': 'true' + })) }, teardown: function() { $(document).unbind('iframe:loaded') @@ -82,4 +86,19 @@ asyncTest('link with "data-method" and cross origin', 1, function() { notEqual(data.authenticity_token, 'cf50faa3fe97702ca1ae') }) +asyncTest('do not interact with contenteditable elements', 6, function() { + var contenteditable_div = $('#qunit-fixture').find('div') + contenteditable_div.append('') + + var link = $('#edit-div').find('a') + link.triggerNative('click') + + start() + + collection = document.getElementsByTagName('form') + for (const item of collection) { + notEqual(item.action, "http://www.shouldnevershowindocument.com/") + } +}) + })() diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js index 6ed0f272332e6..9d7c4a6d15123 100644 --- a/actionview/test/ujs/public/test/data-remote.js +++ b/actionview/test/ujs/public/test/data-remote.js @@ -41,6 +41,9 @@ module('data-remote', { })) .find('form').append($('')) + $('#qunit-fixture').append($('
', { + id: 'edit-div', 'contenteditable': 'true' + })) } }) @@ -508,4 +511,21 @@ asyncTest('inputs inside disabled fieldset are not submitted on remote forms', 3 .triggerNative('submit') }) +asyncTest('clicking on a link with contenteditable attribute does not fire ajaxyness', 0, function() { + var contenteditable_div = $('#qunit-fixture').find('div') + var link = $('a[data-remote]') + contenteditable_div.append(link) + + link + .bindNative('ajax:beforeSend', function() { + ok(false, 'ajax should not be triggered') + }) + .bindNative('click', function(e) { + e.preventDefault() + }) + .triggerNative('click') + + setTimeout(function() { start() }, 20) +}) + })()