Skip to content

Commit 88b5e94

Browse files
cectonsiku2
andauthored
Few improvements for Classes (#1588)
* Initial commit Forked at: 66d506e Parent branch: yewstack/master * Use Extend trait; push and contains can use AsRef<str> * Improve test a tiny bit * rustfmt * Reducing allocations * Add new_static to allow manipulating &'static str instead of String * rustfmt * Revert "rustfmt" This reverts commit 17a5f35. * Revert "Add new_static to allow manipulating &'static str instead of String" This reverts commit 35a767e. * Use Cow<'static, str> instead * Update yew/src/virtual_dom/mod.rs Co-authored-by: Simon <simon@siku2.io> * WIP Forked at: 66d506e Parent branch: yewstack/master * Using clone * Update yew/src/virtual_dom/mod.rs Co-authored-by: Simon <simon@siku2.io> * Apply suggestions from code review Co-authored-by: Simon <simon@siku2.io> * WIP Forked at: 66d506e Parent branch: yewstack/master * Fix futures example * WIP Forked at: 66d506e Parent branch: yewstack/master * WIP Forked at: 66d506e Parent branch: yewstack/master * Revert "WIP" This reverts commit 8962b0e. * Rename Classes to HTMLClasses * Add new object Classes to use with components * WIP Forked at: 66d506e Parent branch: yewstack/master * Get rid of extra implementation \o/ * WIP Forked at: 66d506e Parent branch: yewstack/master * rustfmt * Revert to suggested solution * Add suggested solution to handle component class * Add suggested Transformer * Revert "Add suggested Transformer" (conflicting implementations) This reverts commit f91f7a9. Co-authored-by: Simon <simon@siku2.io>
1 parent 8efba73 commit 88b5e94

3 files changed

Lines changed: 122 additions & 54 deletions

File tree

examples/futures/src/markdown.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@ use yew::{html, Html};
1010
/// Note that this has a complexity of O(n),
1111
/// where n is the number of classes already in VTag plus
1212
/// the number of classes to be added.
13-
fn add_class(vtag: &mut VTag, class: &str) {
13+
fn add_class(vtag: &mut VTag, class: impl Into<Classes>) {
1414
let mut classes: Classes = vtag
1515
.attributes
1616
.iter()
17-
.find(|(k, _)| k == &"class")
18-
.map(|(_, v)| AsRef::as_ref(v))
19-
.unwrap_or("")
20-
.into();
17+
.find(|(k, _)| *k == "class")
18+
.map(|(_, v)| Classes::from(v.to_owned()))
19+
.unwrap_or_default();
2120
classes.push(class);
2221
vtag.add_attribute("class", classes.to_string());
2322
}

yew-macro/src/html_tree/html_tag/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,11 @@ impl ToTokens for HtmlTag {
234234

235235
let push_classes = match classes {
236236
Some(ClassesForm::Tuple(classes)) => {
237+
let n = classes.len();
237238
let sr = stringify::stringify_at_runtime(quote! { __yew_classes });
238239
Some(quote! {
239-
let __yew_classes = ::yew::virtual_dom::Classes::default()
240-
#(.extend(#classes))*;
240+
let mut __yew_classes = ::yew::virtual_dom::Classes::with_capacity(#n);
241+
#(__yew_classes.push(#classes);)*
241242

242243
if !__yew_classes.is_empty() {
243244
#vtag.__macro_push_attribute("class", #sr);

yew/src/virtual_dom/mod.rs

Lines changed: 115 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@ pub mod vtext;
1616
use crate::html::{AnyScope, NodeRef};
1717
use cfg_if::cfg_if;
1818
use indexmap::{IndexMap, IndexSet};
19-
use std::borrow::Cow;
20-
use std::fmt;
21-
use std::{collections::HashMap, hint::unreachable_unchecked, iter, mem, rc::Rc};
19+
use std::{
20+
borrow::{Borrow, Cow},
21+
collections::HashMap,
22+
fmt,
23+
hint::unreachable_unchecked,
24+
iter::{self, FromIterator},
25+
mem,
26+
rc::Rc,
27+
};
2228
cfg_if! {
2329
if #[cfg(feature = "std_web")] {
2430
use crate::html::EventListener;
@@ -315,109 +321,135 @@ impl Default for Attributes {
315321
/// A set of classes.
316322
#[derive(Debug, Clone, Default)]
317323
pub struct Classes {
318-
set: IndexSet<String>,
324+
set: IndexSet<Cow<'static, str>>,
319325
}
320326

321327
impl Classes {
322-
/// Creates an empty set of classes.
328+
/// Creates an empty set of classes. (Does not allocate.)
323329
pub fn new() -> Self {
324330
Self {
325331
set: IndexSet::new(),
326332
}
327333
}
328334

335+
/// Creates an empty set of classes with capacity for n elements. (Does not allocate if n is
336+
/// zero.)
337+
pub fn with_capacity(n: usize) -> Self {
338+
Self {
339+
set: IndexSet::with_capacity(n),
340+
}
341+
}
342+
329343
/// Adds a class to a set.
330344
///
331345
/// If the provided class has already been added, this method will ignore it.
332-
pub fn push(&mut self, class: &str) {
333-
let classes_to_add: Classes = class.into();
346+
pub fn push<T: Into<Self>>(&mut self, class: T) {
347+
let classes_to_add: Self = class.into();
334348
self.set.extend(classes_to_add.set);
335349
}
336350

337351
/// Check the set contains a class.
338-
pub fn contains(&self, class: &str) -> bool {
339-
self.set.contains(class)
352+
pub fn contains<T: AsRef<str>>(&self, class: T) -> bool {
353+
self.set.contains(class.as_ref())
340354
}
341355

342356
/// Check the set is empty.
343357
pub fn is_empty(&self) -> bool {
344358
self.set.is_empty()
345359
}
360+
}
346361

347-
/// Adds other classes to this set of classes; returning itself.
348-
///
349-
/// Takes the logical union of both `Classes`.
350-
pub fn extend<T: Into<Classes>>(mut self, other: T) -> Self {
351-
self.set.extend(other.into().set.into_iter());
352-
self
362+
impl<T: Into<Classes>> Extend<T> for Classes {
363+
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
364+
let classes = iter
365+
.into_iter()
366+
.map(Into::into)
367+
.flat_map(|classes| classes.set);
368+
self.set.extend(classes);
369+
}
370+
}
371+
372+
impl<T: Into<Classes>> FromIterator<T> for Classes {
373+
fn from_iter<IT: IntoIterator<Item = T>>(iter: IT) -> Self {
374+
let mut classes = Self::new();
375+
classes.extend(iter);
376+
classes
377+
}
378+
}
379+
380+
impl IntoIterator for Classes {
381+
type Item = Cow<'static, str>;
382+
type IntoIter = indexmap::set::IntoIter<Cow<'static, str>>;
383+
384+
fn into_iter(self) -> Self::IntoIter {
385+
self.set.into_iter()
353386
}
354387
}
355388

356389
impl ToString for Classes {
357390
fn to_string(&self) -> String {
358391
self.set
359392
.iter()
360-
.map(String::as_str)
361-
.collect::<Vec<&str>>()
393+
.map(Borrow::borrow)
394+
.collect::<Vec<_>>()
362395
.join(" ")
363396
}
364397
}
365398

366-
impl From<&str> for Classes {
367-
fn from(t: &str) -> Self {
368-
let set = t
369-
.split_whitespace()
370-
.map(String::from)
371-
.filter(|c| !c.is_empty())
372-
.collect();
399+
impl From<Cow<'static, str>> for Classes {
400+
fn from(t: Cow<'static, str>) -> Self {
401+
match t {
402+
Cow::Borrowed(x) => Self::from(x),
403+
Cow::Owned(x) => Self::from(x),
404+
}
405+
}
406+
}
407+
408+
impl From<&'static str> for Classes {
409+
fn from(t: &'static str) -> Self {
410+
let set = t.split_whitespace().map(Cow::Borrowed).collect();
373411
Self { set }
374412
}
375413
}
376414

377415
impl From<String> for Classes {
378416
fn from(t: String) -> Self {
379-
Classes::from(t.as_str())
417+
Self::from(&t)
380418
}
381419
}
382420

383421
impl From<&String> for Classes {
384422
fn from(t: &String) -> Self {
385-
Classes::from(t.as_str())
423+
let set = t
424+
.split_whitespace()
425+
.map(ToOwned::to_owned)
426+
.map(Cow::Owned)
427+
.collect();
428+
Self { set }
386429
}
387430
}
388431

389-
impl<T: AsRef<str>> From<Option<T>> for Classes {
432+
impl<T: Into<Classes>> From<Option<T>> for Classes {
390433
fn from(t: Option<T>) -> Self {
391-
t.as_ref()
392-
.map(|s| <Classes as From<&str>>::from(s.as_ref()))
393-
.unwrap_or_default()
434+
t.map(|x| x.into()).unwrap_or_default()
394435
}
395436
}
396437

397-
impl<T: AsRef<str>> From<&Option<T>> for Classes {
438+
impl<T: Into<Classes> + Clone> From<&Option<T>> for Classes {
398439
fn from(t: &Option<T>) -> Self {
399-
t.as_ref()
400-
.map(|s| <Classes as From<&str>>::from(s.as_ref()))
401-
.unwrap_or_default()
440+
Self::from(t.clone())
402441
}
403442
}
404443

405-
impl<T: AsRef<str>> From<Vec<T>> for Classes {
444+
impl<T: Into<Classes>> From<Vec<T>> for Classes {
406445
fn from(t: Vec<T>) -> Self {
407-
Classes::from(t.as_slice())
446+
Self::from_iter(t)
408447
}
409448
}
410449

411-
impl<T: AsRef<str>> From<&[T]> for Classes {
450+
impl<T: Into<Classes> + Clone> From<&[T]> for Classes {
412451
fn from(t: &[T]) -> Self {
413-
let set = t
414-
.iter()
415-
.map(|x| x.as_ref())
416-
.flat_map(|s| s.split_whitespace())
417-
.map(String::from)
418-
.filter(|c| !c.is_empty())
419-
.collect();
420-
Self { set }
452+
Self::from_iter(t.iter().cloned())
421453
}
422454
}
423455

@@ -509,6 +541,20 @@ pub trait Transformer<FROM, TO> {
509541
mod tests {
510542
use super::*;
511543

544+
struct TestClass;
545+
546+
impl TestClass {
547+
fn as_class(&self) -> &'static str {
548+
"test-class"
549+
}
550+
}
551+
552+
impl From<TestClass> for Classes {
553+
fn from(x: TestClass) -> Self {
554+
Classes::from(x.as_class())
555+
}
556+
}
557+
512558
#[test]
513559
fn it_is_initially_empty() {
514560
let subject = Classes::new();
@@ -527,15 +573,17 @@ mod tests {
527573
fn it_adds_values_via_extend() {
528574
let mut other = Classes::new();
529575
other.push("bar");
530-
let subject = Classes::new().extend(other);
576+
let mut subject = Classes::new();
577+
subject.extend(other);
531578
assert!(subject.contains("bar"));
532579
}
533580

534581
#[test]
535582
fn it_contains_both_values() {
536583
let mut other = Classes::new();
537584
other.push("bar");
538-
let mut subject = Classes::new().extend(other);
585+
let mut subject = Classes::new();
586+
subject.extend(other);
539587
subject.push("foo");
540588
assert!(subject.contains("foo"));
541589
assert!(subject.contains("bar"));
@@ -548,6 +596,26 @@ mod tests {
548596
assert!(subject.contains("foo"));
549597
assert!(subject.contains("bar"));
550598
}
599+
600+
#[test]
601+
fn push_and_contains_can_be_used_with_other_objects() {
602+
let mut subject = Classes::new();
603+
subject.push(TestClass);
604+
let other_class: Option<TestClass> = None;
605+
subject.push(other_class);
606+
assert!(subject.contains(TestClass.as_class()));
607+
}
608+
609+
#[test]
610+
fn can_be_extended_with_another_class() {
611+
let mut other = Classes::new();
612+
other.push("foo");
613+
other.push("bar");
614+
let mut subject = Classes::new();
615+
subject.extend(other);
616+
assert!(subject.contains("foo"));
617+
assert!(subject.contains("bar"));
618+
}
551619
}
552620

553621
// stdweb lacks the `inner_html` method

0 commit comments

Comments
 (0)