Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feedback on using ltk.Model #19

Open
Neon22 opened this issue Jan 8, 2025 · 0 comments
Open

Feedback on using ltk.Model #19

Neon22 opened this issue Jan 8, 2025 · 0 comments

Comments

@Neon22
Copy link

Neon22 commented Jan 8, 2025

MVP Feedback:

In trying to migrate to using the MVP (ltk.Model class) I have come across some issues.
I will lay them out here in the hopes that it might help in future development.

  • Using ltk 0.2.20 and jquery@3.7.1 with pyscript 2024.11.1 and pyodide

On a side note <shift-ctrl> doesn't seem to work anymore on rollover in pyodide but is working in mpy. Not sure if its something in the config below (pyodide)

  1. To get around the potential memory leak in widget() I have successfully been using a global var. The MVP approach looks like it may supersede the need for widget()

  2. I am also using a global var to make easy ref to the following reactive component. This is working well.
    I am also using a global to deal with rippling updates, if I update two or more reactive variables. This is working well.

I.e.

g_RC_values = None  # Hold reactive Model
g_inhibit_update = False  # prevent rippling updates

class Reactive_component(ltk.Model):
    """
    These sets of triplet variables update each other. Doing unit conversions
    """
    #  Length
    Len_measure: str = "250yd"
    Len_units: str = "yd"
    Len_altlabel: str = "229m"
    #
    Sk_wraps: str = "20wpi"
    Sk_units: str = "wpi"
    Sk_altlabel: str = "7.9wpcm"

    def changed(self, name, value):
        """
        Called whenever a UI element is changed by user.
        Also invoked for internal changes
        - g_inhibit_update is used to prevent needless rippling
        """
        global g_inhibit_update
        print("Change requested:",name,value)
        if not g_inhibit_update:
            print("Changing:",name,value)
            g_inhibit_update = True  # inhibit rippling calls while we update
            # Skein Length
            if name in ["Len_measure", "Len_units"]:
                vars = ["Len_measure", "Len_units", "Len_altlabel", ["250","yd","m"]]
                if name == "Len_measure":
                    self.update_primary(value, *vars)
                elif name == "Len_units":
                    self.recalc_units(value, *vars)
            # Skein units
            if name in ["Sk_wraps", "Sk_units"]:
                vars = ["Sk_wraps", "Sk_units", "Sk_altlabel", ["20","wpi","wpcm"]]
                if name == "Sk_wraps":
                    self.update_primary(value, *vars)
                elif name == "Sk_units":
                    self.recalc_units(value, *vars)
            # reset ripple updates
            g_inhibit_update = False
        else:
            print(" -Skipping change")

if __name__ == "__main__":
    g_RC_values = Reactive_component()
    w = Widget(g_RC_values)
    widget = w.create()
    widget.appendTo(ltk.window.document.body)

The reason that I am using the names of the variables rather than the variables themselves is because of a side effect of ltk.Model using class variables.
Specifically:

  • I can set a class var just fine with self.Len_measure = "foo"
  • but if I pass that through a function header and try to assign it, then an instance variable is created and the class var is not updated.
    E.g.
# in changed()
self.update_myvar(self.Len_measure)
# and
def update_myvar(self, myvar):
   myvar = "Foo"
   # The iv is created and updated but the classvar is unchanged

So to get around this I send the name of the classvar through (as in changed() above and do a more complicated operation like so:

   def update_primary(self, value, primary_var, unit_var, alt_var,
                      defaults=["250","yd","m"]):
       """
       Update the primary, units, alts on basis of input.
       value can have units or not. Use as supplied and update alt.
       Use defaults if missing or error
       - Have to use self.__setattr__("varname", value) because its class var !
       """
       num, unit = parse_units(value)
       if num and num > -1:
           # got a valid measure but maybe no units
           if not unit:
               # use on-screen units
               units = self.__dict__[unit_var]
               self.__setattr__(primary_var, f"{format_nice(num)}{units}")
               if units in metric:
                   num = num * convert_factors[units]
               self.__setattr__(alt_var, convert_imp(num, unit_swaps[units], self.Len_units=="in"))
           else:  # unit from measure
               if unit not in convert_factors.keys():
                   unit = defaults[1] #"yd"
               self.__setattr__(unit_var, unit)
               usnum,_ = parse_to_US(value)
               self.__setattr__(primary_var, convert_imp(usnum, unit))
               self.__setattr__(alt_var, convert_imp(usnum, unit_swaps[unit]))
       else:  # not valid so set default
           self.__setattr__(primary_var, defaults[0]+defaults[1])
           self.__setattr__(unit_var, defaults[1])
           self.__setattr__(alt_var, convert_imp(int(defaults[0]), defaults[2]))

In short:
I am using self.__setattr__(primary_var, newvalue) instead of primary_var = newvalue because its a classvar.

So I am ok with my solution and its as neat as I can work out how to make it (by using the expanding *vars parameter).

But I wonder if there is another way to architect it so it works differently.
Working example here:

Cheers...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant