Allows Vue3 to communicate with Entity Framework Core data models via convention
npm install elevuetor-jsnpm install elevuetor-js
Connection object knows where and how to find the server endpoints. This also includes some token handling for JWT authentication. TODO There is work being done to make this smarter, allowing authentication to be abstracted/passed in
Connection, the Elevuetor Session object is a simple current-user-state interface for keeping track of... session. I don't know what else I can tell you about session. Properties added to this object are also stored in browser session and retrieved on page load.
_set(session)
session is an object of key value pairs you wish to store at the session level. A key called "token" will be used by Connection for the authorization header, and its presence determines if a user is "logged in"
_unset()
Session and "logs out" the user.
_isLoggedIn()
[EfVueEnum(typeof(RoleType))] //your flag enum (shows up in the config in the Elevuetor model properties)
[EfVuePropertyType("Flag")] //tell Elevuetor to use the flag type
public int Markings { get; set; } = 0; //whatever number property you're using to store the flag
`
- BitArray: TODO: reevaluate how this is used... essentially a list of bools on the front-end
- Guid: this class is mostly to mark a property as being a C# Guid, though otherwise it behaves as a normal string. The _out property simply returns the string value.
- Point: an object with lattidue and longitude properties. This will likely be extended in the future, but we've used (with a custom JSON formatter on the .Net end) it to cleanly transport Point class data between EFCore models and Vue... views... without needing futher intermediary view models and such with individual lat/long properties defined. _out simply returns an object with lattitude and longitude properties.
$3
You can add your own data types as needed. Put these in a directory named "data-types", adjacent to the directories for models, unums, and dtos output by the server.
In DotNet, decorate the property like this:
`
[EfVuePropertyType("SomeType")]
`
This will add an include for your custom data-type definition SomeType, looking for /path/to/your/data/data-type/some-type.js
Quick data access example:
`
`
> Wait, does this posts object have all of our data?
Nope, this example understands that you want to use a Database containing records for your PostModel and will wait patiently for you to access the data before it fetches it. PostModel is coming from a JS file created by the server that informs ElevuetorJS exactly what that data will look like without any API calls so far.
If this is the first time your application has attempted to access the PostModel records in Database, an object handling all interactive functionality for it is quietly created in the background and returned to you. None of the actual posts data has been acquired from the server yet. No calls to the API have been made at all so far.
posts is empty until you try to access deeper data or otherwise tell Database to go and get data. For example, if you know the PrimaryKey (Id) for the post you want to access, posts[id] will:
- Check the local _database[PostModel] object, the in-memory data collection, for the existance of this Id.
- If it does not exist, it will create an empty PostModel instance with this Id. This reference is returned immediately, allowing you to chain further down the data structure.
- Database will then add this Id to a queue that will use Connection to seamlessly ask for the data for your front-end-accessed records from the server.
- When the data is returned from the server, it is used to update the existing, corresponding reactive PostModel object, which in turn triggers Vue's UI updates.
So accessing posts[id].title will show an empty string in your vue template until it manages to get the data back from the server, at which point that beautiful Vue renderer will update the UI with the value. That's not ideal, so there are lots of helpers built in to make the user experience even better, such as a _loaded bool.
Setup
> npm install elevuetor
You'll have to have set up ElevuetorDotNet already for ElevuetorJs to do anything of importance. Otherwise, you won't have any model objects or API endpoints to work with.
Connection object looks for a VITE_ELEVUETOR_URL setting to know who to talk to, so add the path of your server API root in your .env files:
`
VITE_ELEVUETOR_URL=https://localhost:7081/
`
Database object
`
import { Database } from 'elevuetor';
`
This is the object used to access all local "tables". Access is handled by proxies, creating tables on the fly the first time a user attempts to access them.
---
Table object
"Tables" are local collections of your models, accessed via convention through the Database object.
A simple example
`
import { Database } from 'elevuetor';
import PostModel from '@/data/models/PostModel'; //wherever your model files from the server live
const posts = Database[PostModel.name];
`
posts is a table object full of handy methods and properties. Access a record by id like posts[id] and a proxy will return the appropriate Model object, requesting data directly from the server if it hasn't been loaded yet.
$3
#### Search and sort methods (apply to both Table and Indexer objects)
---
_equals(prop, spec, subset = false)
- prop (property) is a string of the dot-notation property path you want to match on. This could be a property of the current model, i.e. created, or it could be a property on a related model, i.e. categories.title. ElevuetorDotNet will know what to do with that path and setup the EF Core query appropriately.
- spec is the value you are searching for. For now, all matches are case-insensitive. This is a full-match.
- subset is optional, and takes an array of ids. If passed, the return will only include any of these ids that matched the query, rather than all ids returned by the API call.
Returns an indexer object, see below
---
_contains(prop, spec, subset = false)
- prop (property) is a string of the dot-notation property path you want to match on. This could be a property of the current model, i.e. created, or it could be a property on a related model, i.e. categories.title. ElevuetorDotNet will know what to do with that path and setup the EF Core query appropriately.
- spec is the value you are searching for. For now, all matches are case-insensitive. This is a partial match.
- subset is optional, and takes an array of ids. If passed, the return will only include any of these ids that matched the query, rather than all ids returned by the API call.
Returns an indexer object, see below
---
_startsWith NOT IMPLEMENTED
---
_endsWith NOT IMPLEMENTED
---
_orderBy(prop, direction, subset=false)
- prop (property) is a string of the dot-notation property path you want to order this model on. This could be a property of the current model, i.e. created, or it could be a property on a related model, i.e. categories.title. ElevuetorDotNet will know what to with that path and return a list of ids ordered by your desired property.
- direction (default 1) 1 = ascending, 2 = descending
- subset is optional, and takes an array of ids. If passed, the return will be an array of only these ids, but reordered, rather than all of the ordered ids for a given property.
Returns an indexer object, see below
Once this is loaded up, it doesn't need to be fetched again unless the data changes. Instead, it saves the ordered list of ids locally like an index. Subset ordering will then use an existing index to reorder the subset on the front end, avoiding extra calls all the way back to the DB just to sort. If the ascending or descending is asked for and the inverse is already indexed, Elevuetor will just invert it.
The less waiting for the server our UI needs to do, the better our UX can be.
---
#### Other Table methods
_all()
No parameters.
Returns a promise.
USE. SPARINGLY. This method will ask for the entire collection of available data from ElevuetorDotNet for this Model. This is very useful for small data collections that change enough to not make sense as an Enum, but are static and universal enough that you might want to preload them immediately.
---
_load(subset)
- subset an array of ids
Similar to _all() but not as dangerous, you can pass a list of ids in and load data for that subset of the model. Great for pre-loading sub-data of a many-to- relationship for a given record - just pass the ids list, i.e. Database[PostModel.name].load(myCurrentPost.commentsIds).
If subset is not passed, it will attempt to load all records for known ids that are not yet loaded in this table. It does not ask ElevuetorDotNet for the full list of ids before doing so.
---
_save(id/data)
- id/data can be the id or the actual record reference
Sends request to ElevuetorDotNet to add or update this record.
If data is passed with no Id, it's an add. If an Id is passed, or data is passed with an Id, it will attempted an update. Mostly though, it's easier to use the
_save method directly on the record itself. And don't loop - if you have many things to save at once, use _saveAll.
---
_add(id, data)
- id the id of the record you want to add. Must match the id type on the Model (int or guid)
- data an object with the values for the corresponding properties on the Model. Extra properties are ignored.
Adds records locally to the Database for this Model.
---
_saveAll(ids/references)
- ids an array of ids or actual model references (or even a mix).
Send the indicated records to ElevuetorDotNet to be saved.
If ids parameter is omitted, all modified (including added) records for this table will be gathered and sent up.
---
_remove(id/reference)
- id can be the id or the actual record reference
Removes the record from the local Database, but doesn't delete it from the server.
---
_delete(id/reference)
- id can be the id or the actual record reference
Sends request to ElevuetorDotNet to delete the record. Also removes it from the local Database.
---
_refresh(id/reference)
- id can be the id or the actual record reference
Explicitly asks for this record from ElevuetorDotNet, even if you already have it loaded
Returns a promise.
---
_refreshList()
No parameters.
Manually refresh the list of all ids from ElevuetorDotNet. See Table _list property.
$3
_keys
A Vue reactive array of keys that already exist locally for this Model. You probably want the _list property.
---
_list
A Vue reactive array of keys that... wait we already have that? This is the more useful version, but use with caution. The first time this is accessed, it will quietly kick off an API call to get the list of every accessible id for this Model, and return the same internal Vue reactive array that _keys does.
See _refreshList() table method for refreshing the values here.
!important _list is what you use to see what record ids you have without loading up the full data for said records. If you try to access the records in the Table through the individual Model objects (such as looping through the entire Table), the accessing of those Model objects will trigger their load from ElevuetorDotNet.
---
_array
A Vue reactive array of the available Model data. This is what you will pass to your template most often. New records are automatically pushed to it, giving you that awesome Vue reactivity downstream.
Dev story: this is accessed through a property instead of being the default return for Database[Model] because of some really terrible things that happen when mixing a vue reactive and the database proxies. Basically, it just keeps trying to access every property everywhere, and will systematically seek out every relationship in your database until the client has the entire available data store. This is literally infinitely worse if you have circular data references.
---
_length
A Vue ref to the number of ids currently in the Database[Model]. Most often used after _list, ensuring all current ids have been fetched from ElevuetorDotNet, regardless of if the records have been pulled down yet.
---
_loaded
A Vue ref to the current state of the Database[Model], which is true if all known record ids have been loaded. This is actually unlikely to be useful to you, you probably want the individual record's _loaded property.
---
_loader
Returns a promise object, created from Promise.All using every outstanding promise for this Model.
---
_promises
Returns a read-only array of every outstanding promise for this Model.
Indexer object
TODO This is only mildly tested and might be a bit wonky yet
The Indexer object is actually an extension of Array. It is used to hold index lists and can chain additional search and sort functions, which will pass the result of each step into the subset property of the next.
$3
_loader
A promise, which resolves when ElevuetorDotNet responds to a sort search or order request.
_loaded
A bool indicator of the status of _loader TODO don't rely on this property currently, buggy.
$3
_keys()
An array of all the ids in the Indexer object
_list()
A reactive array of all the ids in the
_array()
Model object
`
const post = Database[PostModel.name][postId];
`
Now that you have your model object, what do you do with it?
Any property defined by your exported C# Model from ElevuetorDotNet will be enumerable and accessible. Properties for related models are seamlessly accessed from the Database object automatically based on their Foreign Key, similar to how Entity Framework navigates between records. Just drill into your model object how you like and the data will be gathered for you.
There are a few extra properties and methods to help you out.
$3
_trigger()
No parameters, no return. Pokes the reactive model for sticky situations. I haven't had to use this since rebuilding this library, so I've forgotten why I needed it and it looks like I ~~am a genius~~ incidentally fixed the issue.
---
_populate(record)
Takes a naked object of property:values (such as that returned from ElevuetorDotNet) and populates each model property. This may be very different from values seen through direct proeprty getting and setting.
This does all the wiring for relationships and handles any data-type translations for those non-js-standard data types, such as guids, bit-arrays, flags, and points.
You shouldn't be using this directly very often, ElevuetorDotNet should be populating your data automatically.
---
_out()
Returns the current values for the record, converted back to formats ready to be shipped back up to C# through ElevuetorDotNet.
Again, you shouldn't be using this directly very often, mostly useful for debugging.
---
_save()
If you're editing a record, this will ask ElevuetorDotNet to update it.
If you're creating a new record, this will ask ElevuetorDotNet to save it. Do not set the id - if the id is set, it will try to update a record with that id. Let your RDB provide the primary keys for new records.
If this model is a data transfer object, it will bypass the local Database and hit the route specified in the model definition. See (ElevuetorDotNet DTO)[https://github.com/freer4/ef-vue-ElevuetorDotNet]
TODO: if there's a related DB model for the DTO, refresh populate the Database automagically.
---
_remove()
Removes this record from the local Database.
---
_delete()
Sends request to ElevuetorDotNet to delete this record. Also removes it from the local Database
---
_typeof(prop)
Returns the type of the passed property
$3
_loaded
Returns a bool if this record has been loaded. This is how Database identifies if a record needs fetched or if the local data is ready to be used.
You'll use this extensively with related objects to check if the related object is ready to use.
Example:
`
{{post.author.name}}
`
Author's log, stardate 184.32.8: TODO It would be nice to be able to not have to wrap these. You can technically get away with a single level, as the properties will all just be empty (null, etc). I think I have a way to accomplish this, just need to try it out.
---
_loader
Returns a promise that gets resolved once the model is loaded with data via _populate - which is what it does internally when fetching from ElevuetorDotNet. Useful for logic around records in your setup.
---
_fetching
Returns a bool, indicating if this record is in the process of fetching data from ElevuetorDotNet
---
_error
Returns a bool, indicating if there are any errors on this record.
TODO: Currently not syncing correctly with the _errors object, use instead:
`
Object.keys(record._errors).length
`
---
_errors
An object whose keys are any fields with a validation error. Each value is an array of error objects.
This is only updated when _validate method is called on the record.
TODO: need add error method to shortcut the complex _errors object structure (add key, array if not existing, add object of format). Maybe codify the error object specifically.
---
_modified
Returns a bool, indicating if this record has been changed locally since it was loaded. Fair warning: I haven't tested this much yet.
TODO: this has an issue where initial setting of FK arrays within populate is async thanks to the watch, so it triggers _modified
---
_values
These are vue ref (or shallowRef, when an enumerable data type) references to the values. You can use Vue's unref to get the raw value from these.
The one place this is particularly useful: watches. Rather than wraping values from properties in ref, get the _values of that property:
`
watch(posts._values.comments, () => { / Do something when posts.comments changes / });
`
Otherwise, using this a bunch probably means you're overcomplicating matters. Just access the properties directly and let the proxy do its thing.
---
_toReactive
Returns a reactive object of this model.
TODO I think this is vestigial, a holdover from a previous version, so do not use this. You should be able to reference the properties directly from the model object. Need to test my assumption.
Examples
Yeah examples would help.
Get Started
Create new vue project.
npm install elevuetor-js
Recomended: npm install vue-router
Create .env.local file in project root
Add VITE_ELEVUETOR_URL=https://localhost:7221/` line where url is the back-end Elevuetor server entry path