details.js 5.1 KB

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