ujs + remotipaart - when running jQuery `.html(…)` calls, the appended html becomes just text

Question!

I am using rails 5, with remotipart. The remotipart version is:

gem 'remotipart', github: 'mshibuya/remotipart'

(At the moment of this question, the current commit is 88d9a7d55bde66acb6cf3a3c6036a5a1fc991d5e).

When I want to submit a form having multipart: true andremote: true` but without sending any attached file, it works great. But when I send a file, it fails.

To illustrate the case, consider a response like this:

(function() {
  modelErrors('<div class=\'alert alert-danger alert-dismissible\' role=\'alert\'>\n  <div aria-label=\'Close\' class=\'close fade in\' data-dismiss=\'alert\'>\n    <span aria-hidden>\n      &times;\n    <\/span>\n  <\/div>\n  Code can\'t be blank\n<\/div>\n');
}).call(this);

When this response is executed, immediately after arriving (since it is js), the form looks as it expected (in this case, this validation error is right to happen, and the rendered danger alert is right to appear with such text):

enter image description here

However, when I fill the file field, and repeat the exact same case (exact same validation error), the form looks quite different:

enter image description here

If you can guess, the contents are passed as text. The actual response being received from the server is a bit different:

<script type="text/javascript">try{window.parent.document;}catch(err){document.domain=document.domain;}</script>(function() {
  modelErrors('<div class=\'alert alert-danger alert-dismissible\' role=\'alert\'>\n  <div aria-label=\'Close\' class=\'close fade in\' data-dismiss=\'alert\'>\n    <span aria-hidden>\n      &times;\n    <\/span>\n  <\/div>\n  Code can\'t be blank\n<\/div>\n');
}).call(this);

This is the actual body of my response (it is not a bad copypaste, but the actual response as wrapped by remotipart).

The modalErrors function is quite dumb:

function modalErrors(html) {
    $('#main-modal > .modal-dialog > .modal-content > .modal-body > .errors').html(html);
}

Comparing the jQuery-appended html chunks (I look for them in the browser's DOM inspector), they look like this:

Good:

<div class="alert alert-danger" role="alert">
  <ul style="list-style-type: none">
    <li>Code can't be blank</li>
  </ul>
</div>

Bad:

Code can't be blank

What am I missing here? What I want is to allow my resposes to append html content when needed.



Answers

The remotipart lib, which uses something named iframe-transport, unwraps the content later and executes the response as if it was fetched with ajax.

However, the tags are stripped from the response, so the response will be converted to something like this:

try{window.parent.document;}catch(err){document.domain=document.domain;}(function() {
  modelErrors('\n  \n    \n      &times;\n    \n  \n  Code can\'t be blank\n\n');
}).call(this);

The solution I found to interact with this library in a sane way, is to define another helper helper, similar to j / escape_javascript:

module ApplicationHelper

  JS_ESCAPE_MAP = {
      '\\'   => '\\\\',
      '<'    => '\\u003c',
      '&'    => '\\u0026',
      '>'    => '\\u003e',
      "\r\n" => '\n',
      "\n"   => '\n',
      "\r"   => '\n',
      '"'    => '\\u0022',
      "'"    => "\\u0027"
  }

  JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = '&#x2028;'
  JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = '&#x2029;'

  def escape_javascript_with_inside_html(javascript)
    if javascript
      result = javascript.gsub(/(\\|\r\n|\342\200\250|\342\200\251|[\n\r<>&"'])/u) {|match| JS_ESCAPE_MAP[match] }
      javascript.html_safe? ? result.html_safe : result
    else
      ''
    end
  end

  alias_method :jh, :escape_javascript_with_inside_html

end

In the views which are susceptible of being sent both by regular ajax and remotipart -in different scenarios, I mean- just replace the j calls with jh calls. Example:

modalErrors('<%= j render 'shared/model_errors_alert', instance: @team %>')

was replaced with

modalErrors('<%= jh render 'shared/model_errors_alert', instance: @team %>')


When an arrow function has more than one statement you can no longer use the implicit return syntax.

Add block braces and a return statement:

array.map((element, index) => {
  let disturbingVariable = 100 + index
  return <MyComponent disturbingVariable={disturbingVariable} />
})

Alternatively, forgo the variable declaration and perform the addition in-place, maintaining the implicit return:

array.map((element, index) =>
  <MyComponent disturbingVariable={100 + index} />)
By : sdgluck


Alternatively, you could omit return and block braces, but the function body should be one liner with implicit return:

render() {
  return(
    <div>
      {array.map((element, index) => <MyComponent disturbingVariable={100 + index}/>)}
    </div>
  )
}

More about implicit return here

By : leo


This video can help you solving your question :)
By: admin