seed/
shortcuts.rs

1//! This file exports helper macros for element creation, populated by a higher-level macro,
2//! and macros for creating the parts of elements. (attrs, style, events)
3
4// @TODO merge with `pub use` & `prelude` in `lib.rs` and `browser::util`?
5
6use crate::virtual_dom::{At, Attrs};
7
8/// Allows to write nested macros.
9///
10/// [Related Rust issue](https://github.com/rust-lang/rust/issues/35853#issuecomment-415993963)
11///
12/// # Example
13///
14/// ```rust,no_run
15/// #[macro_export]
16/// macro_rules! create_t {
17///     ( $i18n:expr ) => {
18///         // This replaces $d with $ in the inner macro.
19///         seed::with_dollar_sign! {
20///             ($d:tt) => {
21///                 macro_rules! t {
22///                     { $d key:expr } => {
23///                         {
24///                             $i18n.translate($d key, None)
25///                         }
26///                     };
27///                     { $d key:expr, $d args:expr } => {
28///                         {
29///                             $i18n.translate($d key, Some(&$d args))
30///                         }
31///                     };
32///                 }
33///             }
34///         }
35///    }
36/// }
37///```
38#[macro_export]
39macro_rules! with_dollar_sign {
40    ($($body:tt)*) => {
41        macro_rules! __with_dollar_sign { $($body)* }
42        __with_dollar_sign!($);
43    }
44}
45
46/// Create struct `Urls`. It's useful especially for building `Url`s in nested modules.
47///
48/// # Example
49///
50/// ```rust,ignore
51/// use seed::{prelude::*, *};
52///
53/// mod page {
54///     pub mod admin {
55///         use seed::{prelude::*, *};
56///         struct_urls!();
57///         impl<'a> Urls<'a> { }
58///     }
59/// }
60///
61/// const ADMIN: &str = "admin";
62///
63/// struct Model {
64///     base_url: Url
65/// }
66///
67/// enum Msg { }
68///
69/// fn init(url: Url, _: &mut impl Orders<Msg>) -> Model {
70///     Model {
71///         base_url: url.to_base_url(),
72///     }
73/// }
74///
75/// // ------ ------
76/// //     Urls
77/// // ------ ------
78///
79/// struct_urls!();
80/// impl<'a> Urls<'a> {
81///     pub fn home(self) -> Url {
82///         self.base_url()
83///     }
84///     pub fn admin_urls(self) -> page::admin::Urls<'a> {
85///         page::admin::Urls::new(self.base_url().add_path_part(ADMIN))
86///     }
87/// }
88///
89/// fn view(mdl: &Model) -> Node<Msg> {
90///     a![
91///         attrs!{ At::Href => Urls::new(&mdl.base_url).home() }
92///     ]
93/// }
94/// ```
95#[macro_export]
96macro_rules! struct_urls {
97    () => {
98        pub struct Urls<'a> {
99            base_url: std::borrow::Cow<'a, $crate::browser::Url>,
100        }
101
102        impl<'a> Urls<'a> {
103            /// Create a new `Urls` instance.
104            ///
105            /// # Example
106            ///
107            /// ```rust,ignore
108            /// Urls::new(base_url).home()
109            /// ```
110            pub fn new(base_url: impl Into<std::borrow::Cow<'a, $crate::browser::Url>>) -> Self {
111                Self {
112                    base_url: base_url.into(),
113                }
114            }
115
116            /// Return base `Url`. If `base_url` isn't owned, it will be cloned.
117            ///
118            /// # Example
119            ///
120            /// ```rust,ignore
121            /// pub fn admin_urls(self) -> page::admin::Urls<'a> {
122            ///     page::admin::Urls::new(self.base_url().add_path_part(ADMIN))
123            /// }
124            /// ```
125            pub fn base_url(self) -> $crate::browser::Url {
126                self.base_url.into_owned()
127            }
128        }
129    };
130}
131
132/// Create macros exposed to the package that allow shortcuts for Dom elements.
133/// In the matching pattern below, we match the name we want to use with the name under
134/// the `seed::virtual_dom::Tag` enum. Eg the div! macro uses `seed::virtual_dom::Tag::Div`.
135macro_rules! element {
136    // Create shortcut macros for any element; populate these functions in this module.
137    ($($Tag:ident => $Tag_camel:ident);+) => {
138        // This replaces $d with $ in the inner macro.
139        with_dollar_sign! {
140            ($d:tt) => {
141                $(
142                    #[macro_export]
143                    macro_rules! $Tag {
144                        ( $d($d part:expr),* $d(,)? ) => {
145                            {
146                                #[allow(unused_mut)]
147                                let mut el = El::empty($crate::virtual_dom::Tag::$Tag_camel);
148                                $d (
149                                    $d part.update_el(&mut el);
150                                )*
151                                $crate::virtual_dom::Node::Element(el)
152                            }
153                        };
154                    }
155                )+
156            }
157        }
158   }
159}
160/// Similar to the element! macro above, but with a namespace for svg.
161macro_rules! element_svg {
162    // Create shortcut macros for any element; populate these functions in this module.
163    ($($Tag:ident => $Tag_camel:ident);+) => {
164        // This replaces $d with $ in the inner macro.
165        with_dollar_sign! {
166            ($d:tt) => {
167                $(
168                    #[macro_export]
169                    macro_rules! $Tag {
170                        ( $d($d part:expr),* $d(,)? ) => {
171                            {
172                                #[allow(unused_mut)]
173                                let mut el = El::empty_svg($crate::virtual_dom::Tag::$Tag_camel);
174                                $d ( $d part.update_el(&mut el); )*
175                                $crate::virtual_dom::Node::Element(el)
176                            }
177                        };
178                    }
179                )+
180            }
181        }
182   }
183}
184
185// @TODO merge with make_tags!
186// El must be exposed in the module where this is called for these to work.
187element! {
188    address => Address; article => Article; aside => Aside; footer => Footer;
189    header => Header; h1 => H1;
190    h2 => H2; h3 => H3; h4 => H4; h5 => H5; h6 => H6;
191    hgroup => Hgroup; main => Main; nav => Nav; section => Section;
192
193    blockquote => BlockQuote;
194    dd => Dd; dir => Dir; div => Div; dl => Dl; dt => Dt; figcaption => FigCaption; figure => Figure;
195    hr => Hr; li => Li; ol => Ol; p => P; pre => Pre; ul => Ul;
196
197    a => A; abbr => Abbr;
198    b => B; bdi => Bdi; bdo => Bdo; br => Br; cite => Cite; code => Code; data => Data;
199    dfn => Dfn; em => Em; i => I; kbd => Kbd; mark => Mark; q => Q; rb => Rb;
200    rp => Rp; rt => Rt; rtc => Rtc; ruby => Ruby; s => S; samp => Samp; small => Small;
201    span => Span; strong => Strong; sub => Sub; sup => Sup; time => Time; tt => Tt;
202    u => U; var => Var; wbr => Wbr;
203
204    area => Area; audio => Audio; img => Img; map => Map; track => Track; video => Video;
205
206    applet => Applet; embed => Embed; iframe => Iframe;
207    noembed => NoEmbed; object => Object; param => Param; picture => Picture; source => Source;
208
209    canvas => Canvas; noscript => NoScript; Script => Script;
210
211    del => Del; ins => Ins;
212
213    caption => Caption; col => Col; colgroup => ColGroup; table => Table; tbody => Tbody;
214    td => Td; tfoot => Tfoot; th => Th; thead => Thead; tr => Tr;
215
216    button => Button; datalist => DataList; fieldset => FieldSet; form => Form; input => Input;
217    label => Label; legend => Legend; meter => Meter; optgroup => OptGroup; option => Option;
218    output => Output; progress => Progress; select => Select; textarea => TextArea;
219
220    details => Details; dialog => Dialog; menu => Menu; menuitem => MenuItem; summary => Summary;
221
222    content => Content; element => Element; shadow => Shadow; slot => Slot; template => Template
223}
224
225// @TODO merge with make_tags!
226element_svg! {
227    // SVG shape elements
228    line_ => Line;  // line is a builtin rust macro.
229    rect => Rect; circle => Circle; ellipse => Elipse; polygon => Polygon; polyline => Polyline;
230    mesh => Mesh; path => Path;
231    // SVG container elements
232    defs => Defs; g => G; marker => Marker; mask => Mask;
233    // missing-glyph => MissingGlyph; // todo unable to populate with macro due to hyphen
234    pattern => Pattern; svg => Svg; switch => Switch; symbol => Symbol; unknown => Unknown;
235    // SVG gradient elements
236    linearGradient => LinearGradient; radialGradient => RadialGradient; meshGradient => MeshGradient;
237    stop => Stop;
238    // SVG graphics elements
239    image => Image;
240    // SVG graphics referencing elements
241    r#use => Use;
242    // SVG text content elements
243    altGlyph => AltGlyph; altGlyphDef => AltGlyphDef; altGlyphItem => AltGlyphItem; glyph => Glyph;
244    glyphRef => GlyphRef; textPath => TextPath; text => Text; tref => TRef; tspan => TSpan;
245    // SVG uncategorized elements
246    clipPath => ClipPath; cursor => Cursor; filter => Filter; foreignObject => ForeignObject;
247    hatchpath => HatchPath; meshPatch => MeshPatch; meshrow => MeshRow; view => View;
248    // style missing due to conflict with the style macro.
249    // colorProfile => ColorProfile;  // todo hypthen-issue
250    // SVG animation elements
251    animate => Animate; animateColor => AnimateColor; animateMotion => AnimateMotion;
252    animateTransform => AnimateTransform; discard => Discard; mpath => Mpath; set => Set;
253    // SVG descriptive elements
254    desc => Desc; metadata => Metadata; title => Title;
255    // SVG filter primitive elements
256    feBlend => FeBlend; feColorMatrix => FeColorMatrix; feComponentTransfer => FeComponentTransfer;
257    feComposite => FeComposite; feConvolveMatrix => FeConvolveMatrix;
258    feDiffuseLighting => FeDiffuseLighting; feDisplacementMap => FeDisplacementMap;
259    feDropShadow => FeDropShadow; feFlood => FeFlood; feFuncA => FeFuncA; feFuncB => FeFuncB;
260    feFuncG => FeFuncG; feFuncR => FeFuncR; feGaussianBlur => FeGaussianBlur; feImage => FeImage;
261    feMerge => FeMerge; feMergeNode => FeMergeNode; feMorphology => FeMorphology;
262    feOffset => FeOffset; feSpecularLighting => FeSpecularLighting; feTile => FeTile;
263    feTurbulence => FeTurbulence;
264    // SVG font elements
265    font => Font; hkern => HKern; vkern => VKern;
266    // todo many font elements with hyphen issue
267    // SVG Paint sever elements
268    hatch => Hatch; solidcolor => SolidColor
269}
270
271#[macro_export]
272macro_rules! empty {
273    () => {
274        $crate::virtual_dom::Node::Empty
275    };
276}
277
278#[macro_export]
279macro_rules! raw {
280    ($raw_html:expr) => {
281        Node::from_html(None, $raw_html)
282    };
283}
284
285#[macro_export]
286macro_rules! raw_svg {
287    ($raw_svg:expr) => {
288        Node::from_html(Some(&Namespace::Svg), $raw_svg)
289    };
290}
291
292#[macro_export]
293macro_rules! plain {
294    ($text:expr) => {
295        $crate::virtual_dom::Node::new_text($text)
296    };
297}
298
299#[macro_export]
300macro_rules! custom {
301    ( $($part:expr),* $(,)? ) => {
302        {
303            let default_tag_name = "missing-tag-name";
304            let mut el = El::empty($crate::virtual_dom::Tag::from(default_tag_name));
305            $ ( $part.update_el(&mut el); )*
306
307            if let $crate::virtual_dom::Tag::Custom(tag_name) = &el.tag {
308                let tag_changed = tag_name != default_tag_name;
309                assert!(tag_changed, "Tag has not been set in `custom!` element. Add e.g. `Tag::from(\"code-block\")`.");
310            }
311
312            $crate::virtual_dom::Node::Element(el)
313        }
314    };
315}
316
317/// Provide a shortcut for creating attributes.
318#[macro_export]
319macro_rules! attrs {
320    { $($key:expr => $value:expr $(;)?$(,)?)* } => {
321        {
322            let mut vals = IndexMap::new();
323            $(
324                // We can handle arguments of multiple types by using this:
325                // Strings, &strs, bools, numbers etc.
326                // And cases like `true.as_attr_value()` or `AtValue::Ignored`.
327                vals.insert($key.into(), (&$value).into());
328            )*
329            $crate::virtual_dom::Attrs::new(vals)
330        }
331     };
332}
333
334#[deprecated(since = "0.8.0", note = "use [`C!`](macro.C!.html) instead")]
335/// Convenience macro. Ideal when there are multiple classes, and no other attrs.
336#[macro_export]
337macro_rules! class {
338    { $($class:expr $(=> $predicate:expr)? $(,)?)* } => {
339        {
340            let mut result = $crate::virtual_dom::Attrs::empty();
341            let mut classes = Vec::new();
342            $(
343                // refactor to labeled block once stable (https://github.com/rust-lang/rust/issues/48594)
344                {
345                    $(
346                        if !$predicate { return }
347                    )?
348                    classes.push($class);
349                };
350            )*
351            result.add_multiple(At::Class, &classes);
352            result
353        }
354     };
355}
356
357/// Add classes into the element.
358///
359/// # Example
360///
361/// ```rust,ignore
362///div![
363///    C!["btn", IF!(active => "active")],
364///    "Button",
365///]
366/// ```
367#[macro_export]
368macro_rules! C {
369    ( $($class:expr $(,)?)* ) => {
370        {
371            let mut all_classes = Vec::new();
372            $(
373                $crate::shortcuts::_fill_all_classes(&mut all_classes, $class.to_classes());
374            )*
375            $crate::shortcuts::_all_classes_to_attrs(&all_classes)
376        }
377    };
378}
379
380pub fn _fill_all_classes(all_classes: &mut Vec<String>, classes: Option<Vec<String>>) {
381    if let Some(classes) = classes {
382        for class in classes {
383            if !class.is_empty() {
384                all_classes.push(class);
385            }
386        }
387    }
388}
389
390pub fn _all_classes_to_attrs(all_classes: &[String]) -> Attrs {
391    let mut attrs = Attrs::empty();
392    if !all_classes.is_empty() {
393        attrs.add_multiple(
394            At::Class,
395            &all_classes.iter().map(String::as_str).collect::<Vec<_>>(),
396        );
397    }
398    attrs
399}
400
401/// `IF!(predicate => expression) -> Option<expression value>`
402/// - `expression` is evaluated only when `predicate` is `true` (lazy eval).
403/// - Alternative to `bool::then`.
404///
405/// # Example
406///
407/// ```rust,ignore
408/// use seed::{prelude::*, *};
409/// let active = false;
410/// div![
411///     C!["btn", IF!(active => "active")],
412///     "Button",
413///     IF!(not(disabled) => ev(Ev::Click, Msg::Clicked)),
414/// ]
415/// ```
416#[macro_export]
417macro_rules! IF {
418    ( $predicate:expr => $value:expr ) => {{
419        // @TODO replace with `bool::then` once stable.
420        if $predicate {
421            Some($value)
422        } else {
423            None
424        }
425    }};
426}
427
428/// Convenience macro, for brevity.
429#[macro_export]
430macro_rules! id {
431    { $id:expr } => {
432        {
433            $crate::virtual_dom::Attrs::from_id($id)
434        }
435     };
436}
437
438// reference for workaround used by style! macro
439// (https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md)
440/// Provide a shortcut for creating styles.
441#[macro_export]
442macro_rules! style {
443    { $($key:expr => $value:expr $(;)?$(,)?)* } => {
444        {
445            #[allow(unused_imports)]
446            use $crate::virtual_dom::values::{
447                ToCSSValueForCSSValue, ToCSSValueForOptionToString, ToCSSValueForToString
448            };
449            let mut vals = IndexMap::new();
450            $(
451                vals.insert($key.into(), ($value).to_css_value());
452            )*
453            $crate::virtual_dom::Style::new(vals)
454        }
455     };
456}
457
458#[macro_export]
459/// Converts items to `Vec<Node<Ms>` and returns flattened `Vec<Node<Ms>`.
460///
461/// Items have to implement the trait `IntoNodes`.
462///
463/// # Example
464///
465/// ```rust,ignore
466/// use seed::{prelude::*, *};
467/// type Msg = ();
468/// let _ : Vec<Node<Msg>> = nodes![
469///     h1!["Hello"],
470///     h2!["world"],
471///     vec![
472///         div!["Do you like"],
473///         div!["Seed?"]
474///     ],
475/// ];
476/// ```
477macro_rules! nodes {
478    (  $($element:expr $(,)?)* ) => {
479        {
480            use $crate::virtual_dom::IntoNodes;
481            let mut nodes = Vec::new();
482            $(
483                nodes.append(&mut ($element).into_nodes());
484            )*
485            nodes
486        }
487    };
488}
489
490/// A key-value pairs, where the keys and values must implement `ToString`.
491#[macro_export]
492macro_rules! key_value_pairs {
493    { $($key:expr => $value:expr),* $(,)? } => {
494        {
495            let mut result = IndexMap::new();
496            $(
497                // We can handle arguments of multiple types by using this:
498                // Strings, &strs, bools, numbers etc.
499                result.insert($key.to_string(), $value.to_string());
500            )*
501            result
502        }
503     };
504}