landmark-serve
Version:
Web Application Framework and Admin GUI / Content Management System built on Express.js and Mongoose
424 lines (388 loc) • 19.1 kB
text/jade
extends ../layout/base
include ../mixins/rows
include ../mixins/pagination
block css
if list.fieldTypes.markdown
link(rel="stylesheet", href="/landmark/js/lib/bootstrap-markdown/css/bootstrap-markdown.css")
block js
if list.fieldTypes.location
script(src="/landmark/js/common/ui-location.js")
if list.fieldTypes.markdown
script(src='/landmark/js/lib/marked/marked.js')
script(src='/landmark/js/lib/bootstrap-markdown/js/bootstrap-markdown.js')
script(src="/landmark/js/common/ui-markdown.js")
if list.fieldTypes.wysiwyg
script(src='/landmark/js/lib/tinymce/tinymce.min.js')
script(src='/landmark/js/lib/tinymce/jquery.tinymce.min.js')
script(src="/landmark/js/common/ui-wysiwyg.js")
script(src='/landmark/js/views/list.js')
script(src='/landmark/js/lib/browserified/querystring.js')
script(src='/landmark/js/lib/browserified/queryfilter.js')
script.
Landmark.list = { path: "#{list.path}", label: "#{list.label}", singular: "#{list.singular}", plural: "#{list.plural}", cols: !{JSON.stringify(colPaths)}, perPage: !{ Number(list.perPage) || 50 } };
Landmark.wysiwyg = { options: !{JSON.stringify(wysiwygOptions)} };
Landmark.items = !{ JSON.stringify(items) };
Landmark.search = "#{search}";
Landmark.filters = !{ JSON.stringify(filters) };
Landmark.sort = "#{sort.by}";
Landmark.query = "#{query}";
Landmark.csrf_query = "!{csrf_query}";
block content
//-
//- Create Item
//- ------------------------------
if !list.get('nocreate')
.create-item
if list.get('autocreate')
.toolbar
a(href='?new' + csrf_query).btn.btn-default.btn-create.btn-create-item
span.ion-plus-round
| Create #{list.singular}
else
form(method='post', action='/landmark/' + list.path)
input(type='hidden', name='action', value='create')
input(type='hidden', name=csrf_token_key, value=csrf_token_value)
.form
h4 New
if list.nameIsInitial
if list.nameField.type == 'name'
.field.item-name: .col-sm-12: input(type='text', name=list.nameField.paths.full, value=submitted[list.nameField.paths.full], placeholder=list.singular + ' ' + list.nameField.label.toLowerCase()).form-control.input-lg
else if list.nameField.type == 'text'
.field.item-name: .col-sm-12: input(type='text', name=list.nameField.path, value=submitted[list.nameField.path], placeholder=list.singular + ' ' + list.nameField.label.toLowerCase()).form-control.input-lg
else
.alert.alert-danger Unsupported field type (#{list.nameField.type}) for item name (#{list.nameField.path}).
each field in list.initialFields
|!{field.render('initial',submitted) }
.toolbar
.toolbar-create
button(type='submit').btn.btn-default.btn-create Create
a(href=js).btn.btn-link.btn-cancel.btn-cancel-create-item cancel
.toolbar-default
a(href=js, class=(showCreateForm ? 'autoclick' : null)).btn.btn-default.btn-create.btn-create-item
span.ion-plus-round
| Create #{list.singular}
hr
//-
//- List
//- ------------------------------
- var xFilters = _.size(filters);
.page-header.list-header(class=!items.total && !search && !xFilters ? 'empty-list' : '')
h1
if items.total
span.items-total= utils.plural(items.total, '* ' + list.singular, '* ' + list.plural)
else
span.items-total No
= search || xFilters ? '' : '.'
.search-sort(style='display:inline-block')
if search
span.text-muted matching
span=search
if xFilters
span.text-muted and
span= utils.plural(xFilters, ' * other filter')
else if xFilters
span.text-muted matching
span= utils.plural(xFilters, ' * filter')
//- Sort
if items.results.length
if sort.by
span.text-muted ordered by
else
span.text-muted —
span.dropdown.list-sort-dropdown
a(href=js, data-toggle='dropdown').dropdown-toggle
if sort.label
= sort.label.toLowerCase() + ' '
if sort.inv
| (descending)
else
| sort by
b.caret
ul.dropdown-menu
if list.get('sortable')
if sort.by == 'sortOrder'
li: a(href=link_to({sort: '-sortOrder'}))
span(class='fieldicon fieldicon-sort')
span Display Order (inverted)
else if sort.by == '-sortOrder'
li: a(href=link_to({sort: 'sortOrder'}))
span(class='fieldicon fieldicon-sort')
span Display Order (normal)
else
li: a(href=link_to({sort: 'sortOrder'}))
span(class='fieldicon fieldicon-sort')
span Display Order
each el in list.uiElements
if el.type == 'heading'
li.dropdown-header= el.heading
else if el.type == 'field'
if el.field.type != 'relationship' && !el.field.nosort && !el.field.hidden
if sort.field && sort.field.path == el.field.path
if sort.inv
li: a(href=link_to({ sort: el.field.path }))
span(class='fieldicon fieldicon-' + el.field.type)
span= el.field.label + ' (ascending)'
else
li: a(href=link_to({ sort: '-' + el.field.path }))
span(class='fieldicon fieldicon-' + el.field.type)
span= el.field.label + ' (descending)'
else
li: a(href=link_to({ sort: el.field.path }))
span(class='fieldicon fieldicon-' + el.field.type)
span= el.field.label
//-
//- Filters
//- ------------------------------
form
//-
//- Search Bar
//- ------------------------------
if items.results.length || search || xFilters
.list-toolbar
.search-list
.form-inline
.dropdown-inline.list-filter-dropdown.js-recent-searches.hidden.mr-5
a(href=js, data-toggle='dropdown', title='Recent Filters').btn.btn-default.dropdown-toggle
span.sr-only Recent Filters
span.ion-clock
|
span.caret
ul.dropdown-menu
li.dropdown-header Recent Filters
.form-inline.searchbox-form.mr-5
.searchbox-field
input(type='search', name='search', placeholder='Search #{list.plural.toLowerCase()}', value=search).form-control.js-search-list.searchbox-input
if search
a(href='/landmark/#{list.path}', title='Clear search').search-list-clear ×
.searchbox-button: button(type='submit').btn.btn-default.btn-search.searchbox-submit
span.visible-xs Go
span.hidden-xs Search
//- .search-field
input(type='text', name='search', placeholder='Search ' + list.plural.toLowerCase(), value=search).form-control
//- span.search-button: button(type='submit').btn.btn-default.btn-search
span.visible-xs Go
span.hidden-xs Search
.dropdown-inline.list-filter-dropdown
a(href=js, data-toggle='dropdown').btn.btn-default.dropdown-toggle
| Add Filter
b.caret
ul.dropdown-menu: each el in list.uiElements
if el.type == 'heading'
li.dropdown-header= el.heading
else if el.type == 'field' && !el.field.nofilter && !el.field.hidden
li: a(href=js, data-path=el.field.path).add-list-filter
span(class='fieldicon fieldicon-' + el.field.type)
span= el.field.label
//- lg screens only
//- columns and download aren't relevant when using a mobile device
if items.total
.pull-right.hidden-xs
.pull-right
a(href=download_link, data-confirm="Download a .csv of #{utils.plural(items.total, '* ' + list.singular, '* ' + list.plural).toLowerCase()}?").btn.btn-default Download
div(style='margin-right: 10px;').dropdown.list-columns-dropdown.pull-right
a(href=js, data-toggle='dropdown').btn.btn-default.dropdown-toggle
| Columns
b.caret
ul.dropdown-menu
each el in list.uiElements
if el.type == 'heading'
li.dropdown-header= el.heading
else if el.type == 'field' && !el.field.nocol && !el.field.hidden
li: a(href=js, data-col=el.field.path).btn-toggle-column
span(class=_.contains(colPaths, el.field.path) ? 'ion-checkmark' : '').icon
span= el.field.label
if query.cols
li.divider
li: a(href=link_to({ cols: undefined }))
span.ion-close.icon
span Reset to default
//-
//- List Filters
//- ------------------------------
.list-filters
each field in list.fields
if !field.nofilter && !field.hidden
.filter(data-path=field.path, data-type=field.type, class=filters[field.path] ? 'active' : '')
.filter-label
a(href=js, title='Remove filter', data-toggle='tooltip', data-placement='right', data-container='body').btn-companion-xs.clear-filter ×
span(class='fieldicon fieldicon-' + field.type)
.btn-companion-xs=field.label
.filter-options
- var ops = filters[field.path] || {}
//- Text Fields
if field.type == 'text' || field.type == 'textarea' || field.type == 'html' || field.type == 'email' || field.type == 'url' || field.type == 'key'
.btn-group.btn-group-xs(data-toggle='buttons')
button(type='button', class=!ops.exact ? 'active' : '').btn.btn-default
input(type='radio', name='options')
| contains
button(type='button', class=ops.exact ? 'active' : '', data-opt='exact', data-value='true').btn.btn-default
input(type='radio', name='options')
| exact match
.filter-input.filter-input-md: input(type='text', name='value', value=ops.value).form-control.input-xs
button(type='button', data-toggle='button', class=ops.inv ? 'active' : '', data-opt='inv', data-value='true').btn.btn-xs.btn-default invert
//- Numeric Fields
if field.type == 'number' || field.type == 'money'
.btn-group.btn-group-operator.btn-group-xs(data-toggle='buttons')
button(type='button', class=!ops.operator ? 'active' : '').btn.btn-default
input(type='radio', name='options')
| exactly
button(type='button', class=ops.operator == 'gt' ? 'active' : '', data-opt='operator', data-value='gt').btn.btn-default
input(type='radio', name='options')
| greater than
button(type='button', class=ops.operator == 'lt' ? 'active' : '', data-opt='operator', data-value='lt').btn.btn-default
input(type='radio', name='options')
| less than
button(type='button', class=ops.operator == 'bt' ? 'active' : '', data-opt='operator', data-value='bt').btn.btn-default
input(type='radio', name='options')
| between
.filter-input
.filter-input-range.form-inline.text-sm(style='display:none')
input(type='number', value=(ops.operator === 'bt' && ops.value[0])).form-control.input-xs.field-inline--xs.filter-input-range1
span.mh-1 and
input(type='number', value=(ops.operator === 'bt' && ops.value[1])).form-control.input-xs.field-inline--xs.filter-input-range2
span.mh-1 (inclusive)
.filter-input-standard(style='display:none')
input(type='number', name='value', value=(ops.operator !== 'bt' && ops.value)).form-control.input-xs
//- Date
if field.type == 'date' || field.type == 'datetime'
.btn-group.btn-group-operator.btn-group-xs(data-toggle='buttons')
button(type='button', class=(!ops.operator) ? 'active' : '').btn.btn-default
input(type='radio', name='options')
| on
button(type='button', class=ops.operator == 'gt' ? 'active' : '', data-opt='operator', data-value='gt').btn.btn-default
input(type='radio', name='options')
| after
button(type='button', class=ops.operator == 'lt' ? 'active' : '', data-opt='operator', data-value='lt').btn.btn-default
input(type='radio', name='options')
| before
button(type='button', class=ops.operator == 'bt' ? 'active' : '', data-opt='operator', data-value='bt').btn.btn-default
input(type='radio', name='options')
| between
.filter-input
.filter-input-range.form-inline.text-sm(style='display:none')
input(type='text', value=(ops.operator === 'bt' && ops.value[0])).form-control.input-xs.field-inline--sm.ui-datepicker.filter-input-range1
span.mh-1 and
input(type='text', value=(ops.operator === 'bt' && ops.value[1])).form-control.input-xs.field-inline--sm.ui-datepicker.filter-input-range2
span.mh-1 (inclusive)
.filter-input-standard(style='display:none')
input(type='text', name='value', value=(ops.operator !== 'bt' && ops.value)).form-control.input-xs.ui-datepicker
//- Location
if field.type == 'location'
- ops.value = ops.value || []
.filter-input.filter-input-md: input(type='text', name='address', value=ops.value[0], placeholder='Address').form-control.input-xs
.filter-input.filter-input-sm: input(type='text', name='suburb', value=ops.value[1], placeholder='Suburb').form-control.input-xs
.filter-input.filter-input-sm: input(type='text', name='state', value=ops.value[2], placeholder='State').form-control.input-xs
.filter-input.filter-input-sm: input(type='text', name='postcode', value=ops.value[3], placeholder='Postcode').form-control.input-xs
.filter-input.filter-input-sm: input(type='text', name='country', value=ops.value[4], placeholder='Country').form-control.input-xs
//- Select
if field.type == 'select'
.btn-group.btn-group-xs(data-toggle='buttons')
button(type='button', class=!ops.inv ? 'active' : '').btn.btn-default
input(type='radio', name='options')
| is
button(type='button', class=ops.inv ? 'active' : '', data-opt='inv', data-value='true').btn.btn-default
input(type='radio', name='options')
| is not
.filter-input.filter-input-md: select(name='value')
option(value='')
each op in field.ops
option(value=op.value, selected=ops.value == op.value)= op.label
//- Boolean
if field.type == 'boolean'
.btn-group.btn-group-xs(data-toggle='buttons')
button(type='button', class=ops.value !== false ? 'active' : '', data-opt='value', data-value='true').btn.btn-default
input(type="radio", name="options")
| checked
button(type='button', class=ops.value === false ? 'active' : '', data-opt='value', data-value='false').btn.btn-default
input(type="radio", name="options")
| not checked
//- Cloudinary Image
if field.type == 'cloudinaryimage'
.btn-group.btn-group-xs(data-toggle='buttons')
button(type='button', class=ops.value !== false ? 'active' : '', data-opt='value', data-value='true').btn.btn-default
input(type="radio", name="options")
| has image
button(type='button', class=ops.value === false ? 'active' : '', data-opt='value', data-value='false').btn.btn-default
input(type="radio", name="options")
| has no image
//- S3 File
if field.type == 's3file'
.btn-group.btn-group-xs(data-toggle='buttons')
button(type='button', class=ops.value !== false ? 'active' : '', data-opt='value', data-value='true').btn.btn-default
input(type="radio", name="options")
| has file
button(type='button', class=ops.value === false ? 'active' : '', data-opt='value', data-value='false').btn.btn-default
input(type="radio", name="options")
| has no file
//- Local File
if field.type == 'localfile'
.btn-group.btn-group-xs(data-toggle='buttons')
button(type='button', class=ops.value !== false ? 'active' : '', data-opt='value', data-value='true').btn.btn-default
input(type="radio", name="options")
| has file
button(type='button', class=ops.value === false ? 'active' : '', data-opt='value', data-value='false').btn.btn-default
input(type="radio", name="options")
| has no file
//- Relationships
if field.type == 'relationship'
.btn-group.btn-group-xs(data-toggle='buttons')
button(type='button', class=ops.inv ? '' : 'active', data-opt='inv', data-value='false').btn.btn-default
input(type="radio", name="options")
| linked to
button(type='button', class=ops.inv ? 'active' : '', data-opt='inv', data-value='true').btn.btn-default
input(type="radio", name="options")
| not linked to
input(type='hidden',
name=field.path,
id='field_' + field.path,
value=ops.value,
data-ref-path=field.refList.path,
data-ref-singular=field.refList.singular,
data-ref-plural=field.refList.plural).ui-select2-ref
.list-filters-action
button(type='submit').btn.btn-default.btn-filter Search
a(href='/landmark/#{list.path}').btn.btn-link.btn-cancel clear filters
if items.results.length
//-
//- Pagination
//- ------------------------------
.list-pagination
if items.totalPages > 1
+pagination(items)
else
.count Showing #{utils.plural(items.total, '* ' + list.singular, '* ' + list.plural)}
- var sortable = list.get('sortable') && !list.get('sortContext') && sort.by == 'sortOrder'
- var firstColspan = 1;
.items-list-wrapper: table(cellpadding=0, cellspacing=0, class=sortable ? 'sortable' : false, data-list-path=list.path).table.items-list
if !list.get('nodelete')
- firstColspan++;
col(width=26)
if sortable
- firstColspan++;
col(width=26)
each col in columns
col(width=col.width)
thead
tr
each col, i in columns
th(colspan=i == 0 && firstColspan > 1 ? firstColspan : false)
if col.field && sort.field && sort.field.path == col.field.path
if sort.inv
a(href=link_to({ sort: col.field.path }), title='Sort by ' + col.label + ' (asc)', class='th-sort--desc').th-sort
= col.label
span.th-sort__icon
else
a(href=link_to({ sort: '-' + col.field.path }), title='Sort by ' + col.label + ' (desc)', class='th-sort--asc').th-sort
= col.label
span.th-sort__icon
else if col.field
a(href=link_to({ sort: col.field.path }), title='Sort by ' + col.label).th-sort= col.label
span.th-sort__icon
else
= col.label
tbody
each item in items.results
+row(list, colums, item)
if items.totalPages > 1
.list-pagination
+pagination(items)