Friday, 10 March 2017

How to style your input file in Reagent with Bootstrap

Ok this caused me a lot of headache, hope it will help anyone.
In bootstrap the only way to style a input with type file, is to close it in a label. Like this:
  • <label class="btn btn-default btn-file"> Browse <input type="file" style="display: none;"> </label>
   

Unfortunately, as things are now, the only way to do something like this in Reagent is to set the InnerHhtml and do something like this:

(defn ^:export on-click
  [event]
  (js/alert "Upload Button Clicked")

(defn ^:export on-change
  [event]
  (js/alert "File Changed)

(def input-html "<input id='upload-file' name='updload-file' type='file' 
style='display: none;' onclick='{ns}.table.on_click(event)' 
onchange='{ns}.table.on_change(event);'></input>")

[:label.btn.btn-primary.col-span-1
     {:dangerouslySetInnerHTML
      {:__html (str (label-input "Select File") input-html)}}]


Also the javascript functions that will be called will have to be exported and called using {ns}.function
This is the only 'non elegant' solution that is possible now.
An interesting thing that give me headache was this one.
I wanted to change the label str ("Select File") in the on-change function. I used a reagent atom for that and changed the label in this way:

(def label (reagent/atom "Select File"))

(defn ^:export on-change
  [event]
  (reset! label "New Label)
[:label.btn.btn-primary.col-span-1      {:dangerouslySetInnerHTML       {:__html (str (label-input @label) input-html)}}]
This actually cause the input to loose it's file because React replace the <label> with it's html recreating the input and loosing the file.
Hope this will prevent some other guys to have the same headache I had :D

Thursday, 9 March 2017

How to do an upload using REST (Swagger) and Clojurescript (ajav

Hoping to help someone else that is also struggling with this.
I have used Luminus with Reagent to try this.
Luminus come with Swagger for REST API, it's a great tool because it gives you the possibility to try the API as soon as you have write it.
This is the code to write a REST end-point that accept a file in upload

(ns your-clojure-ns
  (:require [ring.util.http-response :refer :all]
            [compojure.api.sweet :refer :all]
            [schema.core :as s]
            [ring.swagger.upload :as upload])

(defapi service-routes
  {:swagger {:ui "/swagger-ui"
             :spec "/swagger.json"
             :data {:info {:version "1.0.0"
                           :title "Sample API"
                           :description "Sample Services"}}}}

  (context "/your-context" []
    :tags ["Your Context"]

    (POST "/upload" []
      :multipart-params [file :- upload/TempFileUpload]
      :middleware       [upload/wrap-multipart-params]
      (ok (let [{:keys [filename tempfile]} file ]
            (do-something-with tempfile)
            {:success true}))))



You should already have the right imports in the file created by Luminus, the only one that you have to add is [ring.swagger.upload :as upload] that will give you the Middleware to use for your request.
Once you have added this. You should see in your Swagger ui (localhost:3000/swagger-ui) your new API, with the possibility to already send a file to try your logic (do-something-with)
Now let's go to the Clojurescript call (that is the hardest part, I lost an evening to figure out the right combination)

(ns your-clojurescript-ns
  (:require [ajax.core :refer [GET POST]]))

(defn upload-file [file]
  (letfn [(handle-response-ok [] (js/alert "File Uploaded")
          (handle-response-error [] (js/alert "There were problems during the upload"))]
    (let [form-data (doto
                      (js/FormData.)
                      (.append "file" file))]
      (POST "/your-context/upload"
            {:body form-data
             :response-format :json
             :keywords? true
             :handler handle-response-ok
             :error-handler handle-response-error}))))



(.append "file" file) is very important, file is the same name you used in the clojure namespace as parameter, also you have to send the form-data as :body, if you try to send it as a :params (like is written around in internet) you will have an error.
Hope this post will be useful.

Coming soon... How to style your input with a file using Bootstrap, Reagent and Clojurescript. How changing the label using a Reagent atom can give you some headache.

First project with Luminus and Reagent... impressions.

Ok I have started a project for a friend https://github.com/marcomanzi/arcatron, it's web application to charge customers for calls done.
I'm using Luminus with Reagent as frontend... how it is going...
I will compare this with my experience in doing something similar using Java, with Spring and Vaadin. During this development I had more feeling coming out, these are the things I want to share (not ordered by relevance)

  • Fun: Well, this is a totally new experience, I have Terminator with 3 windows, one with lein test-refreh, one with lein run and one with lein figwheel. I really enjoy that I see happening in my browser while I change them. It's a new experience because with Vaadin every little change I had to restart the application server (it does not take long though)
  • Creativity: Because there is not a clear path written (with Spring a Vaadin you are taken by hand and they guide you to the final result like a parent with a child) I was able to come out with my solutions for problems, like the creation of a paginated-table that handle the rendering of a table that automatically resize when the browser window change size.
  • Frustration: well also this feeling come out a lot... I'm using swagger to create Rest API, and I'm calling them using ajax.core Get and POST methods. Using a dynamic language is very hard because if you can't find documentation on Internet you are not able to figure how you should use these functions. For example it took me a good evening to make the upload of a file working because I was using the :params key in the POST configuration map instead of the :body, and there was really no feedback to understand that
  • Productivity is still very low: Unfortunately the horrible stacktrace of clojure and the error messages make me think that there is still work to do in that direction in clojure to make it more productive.
Next steps:
  • Improve my paginated table to make it easier to use, I was not able to encapsulate it fully.
  • Move to clojure 1.9 to check if errors are a little more useful
Wishes:
  • Luminus is a good framework, but still documentation is poor on simple tasks (like doing a full rest application with clojurescript as frontend, lots of common problems (like timeout or security using tokens, ..) don't have a documented solution leaving open the implementation. These are problems that are "old", production tested solution should be documented in the framework and should take less time to set it up.
If any of you want to go through the code and give me some advice I will gratefully accept that, I feel that I still have to learn a lot on how to write functional programming but I only have my free time to try it :), help from someone is great, also if you have a project where you need some help, let me know.