Het bouwen van een persistente in-line editing ervaring met Crystal, MongoDB (datanoise / sam0x17)

Aangezien de documentatie nog schaars is, zou ik er graag wat aan toevoegen.

Dit is wat ik momenteel aan het bouwen ben:

afbeelding

De afzonderlijke velden kunnen worden bewerkt met een inline editor, die automatisch wordt opgeslagen in de backend - de hele pagina hoeft dus niet opnieuw te worden geladen.

afbeelding

De bedoeling van het geheel is een backend voor mijn bedrijf, om een aantal processen te kunnen automatiseren, voorraadprijzen te berekenen, dingen op voorraad te houden, etc. Een magazijn applicatie in principe, op maat gemaakt voor mij!

Gebruikte technologieën

Belangrijke bits

afbeelding

MongoORM gebruikt _id als de hoofdindex. Let op de underscore!

De _id is GEEN tekenreeks. Het is van het type ObjectId

Daarom zal het moeten worden geconstrueerd. Als je het wil opbouwen vanuit een vorig id, gebruik dan deze code:

mongo_id = BSON::ObjectId.new mijn_string_id

waarbij mijn_string_id een String is, en mongo_id een ObjectId.

De code

config/database.yml

is verantwoordelijk voor het opzetten van de communicatie met uw Mongo DB instantie. Raadpleeg hiervoor Sam's documentatie.

stock.cr, verantwoordelijk voor het definiëren van Stockitem, Subitem, en de juiste Kemal handlers:

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

module Voorraad
     Macro's omvatten

    class Itemgroup < Mongo::ORM::Document #used voor het groeperen, bijvoorbeeld Pimoroni producten, Verzending, enz.
         veld groep : String
         has_many :stockitems 1TP3Merk op dat een stockitem slechts tot één ItemGroup tegelijk kan behoren.
     einde

    1TP3Dit is inclusief andere items ook (scheepvaart, digitale downloads, enz.)
     klasse Stockitem < Mongo::ORM::Document
         veld sku : String
         veld beschrijving : String
         veld is_alias : Bool
         veld alias_van : String 1TP3Als dit veld is ingesteld, gebruik dan geen andere definities van dit item, haal het gealiaste master item
         field is_set : Bool # als dit item een set is van andere items
         embeds_many :subitems # moet alleen worden ingesteld indien is_set = true
         veld is_physical_item : Bool
         field weee_applicable : Bool #kan zijn: betaald door iemand anders, of item is geen elektronica, ...
         veld gewicht : Int32 # in g
         tijdstempels
     einde

    klasse Subitem < Mongo::ORM::EmbeddedDocument
         field sku : String #a verwijzing naar het belangrijkste voorraaditem. NB: dit kan waarschijnlijk op een mooiere manier worden herwerkt met belongs_to ? bv. belongs_to :itemref, class_name: Stock::Stockitem
         veld opmerking : String
         veld bedrag : Int32
     einde

    get "/stock/show" do |env|
         #https://github.com/datanoise/mongo.cr
         sis = Stockitem.all({"is_alias" => {"$eq" => false}})
         name = "Voorraad :: Tonen en bewerken"
         mrender "stock"
     einde

    post "/stock/edit/:what" do |env|
         pp "Wijziging invoeren, met doel " + env.params.url["wat"]
         pp env.params.body
         if env.params.body.has_key?("pk") && env.params.body.has_key?("value")
             pp "Inside pk & value"
             pk = env.params.body["pk"]?.as(String)
             waarde = env.params.body["waarde"]?.as(String)
             indien si = Stockitem.find(BSON::ObjectId.new pk)   
                 case env.params.url["wat"]
                 wanneer "sku"
                     si.sku = waarde
                 wanneer "omschrijving"
                     si.description = waarde
                 wanneer "is_alias"
                     si.is_alias = value == "true" ? true : false
                 wanneer "alias_van"
                     si.alias_van = waarde
                 wanneer "is_set"
                     si.is_set = waarde == "true" ? true : false
                 wanneer "is_physical_item"
                     si.is_physical_item = value == "true" ? true : false
                 wanneer "weee_applicable"
                     pp "weee_applicable"
                     si.weee_applicable = value == "true" ? true : false
                 einde
                
                 als si.save
                     pp "inside si.save"
                     env.response.status_code = 200
                 anders
                     env.response.status_code = 400
                 einde
             anders
                 env.response.status_code = 400   
             einde
         anders
             env.response.status_code = 400
         einde
     einde

    #genereren uit WEEE inzendingen
     get "/stock/generate/:jaar" do |env|
         1TP3Wandel door de hele database, verzamel de items, en toon een lijst
         voorraad_verzameling = Hash(String,String).new
         facturen = WEEE::Invoice.all({"jaar"=>env.params.url["jaar"].to_i32})       
         invoices.each do |invoice|
             #p "verwerking factuur #{invoice.number}"
             1TP3Merk op dat we misschien niet helemaal alle facturen hier krijgen, omdat easybill een andere kijk heeft op de data (import)
             invoice.items.each do |item|
                 indien !.item.omschrijving.nihil? && !.item.aantal.nihil?
                     sleutel = item.nummer == "" ? item.omschrijving : item.nummer
                     indien !.key.nil?
                         indien !stock_collection.has_key?(sleutel)
                             description = ""
                             indien !.item.description.nil?
                                 beschrijving = item.beschrijving
                             einde                   
                             #stock_collection[key] = item.description
                             indien !.description.nil?
                                 voorraad_verzameling[sleutel] = beschrijving
                             einde
                         end # end of !stock_collection.has_key?
                     end # end of if !.key.nil?
                 einde
             end #end van invoice.items.each do
         einde #end van facturen.elk
         1TP3Verifieer dat dit niet al bestaat in onze database, anders maak het aan.
         stock_collection.each do |sku,description|
             indien !Stockitem.find_by(:sku, sku)
                 si = Stockitem.new
                 si.sku = sku
                 si.is_alias = false
                 si.alias_van = ""
                 si.description = beschrijving
                 si.is_set = false
                 si.is_physical_item = true
                 si.weee_applicable = true
                 si.weight = 100
                 Ja, spaar!
             einde
         einde
     einde #end van get macro

einde #en einde van module

Opmerkingen:

  • Sam's documentatie legt delen van deze code uit, ik zal alleen ingaan op de struikelblokken:
  • sis = Stockitem.all({"is_alias" => {"$eq" => false}}) - deze code zoekt naar Stockitems (Mongo ORM zal zorgen voor de DB naam voor u, enz.)
  • si = Stockitem.find(BSON::ObjectId.new pk)
    • Merk op dat hier BSON::ObjectId.new wordt gebruikt om de id te reconstrueren uit de sleutel pk die werd doorgegeven
  • de retourcode 200 bij succes en 400 bij falen is voor x-editable om te weten of de bewerking geslaagd is

Gedrag van si.save

  • si.save! geeft een foutmelding als opslaan niet mogelijk is
  • si.save zal true teruggeven als opslaan mogelijk was, en false als het niet mogelijk was

stock.ecr, het HTML sjabloon

<% content_for “name” do %>
     <%= name %>
<% end %>

<% 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”">
             Opgelet: alias'ed items worden niet uit de databank geladen.
            


                 <thead>
                     <tr role="”row”">
                         <th class="”sorting_asc”" tabindex="”0″">sku</th>
                         <th>beschrijving</th>
                         <th>is_alias?</th>
                         <th>alias_van</th>
                         <th>is_set?</th>
                         <th>subitems</th>
                         <th>is_physical_item?</th>
                         <th>wee_toepasselijk?</th>
                         <th>gewicht (g)</th>
                     </tr>
                 </thead>
                 <% sis.each do |si| %>
                
                    
                    
                    
                    
                    
                    
                    
                    
                    
                
                 <% end %>

           

tbd.<A HREF="#"
                             id=".weee_applicable"
                             class="weee_applicable editable"
                             data-type="select"
                             data-pk=""
                             data-value="true"
                             data-source="[{waarde: 'true', tekst: 'true'}, {waarde: 'false', tekst: 'false'}]"
                             data-url="/stock/edit/weee_applicable"
                             data-title="WEEE: "
                         >
g

         </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(functie(){
                         $next.editable('toon');
                     }, 300);
                 }
             });
         });
     }
</script>
<% end %>

Opmerkingen:

  • aangezien we onze JS scripts hierna toevoegen (ik zou dit moeten herwerken om een aangepaste script sectie aan het einde toe te voegen), moeten we ze verpakken in een window.onload functie - anders wordt $ niet gedefinieerd (jQuery snelkoppeling)
  • Merk op hoe we hier een functie gebruiken om naar het volgende bewerkbare te springen bij succes met opslaan
  • si._id.to_s.rchop - de BSON id wordt vertaald naar een string, en dan wordt het laatste vreemde teken (null terminator?) afgehakt. Dit is een belangrijk struikelblok!

lay-out.ecr (uittreksels)

<!DOCTYPE html>
<html>
<head>
   <meta charset=”utf-8″>
   <meta http-equiv=”X-UA-Compatible” content=”IE=edge”>
   TaxGod |
   <meta content=”width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no” name=”viewport”>
   <link rel=”stylesheet” href=”/bower_components/bootstrap/dist/css/bootstrap.min.css”>
   <link rel=”stylesheet” href=”/bower_components/font-awesome/css/font-awesome.min.css”>
   <link rel=”stylesheet” href=”/bower_components/Ionicons/css/ionicons.min.css”>
   <link rel=”stylesheet” href=”/dist/css/AdminLTE.min.css”>
   <link rel=”stylesheet” href=”/dist/css/skins/skin-blue.min.css”>
   <link rel=”stylesheet” href=”/danielm_uploader/css/jquery.dm-uploader.min.css”>
   <link rel=”stylesheet” href=”/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css”>
   <link rel=”stylesheet” href=”/tg_css/bootstrap-editable.css”>
   <link rel=”stylesheet” href=”/tg_css/picockpit.css”>
   <link rel=”stylesheet” href=”/tg_css/dropzone.css”> 
   <!–[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="stylesheet"
         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 ::
         God zijn is leuk.
       </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>

Verdere referenties