| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- const _ = require('lodash')
- const app = require('../../app')
- const { Set } = require('immutable')
- const { dollarIcon, dropdownIcon } = require('../../assets')
- /**
- * @param {CrudPagesOptions} opts
- */
- const details = (opts) => {
- const hideCriteria = column => column.routeParam ? `$ctrl.$routeParams.${column.routeParam} !== '${opts.paramAll}'` : 'false'
- const autocompleteInput = column => {
- if (!column.apiPrefix) throw new Error('apiPrefix is required for autocomplete fields')
- return html`
- <md-autocomplete flex
- md-selected-item="$ctrl.autocomplete.${raw(column.camelName)}.selectedItem"
- md-search-text="$ctrl.autocomplete.${raw(column.camelName)}.searchText"
- md-items="item in $ctrl.autocomplete.${raw(column.camelName)}.getItems($ctrl.autocomplete.${raw(column.camelName)}.searchText)"
- md-item-text="item.name || item.key || item.id"
- md-require-match="true"
- md-selected-item-change="$ctrl.autocomplete.${raw(column.camelName)}.onChange()"
- md-min-length="0"
- placeholder="${column.titleName}"
- ng-hide="${hideCriteria(column)}">
- <md-item-template>
- <span md-highlight-text="$ctrl.searchText.${raw(column.camelName)}" md-highlight-flags="^i">{{item.name || item.key || item.id}}</span>
- </md-item-template>
- </md-autocomplete>
- `
- }
- const multiSelectInput = column => {
- if (!column.apiPrefix) throw new Error('apiPrefix is required for multi-select fields')
- return html`
- <md-input-container flex>
- <label>${column.titleName}</label>
- <md-select ng-model="model.${raw(column.camelName)}" multiple>
- <md-option ng-repeat="item in ::$ctrl.multiSelect.${raw(column.camelName)}.items" value="{{item.key}}">
- {{item.name || item.key}}
- </md-option>
- </md-select>
- </md-input-container>
- `
- }
- const attrs = obj => obj ? raw(_.toPairs(obj).map(([key, value]) => `${key}="${value}"`).join(' ')) : ''
- const standardInput = column => html`
- <md-input-container flex ng-hide="${hideCriteria(column)}">
- <label>${column.titleName}</label>
- <input type="${column.type || 'text'}" ${attrs(column.attrs)} ng-model="model.${raw(column.camelName)}" />
- </md-input-container>
- `
- const currencyInput = column => html`
- <md-input-container flex ng-hide="${hideCriteria(column)}">
- <label>${column.titleName}</label>
- <input type="number" step="0.01" ng-model="model.${raw(column.camelName)}" />
- <md-icon md-svg-src="${dollarIcon}"></md-icon>
- </md-input-container>
- `
- const defaultField = column =>
- column.type === 'autocomplete'
- ? autocompleteInput(column)
- : column.type === 'currency'
- ? currencyInput(column)
- : column.type === 'multi-select'
- ? multiSelectInput(column)
- : standardInput(column)
- const layout = () => {
- if (opts.layout) {
- const cols = _.fromPairs(opts.columns.map(col => [col.camelName, col]))
- return opts.layout.map(section => html`
- <md-card>
- ${section.section ? html`<h2 style="padding-left: 15px">${section.section}</h2>`:''}
- <md-card-content layout="column" layout-margin>
- ${raw(section.rows ? section.rows.map(
- row => html`
- <div flex layout="column" layout-gt-sm="row" layout-margin>
- ${raw(row
- .map(field => cols[field])
- .map(c => c.field || defaultField(c))
- .join('\n')
- )}
- </div>
- `).join('\n')
- : '')}
- </md-card-content>
- </md-card>`).join('\n')
- } else {
- return html`
- <md-card>
- <md-card-content layout="column" layout-gt-sm="column" layout-margin>
- ${opts.columns.map(c => html`
- ${c.field || defaultField(c)}
- `)}
- </md-card-content>
- </md-card>
- `
- }
- }
- const template = html`
- <app-user-area title-text="${opts.titles && opts.titles.details ? `{{$ctrl.titleFn($ctrl)}}` : `${opts.titleName} Details`}">
- <form name="form" ng-submit="$ctrl.submit()" layout="column" layout-margin
- flex-xs="100"
- flex-sm="100"
- flex-md="70"
- flex-lg="70"
- flex-xl="70"
- >
- ${raw(layout())}
- <div>
- <md-button type="submit" class="md-raised md-primary">Submit</md-button>
- </div>
- </form>
- </app-user-area>
- `
- console.log(`crud: app${opts.pascalName}DetailsPage`)
- app.component(`app${opts.pascalName}DetailsPage`, {
- template,
- controller: function(api, $scope, $routeParams, $mdToast, $location, $q, util) {
- this.titleFn = opts.titles && opts.titles.details
- this.$scope = $scope
- this.$mdToast = $mdToast
- this.$location = $location
- this.$q = $q
- this.util = util
- this.api = api
- this.$routeParams = $routeParams
- this.template = template // For inspection purposes
- this.apiPrefix = util.fillPath(opts.apiPrefix, $routeParams)
- this.appPrefix = util.fillPath(opts.appPrefix, $routeParams)
- this.isNew = $routeParams[opts.routeParam] === opts.paramNew
- const crud = api.crud(this.apiPrefix)
- let original
- if (this.isNew) {
- original = {}
- $scope.model = Object.create(original)
- this.loadingPromise = $q.resolve($scope.model)
- } else {
- this.loadingPromise = crud.read($routeParams[opts.routeParam]).then(model => {
- original = model
- $scope.model = Object.create(original)
- return $scope.model
- })
- }
- this.submit = async () => {
- try {
- if (this.isNew) {
- await crud.create($scope.model)
- } else {
- const obj = {}
- for (var key in $scope.model) {
- if ($scope.model.hasOwnProperty(key)) {
- obj[key] = $scope.model[key]
- }
- }
- await crud.update(original.id, obj)
- }
- $mdToast.showSimple(`${opts.titleName} saved.`)
- $location.url(this.appPrefix)
- } catch (err) {
- console.error(err)
- $mdToast.showSimple(`Could not save ${opts.titleName}: ${err.message || err.statusText || err}`)
- }
- }
- /* Autocomplete fields */
- this.searchText = {}
- this.autocomplete = {}
- opts.columns.filter(c => c.type === 'autocomplete').forEach(c => {
- const crud = api.crud(util.fillPath(c.apiPrefix, $routeParams))
- const ac = this.autocomplete[c.camelName] = {
- onChange() {
- $scope.model[c.camelName] = ac.selectedItem
- ? ac.selectedItem.id
- : null
- },
- getItems(searchText) {
- return crud.autocomplete(searchText)
- }
- }
- this.loadingPromise.then(model => {
- if (model[c.camelName]) {
- crud.read(model[c.camelName]).then(record => {
- ac.selectedItem = record
- })
- }
- })
- })
- /* Multi-Select fields */
- this.multiSelect = {}
- opts.columns.filter(c => c.type === 'multi-select').forEach(c => {
- const crud = api.crud(util.fillPath(c.apiPrefix, $routeParams))
- const ms = {}
- this.multiSelect[c.camelName] = ms
- const updateLabel = () => {
- ms.label = ms.set.toArray()
- .map(key => ms.lookup[key].name || key)
- .join(', ')
- }
- this.loadingPromise.then(() => {
- crud.list().then(items => {
- ms.lookup = _.fromPairs(items.map(x => [x.key, x]))
- ms.items = items
- ms.set = new Set($scope.model[c.camelName])
- updateLabel()
- })
- })
- ms.add = (key) => {
- ms.set = ms.set.add(key)
- $scope.model[c.camelName] = ms.set.toArray()
- updateLabel()
- }
- ms.remove = (key) => {
- ms.set = ms.set.remove(key)
- $scope.model[c.camelName] = ms.set.toArray()
- updateLabel()
- }
- ms.toggle = (key) =>
- ms.set.has(key) ? ms.remove(key) : ms.add(key)
- })
- if (opts.controllers && opts.controllers.details) {
- opts.controllers.details.apply(this)
- }
- }
- })
- }
- module.exports = details
|