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

Fiery Comets: Support for AJAX responses with comets (shifted to master) #1585

Merged
merged 32 commits into from Jul 24, 2014

Conversation

Projects
None yet
2 participants
@Shadowfiend
Copy link
Member

Shadowfiend commented Jun 20, 2014

This is #1565 reopened with respect to master.

To support AJAX responses that send down comets, this PR actually
refactors a few aspects of comet creation:

  • LiftSession now has a new internal (private) API for finding or creating
    a comet. There are two variations of findOrCreateComet, one that takes
    a type parameter (findOrCreateComet[MyCometType](name, html, attributes))
    and one that takes a string comet type (findOrCreateComet("MyCometType", ...))
    The type parameter version uses manifests to allow direct instantiation of a
    comet without reflection overhead and without requiring the comet to be in the
    regular comet package if not wanted.
  • LiftSession's comet-related code has been significantly refactored to
    eliminate duplication and make things a little easier to follow. Notably, there
    are still some anonymous subclasses of CometActor created for round-trips
    and streaming promises; those will be refactored to independent subclasses
    at some point, but I didn't do it in this PR.
  • Comet ID info (type + name) now has a (private) case class CometId to represent
    it in LiftSession.
  • The CometCreationInfo case class, which was in the Templates.scala file, is
    now in the CometActor.scala file, and is used pervasively to pass comet creation
    info around (before, we were using separate parameters in many places). This is
    still mostly an internal class, however.
  • Publicly, S has a new API for finding or creating a comet, S.findOrCreateComet.
    It works much like the LiftSession API, taking either a type parameter or a string
    that is a comet class's name. Both of the overloads also have a
    receiveUpdatesOnPage parameter that is defaulted to false. When set to true,
    it will automatically append the created CometActor to the page or AJAX request
    so that it will receive updates.
  • S also has a method for adding a comet to the currently rendering page or page
    that invoked the current AJAX callback, S.addComet. It takes a LiftCometActor,
    which you can get from S.findOrCreateComet if you don't have a direct handle
    to it already.
  • The internal client-side JavaScript API now supports aborting the current comet
    request, mostly in order to support restarting the current comet cycle. This is used
    when a new comet is received from the server to ensure the comet starts receiving
    updates immediately.
  • The comet snippet has been updated and simplified to work with these changes.
    It also now has a containerForCometActor method that is publicly available and
    lets you get the HTML container for any given comet actor. This lets you do your
    own work to create a comet container without directly invoking the comet snippet.

Shadowfiend added some commits May 1, 2014

Fix docs on runAjax.
The comment documenting runAjax was outdated in terms
of how that function works, referring to an old continuation-
based approach rather than the current two-thread
approach to dealing with long-running AJAX processing.
Add restartComet() for restarting comet cycle.
restartComet is meant for cases where, for example, a new
comet is added to toWatch and we want its presence
reflected immediately. Without it, we would have to wait for
the current comet request to finish, which could take up to
90s if the old comets had no further updates, and could
bottle up updates from the new comet for that long, because
the new comet wouldn’t be able to push its contents through
the old connection.
Separate registerComet and registerComets in JS.
registerComet is used for registering a single comet, while
registerComets is used to register multiple comets. This as
opposed to the previous form that had some nasty
overloading of parameter meanings to decide based on type
whether we were trying to set one or multiple comets.
AJAX requests now investigate cometAtEnd.
If cometAtEnd contains references to any comets, they are
sent down to the client, though their XHTML currently is not.
Move CometCreationInfo to CometActor.scala.
It was hanging out in Templates.scala for some reason,
where it had no relation to its containing file nor was it used
therein.
initCometActor now takes a CometCreationInfo.
This propagates through callInitCometActor and most
callers thereof. We leave most of LiftSession alone because
comet stuff there is about to get rewritten.

CometCreationInfo’s attributes also change names, to
more descriptive ones.
Add CometId case class.
CometId describes a comet uniquely in the session, and
consists of the comet type and the (possibly Empty) comet
name. The cometSetup SessionVar and nasyncComponents
ConcurrentHashMap both use CometId for indexing.

Not all places that interact with these have been updated,
as there are chunks of LiftSession that are about to get
extensively rewritten; as such, this doesn’t currently
compile.
Significantly reorganize comet find/setup stuff.
Things are split up a little more granularly. We also have a
more uniform and descriptive API (findComet only finds
existing comets, while findOrBuildComet is used to build a
comet if it wasn’t found). The API is still internal to Lift’s
packages.

This still doesn’t quite compile, as it breaks the Comet
snippet.
Expose comet finding/creation in S.
We now have S.findOrCreateComet in two forms, with a
type parameter and with a String cometType. We also use
this to make the Comet snippet work again.

Note that the Comet snippet used to support comets with no
types, but that is no longer acceptable.
Strip callInitCometActor overload.
This was the old non-CometCreationInfo overload for
callInitCometActor, which is no longer used.
Rework findOrBuildComet[T].
Instead of converting T to a simple type name and then going
through the usual comet lookup pipeline, we use the Class
instance given us by the manifest for T to directly try to
instantiate the class, which is just cleaner.
Consolidate functionality in comet building code.
This all in LiftSession, where the comet building code
refactoring had left some duplication littered around.
Comet UpdateDefaultXml message->UpdateDefaultHtml.
We’ve moved towards naming things “html” when dealing with
comets, so this follows that trend.
Rework Comet snippet.
In particular, the snippet now exposes a
Comet.containerForCometActor method that can be used by
an arbitrary third party consumer to produce the container for
a given comet actor when a handle to the LiftCometActor
object already exists.

It is, of course, still possible to materialize a CometActor using
regular lift:comet snippet.
Track comets to add to LiftMerge using RequestVar.
Before, comet tags included a lift:when attribute that was
used to extract comet versions and such. We now track
these when S.findOrCreateComet is called, and in LiftMerge
we use this list instead of looking for the marker attribute.

Because all invocations of the Comet snippet go through
S.findOrCreateComet, these are automatically tracked;
however, things added via S.addCometAtEnd are currently
lost, and we’re not handling AJAX send-down yet.
Fix AJAX send-down to use S.requestCometVersions.
This as opposed to using the lift:when marker attributes in
S.cometAtEnd.
Rip out lift:when attribute from comet stuff.
We’re no longer using this to properly register comets on the
client.
Replace S.addCometAtEnd with S.addComet.
S.addComet takes a LiftCometActor instead of an Elem. It
replaces the private trackComet that simply added an entry
to S.requestCometVersions.

Additionally, this makes server-side actors and roundtrip
streaming promises work again.
Default S.findOrCreateComet to not call addComet.
The first pass made receiveUpdatesOnPage true by default;
on second thought, S.findOrCreateComet will by default not
add the comet to receive updates on the page. The Comet
snippet explicitly sets that parameter to true so that those
comets are registered to receive updates.

You can also separately receive updates by calling
S.addComet with the comet actor.
Don't call lift.registerComets if there are none.
Before we were sending down the lift.registerComets
invocation with an empty object even in cases where there
were no comets to send down with an AJAX response; we
now generate no additional JS in those cases.
Return XHR objects from ajaxGet.
jQuery returns an API-compatible object. We do this so that
we can properly abort requests when comets need to be
reset.
Don't invoke AJAX failure callback on abort.
We only abort to reset comet requests, and in those cases
we explicitly handle retries as successes rather than
failures.

@Shadowfiend Shadowfiend changed the title Fiery Comets: Support for AJAX responses with comets Fiery Comets: Support for AJAX responses with comets (shifted to master) Jun 21, 2014

@farmdawgnation farmdawgnation added this to the 3.0-M2 milestone Jun 28, 2014

@farmdawgnation

This comment has been minimized.

Copy link
Member

farmdawgnation commented Jun 28, 2014

It's on my to-do list to do a pass on this PR this weekend. I will (hopefully) actually get to that pass. Want to test this by building a sample project and seeing if I can break it.

@Shadowfiend

This comment has been minimized.

Copy link
Member Author

Shadowfiend commented Jul 2, 2014

Any news? :)

@farmdawgnation

This comment has been minimized.

Copy link
Member

farmdawgnation commented Jul 2, 2014

Still on my list.

@farmdawgnation

This comment has been minimized.

Copy link
Member

farmdawgnation commented Jul 4, 2014

Haven't looked at the code quite yet, but I was able to do a successful test of sending down a comet via AJAX!

You can find the sample project I used here: https://github.com/farmdawgnation/fiery-comets-test

* &lt;lift:comet type=&lt;Your comet class> name=&lt;Optional, the name of this comet instance>>{xhtml}</lift:comet>
* </pre>
* {{{
* <lift:comet type="MyCometClass"> name="Optional, the name of this comet instance">{xhtml}</lift:comet>

This comment has been minimized.

@farmdawgnation

farmdawgnation Jul 4, 2014

Member

Can we change this to use the data-lift syntax while we're at it?

This comment has been minimized.

@Shadowfiend

Shadowfiend Jul 6, 2014

Author Member

Oh suure.

cometName: Box[String],
cometHtml: NodeSeq,
cometAttributes: Map[String, String],
session: LiftSession)

This comment has been minimized.

@farmdawgnation

farmdawgnation Jul 4, 2014

Member

Would we benefit from providing default attributes for any of these?

This comment has been minimized.

@Shadowfiend

Shadowfiend Jul 6, 2014

Author Member

This is used internally at the moment, but has been public API since it was introduced. I'm not messing with it for now, we'll see in the future. Interactions with it should generally go through the S methods anyway.

name: Box[String],
defaultHtml: NodeSeq,
attributes: Map[String, String]): Unit
protected def initCometActor(creationInfo: CometCreationInfo): Unit

def theType: Box[String]

This comment has been minimized.

@farmdawgnation

farmdawgnation Jul 4, 2014

Member

This should be changed to a String since no-type Comets aren't supported anymore right?

This comment has been minimized.

@Shadowfiend

Shadowfiend Jul 6, 2014

Author Member

I believe this is still here because I didn't get a chance to fully extricate it. Since it's rarely used except internally in comet stuff, I'd rather put off completing this removal until a later PR.

case (Full(xml), _, true) => LiftRules.jsArtifacts.setHtml(id + "_outer", (
spanFunc(0, Helpers.stripHead(xml)) ++ fixedXhtml.openOr(Text(""))))
spanFunc(Helpers.stripHead(xml)) ++ fixedXhtml.openOr(Text(""))))

This comment has been minimized.

@farmdawgnation

farmdawgnation Jul 4, 2014

Member

What were these ints used for anyway?

This comment has been minimized.

@Shadowfiend

Shadowfiend Jul 6, 2014

Author Member

We were using a lift:when attribute to handle some aspects of client-side comet registration at some point in the past (in particular, I believe to indicate the current version of the comet at render time so you could get any messages after it when the browser fired up the comet connection). It's no longer used, however.

true
).cmd
} else {
js.JsCmds.Noop

This comment has been minimized.

@farmdawgnation

farmdawgnation Jul 4, 2014

Member

Can we do some package imports so we don't have to mention these package names all throughout this function?

This comment has been minimized.

@Shadowfiend

Shadowfiend Jul 6, 2014

Author Member

I intentionally didn't do that because it feels like this code block is doing too much JS work right now. I didn't figure out how to fix it when I first opened the PR, but I'd like for it to stand out as a sign that we should probably be doing this a different way. Hopefully I'll get to revisit it at some point, since I think there's still some good comet work to be done.

This comment has been minimized.

@farmdawgnation

attemptedComet match {
case fail @ Failure(_, Full(e: java.lang.NoSuchMethodException), _) =>
val message = s"Failed to find appropriate comet constructor for ${cometClass.getCanonicalName}. Tried no arguments and (LiftSession, Box[String], NodeSeq, Map[String,String])"

This comment has been minimized.

@farmdawgnation

farmdawgnation Jul 4, 2014

Member

Do you think the added information about which variants of constructors were tried will be valuable to the end user? Seems like something that could quickly become a lie (constructors change, message doesn't). Would it inform their action?

This comment has been minimized.

@Shadowfiend

Shadowfiend Jul 6, 2014

Author Member

It is a huge hint on what actually went wrong, and I think more detail is always a better move in cases where we're trying to implicitly invoke something without the user telling us that's what we should be invoking. Moreover, I think essentially nobody will run into this particular issue, as a no-arg constructor is the way most people use comet actors. In the future, it might be worth exploring how to unify the message with the code to make it harder to let those two get unsynced.

Also fingers crossed on code review catching such a change, of course ;)

This comment has been minimized.

@farmdawgnation

farmdawgnation Jul 7, 2014

Member

Ahhhhhhhh ok, after chatting with you I now understand how this error is generated. I didn't previously... read like something internal that the user couldn't fix. Perhaps this message could read...

Couldn't find valid comet constructor for ${cometClass.getCanonicalName}. Comets should have a no argument constructor or one that takes the following arguments: (LiftSession, Box[String], NodeSeq, Map[String,String]).

How does that sound?

This comment has been minimized.

@Shadowfiend

Shadowfiend Jul 24, 2014

Author Member

I'm down.

@Shadowfiend

This comment has been minimized.

Copy link
Member Author

Shadowfiend commented Jul 6, 2014

A broad comment before I respond on more specific stuff: my general policy when working on Lift is “clean up/refactor as much as I'm motivated to in a given pass”. Other folks are welcome to do additional cleanup and refactoring, but don't be surprised if I turn down the option of doing additional work on a given corner that's unrelated to the feature work.

Tweak comet snippet API documentation.
We now use div data-lift instead of lift:comet, and fix some small issues in
the remaining docs.
@farmdawgnation

This comment has been minimized.

Copy link
Member

farmdawgnation commented Jul 22, 2014

Hey Antonio, if you can give me your thoughts on this line comment - that's the only thing I want an answer on before merging this. :)

@farmdawgnation

This comment has been minimized.

Copy link
Member

farmdawgnation commented Jul 24, 2014

ship it

farmdawgnation added a commit that referenced this pull request Jul 24, 2014

Merge pull request #1585 from lift/fiery-comets
Support for sending down comets via AJAX, and does various refactoring in the comet code.

@farmdawgnation farmdawgnation merged commit 19a71d7 into master Jul 24, 2014

@farmdawgnation farmdawgnation deleted the fiery-comets branch Jul 24, 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment