I have a base class item something like this:
Base.qml:
Item {
Thing {
id: theThing;
onMySignal: { console.log("The signal"); }
}
}
And I'm trying to make a derived item - Derived.qml.
How can I override the onMySignal handler of theThing? I have tried stuff like this...
Derived.qml:
Base {
theThing.onMySignal: { console.log("Do different things with theThing in Derived") }
}
but I can't find anything to tell me how to express this syntactically correctly, or whether/how to actually go about it!
You can define the code of the signal handler as a property in superclass and override it in the derived item:
Item {
property var handlerCode: function () { console.log("In Superclass") };
Thing {
id: theThing;
onMySignal: handlerCode()
}
}
Overriding :
Base {
handlerCode: function () { console.log("In Derived Item!") };
}
Related
Still some what new to SwiftUI. Now I'm trying to present a sheet from a button in a Menu. I can reproduce the issue with the sample code below:
import SwiftUI
struct SheetView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button("Press to dismiss") {
presentationMode.wrappedValue.dismiss()
}
.font(.title)
.padding()
.background(Color.black)
}
}
struct TestButtonInMenu: View {
#State private var showingSheet = false
var body: some View {
Button("Show Sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SheetView()
}
}
}
enum SampleEnum: String, CaseIterable {
case one, two, three, four
}
struct ContentView: View {
var body: some View {
Form {
Section {
VStack {
ForEach(SampleEnum.allCases, id:\.self) { id in
Menu("\(Text(id.rawValue))") {
TestButtonInMenu()
}
}
}
}
}
}
}
I've tried different sheet initializers but they don't make a difference.
What am I missing? Is this possible in SwiftUI?
You have a couple of problems with the code. First of all, in your ContentView you have the Menu inside of the ForEach. By doing it that way, you have created four menus with one button each, instead of one menu with four buttons. The point of Menu is to hide the buttons until they are needed.
The second issue is that you are trying to show one sheet off the button that is buried in another view in the menu. The sheet really should be declared in the parent, not a child, and I think you have confused the OS. That being said, I think eventually you intend to call four different sheets from the different buttons, and the answer Asperi pointed you to will help as you will be calling different sheets from the one .sheet. I corrected the code and just brought the button into the main UI and out of its own struct.
struct SheetView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button("Press to dismiss") {
presentationMode.wrappedValue.dismiss()
}
.font(.title)
.padding()
.background(Color.black)
}
}
enum SampleEnum: String, CaseIterable {
case one, two, three, four
}
struct ContentView: View {
#State private var showingSheet = false
var body: some View {
Form {
Section {
VStack {
Menu("Show Sheet") {
ForEach(SampleEnum.allCases, id:\.self) { id in
Button(id.rawValue) {
showingSheet.toggle()
}
}
}
}
}
}
.sheet(isPresented: $showingSheet) {
SheetView()
}
}
}
I was taking a look at this :
tornadofx
and tried to expand on it with database connection and little more options, (not all of them make sense, but its just playing in a sandbox).
Even though table can be directly edited and the data will persist in database, i did try to do edit through text fields too. actual table editing would happen through different view and not table itself, as i said its just example.
Database used is Jetbrains Exposed.
object Categories : IntIdTable() {
val name = varchar("name", 64).uniqueIndex()
val description = varchar("description", 128)
}
class Category(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Category>(Categories)
var name by Categories.name
var description by Categories.description
override fun toString(): String {
return "Category(name=\"$name\", description=\"$description\")"
}
}
now controller looks something like this, functions are just rudimentary and picked as an example.
typealias ModelToDirtyState = Map.Entry<CategoryModel, TableColumnDirtyState<CategoryModel>>
class CategoryModel() : ItemViewModel<Category>() {
val name: SimpleStringProperty = bind(Category::name)
val description: SimpleStringProperty = bind(Category::description)
}
class DBController : Controller() {
val categories: ObservableList<CategoryModel> by lazy {
transaction {
SchemaUtils.create(Categories)
Category.all().map {
CategoryModel().apply {
item = it
}
}.observable()
}
}
init {
Database.connect(
"jdbc:mysql://localhost:3306/test", driver = "com.mysql.cj.jdbc.Driver",
user = "test", password = "test"
)
TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
}
fun deleteCategory(model: CategoryModel) {
runAsync {
transaction {
model.item.delete()
}
}
categories.remove(model)
}
fun updateCategory(model: CategoryModel) {
transaction {
Categories.update {
model.commit()
}
}
}
fun commitDirty(modelDirtyMappings: Sequence<ModelToDirtyState>) {
transaction {
modelDirtyMappings.filter { it.value.isDirty }.forEach {
it.key.commit()
println(it.key)// commit value to database
it.value.commit() // clear dirty state
}
}
}
Just to quickly comment on controller, delete method works as "intended" however the update one does not, it does not work in sense that after using delete item is remove both from database and tableview(underlying list) itself, and when i do update its not, now i know the reason, i call remove manually on both database and list, now for update perhaps i could do change listener, or maybe tornadofx can do this for me, i just cant set it up to do it. Following code will make things clearer i think.
class CategoryEditor : View("Categories") {
val categoryModel: CategoryModel by inject()
val dbController: DBController by inject()
var categoryTable: TableViewEditModel<CategoryModel> by singleAssign()
var categories: ObservableList<CategoryModel> by singleAssign()
override val root = borderpane {
categories = dbController.categories
center = vbox {
buttonbar {
button("Commit") {
action {
dbController.commitDirty(categoryTable.items.asSequence())
}
}
button("Roll;back") {
action {
categoryTable.rollback()
}
}
// This model only works when i use categorytable.tableview.selected item, if i use categoryModel, list gets updated but not the view itself
// Question #1 how to use just categoryModel variable without need to use categorytable.tableview.selecteditem
button("Delete ") {
action {
val model = categoryTable.tableView.selectedItem
when (model) {
null -> return#action
else -> dbController.deleteCategory(model)
}
}
}
//And here no matter what i did i could not make the view update
button("Update") {
action {
when (categoryModel) {
null -> return#action
else -> dbController.updateCategory(categoryModel)
}
categoryTable.tableView.refresh()
}
}
}
tableview<CategoryModel> {
categoryTable = editModel
items = categories
enableCellEditing()
enableDirtyTracking()
onUserSelect() {
//open a dialog
}
//DOES WORK
categoryModel.rebindOnChange(this) { selectedItem ->
item = selectedItem?.item ?: CategoryModel().item
}
// Question #2. why bindSelected does not work, and i have to do it like above
//DOES NOT WORK
// bindSelected(categoryModel)
//
column("Name", CategoryModel::name).makeEditable()
column("Description", CategoryModel::description).makeEditable()
}
}
right = form {
fieldset {
field("Name") {
textfield(categoryModel.name)
}
}
fieldset {
field("Description") {
textfield(categoryModel.description)
}
}
button("ADD CATEGORY") {
action {
dbController.addCategory(categoryModel.name.value, categoryModel.description.value)
}
}
}
}
}
I apologize for huge amount of code, also in last code snipped i left questions in form of comments where i fail to achive desired results.
I am sure i am not properly binding code, i just dont see why, also i sometimes use one variable to update data, my declared one "categoryModel" and sometimes i use tableview.selecteditem, it just seems hacky and i cant seem to grasp way.
Thank you!
I am converting a custom element dropdown over to lit-element. The way the existing element shows the dropdown options is by setting an expanded boolean attribute on the element, and the options are shown/hidden via css:
my-element:not([expanded]) .options-container {
display: none;
}
my-element[expanded] .options-container {
display: block;
}
The component doesn't need to do any rerenders because the logic is all in the css.
How can I achieve this behavior with lit-element, and not rerender the component? Rerendering can be costly if there are a lot of dropdown options.
I have tried implementing a shouldUpdate that returns false if only expanded has changed - but this causes lit-element not to reflect expanded to the attribute when set via a property, which is necessary in order to show/hide via css.
This is what I have, which doesn't work:
class MyDropdown extends LitElement {
static get properties() {
return {
expanded: { type: Boolean, reflect: true },
...
};
}
shouldUpdate(changedProperties) {
if (changedProperties.has('expanded') && changedProperties.size === 1) {
return false;
}
return true;
}
// disable shadow-dom
createRenderRoot() {
return this;
}
}
Note that I am not using shadow dom yet, not sure if that would change the solution. I'm on lit-element 2.2.1.
The idea is to not use LitElement's static properties or #Property decorator. Write your own property implementation like this:
class MyDropdown extends LitElement {
_expanded = false;
get expanded() {
return this._expanded;
}
set expanded(val) {
this._expanded = val;
// Manually setting the property and reflecting attribute.
if (val) {
this.setAttribute('expanded', '');
} else {
this.removeAttribute('expanded');
}
}
// disable shadow-dom
createRenderRoot() {
return this;
}
}
Similarly, you can listen for attributeChangedCallback lifecycle event and adjust _expanded property whenever user changes the attribute and not property.
I have a list page, and click a column to enter the details page. Edit and return. Because using RouteReuseStrategy, it can maintain the list page of the scene remains the same. But I'd like to partially update, However, I don't know how to trigger it.
Here is my RouteReuseStrategy service and most of the same.
export class SimpleReuseStrategy implements RouteReuseStrategy {
_cacheRouters: { [key: string]: any } = {};
shouldDetach(route: ActivatedRouteSnapshot): boolean {
return true;
}
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
this._cacheRouters[route.routeConfig.path] = {
snapshot: route,
handle: handle
};
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
return !!this._cacheRouters[route.routeConfig.path];
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
return this._cacheRouters[route.routeConfig.path].handle;
}
shouldReuseRoute(future: ActivatedRouteSnapshot, curr:
ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig;
}
}
In your list page component you can listen to route changes:
Detect if the navigation start came from the detail route i.e. /detail/1
if so, get the 'id' param of the route /detail/1
replace that element in the list
// List component
ngOnInit(): void {
this.subscription = this.router.events.subscribe((event) => {
if (event instanceof NavigationStart) {
// checkIfDetailPage and store ID
}
if (event instanceof NavigationEnd) {
// checkIfThisPage and get the ID
// replace element with ID
}
});
}
An alternative approach for detecting something changed (instead of observing the route changes) you could implement Service to which your list component subscribes and your detail component notifies.
I need to traverse a tree with an unknown number of nodes by making asynchronous URL requests. My current approach looks like this:
class Parent {
var foo: String
var id: Int
var children: [Child]?
func loadChildren() {
for child in self.children {
self.getAllChildren(child)
}
}
func getAllChildren(current: Child) {
current.load() {(success) in
if (success) {
if let children = current.children {
for child in children {
self.getAllChildren(child)
}
}
}
}
}
}
class Child {
var bar: String
var id: Int
var children: [Child]?
func load(success: (Bool) -> ()) {
// Load from API and initalize values
}
}
The problem with my current approach above is that I don't known when the loading has finished. I don't care if some children fail to load, but i need to make UI updates when all children (and their children) of an parent have been loaded.
I looked into various approaches like promises and dispatch groups but i'm struggling to get it work. I'm using Swift 2 and ideally the parent would have a function like this:
func loadChildren(success: (Bool) -> ()) {
// do stuff
}