Aufbau eines persistenten Inline-Editing-Erlebnisses mit Crystal, MongoDB (datanoise / sam0x17)

Da die Dokumentation noch spärlich ist, möchte ich sie gerne ergänzen.

Dies ist das, was ich derzeit baue:

Bild

Die einzelnen Felder werden mit einem Inline-Editor bearbeitet werden können, der automatisch im Backend gespeichert wird, ohne dass die gesamte Seite neu geladen werden muss.

Bild

Der Sinn des Ganzen ist ein Backend für mein Unternehmen, um einige Prozesse zu automatisieren, Lagerpreise zu berechnen, Dinge auf Lager zu halten, etc. Im Grunde eine Lageranwendung, die auf mich zugeschnitten ist!

Verwendete Technologien

  • Kristall
  • Kemal
  • VerwalterLTE 2
    • Bootstrap 3
    • dataTables
  • x-editable - eine wirklich großartige Bibliothek für Inline-Editing, die leider seit einiger Zeit nicht mehr gepflegt wird
    • siehe Beispiele für Bootstrap 3 hier
  • MongoDB
  • Sublime Text für die Bearbeitung Lächeln
  • Robo 3T für ein GUI für MongoDB

Wichtige Bits

Bild

MongoORM verwendet _id als Hauptindex. Bitte beachten Sie den Unterstrich!

Die _id ist NICHT eine Zeichenkette. Sie ist vom Typ ObjectId

Daher muss sie konstruiert werden. Wenn Sie sie aus einer früheren id konstruieren wollen, verwenden Sie diesen Code:

mongo_id = BSON::ObjectId.new my_string_id

wobei my_string_id ein String und mongo_id eine ObjectId ist.

Der Code

config/database.yml

ist für die Einrichtung der Kommunikation mit Ihrer Mongo DB-Instanz verantwortlich. Bitte beachten Sie Sams Dokumentation.

stock.cr, verantwortlich für die Definition von Stockitem, Subitem und den entsprechenden Kemal-Handlern:

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

Modul Bestand
     Makros einbeziehen

    class Itemgroup < Mongo::ORM::Document #used für die Gruppierung, z.B. Pimoroni Produkte, Versand, etc.
         Feld Gruppe : String
         has_many :stockitems 1TP3Bemerken Sie, dass ein Stockitem jeweils nur zu einer ItemGroup gehören kann.
     Ende

    1TP3Dies schließt auch andere Artikel ein (Versand, digitale Downloads, etc.)
     Klasse Stockitem < Mongo::ORM::Document
         Feld sku : String
         Feldbeschreibung : String
         Feld is_alias : Bool
         Feld alias_of : String 1TP3Wenn dieses Feld gesetzt ist, werden keine anderen Definitionen aus diesem Element verwendet, sondern es wird das verknüpfte Hauptelement verwendet
         field is_set : Bool # wenn diese Position eine Menge von anderen Positionen ist
         embeds_many :subitems # sollte nur gesetzt werden, wenn is_set = true
         field is_physical_item : Bool
         field weee_applicable : Bool #can be either: paid for someone else, or item is not electronics, ...
         Feldgewicht : Int32 # in g
         Zeitstempel
     Ende

    Klasse Subitem < Mongo::ORM::EmbeddedDocument
         Feld sku : String #a Verweis auf den Hauptbestandteil. NB: Dies könnte wahrscheinlich auf eine schönere Art und Weise mit belongs_to überarbeitet werden ? z.B. belongs_to :itemref, class_name: Stock::Stockitem
         Feld Anmerkung : String
         Feld Betrag : Int32
     Ende

    get "/stock/show" do |env|
         #https://github.com/datanoise/mongo.cr
         sis = Stockitem.all({"is_alias" => {"$eq" => false}})
         name = "Bestand :: Anzeigen & Bearbeiten"
         mrender "Bestand"
     Ende

    post "/stock/edit/:what" do |env|
         pp "Eingabe der Bearbeitung, mit Ziel " + env.params.url["was"]
         pp env.params.body
         if env.params.body.has_key?("pk") && env.params.body.has_key?("value")
             pp "Innerhalb von pk & Wert"
             pk = env.params.body["pk"]?.as(String)
             Wert = env.params.body["Wert"]?.as(String)
             if si = Stockitem.find(BSON::ObjectId.new pk)   
                 case env.params.url["what"]
                 wenn "sku"
                     si.sku = Wert
                 wenn "Beschreibung"
                     si.description = Wert
                 wenn "is_alias"
                     si.is_alias = Wert == "true" ? true : false
                 wenn "alias_von"
                     si.alias_of = Wert
                 wenn "is_set"
                     si.is_set = Wert == "true" ? true : false
                 wenn "is_physical_item"
                     si.is_physical_item = Wert == "true" ? true : false
                 wenn "weee_applicable"
                     pp "weee_applicable"
                     si.weee_applicable = Wert == "true" ? true : false
                 Ende
                
                 wenn si.save
                     pp "inside si.save"
                     env.response.status_code = 200
                 sonst
                     env.response.status_code = 400
                 Ende
             sonst
                 env.response.status_code = 400   
             Ende
         sonst
             env.response.status_code = 400
         Ende
     Ende

    #aus WEEE-Einträgen generieren
     get "/stock/generate/:year" do |env|
         1TP3Die gesamte Datenbank durchsuchen, die Elemente sammeln und eine Liste anzeigen
         stock_collection = Hash(String,String).new
         Rechnungen = WEEE::Invoice.all({"Jahr"=>env.params.url["Jahr"].to_i32})       
         rechnungen.each do |Rechnung|
             #p "Bearbeitung der Rechnung #{Rechnungsnummer}"
             1TP3Bitte beachten Sie, dass wir hier möglicherweise nicht alle Rechnungen erhalten, da easybill eine andere Sicht auf die Daten hat (Import)
             invoice.items.each do |item|
                 if !item.description.nil? && !item.number.nil?
                     Schlüssel = item.number == "" ? item.description : item.number
                     if !key.nil?
                         if !stock_collection.has_key?(key)
                             description = ""
                             if !item.description.nil?
                                 description = item.description
                             Ende                   
                             #stock_collection[Schlüssel] = item.description
                             if !description.nil?
                                 stock_collection[key] = Beschreibung
                             Ende
                         end # end of !stock_collection.has_key?
                     end # end of if !key.nil?
                 Ende
             end #end of invoice.items.each do
         Ende #end von invoices.each
         1TP3Prüfen Sie, ob dieser nicht bereits in unserer Datenbank vorhanden ist, sonst erstellen Sie ihn.
         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 = Beschreibung
                 si.is_set = false
                 si.is_physical_item = true
                 si.weee_applicable = true
                 si.gewicht = 100
                 si.save!
             Ende
         Ende
     Ende #end des Makros get

Ende 1TP3Ende des Moduls

Anmerkungen:

  • In Sams Dokumentation werden Teile dieses Codes erklärt, ich werde nur auf die Stolpersteine eingehen:
  • sis = Stockitem.all({"is_alias" => {"$eq" => false}} - dieser Code sucht nach Stockitems (Mongo ORM kümmert sich für Sie um den DB-Namen usw.)
  • si = Stockitem.find(BSON::ObjectId.new pk)
    • Beachten Sie, dass hier BSON::ObjectId.new verwendet wird, um die ID aus dem übergebenen Schlüssel pk zu rekonstruieren
  • Rückgabecode 200 bei Erfolg und 400 bei Misserfolg, damit x-editable weiß, ob die Bearbeitung erfolgreich war

Verhalten von si.save

  • si.save! gibt einen Fehler aus, wenn das Speichern nicht möglich ist
  • si.save gibt true zurück, wenn das Speichern möglich war, und false, wenn es nicht möglich war.

stock.ecr, die HTML-Vorlage

<% 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”">
             Bitte beachten Sie: Alias-Einträge werden nicht aus der Datenbank geladen.
            


                 <thead>
                     <tr role="”row”">
                         <th class="”sorting_asc”" tabindex="”0″">sku</th>
                         <th>Beschreibung</th>
                         <th>is_alias?</th>
                         <th>alias_von</th>
                         <th>is_set?</th>
                         <th>Unterpunkte</th>
                         <th>is_physical_item?</th>
                         <th>weee_applicable?</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="[{value: 'true', text: 'true'}, {value: 'false', text: '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(Grund === 'speichern' || Grund === 'noch ändern'){
                     var $next = $(this).closest('tr').next().find('.editable');
                     setTimeout(function(){
                         $next.editable('show');
                     }, 300);
                 }
             });
         });
     }
</script>
<% end %>

Anmerkungen:

  • da wir unsere JS-Skripte danach hinzufügen (ich sollte dies überarbeiten, um einen benutzerdefinierten Skriptabschnitt am Ende hinzuzufügen), müssen wir sie in eine window.onload-Funktion verpacken - andernfalls ist $ nicht definiert (jQuery-Abkürzung)
  • Beachten Sie, wie wir hier eine Funktion verwenden, um bei erfolgreicher Speicherung zum nächsten bearbeitbaren Eintrag zu springen.
  • si._id.to_s.rchop - die BSON id wird in eine Zeichenkette übersetzt, und dann wird das letzte seltsame Zeichen (Nullterminator?) abgeschnitten. Dies ist ein wichtiger Stolperstein!

layout.ecr (Auszüge)

<!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 ::
         Gott zu sein macht Spaß.
       </h1>
     </section>

    <!– Main content –>
    

     <%= yield_content “main” %>

    </section>
     <!– /.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>

Weitere Referenzen