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

Toggling classes on click #1032

Closed
cramforce opened this issue Dec 1, 2015 · 20 comments
Closed

Toggling classes on click #1032

cramforce opened this issue Dec 1, 2015 · 20 comments

Comments

@cramforce
Copy link
Member

I believe we discussed this before, but I think it might the time to do this.

<button on="tap:element.toggleClass(someClass)">

or similar.

I'd like to avoid this being used to toggle ads, so I was thinking to limit it on elements that do not contain ads. One could still e.g. change zIndexes to put ads behind content. Anyone have an idea how to make it more foolproof?

CC @dvoytenko @rudygalfi

@rudygalfi
Copy link
Contributor

@cramforce Do you think this is the same as #1017?

@AliberGzz
Copy link

This will be possible ?

@sebastianbenz
Copy link
Contributor

Have we got a timeline for this? Would love to have a show/hide boilerplate button for http://amp-by-example.appspot.com.

@dvoytenko
Copy link
Contributor

We got according now that could be used for this, I think. /cc @sriramkrish85

@camelburrito
Copy link
Contributor

@sebastianbenz <amp-accordion> should let you do that.
amp-accordion is now experimental - we will need to add validator support for this.

@sebastianbenz
Copy link
Contributor

@sriramkrish85 @dvoytenko Works splendid! When is it going to be released?

@sebastianbenz
Copy link
Contributor

@dvoytenko
Copy link
Contributor

@sebastianbenz I'll leave it to @rudygalfi to state the dates.

@rudygalfi
Copy link
Contributor

We need to get the validation in. @ericlindley-g is going to track that down with @Gregable.

@rudygalfi
Copy link
Contributor

@ericlindley-g @choumx To resolve if this is covered by bind, could be folded into bind, or should be closed.

@ericlindley-g
Copy link
Contributor

Yep—covered by bind. Closing for now, but folks should feel free to re-open if there's a need for a simpler syntax, as proposed above.

@westonruter
Copy link
Member

@ericlindley-g I think more is needed here. In particular, what if there are multiple variable classes on an element already (such as those added to body by WordPress), and we need to toggle just one of them? Currently this can't be done with bind because isn't able to mutate the existing classes.

If bind were able to reference the existing state that could be a solution. So maybe something like this, with WP template code to show intended usage:

<body <?php body_class(); ?> [class]="ampConf.mobileMenu ? add( 'no-scroll' ) : remove( 'no-scroll' )'" >

It would be important for amp-bind to compare the class attribute in a way that ordering of the class names doesn't matter, or else we'd get:

amp-bind: Default value for [class] does not match first expression result (). This can result in unexpected behavior after the next state change.

So perhaps it is better to go the action route as initially proposed.

<body id="body" <?php body_class(); ?>>
<button on="tap:body.toggleClass('no-scroll')">Toggle menu</button>

There should probably be addClass and removeClass as well.

@ericlindley-g
Copy link
Contributor

Good point, @westonruter — reopening and /cc @choumx for visibility

@ericlindley-g ericlindley-g reopened this Feb 2, 2018
@dreamofabear
Copy link

A bit clunky, but you could store the default classes in an <amp-state> component:

<body [class]="toggleVariable 
    ? defaultBodyClasses.concat('no-scroll') 
    : defaultBodyClasses">

But yea, this sounds a lot like #10622. And @sebastianbenz's suggestion in particular: #10622 (comment)

toggleClass() action makes sense for simple cases, but won't interoperate with amp-bind so we could build both options. We do have show() and hide() actions which also overlap with amp-bind's functionality. This might make a decent GFI actually.

@westonruter
Copy link
Member

Here's how I ended up implementing a state-based toggle on the body element in WordPress:

<body <?php body_class(); ?> [class]="navMenu.expanded ? <?php echo esc_attr( wp_json_encode( get_body_class( 'sidebar-open' ) ) ) ?> : <?php echo esc_attr( wp_json_encode( get_body_class() ) ); ?>">

Importantly, this only would allow for toggling a single class. It's also not very pretty.

@westonruter
Copy link
Member

westonruter commented May 3, 2018

In the WordPress theme there is a button that invokes jQuery to toggleClass:

<button class="menu-toggle" aria-controls="primary-menu" aria-expanded="false"><?php esc_html_e( 'Primary Menu', '_s' ); ?></button>

Here's how I worked out to accomplish (mostly) the same with AMP, with hacking a label to work like a button that toggles a hidden checkbox: (Update: This is wrong. See #1032 (comment))

<amp-state id="siteNavigationMenu">
	<script type="application/json">
		{
			"expanded": false
		}
	</script>
</amp-state>
<input
	id="site-navigation-expanded"
	type="checkbox"
	hidden
	on="change:AMP.setState( { siteNavigationMenu: { expanded: event.checked } } )"
>
<label
	class="menu-toggle"
	for="site-navigation-expanded"
	tabindex="0"
	role="button"
	aria-controls="primary-menu"
	aria-expanded="false"
	[aria-expanded]="siteNavigationMenu.expanded ? 'true' : 'false'"
>
	<?php esc_html_e( 'Primary Menu', '_s' ); ?>
</label>

Notice importantly that the “button” isn't only needing to cause the body class to toggle. It also needs to cause the aria-expanded attribute to toggle. This would be well served by an AMP action which toggles some state. With such a state toggle, the above checkbox+label could be restored as a button, something like this: (Update: This is unnecessary. See #1032 (comment))

<button
	class="menu-toggle"
	aria-controls="primary-menu"
	aria-expanded="false"
	[aria-expanded]="siteNavigationMenu.expanded ? 'true' : 'false'"
	on="change:AMP.toggleState( 'siteNavigationMenu', 'expanded' )"
>
	<?php esc_html_e( 'Primary Menu', '_s' ); ?>
</button>

Where toggleState takes a list of args that corresponds to the state tree branch to toggle.

@sebastianbenz
Copy link
Contributor

Interesting problem. Here is another approach that supports toggling multiple body classes:

<body class="a b c" [class]="globalState.join(' ')">
  
  <amp-state id="globalState"><script type="application/json">["a", "b", "c"]</script></amp-state>
  
  <amp-bind-macro id="toggle" 
                arguments="value" 
                expression="globalState.includes(value) ? globalState.filter(x => x != value) : globalState.concat(value)" />
  
  <button on="tap:AMP.setState({ globalState: toggle('a') })" aria-expanded="true" [aria-expanded]="[globalState.includes('a')]">toggle 'a'</button>
  <button on="tap:AMP.setState({ globalState: toggle('b') })" aria-expanded="true" [aria-expanded]="[globalState.includes('b')]">toggle 'b'</button>
  <button on="tap:AMP.setState({ globalState: toggle('c') })" aria-expanded="true" [aria-expanded]="[globalState.includes('c')]">toggle 'c'</button>

</body>

Full example: https://jsbin.com/qilalovife/edit?html,output.

It's still not very nice, but solves the problem. It'd be nicer if amp-bind-macro would support actions (or a new extension amp-bind-action.

@westonruter
Copy link
Member

@sebastianbenz Good idea to use concat. That made me realize I could actually avoid manipulating the body class state entirely, in favor of dynamically concatenating a class name based on whether or not some other state value changes:

<body <?php body_class(); ?> [class]="minnow.bodyClasses.concat( minnow.navMenuExpanded ? 'sidebar-open' : '' ).filter( className =&gt; '' != className )">
<amp-state id="minnow">
	<?php
	$state = array(
		'bodyClasses'     => get_body_class(),
		'navMenuExpanded' => false,
	)
	?>
	<script type="application/json"><?php echo wp_json_encode( $state ); ?></script>
</amp-state>

Given this value of the [class] attribute, the .concat( minnow.navMenuExpanded ? 'sidebar-open' : '' ) part could be repeated any number of times according to the class names that should toggle based on changes to state props. Maybe it's simpler than your example, but maybe it's not as great either.

Note: The =&gt; here is due to a limitation in the AMP for WordPress plugin, noted in ampproject/amp-wp@52b3616

Aside: I found that if you return an array as the value of [class] it automatically gets concatenated with a space.

@westonruter
Copy link
Member

What I wrote in #1032 (comment) was wrong. It is trivial to toggle state already using straight AMP.setState(). All that is needed is to add an on attribute like so:

<button
	class="menu-toggle"
	aria-controls="primary-menu"
	aria-expanded="false"
	on="tap:AMP.setState( { siteNavigationMenu: { expanded: ! siteNavigationMenu.expanded } } )"
	[aria-expanded]="siteNavigationMenu.expanded ? 'true' : 'false'"
>...

@sebastianbenz
Copy link
Contributor

:-) even better. I thought being able to toggle multiple classes on the same element was your problem, which can result in awkward nested if statements.

@dreamofabear dreamofabear changed the title Toggling classes on click amp-bind: Easier toggling of classes Jul 9, 2018
@dreamofabear dreamofabear changed the title amp-bind: Easier toggling of classes Toggling classes on click Dec 6, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants