gss / engine

GSS engine
http://gss.github.io
MIT License
2.86k stars 105 forks source link

Is it possible to create constrains in Javascript? #163

Open ccorcos opened 9 years ago

ccorcos commented 9 years ago

Is it possible to code up all these constraints using javascript?

Inviz commented 9 years ago

You have multiple options:

There're two optional arguments for engine.solve when you use it with tree: continuation and scope. Continuation is a string path, which can be passed to engine.remove(continuation) to undo constraints. Cassowary instance are destroyed automatically for you when all continuation keys were removed. Last argument is a scope, which will be used to scope all your variables as properties (e.g engine.solve(['==', ['get', 'a'], 100], '', '$test') will produce '$test[a]' variable, so you can use the same AST tree for multiple different instances of objects.

In case you don't want to use DOM, you may bypass most of gss completely via engine.update([{key: continuation}, ['get', '$my-id[width]', ['get', 'a']]]). We use it for worker internally. It detects dependency graphs, and creates multiple cassowaries for you. Use engine.remove(continuation) to clean things up.

bergie commented 9 years ago

Another example of GSS JavaScript API usage: https://github.com/gss/engine/blob/2.1.x/spec/nodejs/smoke.coffee (running on Node.js, no DOM needed!)

ccorcos commented 9 years ago

Ok. Suppose I want to use the parser so I can write something like #gss[width] + #cassowary[width] == 350. How do I get CSS rules from this? Or if its done with javascript, how are those rules enforced?

Inviz commented 9 years ago

GSS.Parser.parse(text).commands will output an AST that you can pass to engine.solve() as a first argument. There was a typo in my previous comment, it seems you have to use .commands property to access the actual AST as of now

ccorcos commented 9 years ago

ok, and engine.solve will keep the dom updated?

Inviz commented 9 years ago

Yes. It will observe DOM via MutationObserver and update values if new constraints were added or removed. Basically engine.solve(operation, continuation, scope), engine.remove(continuation) and engine.addEventListener('solve', ...) is most like you only things you will ever need to use.

idokutela commented 9 years ago

Hi there, I've also been playing around with dynamically adding constraints in js, but I've been having problems. I thought I understood Inviz's recipe, but it seems like I don't, and hunting around the GSS 'engine' and 'document' code hasn't helped. I hope you don't mind if I ask some more details.

Here is what I imagine doing:

I've hacked together a Plunkr to do that -- http://plnkr.co/edit/HDui1UQqznAa141JkuSQ At least, I hoped it would do that.

I ran into the following problems, though:

  1. I seem to have problems applying 'solve' more than once. To illustrate this, I have a button that whenever pressed, adds a constraint of the form '.dingbat[top]>=::window[top]+counter', where the counter increases with each press. Clearly these constraints don't contradict each other, but after solving the first one successfully, the engine stops doing anything when the new constraints are added. What am I missing?
  2. I don't understand what a continuation is, and how to remove constraints. To illustrate that, the Plunkr has two radio buttons that are supposed to anchor the panel left and right when checked. I wanted to do this by adding a constraint for the appropriate edge when the button is checked, with a continuance to label it, and if the button is unchecked, using the label to remove the constraint. It seems, however, that it doesn't quite work as I imagined.

Of course, I am perfectly aware that both of these simple examples can be solved in much simpler ways. They were just the most basic ways I could come up with to illustrate my problems.

Sorry about the basic questions. If there is some documentation of how to interact with GSS in JS that would answer all of them, please feel free just to send me there -- I looked for such documentation, but couldn't find it.

A not-very-related (hopefully small) question : it seems one keeps track of the GSS engine lifecycle by listening to events: is there a list of such events, and a description of the actual GSS lifecycle anywhere?

Thanks in advance for any help!

Inviz commented 9 years ago

Hey. Sorry, it's my bad. I think I didnt rewire engine.remove back properly when we split engine & document. It doesnt update DOM query storage, but only cleans up constraints (which we have tests for). I used this custom remove function:

engine.$remove = function(key) {
    engine.solve(function() { // open transaction
        engine.input.Query.prototype.unobserve(this, key); // remove observers
        engine.input.Query.prototype.clean(this, key); // remove current queries
        this.remove(key) // remove all globals if any
    })
}

http://plnkr.co/edit/B4qNo4GXMqg35JqIofAj?p=preview

We use events very sparringly in GSS. You can find all of then in Engine.coffee file in engine repo in commit() function. They just represent certain parts of (cycling) workflow. Only other even we do is remove which is called for each removed string path

Continuation is a string that represents path in GSS source. I think you got that right. Your effects have global continuation like leftKey, while .panel selector will have continuation leftKey.panel$element-id

Inviz commented 9 years ago

To push element further and further down, use suggested variables (see docs). Basically you'll have a static constraint like:

.panel[top] >= $my-special-value

then do engine.solve({'my-special-value': 100}). For best results, make sure you initialize engine with this object so variables have some initial values, otherwise GSS will try to solve them

Inviz commented 9 years ago

http://plnkr.co/edit/OIz47SIjlHKtf3flaQCe?p=preview Here's your code working without changes working on latest ranges2 branch of document. I fixed the bug. Please use that branch for now.

idokutela commented 9 years ago

That's brilliant! Thanks a lot. I hope you don't mind me asking some more clarifying questions:

  1. As I understand it, solve takes three types of arguments in the first parameter -- objects, AST, and callbacks. Am I right that when one passes it objects, this should be a key/value pair where the key is the name of a variable, and the value is whatever the value should be of that variable? Or is it possible to put other things in that object (like more general constraints)?
  2. I had never understood what the callback function was supposed to do when passed to solve, but your example clarifies this nicely. So is it the case that when one passes a callback to solve, the callback does a bunch of operations, that solve then executes as a single transaction? Could you perhaps point me to somewhere in the code where I can see some examples of how this is done?

As to pushing the element down. I'm afraid I didn't do a very good job of explaining the point of including that button: what confuses me is that I don't seem to be able to call solve multiple times and have it have an effect. You are, of course, perfectly correct that pushing down the element can be done much more elegantly (and presumably, also with better performance) by having a single constraint with a variable that one then changes (thanks, btw, to whomever wrote the relevant documentation here: http://gridstylesheets.org/guides/ccss/) but I was playing around with adding constraints dynamically, and noticed that sometimes solve just didn't seem to act after the first constraint was added. The 'pushing down' example was the simplest I could come up with to illustrate that -- I would love to understand why it doesn't work. After all, putting something (admittedly retarded) like:

    .something[top]>=::window[top];
    .something[top]>=::window[top]+10;

into a gss script works perfectly well.

Ok, that is enough for now. Thanks again for your help.

Inviz commented 9 years ago

1) solve()s first argument is a problem - either transaction closure, suggested values object or AST (which is fairly clean in itself, you might want to write it by hand instead of generating strings. Then if there's a problem argument, a following function argument might be treated as callback.You can also use engine.then() which is the same as engine.once('solve',...). Unless you use workers, everything's computed synchronously - so you dont actually need to wait in most cases (as you did with outputting results of solution). I think you can remove + add constraints within a single transaction. and it wont redraw intermediate state.

https://github.com/gss/document/blob/master/src/Document.coffee#L267-L276 Here's an example of transaction used to update multiple outside values in a single tick

2) In theory the way you use lowerAST should work, but it doesnt right now, I think because of DOM Query module mishap - it consider there were no updates in .panel collection and kinda bails out. The remedy again would be to use unique continuation strings for each source of side effect, and clean them up as well. It doesnt happen internally, because system generates unique paths during evaluation. But in your case there's just a .panel query. I plan to revisit Query module and will think how to deal better with this problem.

idokutela commented 9 years ago

Ok, thanks a lot. I made a simple change as you suggested to the plunkr, to add a unique key to each new constraint, and it works perfectly. For the sake of anyone else who stumbles on this thread, here's the result: http://plnkr.co/edit/F9LZtyk4J45EuSeRQpMN . I think I don't quite understand what continuations really are, though. I was under the impression that they were just keys, but when I tried to use a number as a continuation, the engine complained. Are they actually more subtle than just keys? And what types are accepted as continuations?

I don't think I completely understand how to use and build transactions either, but I think that's more my fault than anything else. I guess I've got some code to read...

Thanks again for all your help!

ccorcos commented 8 years ago

@Inviz I played around with raw cassowary tonight and it doesn't have all the layout niceness you get from GSS. Particularly if you absolutely position everything, you can't use the native layout engine for text rendering... How does GSS deal with this?