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:
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.
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
- Mongo ORM von sam0x17
- die sich wiederum auf datanoise's Mongo-Treiber für Crystal (mongo.cr)
- Sublime Text für die Bearbeitung
- Robo 3T für ein GUI für MongoDB
Wichtige Bits
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 einbeziehenclass 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.
Ende1TP3Dies 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
EndeKlasse 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
Endeget "/stock/show" do |env|
#https://github.com/datanoise/mongo.cr
sis = Stockitem.all({"is_alias" => {"$eq" => false}})
name = "Bestand :: Anzeigen & Bearbeiten"
mrender "Bestand"
Endepost "/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 getEnde 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.)
- beachten Sie, dass es mir nicht gelungen ist, ({}) zu setzen - ich werde dies weiter untersuchen müssen
- diese Syntax entspricht im Wesentlichen dem, was MongoDB erwartet. siehe https://docs.mongodb.com/manual/reference/operator/query/
- 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| %>
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
<% end %>
</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
- https://github.com/sam0x17/mongo_orm/blob/893cd520cb90f7049d53b809ada30198deeed0f7/spec/fields_spec.cr
- https://github.com/datanoise/mongo.cr/commit/86c9e530c0ac980ed7b15e653746bc5b6f2527fc
- https://github.com/amberframework/granite/blob/master/docs/getting_started.md
- https://docs.mongodb.com/manual/reference/operator/query/
- https://github.com/datanoise/mongo.cr/issues/19
- https://github.com/datanoise/mongo.cr/blob/86c9e530c0ac980ed7b15e653746bc5b6f2527fc/src/mongo/collection.cr -> Definition von speichern in mongo.cr
- https://github.com/sam0x17/mongo_orm/blob/015ae29a29ca8b80a98a531de891095bbd42d028/src/mongo_orm/persistence.cr -> Definition von speichern in mongo_orm