OOP in Svelte and its reactivity - oop

I wanna use svelte for a little app that Im making. The app was half finished using plain html/css/js when I stumbled upon Svelte.
I was using a lot of javascript classes and aimed for object oriented programming.
Now looking at svelte, it looks like its not made for OOP at all. Am I wrong? Properties of classes wont be tracked and updated by svelte.
Maybe my approach is wrong. I basicly used a View/Model pattern, where I have a model class object that Im feeding the svelte component. Using the object's properties in html wont update obviously. (This works great with angular i.e.)
<script lang="ts">
import type { Key } from "../key";
export let key: Key
const onTrigger = () => {
key.trigger()
}
const onRelease = () => {
key.release()
}
</script>
<div
class="key"
class:black={key.note[1] == '#' || key.note[1] === 'b'}
class:pressed={key.isPressed}
on:pointerdown={onTrigger}
on:pointerup={onRelease}
on:pointerleave={onRelease}
style={key.isPressed ? 'transform: scale(1.5); transform-origin: center;' : ''}>
<div class="key-mapping">{#html key.mapping.toLocaleUpperCase() + '<br/>' }</div>
<div class="key-note">{ key.note + (key.octave ? key.octave.toString() : '') }</div>
</div>
(Key represents a piano key sitting inside a piano component, things like key.isPressed or key.octave wont update, because they are changed in the model class)
Demo here
I really dont wanna use the store for ALL properties of my classes that I use in html, because I think this is not the purpose of the store. I was hoping to save some code by using Svelte and not make it weird and complex.
I saw the trick to reassign the whole object like this
object.property = 'some value'
object = object
to trigger reactivity, but this wont work when changing the properties outside of the component.
Also using the reactive marking $: { ... } I wasnt able to update any class' property (Only when changing it directly from a html event)
Also saw a decorator function to make classes reactive to svelte, but the decorator makes the class singleton too, which makes it useless to me.
So there are a few questions I wanna ask:
Is there any proper way to update class properties in Svelte?
If not, whats the prefered coding style? Functional?
Will there be OOP support in the future?

You don't need dummy assignments as soon as you assign to a property (rather than invoking a method) and there is no issue with using classes as long as you do not "hide" changes from Svelte's compiler.
E.g. this will work just fine (inside the component):
const onTrigger = () => {
key.isPressed = true;
}
const onRelease = () => {
key.isPressed = false;
}
In general your components should be fairly specialized so they do not have to deal with deeply nested data and complex modifications which makes it easy to lose reactivity.
Ideally you just have some very simple local state via component properties rather than objects. Here your Key component should just use properties for all its state e.g. isPressed should just be a property that then can be bound on the level of the parent component.

Related

Prevent DOM reuse within lit-html/lit-element

I am looking for a way to NOT reuse DOM elements within lit-html/lit-element (yes, I know, I'm turning off one of the prime features). The particular scenario is moving an existing system to lit-element/lit-html that at certain points embeds the trumbowyg WYSIWYG editor. This editor attaches itself to a <div> tag made within lit-element and modifies its own internal DOM, but of course lit-html does not know that this has happened, so it will often reuse the same <div> tag instead of creating a new one. I am looking for something similar to the vue.js key attribute (e.g., preventing Vue from aggresively reusing dom-elements)
I feel like the live() directive in lit-html should be useful for this, but that guards against reuse based on a given attribute, and I want to prevent reuse even if all attributes are identical. Thanks!
I have had similar issues with rich text editors and contenteditable - due to how templates update the DOM you don't want that to be part of a template.
You do this by adding a new element with the non-Lit DOM and then adding that to the DOM that Lit does manage:
class TrumbowygEditor
extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
const div = document.createElement('div');
shadow.appendChild(div);
const style = document.createElement('style');
// Add CSS required
shadow.appendChild(style);
$(div).trumbowyg(); //init
}
}
customElements.define('trumbowyg-editor', TrumbowygEditor);
As this is running in a custom element's shadow DOM Lit won't touch it, you can do:
html`
<div>Lit managed DOM</div>
<trumbowyg-editor></trumbowyg-editor>`;
However, you will have to implement properties and events on TrumbowygEditor to add everything you want to pass to or get from the nested jQuery component.
You can add the scripts with import if you can get module versions of jQuery/Trumbowyg (or your build tools support it) or you can add <script> tags to your component, add fallback loading DOM content in the constructor, and then on the load event of the <script> call the $(div).trumbowyg() to init the component.
While messier and more work I'd recommend the latter as both components are large and (thanks to jQuery being built on assumptions that are now 15 years old) need to load synchronously (<script async or <script defer don't work). Especially on slower connections Lit will be ready long before jQuery/Trumbowyg have loaded in, so you want <trumbowyg-editor> to look good (show spinner, layout in the right amount of space etc) while that's happening.
You write that you attach the external library directly to an element managed by lit-html. It sounds like you're doing essentially this:
render(html`<section><div id=target></div></section>`, document.body)
external_lib.render_to(document.querySelector("#target"))
If this is what you do instead try to create your own div, let the external lib render to that div, and finally attach that div to lit-html:
let target_div = document.createElement('div')
render(html`<section>${div}</section>`, document.body)
external_lib.render_to(target_div)
The most up-to-date answer to this problem is to use Lit's built-in keyed directive. This scenario is exactly what it's for:
https://lit.dev/docs/templates/directives/#keyed
Associates a renderable value with a unique key. When the key changes, the previous DOM is removed and disposed before rendering the next value, even if the value—such as a template—is the same.
#customElement('my-element')
class MyElement extends LitElement {
#property()
userId: string = '';
render() {
return html`
<div>
${keyed(this.userId, html`<user-card .userId=${this.userId}></user-card>`)}
</div>`;
}
}

Vue - same mutation refreshes (or not!) components depending on which component it is called from?

I have problem understanding why THE SAME mutation fails to refresh data displayed in components (although it does change underlying vuex store data!) if it is called from one of the components, but it does refresh the data if called from another component?
I am updating Filter objects stored in store this way: state.report.filters[], where filters is array of Filter objects.
const state = {
report: {
filters: [], // array of Filter objects
...
}
}
My mutation looks for a filter in the array and substitutes the whole Filter object.
const mutations = {
setFilter: (state, newFilterValue) => {
let changedFilter = state.report.filters.find(filter => {
return filter.fieldName === newFilterValue.fieldName;
});
changedFilter = newFilterValue;
}
}
The mutation is called from a method of Filter class defined like this (separate module):
import { store } from './store';
export class Filter {
constructor ({
...
} = {}) {
this.operators = []; // Array of Operator objects
this.value = []; // Array of values - in this case Dates
};
updateOperator (operatorName) { // this mutation refreshes components when executed
this.operator[0] = new Operator(operatorName);
store.commit('setFilter', this); // whole object passed to the mutation
};
updateValue (newValue) { // this mutation changes store value, but fails to refresh components
this.value[0] = newValue; // newValue is a Date
store.commit('setFilter', this);
};
};
The app displays data in rows (each Filter has a separate row), each row contains cells, of which one contains components dedicated to Filter's value and Operator. These dedicated components receive as props callback functions which are methods of the Filter object. They execute the callback functions when a new value is entered passing the value to the Filter which then updates a relevant property and calls the mutation passing in both cases the whole Filter object as payload.
// TABLE CELL COMPONENT displaying filter value and operator
<template>
<td>
<operator-component
:iconName="proppedFilterObject.operator.iconName"
:callback="proppedFilterObject.updateOperator.bind(proppedFilterObject)"
></operator-component>
<value-component
:date="proppedFilterObject.value[0]"
:callback="proppedFilterObject.updateValue.bind(proppedFilterObject)"
></value-component>
</td>
</template>
<script>
export default {
props: ['proppedFilterObject'] // whole filter object
};
</script>
// OPERATOR COMPONENT
<template>
<div #click.stop="chooseOperator">
{{ iconName }} // some operator value display
</div>
</template>
<script>
export default {
methods: {
chooseOperator () {
const modal = new ChooseOperatorModal({
callback: this.callback // this displays another modal for receiving data. The modal calls the callback.
});
},
},
props: ['callback', 'iconName']
};
</script>
// VALUE COMPONENT
<template>
<date-picker v-model="computedDate"> // THIRD PARTY COMPONENT
</date-picker>
{{ date }} // additional display to verify if there's a problem within 'date-picker'
</template>
<script>
import DatePicker from 'vue2-datepicker'; // THIRD PARTY COMPONENT
export default {
components: { DatePicker },
computed: {
computedDate: {
get: function () {
return this.date;
},
set: function (newValue) {
this.callback(newValue);
}
}
},
props: ['callback', 'date']
};
</script>
So, if eg. I enter new operator value from Operator component, everything refreshes. When I enter a new value in the value component, the mutation is executed and store value changed, but displayed data are not refreshed. However, if afterwards I change an operator all the components will refresh and value will get displayed. Even if I change operator in a different Filter object(!). Ie:
a) Change in report.filters[0].value - display not refreshed, but...
b) then change report.filters[1].operator - both report.filters[1].operator AND PREVIOUSLY CHANGED report.filters[0].value get refreshed(?!).
What can be a reason of such behaviour? Where to look for the problem?
Some additional remarks:
1) I am using a third party component "vue2-date-picker" for date choice and display. However it does not seem to be responsible for the problem, as if I try to display the new value just in {{ }} notation it behaves the same way. I have used the date picker in other components and there it functions correctly as well.
2) In the code samples I left out most imports/exports and other seemingly irrelevant elements to keep the question reasonably short.
There are a lot of problems with the code and several of them are contributing to the problems you're seeing. A full, thorough answer that addresses all of these problems would be ridiculously long so instead I will skim through them without going into huge amounts of detail. You will need to do some further reading and experimentation to understand each of these topics properly.
Let's start with this line in the mutation:
changedFilter = newFilterValue;
This line assigns a new value to the local variable changedFilter. That's all. As it's the last line of the mutation the net result is that it doesn't really do anything.
Presumably your intent was to update the array state.report.filters, replacing the old entry with a new entry. However, just updating a local variable isn't going to do that.
At this point you may be wondering 'If that doesn't do anything, then why is the state in my store changing?'. I'll come to that in a moment but first let me prove to you that your existing code does nothing.
Try removing the code inside setFilter completely. Just leave an empty function. Then try clicking around in the UI just like you did before. You'll find that the store state updates just the same as it did before, even though you've removed the code to update the array.
The correct way to implement that mutation would be to use findIndex to find the relevant index and then use either Vue.set or the array's splice method to update the array accordingly. That will change the item in the array. However...
This brings us back to the earlier question. Why is the state updating if the mutation does nothing?
This is because you're using the same object in multiple places. The Filter object held in the array is the same object that your UI is editing. There are no copies being taken, there is just a single object. So when you change the properties of that object inside updateOperator or updateValue this will immediately be reflected inside the store. Calling the setFilter mutation is just asking the store to replace an object with itself.
There's nothing specific to Vue about this. This is just the standard behaviour of reference types in JavaScript. It is also common with many other programming languages that don't directly expose pointers. It can be useful to learn a little about how pointers work in other languages as it will give you a better initial mental model before attempting to understand how reference types behave in JavaScript. Understanding the difference between 'by value' and 'by reference' may also be a useful starting point.
The next topic to cover is reactivity, which very much is a Vue topic.
Specifically, there are certain changes that Vue can't detect. These are usually referred to as the reactivity caveats. You can find more about them in the official documentation:
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
https://v2.vuejs.org/v2/guide/list.html#Caveats
There are at least two lines in your code that violate these rules:
this.operator[0] = new Operator(operatorName);
and
this.value[0] = newValue;
You can't set array entries directly by index. The array will update but it won't trigger any reactive dependencies within Vue. Instead you need to use either Vue.set or one of the array methods, e.g. push, pop, splice, etc.. In this example you could use splice.
e.g. Using Vue.set:
Vue.set(this.value, 0, newValue);
e.g. Using splice:
this.value.splice(0, 0, newValue);
Why does all of this matters?
Well Vue will only re-render a component if its reactive dependencies have changed. They are very similar to computed properties in that regard. Here's how it works...
Vue compiles the template down to a function. That function is referred to as the render function. When rendering a component Vue calls the render function and that function returns a description of how to render the component. Any reactive properties that are touched while that function is running will be recorded as dependencies. If, at some point in the future, the value of one of those reactive properties changes then Vue will rerun the render function to generate a new rendering of that component.
There are two key points to take out of this description:
If you fall foul of one of the reactivity caveats then Vue won't know the dependency has changed, so it won't re-render the component.
The render function runs as a whole. It doesn't just target a small chunk of the template, it always runs the whole thing.
So if you change a dependency in a non-reactive way (i.e. one of the caveats) it won't trigger a rendering update. But if you subsequently update a dependency properly, Vue will detect that and will rerun the render function. When it runs it will run the whole thing, so any new values will be picked up, even if they weren't detected when they changed.
It isn't immediately clear to me which rendering dependency is causing your component to re-render. However, it only needs one of them to change in a detectable manner. Any other changes will then get pulled in incidentally when the render function runs and reads their current values.
That covers why your code isn't working. However, I would also worry about your decision to introduce a Filter class. I understand how that may be appealing if you've come from some other OO environment but it isn't typically how Vue is used. It is possible to make it work but you will need a good understanding of both JavaScript reference types and the Vue reactivity system to avoid falling through the cracks. There is no reason why using a specific class to hold your data can't be made to work but in practice it usually ends up being less maintainable than not using such a class. A more typical Vue approach would be to use simple, anonymous objects/arrays to hold the data and then for the data owner (either a component or store module) to be responsible for making any mutations to that data. Events are used to pass changes up the component hierarchy rather than callback props.
Ultimately you will need to judge whether the Filter class is justified but it is probably not what future maintainers of your code will be expecting.

Custom-directive in Vue.js

I have a custom-directive :myMethod="loadProfile(currentUser)" which loads the JSON when the component is loaded. I use mapGetters from Vuex but surprisingly, this method is also working very well..I have a dilemma if this is also correct or performance-wise compared to other approaches like calling methods in mounted or created hooks (which I also tried)? Which is better to call on the hook or custom-directive?
Here is my sample code:
<template v-if="currentUser.username === 'admin'">
... //
<template v-else>
<div :myMethod="loadProfile(currentUser)"></div>
</template>
Vuex Getters
computed: {
...mapGetters([
'currentUser',
])
}
Method
loadProfile(payload){
this.user.last_name = payload.last_name,
this.user.first_name = payload.first_name,
this.user.image = ( payload.image === 'no_avatar.png' ? '/image/no_avatar.png' : '/storage/images/'+ payload.image)
}
Don't use methods unless you really have to. In this case there seems to be little reason to call loadProfile without currentUser, so lets just move it to a computed property:
computed: {
...mapGetters([
'currentUser'
]),
actualUser () {
// Make a shallow copy
const user = { ...this.user };
if (this.currentUser) {
user.lastName = this.currentUser.lastName;
user.firstName = this.currentUser.firstName;
user.image = (this.currentUser.image === 'no_avatar.png' ? '/image/no_avatar.png' : `/storage/images/${this.currentUser.image}`);
}
return user;
}
}
Why use a computed property? Computed properties are calculated and updated whenever their dependencies update. When the dependencies stay constant, the computed property will just be loaded from cache. This is a better for performance and makes it so you don't have to worry about when to update your data yourself.
As for the use of a directive. I most cases you don't need to use directives. You don't send a function reference to the directive here, but rather the evaluated result of the method, so doing <div :myMethod="actualUser"></div> would work too. You may want to make sure that what you are doing cannot be done with actual components. Components are re-usable and much easier to read than the code in a custom directive.
Vue.js directives are only meant to do some low-level DOM manipulations that you cannot do with a regular component. So in short, you should not be doing what you are currently doing.
Now on to longer answer:
In your code, you are doing something like this: <div :myMethod="loadProfile(currentUser)"></div>. If :myMethod is a directive then it should be v-myMethod="" instead of :myMethod="". Latter syntax i.e. colon syntax refers to props and not directive. So, it means your code is not really executing directive. Just treating myMethod as some prop.
Second, doing :myMethod="loadProfile(currentUser)" doesn't mean you are doing anything within an actual directive code. It is still being called from the component template.
Third, if we look at your loadProfile() method implementation, then it is not a function that returns a value. It is simply a function modifying component instance using this. If all you need to do this, then you don't need a directive or prop.
Fourth, doing :myMethod="loadProfile(currentUser)" will work but it looks awkward definitely not readable. Instead, you should rely on a watcher or computed property. It is the idiomatic way of doing things in Vue.js. Example watcher implementation would look like:
watch: {
currentUser(newVal) {
this.user.last_name = newVal.last_name,
this.user.first_name = newVal.first_name,
this.user.image = ( newVal.image === 'no_avatar.png' ? '/image/no_avatar.png' : '/storage/images/'+ newVal.image)
}
}
As far as the performance is concerned, you should not worry about it. Performance penalty if any will be negligible.

Passing props to child component Cyclejs

I m studying CycleJs and I m looking for a proper way to handle passing props to child component.
Actually, I m having the following stuff :
import {div, input} from '#cycle/dom'
export function App(sources) {
const inputOnChange$ = sources.DOM.select('input').events('input')
const streamofResult = inputOnChange$
.map(e => e.target.value)
.startWith('')
.map(defaultInput => {
const title = Title({value: defaultInput})
return div([
title,
input({attrs: {type: 'text'}})
])
})
const sinks = {DOM: streamofResult}
return sinks
}
export function Title(sources) {
return div(sources.value)
}
It simply allows to make some inputs, and to display it in a child component called Title.
I think I should use a stream to handle passing props to my child.
But I don't understand why it would be a better solution in this simple to use a stream instead of a primitive ?
There is something that I probably have not understood.
You haven't misunderstood anything. There is no right answer. If you know for a fact you'll never want to change the props after initialization then you could pass the props as a primitive, but the more common convention is to send a props$ since it's not much costlier to do something like O.of(x) vs x (assuming RxJS) and using streams everywhere is consistent with the philosophy of the framework. Additionally, there are occasions when you'll want to change the properties dynamically after component initialization, where a stream is appropriate.
Keeping a consistent props or props$ convention for all your components can make reading the code easier since you won't have to think, "Does this component use a primitive or a stream for props...?"

How to implement a switcher/viewstack component with slot?

I'm trying to implement a 'switcher' or 'viewstack' component in Aurelia, this would be useful for wizards, paged content, and stepping through a list of tasks. This would show a single child component at a time from a number of possible children. I'd like the usage markup to resemble:
<my-switcher>
<one-component></one-component>
<two-component></two-component>
<three-component></three-component>
</my-switcher>
Now, the obvious alternatives here are:
<compose view-model.bind="currentStep"> and point the currentStep variable to each component at a time.
(+ves: components are only instantiated when accessed, -ves: needing to know the path for each component, all children need to be valid view-models)
Add an if.bind='active' within the definition of each component in the slot, and just set this active member from the my-switcher class. (+ves: easier to follow, -ves: components need to be specifically written for use here).
Retrieve the children via #children (if this now works reliably?) and add the Element as a child DOM element manually, then call ViewCompiler.enhance. (-ves: can't seem to get get #children to work, larger amount of custom code)
Each of these feels a bit contrived a solution. Does anyone have any idea about whether there a cleaner approach that could/should be used instead?
Combine options 2 and 3 while avoiding the negatives (not sure why you can't get #children to work).
consumer.html
<my-switcher>
<my-switcher-item>
<one-component></one-component>
</my-switcher-item>
<my-switcher-item>
<two-component></two-component>
</my-switcher-item>
<my-switcher-item>
<three-component></three-component>
</my-switcher-item>
</my-switcher>
my-switcher-item.js
export class MySwitcherItem {
constructor() {
this.isActive = false;
}
}
my-switcher-item.html
<template show.bind="isActive">
<slot></slot>
</template>
my-switcher.js
import {children} from 'aurelia-framework';
export class MySwitcher {
#children('my-switcher-item') items = [];
// Here you have access to this.items[index].isActive.
// You can set this value to true or false from this class
// and make sure only one item's isActive is true.
// You could add next() and previous() methods to move between
// the items, etc.
}