Navigation

    Quasar Framework

    • Register
    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    1. Home
    2. rconstantine
    R
    • Profile
    • Following
    • Followers
    • Topics
    • Posts
    • Best
    • Groups

    rconstantine

    @rconstantine

    18
    Reputation
    147
    Posts
    380
    Profile views
    1
    Followers
    0
    Following
    Joined Last Online

    rconstantine Follow

    Best posts made by rconstantine

    • RE: [Solved] QTable selection option not working when slots are overridden?

      Got it working for those who might find this:

      <!--
        Forked from:
        https://quasar.dev/vue-components/table#Example--Popup-editing
      -->
      <div id="q-app">
        <div class="q-pa-md">
            <q-table
              title="Treats"
              :data="data"
              :columns="columns"
              row-key="name"
              binary-state-sort
              selection="single" 
              :selected.sync="selected" 
            ><!-- add the two last (selection) lines, above -->
              <template v-slot:body="props">
                <q-tr :props="props">
                  <q-td>                                  <!-- add this line -->
                    <q-checkbox v-model="props.selected"/><!-- add this line -->
                  </q-td>                                 <!-- add this line -->
                  <q-td key="desc" :props="props">
                    {{ props.row.name }}
                    <q-popup-edit v-model="props.row.name">
                      <q-input v-model="props.row.name" dense autofocus counter ></q-input>
                    </q-popup-edit>
                  </q-td>
                  <q-td key="calories" :props="props">
                    {{ props.row.calories }}
                    <q-popup-edit v-model="props.row.calories" title="Update calories" buttons>
                      <q-input type="number" v-model="props.row.calories" dense autofocus ></q-input>
                    </q-popup-edit>
                  </q-td>
                  <q-td key="fat" :props="props">
                    <div class="text-pre-wrap">{{ props.row.fat }}</div>
                    <q-popup-edit v-model="props.row.fat">
                      <q-input type="textarea" v-model="props.row.fat" dense autofocus ></q-input>
                    </q-popup-edit>
                  </q-td>
                  <q-td key="carbs" :props="props">
                    {{ props.row.carbs }}
                    <q-popup-edit v-model="props.row.carbs" title="Update carbs" buttons persistent>
                      <q-input type="number" v-model="props.row.carbs" dense autofocus hint="Use buttons to close" ></q-input>
                    </q-popup-edit>
                  </q-td>
                  <q-td key="protein" :props="props">{{ props.row.protein }}</q-td>
                  <q-td key="sodium" :props="props">{{ props.row.sodium }}</q-td>
                  <q-td key="calcium" :props="props">{{ props.row.calcium }}</q-td>
                  <q-td key="iron" :props="props">{{ props.row.iron }}</q-td>
                </q-tr>
              </template>
            </q-table>
          </div>
      </div>
      
      new Vue({
        el: '#q-app',
        data () {
          return {
            columns: [
              {
                name: 'desc',
                required: true,
                label: 'Dessert (100g serving)',
                align: 'left',
                field: row => row.name,
                format: val => `${val}`,
                sortable: true
              },
              { name: 'calories', align: 'center', label: 'Calories', field: 'calories', sortable: true },
              { name: 'fat', label: 'Fat (g)', field: 'fat', sortable: true, style: 'width: 10px' },
              { name: 'carbs', label: 'Carbs (g)', field: 'carbs' },
              { name: 'protein', label: 'Protein (g)', field: 'protein' },
              { name: 'sodium', label: 'Sodium (mg)', field: 'sodium' },
              { name: 'calcium', label: 'Calcium (%)', field: 'calcium', sortable: true, sort: (a, b) => parseInt(a, 10) - parseInt(b, 10) },
              { name: 'iron', label: 'Iron (%)', field: 'iron', sortable: true, sort: (a, b) => parseInt(a, 10) - parseInt(b, 10) }
            ],
            data: [
              {
                name: 'Frozen Yogurt',
                calories: 159,
                fat: 6.0,
                carbs: 24,
                protein: 4.0,
                sodium: 87,
                calcium: '14%',
                iron: '1%'
              },
              {
                name: 'Ice cream sandwich',
                calories: 237,
                fat: 9.0,
                carbs: 37,
                protein: 4.3,
                sodium: 129,
                calcium: '8%',
                iron: '1%'
              },
              {
                name: 'Eclair',
                calories: 262,
                fat: 16.0,
                carbs: 23,
                protein: 6.0,
                sodium: 337,
                calcium: '6%',
                iron: '7%'
              },
              {
                name: 'Cupcake',
                calories: 305,
                fat: 3.7,
                carbs: 67,
                protein: 4.3,
                sodium: 413,
                calcium: '3%',
                iron: '8%'
              },
              {
                name: 'Gingerbread',
                calories: 356,
                fat: 16.0,
                carbs: 49,
                protein: 3.9,
                sodium: 327,
                calcium: '7%',
                iron: '16%'
              },
              {
                name: 'Jelly bean',
                calories: 375,
                fat: 0.0,
                carbs: 94,
                protein: 0.0,
                sodium: 50,
                calcium: '0%',
                iron: '0%'
              },
              {
                name: 'Lollipop',
                calories: 392,
                fat: 0.2,
                carbs: 98,
                protein: 0,
                sodium: 38,
                calcium: '0%',
                iron: '2%'
              },
              {
                name: 'Honeycomb',
                calories: 408,
                fat: 3.2,
                carbs: 87,
                protein: 6.5,
                sodium: 562,
                calcium: '0%',
                iron: '45%'
              },
              {
                name: 'Donut',
                calories: 452,
                fat: 25.0,
                carbs: 51,
                protein: 4.9,
                sodium: 326,
                calcium: '2%',
                iron: '22%'
              },
              {
                name: 'KitKat',
                calories: 518,
                fat: 26.0,
                carbs: 65,
                protein: 7,
                sodium: 54,
                calcium: '12%',
                iron: '6%'
              }
            ],
            selected: [] // add this line
          }
        }
      })
      
      posted in Help
      R
      rconstantine
    • [Solved] How to use CASL with Quasar

      I just got a PM asking how to do this, so figured I’d write it up for any interested…

      First, create a boot file like this:

      import { abilitiesPlugin } from '@casl/vue'
      
      export default ({ Vue }) => {
        Vue.use(abilitiesPlugin)
      }
      

      In your boot folder, have a subfolder or file containing your abilities:

      /* ability objects look like this:
       * {"name": "Role edit", "subject": "Role", "actions": "edit", "inverted": "false", "conditions": "???", "fields": "???", "reason": "???"}
       * -- name, subject, and actions are mandatory.
       * -- Inverted defaults to false.
       * -- Conditions must use fields belonging to the model used in Subject. *** Note: We aren't tying permissions to models in our application.
       * -- Fields is used when field-level permissions are desired
       * -- Reason is usually set for inverted rules and can be used to tell a user why they can"t do something.
       *
       * See also: https://stalniy.github.io/casl/abilities/2017/07/20/define-abilities.html
       */
      // TODO move this to only the server side and add an API endpoint to retrieve whatever we need from there instead of here
      
      export const abilities = '{"allAbilities": [' +
        '  {"group_name": "Administration",' +
        '    "description": "Administer this application",' +
        '    "abilities": [' +
        '      {"name": "Administer manage", "subject": "Administer", "actions": "manage"}' +
        '    ]' +
        '  },' +
        '  { "group_name": "CQA",' +
        '    "description": "View and/or edit CQA",' +
        '    "abilities": [' +
        '      {"name": "CQA edit", "subject": "CQA", "actions": "edit"},' +
        '      {"name": "CQA view", "subject": "CQA", "actions": "view"}' +
        '    ]' +
        '  },' +
        '  { "group_name": "Kinetics",' +
        '    "description": "View and/or edit Kinetics",' +
        '    "abilities": [' +
        '      {"name": "Kinetics edit", "subject": "Kinetics", "actions": "edit"},' +
        '      {"name": "Kinetics view", "subject": "Kinetics", "actions": "view"}' +
        '    ]' +
        '  },' +
        '  {"group_name": "Permissions",' +
        '    "description": "View defined permissions (CASL abilities)",' +
        '    "abilities": [' +
        '      {"name": "Permissions view", "subject": "Permission", "actions": "view"}' +
        '    ]' +
        '  },' +
        '  {"group_name": "PharmAssist",' +
        '    "description": "Permissions for the PharmAssist workflows",' +
        '    "abilities": [' +
        '      {"name": "PharmAssist view", "subject": "PharmAssist", "actions": "view"},' +
        '      {"name": "PharmAssist edit", "subject": "PharmAssist", "actions": "edit"},' +
        '      {"name": "PharmAssist Reports view", "subject": "PharmAssist Reports", "actions": "view"}' +
        '    ]' +
        '  },' +
        '  { "group_name": "Reports",' +
        '    "description": "View and/or manage reports",' +
        '    "abilities": [' +
        '      {"name": "Reports edit", "subject": "Reports", "actions": "edit"},' +
        '      {"name": "Reports view", "subject": "Reports", "actions": "view"}' +
        '    ]' +
        '  },' +
        '  { "group_name": "Role",' +
        '    "description": "View and/or manage roles",' +
        '    "abilities": [' +
        '      {"name": "Role edit", "subject": "Role", "actions": "edit"},' +
        '      {"name": "Role delete", "subject": "Role", "actions": "delete"},' +
        '      {"name": "Role view", "subject": "Role", "actions": "view"}' +
        '    ]' +
        '  },' +
        '  {"group_name": "RolePerms",' +
        '    "description": "View and/or manage rolesPerms",' +
        '    "abilities": [' +
        '      {"name": "RolesPerms edit", "subject": "RolesPerms", "actions": "edit"},' +
        '      {"name": "RolesPerms view", "subject": "RolesPerms", "actions": "view"}' +
        '    ]' +
        '  },' +
        '  { "group_name": "Training",' +
        '    "description": "View and/or edit training",' +
        '    "abilities": [' +
        '      {"name": "Training edit", "subject": "Training", "actions": "edit"},' +
        '      {"name": "Training view", "subject": "Training", "actions": "view"}' +
        '    ]' +
        '  },' +
        '  {"group_name": "Users",' +
        '    "description": "View and/or manage users\' roles",' +
        '    "abilities": [' +
        '      {"name": "User edit", "subject": "User", "actions": "edit"},' +
        '      {"name": "User delete", "subject": "User", "actions": "delete"},' +
        '      {"name": "User view", "subject": "User", "actions": "view"}' +
        '    ]' +
        '  },' +
        '  {"group_name": "Groups",' +
        '    "description": "View and/or manage group\' roles",' +
        '    "abilities": [' +
        '      {"name": "Group edit", "subject": "Group", "actions": "edit"},' +
        '      {"name": "Group delete", "subject": "Group", "actions": "delete"},' +
        '      {"name": "Group view", "subject": "Group", "actions": "view"}' +
        '    ]' +
        '  },' +
        '  {"group_name": "Security and Logs",' +
        '    "description": "View and/or manage logs",' +
        '    "abilities": [' +
        '      {"name": "Watchdog view", "subject": "Watchdog", "actions": "view"},' +
        '      {"name": "ReportUsage view", "subject": "ReportUsage", "actions": "view"},' +
        '      {"name": "Session view", "subject": "Session", "actions": "view"}' +
        '    ]' +
        '  }' +
        ']' +
        '}'
      
      export function displayAbilities () {
        let newAblArray = []
        let abl = JSON.parse(abilities)
        abl.allAbilities.forEach((element) => {
          element.abilities.forEach((el) => {
            el.id = newAblArray.length + 1
            if (typeof el.inverted === 'undefined') {
              el.inverted = false
            }
            newAblArray.push(el)
          })
        })
        // console.log('newAbl', newAblArray)
        return newAblArray
      }
      
      export function displayAbilitiesGroups () {
        var abl = JSON.parse(abilities, (key, value) => {
          return key === 'abilities' ? '' : value
        })
      
        // console.log('ability groups', abl)
        return abl.allAbilities
      }
      
      export function getAbilityTree () {
        var abl = JSON.parse(abilities)
      
        // console.log('ability groups', abl)
        return abl.allAbilities
      }
      

      You can intercept users before they go to a page using something like the below. I put it in my boot folder:

      /**
       * This file is to be used to control Vue's routing behavior. We can and should intercept
       * users who attempt to go to a URL without authorization. They could do this by bookmarking
       * a path they no longer have access for. This is the more fool-proof protection of our content
       * than the permissions checking we have on menu items and buttons, although that is important
       * too.
       *
       * 1) Use the routes.js file to identify the routes that need protection.
       * 2) Use the permissions defined in abilities.js as the things to check.
       *
       * Q: Why do we use 'can' and repeat a call to next()? Can't we just use cannot and fall
       * through to a common next()?
       *
       * A: Using cannot is a bad idea because all of our rules are positive. We must ensure someone
       * CAN do something before sending them along. Additionally, what if we forget to update this
       * file with new rules or paths? We need to default to blocking access, not granting it.
       */
      
      // import Ability
      import { Ability } from '@casl/ability'
      
      const routeAbility = new Ability()
      
      export default ({ app, router, store, Vue }) => {
        router.beforeEach((to, from, next) => {
          const redirectPath = {
            path: '/',
            query: { redirect: to.path }
          }
      
          routeAbility.update(store.state.account.abilityRules)
          // Now you need to add your authentication logic here, like calling an API endpoint
          // console.log('to', to)
          // console.log('from', from)
      
          if (to.matched.some(record => record.meta.requiresAuth)) {
            if (!store.state.account.authenticated) {
              // console.log('not authenticated')
              next(redirectPath)
            } else {
              // console.log('authenticated')
              if (store.state.account.abilityRules.length !== 0) {
                // console.log('not empty')
                // console.log('abilityRules', store.state.account.abilityRules)
                //* ****************** Administration *************************************** userRoles
                if (to.matched.some(record => record.path === '/users')) {
                  // console.log('users entered')
                  // console.log('routeAbility', routeAbility)
                  if (routeAbility.can('manage', 'Administer')) {
                    // console.log('can adminster users')
                    if (to.matched.some(record => record.path === '/users/userRoles')) {
                      if (routeAbility.can('view', 'User')) {
                        // console.log('can user')
                        next()
                      }
                    } else if (to.matched.some(record => record.path === '/users/groupRoles')) {
                      if (routeAbility.can('view', 'Group')) {
                        // console.log('can group')
                        next()
                      }
                    } else if (to.matched.some(record => record.path === '/users/rolePerms')) {
                      if (routeAbility.can('view', 'RolesPerms')) {
                        // console.log('can rolesperms')
                        next()
                      }
                    } else if (to.matched.some(record => record.path === '/users/roles')) {
                      if (routeAbility.can('view', 'Role')) {
                        // console.log('can role')
                        next()
                      }
                    } else if (to.matched.some(record => record.path === '/users/permissions')) {
                      if (routeAbility.can('view', 'Permission')) {
                        // console.log('can permission')
                        next()
                      }
                    } else {
                      next(redirectPath)
                    }
                  } else {
                    // console.log('cannot')
                    next(redirectPath)
                  }
                }
      
                if (to.matched.some(record => record.path === '/usage')) {
                  // console.log('users entered')
                  // console.log('routeAbility', routeAbility)
                  if (routeAbility.can('manage', 'Administer')) {
                    // console.log('can usage')
                    if (to.matched.some(record => record.path === '/usage/securityLog')) {
                      if (routeAbility.can('view', 'Watchdog')) {
                        // console.log('can securityLog')
                        next()
                      }
                    } else if (to.matched.some(record => record.path === '/usage/reportUsage')) {
                      if (routeAbility.can('view', 'ReportUsage')) {
                        // console.log('can reportUsage')
                        next()
                      }
                    } else if (to.matched.some(record => record.path === '/usage/sessionLog')) {
                      if (routeAbility.can('view', 'Session')) {
                        // console.log('can sessionLog')
                        next()
                      }
                    }
                    next()
                  } else {
                    // console.log('cannot')
                    next(redirectPath)
                  }
                }
      
                if (to.matched.some(record => record.path === '/reports')) {
                  if (routeAbility.can('manage', 'Administer')) {
                    next()
                  }
                }
      
                if (to.matched.some(record => record.path === '/server')) {
                  if (routeAbility.can('manage', 'Administer')) {
                    next()
                  }
                }
      
                //* ******************* End User Menu Items Begin ****************************
                if (to.matched.some(record => record.path === '/userReports')) {
                  if (routeAbility.can('edit', 'Reports') || routeAbility.can('view', 'Reports')) {
                    // console.log('can Reports')
                    next()
                  } else {
                    // console.log('cannot Reports')
                    next(redirectPath)
                  }
                }
      
                if (to.matched.some(record => record.path === '/kinetics')) {
                  if (routeAbility.can('edit', 'Kinetics') || routeAbility.can('view', 'Kinetics')) {
                    // console.log('can Kinetics')
                    next()
                  } else {
                    // console.log('cannot Kinetics')
                    next(redirectPath)
                  }
                }
      
                if (to.matched.some(record => record.path === '/pharmAssist')) {
                  if (routeAbility.can('edit', 'PharmAssist') ||
                      routeAbility.can('view', 'PharmAssist') ||
                      routeAbility.can('view', 'PharmAssist Reports')) {
                    // console.log('can PharmAssist')
                    next()
                  } else {
                    // console.log('cannot PharmAssist')
                    next(redirectPath)
                  }
                }
      
                if (to.matched.some(record => record.path === '/cqa')) {
                  if (routeAbility.can('edit', 'CQA') || routeAbility.can('view', 'CQA')) {
                    // console.log('can CQA')
                    next()
                  } else {
                    // console.log('cannot CQA')
                    next(redirectPath)
                  }
                }
      
                if (to.matched.some(record => record.path === '/training')) {
                  if (routeAbility.can('edit', 'Training') || routeAbility.can('view', 'Training')) {
                    // console.log('can Training')
                    next()
                  } else {
                    // console.log('cannot Training')
                    next(redirectPath)
                  }
                }
              } else {
                // the user has no assigned permissions, send them to the main page
                // TODO throw an error about not having permissions
                // console.log('ability rules empty')
                next(redirectPath)
              }
      
              // next(redirectPath)
            }
          } else {
            // doesn't require authentication, so this is safe to forward without further checks
            next()
          }
        })
      }
      

      In my router folder, I have this in my index.js file:

      import Vue from 'vue'
      import VueRouter from 'vue-router'
      
      import routes from './routes'
      
      Vue.use(VueRouter)
      
      /*
       * If not building with SSR mode, you can
       * directly export the Router instantiation
       */
      
      export default function (/* { store, ssrContext } */) {
        const Router = new VueRouter({
          scrollBehavior: () => ({ x: 0, y: 0 }),
          routes,
      
          // Leave these as is and change from quasar.conf.js instead!
          // quasar.conf.js -> build -> vueRouterMode
          // quasar.conf.js -> build -> publicPath
          mode: process.env.VUE_ROUTER_MODE,
          base: process.env.VUE_ROUTER_BASE
        })
      
        return Router
      }
      

      and in the routes.js file in the same folder:

      import Default from '../layouts/default'
      import Index from '../pages/index'
      
      import UserReports from '../pages/userReports'
      import AllReports from '../pages/usersReports/allReports'
      import RecentReports from '../pages/usersReports/recentReports'
      import DepartmentReports from '../pages/usersReports/departmentReports'
      import FacilityReports from '../pages/usersReports/facilityReports'
      import ThrowAway from '../pages/throwAway'
      
      import Kinetics from '../pages/kinetics'
      import PharmAssist from '../pages/pharmAssist/allWorkflows'
      import CQA from '../pages/cqa'
      import Training from '../pages/training'
      
      import UsersAdmin from '../pages/usersAdmin'
      import Roles from '../pages/users/roles'
      import Permissions from '../pages/users/permissions'
      import RolesPerms from '../pages/users/rolePerms'
      import UserRoles from '../pages/users/userRoles'
      import GroupRoles from '../pages/users/groupRoles'
      
      import Usage from '../pages/usage'
      import SecurityLogs from '../pages/usage_security_logs/securityLog'
      import ReportUsage from '../pages/usage_security_logs/reportUsage'
      import SessionLogs from '../pages/usage_security_logs/sessionLog'
      
      import Reports from '../pages/reports'
      import Schedule from '../pages/manageSchedule/schedule'
      import Manage from '../pages/manageSchedule/manage'
      
      import Server from '../pages/server'
      
      const routes = [
        {
          path: '/',
          component: Default,
          children: [
            { path: '', component: Index },
            {
              path: 'userReports',
              component: UserReports,
              meta: { requiresAuth: true },
              children: [
                {
                  path: '',
                  redirect: 'reports/my',
                  meta: { requiresAuth: true }
                },
                {
                  path: 'recentReports',
                  component: RecentReports,
                  meta: { requiresAuth: true }
                },
                {
                  path: 'departmentReports',
                  component: DepartmentReports,
                  meta: { requiresAuth: true }
                },
                {
                  path: 'facilityReports',
                  component: FacilityReports,
                  meta: { requiresAuth: true }
                },
                {
                  path: 'reports/:mode',
                  component: AllReports,
                  meta: { requiresAuth: true }
                }
              ]
            },
            {
              path: 'throwAway',
              component: ThrowAway
            },
            {
              path: 'kinetics',
              component: Kinetics,
              meta: { requiresAuth: true }
            },
            {
              path: 'pharmAssist',
              component: PharmAssist,
              meta: { requiresAuth: true }
            },
            {
              path: 'cqa',
              component: CQA,
              meta: { requiresAuth: true }
            },
            {
              path: 'training',
              component: Training,
              meta: { requiresAuth: true }
            },
            {
              path: 'users',
              component: UsersAdmin,
              meta: { requiresAuth: true },
              children: [
                {
                  path: '',
                  redirect: 'userRoles',
                  meta: { requiresAuth: true }
                },
                {
                  path: 'userRoles',
                  component: UserRoles,
                  meta: { requiresAuth: true }
                },
                {
                  path: 'groupRoles',
                  component: GroupRoles,
                  meta: { requiresAuth: true }
                },
                {
                  path: 'roles',
                  component: Roles,
                  meta: { requiresAuth: true }
                },
                {
                  path: 'permissions',
                  component: Permissions,
                  meta: { requiresAuth: true }
                },
                {
                  path: 'rolePerms',
                  component: RolesPerms,
                  meta: { requiresAuth: true }
                }
              ]
            },
            {
              path: 'usage',
              component: Usage,
              meta: { requiresAuth: true },
              children: [
                {
                  path: '',
                  redirect: 'reportUsage',
                  meta: { requiresAuth: true }
                },
                {
                  path: 'reportUsage',
                  component: ReportUsage,
                  meta: { requiresAuth: true }
                },
                {
                  path: 'securityLog',
                  component: SecurityLogs,
                  meta: { requiresAuth: true }
                },
                {
                  path: 'sessionLog',
                  component: SessionLogs,
                  meta: { requiresAuth: true }
                }
              ]
            },
            {
              path: 'reports',
              component: Reports,
              meta: { requiresAuth: true },
              children: [
                {
                  path: '',
                  redirect: 'schedule',
                  meta: { requiresAuth: true }
                },
                {
                  path: 'schedule',
                  component: Schedule,
                  meta: { requiresAuth: true }
                },
                {
                  path: 'manage',
                  component: Manage,
                  meta: { requiresAuth: true }
                }
              ]
            },
            { path: 'server', component: Server, meta: { requiresAuth: true } }
          ]
        }
      ]
      
      // Always leave this as last one
      if (process.env.MODE !== 'ssr') {
        routes.push({
          path: '*',
          component: () => import('pages/Error404.vue')
        })
      }
      
      export default routes
      

      And that is it. Navigation is intercepted when a user clicks a link and evaluated before the page changes.

      posted in Help
      R
      rconstantine
    • RE: Need advice :-) Angular or Vue and Quasar

      It has been almost two years since I chose Vue, but I had started with Angular and quickly abandoned it in favor of Vue. I had looked at React at the time, but I don’t recall what I didn’t like about it.

      Since then, I have developed a very large enterprise web application for my (also) very large hospital system’s pharmacy and nursing departments consisting of a Quasar frontend and ExpressJS backend connected to Active Directory and SQL Server.

      There are a number of things that have made me glad I chose this framework, but the top ones are:

      1. such excellent documentation
      2. responsive (for the most part) forums and discord
      3. continuing development which, for the most part, doesn’t break my existing code
      4. very few bugs - in fact, if there are any in the version I’m using at the moment, they aren’t exposed in my application

      I very much like the VUE file format with the HTML template on top, the JS in the middle, and CSS on the bottom. Everything I need is right there except where I bring in multiple other components which have their own VUE files. Keeping the file structure sane and understandable helps a lot, especially now that my program is 10s of thousands of lines of code.

      I use WebStorm as my IDE and with the app extension (https://www.npmjs.com/package/quasar-app-extension-ide-helper) it sometimes feels like the IDE reads my mind.

      That’s my opinion on the matter.

      posted in Help
      R
      rconstantine
    • RE: Maximize modals on mobile, minimize on desktop

      If memory serves, you can detect the platform you’re on (ah, yes: https://quasar-framework.org/components/platform-detection.html).

      You can then assign minimized and maximized booleans based on the result of checking platform. the properties would look like :minimized=“someDataElement” and you’d set someDataElement in the check.

      posted in Framework
      R
      rconstantine
    • Recommendation for generating documentation?

      I’ve used documentation generators ages ago, and my team would like to generate documentation for our Quasar projects. The only thing I’ve come across that looks like it might work is jsdoc plus the vue plugin. But it doesn’t seem like that alone will work with Quasar due to the unique structure and features added.

      Do you guys have any recommendations? If you’ve used jsdoc, do you know how we could get it to properly document Quasar stuff?

      posted in Help
      R
      rconstantine
    • RE: Where to post bugs? Here or?

      Thanks. Found my answer: https://github.com/quasarframework/quasar/issues/1324

      Didn’t realize slot=“title” was required, nor am I still sure what it is doing, but that did the trick.

      posted in Framework
      R
      rconstantine
    • RE: [Ignore] accessing 'this' in async response - possibly Vue question, not Quasar

      As happens every time I make a post, just about, I figured it out on my own. It’s like magic, so I should just make posts sooner!

      Anyway, although myThis wouldn’t print to the log, it was working. I printed myThis.$refs and it worked. And so did printing myThis.$refs.reportFrame. So I used that to change the src and it was just fine. I still don’t get why frame1 and frame2 didn’t work though.

      posted in Help
      R
      rconstantine
    • RE: [Solved] How to use CASL with Quasar

      Note that the abilities file must also be used on the server side if you’re going to manage the permissions in your own application. Instead of @casl/vue, you will use

      import { Ability } from '@casl/ability'
      

      That would be a much bigger post.

      posted in Help
      R
      rconstantine
    • RE: [Solved] How to use CASL with Quasar

      You can also use $can checks in your Vue files.

      One of my Computed fields is defined like this:

      hideChecks () {
        return !(this.$can('edit', 'RolesPerms'))
      },
      

      The computed field is useful if you have several elements that need to be hidden and they aren’t in a shared DIV.

      Similarly, you can use them in your templates too like this:

      <q-route-tab
        v-if="$can('view','User')"
        to="userRoles"
        :count="numUsers"
        name="tab-user-roles"
        exact
      >...
      

      The q-route-tab won’t be shown without the right permissions.

      posted in Help
      R
      rconstantine
    • RE: [Solved] How to use CASL with Quasar

      To give you a quick thumbnail idea of what else I’m doing with permissions/abilities, I’ll just say that I have permissions/abilities defined in a file (mentioned above). That file is copied to both the server app and front-end app. I have DB tables for role-perms (roles linked to permissions), roles, users and user-roles (users linked to roles). I have an administration section of my front-end, with corresponding server-side functionality, to setup all the linkages between users, roles, and abilities.

      When a user logs in, I lookup all of their permissions via the linkages just mentioned. I then save their ability tree to my Vuex store for easy reference. I also save it to the user’s session. I can’t recall which one is used by the $can operation.

      posted in Help
      R
      rconstantine

    Latest posts made by rconstantine

    • RE: [solved] how to access $ variables from a mutation file?

      Kept looking and found the answer here: https://forum.quasar-framework.org/topic/7148/unable-to-use-this-axios-in-vuex-ts2339-property-axios-does-not-exist-on-type-store/11

      I needed to a) make a function, and not use fat arrow, and b) use this._vm.$can(‘view’, loopVar)

      posted in Help
      R
      rconstantine
    • [solved] how to access $ variables from a mutation file?

      Hey guys, I have quite a number of items I have added to my boot folder including axios, casl, lodash, vuelidate, and others, which I access via $ in my vue files.

      I think I’m missing something basic, so forgive me. How do I use these items from a mutation.js file, found in my store? In my vue files, I simply do something like:

      this.$axios...
      

      or

      this.$can('view', 'something')
      

      I have tried this._vm.$can(…) but that didn’t work (nor did it throw errors).

      I also didn’t know what to call this, so my search through the forums was unsuccessful. If this has been asked and answered, just let me know the search terms I should have used.

      Thanks!

      posted in Help
      R
      rconstantine
    • RE: [solved] I changed a prop type but getting mismatch error

      Holy crap! The issue was my IDE stopped saving my changes!!! I closed it at the end of the work day yesterday and opened it today and all my changes are gone! I started making them over again, including the above, and it works just fine. Never had that happen before with WebStorm. Weird.

      posted in Help
      R
      rconstantine
    • RE: [solved] I changed a prop type but getting mismatch error

      I suppose the question is: why does it say it expected a string? My definition says number.

      posted in Help
      R
      rconstantine
    • RE: [solved] I changed a prop type but getting mismatch error

      I have others in the same component definition that are working like this pair:

      patAge: {
            type: Number
          },
      
      :pat-age="selectedPat.age"
      
      posted in Help
      R
      rconstantine
    • RE: [solved] I changed a prop type but getting mismatch error

      I think I’m doing that:

      :crcl="estCrCl"
      

      Where estCrCl = 57.3325 as a number.

      posted in Help
      R
      rconstantine
    • [solved] I changed a prop type but getting mismatch error

      Hey folks, I have a PWA and on one ‘page’ a component where I was previously passing in a string, but changed it to pass in a number. I updated the props definition in the component after I realized I forgot to, but I still get an error saying:
      “Invalid prop: type check failed for prop “crcl”. Expected String with value “57.3325”, got Number with value 57.3325.”

      The prop says:
          crcl: {
            type: Number
          },
      

      Am I forgetting something else too?

      OR— is my dev version not compiling correctly? Where do compiled dev files live, anyway?

      posted in Help
      R
      rconstantine
    • RE: Is it possible to do a CodePen with axios hitting a DB?

      @dobbel Thanks for the reactivity note. I found this: https://stackoverflow.com/questions/59107201/vue-component-not-immediately-updating-after-associated-data-changes

      And I’m currently testing my changes. I think I mentioned that the weird thing was that SOMETIMES reactivity was working, but other times it wasn’t. I’m hoping using getters will make them always work. Note that with always refreshing the data, I was never actually updating sub-items of an array. Instead, I was replacing the whole array.

      In any case, I do think you’re right about it being a reactivity issue, so that is where I’m focused now. Thanks for the reply.

      posted in Help
      R
      rconstantine
    • Is it possible to do a CodePen with axios hitting a DB?

      Hi,

      I’ve been having issues with q-table not updating after Vuex store data is refreshed. My code is too complicated to simply copy to a codepen, but I figure I at least need to use the same components in order to duplicate the issue. My DB is behind a corporate firewall, and I don’t have any online DBs I can use. Does anyone have suggestions for how I can create a representative duplicate of my code?

      What I see is this:

      1. I have a table with a column which includes action buttons (copy row, up vote, and down vote).
      2. My data has that nifty popup in place editor so I can edit the data.
      3. I do not update the Vuex data store directly before sending to the DB. Instead, I send only the change to the DB, then reload all of the data and store it again in the Vuex store. When this happens, the table does not always reflect the change!
      4. Likewise when I use any of the action buttons - no updates reflected.
      5. In both cases, changes are not reflected, but the data IS pushed to the DB and the request for new data is accomplished according to the log of my backend and the DB itself.

      If I log out of my application, then hit CTRL+F5 to hard refresh, then login, everything works like it is supposed to. That says to me there is a memory issue. I use Chrome, latest version, have had this problem for several versions.

      Alternatively, is there a way to programmatically do the same thing as a CTRL+F5?

      posted in Help
      R
      rconstantine
    • RE: Vertical toggles?

      @metalsadman Thanks for pointing me to ‘rotate’. Never used that before, or had a need to. That worked. It made the horizontal space usage less, which was needed in my case.

      As for other frameworks, I can’t find what I was thinking of, but even if I did, I could be remembering wrong. I think it was something I found while looking at VueJS’s awesome page.

      posted in Help
      R
      rconstantine