diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b677bce --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.gradle/ +build/ +*.iml +*.class +*.prefs +*.classpath +.project +.idea/ +gradle.properties +secring.gpg +memory \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..0c96504 --- /dev/null +++ b/build.gradle @@ -0,0 +1,49 @@ +buildscript { + ext.kotlin_version = '1.0.3' + + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: "kotlin" + +repositories { + mavenCentral() + maven { url "https://jitpack.io" } +} + +apply plugin: 'application' +mainClassName = 'app.MyApp' + +dependencies { + + //TornadoFX dependencies + compile 'com.github.edvin:tornadofx:9c6b39fdcf' + //compile 'no.tornado:tornadofx:1.5.2' + + //sqlite and RxJava-JDBC dependencies + compile 'org.xerial:sqlite-jdbc:3.8.11.2' + compile 'com.github.davidmoten:rxjava-jdbc:0.7.2' + compile 'org.slf4j:slf4j-simple:1.7.21' + + //RxKotlin and RxKotlinFX dependencies + compile 'com.github.thomasnield:rxkotlinfx:0.1.3.1' + compile 'io.reactivex:rxkotlin:0.60.0' + + //Testing dependencies + testCompile 'junit:junit:4.12' +} + +jar { + manifest { + attributes( + 'Class-Path': configurations.compile.collect { it.getName() }.join(' '), + 'Main-Class': 'app.MyApp' + ) + } + from configurations.compile.collect { entry -> zipTree(entry) } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..9411448 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..09c7815 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jul 25 18:23:27 CDT 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..230ad50 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +\#!/usr/bin/env bash + +\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\# +\#\# +\#\# Gradle start up script for UN*X +\#\# +\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\# + +\# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "\$0"` + +\# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "\$*" +} + +die ( ) { + echo + echo "\$*" + echo + exit 1 +} + +\# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +\# Attempt to set APP_HOME +\# Resolve links: \$0 may be a link +PRG="\$0" +\# Need this for relative symlinks. +while [ -h "\$PRG" ] ; do + ls=`ls -ld "\$PRG"` + link=`expr "\$ls" : '.*-> \(.*\)\$'` + if expr "\$link" : '/.*' > /dev/null; then + PRG="\$link" + else + PRG=`dirname "\$PRG"`"/\$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"\$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "\$SAVED" >/dev/null + +CLASSPATH=\$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +\# Determine the Java command to use to start the JVM. +if [ -n "\$JAVA_HOME" ] ; then + if [ -x "\$JAVA_HOME/jre/sh/java" ] ; then + \# IBM's JDK on AIX uses strange locations for the executables + JAVACMD="\$JAVA_HOME/jre/sh/java" + else + JAVACMD="\$JAVA_HOME/bin/java" + fi + if [ ! -x "\$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +\# Increase the maximum file descriptors if we can. +if [ "\$cygwin" = "false" -a "\$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ \$? -eq 0 ] ; then + if [ "\$MAX_FD" = "maximum" -o "\$MAX_FD" = "max" ] ; then + MAX_FD="\$MAX_FD_LIMIT" + fi + ulimit -n \$MAX_FD + if [ \$? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: \$MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: \$MAX_FD_LIMIT" + fi +fi + +\# For Darwin, add options to specify how the application appears in the dock +if \$darwin; then + GRADLE_OPTS="\$GRADLE_OPTS \"-Xdock:name=\$APP_NAME\" \"-Xdock:icon=\$APP_HOME/media/gradle.icns\"" +fi + +\# For Cygwin, switch paths to Windows format before running java +if \$cygwin ; then + APP_HOME=`cygpath --path --mixed "\$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "\$CLASSPATH"` + JAVACMD=`cygpath --unix "\$JAVACMD"` + + \# We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in \$ROOTDIRSRAW ; do + ROOTDIRS="\$ROOTDIRS\$SEP\$dir" + SEP="|" + done + OURCYGPATTERN="(^(\$ROOTDIRS))" + \# Add a user-defined pattern to the cygpath arguments + if [ "\$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="\$OURCYGPATTERN|(\$GRADLE_CYGPATTERN)" + fi + \# Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "\$@" ; do + CHECK=`echo "\$arg"|egrep -c "\$OURCYGPATTERN" -` + CHECK2=`echo "\$arg"|egrep -c "^-"` \#\#\# Determine if an option + + if [ \$CHECK -ne 0 ] && [ \$CHECK2 -eq 0 ] ; then \#\#\# Added a condition + eval `echo args\$i`=`cygpath --path --ignore --mixed "\$arg"` + else + eval `echo args\$i`="\"\$arg\"" + fi + i=\$((i+1)) + done + case \$i in + (0) set -- ;; + (1) set -- "\$args0" ;; + (2) set -- "\$args0" "\$args1" ;; + (3) set -- "\$args0" "\$args1" "\$args2" ;; + (4) set -- "\$args0" "\$args1" "\$args2" "\$args3" ;; + (5) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" ;; + (6) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" ;; + (7) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" ;; + (8) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" ;; + (9) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" "\$args8" ;; + esac +fi + +\# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("\$@") +} +eval splitJvmOpts \$DEFAULT_JVM_OPTS \$JAVA_OPTS \$GRADLE_OPTS +JVM_OPTS[\${\#JVM_OPTS[*]}]="-Dorg.gradle.appname=\$APP_BASE_NAME" + +exec "\$JAVACMD" "\${JVM_OPTS[@]}" -classpath "\$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "\$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/kotlin/app/MyApp.kt b/src/main/kotlin/app/MyApp.kt new file mode 100644 index 0000000..db95de7 --- /dev/null +++ b/src/main/kotlin/app/MyApp.kt @@ -0,0 +1,13 @@ +package app + +import tornadofx.App +import tornadofx.importStylesheet +import view.MainView + +class MyApp : App() { + override val primaryView = MainView::class + + init { + importStylesheet(Styles::class) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/Styles.kt b/src/main/kotlin/app/Styles.kt new file mode 100644 index 0000000..0968fd6 --- /dev/null +++ b/src/main/kotlin/app/Styles.kt @@ -0,0 +1,21 @@ +package app + +import javafx.scene.text.FontWeight +import tornadofx.Stylesheet +import tornadofx.box +import tornadofx.cssclass +import tornadofx.px + +class Styles : Stylesheet() { + companion object { + val heading by cssclass() + } + + init { + label and heading { + padding = box(10.px) + fontSize = 20.px + fontWeight = FontWeight.BOLD + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/domain/ClientCompany.kt b/src/main/kotlin/domain/ClientCompany.kt new file mode 100644 index 0000000..30f720d --- /dev/null +++ b/src/main/kotlin/domain/ClientCompany.kt @@ -0,0 +1,12 @@ +package domain + +class ClientCompany(val id: Int, val name: String) { + + companion object { + val all = db.select("SELECT * FROM CLIENT_COMPANY") + .get { ClientCompany(it.getInt("ID"), it.getString("NAME")) } + + fun forId(id: Int) = db.select("SELECT * FROM CLIENT_COMPANY WHERE ID = ?") + .get { ClientCompany(it.getInt("ID"), it.getString("NAME")) } + } +} \ No newline at end of file diff --git a/src/main/kotlin/domain/Database.kt b/src/main/kotlin/domain/Database.kt new file mode 100644 index 0000000..f81982f --- /dev/null +++ b/src/main/kotlin/domain/Database.kt @@ -0,0 +1,82 @@ +package domain + +import com.github.davidmoten.rx.jdbc.ConnectionProviderFromUrl +import com.github.davidmoten.rx.jdbc.Database +import rx.lang.kotlin.subscribeWith +import rx.lang.kotlin.toObservable + +/** + * An in-memory database using SQLite holding + */ +val db: Database = Database.from(ConnectionProviderFromUrl("jdbc:sqlite:memory").get()).apply { + + //create CLIENT_COMPANY TABLE + val drop1 = update("DROP TABLE IF EXISTS CLIENT_COMPANY").count() + + val create1 = update("CREATE TABLE CLIENT_COMPANY (ID INTEGER PRIMARY KEY, NAME VARCHAR)") + .dependsOn(drop1) + .count() + + val clientCompanyValues = listOf( + "Alpha Analytics", + "Rexon Solutions", + "Travis Technologies", + "Anex Applications", + "Edvin Enterprises", + "T-Boom Consulting", + "Nield Industrial", + "Dash Inc" + ).toObservable() + + update("INSERT INTO CLIENT_COMPANY (NAME) VALUES (?)") + .parameters(clientCompanyValues) + .dependsOn(create1) + .returnGeneratedKeys() + .getAs(Int::class.java) + .toList() + .subscribeWith { + onNext { println("CLIENT_COMPANY table created, KEYS: $it")} + onError { throw RuntimeException(it) } + } + + //create SALES_PERSON TABLE + val drop2 = update("DROP TABLE IF EXISTS SALES_PERSON").count() + + val create2 = update("CREATE TABLE SALES_PERSON (ID INTEGER PRIMARY KEY, FIRST_NAME VARCHAR, LAST_NAME VARCHAR)") + .dependsOn(drop2) + .count() + + val salesPersonValues = listOf( + "Joe","McManey", + "Heidi","Howell", + "Eric","Wentz", + "Jonathon","Smith", + "Samantha","Stewart", + "Jillian","Michelle" + ).toObservable() + + update("INSERT INTO SALES_PERSON (FIRST_NAME,LAST_NAME) VALUES (?,?)") + .dependsOn(create2) + .parameters(salesPersonValues) + .returnGeneratedKeys() + .getAs(Int::class.java) + .toList() + .subscribeWith { + onNext { println("SALES_PERSON table created, KEYS: $it") } + onError { throw RuntimeException(it) } + } + + //CREATE ASSIGNMENTS TABLE + val drop3 = update("DROP TABLE IF EXISTS ASSIGNMENT").count() + + update("CREATE TABLE ASSIGNMENT (ID INTEGER PRIMARY KEY, " + + "CLIENT_COMPANY_ID INTEGER, SALES_PERSON_ID INTEGER)") + .dependsOn(drop3) + .count() + .subscribeWith { + onNext { println("ASSIGNMENT table created") } + onError { throw RuntimeException(it) } + } +} + + diff --git a/src/main/kotlin/domain/SalesPerson.kt b/src/main/kotlin/domain/SalesPerson.kt new file mode 100644 index 0000000..8bb11f2 --- /dev/null +++ b/src/main/kotlin/domain/SalesPerson.kt @@ -0,0 +1,59 @@ +package domain + +import javafx.collections.FXCollections +import rx.javafx.kt.onChangedObservable +import rx.javafx.kt.toBinding +import rx.lang.kotlin.subscribeWith +import rx.lang.kotlin.toObservable +import java.util.* + +class SalesPerson(val id: Int, val firstName: String, val lastName: String) { + + /** + * The assigned CompanyClient ID's for this SalesPerson + */ + val assignments by lazy { + FXCollections.observableSet(HashSet()).apply { + assignmentsFor(id) + .subscribeWith { + onNext { add(it) } + onError { throw RuntimeException(it) } + } + } + } + + /** + * A Binding holding a concatenation of the CompanyClient ID's for this SalesPerson + */ + val assignmentsBinding by lazy { + assignments.onChangedObservable().flatMap { + it.toObservable().map { it.toString() }.reduce("") { x, y -> if (x == "") "" else "$x|y" } + }.toBinding() + } + + companion object { + /** + * Retrieves all SalesPerson instances from database + */ + val all = db.select("SELECT * FROM SALES_PERSON") + .get { SalesPerson(it.getInt("ID"),it.getString("FIRST_NAME"),it.getString("LAST_NAME")) } + .toList().flatMap { it.toObservable() } //workaround for SQLite locking error + + /** + * Emits the SalesPerson for their given ID + */ + fun forId(salesPersonId: Int) = db.select("SELECT * FROM SALES_PERSON") + .parameter(salesPersonId) + .get { SalesPerson(it.getInt("ID"),it.getString("FIRST_NAME"),it.getString("LAST_NAME")) } + .toList().flatMap { it.toObservable() } //workaround for SQLite locking error + + /** + * Retrieves all assigned CompanyClient ID's for a given SalesPerson + */ + fun assignmentsFor(salesPersonId: Int) = + db.select("SELECT CLIENT_COMPANY_ID FROM ASSIGNMENT WHERE SALES_PERSON_ID = ?") + .parameter(salesPersonId) + .getAs(Int::class.java) + .toList().flatMap { it.toObservable() } //workaround for SQLite locking error + } +} \ No newline at end of file diff --git a/src/main/kotlin/view/CompanyClientView.kt b/src/main/kotlin/view/CompanyClientView.kt new file mode 100644 index 0000000..6d7cad4 --- /dev/null +++ b/src/main/kotlin/view/CompanyClientView.kt @@ -0,0 +1,62 @@ +package view + +import domain.ClientCompany +import javafx.geometry.Orientation +import javafx.scene.control.* +import javafx.scene.layout.BorderPane +import rx.javafx.kt.actionEvents +import rx.javafx.kt.onChangedObservable +import rx.javafx.kt.plusAssign +import rx.lang.kotlin.filterNotNull +import rx.lang.kotlin.subscribeWith +import rx.lang.kotlin.toObservable +import tornadofx.* + +class CompanyClientView: View() { + override val root = BorderPane() + private val controller: EventController by inject() + + init { + with(root) { + top(Label("CLIENT COMPANIES")) + + center(TableView()) { + column("ID",ClientCompany::id) + column("NAME",ClientCompany::name) + + selectionModel.selectionMode = SelectionMode.MULTIPLE + + //broadcast selections + controller.selectedClients += selectionModel.selectedItems.onChangedObservable() + .flatMap { it.toObservable().filterNotNull().toList() } + + //Import data and refresh event handling + controller.refreshCompanyClients.toObservable().startWith(Unit) + .flatMap { + ClientCompany.all.toList() + }.subscribeWith { + onNext { items.setAll(it) } + onError { alert(Alert.AlertType.ERROR,"PROBLEM!",it.message?:"").show() } + } + } + left(ToolBar()) { + orientation = Orientation.VERTICAL + button("⇇\uD83D\uDD0E") { + controller.searchClientUsages += actionEvents().flatMap { + controller.selectedClients.toObservable().take(1) + .flatMap { it.toObservable() } + .map { it.id } + .toList() + } + } + button("⇉\uD83D\uDD0E") + button("⇇") { + useMaxWidth = true + } + button("⇉") { + useMaxWidth = true + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/view/EventController.kt b/src/main/kotlin/view/EventController.kt new file mode 100644 index 0000000..0744d27 --- /dev/null +++ b/src/main/kotlin/view/EventController.kt @@ -0,0 +1,21 @@ +package view + +import domain.ClientCompany +import domain.SalesPerson +import rx.javafx.sources.CompositeObservable +import tornadofx.Controller + +class EventController: Controller() { + val searchClients = CompositeObservable>() + val searchClientUsages = CompositeObservable>() + + val applyClients = CompositeObservable>() + val removeClients = CompositeObservable>() + + val refreshSalesPeople = CompositeObservable() + val refreshCompanyClients = CompositeObservable() + + val selectedClients = CompositeObservable>(1) //cache last selection + val selectedSalesPeople = CompositeObservable>(1) //cache last selection + +} \ No newline at end of file diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt new file mode 100644 index 0000000..4103683 --- /dev/null +++ b/src/main/kotlin/view/MainView.kt @@ -0,0 +1,40 @@ +package view + +import javafx.geometry.Orientation +import javafx.scene.control.MenuBar +import javafx.scene.control.SplitPane +import javafx.scene.layout.BorderPane +import rx.javafx.kt.actionEvents +import rx.javafx.kt.plusAssign +import tornadofx.* + +class MainView : View() { + override val root = BorderPane() + + private val salesPeopleView: SalesPeopleView by inject() + private val companyClientView: CompanyClientView by inject() + + private val controller: EventController by inject() + + init { + title = "Client/Salesperson Assignments" + + with(root) { + top(MenuBar()) { + menu("File") { + menuitem("Refresh").apply { + controller.refreshCompanyClients += actionEvents().map { Unit } + controller.refreshSalesPeople += actionEvents().map { Unit } + } + } + } + center(SplitPane()) { + orientation = Orientation.HORIZONTAL + items { + this += salesPeopleView + this += companyClientView + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/view/SalesPeopleView.kt b/src/main/kotlin/view/SalesPeopleView.kt new file mode 100644 index 0000000..8a4c3b3 --- /dev/null +++ b/src/main/kotlin/view/SalesPeopleView.kt @@ -0,0 +1,61 @@ +package view + +import domain.SalesPerson +import javafx.scene.control.Label +import javafx.scene.control.SelectionMode +import javafx.scene.control.TableView +import javafx.scene.layout.BorderPane +import rx.javafx.kt.onChangedObservable +import rx.javafx.kt.plusAssign +import rx.lang.kotlin.filterNotNull +import rx.lang.kotlin.subscribeWith +import rx.lang.kotlin.toObservable +import tornadofx.View +import tornadofx.center +import tornadofx.column +import tornadofx.top + +class SalesPeopleView: View() { + + override val root = BorderPane() + private val controller: EventController by inject() + + init { + with(root) { + + top(Label("SALES PEOPLE")) + + center(TableView()) { + column("ID",SalesPerson::id) + column("First Name",SalesPerson::firstName) + column("Last Name",SalesPerson::lastName) + column("Assigned Clients",SalesPerson::assignmentsBinding) + + selectionModel.selectionMode = SelectionMode.MULTIPLE + + //broadcast selections + controller.selectedSalesPeople += selectionModel.selectedItems.onChangedObservable() + .flatMap { it.toObservable().filterNotNull().toList() } + + //handle refresh events and import data + controller.refreshSalesPeople.toObservable().startWith(Unit) + .flatMap { + SalesPerson.all.toList() + }.subscribeWith { + onNext { items.setAll(it) } + onError { it.printStackTrace() } + } + } + + } + connect() + } + + private fun connect() { + with(controller) { + searchClientUsages.toObservable().subscribeWith { + onNext { } + } + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/domain/DatabaseTest.kt b/src/test/kotlin/domain/DatabaseTest.kt new file mode 100644 index 0000000..7665d2b --- /dev/null +++ b/src/test/kotlin/domain/DatabaseTest.kt @@ -0,0 +1,15 @@ +package domain + +import org.junit.Test +import rx.lang.kotlin.subscribeWith + +class DatabaseTest { + @Test + fun testSqliteConnection() { + db.select("SELECT * FROM SALES_PERSON") + .count() + .subscribeWith { + onError { throw RuntimeException(it) } + } + } +} \ No newline at end of file