details.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. const _ = require('lodash')
  2. const app = require('../../app')
  3. const { dollarIcon } = require('../../assets')
  4. /**
  5. * @param {CrudPagesOptions} opts
  6. */
  7. const details = (opts) => {
  8. const hideCriteria = column => column.routeParam ? `ctrl.$routeParams.${column.routeParam} !== '${opts.paramAll}'` : 'false'
  9. const autocompleteInput = column => {
  10. if (!column.apiPrefix) throw new Error('apiPrefix is required for autocomplete fields')
  11. return html`
  12. <md-autocomplete flex
  13. md-selected-item="ctrl.autocomplete.${raw(column.camelName)}.selectedItem"
  14. md-search-text="ctrl.autocomplete.${raw(column.camelName)}.searchText"
  15. md-items="item in ctrl.autocomplete.${raw(column.camelName)}.getItems(ctrl.autocomplete.${raw(column.camelName)}.searchText)"
  16. md-item-text="item.name || item.key || item.id"
  17. md-require-match="true"
  18. md-selected-item-change="ctrl.autocomplete.${raw(column.camelName)}.onChange()"
  19. md-min-length="0"s
  20. placeholder="${column.titleName}"
  21. ng-hide="${hideCriteria(column)}">
  22. <md-item-template>
  23. <span md-highlight-text="ctrl.searchText.${raw(column.camelName)}" md-highlight-flags="^i">{{item.name || item.key || item.id}}</span>
  24. </md-item-template>
  25. </md-autocomplete>
  26. `
  27. }
  28. const attrs = obj => obj ? _.toPairs().map((key, value) => html`${key}="${value}"`).join(' ') : ''
  29. const standardInput = column => html`
  30. <md-input-container flex ng-hide="${hideCriteria(column)}">
  31. <label>${column.titleName}</label>
  32. <input type="${column.type || 'text'}" ${attrs(column.attrs)} ng-model="model.${raw(column.camelName)}" />
  33. </md-input-container>
  34. `
  35. const currencyInput = column => html`
  36. <md-input-container flex ng-hide="${hideCriteria(column)}">
  37. <label>${column.titleName}</label>
  38. <input type="number" step="0.01" ng-model="model.${raw(column.camelName)}" />
  39. <md-icon md-svg-src="${dollarIcon}"></md-icon>
  40. </md-input-container>
  41. `
  42. const defaultField = column =>
  43. column.type === 'autocomplete'
  44. ? autocompleteInput(column)
  45. : column.type === 'currency'
  46. ? currencyInput(column)
  47. : standardInput(column)
  48. const layout = () => {
  49. if (opts.layout) {
  50. const cols = _.fromPairs(opts.columns.map(col => [col.camelName, col]))
  51. return opts.layout.map(section => html`
  52. <md-card>
  53. ${section.section ? html`<h2 style="padding-left: 15px">${section.section}</h2>`:''}
  54. <md-card-content layout="column" layout-margin>
  55. ${raw(section.rows ? section.rows.map(
  56. row => html`
  57. <div flex layout="column" layout-gt-sm="row" layout-margin>
  58. ${raw(row
  59. .map(field => cols[field])
  60. .map(c => c.field || defaultField(c))
  61. .join('\n')
  62. )}
  63. </div>
  64. `).join('\n')
  65. : '')}
  66. </md-card-content>
  67. </md-card>`).join('\n')
  68. } else {
  69. return html`
  70. <md-card>
  71. <md-card-content layout="column" layout-gt-sm="column" layout-margin>
  72. ${opts.columns.map(c => html`
  73. ${c.field || defaultField(c)}
  74. `)}
  75. </md-card-content>
  76. </md-card>
  77. `
  78. }
  79. }
  80. const template = html`
  81. <app-user-area>
  82. <h1 ng-show="ctrl.isNew">New ${opts.titleName}</h1>
  83. <h1 ng-hide="ctrl.isNew">${opts.titleName} Details</h1>
  84. <form name="form" ng-submit="ctrl.submit()" layout="column" layout-margin
  85. flex-xs="100"
  86. flex-sm="100"
  87. flex-md="70"
  88. flex-lg="70"
  89. flex-xl="70"
  90. >
  91. ${raw(layout())}
  92. <div>
  93. <md-button type="submit" class="md-raised md-primary">Submit</md-button>
  94. </div>
  95. </form>
  96. </app-user-area>
  97. `
  98. console.log(`crud: app${opts.pascalName}DetailsPage`)
  99. app.component(`app${opts.pascalName}DetailsPage`, {
  100. template,
  101. controllerAs: 'ctrl',
  102. controller: function(api, $scope, $routeParams, $mdToast, $location, $q, util) {
  103. this.$routeParams = $routeParams
  104. this.template = template // For inspection purposes
  105. this.apiPrefix = util.fillPath(opts.apiPrefix, $routeParams)
  106. this.appPrefix = util.fillPath(opts.appPrefix, $routeParams)
  107. this.isNew = $routeParams[opts.routeParam] === opts.paramNew
  108. const crud = api.crud(this.apiPrefix)
  109. let original
  110. if (this.isNew) {
  111. original = {}
  112. $scope.model = Object.create(original)
  113. this.loadingPromise = $q.resolve($scope.model)
  114. } else {
  115. this.loadingPromise = crud.read($routeParams[opts.routeParam]).then(model => {
  116. original = model
  117. $scope.model = Object.create(original)
  118. return $scope.model
  119. })
  120. }
  121. this.submit = async () => {
  122. try {
  123. if (this.isNew) {
  124. await crud.create($scope.model)
  125. } else {
  126. const obj = {}
  127. for (var key in $scope.model) {
  128. if ($scope.model.hasOwnProperty(key)) {
  129. obj[key] = $scope.model[key]
  130. }
  131. }
  132. await crud.update(original.id, obj)
  133. }
  134. $mdToast.showSimple(`${opts.titleName} saved.`)
  135. $location.url(this.appPrefix)
  136. } catch (err) {
  137. console.error(err)
  138. $mdToast.showSimple(`Could not save ${opts.titleName}: ${err.message || err.statusText || err}`)
  139. }
  140. }
  141. /* Autocomplete fields */
  142. this.searchText = {}
  143. this.autocomplete = {}
  144. opts.columns.filter(c => c.type === 'autocomplete').forEach(c => {
  145. const crud = api.crud(util.fillPath(c.apiPrefix, $routeParams))
  146. const ac = this.autocomplete[c.camelName] = {
  147. onChange() {
  148. $scope.model[c.camelName] = ac.selectedItem
  149. ? ac.selectedItem.id
  150. : null
  151. },
  152. getItems(searchText) {
  153. return crud.autocomplete(searchText)
  154. }
  155. }
  156. this.loadingPromise.then(model => {
  157. if (model[c.camelName]) {
  158. crud.read(model[c.camelName]).then(record => {
  159. ac.selectedItem = record
  160. })
  161. }
  162. })
  163. })
  164. }
  165. })
  166. }
  167. module.exports = details