Costruire un'esperienza di editing persistente in linea con Crystal, MongoDB (datanoise / sam0x17)

Poiché la documentazione è ancora scarsa, vorrei aggiungerne un po'.

Questo è quello che sto costruendo attualmente:

immagine

I singoli campi saranno modificabili con un editor in linea, che salverà automaticamente nel backend - senza bisogno di ricaricare l'intera pagina.

immagine

Il punto del tutto è un backend per la mia azienda, per essere in grado di automatizzare alcuni processi, calcolare i prezzi delle scorte, tenere le cose in magazzino, ecc. Un'applicazione di magazzino in pratica, su misura per me!

Tecnologie utilizzate

  • Cristallo
  • Kemal
  • AdminLTE 2
    • Bootstrap 3
    • dataTables
  • x-editable - una libreria davvero impressionante per l'editing in linea, purtroppo non mantenuta per qualche tempo
    • vedi esempi per Bootstrap 3 qui
  • MongoDB
  • Sublime Text per l'editing Sorriso
  • Robo 3T per una GUI per MongoDB

Bit importanti

immagine

MongoORM usa _id come indice principale. Si prega di notare il trattino basso!

L'_id NON è una stringa. È del tipo ObjectId

Pertanto dovrà essere costruito. Se volete costruirlo da un id precedente, usate questo codice:

mongo_id = BSON::ObjectId.new my_string_id

dove my_string_id è una stringa e mongo_id è un ObjectId.

Il codice

config/database.yml

è responsabile dell'impostazione della comunicazione con la tua istanza di Mongo DB. Si prega di fare riferimento a Documentazione di Sam.

stock.cr, responsabile della definizione di Stockitem, Subitem, e dei gestori Kemal appropriati:

richiedere "mongo_orm"
richiedere "json"
richiedere "./macros.cr".
richiedere "./weee.cr".

modulo Stock
     includere le macro

    classe Itemgroup < Mongo::ORM::Document #used per il raggruppamento, ad esempio prodotti Pimoroni, Shipping, ecc.
         campo gruppo : Stringa
         has_many :stockitems 1TP3Nota che lo stockitem può appartenere solo ad un ItemGroup alla volta.
     fine

    1TP3Questo include anche altri articoli (spedizione, download digitali, ecc.)
     classe Stockitem < Mongo::ORM::Document
         campo sku : Stringa
         descrizione del campo : Stringa
         campo is_alias : Bool
         campo alias_of : String 1TP3Se questo campo è impostato, non utilizzare altre definizioni da questo elemento, ottenere l'elemento principale con alias
         campo is_set : Bool # se questo elemento è un insieme di altri elementi
         embeds_many :subitems # dovrebbe essere impostato solo se is_set = true
         campo is_physical_item : Bool
         campo weee_applicable : Bool #può essere sia: pagato da qualcun altro, o voce non è elettronica, ...
         peso del campo : Int32 # in g
         timestamps
     fine

    classe Subitem < Mongo::ORM::EmbeddedDocument
         campo sku : Stringa #a riferimento all'articolo principale dello stock. NB: questo potrebbe probabilmente essere rielaborato in modo più carino con belongs_to ? ad esempio belongs_to :itemref, class_name: Stock::Stockitem
         campo nota : Stringa
         campo importo : Int32
     fine

    ottenere "/stock/show" do |env|
         #https://github.com/datanoise/mongo.cr
         sis = Stockitem.all({"is_alias" => {"$eq" => false}})
         nome = "Stock :: Mostra e Modifica"
         mrender "stock"
     fine

    post "/stock/edit/:what" do |env|
         pp "Entrare in modifica, con destinazione " + env.params.url["cosa"]
         pp env.params.body
         if env.params.body.has_key?("pk") && env.params.body.has_key?("value")
             pp "Dentro pk & valore"
             pk = env.params.body["pk"]?.as(String)
             valore = env.params.body["value"]?.as(String)
             se si = Stockitem.find(BSON::ObjectId.new pk)   
                 caso env.params.url["cosa"]
                 quando "sku"
                     si.sku = valore
                 quando "descrizione"
                     si.description = valore
                 quando "is_alias"
                     si.is_alias = value == "true" ? true : false
                 quando "alias_di"
                     si.alias_of = valore
                 quando "è_set"
                     si.is_set = value == "true" ? true : false
                 quando "is_physical_item"
                     si.is_physical_item = value == "true" ? true : false
                 quando "weee_applicable"
                     pp "weee_applicable"
                     si.weee_applicable = value == "true" ? true : false
                 fine
                
                 se si.save
                     pp "dentro si.save"
                     env.response.status_code = 200
                 else
                     env.response.status_code = 400
                 fine
             else
                 env.response.status_code = 400   
             fine
         else
             env.response.status_code = 400
         fine
     fine

    #generare da voci WEEE
     get "/stock/generate/:year" do |env|
         1TP3Passare attraverso l'intero database, raccogliere gli elementi e visualizzare un elenco
         stock_collection = Hash(String,String).new
         fatture = WEEE::Invoice.all({"anno"=>env.params.url["anno"].to_i32})       
         invoices.each fare |invoice|
             #p "elaborazione della fattura #{numero.fattura}"
             1TP3Nota che potremmo non ottenere assolutamente tutte le fatture qui, poiché easybill ha una visione diversa delle date (importazione)
             invoice.items.each do |item|
                 if !item.description.nil? && !item.number.nil?
                     key = item.number == "" ? item.description : item.number
                     se !key.nil?
                         if !stock_collection.has_key?(key)
                             descrizione = ""
                             if !item.description.nil?
                                 descrizione = item.description
                             fine                   
                             #stock_collection[key] = item.description
                             if !description.nil?
                                 stock_collection[key] = descrizione
                             fine
                         end # end of !stock_collection.has_key?
                     fine # fine di if !key.nil?
                 fine
             fine #end di invoice.items.each fare
         fine #end di invoices.each
         1TP3Controlla che questo non esista già nel nostro database, altrimenti crealo.
         stock_collection.each do |sku,description|
             if !Stockitem.find_by(:sku, sku)
                 si = Stockitem.new
                 si.sku = sku
                 si.is_alias = false
                 si.alias_of = ""
                 si.description = descrizione
                 si.is_set = false
                 si.is_physical_item = true
                 si.weee_applicable = true
                 si.weight = 100
                 si.save!
             fine
         fine
     fine #end della macro get

fine 1TP3Fine del modulo

Note:

  • La documentazione di Sam spiega parti di questo codice, io affronterò solo gli ostacoli:
  • sis = Stockitem.all({"is_alias" => {"$eq" => false}}) - questo codice cerca Stockitems (Mongo ORM si prenderà cura del nome del DB per te, ecc.)
  • si = Stockitem.find(BSON::ObjectId.new pk)
    • notare che qui BSON::ObjectId.new è usato per ricostruire l'id dalla chiave pk che è stata passata
  • il codice di ritorno 200 in caso di successo e 400 in caso di fallimento serve a x-editable per sapere se la modifica è riuscita

Comportamento di si.save

  • si.save! genera un errore se il salvataggio non è possibile
  • si.save restituirà true se il salvataggio è stato possibile, e false se non è stato possibile

stock.ecr, il template HTML

<% content_for “name” do %>
     .
.

<% content_for “main” do%>
<div class="”col-md-12″">
     <div class="”box" box-primary”>
         <div class="box-header" with-border”>
             <h3 class="”box-title”"><%= name %></h3>
         </div>
         <div class="”box-body”">
             Nota: gli elementi alias non sono caricati dal database.
            


                 <thead>
                     <tr role="”row”">
                         <th class="”sorting_asc”" tabindex="”0″">sku</th>
                         <th>descrizione</th>
                         <th>è_alias?</th>
                         <th>alias_di</th>
                         <th>è_set?</th>
                         <th>sottopunti</th>
                         <th>is_physical_item?</th>
                         <th>weee_applicable?</th>
                         <th>peso (g)</th>
                     </tr>
                 </thead>
                 <% sis.each do |si| %>
                
                    
                    
                    
                    
                    
                    
                    
                    
                    
                
                 .

            </TABLE>
         </div>
     </div>
</div>
<script>
     //https://datatables.net/examples/styling/bootstrap
     //demo.js x-editable
     window.onload = function() {
         $(document).ready(function (){
             $("#stock_items").DataTable();
             $.fn.editable.defaults.mode = 'popup';
             $(".editable").editable();
             $(".editable").on('hidden', function(e, reason){
                 if(reason === 'save' || reason === 'nochange'){
                     var $next = $(this).closest('tr').next().find('.editable');
                     setTimeout(function(){
                         $next.editable('show');
                     }, 300);
                 }
             });
         });
     }
</script>
.

Note:

  • poiché aggiungiamo i nostri script JS dopo questo (dovrei rielaborare questo per aggiungere una sezione di script personalizzati alla fine), dobbiamo avvolgerli in una funzione window.onload - altrimenti $ non è definito (scorciatoia jQuery)
  • notate come usiamo una funzione qui per saltare al prossimo modificabile al successo del salvataggio
  • si._id.to_s.rchop - l'id BSON viene tradotto in una stringa, e poi l'ultimo carattere strano (terminatore nullo?) viene tagliato via. Questo è uno scoglio importante!

layout.ecr (estratti)

<!DOCTYPE html>
<html>
<head>
   .
   .
   TaxGod |
   .
   .
   .
   .
   .
   .
   .
   .
   .
   .
   . 
   <!–[if lt IE 9]>
   <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js”>
   <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js”>
   <![endif]–>

  <link rel="foglio di stile"
         href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic”>
</head>
<body class="”hold-transition" skin-blue sidebar-mini”>
<div class="”wrapper”">

(...)

  <!– Content Wrapper. Contains page content –>
   <div class="”content-wrapper”">
     <!– Content Header (Page header) –>
    


       <h1>
         TaxGod ::
         Essere Dio è divertente.
       </h1>
    

    <!– Main content –>
    

     <%= yield_content “main” %>

   


     <!– /.content –>
   </div>
   <!– /.content-wrapper –>

(...)
</div>
<!– ./wrapper –>

<!– REQUIRED JS SCRIPTS –>

<!– jQuery 3 –>
<script src=”/bower_components/jquery/dist/jquery.min.js”></script>
<!– Bootstrap 3.3.7 –>
<script src=”/bower_components/bootstrap/dist/js/bootstrap.min.js”></script>
<!– AdminLTE App –>
<script src=”/danielm_uploader/js/jquery.dm-uploader.min.js”></script>
<script src=”/bower_components/datatables.net/js/jquery.dataTables.min.js”></script>
<script src=”/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js”></script>
<script src=”/dist/js/adminlte.min.js”></script>
<script src=”/tg_js/bootstrap-editable.min.js”></script>
<script src=”/tg_js/dropzone.min.js”></script>
</body>
</html>

Ulteriori riferimenti

Plugin WordPress Cookie di Real Cookie Banner
tbd.<A HREF="#"
                             id=".weee_applicable"
                             class="weee_applicable editable"
                             data-type="select"
                             data-pk=""
                             data-value="true"
                             data-source="[{valore: 'vero', testo: 'vero'}, {valore: 'falso', testo: 'falso'}]"
                             data-url="/stock/edit/weee_applicable"
                             data-title="WEEE: "
                         >
g