Using tag-chooser in a plugin

I’m working on a plugin that will assign a default tag to new topics created in a category.

The below code “works” if I enter tag names separated by “|”, so “tag1|tag2” works. (I can hardly believe it! But I digress.)

<h3>{{i18n 'topic_default_tag.title'}}</h3>
<section class='field default-tag'>
  <div class="default-tag">
  <label>
    {{input type="list" value=category.custom_fields.default_tag }}
    {{popup-input-tip validation=tagValidation}}
    {{i18n 'topic_default_tag.default_tag'}}
  </label>
  </div>
</section>

What I’d like now is to have a proper tag selector rather than an un-validated string. It seems like the below should work:

<h3>{{i18n 'topic_default_tag.title'}}</h3>
<section class='field default-tag'>
  <div class="default-tag">
  <label>
    {{tag-chooser tags=category.custom_fields.default_tag tabindex="4" categoryId=category.id}}
    {{popup-input-tip validation=tagValidation}}
    {{i18n 'topic_default_tag.default_tag'}}
  </label>
  </div>
</section>

But it doesn’t. The basis of my problem is that I have nearly zero idea how the magic of those handlebars works. Like I guess that the first section works because it’s magically getting the field name from default-tag in the <section class='field default-tag'>, but that was lucky. :slight_smile:

When I use the tag-chooser the tags get passed to Rails as an array, which it throws away before I can make them into a |-delimited string to stick into the CategoryCustomField. With the {{input type=list...}} I can enter the |-delimited string myself and it works just fine. Do I need some kind of Ember magic to conver the array into a string on the Ember side?

Maybe I need to do something like What's the best approach to access category specific settings??

EDIT: To add tags before the webhook gets called, use after_create rather than DiscourseEvent.on(:post_created). Rails mostly makes sense to me now, but Ember, Javascript, and CSS, not so much.

1 Like

you can check if the variables you’re using contain the values you need by using {{log variable_name}} and checking the console. If the correct values aren’t passed to the component, then it wouldn’t show the correct output.

You can verify them by doing the same in the core code to confirm whether the values are passed correctly.

Forgive me if it sounds naive to you.

No! I’m just a caveman.

That’s very helpful, as I didn’t know how to do that. As it turns out, I do have data in those fields. AND, the tag name data is in category.custom_fields.default_tag and getting passed to the mini-tag-chooser. category.id does have the ID of a category, but the chooser isn’t offering any tags.

I can’t tell whether if it were that data would get pushed into the field.

EDIT: OOOOH! But using tag-chooser instead of mini-tag-chooser is working as it should (I will edit the code above accordingly). The only problem is that the data isn’t getting saved when I click save.

This line makes me suspicious. I think tags might be taking in an array and as it looks to me, you’re passing a single value(you need to confirm that). If it is being passed as a single value, just make it an array and try.

Well, on the rails side, it’s a |-delimited string and I see after I submit that the stuff I’ve entered is getting submitted, e.g., "custom_fields"=>{"de fault_tag"=>["error", "high-availability", "best-practices"]}, and "custom_fields"=>{"de fault_tag"=>["health-checks"]},

Here’s the whole thing:

Started PUT "/categories/2" for 127.0.0.1 at 2019-08-01 13:56:13 -0700
Processing by CategoriesController#update as */*
  Parameters: {"name"=>"Lounge", "slug"=>"lounge", "color"=>"EEEEEE", "text_color"=>"652D90", "permissions"=>{"trus
t_level_3"=>"1"}, "auto_close_hours"=>"", "auto_close_based_on_last_post"=>"false", "position"=>"3", "email_in"=>""
, "email_in_allow_strangers"=>"false", "mailinglist_mirror"=>"false", "allow_badges"=>"true", "custom_fields"=>{"de
fault_tag"=>["error", "high-availability", "best-practices"]}, "topic_template"=>"", "suppress_from_latest"=>"false
", "all_topics_wiki"=>"false", "allow_global_tags"=>"false", "sort_order"=>"", "sort_ascending"=>"", "topic_feature
d_link_allowed"=>"true", "show_subcategory_list"=>"false", "num_featured_topics"=>"3", "default_view"=>"", "subcate
gory_list_style"=>"rows_with_featured_topics", "default_top_period"=>"all", "minimum_required_tags"=>"0", "navigate
_to_first_post_after_read"=>"false", "search_priority"=>"0", "id"=>"2"}

So it looks like the Right Thing is happening on the mysterious side, but that Rails isn’t updating the Category Custom Field. I’ll go back to comparing what I have with another plugin that deals with CategoryCustomFields.

Thanks so much, @fzngagan!

EDIT: Hmm. I think the problem is:

Unpermitted parameter: :default_tag
1 Like

You’ll really enjoy finding the answer to that. Still, if you need help, I would be more than happy to do so. :slight_smile:

1 Like

Yes it is. Rails doesn’t know yet what to do with this new field. you need to add_to_serializer the new field. You can see examples of this in plugins which add custom fields.

Well, plugin.rb includes:

  Category.register_custom_field_type('default_tag', :list)
  Site.preloaded_category_custom_fields << 'default_tag' if Site.respond_to? :preloaded_category_custom_fields
  add_to_serializer(:basic_category, :default_tag) { object.custom_fields["default_tag"] }

It’s over at GitHub - pfaffman/discourse-topic-default-tag: Allow topics to include default tags if you want to look there.

I’m confused because having

   {{input type="list" value=category.custom_fields.default_tag }}

worked but

{{tag-chooser tags=category.custom_fields.default_tag tabindex="4" categoryId=category.id}}

Does not.

EDIT: It seems that using tag-chooser my custom field gets sent to Rails as an array and then gets thrown away before it’s processed, but using a text field where I create a |-delimited string, it works just fine. (I thought that maybe I could fix it with a before_validation on the Rails side, but no joy.)

So I guess I need some magic on the Ember side to convert the array to a string before it sends it back?

1 Like

I too used tag-chooser without params and it fetched all the tags available.

Plus, I think I found the reason why those aren’t getting saved(or even reaching) :wink: on the rails side when using tag-chooser. When you use input type list and add a value field, that automatically becomes a part of the form.

but the markup generated by the tag chooser is a div. Thats why it isn’t added to the form submission.

I am trying to put together a workaround for it. I’ll submit a PR once I get it to work.

I talked to @joffreyjaffeux yesterday and he’s working on making a change.

1 Like

Thats exactly what I am on.

Thats great. As a newbie, I am trying to learn more and more and this helps a lot :slight_smile:

Hello @pfaffman.

I managed to put together a fix. I wasn’t able to do it from the ember’s side as tag-chooser seemed too cool to drop, but I managed it from Rails side.

I wrote a simple function in the CategoriesController and called it :before_action, in that I converted the default_tag array to a | delimited string.

Secondly, I changed before_update to before_commit as the custom_fields seem to be set at that point in execution.

1 Like

Hmm. I thought I had tried that but I’ll have a look when I get to the office. Thanks!

1 Like

Aha! That’s what I don’t know how to do. You stick that in plugin.rb?

1 Like

Yes. This did the trick for me.

class ::ApplicationController
    def convert_default_tag
      return unless :topic_default_tag_enabled
      puts 'request'
     #Just check whether the field exists to avoid running into errors
      request.params["custom_fields"]["default_tag"] = request.params["custom_fields"]["default_tag"].join('|')
      p request.params["custom_fields"]["default_tag"]
    end
  end
  require 'categories_controller'
  class ::CategoriesController
     before_action :convert_default_tag , only: [:create]
  end

I think its possible to have the method default_tag in the controller itself, but I couldn’t get it to work that way. As ApplicationController is what every Discourse Controller inherits from, The method is loaded on the onset of extending the class from application controller which does the job.

@merefield
Is it possible to define a method to be called :before_action in the controller itself?

2 Likes

You should put three back ticks before and after the code block.

```
 Code goes here
  ```

I can’t wait to try it tomorrow! Thanks so much.

1 Like

Thanks. I thought it was single backtick but was stuck. So I used the plain old html <pre></pre> tags to make it work.

Yes, I believe so

1 Like

That’s a big help. Now I have this:

  class ::CategoriesController
    before_action :default_tag_to_string, only: [:create, :update]

    def default_tag_to_string
      puts "CDT: #{params}"

      return unless :topic_default_tag_enabled
      #Just check whether the field exists to avoid running into errors
      request.params["custom_fields"]["default_tag"] = request.params["custom_fields"]["default_tag"].join('|')
      p request.params["custom_fields"]["default_tag"]
    end

  end

And now the selected tags get converted to a string before rails tries to update the record and it works as expected.

NOW the problem is that I need to figure out where to convert the string on the rails site into the array that we need on the Javascript/Ember side. @joffreyjaffeux, I can’t remember (or, more likely, didn’t understand) whether the problem you were trying to solve was re-writing this value on the javascript-to-rails side (fixed!) or the rails-to-Javascript side (still confused).

1 Like

What worked for me is, I changed the :before_update to :before_commit in your code when assigning tags to the topic.

Secondly, When you open the edit category page, the default tag setting field would require an array in its tags param, in order to display the selected tags.

2 Likes