Skip to content
This repository

jquery-select2 and pyjs #752

Open
wants to merge 10 commits into from

2 participants

gonvaled C Anthony Risinger
gonvaled

== Description ==

This is an example of how to integrate a complex jQuery component with pyjs.

Here we integrate Select2 to implement a tagging component.
Not all functionality of Select2 is implemented. Only:

  • set / get values
  • receive change notifications

== Issues ==

pyjd is not supported (doesn't do javascript)

The CSS is not right

C Anthony Risinger
Collaborator
xtfxme commented July 18, 2012

bleh im hesitant to include stuff like this because i don't want to encourage it ... and it's subject to change ... but we'll do it once ;-)

i can't give this proper review right at the moment, but here are a couple first-pass changes:

  • force push a new branch that is a single commit. multiple commits are fine if the work calls for it, but these are just your work log (test this/that, trying this/that, etc) and not relevant to the change
  • move jquery-select2 to jqueryselect2 or jquery_select2. i plan on changing the examples to packages rather than independent entities, thus they need to be importable.
  • drop download.sh. i eradicated all shell files and unix-y stuff from the examples structures, use the methods provided (i think you did it correctly, but see EmployeeAdmin as example)
  • that JS eval should not be needed
gonvaled

Thanks Anthony. Still not clear to me is the JS eval comment. How would you change for example this part of the code:

    def get_val(self):
        # This uses select2 to get the value of the element
        myjs = 'parent.jQuery("#%s").select2("val");' % (self.myid)
        return JS(""" eval(@{{myjs}}) """)

I am preparing a branch witha single commit for this, and as soon as this is clarified I will send a pull request.

C Anthony Risinger
Collaborator
xtfxme commented July 25, 2012

sorry i've let this stall for awhile -- i wanted to make sure i could review it proper.

my son is at gradma's Wed - Sun, giving me a block of time to catch up on stuff. as such, i intend on giving this attention sometime in the next 2 days or so ... thanks much :-)

gonvaled gonvaled closed this July 26, 2012
C Anthony Risinger
Collaborator
xtfxme commented July 26, 2012

so you've decided not to see this thru then i take it? i wasn't purposfully stalling ;-) it's just a sensitive subject and needed proper attention.

anyways, i have a block of time tomorrow i intended to direct at this -- LMK ASAP if this was a mistake, else we'll just let it go, no worries either way. thanks gonvaled!

gonvaled gonvaled reopened this July 26, 2012
gonvaled

Oooops! My bad. It seems I closed the pull request which is not at all what I wanted! I was writing a comment an it seems I pushed the wrong button. :(

I repeat here my comment:

"Good that you have some time for this. If you could comment on the get_val question about the JS directive, I can provide again a pull request with a squashed commit of all my changes - that JS bit is the only thing missing now"

C Anthony Risinger
Collaborator

ok @gonvaled, thanks for being so patient.

here are a couple simple things that should be fixed:

  • README -> README.rest
  • rename directory without - (use _ or nothing so it's importable)
  • remove download.sh (legacy, not used)

... i'll follow up with some examples on how to fix the eval() stuff.

gonvaled

Sure Anthony, all those things are fixed on my alternate branch, which I am not showing to you because I want to do it in a single commit (as you correctly pointed out last time). I still haven't committed because of the missing solution for eval.

C Anthony Risinger
Collaborator

ok ... for the most part your example is pretty cool, nice job :-)

now, even though most of this kinda makes me feel yucky, or requires some black magic to pull off, here goes ... i tested these some, and they seem to work as intended (if not, LMK and we'll work thru it). i'm only demonstrating the Select2TaggingComponent.update method -- the others should be clear (again, if not, just LMK).

fairly correct(?) way:

    def update(self, values):
        from __pyjamas__ import wnd
        from __pyjamas__ import Array
        from __pyjamas__ import Object
        values_js = Array()
        for value in values:
            jsob = Object()
            jsob.id = value['id']
            jsob.text = value['text']
            values_js.push(jsob)
        wnd().jQuery('#' + self.myid).select2('val', values_js)

... amazingly, that does the trick. blasphemies like this essentially work because we take many shortcuts in the name of performance and simplicity; for example, "python" strings are still JS strings, anything visible to JS can technically be imported from __pyjamas__, and setattr(...)/getattr(...) calls fall thru to simple dotted attribute access, which of course works in JS. if getattr support is disabled, then the translator emits dotted attribute access directly.

... basically, you have insider knowledge and are taking sweet advantage of it ;-)

somewhat(?) correct way:

    def update(self, values):
        sid = self.myid
        # identical ... look at output JS ;-)
        sid = JS("@{{getattr}}(@{{self}}, 'myid')")
        JS('values_js = Array()')
        for value in values:
            _id = value['id']
            _text = value['text']
            JS('jsob = {}')
            JS('jsob.id = @{{_id}}')
            JS('jsob.text = @{{_text}}')
            JS('values_js.push(jsob)')
        JS("$wnd.jQuery('#' + @{{sid}}).select2('val', values_js)")

... this one is the polar opposite: using raw JS() for almost everything, whilst achieving the same goal and making good use of the @{{...}} construct. i didn't specify which is the "better" method because honestly, i'm not 100% sure. the first "feels" nicer, but it runs more code against the JS objects that is meant for Python. the second is more difficult to understand, but frankly, is probably more stable.

of course, anything using JS(...) is not guranteed to be stable, but for the forseeable future, probably will be. this should give you a better idea about how to make do without eval() or otherwise -- for the example however, i would prefer you used the second method (chock full of JS() calls) because importing random crap from __pyjamas__ is not nearly as supported as the @{{...}} construct.

lastly, i'm pretty sure @{{...}} should work for my.val and my['val'], but i couldn't get it to cooperate ... regardless, the safest method is to always resolve the Python "half" to a single, simple identifier, then "cross-over" to JS. this keeps the "jump" as small as possible, with fewer gotchas.

LMK if issues/questions. thanks @gonvaled!

gonvaled

I see why it took you some time to prepare this! :) Not a simple one-liner ...

I will try this later today, but I have some comments:

  1. The first method is mostly python, the second is mostly javascript. Just because of this fact, I would prefer the first method. Pyjs is supposed to "free us" from javascript, isn't it? :) Getting all the syntax quirks of javascript right can be frustrating ... Of course some javascript can not be avoided (we are integrating a javascript component, so that is expected), but I would prefer to reduce it to the minimum necessary.
  2. Both methods look more complicated than the JS(""" eval(@{{myjs}}) """) that I was using. I am curious: why is that construct 'evil'? It looks very clean and compact, compared to this options.
  3. As you mention, both methods are not really "fully supported". Don't we have a standard, supported way of doing this in pyjs?
C Anthony Risinger
Collaborator

The first method is mostly python, the second is mostly javascript. Just because of this fact, I would prefer the first method. Pyjs is supposed to "free us" from javascript, isn't it? :) Getting all the syntax quirks of javascript right can be frustrating ... Of course some javascript can not be avoided (we are integrating a javascript component, so that is expected), but I would prefer to reduce it to the minimum necessary.

yeah i know the first seems preferable :-) one problem is, as you've stated, we are integrating a JS component so we cannot be free of JS (and to be fair, if one stays in Python-land, one can be nearly 100% free of JS :-). however, the core problem is the first method is a complete hack, and basically exists for demonstration only ... i guess i lied when i said i didn't know which was better, as nowhere else in the entire library exists such code. there are only a handful of objects that are valid imports from __pyjamas__: JS, debugger, INT, and maybe one or two more. the fact that Array and Object work here is an more-or-less an exploit, and thus not really suitable for an example. the @{{...}} method however, is used heavily all over the stdlib, and is the One True Way to marshal Python objects to JS, with robust translator support.

basically, if we are going to offer such solutions to people seeking examples, they should be the most robust/proven/supported. the first method needlessly runs the JS objects thru several Python-land functions (look at the generated code), each of which may introduce bugs and obscurities i'd rather not point out to people in the future. now, the translator could likely be improved to track these things better, but that support does not currently exist.

alas, if you'd like to document both methods (caveats included) that would be perfectly fine, and useful for others' understanding, but it would have to emphasize which is the "correct-y-ish" way.

Both methods look more complicated than the JS(""" eval(@{{myjs}}) """) that I was using. I am curious: why is that construct 'evil'? It looks very clean and compact, compared to this options.

well, i suppose for all the reasons people tend to avoid eval(...) -- it's more difficult to control/debug/understand, weird quoting problems, and poorer performance to boot. my examples are longer than they need to be, i mainly wrote them that way so they would be a 1-to-1 match with each other. but you could easily combine the JS(...) calls:

    def update(self, values):
        sid = self.myid
        JS('values_js = Array();')
        for value in values:
            _id = value['id']
            _text = value['text']
            JS("""values_js.push({id: @{{_id}}, text: @{{_text}}});""")
        JS("$wnd.jQuery('#' + @{{sid}}).select2('val', values_js);")

... which IMO anyway, is much clearer than building a string + eval'ing, even if you didn't know what was going on. ultimately though, if it works i suppose it works, but for an example i want to detail the most "proper-esque" way possible -- to help people succeed -- i don't wan't to disillusion them with methods that "appear to work" or "probably work", esp. if another method is superior/better supported.

As you mention, both methods are not really "fully supported". Don't we have a standard, supported way of doing this in pyjs?

aside from @{{...}} ... not really, and i don't know if we ever will. i've been kicking around the idea of writing a real interface "module" (similar to __pyjamas__) that would allow for bidirectional marshaling of simple types, which could be used to interface more complex types, but no code has been written.

the crux of the issue is you're dropping a level and "gaming" the system, so-to-speak. it's really no different than using assembler within C code -- it only works in certain contexts, certain environments, certain machines/processors/etc: the analogy holds. one of the things i want to accomplish is a 100% asynchronous engine that allows for "pausing" the Python runtime, thus making such things as porting the Threading modules and true XHR dynamic imports possible and/or simpler ... to achieve this end, we'd potentially compile an entire python application/runtime down to a single (or minimal) switch + while loop (which is also, conveniently, a massive performance boost) ... in this context JS(...) would neither make sense nor work at all (but some method/interface would exist for sure).

consider JS "compressor"/minification technology -- ie. renaming/rearranging all of your variables/attrs/etc -- how could you effectively use JS(...) if you can't even guarantee the names are stable? this can wreak havok on dynamic langs like python who are capable of using strings to access attributes: getattr(self, 'some_string').

i guess the simple answer is: no one has come up with or stepped up with a clear way to achieve stability in this area. a good primer/case-study is the libffi project (foreign function interface) which is an interesting library and also happens to be the foundation of the python ctypes module, as well as the dynamic gobject-introspection tools (which are capable of generating python bindings to any gobject-based library, including WebKit, and all of Gnome).

good stuff :-)

C Anthony Risinger
Collaborator

i should clarify in case it's not clear: the JS(...) function is completely supported ... the @{{...}} construct is completely supported (both for the [far] foreseeable future at least) ... but anything you might find, do, see, hallucinate, delete, kick, create, share, shun, ignore, save, or simply interact with -- in any way -- while digging around in "JS-land" is 100% unsupported. in other words ...

dragons be there

... what you see today may change tomorrow with no obligation for stability (but in practice you will be told, and for the most part it's fairly stable...ish).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.