Vue.js fellow developers,
I have a data set I'm storing in a .js file (reason for using .js right now, is so I can preserve comments, etc).
My directory structure looks as such:
├── data
│ ├── cards
│ │ ├── en.json
│ │ ├── fr.json
Somewhere in my app, I import that data and then declare it in my data.
import Cards from '#/data/cards/en.js';
data() {
return {
cards: Cards, // < Imported.
...
}
}
What I'm trying to accomplish, is import that locale file based off of a language property set in my .env file VUE_APP_LANG=en.
What's the best way to go about this? Although I know it's not possible, it would be nice to import using string literals (e.g. import Cards from `#/data/cards/${curLang}.js`;).
I'm aware, that yes I can just group the two in a Cards.js file and have my array return an object, one for each locale but it's not as clean. My preference is to keep locale data each in it's own dedicated file.
Yes, I'm also aware of available plugins like vue-i18n, unfortunately it has shortcomings specific to my project needs.
Thanks in advance!
You can actually use dynamic imports
import(`#/data/cards/${curLang}.js`).then(module => {
// do something with the translations
});
Cant those lang files be json?
If those lang files have js functions in there, aside from data, then maybe its better to implement it as a factory function.
But if its just pure data, why not json?
Or if it really needs to be on js, why not just attach it globally or have it on vuex? So you dont need to import it.
And since you will be adding it to the current Vue component instance as data, probably just have it on vuex and add it as computed property.
You can alway check the current VUE_APP_LANG set on your .env using
process.env(VUE_APP_LANG)
try this:
const card = () => import "./Card"
Related
Recently, I've supporting a Laravel project that uses Laravel Mix as front-end asset bundler (CSS and JS). I've noticed that, in main JS file, the developer was used some require() rules at start, and those require() rules was referring to other JS files located in same directory, and those JS files are "normal" browser-friendly JS script (like jQuery, Bootstrap, GSAP etc). After those require() rules, the developer used normal import rules from ES6, referring to NodeJS dependencies and other modules within the project.
To better explain what I'm trying to show...
File tree:
.
└── src
├── bootstrap.js
├── components
│ ├── AnotherFeature.js
│ ├── Carousel.js
│ └── Something.js
├── jquery.js
└── main.js
The main.js file...
require("jquery");
require("bootstrap");
import {AnotherFeature} from "./components/AnotherFeature";
import {Carousel} from "./components/Carousel";
import {Something} from "./components/Something";
class Main {
constructor() {
new AnotherFeature();
new Carousel();
new Something();
}
}
new Main();
And, even using Laravel Mix (that uses Webpack), the jQuery, $ and bootstrap are available to run through browser`s console.
My question is: how can I do something like this outside Laravel Mix? I'm working on a project that uses JS, and some third-party resources aren`t ES6 modules, and I can't find a beautiful solution to use them in my project. The way Laravel Mix "import" extra JS files is a good way to fix my issues, keeping simple and all thing in one bundled JS file.
You can find my project at my GitHub. It consists in a Wordpress starter template that people want to use at my job, and this method of "requiring" and "importing" things will be very helpful.
Thanks!
I have a Node.js with Express.js app. I have folders like such:
src
/models
/router
/store
/api/v1
index.js
My router folder has a file called index.router.js which contains my app's routes e.g:
import UserAPI from '../api/v1/user.api.js'
expressRouter.post('/register', function async function (req, res) {
await UserAPI.registerUser({
EMail: req.body.EMail,
Password: req.body.Password,
Name: req.body.Name
});
The above is a route to an API so it went into my index.router.js file. To perform an API action on this endpoint, I created another file for API functionality called user.api.js and it would contain something like:
async function registerUser({EMail, Password, Name}) {
// Import method from model file and perform DB action
});
As the application has grown I have come to wonder whether I have made it too complex and created an unecessary layer with a separate file of user.api.js which could possibly be refactored to be part of index.router.js.
What I do not understand is what is the standard practice of file structuring an API for scalability and should the API endpoints be in a separate file/folder of api/v1, api/v2 or should they be part of the router?
One thing that could be advantagous of having separate api files is resuability. Because it only contains functions and no routing, the functions could be reused across many different router files.
You have basically re-invented the MVC design pattern.
It's not overly complicated. It is considered good practice and encouraged.
C
Traditionally, what you call Routers is usually called the Controller. The job of the controller is simply to handle routing, handle argument parsing (request body, query parameters, is user logged in etc.) and sometimes handle validation. This is exactly what Express is designed to do. And Express allows controller functionality like authentication and validation to be refactored into middlewares.
Note: Sometimes you will see tutorials on the internet where people separate controllers and routes. My personal recommendation is do not do this. Express routing has been designed to be perfect for writing controllers. About the ONLY reason to separate them is if you have two different URLs that do the exact same thing. In my opinion that is better handled by a redirect.
M
Traditionally what you call API is called the Model. The model is your traditional collection of objects or data structures that you learned to program with. The model is what performs the application logic. Normally classes or modules that implement models are not labeled with anything. For example a user model would not be called UserAPI or UserModel but simply called User. However, what you name things is just a convention. Stick with what makes sense to you.
V
The final part of MVC is the View. In Express the view is simply res.json() or res.render() with its associated HTML template. This part is 99% already written by Express developers - you just need to tell the view functions what to send to the front-end.
Your architecture is good
There are very good reasons for separating the model (API) from the controller (router). First is that it allows you to solve your problems without polluting your core logic with parameter parsing logic. Your models should not need to worry about weather the user is logged in or how data is passed to it.
Second is that it allows you to use your model (API) logic outside of Express. The most obvious use for this is unit testing. This allows you to unit test your core logic without the web parts of the code. I also usually write utility scripts that I can use to do things like create a new user, dump user data, generate authentication token so I can use it with Postman etc.
For example you can create a script like:
#! /usr/bin/env node
// register-user.js
import UserAPI from '../api/v1/user.api.js'
UserAPI.registerUser({
EMail: process.argv[2],
Password: process.argv[4],
Name: process.argv[3]
})
.then(x => {console.log(x); process.exit()})
.catch(console.error);
Which you can then execute on the command line to create new users without needing to run the server:
$ ./register-user.js myemail#address.com 'My Name' 123456
It looks like your software is already structured according to MVC. Keep it that way. It will make maintaining and modifying the software a little easier.
API and router are two different things and even from different worlds.
Most of the applications are composed of two basic building blocks, UIs and APIs. UIs is supposed to be for humans and APIs for machines. You can have 0-n UIs and 0-n APIs. There is no rule for that.
For example UI you might have a webpage for common visitiors, application for paying visitors or those who bought your product and application for administrators. Those are three separate UIs. If one is malfunctioning, others are working.
The other example are APIs. There can be separate API for each of these UI. Also there can be APIs for third parties or other microservices of your own system. And again, if one API doesn't work, it doesn't impact others.
Router is the design pattern for URL control. Or a specific implementation of the design pattern if you wish.
While both UIs and APIs might require URL control, the same pattern can be used in both parts of the application system. But it doesn't mean it should be in the same folder. Things for building blocks should be in separate folders. Router, unlike the model, is not a common thing you would share among building blocks.
The folder structure you present here, Im sure you have seen it somewhere on the interent. But I do not consider it mature. I would consider something like that more mature:
├── model
├── website-ui
│ ├── routers
│ ├── templates
│ └── index.js // this is router
├── website-api
│ ├── user
│ │ └── index.js // this might be a router (API dependent
│ ├── item
│ │ └── index.js // this might be a router
│ └── index.js // this is router
├── index.js
└── main-router.js // this is router
Usually you don't even do main-router like that, because this responsibility goes often to load balancer outside of Nodejs. But everyone must start somewhere. This implementation is easy to upgrade later.
Do not confuse multiple APIs with API versions. In the best case scenario, you never want API versions, ever. You would proceed like this:
├── website-api
│ ├── user-old
│ │ └── index.js
│ ├── user
│ │ └── index.js
│ ├── item
│ │ └── index.js
│ ├── index-v1.js
│ └── index-v2.js
or that:
├── website-api-v1
│ ├── user
│ │ └── index.js
│ ├── item
│ │ └── index.js
│ └── index.js
├── website-api-v2
│ ├── user
│ │ └── index.js
│ ├── item
│ │ └── index.js
│ └── index.js
You can't tell now. And you shouldn't care. When you do changes in API, you do them backward compatible. If you can't do that anymore, it means you have done some critical mistakes in the past or large business changes comes in. This is not predictable.
One thing that could be advantagous of having separate api files is resuability.
Yes, but you can't tell now.
Regarding to your other questions. Stick to the SOLID rinciples. Each part of the code should have one specific purpose.
When I see your router folder, I have no idea what is there. Well I know routers, but routers of what? There can be everything, yet nothing. Thus not easily extensible = bad.
When I look at my structure, I can more easily predict what is in there.
You should design your architecture regarding to the purpose and not the specific implementations. Lets say you have two APIs because you have two purposes. Are they both REST or GraphQL? Can I share code and remove duplicities? Not that important. Sharing code is actually very dangerous if not done properly. Shared code is the worst part to refactor and it often doesn't provide as many advantages.
... I have come to wonder whether I have made it too complex...
Depends, is it a 14 days project? Yes you did. Is it for a year or more? You should go even deeper.
I really like the folder structure as can be seen here when dealing with a React frontend and a some backend with express:
root
├── backend
| ├── node_modules
| ├── public
| ├── src
│ │ └── Server.ts
| ├── package.json
| └── tsconfig.json
├── frontend (created using create-react-app)
| ├── node_modules
| ├── public
| ├── src
│ │ └── Index.js
| ├── package.json
| └── tsconfig.json
I think that having separate packages with individual node_modules is reasonable since the frontend and backend are basically completely different things, e. g. they need different node modules. Also, this modular approach is visually appealing to me and the repository looks tidy.
However, I encounter a problem with this structure when I need to share content between the frontend and the backend. I added a shared folder under the root-of-project which contains its own project with its own tsconfig.json, package.json and so on. This approach is a mix of the approaches here and here. For the backend, this works totally fine: having set up the tsconfig.json appropriately (using TypeScript Project References and aliased imports), I can reference the file root/shared/src/myFile.ts like this:
import { myFunction } from #shared/myFile;
I created the React frontend using create-react-app. It's ok for me that alias imports don't work, so I would have to use (inside the src folder in frontend):
import { myFunction } from '../../shared/src/myFile';
Sadly, these imports from outside the src directory are not supported by create-react-app and I don't want to use eject since I have no experience with webpack and don't want to maintain all the configuration files on my own (that's why I used create-react-app in the first place).
I know I can move the shared content to the frontend's src directory. But this would mean, I had to add the tags needed for using Project References in TypeScript, e. g. setting composite to true, in the frontend's tsconfig.json which seems odd to me and feels more like a hack. I'd like to have a separate npm project with my shared content.
Since create-react-app does not inherently support imports from outside the src directory, I thought that maybe I'm getting the big picture wrong. Isn't the folder structure I use right now a valid way of how to setup a React project with a backend? What mechanism does create-react-app provide to link files between the frontend and the backend? I could also think of having a root project with a src folder and inside of that the two folders backend and frontend. But this means, that we'd have one shared node_modules folder in root.
It's my first project with React and I'd love to get to know some best practicese for this kind of architectural problem. Some links to trustful resources where project structures for full-stack React development are explained would be really helpful. Thank you 😊
It's perfectly reasonable to want to share code between your front and back end. It's one of the reasons to code in javascript instead of Ruby or PHP.
You can accomplish exactly what you want by using yarn instead of npm and yarn workspaces: https://yarnpkg.com/lang/en/docs/workspaces/. At the top level you set up three modules/packages in your package.json (make sure you name the workspaces correctly in their respective package.json files):
"workspaces": {
"packages": [
"backend",
"frontend",
"shared"
]
},
Once you do, you can import shared code in your CRA app or your back end simply like this:
import { myFunction } from 'shared/src/myFile';
The drawback is that you can't import react components from the shared directory into frontend as long as you are using CRA. This won't affect you now since you only have one react app. Should you need to share react components among multiple projects, look into some on the suggestions above like bit.dev.
ADDENDUM!!! It's now possible to use CRA and yarn workspaces to share React code, if you replace CRA with CRACO. All you do is create another workspace with the shared react code. Then create a symbolic link in each module where you want to access it:
root
├── fontend-one
| ├── symbolic link to frontend-shared
├── frontend-two
| ├── symbolic link to frontend-shared
├── frontend-shared
Each of the CRA front end modules modules also requires a craco.config.js file where you tell web-pack not to follow symbolic links:
module.exports = {
// ...
webpack: {
configure: {
resolve: {
symlinks: false
}
}
}
};
You import from shared-frontend normally:
import { myFunction } from 'shared-frontend/src/myFile';
It's a pretty lo-fi solution but has proven robust for the year we've been using it.
Architecture is a tricky one, everyone has a different opinion and every option has pro and cons.
Personally I believe its best to separate the backend and frontend into sperate projects and keep them that way. Now as JavaScript/React/Node encourage component-based approaches a really nice way of sharing code between them is Bit.dev.
https://bit.dev
I am currently using it to share components and functions between three web apps and a few Node microservices.
A good structure for React app can be found here, this one works well and scales nicely:
https://hackernoon.com/fractal-a-react-app-structure-for-infinite-scale-4dab943092af
As for Express, there are so many ways to structure the project but personally recommend a folder for your Routes, a folder for your Controllers, this is where the logic for Routes live. Then go from there. Check this link out:
https://www.freecodecamp.org/news/how-to-write-a-production-ready-node-and-express-app-f214f0b17d8c/
Depending on what your building you may not even need a full backend you can check out the JAMStack here for more info:
https://jamstack.org
I would consider separating them though as the project scales it makes it much easier to manage. You can release your front end on something like Netlify and then use something like AWS or Azure to host your Node/Express server.
Having separate sub-projects for frontend and backend makes perfect sense due to vastly different dependencies. Mixing it up increases disk space consumption in production deployments and goes against security guidelines. Your folder structure is reasonable (except for public/ subfolders I'm unsure about, maybe I'm missing something).
The approach import { myFunction } from #shared/myFile; is fine. Just don't use CRA.
For a project with a single frontend and a single backend there is no need for a shared\ top-level folder because the frontend is the only source of 'UI truth' (e.g. the source of types and components related to UI) that is consumed by the frontend and the backend is the only source of 'API truth' consumed by both frontend and backend. With this arrangement only #backend/api_shared_stuff needs to be shared.
Some links to trustful resources where project structures for full-stack React development are explained would be really helpful. On the one hand, usually project authors have to explain plenty of other things and on the other hand keep the documentation (typically a README) reasonably concise. You may find that providing a justification/explanation why the subdirectory structure is this and not that was not the top priority.
So at work we bought a theme that I have to customize and use it for an internal project. They use pug to generate the final files from some templates. In the .pug templates I can insert, of course, some external javascript files.
An empty js example for a file from them looks like this:
(function(window, document, $) {
'use strict';
$(window).on('load', function () {
})
})(window, document, jQuery);
(jQuery and bootstrap are automatically included in the templates).
I sucessfully created my javascript files and used them to work on the project using only pure js and some jQuery (however I kind of missed angularjs, but I wasn't able to use .pug and angular in the same time).
I came to the point where I had to make some multiple GET request to a server and I need them to be asynchronous. In my previous projects I successfully used the async module and I wanted to use that here too. Surprise, surprise, I can not import the module.
I tried with a simple line like import * as async from '../libs/async/async.js'; but if I want to use a simple async.series, it says: Uncaught TypeError: async.series is not a function. I tried in every way possible. The errors were varying from the one above to "Unrecognized token *". And yes, I declared the .js file as type="module".
I tried to simulate the problem in by myself and I created the following files structure (it resembles the one in the theme).
.
├── assets
│ └── js
│ ├── dashboards
│ │ └── dash.js
│ └── libs
│ └── async.js
└── src
└── index.html
In the .html file, I only include jQuery and the dash.js file. The dash.js file contains the following code:
'use strict';
import * as async from '../libs/async.js';
$(window).on('load', function () {
function init() {
console.log("Hello there!");
async.series([
function(callback) { console.log(5); callback(); },
function(callback) { console.log(6); callback(); }
]);
}
init();
});
But...errors again.
Uncaught ReferenceError: async is not defined
at init (dash.js:8)
How on earth can I import the async module (or any other module) and use it correctly?
UPDATE:
In the .html file, If I include the line
<script src="https://cdnjs.cloudflare.com/ajax/libs/async/2.6.1/async.js"></script>
it seems it works, but I really want to know if this can be done without it.
In the .pug file, changing the order of the .js files helped. I put async.js
script(src=assets_path + '/js/libs/async.min.js', type='text/javascript')
as the first line for the custom js files and it worked. No other imports were needed.
I've recently started on some social media web site using Django. Im using the default django template engine to fill my pages. But at this moment, I want to add javascript to make the site more dynamic. This means:
The header and footer is the same on each page. The header should have a dropdown menu, a search form that searches when you're typing.
My current django application has a base template that has the header and footer HTML, since every page should have this.
The site consists of multiple pages, think of index page, profile page, register page. Each of these pages have some common but also a lot of different dynamic components. For example the register page should have form validation on the fly, but the profile page doesn't need this. The profile page should have a status feed with infinite scrolling.
I want to use Vue to deal with the dynamic components, but I don't know how I should get started. The application should not be a SPA.
How should I structure the Vue code?
How should I bundle this. Using Gulp? Or maybe django-webpack-loader?
I should still be able to use the Django template tags, for example I want to be able to use {% url 'index' %} in the dropdown menu.
This looks like an opinion based question for which there is no clear answer.
You mentioned that you do not want the app to be a Single Page Application (SPA). If so, what is the motivation for using Vue? To handle user interactions within page?
Vue can work perfectly alright in non-SPA context. It will help you handle rich interactions within the page, like binding your data to drop-downs, forms, etc. But the real power of Vue comes out when you use it in SPA context.
For your case, I would recommend using Vue.js in standalone mode, where you can quickly define template within Vue components and write all your code easily in one javascript file.
Here is what you need: https://vuejs.org/guide/installation.html#Standalone
In "Vue.js standalone mode", There is no need for any webpack build system or vue-cli. You can build the app directly in your existing dev environment for django. gulp can optionally minify and bundle your javascript files normally, just like you do with your jQuery based apps.
Vue.js uses double curly braces {{..}} for templates, so it will not interfere with your django template strings.
All the jsFiddle examples for Vue.js run in standalone mode. That is precisely what you need at this moment. You may look at some of the recent questions with vue.js tag, find a sample jsFiddle and see how it is done.
For complex SPA apps, you need to build your Vue code separately from server side, test it thoroughly with dummy AJAX calls, build it for production and then drop the final production build into your server for end-to-end testing. This is something you can do in future.
I looked at this question and others a while back while looking to do pretty much what the OP was asking. Unfortunately, most of the information on Vue is given in an SPA context. However, as Evan You has often repeated, Vue is not opinionated and does not require usage within an SPA.
I'd like to share some of my findings and sketch out a possible approach to making Django and Vue work together. While an SPA is not required
I think the real power of Vue comes from its Components and that kinda pushes you towards a Webpack or similar approach, not simple standalone
mode in the html.
Also, just to be very clear: 90%+ of my the contents Django view code and templates remained exactly the same as before. Django doesn't particularly care that it's using webpack_loader and render_bundle. And even less that render_bundle has something to do with Vue. Between Django template blocks, verbatim tags and Vue slots, you get a lot of flexibility that allows you to leave most of your existing content alone.
Last, once your app is stable, you can disable the hotreload server on port 3000, run npm run build-production and use collectstatic to have your JS served via nginx/apache like any normal static content. So node runs as the occasional batch rather than as a service. Takes a bit of fiddling with Django's config, but well within reason.
Apologies, this is quite sketchy, don't expect working code as I am stripping so much out. But hopefully it'll give you an idea.
mysite/__full12_vue.html:
This is my base Vue-ify Django template that extends my existing Django base template, __full12.html.
assume that __full12.html defines all the general Django blocks, like {% block content %} and the like
(actually, there is a very important div with ID bme-vue so I've added this template at the end as well. )
I've added a Vue Component to display user messages.
And redefined the menu template to use Vue + Bootstrap dropdowns.
{% extends "mysite/__full12.html" %}
<!-- KEY: use this to hook up to https://github.com/ezhome/django-webpack-loader -->
{% load render_bundle from webpack_loader %}
{% block nav %}
<!-- uses Vue to setup Bootstrap dropdown menus -->
{% include "mysite/menu_vue.html" %}
{% endblock nav %}
{% block user_msg %}
<div class="row">
<div class="col-6">
<!-- uses Vue to display user messages -->
<bme-user-messages>
<div>THIS SHOULDNT APPEAR ON SCREEN IF VUE WORKED</div>
</bme-user-messages>
</div>
</div>
{% endblock user_msg %}
{%block extra_js_body_end%}
<!-- KEY this points Django Webpack loader to appropriate Webpack entry point -->
{% render_bundle bundle_name %}
{%endblock extra_js_body_end%}
webpack.config.development.js:
This is where you tell Webpack which JS to serve for the bundle_name you specify in the view.
How to configure Webpack is out of scope of my post, but I can assure you it was a real PIA. I started out with pip django-webpack-loader, then https://github.com/NdagiStanley/vue-django then Bootstrap 4 stuff. However, the end result is way more powerful than standalone, in my opinion.
/*
webpack config dev for retest
*/
config.entry = {
"main" : [
'webpack-dev-server/client?http://localhost:3000','../../static_src/js/index'],
// ....stuff..
//KEY: ONE entrypoint for EACH bundlename that you use.
"mydjangoappname/some_django_view" : ["../../static_src/js/mydjangoappname/some_django_view"],
"mydjangoappname/another_django_view" : ["../../static_src/js/mydjangoappname/another_django_view"],
// ....stuff..
}
// ....stuff left out...
mydjangoappname/some_django_template.html
Finally, we are ready to display some actual contents:
bme-nav-item and bme-tab-pane are 2 custom Vue components I use for Boostrap 4 tab navs and contents.
Django uses var settings= some-json-object to communicate instance-specific, rather than page-generic, data to Vue and JS
{% extends "mysite/__full12_vue.html" %}
<script>
// KEY: settings is provided by json.dumps(some_settings_dictionary)
// which your views puts into your RequestContext.
// this is how Django tells Vue what changes with different data, on the same view
var settings = {{settings | safe}};
</script>
{% block content %}
<!-- a button to run a Celery batch via a post command, url should probably come
from Django url reverse and be put into a Vue property...
-->
<button v-bind:data-url="url_batch" type="button" class="btn btn-block btn-outline-primary" #click.prevent="run_batch">
<!-- lotsa stuff left out.... -->
<ul id="tab-contents-nav" class="nav nav-tabs nav-pills">
<!-- *label* is using a Vue Prop and because there is no {% verbatim %} guard around it, Django will
inject the contents. {% urlrev xxx %} could be used to write to an 'url' prop. Ditto the conditional
inclusion, by Django, of a template if it's in the RequestContext.
-->
{% if templatename_details %}
<bme-nav-item link="details"
label="{{details_label}}" >
</bme-nav-item>
{% endif %}
<!-- lotsa stuff left out.... -->
<bme-tab-pane link="details">
<div slot="content">
<!-- KEY: Vue slots are incredibly powerful with Django. Basically this is saying
to Django : inject what you want in the slot, using your include, I'll tidy up afterwards.
In my case, this is a Bootstrap NavItem + NavTab
-->
{% if templatename_details %}
{% include templatename_details %}
{% else %}
<span class="text-warning">SHOULDNT APPEAR IF VUE WORKED </span>
{% endif %}
</div>
</bme-tab-pane>
{% endblock content %}
mydjangoappname/some_django_view.js:
import Vue from 'vue';
import Vuex from 'vuex';
//now Vue is using Vuex, which injects $store centralized state variables as needed
Vue.use(Vuex);
//KEY: re-using components defined once.
import {base_messages, base_components} from '../mysite/commonbase.js';
var local_components = {
//nothing, but I could have imported some other components to mix-n-match
//in any case, bme-nav-item, bme-tab-pane and bme-user-messages need to
//coming from somewhere for this page!
};
const components = Object.assign({}, base_components, local_components);
//we're going to put together a Vue on the fly...
export function dovue(config) {
//KEY: The store is a Vuex object - don't be fooled, it's not SPA-only
// it's the easiest way to coherently share data across Vue Components, so use it.
store.commit('initialize', config);
//here I am telling the store which html IDs need hiding
var li_tohide = settings.li_tohide || [];
li_tohide.forEach(function(hidden) {
store.commit('add_hidden', hidden);
});
/* eslint-disable no-new */
var vm = new Vue({
//KEY: This tells the Vue instance what parts of your html are in its scope.
el: '#bme-vue'
//KEY: each bme-xxx and bme-yyy special tag needs to be found in components below
//otherwise you'll see my SHOULDNT APPEAR IF VUE WORKED text in your page
,components: components
,data: {
li_rowcount: window.settings.li_rowcount || []
,csrf_token: window.csrf_token
,url_batch: "some url"
}
,mounted: function () {
// a Vue lifecycle hook. You could use to set up Vue Event listening for example
console.log("data.js.lifecycle.mounted");
}
,methods : {
,run_batch: function(e) {
//hook this up to a button
console.assert(this.$data, COMPONENTNAME + ".run_batch.this.$data missing. check object types");
var url = e.target.dataset.url
//this is defined elsewhere
post_url_message(this, url, this.csrf_token);
}
}
//the Vuex instance to use for this Vue.
,store: store
});
//did Django provide any user messages that need displaying?
var li_user_message = config.li_user_message || [];
li_user_message.forEach(function(user_message, i) {
//the bme-user-messages Component? It's listening for this event
//and will display Bootstrap Alerts for them.
vm.$emit(base_messages.EV_USER_MESSAGE, user_message);
});
return vm;
}
//various app and page specific settings...
import {app_config, LOCALNAV_LINK, TOPNAV_LINK_OTHERS} from "./constants";
var page_config = {
//used to show which navigation items are .active
localnav_link : LOCALNAV_LINK.data
, topnav_link: TOPNAV_LINK_OTHERS.system_edit_currentdb
};
//this is coming from Django's RequestContext.
var generated_config = window.settings;
//ok, now we are merging together a Django app level config, the page config and finally
//what the Django view has put into settings. This will be passed to the Vuex store
//individual Vue Components will then grab what they need from their $store attribute
//which will point to that Vuex instance.
var local_config = Object.assign({}, app_config, page_config, generated_config);
var vm = dovue(local_config);
vuex/generic.js:
And a naive, mostly read-only store implementation:
//you can add your page's extra state, but this is a shared baseline
//for the site
const state = {
active_tab: ""
,topnav_link: ""
,localnav_link: ""
,li_user_message: []
,s_visible_tabid: new Set()
,urls: {}
};
const mutations = {
//this is what your page-specific JS is putting into the state.
initialize(state, config){
//initialize the store to a given configuration
//KEY: attributes that did not exist on the state in the first place wont be reactive.
// console.log("store.initialize");
Object.assign(state, config);
},
//a sample mutation
set_active_tab(state, tabid){
//which bme-tab-nav is active?
if (! state.s_visible_tab.has(tabid)){
return;
}
state.active_tab = tabid;
},
};
export {state as generic_state, mutations};
and to give you an idea of the general file hierarchy:
.
./manage.py
./package.json //keep this under version control
./
├── mydjangoappname
│ ├── migrations
│ └── static
│ └── mydjangoappname
├── node_modules
├ //lots of JavaScript packages here, deposited/managed via npm && package.json
├── static
│ └── js
├── static_src
│ ├── assets
│ ├── bundles
│ │ // this is where django-webpack-loader's default config deposits generated bundles...
│ │ // probably belonged somewhere else than under static_src ...
│ │ ├── mydjangoappname
│ ├── components
│ │ ├── mydjangoappname
│ ├── css
│ ├── js
│ │ ├── mydjangoappname
│ │ └── mysite
│ └── vuex
│ ├── mydjangoappname
├── staticfiles
│ // for Production, collectstatic should grab django-webpack-loader's bundles, I think...
├── templates
│ ├── admin
│ │ └── pssystem
│ ├── mydjangoappname
│ └── mysite
└── mysite
├── config
├ // where you configure webpack and the entry points.
├ webpack.config.development.js
├── sql
│ └── sysdata
├── static
│ └── mysite
└── templatetags
OK, I did have to modify the site's base template to make sure that div#bme-vue is always available.
Probably a bit of refactoring needed between this and mysite/__full12_vue.html.
mysite/__full12.html:
<!-- lots of stuff left out -->
<body>
<!-- KEY: the #bme-vue wrapper/css selector tells Vue what's in scope.
it needs to span as much of the <body> as possible, but
also end right BEFORE the render_bundle tag. I set that css
selector in mydjangoappname/some_django_view.js and I'd want to
use the same selector everywhere throughout my app.
-->
<div id="bme-vue">
<!-- anything that ends up here
, including through multiple nested/overridden Django content blocks
, can be processed by Vue
, but only when have Vue-relevant markup
such as custom component tags or v-for directives.
-->
...more blocks...
{% block search %}
{% endblock search %}
<div id="main" role="main">
<div> <!-- class="container"> -->
{% block content %}
{% endblock %}
</div>
</div>
...more blocks...
</div> <!-- bme-vue -->
{%block extra_js_body_end%}
{%endblock extra_js_body_end%}
</body>
</html>
This is how I integrate Vue with a Django project:
The first approach is building separate Django and Vue apps. Django will be responsible for serving the API built using Django REST framework and Vue will consume these APIs using the Axios client or the browser's fetch API. You'll need to have two servers, both in development and production, one for Django(REST API) and the other for Vue (to serve static files).
The second approach is different the frontend and backend apps will be coupled. Basically you'll use Django to both serve the Vue frontend and to expose the REST API. So you'll need to integrate Vue and Webpack with Django, these are the steps that you can follow to do that
First generate your Django project then inside this project directory generate your Vue application using the Vue CLI
For Django project install django-webpack-loader with pip:
pip install django-webpack-loader
Next add the app to installed apps and configure it in settings.py by adding the following object
WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': '',
'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
}
}
Then add a Django template that will be used to mount the Vue application and will be served by Django
{ % load render_bundle from webpack_loader % }
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Django + Vue</title>
</head>
<body>
<div id="root">
This is where Vue will be mounted
</div>
{ % render_bundle 'app' % }
</body>
</html>
Then add an URL in urls.py to serve this template
from django.conf.urls import url
from django.contrib import admin
from django.views.generic import TemplateView
urlpatterns = [
url(r'^', TemplateView.as_view(template_name="main.html")),
]
If you start both the Django and Vue servers at this point you'll get a Django error saying the webpack-stats.json doesn't exist. So next you need to make your Vue application able to generate the stats file.
Go ahead and navigate inside your Vue app then install webpack-bundle-tracker
npm install webpack-bundle-tracker --save
Go to build/webpack.base.conf.js then add
let BundleTracker = require('webpack-bundle-tracker')
module.exports = {
// ...
plugins: [
new BundleTracker({filename: '../webpack-stats.json'}),
],
}
This add the BundleTracker plugin to Webpack and tell it to generate the webpack-stats.json file in the parent folder where Django files live.
Now if you re-run your Vue server the webpack-stats.json will be generated and Django will be able to consume it to find information about the Webpack bundles generated by Vue dev server.
You can find more information from this tutorial.