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

Multiple blocks in jade mixins? #1693

Open
mentalhead opened this issue Oct 17, 2014 · 33 comments
Open

Multiple blocks in jade mixins? #1693

mentalhead opened this issue Oct 17, 2014 · 33 comments

Comments

@mentalhead
Copy link

I was just wondering is there a way to use multiple blocks with different content with a jade mixin, like this or are we just limited to one block with same content per mixin?

mixin article
  block
  p Mixin paragraph
  block
  block

and get the following results:

<p>This is my first block</p>
<p>Mixin paragraph</p>
<p>This is my second block</p>
<p>This is my third block</p>
@iamstarkov
Copy link

I want this feature too

@iamstarkov
Copy link

In my case I want mixins to behave like extends, so I will can declare mixin with named blocks inside, and then use it, without extend. (I like concatinating more than extending)

@ForbesLindesay
Copy link
Member

Currently, we don't support that, you can repeat the block keyword, but it will just repeat the same block multiple times. There is some work going in to adapting how mixins work though, so this may be something we support in the future.

@iamstarkov
Copy link

If I wil implement this by myself, will you merge this feature?

@vendethiel
Copy link
Contributor

How would you call the mixin?

@ForbesLindesay
Copy link
Member

If we can agree on a nice design, then I will merge a pull request, I'm still not sure if we've quite got mixins right yet. We need to settle on both how they will be implemented (the current implementation isn't great) and how they will be defined/called.

This needs to cover:

  1. how to define a mixin that takes a single anonymous block
  2. how to call a mixin that takes a single anonymous block
  3. how to define a mixin that takes multiple named blocks
  4. how to call a mixin that takes multiple named blocks
  5. how to pass a value to the block (both named and anonymous) - this is so you can have mixins that have loops and call the block for each item in an array)
  6. how to get the value of a block (both named and anonymous) as a string (rather than just outputting it directly) - this is so you can have mixins that apply filters to their blocks (i.e. filters but at runtime)

We could cover point 6 by instead supporting some other kind of "runtime filter". Perhaps :filter could be compile time and ::filter could be runtime I suppose, but the other features all need to be supported in a clean and consistent fashion.

@klarezz
Copy link

klarezz commented Nov 11, 2014

meanwhile you could use something like this

// initialization
- var blocks={};

mixin set(key)
    - blocks[key]=this.block

// mixin definition
mixin A
    block
    - blocks.content()

// mixin call
+A
    +set('content')
        p A content

@ForbesLindesay, is there a way to pass a jade syntax to javascript without using a mixin.block?

@ghost
Copy link

ghost commented Jul 15, 2015

So both issues are closed but one links to the other. Is there a solution for this? This could really make things easy for coding themes for static sites generators.

One could make custom mixins for things like easy tabs, accordions, or any other reuseable, yet custom styleable UI elements.

@cheshrkat
Copy link

Could the syntax extend the original mixin name, to avoid clashes?

//- define
mixin foo()
    p Bar:
    block :bar
    p Baz:
    block :baz
    block

//- call
+foo()
    +foo:bar()
       p block content for bar
    +foo:baz()
       p block content for baz
    p Anonymous block content

//- output
    p Bar:
    p block content for bar
    p Baz:
    p block content for baz
    p Anonymous block content

@cheshrkat
Copy link

We'd use this lots of ways but an easy use case is a tab set with multiple content blocks.

@viktorbezdek
Copy link

Or just name the block as in templates. It can't collide with template level blocks thanks to different context. Or filter-like notation would be an option. The syntax would feel natural. See example below.

Mixin definition:

mixin Foo()
    .bar
         if block bar
             block bar
         else
             p You haven't defined block bar.
    .baz
         if block baz
             block baz
         else
             p You haven't defined block baz.

Usage:

+Foo()
    block bar //- or &bar as a filter like notation
        p My awesome bar content
    block baz //- or &baz
        p Baz is also quite a catch.

Not sure whether this feature is handy as it might seem. Templates should be understandable and usable without the need of documentation. Multiple blocks in mixins allows devs to increase templates complexity to whole new levels.

@TimothyGu
Copy link
Member

@viktorbezdek check out the newest proposal for blocks at #2055.

@ForbesLindesay ForbesLindesay added this to the 3.0.0 milestone Oct 5, 2015
@gvr37leo
Copy link

gvr37leo commented Jul 8, 2016

I think all these issue would be solved if it were possible to store blocks in variables and pass them as parameters.

this would remove the weird exception of being able to declare a block under a mixin and allow multiple blocks in mixins.

//dont really know what block initialization should look like but maybe something like this
block1=
    p.
        hello everyone


Foo(block1,block2)
    .content1
        = block1
    .content2
        = block2

if this were implemented mixins could also replace extends by calling a mixin and passing the blocks that have to fill up certain places

@TimothyGu
Copy link
Member

@gvr37leo, using mixins as regular JavaScript functions is on the Agenda. That should make it a bit easier to do something like your example.

@Miller547719886
Copy link

@cheshrkat I am using this writing mode to create a tab mixin but the tab pane number is not static:

mixin _tab-bs(tab)
	//- dependence:bootstrap
	- var $cls = tab.cls ? tab.cls : "";
	- var $nav = tab.nav ? tab.nav : [{text:"第1页",href:"1"},{text:"第2页",href:"2"},{text:"第3页",href:"3"}];
	- var $transition = tab.transition ? tab.transition : false;

	._tab-bs(class=$cls)
		._tab-nav
			block nav-prev
			ul.nav.nav-tabs(role="tablist")
				- for item,i in $nav
					li(role="presentation",class=(i===0?"active":""))
						a(role="tab",data-toggle="tab",aria-expanded="true",href="#"+item.href,id=item.href+"-tab") #{item.text}
			block nav-next
		.tab-content
			- for item,i in $nav
				.tab-pane(role="tabpanel",class=$transition===true?(i===0?"fade in active":"fade"):(i===0?"active":""),id=item.href)
					block :#{i+1}
						p #{i+1}


// using

- var tab = {};
- tab.cls = "my-tab";
- tab.nav = [{text:"page1",href:"1"},{text:"page2",href:"2"},{text:"page3",href:"3"},{text:"page4",href:"4"}];
- tab.transition = true;
+_tab-bs(tab)
	+_tab-bs :1()
		121221
	+_tab-bs :2()
		121221111
	+_tab-bs :3()
		121221222
	+_tab-bs :4()
		121221232

but this won't work,I want to define an Independent block in each tab content so I can write my own content when using this mixin.Is there a way to fix this question?

@cheshrkat
Copy link

@Miller547719886 the syntax in my comment is just suggested, not supported in Jade.

In current production, to produce tabs we ended up making two templates - one for the wrapper and another for the tabs. Simplified:

+tabs()
    +tab()
        p Block content
    +tab()
        p Block content
    +tab()
        p Block content

It's not clever but it works.

@Miller547719886
Copy link

@cheshrkat what if the tab number is unknown?The expected result is creating differently named blocks with circular statement,so I can write multiple contents in each block.

@cheshrkat
Copy link

@Miller547719886 I'm not quite sure what you're asking about. If you are asking about the simplified code we use in production, we just put in however many instances of tab we need. If you need to process a data source, whatever does the processing writes in the instances.

To put it another way, I don't think we have a solution that matches what you're trying to do. We are using Jade mostly for static sites where we know what the content will be.

@anasanzari
Copy link

any updates?

@ForbesLindesay
Copy link
Member

@anasanzari the answer to "any updates?" is always no. If there were updates, they'd be in this issue.

@dek4nice
Copy link

dek4nice commented Oct 1, 2017

I have wrote this workaround for multiple blocks in Jade/Pug
https://codepen.io/DimWeb/pen/LzjbeO

@StudioSpindle
Copy link

There is one problem with the proposal to initialise the blocks. It doens't work well if you'd call the same mixin with only one instance of the block. For example: https://codepen.io/anon/pen/gzXRre?editors=1000

(souce: https://stackoverflow.com/questions/32456939/multiple-use-of-jade-block-mixin-in-one-template#32462662)

@Kris-Bastiani
Copy link

Knocked this together super quick, so it's not thoroughly tested, but this seems like it might solve the issue by unsetting the blocks at the end of the parent mixin https://codepen.io/anon/pen/RJBPmr

@bdrazen
Copy link

bdrazen commented Jun 25, 2018

@OrchidAugur Neat. Didn't know you could assign and invoke blocks like that.

@albedoa
Copy link

albedoa commented Nov 7, 2018

Not sure whether this feature is handy as it might seem. Templates should be understandable and usable without the need of documentation. Multiple blocks in mixins allows devs to increase templates complexity to whole new levels.

@viktorbezdek I realize this comment is from a few years ago, but I thought it could use some balance. If done right, this feature could significantly decrease complexity. Just look at the hacks that people are throwing together in this thread to solve real and common problems. Or take a media object mixin with unknown content for example:

mixin media
    .media
        .media-figure
            // ...
        .media-body
            // ...

If I want to inject two pieces of content, one option is to make media-figure and media-body mixins themselves:

mixin media-figure
    .media-figure
        block

mixin media-body
    .media-body
        block

mixin media
    .media
        block

+media
    +media-figure
        // ...
    +media-body
        // ...

This is the closest we have to native named blocks, and it certainly isn't less complex. And that's for a simple object. Named mixin blocks feel like a missing feature when I am using Pug.

@vendethiel
Copy link
Contributor

This is a bit reminiscent of the embed tag in Twig.

@Mifrill
Copy link

Mifrill commented Mar 18, 2019

any updates?

@waspeer
Copy link

waspeer commented Nov 5, 2019

+1

1 similar comment
@vBerezin
Copy link

vBerezin commented Dec 8, 2019

+1

@Profesor08
Copy link

Profesor08 commented Jul 16, 2020

You can use my solution, it uses global javascript scope to store data

Using this approach you don't need to make a lot of additional mixins to solve this common problem, just name your block how you want.

Live example on CodePen

Usage example

+title-block()
  //- Avoid using tags for wrapping content, use only named blocks
  +mixin-block("title")
    | Blabl title 1
  +mixin-block("sub-title")
    | Blabla sub title 1

+title-block()
  +mixin-block("title")
    | Blabl title 2

+title-block()
  +mixin-block("sub-title")
    | Blabla sub title 3

Mixin code

mixin title-block
  //- Important! We have to execute out block code first
  block
  //- Wrappers goes there if you need them
  .title-block
    //- Checking if named block data exists
    +mixin-block-exists("title")
      h3
        //- Displaying block data
        +mixin-block-get("title")
    +mixin-block-exists("sub-title")
      p
        +mixin-block-get("sub-title")
  //- Important! Switching helps to avoid data overlaping
  +mixin-next()

Core code required to work

//- All Thanks to Holy Javascript and me

//- Core code for all mixins, you must put this code in some file and include on you need

mixin mixin-block(name)
  //- this mixin set named block data to some global scope
  - global.maxinData = global.maxinData || {}
  - global.mixinCallId = global.mixinCallId || 0
  - global.maxinData[global.mixinCallId] = global.maxinData[global.mixinCallId] || {}
  if block
    - global.maxinData[global.mixinCallId][name] = () => block()

mixin mixin-block-exists(name)
  //- Checking if named data exists and displaying block
  - global.maxinDataExists = global.maxinDataExists || function(name) { return global.maxinData !== undefined && global.maxinData[global.mixinCallId] !== undefined && global.maxinData[global.mixinCallId][name] !== undefined; }
  if block && global.maxinDataExists(name)
    block
    
mixin mixin-block-get(name)
  //- Displaying block if exists
  if global.maxinData !== undefined && global.mixinCallId !== undefined
    if global.maxinData[global.mixinCallId]
      if global.maxinData[global.mixinCallId][name]
        - global.maxinData[global.mixinCallId][name]()

mixin mixin-next
  //- Switching to next
  - global.mixinCallId = (global.mixinCallId || 0) + 1;
  
//- End of core code

@andraaspar
Copy link

I had trouble using the technique of @klarezz because of global variables. I found this pen. It looks like this when simplified:

//- Declare

mixin SomeComp()
  - const slots = {}

  mixin Slot(key)
    - slots[key] = block

  - block() // Ignore block

  .some-comp
    if slots.foo
      .foo
        - slots.foo()
    if slots.bar
      .bar
        - slots.bar()

//- Use

+SomeComp
  +Slot('foo')
    p foo
  +Slot('bar')
    p bar

And the output is like this:

<div class="some-comp">
  <div class="foo">
    <p>foo</p>
  </div>
  <div class="bar">
    <p>bar</p>
  </div>
</div>

@vectorjon
Copy link

It doesn't look like multiple blocks in mixins are implemented yet. I've looked around and saw some workarounds and decided to try and improve their functionality. Here is what I came up with: https://codepen.io/Vectorjon/pen/GRmYWjN

mixin block(key, marker=false)
	- global.my_blocks = global.my_blocks || {}
	if marker
		if global.my_blocks[key]
			- let temp = {}
			- temp[key] = global.my_blocks[key]
			- global.my_blocks[key] = null
			- temp[key]()
		else
			block
	else
		- global.my_blocks[key] = this.block

Quick overview on usage:

mixin example
	block
	.part-1
		+block('one', true)
			p one's default
	.part-2
		+block('two', true)

+example
	+block('one')
		p whatever
		p you
		p want
	+block('two')
		p also
		p whatever

Hope you find it useful. Comment on my CodePen if you find any bugs or have suggestions.

@alterastro
Copy link

@andraaspar, this is a small fix that makes it NOT mandatory to use a slot if it is declared in the mixin:

//- Declare

mixin SomeComp()
  - const slots = {}

  mixin Slot(key)
    - slots[key] = block

  - block ? block() : undefined // Ignore block (with hotFix)

  .some-comp
    if slots.foo
      .foo
        - slots.foo()
    if slots.bar
      .bar
        - slots.bar()

//- Use
//- This works

+SomeComp
  +Slot('foo')
    p foo
  +Slot('bar')
    p bar

//- And this too

+SomeComp

Otherwise, I get the error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests