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

Add helper methods to help facilitate dynamic attributes on elements #57

Closed
jasonmp85 opened this issue Feb 28, 2013 · 12 comments
Closed

Comments

@jasonmp85
Copy link

This is a bit long, but requires some explanation…

I realize this may not be entirely Haml Coffee's responsibility, but since it already has a fair deal of code to help with generating tags, I figured it might be worth a request.

I'm quite fond of using the "partials" pattern to codify markup for a certain input element so it can be reliably repeated any time I need a special widget, fancy validation, etc. Basically what I'm talking about doing is writing a pared-down client-side SimpleForm.

So I've got a little form class that wraps a model and allows me to generate inputs with that model's attribute values populated (this also handles error responses, etc.). It's got hamlc partials for each possible type, so I may write something like:

.form-inputs
  %fieldset
    %legend Major Details
    %fieldset
      != @form.input('name')
      != @form.input('description')
      != @form.input('price', as: 'float', attrs: { min: 0.01, step: 0.01 })
      != @form.input('wins',  as: 'integer', attrs: { min: 0, step: 1 })
  %fieldset
    %legend Extra Info
    %fieldset
      != @form.input('retired',  as: 'boolean')
      != @form.input('status',   as: 'select', options: ['active', 'injured', 'resting', 'vacation', 'free'])
      != @form.input('birthday', as: 'date', attrs: { max: '2013-02-28' })
.form-actions
  %button(type='button' name='action' value='Cancel') Cancel
  %input(type='submit' value='Save')

And each call to @form.input is responsible for rendering a particular hamlc partial. For instance, integer.hamlc looks like this:

.input.integer
  %label.integer(for=@id)= @label
  %input.integer(id=@id name=@name type='number' value=@value step='1')
  %span.hint
  %span.errors

But what if the user wants to specify extra classes to go on the input field? What if they want to specify a min or a max? Or readonly or formnovalidate or disabled? Or random data-* keys?

Since (as far as I know), hamlc will only let me specify dynamic values for the values and not the keys of my attributes hash, this isn't possible without me writing my own code to generate arbitrary HTML tags. Which isn't my core competency.

Rails has the tag and content_tag methods to help in its Haml views. Can something similar be added to haml-coffee to allow views to safely generate arbitrary HTML tags and attributes?

@jasonmp85
Copy link
Author

This was hinted at in #49, but @netzpirat was able to solve that by properly parsing boolean attributes. In my case I've got a much larger space of possible attributes (as mentioned, readonly, disabled, min, max, pattern, step, etc.), so making hardcoded case statements to cover all combinations is out of the question.

@jasonmp85
Copy link
Author

And to be totally honest… I have a workaround. Putting it here for others' benefit if they find this in Google:

I wrote a simple jQuery extension to mimic the Rails tag method mentioned above:

# Returns an HTML tag of type `name` with attributes set to `attrs`. This is
# modeled after `ActionView::Helpers::TagHelper.tag` and makes dealing with
# dynamic attributes simple.
$.extend
  tag: (name, attrs = null) ->
    throw 'invalid HTML tag' unless name.match(/^[a-z]+$/)

    $("<#{name}>", attrs).get(0).outerHTML

This kind of sucks because it needs jQuery to parse that arg as HTML, which is slower than just building it blindly. Additionally, I believe a DOM element is created just so I can get its HTML, which seems wrong (I suppose I should destroy it after getting the return value).

With this new method, I was able to rewrite integer as number like so:

.input.number
  %label.number(for=@id)= @label
  != $.tag('input', _(type: 'number').defaults(@attrs))
  %span.hint
  %span.errors

And I'm using it like this:

      -# ...
      != @form.input('price', as: 'number', attrs: { min: 0.01, step: 0.01 })
      != @form.input('wins',  as: 'number', attrs: { min: 0, step: 1 })
      -# ...

@mehcode
Copy link
Collaborator

mehcode commented Feb 28, 2013

Take a look at https://github.com/edspencer/jaml.

Not sure if its in the domain of haml-coffee to provide that sort of an interface.

@netzpirat
Copy link
Owner

I think you want something like a Haml attribute method, but this isn't available in Haml-Coffee yet. The main problem is to extend the attribute parser to recognize single attribute tokens instead of the pairs we normally have (sounds easy but it isn't). It's definitely something I like to have, because we can also use at as shortcut for attributes without values, like it's popular in AngularJS, but I'm short on time because of a bigger project and won't have time to take care of it.

@vendethiel
Copy link
Collaborator

@jasonmp85 You don't need jQuery to parse your HTML, you can document.createElement + outerHTML (you just need to wrap some of these, ie look at what domify does).

@KODerFunk
Copy link

Hi @jasonmp85, @netzpirat, some time ago I created a port of the original modules from the Rails: https://github.com/evrone/ultimate-helpers
Wanted present it later, but could not resist to do it now.
At the moment, I have been actively engaged in major projects, but has set himself issues in this project. Really try this month to fill the readme. Expect approval and criticism)

@netzpirat
Copy link
Owner

@KODerFunk Wow, that looks awesome. Need to give it a try!

@huetsch
Copy link
Collaborator

huetsch commented Mar 6, 2013

@KODerFunk About a year ago I got fed up maintaining two very different haml files for client-side and server-side views, so I ported the majority of Rail's ActionView helpers (https://github.com/rails/rails/tree/master/actionpack/lib/action_view/helpers) over to Coffeescript. I especially missed the date select helpers - that was my initial motivation for the project. You can see some examples here:

https://github.com/huetsch/date-helper/blob/master/date_helper.coffee
https://github.com/huetsch/instance-tag/blob/master/instance_tag.coffee
https://github.com/huetsch/form-helper/blob/master/form_helper.coffee

... and more if you look at my profile page.

Your code and mine seem to have some overlap. I actually never finished form helper support for object instances, but I finished most of the rest of the code, and modeled it as closely after the Ruby as I could. I even ported some of Rails' core_ext to make the port closer to the original code (https://github.com/huetsch/cream/blob/master/cream.coffee). It looks like you may have focused on precisely the part I didn't. Would you like to collaborate and try to get all of Rails' view helpers ported to Coffeescript? I use this code every day and have unit tests for it, so I have a pretty strong base to build on.

As the OP mentioned, this isn't really the realm of haml-coffee. We could create a new project called "coffee-view-helpers" and go from there.

@jasonmp85
Copy link
Author

I predicted this would be the response, which is fine. I agree it's not totally in haml-coffee's area of responsibility to facilitate generation of arbitrary tags, I just thought:

  • There are already other helper methods, so it would just be adding another
  • haml-coffee already needs to generate HTML strings, so the helper could reuse that code path

@Nami-Doc: Even without the overhead of parsing a string in jQuery, going to a DOM element, and then back to a string, it seems kind of silly to create a DOM element just to turn it into a string so I can turn it back into a DOM element when it's finally rendered. If I'm creating hundreds of table rows this way, it'd be much better to just create them as strings to begin with before concatenating them and inserting them in bulk. That said, I'm going to switch away from my jQuery method to the bare call you've presented. I just went with jQuery at first because it was correct and easy for me to deal with (I'm not versed in the intricacies of safely using bare DOM methods across browsers).

@KODerFunk: That looks great! I should have thought of just porting tag and content_tag to CoffeeScript myself. Your library seems to add many global functions to the top-level namespace, though, so I'm not sure I can use it as-is. But I'll definitely give it a read. I've ported pieces of Rack (parameter parsing so I can use nested form names and "collect" them into deep objects using jQuery) to CoffeeScript before, so I don't know why the "just port it" idea didn't cross my mind.

@huetsch: Unfortunately my library is for internal use and will have input types that are very domain-specific. And I've not been shy about bringing in other libraries with it, which I'm not sure others will like (in particular, my date helper uses Modernizr to detect whether a real date widget is possible—as in Chrome—and falls back to jQuery UI datepicker if not, appropriately wiring it to display 'MM/DD/YYYY' but send 'YYYY-MM-DD' in a hidden field). The markup I generate is also closely tied to the included CSS (this is in a Rails engine). But I agree that this is a ripe area for innovation: having a form_for that takes a JavaScript object, Backbone model, or whatever and lets you easily generate a form within haml-coffee templates is a great feature to have.

@jasonmp85
Copy link
Author

Closing now because helper methods probably aren't the best solution to this problem.

It would be nice to have a syntax for allowing an arbitrary hash of attributes on a node. Something like

- extraAttributes = someHashReturningMethod(input)
%input(type=text value=value){extraAttributes}

But since my ticket was about helpers I'm going to close. Had a great discussion and it was nice to see others are also coming up with clever solutions to this problem.

@vendethiel
Copy link
Collaborator

On another note, I'd love to be able to interpolate tag names, ie %#{tag} abc (i know jade allows that). might take a look later

@shioyama
Copy link

shioyama commented Apr 3, 2013

+1 for interpolating tag names.

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

No branches or pull requests

7 participants