diff --git a/client/src/main/scala/ch/wsl/box/client/styles/BoxStyle.scala b/client/src/main/scala/ch/wsl/box/client/styles/BoxStyle.scala
index 036e0aa1..a2a05554 100644
--- a/client/src/main/scala/ch/wsl/box/client/styles/BoxStyle.scala
+++ b/client/src/main/scala/ch/wsl/box/client/styles/BoxStyle.scala
@@ -65,6 +65,7 @@ trait BoxStyle {
val headerTitle: StyleA
val linkHeaderFooter: StyleA
val fullHeightMax: StyleA
+ val fullHeightMaxForm: StyleA
val tableWrapper: StyleA
val fullHeight: StyleA
val loading: StyleA
diff --git a/client/src/main/scala/ch/wsl/box/client/styles/GlobalStyles.scala b/client/src/main/scala/ch/wsl/box/client/styles/GlobalStyles.scala
index 8a0b0b85..e9fdf0ab 100755
--- a/client/src/main/scala/ch/wsl/box/client/styles/GlobalStyles.scala
+++ b/client/src/main/scala/ch/wsl/box/client/styles/GlobalStyles.scala
@@ -831,7 +831,16 @@ class GlobalStyles(settings:Settings,conf:StyleConf) extends StyleSheet.Inline()
override val fullHeightMax = style(
- height :=! "calc(100vh - 150px)",
+ height :=! "calc(100vh - 201px)",
+ media.maxWidth(600 px)(
+ height :=! "calc(100vh - 110px)",
+ //paddingBottom(70 px)
+ ),
+ overflow.auto
+ )
+
+ override val fullHeightMaxForm = style(
+ height :=! "calc(100vh - 229px)",
media.maxWidth(600 px)(
height :=! "calc(100vh - 110px)",
//paddingBottom(70 px)
@@ -1447,7 +1456,7 @@ class GlobalStyles(settings:Settings,conf:StyleConf) extends StyleSheet.Inline()
override val mapTable = style(
- height :=! "calc(100vh - 105px)",
+ height :=! "calc(100vh - 201px)",
media.maxWidth(600 px)(
height :=! "calc(100vh - 50px)",
),
@@ -1513,6 +1522,9 @@ class GlobalStyles(settings:Settings,conf:StyleConf) extends StyleSheet.Inline()
unsafeChild("svg")(
marginTop(-4 px),
svgStroke(c"#fff")
+ ),
+ &.focus(
+ borderColor(conf.colors.main).important
)
)
diff --git a/client/src/main/scala/ch/wsl/box/client/views/EntityFormView.scala b/client/src/main/scala/ch/wsl/box/client/views/EntityFormView.scala
index 2003c00a..4f52f2b5 100755
--- a/client/src/main/scala/ch/wsl/box/client/views/EntityFormView.scala
+++ b/client/src/main/scala/ch/wsl/box/client/views/EntityFormView.scala
@@ -924,7 +924,7 @@ case class EntityFormView(model:ModelProperty[EntityFormModel], presenter:Entity
if(showHeader && _form.isDefined) {
formHeader(nested,showId,_form.get).render
},
- div(BootstrapCol.md(12),if(showHeader) { ClientConf.style.fullHeightMax },
+ div(BootstrapCol.md(12),if(showHeader) { ClientConf.style.fullHeightMaxForm },
_form match {
case None => div()
diff --git a/client/src/main/scala/ch/wsl/box/client/views/EntityTableView.scala b/client/src/main/scala/ch/wsl/box/client/views/EntityTableView.scala
index 9226ae2c..e620b71e 100755
--- a/client/src/main/scala/ch/wsl/box/client/views/EntityTableView.scala
+++ b/client/src/main/scala/ch/wsl/box/client/views/EntityTableView.scala
@@ -1033,7 +1033,7 @@ case class EntityTableView(model:ModelProperty[EntityTableModel], presenter:Enti
sc.set(sc.get.flatMap{ f =>
if(f.title == oldPosition) Seq()
- else if(f.title == newPosition) metadata.table.find(_.title == oldPosition) ++ Seq(f)
+ else if(f.title == newPosition) metadata.fields.find(_.title == oldPosition) ++ Seq(f)
else Seq(f)
})
diff --git a/client/src/main/scala/ch/wsl/box/client/views/components/table/ExportTableDialog.scala b/client/src/main/scala/ch/wsl/box/client/views/components/table/ExportTableDialog.scala
index 6dd0cddc..f04819f4 100644
--- a/client/src/main/scala/ch/wsl/box/client/views/components/table/ExportTableDialog.scala
+++ b/client/src/main/scala/ch/wsl/box/client/views/components/table/ExportTableDialog.scala
@@ -64,7 +64,6 @@ class ExportTableDialog extends Logging {
Seq(
button(`type` := "button", onclick :+= ((e:Event) => open(modal,onOpen())), ClientConf.style.boxButton, "Export"),
-
)
}
diff --git a/client/src/main/scala/ch/wsl/box/client/views/components/widget/TristateWidget.scala b/client/src/main/scala/ch/wsl/box/client/views/components/widget/TristateWidget.scala
index d5c4d415..05bab67d 100644
--- a/client/src/main/scala/ch/wsl/box/client/views/components/widget/TristateWidget.scala
+++ b/client/src/main/scala/ch/wsl/box/client/views/components/widget/TristateWidget.scala
@@ -1,5 +1,6 @@
package ch.wsl.box.client.views.components.widget
import ch.wsl.box.client.services.ClientConf
+import ch.wsl.box.client.utils.ListenerManager
import io.circe._
import io.circe.syntax._
import io.udash._
@@ -12,12 +13,19 @@ import io.udash.bindings.modifiers.Binding
import io.udash.bootstrap.tooltip.UdashTooltip
import scalacss.ScalatagsCss._
import io.udash.css._
-import org.scalajs.dom.Event
+import org.scalajs.dom.{Event, KeyboardEvent}
case class TristateWidget(field:JSONField, data: Property[Json]) extends Widget with IsCheckBoxWithData {
val noLabel = field.params.exists(_.js("nolabel") == true.asJson)
+ val lm = new ListenerManager()
+ import lm._
+
+ override def killWidget(): Unit = {
+ lm.clearAll()
+ }
+
def jsToBool(json:Json):Option[Boolean] = field.`type` match {
case JSONFieldTypes.BOOLEAN => json.asBoolean
case JSONFieldTypes.NUMBER => json.asNumber.flatMap(_.toInt).map(_ == 1)
@@ -34,12 +42,25 @@ case class TristateWidget(field:JSONField, data: Property[Json]) extends Widget
case _ => Json.Null
}
+
+
def tristateCheckbox(booleanModel:Property[Option[Boolean]]) = {
+
+ def rotate() = {
+ booleanModel.set{
+ booleanModel.get match {
+ case Some(true) => Some(false)
+ case Some(false) => None
+ case None =>Some(true)
+ }}
+ }
+
// https://carsonf92.medium.com/introducing-the-three-state-checkbox-1b6f00b6ec89
val positive = raw("")
val negative = raw("")
val checkbox = span(
+ tabindex := 0,
`class`.bind(booleanModel.transform {
case Some(true) => Seq(ClientConf.style.tristateCheckBox.htmlClass,ClientConf.style.tristatePositive.htmlClass).mkString(" ")
case Some(false) => Seq(ClientConf.style.tristateCheckBox.htmlClass,ClientConf.style.tristateNegative.htmlClass).mkString(" ")
@@ -51,15 +72,21 @@ case class TristateWidget(field:JSONField, data: Property[Json]) extends Widget
case None => span().render
},
onclick :+= {(e:Event) =>
- booleanModel.set{
- booleanModel.get match {
- case Some(true) => Some(false)
- case Some(false) => None
- case None =>Some(true)
- }}
+
e.preventDefault()
}
- )
+ ).render
+
+ checkbox.listen("click", _ => rotate())
+ checkbox.listen("keydown",{
+ case ke: KeyboardEvent if ke.key == " " => {
+ rotate()
+ ke.stopPropagation()
+ ke.preventDefault()
+ }
+ case _ => true
+ })
+
checkbox
}
@@ -75,7 +102,7 @@ case class TristateWidget(field:JSONField, data: Property[Json]) extends Widget
div(
div(ClientConf.style.label50,if(!noLabel) { WidgetUtils.toLabel(field,WidgetUtils.LabelRight) } else frag()),
- tooltip(tristateCheckbox(booleanModel).render)._1
+ tooltip(tristateCheckbox(booleanModel))._1
)
}
@@ -83,7 +110,7 @@ case class TristateWidget(field:JSONField, data: Property[Json]) extends Widget
val booleanModel:Property[Option[Boolean]] = Property(None)
autoRelease(data.sync[Option[Boolean]](booleanModel)(js => jsToBool(js),bool => boolToJson(bool)))
- tristateCheckbox(booleanModel).render
+ tristateCheckbox(booleanModel)
}
override def json(): _root_.io.udash.ReadableProperty[Json] = data
diff --git a/client/src/main/scala/ch/wsl/box/client/views/components/widget/WidgetRegistry.scala b/client/src/main/scala/ch/wsl/box/client/views/components/widget/WidgetRegistry.scala
index ea6f6acd..fe3b2156 100644
--- a/client/src/main/scala/ch/wsl/box/client/views/components/widget/WidgetRegistry.scala
+++ b/client/src/main/scala/ch/wsl/box/client/views/components/widget/WidgetRegistry.scala
@@ -5,7 +5,7 @@ import ch.wsl.box.client.views.components.widget.array.{ChoicesWidget, MultiWidg
import ch.wsl.box.client.views.components.widget.boolean.SelectBooleanWidget
import ch.wsl.box.client.views.components.widget.child.{EditableTable, LookupFormWidget, SimpleChildFactory, TableChildFactory, TrasparentChild}
import ch.wsl.box.client.views.components.widget.geo.{MapChild, MapPointWidget, OlMapListWidget, OlMapWidget}
-import ch.wsl.box.client.views.components.widget.child.{EditableTable, LookupFormWidget, SimpleChildFactory, Spreadsheet, TableChildFactory, TrasparentChild}
+import ch.wsl.box.client.views.components.widget.child.{EditableTable, LookupFormWidget, SimpleChildFactory, Spreadsheet, TableChildFactory, TrasparentChild,ExportWidget}
import ch.wsl.box.client.views.components.widget.geo.{MapPointWidget, OlMapListWidget, OlMapWidget}
import ch.wsl.box.client.views.components.widget.labels.{HtmlWidget, LinkedFormWidget, LookupLabelWidget, StaticTextWidget, TitleWidget}
import ch.wsl.box.client.views.components.widget.lookup.{MultipleLookupWidget, PopupSelectWidget, SelectWidgetFactory}
@@ -56,6 +56,7 @@ object WidgetRegistry extends Logging {
LookupFormWidget,
LinkedFormWidget,
Spreadsheet,
+ ExportWidget,
FileSimpleWidgetFactory,
diff --git a/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/EditableTable.scala b/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/EditableTable.scala
index bfb8f2ba..a8c362d6 100644
--- a/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/EditableTable.scala
+++ b/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/EditableTable.scala
@@ -125,6 +125,7 @@ case class TableStyle(conf:StyleConf,columns:Int) extends StyleSheet.Inline {
object EditableTable extends ChildRendererFactory {
+
override def name: String = WidgetsNames.editableTable
@@ -133,6 +134,9 @@ object EditableTable extends ChildRendererFactory {
case class EditableTableRenderer(widgetParam:WidgetParams) extends ChildRenderer {
+
+ val tableId = s"table-${UUID.randomUUID()}"
+
val parentMetadata = widgetParam.metadata
import ch.wsl.box.client.Context._
@@ -412,17 +416,35 @@ object EditableTable extends ChildRendererFactory {
case _ => None
}
+ def setFocus(row:Int,col:Int):Boolean = Seq("select", "input").map { tagname =>
+ document
+ .querySelector(s" #$tableId tr[data-row='$row'] > td[data-column='$col']")
+ .getElementsByTagName(tagname).headOption match {
+ case Some(e: dom.HTMLElement) => {
+ e.focus()
+ true
+ }
+ case _ => false
+ }
+ }.exists(x => x)
+
def select(offsetRow:Int,offsetCol:Int) = {
for{
c <- column
r <- row
} yield {
- Seq("select","input").foreach { tagname =>
- document
- .querySelector(s"tr[data-row='${r + offsetRow}'] > td[data-column='${c + offsetCol}']")
- .getElementsByTagName(tagname).headOption.foreach { case e: dom.HTMLElement => e.focus() }
+ if(r + offsetRow >= entity.length) {
+ addItem(child,metadata.get)
+ val observer = Some(new MutationObserver({ (mutations, observer) => {
+ if(setFocus(r + offsetRow,c + offsetCol))
+ observer.disconnect()
+ } }))
+ observer.foreach(_.observe(document,MutationObserverInit(childList = true, subtree = true)))
+ } else {
+ setFocus(r + offsetRow,c + offsetCol)
}
+
}
e.stopImmediatePropagation()
e.preventDefault()
@@ -430,8 +452,16 @@ object EditableTable extends ChildRendererFactory {
}
e match {
- case ke:KeyboardEvent if ke.key == "Enter" || ke.key == "ArrowDown" => select(1,0)
+ case ke:KeyboardEvent if (ke.key == "Enter" && !column.exists(c => fields(metadata.get).lift(c).exists(_.lookup.isDefined))) || ke.key == "ArrowDown" => select(1,0)
case ke:KeyboardEvent if ke.key == "ArrowUp" => select(-1,0)
+ case ke:KeyboardEvent if ke.key == "Tab" => {
+ val lastRow = row.exists(r => r+1 == entity.length)
+ val lastCol = column.exists(c => c+1 == fields(metadata.get).length)
+ if(lastRow && lastCol) {
+ select(1,-column.getOrElse(0))
+ } else true
+
+ }
case _ => true
}
}
@@ -451,7 +481,7 @@ object EditableTable extends ChildRendererFactory {
val additionalColumns = if (write && !disableRemove) 1 else 0
val colWidth = (width := _colWidth(additionalColumns))
- val tab = table(tableStyle.table,
+ val tab = table(id := tableId, tableStyle.table,
thead(
for (field <- f) yield {
val name = colHeader(field)
diff --git a/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/ExportWidget.scala b/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/ExportWidget.scala
new file mode 100644
index 00000000..0ece83e7
--- /dev/null
+++ b/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/ExportWidget.scala
@@ -0,0 +1,51 @@
+package ch.wsl.box.client.views.components.widget.child
+
+import ch.wsl.box.client.views.components.table.{ExportParams, ExportTableDialog}
+import ch.wsl.box.client.views.components.widget.{ComponentWidgetFactory, Widget, WidgetParams}
+import ch.wsl.box.model.shared.{JSONField, JSONQuery, JSONQueryFilter, WidgetsNames}
+import ch.wsl.box.shared.utils.JSONUtils.EnhancedJson
+import io.udash.bindings.modifiers.Binding
+import scalatags.JsDom
+import scalatags.JsDom.all._
+
+
+
+object ExportWidget extends ComponentWidgetFactory {
+
+ override def name: String = WidgetsNames.`export`
+
+ override def create(params: WidgetParams): Widget = ExportWidgetImpl(params)
+
+ case class ExportWidgetImpl(params: WidgetParams) extends Widget {
+
+ val exportDialog = new ExportTableDialog
+
+ override def field: JSONField = params.field
+
+
+ def exportParams = for{
+ c <- field.child
+ m <- params.children.find(_.objId == c.objId)
+ } yield {
+ val childFilters = c.mapping.map(m => JSONQueryFilter.WHERE.eq(m.child,params.allData.get.get(m.parent)))
+ ExportParams(m,m.table,c.childQuery.getOrElse(JSONQuery.empty).filterWith(childFilters:_*))
+ }
+
+ override def killWidget(): Unit = {
+ super.killWidget()
+ exportDialog.clean()
+ }
+
+ override protected def show(nested:Binding.NestedInterceptor): JsDom.all.Modifier = {
+ exportParams match {
+ case Some(ep) => div(
+ exportDialog.render(nested,() => ep)
+ )
+ case None => div("Please check form definition, can't render the export")
+ }
+
+ }
+
+ override protected def edit(nested:Binding.NestedInterceptor): JsDom.all.Modifier = show(nested)
+ }
+}
diff --git a/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/Spreadsheet.scala b/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/Spreadsheet.scala
index 38f530c7..13507019 100644
--- a/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/Spreadsheet.scala
+++ b/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/Spreadsheet.scala
@@ -86,32 +86,17 @@ object Spreadsheet extends ComponentWidgetFactory {
case Some(value:JSONFieldLookupRemote) => value
case None => throw new Exception("Not a remote lookup")
}
- val allColumnId: Seq[String] = rawData.map{ row =>
- val keys = fieldLookup.map.localKeysColumn.map { local =>
- val value = row.js(local).fold(
- "null",
- bool => bool.toString,
- num => num.toString,
- str => s"'$str'",
- arr => arr.toString,
- obj => obj.toString
- )
- value
+ val allColumnId: Seq[Seq[Json]] = rawData.map{ row =>
+ fieldLookup.map.localKeysColumn.map { local =>
+ row.js(local)
}
- keys.mkString("(",",",")")
}
logger.debug(s"Loading dropdown for $allColumnId")
- val query =
- s"""
- | ${fieldLookup.map.foreign.keyColumns.mkString("(",",",")")} in ${allColumnId.mkString("(",",",")")}
- |""".stripMargin
+ val query = JSONQuery.filterWith(JSONQueryFilter.WHERE.inSet(fieldLookup.map.foreign.keyColumns,allColumnId))
- logger.debug(s"Generated where: $query")
- val values = JSONQuery.where(query)
-
- services.rest.lookup(EntityKind.FORM.kind,services.clientSession.lang(),childMetadata.get.name,field.name,values).map{ rows =>
+ services.rest.lookup(EntityKind.FORM.kind,services.clientSession.lang(),childMetadata.get.name,field.name,query).map{ rows =>
rows.map { row =>
row.id -> row.value.asJson
}.toMap
diff --git a/server/src/main/scala/ch/wsl/box/db/SQLComposer.scala b/server/src/main/scala/ch/wsl/box/db/SQLComposer.scala
index 896b74d3..434c125c 100644
--- a/server/src/main/scala/ch/wsl/box/db/SQLComposer.scala
+++ b/server/src/main/scala/ch/wsl/box/db/SQLComposer.scala
@@ -96,8 +96,36 @@ trait SQLCompose extends Logging {
.filter(_.nonEmpty)
}
+ if(jsonQuery.operator.contains(Filter.IN_SET)) {
+
+ val t = for{
+ colsJS <- io.circe.parser.parse(jsonQuery.column)
+ cols <- colsJS.as[Seq[String]]
+ valsJs <- io.circe.parser.parse(jsonQuery.value.getOrElse(""))
+ vals <- valsJs.as[Seq[Seq[Json]]]
+ } yield (cols,vals)
+
+ t match {
+ case Right((cols,vals)) => {
+
+ val allColumnId = vals.map(_.map { v =>
+ v.fold(
+ "null",
+ bool => bool.toString,
+ num => num.toString,
+ str => s"'$str'",
+ arr => arr.toString,
+ obj => obj.toString
+ )
+ }).mkString("(",",",")")
+
+
+ Some(sql""" #${cols.mkString("(\"","\",\"","\")")} in #${allColumnId.mkString("(",",",")")} """)
+ }
+ case Left(value) => throw new Exception(s"Fields ${jsonQuery.column} cannot be parsed for inset. $value")
+ }
- if(jsonQuery.operator.exists(o => Filter.multiEl.contains(o))) {
+ } else if(jsonQuery.operator.exists(o => Filter.multiEl.contains(o))) {
col.name match {
case "String" => filterMany(Some(splitAndTrim(v)))
case "Int" => filterMany[Int](Some(splitAndTrim(v).flatMap(_.toIntOption)))
@@ -218,14 +246,11 @@ trait SQLCompose extends Logging {
val kv = jsonQueryComposer()
// val nonEmptyFilters = query.filter.filter(isNonEmptyFilter)
- val where = query.validatedWhere match {
- case Some(whereClause) => sql""" where #${whereClause}"""
- case None => {
+ val where = {
val filters = query.filter.flatMap(kv)
if (filters.nonEmpty) {
filters.tail.foldLeft(concat(sql" where ", filters.head)) { case (builder, pair) => concat(builder, concat(sql" and ", pair)) }
} else sql""
- }
}
diff --git a/server/src/main/scala/ch/wsl/box/rest/metadata/box/FormUIDef.scala b/server/src/main/scala/ch/wsl/box/rest/metadata/box/FormUIDef.scala
index 8a014cc9..e18a5c0c 100644
--- a/server/src/main/scala/ch/wsl/box/rest/metadata/box/FormUIDef.scala
+++ b/server/src/main/scala/ch/wsl/box/rest/metadata/box/FormUIDef.scala
@@ -379,6 +379,7 @@ object FormUIDef {
WidgetsNames.editableTable,
WidgetsNames.trasparentChild,
WidgetsNames.spreadsheet,
+ WidgetsNames.export,
))),
lookup = Some(JSONFieldLookup.withExtractor(
"entity",
@@ -394,6 +395,7 @@ object FormUIDef {
WidgetsNames.editableTable,
WidgetsNames.trasparentChild,
WidgetsNames.spreadsheet,
+ WidgetsNames.export,
))),
lookup = Some(JSONFieldLookup.withExtractor(
"child_form_uuid",
@@ -416,6 +418,7 @@ object FormUIDef {
WidgetsNames.editableTable,
WidgetsNames.trasparentChild,
WidgetsNames.spreadsheet,
+ WidgetsNames.export,
)))
),
CommonField.condition,
diff --git a/server/src/main/scala/ch/wsl/box/rest/routes/v1/Access.scala b/server/src/main/scala/ch/wsl/box/rest/routes/v1/Access.scala
index 71ef4861..47664d61 100644
--- a/server/src/main/scala/ch/wsl/box/rest/routes/v1/Access.scala
+++ b/server/src/main/scala/ch/wsl/box/rest/routes/v1/Access.scala
@@ -48,10 +48,21 @@ case class Access(session:BoxSession)(implicit ec:ExecutionContext,services:Serv
}
}
+ val boxTableRowAccess = pathPrefix(EntityKind.BOX_FORM.kind | EntityKind.BOX_TABLE.kind) {
+ pathPrefix(Segment) { table =>
+ path("row-access") {
+ post {
+ complete(true) // no RLS on Box schema
+ }
+ }
+ }
+ }
+
val route = pathPrefix("access") {
boxTableAccess ~
tableAccess ~
- rowAccess
+ rowAccess ~
+ boxTableRowAccess
}
}
diff --git a/shared/src/main/scala/ch/wsl/box/model/shared/JSONQuery.scala b/shared/src/main/scala/ch/wsl/box/model/shared/JSONQuery.scala
index af9cf0b6..104b8e51 100755
--- a/shared/src/main/scala/ch/wsl/box/model/shared/JSONQuery.scala
+++ b/shared/src/main/scala/ch/wsl/box/model/shared/JSONQuery.scala
@@ -6,6 +6,7 @@ import scribe.Logging
import io.circe._
import io.circe.parser._
import io.circe.generic.auto._
+import io.circe.syntax.EncoderOps
//import ch.wsl.box.model.shared.JSONQuery.empty
@@ -19,20 +20,11 @@ case class JSONQuery(
filter:List[JSONQueryFilter],
sort:List[JSONSort],
paging:Option[JSONQueryPaging],
- sqlWhere:Option[String] = None,
fields:Option[Seq[String]] = None,
lookups:Option[Seq[JSONFieldLookupRemote]] = None,
fullText:Option[String] = None,
){
- def validatedWhere = sqlWhere.map(_.replaceAll("insert ","not valid")
- .replaceAll("update ","not valid")
- .replaceAll("truncate ","not valid")
- .replaceAll("drop ","not valid")
- .replaceAll("delete ","not valid")
- .replaceAll("alter ","not valid")
- .replaceAll("grant ","not valid")
- )
def filterWith(filter:JSONQueryFilter*) = this.copy(filter = filter.toList)
def sortWith(sort:JSONSort*) = this.copy(sort = sort.toList)
@@ -127,6 +119,7 @@ object JSONQueryFilter{
def eq(column: String, value: String) = JSONQueryFilter.withValue(column, Some(Filter.EQUALS), value)
def in(column: String, value: Seq[String]) = JSONQueryFilter.withValue(column, Some(Filter.IN), value.mkString(","))
+ def inSet(columns: Seq[String], value: Seq[Seq[Json]]) = JSONQueryFilter.withValue(columns.asJson.toString(), Some(Filter.IN_SET), value.asJson.toString())
def notIn(column: String, value: Seq[String]) = JSONQueryFilter.withValue(column, Some(Filter.NOTIN), value.mkString(","))
def not(column: String, value: String) = JSONQueryFilter.withValue(column, Some(Filter.NOT), value)
@@ -177,8 +170,6 @@ object JSONQuery extends Logging {
paging = Some(JSONQueryPaging(1000)),
)
- def where(sql:String) = empty.copy(sqlWhere = Some(sql))
-
def filterWith(filter:JSONQueryFilter*) = empty.copy(filter = filter.toList)
def sortWith(sort:JSONSort*) = empty.copy(sort = sort.toList)
@@ -240,6 +231,7 @@ object Filter extends Logging {
final val FK_LIKE = "FKlike"
final val FK_DISLIKE = "FKdislike"
final val IN = "in"
+ final val IN_SET = "in_set"
final val NOTIN = "notin"
final val BETWEEN = "between"
final val IS_NULL = "isNull"
diff --git a/shared/src/main/scala/ch/wsl/box/model/shared/WidgetsNames.scala b/shared/src/main/scala/ch/wsl/box/model/shared/WidgetsNames.scala
index 3326a412..80523a9d 100755
--- a/shared/src/main/scala/ch/wsl/box/model/shared/WidgetsNames.scala
+++ b/shared/src/main/scala/ch/wsl/box/model/shared/WidgetsNames.scala
@@ -42,6 +42,7 @@ object WidgetsNames {
val tableChild = "tableChild"
val editableTable = "editableTable"
val spreadsheet = "spreadsheet"
+ val export = "export"
val h1 = "title_h1"
val h2 = "title_h2"
val h3 = "title_h3"
@@ -117,7 +118,8 @@ object WidgetsNames {
trasparentChild,
dynamicWidget,
popupWidget,
- spreadsheet
+ spreadsheet,
+ `export`
),
FILE -> Seq(
simpleFile,