Skip to content

Instantly share code, notes, and snippets.

@homerjam
Created January 11, 2017 12:57
Show Gist options
  • Save homerjam/7d1981bb1c75dc4e397da49594e96688 to your computer and use it in GitHub Desktop.
Save homerjam/7d1981bb1c75dc4e397da49594e96688 to your computer and use it in GitHub Desktop.
Custom MJML component for use in MailChimp templates
import { MJMLElement, helpers } from 'mjml-core'
import cloneDeep from 'lodash/cloneDeep'
import merge from 'lodash/merge'
import React, { Component } from 'react'
const tagName = 'mc-section'
const parentTag = ['mj-container']
const defaultMJMLDefinition = {
attributes: {
'mc:hideable': null,
'mc:repeatable': null,
'mc:variant': null,
'background-color': null,
'background-url': null,
'background-repeat': 'repeat',
'background-size': 'auto',
'border': null,
'border-bottom': null,
'border-left': null,
'border-radius': null,
'border-right': null,
'border-top': null,
'direction': 'ltr',
'full-width': null,
'padding': '20px 0',
'padding-top': null,
'padding-bottom': null,
'padding-left': null,
'padding-right': null,
'text-align': 'center',
'vertical-align': 'top',
}
}
const baseStyles = {
div: {
margin: '0 auto'
},
table: {
fontSize: '0px',
width: '100%'
},
td: {
textAlign: 'center',
verticalAlign: 'top'
}
}
const postRender = $ => {
$('.mc-section-outlook-background').each(function () {
const url = $(this).data('url')
const width = $(this).data('width')
$(this)
.removeAttr('class')
.removeAttr('data-url')
.removeAttr('data-width')
if (!url) {
return
}
$(this).before(`${helpers.startConditionalTag}
<v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:${width}px;">
<v:fill origin="0.5, 0" position="0.5,0" type="tile" src="${url}" />
<v:textbox style="mso-fit-shape-to-text:true" inset="0,0,0,0">
${helpers.endConditionalTag}`)
$(this).after(`${helpers.startConditionalTag}
</v:textbox>
</v:rect>
${helpers.endConditionalTag}`)
})
$('.mc-section-outlook-open').each(function () {
const $columnDiv = $(this).next()
$(this).replaceWith(`${helpers.startConditionalTag}
<table border="0" cellpadding="0" cellspacing="0"><tr><td style="vertical-align:${$columnDiv.data('vertical-align')};width:${parseInt($(this).data('width'))}px;">
${helpers.endConditionalTag}`)
$columnDiv.removeAttr('data-vertical-align')
})
$('.mc-section-outlook-line').each(function () {
const $columnDiv = $(this).next()
$(this).replaceWith(`${helpers.startConditionalTag}
</td><td style="vertical-align:${$columnDiv.data('vertical-align')};width:${parseInt($(this).data('width'))}px;">
${helpers.endConditionalTag}`)
$columnDiv.removeAttr('data-vertical-align')
})
$('.mc-section-outlook-close').each(function () {
$(this).replaceWith(`${helpers.startConditionalTag}
</td></tr></table>
${helpers.endConditionalTag}`)
})
$('[data-mc-hideable]').each(function () {
$(this)
.attr('mc:hideable', '')
.removeAttr('data-mc-hideable')
})
$('[data-mc-repeatable]').each(function () {
$(this)
.attr('mc:repeatable', $(this).attr('data-mc-repeatable'))
.removeAttr('data-mc-repeatable')
})
$('[data-mc-variant]').each(function () {
$(this)
.attr('mc:variant', $(this).attr('data-mc-variant'))
.removeAttr('data-mc-variant')
})
return $
}
@MJMLElement
class Section extends Component {
styles = this.getStyles()
isFullWidth () {
const { mjAttribute } = this.props
return mjAttribute('full-width') == 'full-width'
}
getStyles () {
const { mjAttribute, parentWidth, defaultUnit } = this.props
const background = mjAttribute('background-url') ? {
background: `${mjAttribute('background-color') || ''} url(${mjAttribute('background-url')}) top center / ${mjAttribute('background-size') || ''} ${mjAttribute('background-repeat') || ''}`.trim()
} : {
background: mjAttribute('background-color')
}
return merge({}, baseStyles, {
td: {
fontSize: '0px',
padding: defaultUnit(mjAttribute('padding'), 'px'),
paddingBottom: defaultUnit(mjAttribute('padding-bottom'), 'px'),
paddingLeft: defaultUnit(mjAttribute('padding-left'), 'px'),
paddingRight: defaultUnit(mjAttribute('padding-right'), 'px'),
paddingTop: defaultUnit(mjAttribute('padding-top'), 'px'),
textAlign: mjAttribute('text-align'),
verticalAlign: mjAttribute('vertical-align')
},
div: {
maxWidth: defaultUnit(parentWidth)
}
}, {
div: this.isFullWidth() ? {} : cloneDeep(background),
table: this.isFullWidth() ? {} : cloneDeep(background),
tableFullwidth: this.isFullWidth() ? cloneDeep(background) : {}
})
}
renderFullWidthSection () {
const { mjAttribute } = this.props
return (
<table
cellPadding="0"
cellSpacing="0"
data-legacy-background={mjAttribute('background-url')}
data-legacy-border="0"
data-mc-hideable={mjAttribute('mc:hideable')}
data-mc-repeatable={mjAttribute('mc:repeatable')}
data-mc-variant={mjAttribute('mc:variant')}
style={merge({}, this.styles.tableFullwidth, this.styles.table)}>
<tbody>
<tr>
<td>
{this.renderSection()}
</td>
</tr>
</tbody>
</table>
)
}
renderSection () {
const { renderWrappedOutlookChildren, mjAttribute, children, parentWidth } = this.props
const fullWidth = this.isFullWidth()
return (
<div
data-mc-hideable={mjAttribute('mc:hideable')}
data-mc-repeatable={mjAttribute('mc:repeatable')}
data-mc-variant={mjAttribute('mc:variant')}
style={this.styles.div}>
<table
cellPadding="0"
cellSpacing="0"
className="mc-section-outlook-background"
data-legacy-align="center"
data-legacy-background={fullWidth ? undefined : mjAttribute('background-url')}
data-legacy-border="0"
data-url={mjAttribute('background-url') || ''}
data-width={parentWidth}
style={this.styles.table}>
<tbody>
<tr>
<td style={this.styles.td}>
{renderWrappedOutlookChildren(children)}
</td>
</tr>
</tbody>
</table>
</div>
)
}
render () {
return this.isFullWidth() ? this.renderFullWidthSection() : this.renderSection()
}
}
Section.tagName = tagName
Section.parentTag = parentTag
Section.defaultMJMLDefinition = defaultMJMLDefinition
Section.baseStyles = baseStyles
Section.postRender = postRender
import Column from 'mjml-column'
Column.parentTag.push(tagName)
import Group from 'mjml-group'
Group.parentTag.push(tagName)
import Raw from 'mjml-raw'
Raw.parentTag.push(tagName)
export default Section
@auginator
Copy link

auginator commented Mar 22, 2017

Thanks for the great gist. I was about to write something similar myself.
Is this an example of the intended syntax?

<mc-section mc:hideable mc:repeatable mc:variant="ExampleSection">
          <mj-column>
                <mj-text>
                    <p>Lorem…</p>
                </mj-text>
          </mj-column>
</mc-section>

This works great, except that unlike <mj-section> the border attributes don't work.

[edited for derp - originally I had a nested mj-section inside and I got an error]

@cabot364
Copy link

cabot364 commented Apr 5, 2017

I keep getting an error "mj-column cannot be used inside of mc-section"...not sure what is going on here. I cut and pasted the above code to test.

@alexandraharet
Copy link

Getting the same error, that mj-column cannot be used in mc-section. An example of how the component is meant to be used would help a lot! Anyone?

@homerjam
Copy link
Author

@alexandraharet @cabot364

Usage:

  1. Compile component with babel
$ babel mjml/components/mc-section/src/index.js -o mjml/components/mc-section/lib/index.js
  1. Register component
const mjml2html = require('mjml').mjml2html;
const registerMJElement = require('mjml').registerMJElement;

const mcSection = require('./mjml/components/mc-section/lib').default;

registerMJElement(mcSection);

// then use mjml as normal...

@homerjam
Copy link
Author

Updated version with support for mc:edit

@absentees
Copy link

Forked homerjams gist as it was stripping css-class

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment