how to extend elixir phoenixframework html form helper - html

I tryed to extend Phoenix.HTML.Form module of the phoenixframework. My intension is to wrap the html form helper text_input to create a text input field for a timex date value to use it with bootstrap-datepicker.
I'm new to elixir but I read about protocols to extend elixir modules. So I tryed:
defprotocol Phoenix.HTML.Form.Extension do
def timex_date_input(form, field, opts \\ [])
end
defimpl Phoenix.HTML.Form.Extension, for: Phoenix.HTML.Form do
def timex_date_input(%{model: model, params: params}, field, opts \\ []) do
# my logic goes here
form = %{model: model, params: params}
text_input(form, field, opts)
end
end
But, it doesn't work, because: "function text_input/3 undefined". What would be the right solution?

You need to import the Phoenix.HTML.Form module to be able to use text_input - you will find that this is already imported into your views (and your templates since they are functions in your views) in your web.ex file.
If you wish to add a new form function, you can simply define the function (there is no need for protocols - these are often used as a way to extend libraries - phoenix_ecto is a great example of this):
defmodule MyApp.FormHelpers
def timex_date_input(form, field, opts \\ []) do
# my logic goes here
form = %{model: model, params: params}
Phoenix.HTML.Form.text_input(form, field, opts)
end
Then you can either import this into your view (import MyApp.FormHelpers), or use the full function name in your template:
<%= timex_date_input(f, :date, ...) %>

Related

Django Rest Framework: what happened to my default Renderer?

I would like calls to /contacts/1.json to return json, 1.api to return browsableAPI, and calls with format=None aka /contacts/1/ to return a template where we call render_form. This way end-users can have pretty forms, and developers can use the .api format, and ajax/apps etc use .json. Seems like a common use case but something isn't clicking for me here in DRF...
Struggling with how DRF determines the Renderer used when no format is given. I found and then lost some info here on stack exchange that basically said to split the responses based on format. Adding the TemplateHTMLRenderer caused all sorts of pain. I had tried to split based on format but that is giving me JSON error below.
I don't understand the de facto way to define what renderer should be used. Especially when no format is provided. I mean, it "just works" when using Response(data). And I can get the TemplateHTMLRenderer to work but at the cost of having no default Renderer.
GET /contacts/1/ Gives the error:
<Contact: Contact object> is not JSON serializable
Using this code:
class ContactDetail(APIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
queryset = Contact.objects.all()
renderer_classes = (BrowsableAPIRenderer, JSONRenderer, TemplateHTMLRenderer,)
"""
Retrieve, update or delete a contact instance.
"""
def get_object(self, pk):
try:
return Contact.objects.get(pk=pk)
except Contact.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
contact = self.get_object(pk)
serializer = ContactSerializer(contact)
if format == 'json' or format == "api":
return Response(serializer.data)
else:
return Response({'contact': contact, 'serializer':serializer}, template_name="contact/contact_detail.html")
But GET /contacts/1.json , 1.api, or 1.html ALL give me the correct output. So it seems that I have created an issue with the content negotiation for the default i.e. format=None
I must be missing something fundamental. I have gone through the 2 tutorials and read the Renderers docs but I am unclear on what I messed up here as far as the default. I am NOT using the DEFAULT_RENDERERS in settings.py, didn't seem to make a difference if in default or inside the actual class as shown above.
Also if anyone knows a way to use TemplateHTMLRenderer without needing to switch on format value, I'm all ears.
EDIT: IF I use
if format == 'json' or format == "api" or format == None:
return Response(serializer.data)
else:
return Response({'contact': contact, 'serializer':serializer},
Then I am shown the browsable API by default. Unfortunately, what I want is the Template HTML view by default, which is set to show forms for end users. I would like to keep the .api format for developers.
TL; DR: Check the order of your renderers - they are tried in order of declaration until a content negotiation match or an error occurs.
Changing the line
renderer_classes = (BrowsableAPIRenderer, JSONRenderer, TemplateHTMLRenderer, )
to
renderer_classes = (TemplateHTMLRenderer, BrowsableAPIRenderer, JSONRenderer, )
Worked for me. I believe the reason is because the content negotiator starts at the first element in the renderer classes tuple when trying to find a renderer. When I have format==None, I'm thinking there is nothing else for DRF to go on, so it assumes I mean the "default" which seems to be the first in the tuple.
EDIT: So, as pointed out by #Ross in his answer, there is also a global setting in the settings.py for the project. If I remove my class level renderer_classes declaration and instead use this in settings.py
# ERROR
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.BrowsableAPIRenderer',
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer',
)
}
Then I get a (different) JSON error. However, as long as
'rest_framework.renderers.BrowsableAPIRenderer',
is not listed first, for example:
# SUCCESS, even though JSON renderer is checked first
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
)
So if we hit BrowsableAPIRenderer before we try TemplateHTMLRenderer then we get an error - whether or not we are relying on renderer_classes or DEFAULT_RENDERER_CLASSES. I imagine it passes through JSONRenderer gracefully but for whatever reason BrowsableAPIRenderer raises an exception.
So I have simplified my view code after analyzing this...
def get(self, request, pk, format=None):
contact = self.get_object(pk)
serializer = ContactSerializer(contact)
if format == None:
return Response({'contact': contact, 'serializer':serializer}, template_name="contact/contact_detail.html")
else:
return Response(serializer.data)
..which better reflects what I was originally trying to do anyway.
When I look at the source code, the priority seems to be the order of the renderers specified in the DEFAULT_RENDERER_CLASSES parameter in settings.py:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer',
),
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.TemplateHTMLRenderer',
)
}
So, if you specify a bunch of renderer classes, the first renderer that is valid will be selected based on if it is valid for the request given the .json/.api/.html extension and the Accept: header (not content-type, as I said in the comment on your question).

Rails actions and jbuilder, why they have to be as difficult?

Rails drive me crazy. I'm trying to respond to with an action with JSON.
My goal is to let be the JSON the only format for a response to a URL.
Let's see some code.
The Model is a Devise user, with some added field.
The Controller is my UsersController that has this action
# /app/controllers/users_controller.rb
def static
render json: current_user
end
I got also this jbuilder view
# /app/views/users/static.json.jbuilder
json.content format_content(#user.content)
json.author do
json.name #user.name
json.email_address #user.email
end
if current_user.admin?
json.someValue "foo"
end
this View doesn't do some interesting stuff, but It's just a try.
Anyway I'll never get the static.json.jbuildercontent. I always get all Devise user's content as a JSON.
Am I doing something wrong? (or better: where I done the epic fail?)
Anyway found the solution:
# /config/route.rb
get 'my-static-json' => 'mycontroller#static', defaults: {format: :json}
# /app/controllers/mycontrollers_controller.rb
def my-static-json
end
# /app/views/mycontrollers/my-static-json.json.jbuolder
json.content "some static content"
this is only an example but gives have all the information that I needed

Include JSON from Django Rest Framework in an HTML template

I am trying to do something very simple but haven't found how to do it yet.
I have a model and an endpoint returning a JSON array reprenseting the instances of this model with Django Rest Framework. I want to include the JSON in an HTML template (for SEO and for fast initial data loading). Something like
<script>
var data = {% json_from_django_rest_framework "mymodel" %};
</script>
Is there an easy way to do this? Should I just go a different way?
Another way of doing this, which gets around rendering the view.
In your views.py;
class FooDetailView(DetailView):
model = Foo
template_name = 'foo/detail.html'
def get_context_data(self, **kwargs):
bars = []
for bar in self.object.bars.all():
bars.append(BarSerializer(bar).data)
kwargs['bars'] = JSONRenderer().render(bars)
return super(FooDetailView, self).get_context_data(**kwargs)
And in your template;
<script>
var bars = {{ bars|safe }};
</script>
It should really go without saying that you should pay attention to potential performance issues with this approach, ie.. perhaps it's best to paginate bars.
As discussed in the comments, here is an example of how to reuse the result from your api endpoint in a normal Django view by using Django's resolve function.
views.py
import json
from django.core.urlresolvers import resolve
from django.views.generic.base import View
class FooView(View):
def get(self, request):
# optional stuff in your view...
##
# Resolving another Django view programmatically
##
rev = '/path/to/api/endpoint/' # or use reverse()
view, vargs, vkwargs = resolve(rev)
vkwargs['request'] = request
res = view(*vargs, **vkwargs)
c = Context({
'data': json.dumps(res.data)
})
# Now the JSON serialized result from the API endpoint
# will be available in the template variable data.
return render(request, 'my-app/my-template.html', c)
my-template.html
<script>
var data = {{ data }};
</script>
Note 1: Instead of hardcoding the path in rev = '/path/to/api/endpoint/' it is better to reverse() the url, but I left it out to remove that as a source for errors. If you are going this direction, here is a list of the default url names provided by DRF
Note 2: The snippet would benefit from exception handling, like making sure that res returned 200, has the data property, etc.

How do you set all of your Sinatra responses to be JSON?

I've been able to set all of my content types to be JSON in a before block, but is there a sinatra after filter that allows me to run to_json on all of the responses (instead of writing to_json 3 times in my example below)?
require 'sinatra'
require 'json'
before do
content_type :json
end
get '/' do
{ song: "Hello" }.to_json
end
get '/go' do
{ song: "Go Yo Ho" }.to_json
end
get '/hi' do
{ song: "Wake me Up" }.to_json
end
Thanks!
You can do that in an after block:
before do
content_type :json
end
get '/' do
{ a: 1 }
end
after do
response.body = JSON.dump(response.body)
end
Sinatra will re-calculate the correct content length for the updated body value.
An alternate way would be to use a helper:
helper do
def j(data)
JSON.dump(data)
end
end
get '/' do
j({ a: 1 })
end
The Sinatra::JSON project does the same thing. Also, you might want to look at libraries designed for building APIs like Grape or Goliath. These two libraries provide a easy way to attach decoders and encoders to handle this type of automatic conversion.
Put set :default_content_type, 'application/json' and all your responses will include a Content-Type: application/json header.

Passing parameter in a Ruby function from HTML

I have two files. .rb (with Ruby code) and .erb(HTML file with some ruby script). I am calling a Ruby function in .rb from .erb.
.erb
Click here
.rb
def showProducts(param)
//Some code
end
I am able to call a function without passing parameters to it. But as and when I pass parameters to function and then call it, I receive an error. I know this is the incorrect way to call a parametrized function from .erb. What is the correct way of calling a parameterized function from HTML?
If you add in another key/value pair to the hash in url_for
<%= url_for :action => :showProducts, :product => "toaster" %>
Your URL should go from, say, http://localhost:3000/showProducts to http://localhost:3000/showProducts?product=toaster
Here, we're adding parameters to the GET request. To access these parameters in the controller we use the params hash. For example to get the product (toaster):
params[:product] #=> "toaster"
I found the solution to my problem :
<a href="<%= url_for :action => :showProducts, :id=> 'Hello' %>">
.rb function:
def showProducts(param)
//Some code
end
I found the solution
def showProducts
#params['product']
end