BasicModelManagerUnit.java
package eu.javaexperience.webgsdb.frontend;
import static eu.jvx.js.lib.bindings.H.H;
import eu.javaexperience.query.F;
import eu.javaexperience.query.LogicalGroup;
import hu.ddsi.java.database.GenericStorage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.teavm.jso.browser.Window;
import org.teavm.jso.dom.events.Event;
import org.teavm.jso.dom.events.EventListener;
import org.teavm.jso.dom.html.HTMLElement;
import org.teavm.jso.dom.xml.Element;
import eu.javaexperience.classes.ClassDescriptor;
import eu.javaexperience.classes.ClassFieldDescriptor;
import eu.javaexperience.classes.dinamic.DinamicModelManager;
import eu.javaexperience.collection.map.OneShotMap;
import eu.javaexperience.datareprez.DataObject;
import eu.javaexperience.datareprez.DataReprezTools;
import eu.javaexperience.functional.BoolFunctions;
import eu.javaexperience.gsdbrpc.WebDbModel;
import eu.javaexperience.gsdbrpc.WebGsdbTools;
import eu.javaexperience.gsdbrpc.api.ModelManager;
import eu.javaexperience.interfaces.simple.getBy.GetBy1;
import eu.javaexperience.interfaces.simple.getBy.GetBy2;
import eu.javaexperience.interfaces.simple.getBy.GetBy3;
import eu.javaexperience.interfaces.simple.getBy.GetBy4;
import eu.javaexperience.interfaces.simple.publish.SimplePublish1;
import eu.javaexperience.interfaces.simple.publish.SimplePublish2;
import eu.javaexperience.patterns.creational.builder.PublisherBuilder;
import eu.javaexperience.reflect.CastTo;
import eu.javaexperience.reflect.Mirror;
import eu.javaexperience.teasite.frontend.table.GeneratedResultTable;
import eu.javaexperience.teasite.frontend.table.ResultTableGenerator;
import eu.javaexperience.teasite.frontend.table.ResultTableGenerator.TableField;
import eu.javaexperience.verify.TranslationFriendlyValidationEntry;
import eu.javaexperience.verify.ValidationResult;
import eu.javaexperience.webgsdb.activity.ModelEditPage;
import eu.javaexperience.webgsdb.commons.FieldExtraAttributes;
import eu.javaexperience.webgsdb.commons.FrontendFieldEditContext;
import eu.javaexperience.webgsdb.test.WebGsdbFrontendTestTools;
import eu.jvx.js.lib.ImpersonalisedHtml;
import eu.jvx.js.lib.TeaVmTools;
import eu.jvx.js.lib.bindings.H;
import eu.jvx.js.lib.bindings.VanillaTools;
import eu.jvx.js.lib.bindings.VanillaTools.ClassList;
import eu.jvx.js.lib.style.StyleTools.StyleAlaCarte;
import eu.jvx.js.lib.style.TbsStyle;
import eu.jvx.js.lib.ui.FrontendTools;
import eu.jvx.js.lib.ui.component.func.HtmlDataContainer;
import eu.jvx.js.lib.ui.component.func.HtmlDataContainerTools;
import eu.jvx.js.lib.ui.component.table.SimpleTableStructureManager;
import eu.jvx.js.lib.ui.component.table.TableRow;
import eu.jvx.js.tbs.TbsGlyph;
import eu.jvx.js.tbs.ui.TbsLayoutTools;
import eu.jvx.js.tbs.ui.TbsTools;
import eu.jvx.js.tbs.ui.TbsLayoutTools.SimpleFormRow;
import eu.teasite.frontend.api.ApiClient;
public class BasicModelManagerUnit<B extends WebDbModel, D extends B> implements ImpersonalisedHtml
{
public ClassDescriptor cls;
public ModelManager<B> acc;
public ApiClient api;
public BasicModelManagerUnit(ClassDescriptor cls, ModelManager<B> acc, ApiClient api)
{
this.cls = cls;
this.acc = acc;
this.api = api;
tableGenerator = new ResultTableGenerator<>();
tableGenerator.getFields = new SimplePublish1<List<TableField<D>>>()
{
@Override
public void publish(List<TableField<D>> a)
{
{
TableField<D> f = new TableField<D>();
f.fieldName = "available_operations_column";
f.renderLabel = new GetBy1<HTMLElement, TableField<D>>()
{
@Override
public HTMLElement getBy(TableField<D> a)
{
if(hasPermission("create") && BasicModelManagerUnit.this.showAdd)
{
return addNewModelButton;
}
return null;
}
};
f.renderField = (x, b, c)-> generateOperations.getBy(BasicModelManagerUnit.this, getPermissions(), c, b);
a.add(f);
}
{
TableField<D> f = new TableField<D>();
f.fieldName = "do";
f.renderLabel = new GetBy1<HTMLElement, TableField<D>>()
{
@Override
public HTMLElement getBy(TableField<D> a)
{
H ret = new H("div");
if(showId)
{
ret.attrs("#text", "id");
}
return ret.getHtml();
}
};
f.renderField = new GetBy3<HTMLElement, ResultTableGenerator.TableField<D>, TableRow, D>()
{
@Override
public HTMLElement getBy(TableField<D> a, TableRow b, D c)
{
String id = String.valueOf(c.get("do"));
H ret = new H("div").attrs("data-name", "do", "data-value", id);
if(showId)
{
ret.attrs("#text", id);
}
return ret.getHtml();
}
};
a.add(f);
}
List<? extends ClassFieldDescriptor> fds = cls.getAllField();
for(ClassFieldDescriptor fd:fds)
{
if(Boolean.TRUE == selectField.getBy("show", fd))
{
if(!fd.isUserAccessible())
{
continue;
}
FieldExtraAttributes mfd = FieldExtraAttributes.parse(fd);
if(null == mfd || mfd.userMaySee)
{
TableField<D> add = new TableField<D>();
add.fieldName = fd.getName();
add.getExtraDataMap().put("fd", fd);
add.renderLabel = (f)->
{
String name = f.fieldName;
if(null != translateFieldName)
{
name = translateFieldName.getBy(name);
}
return new H("span").attrs("#text", name).getHtml();
};
//CUSTOM_FIELD_NAME;
add.renderField = (GetBy3) ModelTableGeneratorTools.DEFAULT_MODEL_FIELD_RENDERER;
a.add(add);
}
}
}
}
};
}
public GetBy2<Boolean, String, ClassFieldDescriptor> selectField = (a,b)->true;
public boolean hasPermission(String perm)
{
return getPermissions().contains(perm);
}
protected Set<String> perms = null;
public Set<String> getPermissions()
{
if(null == perms)
{
perms = acc.getPermissions();
}
return perms;
}
public HTMLElement container = null;
public HTMLElement filterBox = new H("div").getHtml();
public HTMLElement resultTableBox = new H("div").getHtml();
protected HTMLElement addNewModelButton = new H("span").attrs("class", WebGsdbFrontendTestTools.CSS_BTN_NEW_MODEL).style
(
TbsGlyph.PLUS_SIGN,
TbsStyle.BTN_SUCCESS
)
.onClick((e)->popupAddNewModel()).getHtml();
protected D createNewModel()
{
try
{
return acc.createModel(cls);
}
catch(Exception e)
{
Mirror.propagateAnyway(e);
return null;
}
}
public GetBy1<String, String> txtCreateNew = (s)->"Create new "+s;
public void popupAddNewModel()
{
popupWithDataAndTrigger
(
WebGsdbFrontendTestTools.CSS_CREATE_NEW_MODEL_FORM,
null,
createNewModel(),
txtCreateNew.getBy(cls.getClassName()),
new SimplePublish2<DataObject, SimplePublish1<ValidationResult<TranslationFriendlyValidationEntry>>>()
{
@Override
public void publish(DataObject data, SimplePublish1<ValidationResult<TranslationFriendlyValidationEntry>> b)
{
ValidationResult<TranslationFriendlyValidationEntry> ret = acc.create(cls, data);
b.publish(ret);
}
}
);
}
public GetBy2<String, String, D> txtOp = (op, model)->
{
switch (op)
{
case "delete": return "Do you really want to delete: "+model.toString();
case "modify": return "Modifying "+model.toString();
}
return "translate(op: "+op+", model: "+model+")";
};
public GetBy1<String, String> translate = (s)->
{
if(null != s)
{
switch (s)
{
case "btn_cancel": return "Cancel";
case "btn_close": return "Close";
case "btn_save": return "Save";
case "btn_delete": return "Delete";
case "op_deletion_failed": return "Deletion failed";
case "title_confirm_deletion": return "Confirm deletion";
}
}
return "translate("+s+")";
};
public void popupConfirmDelete(D model, TableRow row)
{
H content = H("div");
content.addChilds(new H("div").attrs("#text", txtOp.getBy("delete", model)));
H header = H("h4");
header.attrs("#text", translate.getBy("title_confirm_deletion"));
H footer = H("div");
footer.addChilds(TbsTools.createModalCloseButton(translate.getBy("btn_cancel")));
footer.addChilds(H("button").attrs("class", "op_delete", "#text", translate.getBy("btn_delete")).style(TbsStyle.BTN_DANGER));
VanillaTools.bindListenerToArea
(
footer.getHtml(),
"click",
".op_delete",
FrontendTools.wrapProcessEventWithThread
(
new EventListener<Event>()
{
@Override
public void handleEvent(Event a)
{
ValidationResult<TranslationFriendlyValidationEntry> ret = acc.delete
(
cls,
(Long) CastTo.Long.cast(model.get("do")),
null
);
if(ret.valid)
{
TbsTools.closeModal(content.getHtml());
VanillaTools.remove(row.getHtml());
}
else
{
TbsTools.modalMessage(translate.getBy("op_deletion_failed"), renderMessages(ret.reportEntries));
}
}
}
)
);
TbsTools.modal
(
header.getHtml(),
content.getHtml(),
footer.getHtml()
);
}
public String renderMessages
(
Collection<TranslationFriendlyValidationEntry> reportEntries
)
{
StringBuilder sb = new StringBuilder();
for(TranslationFriendlyValidationEntry e:reportEntries)
{
sb.append(renderEntry(e));
}
return sb.toString();
}
public String renderEntry(TranslationFriendlyValidationEntry ent)
{
return ent.translationSymbol;
}
public void popupEdit(D model, TableRow row)
{
popupWithDataAndTrigger
(
WebGsdbFrontendTestTools.CSS_MODEL_EDIT_FORM,
new OneShotMap<String, String>("do", model.get("do").toString()),
model,
txtOp.getBy("modify", model),
new SimplePublish2<DataObject, SimplePublish1<ValidationResult<TranslationFriendlyValidationEntry>>>()
{
@Override
public void publish(DataObject data, SimplePublish1<ValidationResult<TranslationFriendlyValidationEntry>> b)
{
ValidationResult<TranslationFriendlyValidationEntry> a = acc.update(cls, data);
if(a.valid)
{
DataReprezTools.copyInto(model, data);
model.set("lastModify", System.currentTimeMillis());
ResultTableGenerator.updateRow(lastResultTable.fields, row, model);
}
b.publish(a);
}
}
);
}
static
{
//urchin: never runs on frontend but the TeaVm optimizer don't know about that, and so it keep the related fields/methods
if(!TeaVmTools.IS_FRONTEND)
{
HtmlDataContainer<String> c = HtmlDataContainerTools.browserDatetimeLocal(null);
c.setData("");
}
}
/* public void updateRowBy(TableRow row, DataLike obj)
{
for(TableField<D> k:lastResultTable.fields)
{
TableCell cell = row.getCellByName(k.fieldName);
HTMLElement h = cell.getHtml();
h.clear();
HTMLElement el = k.renderField.getBy(k, row, (D) obj);
if(null != el)
{
h.appendChild(el);
}
}
}
*/
/**
*
* TODO extra fileds (id),
* TODO custom value setting.
* TODO add validation error
*
*
* TODO externalize to customizable class
*/
protected void popupWithDataAndTrigger
(
String extraCss,
Map<String, String> extraAttributes,
D model,
//GetBy2<String, D, String> getModelField,
String title,
SimplePublish2<DataObject, SimplePublish1<ValidationResult<TranslationFriendlyValidationEntry>>> onSave
)
{
H content = H("div");
if(null != extraCss)
{
content.attrs("class", extraCss);
}
PublisherBuilder<SimpleFormRow, HTMLElement, Void> builder = TbsLayoutTools.buildForm();
builder.initialize(null);
if(null != extraAttributes)
{
for(Entry<String, String> kv:extraAttributes.entrySet())
{
builder.publish(new SimpleFormRow(null, null, H("input").attrs("type", "hidden", "name", kv.getKey(), "value", kv.getValue()).getHtml()));
}
}
FrontendFieldEditContext ctx = new FrontendFieldEditContext<>();
ctx.acc = acc;
ctx.api = api;
ctx.gdb = GenericStorage.getOwnerDatabase(model);
ctx.model = model;
List<ClassFieldDescriptor> fields = new ArrayList<>();
for(ClassFieldDescriptor f:cls.getAllField())
{
if(Boolean.TRUE == selectField.getBy("update", f))
{
fields.add(f);
}
}
WebGsdbTools.generateModelEditor
(
builder,
fields,
null == translateFieldName? fd->fd.getTranslationSymbol():fd->translateFieldName.getBy(fd.getName()),
model,
ctx
);
content.addChilds(builder.getResult());
H header = H("h4");
header.attrs("#text", title);
H footer = H("div");
footer.addChilds(TbsTools.createModalCloseButton(translate.getBy("btn_close")));
footer.addChilds(H("button").attrs("class", "op_save", "#text", translate.getBy("btn_save")).style(TbsStyle.BTN_SUCCESS));
VanillaTools.bindListenerToArea
(
footer.getHtml(),
"click",
".op_save",
FrontendTools.wrapProcessEventWithThread
(
new EventListener<Event>()
{
@Override
public void handleEvent(Event a)
{
DataObject data = FrontendTools.serializeInputsInArea(content.getHtml());
onSave.publish(data, new SimplePublish1<ValidationResult<TranslationFriendlyValidationEntry>>()
{
@Override
public void publish(ValidationResult<TranslationFriendlyValidationEntry> a)
{
if(a.valid)
{
TbsTools.closeModal(content.getHtml());
}
else
{
Window.alert("error");
//TODO show valdation entries
}
}
});
}
}
)
);
TbsTools.modal
(
header.getHtml(),
content.getHtml(),
footer.getHtml()
);
}
public GetBy4<HTMLElement, BasicModelManagerUnit<B, D>, Set<String>, D, TableRow> generateOperations = (table, permissions, model, row)->
{
return new H("div").addChilds
(
!permissions.contains("update")?null:new H("span").style(TbsGlyph.PENCIL, TbsStyle.BTN_DEFAULT, StyleAlaCarte.FS_1, StyleAlaCarte.MARGIN_1).onClick((e)->popupEdit(model, row)),
!permissions.contains("delete")?null:new H("span").style(TbsGlyph.TRASH, TbsStyle.BTN_DEFAULT, StyleAlaCarte.FS_1, StyleAlaCarte.MARGIN_1).onClick((e)->popupConfirmDelete(model, row))
).getHtml();
};
protected static GetBy1 CUSTOM_FIELD_NAME = new GetBy1<HTMLElement, TableField>()
{
@Override
public HTMLElement getBy(TableField a)
{
ClassFieldDescriptor fd = (ClassFieldDescriptor) a.getExtraDataMap().get("fd");
String text = "-";
if(null != fd)
{
text = fd.getTranslationSymbol();
text = WebGsdbTools.translate(text);
}
return new H("span").attrs("#text", text).getHtml();
}
};
public boolean showId = true;
public ResultTableGenerator<D> tableGenerator;
protected GeneratedResultTable<D> lastResultTable = null;
protected GeneratedResultTable<D> renderTable(Iterable<D> arr)
{
GeneratedResultTable<D> ret = tableGenerator.generate(SimpleTableStructureManager.INSTANCE, arr);
ClassList cl = VanillaTools.getClassList(ret.html);
cl.add("table");
cl.add("table-hover");
cl.add("table-striped");
cl.add("table-responsive");
return ret;
}
protected SimplePublish1<List<D>> reRenderTable = new SimplePublish1<List<D>>()
{
@Override
public void publish(List<D> a)
{
try
{
lastResultTable = renderTable(a);//ModelTableGeneratorTools.generateResultTable(cls, a, customizeResultTable);
setContent(new H(lastResultTable.html));
}
catch (Throwable e)
{
e.printStackTrace();
}
}
};
public boolean showFilter = true;
protected LogicalGroup filterCriteria = F.gt.is("do", -1);
public boolean showAdd = true;
public SimplePublish2<BasicModelManagerUnit<B, D>, List<D>> afterResultRendered = null;
public GetBy1<String, String> translateFieldName;
public void resetFilterCriteria(boolean apply)
{
setFilterCriteria(F.gt.is("do", -1), apply);
}
public void setFilterCriteria(LogicalGroup lg, boolean apply)
{
filterCriteria = lg;
if(apply)
{
refresh();
}
}
public LogicalGroup getFilterCriteria()
{
return filterCriteria;
}
public void refresh()
{
loadWithFilters(filterCriteria);
}
protected void loadWithFilters(LogicalGroup lg)
{
FrontendTools.runOnThread
(
()->
{
init();
try
{
List<D> res = acc.select(cls, lg);
reRenderTable.publish(res);
if(null != afterResultRendered)
{
afterResultRendered.publish(this, res);
}
}
catch (Exception e)
{
Mirror.propagateAnyway(e);
}
}
);
}
public void reset()
{
container = null;
}
public void init()
{
if(null != container)
{
return;
}
filterBox = new H("div").addChilds
(
!showFilter?null:new H("button").style(TbsGlyph.REFRESH, TbsStyle.BTN_DEFAULT, StyleAlaCarte.FS_1, StyleAlaCarte.MARGIN_1).onClick((e)->refresh()).getHtml()
).getHtml();
container = null;
container = new H("div").addChilds
(
filterBox,
resultTableBox
).getHtml();
}
@Override
public Object getImpersonator()
{
return this;
}
@Override
public Element getHtml()
{
if(null == container)
{
init();
}
return container;
}
public static BasicModelManagerUnit createEditorForClass(ApiClient api, String namespace, String cls)
{
return createEditorForClass(ModelEditPage.createModelManager(api, namespace), api, cls);
}
public static BasicModelManagerUnit createEditorForClass(DinamicModelManager modelManager, ApiClient api, String cls)
{
List<ClassDescriptor> models = modelManager.getManagedModels();
for(ClassDescriptor model:models)
{
if(model.getClassName().equals(cls))
{
return new BasicModelManagerUnit<>(model, modelManager, api);
}
}
throw new RuntimeException("No manager class found with name: "+cls);
}
public void setContent(H content)
{
resultTableBox.clear();
new H(resultTableBox).addChilds(content);
}
public ModelManager<B> getDatabase()
{
return acc;
}
}