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

Feature request: Rewrite JS to not depend on jQuery, or have both version (jQuery-compatible and es6-compatible) #537

Open
pedrofurtado opened this issue Feb 21, 2019 · 22 comments

Comments

@pedrofurtado
Copy link

@nathanvda 👍

@nathanvda
Copy link
Owner

Personally I still use jquery (and happy to), but I understand there is a trend to move away from it.

Also the callbacks and the insertion API now heavily depend on the presence of jquery, but it guess it would be a cool exercise to see how much backward compatibility we would loose when switching to a vanilla js version.

Also I like your suggestion to have both a jquery and vanilla js version.

@pedrofurtado
Copy link
Author

@nathanvda Thanks for feedback!

It's a great option to provide the two versions, one with jQuery compatibility and one without it. Then, all kind of projects can use the gem without problems.
I can help you to test and provide feedback, if you want 👍 🤝

@nathanvda
Copy link
Owner

To be honest: this does not rank very high on my to do list, but I will try to have a look.

@pedrofurtado
Copy link
Author

@nathanvda No problems 👍 Anyway, thanks for your work in this great gem 🥂

@Mirv
Copy link

Mirv commented Mar 4, 2019

So I've been poking through several things ... 7 of 9 javascript gems I checked stated about the same theme ...

There was a vanilla JS version, but it was buggy and slower ... so much so it was less work to just remove it & only use Jquery.

80% of what we do could be done in vanilla javascript, but that last 20% ... there is no better solution than Jquery.


I've been playing around with the idea of gutting the Jquery source code to only do the things Cocoon as a gem needs.

The file is 3,000 lines & I'm not done yet. Also, it will require maintenance to keep it updated - which is kind of a factor here.

I spent first half of the day trying to find a js library which was small enough to justify the work of switching off Jquery, but it really is the gold plated standard (In terms of comprehensiveness).

@thedanbob
Copy link

thedanbob commented Mar 5, 2019

I agree that maintaining the current jquery-style API is nearly impossible, but someone using it in a non-jquery context wouldn't expect to be able to use the same API (for example, having association-insertion-method makes no sense unless you have the jquery traversal methods available). Here's a quick rewrite I did in ES6: https://gist.github.com/thedanbob/b48a9e20b57341ced21fdd9e2e0609b9

import Cocoon from 'cocoon'

new Cocoon('#tasks', {
  insertionNode: '.links',
  beforeInsert: function(content) { ... },
  // etc
})

Note that I wrote this very fast and haven't run it at all so it almost certainly doesn't work yet. I'll work on cleaning it up, if you like what you see I'll submit a pull request.

@Mirv
Copy link

Mirv commented Mar 5, 2019

Yea - I submitted a PR to divide out some of the #add_fields responsibility and make the path moving forward easier, as well as some minor cleanup earlier this week ... couldn't decide if I should leave it open.

@Mirv
Copy link

Mirv commented Mar 5, 2019

@danbob - I grabbed your version & hit it with es-linter - it probably mucked up some stuff but I also cleaned up a few other things and ran my suggestions from regex portion thru it too.

https://gist.github.com/Mirv/ee6b7bee14578fc20f489a316d34e97a

I am not having much luck with your class making syntax - so there's a ton of undefined in there...

    1:7   error  'Cocoon' is defined but never used                      no-unused-vars
   37:28  error  'findWrapperClass' is not defined                       no-undef
   76:3   error  Identifier 'regexp_bracer' is not in camel case         camelcase
   81:3   error  Identifier 'regexp_underscorer' is not in camel case    camelcase
   95:9   error  'newContents' is already defined                        no-redeclare
   95:23  error  'associationIdBuilder' is not defined                   no-undef
   96:9   error  'insertionNodeElem' is assigned a value but never used  no-unused-vars
   96:48  error  'insertionNode' is not defined                          no-undef
   96:63  error  'insertionTraversal' is not defined                     no-undef
  111:9   error  Identifier 'regexp_braced' is not in camel case         camelcase
  111:25  error  'regexp_bracer' is not defined                          no-undef
  112:9   error  Identifier 'regexp_underscored' is not in camel case    camelcase
  112:30  error  'regexp_underscorer' is not defined                     no-undef
  113:17  error  'createNewId' is not defined                            no-undef
  116:53  error  'newcontent_braced' is not defined                      no-undef
  119:10  error  'regexpBraced' is not defined                           no-undef
  120:7   error  Identifier 'regexp_braced' is not in camel case         camelcase
  120:23  error  'regexp_bracer' is not defined                          no-undef
  121:7   error  Identifier 'regexp_underscored' is not in camel case    camelcase
  121:28  error  'regexp_underscorer' is not defined                     no-undef
  148:24  error  'findWrapperClass' is not defined                       no-undef
  175:9   error  'toDelete' is not defined                               no-undef
  178:70  error  'toDelete' is not defined 

@thedanbob
Copy link

thedanbob commented Mar 6, 2019

I got it working with the default form layout, haven't tested it beyond that yet: https://github.com/thedanbob/cocoon/blob/master/app/assets/javascripts/cocoon.es6.js

import Cocoon from 'cocoon.es6'

document.addEventListener('DOMContentLoaded', function() {
  if (document.getElementById('attachments'))
    new Cocoon('#attachments')
})

Edit: insertionFunc and before/after options work properly.

@Mirv
Copy link

Mirv commented Mar 6, 2019

That looks really good - I was just copy & pasting what we needed from jquery lib!

Also - sorry! I saw a bug in there when I got up this morning.

Thought to myself, "hopefully I got to work before he did!"

I had grabbed a copy I was playing with for ordering one of the assignments to shoot it thru some performance tests.

@thedanbob
Copy link

Thanks! I just committed a bunch of refactoring, if you have a chance to look through it let me know if you see anything broken. I also changed the API a bit and dropped support for optional data-* attributes in favor of explicit options in the constructor.

@nathanvda
Copy link
Owner

nathanvda commented Mar 6, 2019

This looks really good! Thanks! 👍

I will try to have a look in more detail somewhere this week/weekend 😱 🙂

@Mirv
Copy link

Mirv commented Mar 6, 2019

@danbob

I was playing with this line 74 for awhile too, in an attempt to ...

  • Use a descriptive name in place of a rather long conditional
  • Encapsulate that conditional
  • Remove the need for the empty ternary conditional & the '`' combo

Would be happy for a descriptive name of a function & just drop the code as is into the function body.

    var associations = `(?:${this.addFieldsLink.getAttribute('data-association')}|${this.addFieldsLink.getAttribute('data-associations')})`
    this.regexId = new RegExp(`_new_${associations}_(\\w*)`, 'g')
    this.regexParam = new RegExp(`\\[new_${associations}\\](.*?\\s)`, 'g')

Can we the right side of the assignment into a function?

addFieldPlural(association){
  var association = this.addFieldsLink.getAttribute('data-association');
  // assuming this one doesn't save an error into association
  if(!association)
    association =  this.addFieldsLink.getAttribute('data-associations')
  return association;
}

var associations = addFieldPlural
this.regexId = new RegExp(`_new_${associations}_(\\w*)`, 'g')
this.regexParam = new RegExp(`\\[new_${associations}\\](.*?\\s)`, 'g')

It's a few extra lines but it gets around some stuff that could be buggy later on ...

@thedanbob
Copy link

thedanbob commented Mar 6, 2019

That's not a ternary conditional but rather a regex non-capturing group with alternation, to match either form of the association. Both are set by the link_to_add_association helper at the same time: https://github.com/nathanvda/cocoon/blob/master/lib/cocoon/view_helpers.rb#L92-L93
That's why the original code tested a regex against the template to find the right one. I'm guessing rails uses the singular version for has_one relations and plural for has_many? Not sure. In any case, I can't think of a situation where both could be present in the same nested fields template so it's probably safe as is. And if not, the link_to_add_association helper should be fixed to only set the correct one.

@Mirv
Copy link

Mirv commented Mar 6, 2019

not a ternary conditional but rather a regex non-capturing group with alternation

Just further showing that line isn't easily deciphered :)

Maybe just name the variable associationRegex to prep the reader if not changing right?

Side: Yea - has_one / has_many sounds legit

@thedanbob
Copy link

Good idea, when it comes to regex it never hurts to clarify :) I also switched the order of the two (plural first now) since this gem is probably used much more for has_many associations than has_one.

@persand
Copy link

persand commented Jul 23, 2019

Here's a vanilla JS thing that we use @kollegorna

https://github.com/kollegorna/cocoon-vanilla-js

I can't take any credit myself, all high fives should go to @osvaldasvalutis

@pedrofurtado
Copy link
Author

pedrofurtado commented Dec 11, 2019

@persand Do you use this is yours systems? Is a complete replacement for cocoon, or there is some feature of coocon that is not covered by this repo? 👍 thanks for share

@thedanbob Your gist is not available anymore. Could you publish it again? 🤝

@persand
Copy link

persand commented Dec 16, 2019

@pedrofurtado Yes, we use it in a lot of projects. It works well for us, but I'm not sure if it's a complete replacement. Ping @osvaldasvalutis

@osvaldasvalutis
Copy link

@pedrofurtado @persand yes, it's a complete solution.

@thedanbob
Copy link

@pedrofurtado I've updated my links with my new username. But I no longer need a vanilla JS version of cocoon and if I do in the future I'll probably just use @osvaldasvalutis's.

For reference, here's how I load the gem's JS in webpacker: https://gist.github.com/thedanbob/36e3d85802e6344d41c6ca9971a7e474

@dkniffin
Copy link

dkniffin commented Jun 3, 2020

I am very interested in using this gem, but I do not want to depend upon jQuery anymore. I see there's a vanilla-js version of the JS side of this at https://github.com/kollegorna/cocoon-vanilla-js

It sounds like the only other thing preventing this gem from removing jQuery as a dependency is the callback API. It seems like you could replace the jQuery even emitting with native DOM events, using dispatchEvent. In fact, this is exactly what the fork linked above does. You could even add some logic in there that says "if jQuery is present, emit the jQuery event too", to not break backwards compatibility.

Edit: @nathanvda if I made a PR for this, would you accept it?

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

7 participants