Implement toggled classes

Closes 
This commit is contained in:
Chris Wong 2016-11-19 15:59:33 +13:00
parent 8f77990e8c
commit 585ed3851b
3 changed files with 67 additions and 20 deletions

View file

@ -481,7 +481,8 @@ impl<'cx, 'a, 'i> Parser<'cx, 'a, 'i> {
/// Parses and renders the attributes of an element.
fn attrs(&mut self) -> PResult<()> {
let mut classes = Vec::new();
let mut classes_static = Vec::new();
let mut classes_toggled = Vec::new();
let mut ids = Vec::new();
loop {
let old_input = self.input;
@ -525,7 +526,18 @@ impl<'cx, 'a, 'i> Parser<'cx, 'a, 'i> {
(Err(_), &[dot!(), ident!(_, _), ..]) => {
// Class shorthand
self.shift(1);
classes.push(self.name().unwrap());
let class_name = self.name().unwrap();
match *self.input {
[TokenTree::Delimited(_, ref d), ..] if d.delim == DelimToken::Bracket => {
// Toggle the class based on a boolean expression
self.shift(1);
let cond = self.with_rust_parser(d.tts.clone(), RustParser::parse_expr)?;
let cond = cond.to_tokens(self.render.cx);
classes_toggled.push((cond, class_name));
},
// Emit the class unconditionally
_ => classes_static.push(class_name),
}
},
(Err(_), &[pound!(), ident!(_, _), ..]) => {
// ID shorthand
@ -538,9 +550,22 @@ impl<'cx, 'a, 'i> Parser<'cx, 'a, 'i> {
},
}
}
if !classes.is_empty() {
if !classes_static.is_empty() || !classes_toggled.is_empty() {
self.render.attribute_start("class");
self.render.string(&classes.join(" "));
self.render.string(&classes_static.join(" "));
for (i, (cond, mut class_name)) in classes_toggled.into_iter().enumerate() {
// If a class comes first in the list, then it shouldn't be
// prefixed by a space
if i > 0 || !classes_static.is_empty() {
class_name = format!(" {}", class_name);
}
let body = {
let mut r = self.render.fork();
r.string(&class_name);
r.into_stmts()
};
self.render.emit_if(cond, body, None);
}
self.render.attribute_end();
}
if !ids.is_empty() {

View file

@ -3,6 +3,8 @@
extern crate maud;
use maud::Markup;
#[test]
fn literals() {
let s = html!("du\tcks" "-23" "3.14\n" "geese").into_string();
@ -76,6 +78,22 @@ fn empty_attributes() {
assert_eq!(s, r#"<div readonly><input type="checkbox" checked></div>"#);
}
#[test]
fn toggle_empty_attributes() {
let rocks = true;
let s = html!({
input checked?[true] /
input checked?[false] /
input checked?[rocks] /
input checked?[!rocks] /
}).into_string();
assert_eq!(s, concat!(
r#"<input checked>"#,
r#"<input>"#,
r#"<input checked>"#,
r#"<input>"#));
}
#[test]
fn colons_in_names() {
let s = html!(pon-pon:controls-alpha a on:click="yay()" "Yay!").into_string();
@ -121,6 +139,26 @@ fn hyphens_in_class_names() {
assert_eq!(s, r#"<p class="rocks-these are--my--rocks">yes</p>"#);
}
#[test]
fn toggle_classes() {
fn test(is_cupcake: bool, is_muffin: bool) -> Markup {
html!(p.cupcake[is_cupcake].muffin[is_muffin] "Testing!")
}
assert_eq!(test(true, true).into_string(), r#"<p class="cupcake muffin">Testing!</p>"#);
assert_eq!(test(false, true).into_string(), r#"<p class=" muffin">Testing!</p>"#);
assert_eq!(test(true, false).into_string(), r#"<p class="cupcake">Testing!</p>"#);
assert_eq!(test(false, false).into_string(), r#"<p class="">Testing!</p>"#);
}
#[test]
fn mixed_classes() {
fn test(is_muffin: bool) -> Markup {
html!(p.cupcake.muffin[is_muffin].lamington "Testing!")
}
assert_eq!(test(true).into_string(), r#"<p class="cupcake lamington muffin">Testing!</p>"#);
assert_eq!(test(false).into_string(), r#"<p class="cupcake lamington">Testing!</p>"#);
}
#[test]
fn ids_shorthand() {
let s = html!(p { "Hi, " span#thing { "Lyra" } "!" }).into_string();

View file

@ -37,22 +37,6 @@ fn attributes() {
assert_eq!(s, r#"<img src="pinkie.jpg" alt="Pinkie Pie">"#);
}
#[test]
fn empty_attributes() {
let rocks = true;
let s = html!({
input checked?[true] /
input checked?[false] /
input checked?[rocks] /
input checked?[!rocks] /
}).into_string();
assert_eq!(s, concat!(
r#"<input checked>"#,
r#"<input>"#,
r#"<input checked>"#,
r#"<input>"#));
}
static BEST_PONY: &'static str = "Pinkie Pie";
#[test]