- Notifications
You must be signed in to change notification settings - Fork0
Full-Stack project using BackboneJS v1.3.3 for the Front-end and Flask v0.12.2 for the Back-end.
License
KawtharE/MyStoreApp
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Full-Stack project withBackboneJS v1.3.3 for the Front-end andFlask v0.12.2 for the Back-end.
Working onLarge Scale Projects need a good choice of technologies to work with, the main goal is to end up with arobust, maintainable and bug-free application that you or any other developer who have the source code can easily add new features to it in the future.
A good choice of technologies for your next big project start with thinking to work with one of the amazingorganizational frameworks or library since it really makes things a lot easier than you have ever imagined.BackboneJS is one of these frameworks that belong to theMV * Pattern Family and what is great about it, is the fact that you end-up doing things in your own way in contrast of other organizational framework likeAngularJS, that make you do everything in its way.
Now for real world project, the presence ofBack-end in the server side, make a lot of sense since we need it to communicate with theDatabase where all of the project data exist. For this project we have been working withFlask, the python micro-framework andSQLAlchemy, the ORM for a full SQL functionality. These two technologies have the advantage of being very flexible and simplify a lot of things in the backend.
For this project we have two models to work with along: Store model and Product model. So the main functionalities of this application will be essentially divided into two parts as shown next:
To make this project work on your machine make sure you have installed:Python 3.5, Flask 0.12.2 and SQLAlchemy
Python 3.5 is installed by default on Ubuntu and Mac, you can make sure by typing:
$ python3 --version
Still you need to installpip3, that we will be using to installFlask andSQLAlchemy:
$ sudo apt-get install -y python3-pip
Next:
$ pip3 install flask $ pip3 install SQLAlchemy
=> Now, since you have everything installed start by creating the database, then if you want you can filled it with same fake data, and once you start the project the application will be available in yourlocalhost:5000
$ python3 database_setup.py $ python3 fakedata.py $ python3 project.py
At the beginning of every project we should always think about --How will our final product look in the eyes of the final user?--, so we need to create a mock-up at the beginning of our development process, first for every page in our application and second for every pageURL.
-Home page ---URL: "/" => The Home page contain a form to create new store and a list of all stores in the Database.
-Store Home page ---URL: "/#stores/store_id/products/" => The Store Home page contain a list of all products of the store with the idstore_id.
-New Product page ---URL: "/#stores/store_id/new-product/" => The New Product page contain a form to add new product to the store with the idstore_id.
-Update Store's Details page ---URL: "/#stores/store_id/edit/" => The Update Store's Details page contain a form with the store's current details that will be edited it by the user and update it.
-Delete Store page ---URL: "/#stores/store_id/delete/" => The Delete Store page contain a confirmation form for the user request to delete the store with the idstore_id.
-Update Product's Details page ---URL: "/#stores/store_id/products/product_id/edit/" => The Update Product's Details page contain a form with the product's current details that will be edited by the user and updated.
-Delete Product page ---URL: "/#stores/store_id/products/product_id/delete/" => The Delete Product page contain a confirmation form for the user request to delete the product with the idproduct_id from the store with the idstore_id.
=> You might notice we actually don't have all that bunch of pages in our project, well that refer to to fact that we have written all those pages in one HTML page asScript Templates and we have display most of theme asModals by using theBackbone Marionette Technique.
This iteration include two key steps:
1- Preparing the project directory: since we will be working withBackboneJS andFlask we should know that Flask, for sure need a specific organization of the project folder structure however Backbone don't, but it is a good practice even if we were working only with Backbone to have a clear project folder structure and that what most developers do. Backing to the fact thatFlask do need a specific folder structure, now Flask by default knows thattemplates must be in thetemplate folder andCSS, JS and media files must be in thestatic folder, so we end-up with the following folder structure:
static |---css |---main.css |---img |---js |---collections |---models |---routers |---views |---app.js templates |---project.html database_setup.py project.py
2- Installing technologies:
$ pip3 install flask $ pip3 install SQLAlchemy
$ npm install jquery@3.3.1 $ npm install underscore $ npm install backbone $ npm install backbone-model-file-upload $ npm install backbone.modal $ npm install backbone.marionette $ npm install backbone.radio
Next we should import all of them at the bottom of theproject.html page.
The project essentially need to have twoModels:The Store Model andThe Product Model.
So we will be starting by creating these two models, their collections and their views files:
js |---collections |---product-collection.js |---store-collection.js |---models |---product-model.js |---store-model.js |---routers |---router.js |---views |---app-view.js |---product-view.js |---productList-view.js |---store-view.js |---storeForm-view.js |---storeList-view.js |---app.js
Now, each of these js file have the same structure:
var app = app || {}; (function(){ ... })();
var app = app || {} => test if the app have already been created otherwise it take a value {}.
Immediate Invoked Function Expression (IIFE) => where the code goes and immediately executed.
Note: a new attribute should be added to every model, since we are using thebackbone-model-file-upload library to upload images to the server, so the store and product model will look like this:
app.Store = Backbone.Model.extend({fileAttribute: 'attachment',default: {id: '',storeName: '',storeImgURL: '',storeOwnerName: '',storeDescription: ''}}); app.Product = Backbone.Model.extend({fileAttribute: 'attachment',default: {id: '',storeID: '',productName: '',productImgURL: '',productCategory: '',productPrice: '',productDescription: ''}});
The Collection of each model will be fetched from the server when instantiating the collection object of the model, so instead of passing an array of data we will be having anurl attribute which refer to theFlask API that take care, in the server side, of giving back all requested data of the specific model.
app.Stores = Backbone.Collection.extend({model: app.Store,url: '/stores/' });app.Products = Backbone.Collection.extend({model: app.Product,url: '',initialize: function(id){this.url = '/stores/'+id+'/products/';}});
The Views of each model have two view levels,a model view anda collection view, so by defining atemplate for the model we can create the second view level just by iterating over the collection and instantiating the model view for each item.
// Store's Model View app.StoreView = Backbone.View.extend({template: _.template($('#store-template').html()), model: app.Store,initialize: function(){this.render();},render: function(){this.$el.html(this.template({store: this.model})); $('#stores').prepend(this.$el);return this;} }); // Store's Collection Viewapp.StoreListView = Backbone.View.extend({el: '#stores',initialize: function(){this.listenTo(app.storesData, 'change', _.debounce(this.renderUpdate, 300));this.listenTo(app.storesData, 'destroy', _.debounce(this.renderUpdate, 300));this.render();},render: function(){var self = this;_.each(app.storesData.toJSON(), function(item){self.renderStore(item);});},renderStore: function(item){var store = new app.StoreView({model: item});},renderUpdate: function(){this.$el.empty();this.render();}});
Flask messions here can be resumed in two main tasks:
1- Communicate with the Client side to return the requested data or get the posted data in order to save it: This type of communication depend from the client side to make anAJAX call and pass in theFlask API URL in the parameter and the data to post. FromBackbone saving model data happen whenever we callmodel.save(null, [options]) where options can be the url of the API, the method..In our Flask API we need therequest module from flask to use in order to be able to get the data from the client.
1-1- Save New Store
@app.route('/new-store', methods=['POST']) def createNewStore(): if 'attachment' not in request.files: newStore = Store(store_name=request.form.get('storeName'), store_img_url='store.jpeg', store_description=request.form.get('storeDescription'), store_owner_name=request.form.get('storeOwnerName')) else: file = request.files['attachment'] if file.filename == 'store.png': newStore = Store(store_name=request.form.get('storeName'), store_img_url='store.jpeg', store_description=request.form.get('storeDescription'), store_owner_name=request.form.get('storeOwnerName')) elif file and allowed_file(file.filename): filename = secure_filename(file.filename) file.save(os.path.join(app.config['UPLOAD_FOLDER_STORES'], filename)) newStore = Store(store_name=request.form.get('storeName'), store_img_url=filename, store_description=request.form.get('storeDescription'), store_owner_name=request.form.get('storeOwnerName')) session.add(newStore) session.commit() return jsonify([newStore.to_json])
1-2- Return All Stores
@app.route('/stores/') def getAllStores(): stores = session.query(Store).all() return jsonify([store.to_json for store in stores])
Theto_json property, return the model data inJSON format.
2- Communicate with the Database to save or retrieve data: This type of communication require anORM technology to simplify things, so we will be using theSQLAlchemy. Now for every Backbone model we will be creating a python Class which will be tied to a table, then after creating a database engine (in our case it is an SQLite database) we will be using thesession instance to communicate with our database.
class Store(Base): __tablename__ = 'stores' id = Column(Integer, primary_key=True) store_name = Column(String(250), nullable=False) store_img_url = Column(String(250), nullable=False) store_description = Column(String(250), nullable=False) store_owner_name = Column(String(250), nullable=False) @property def to_json(self): return { "id": self.id, "storeName": self.store_name, "storeImgURL": self.store_img_url, "storeDescription": self.store_description, "storeOwnerName": self.store_owner_name } class Product(Base): __tablename__ = 'products' id = Column(Integer, primary_key=True) product_name = Column(String(250), nullable=False) product_img_url = Column(String(250), nullable=False) product_description = Column(String(250), nullable=False) product_price = Column(Integer, nullable=False) product_category = Column(String(250), nullable=False) store_id = Column(Integer, ForeignKey('stores.id')) stores = relationship(Store) @property def to_json(self): return { "id": self.id, "productName": self.product_name, "productImgURL": self.product_img_url, "productDescription": self.product_description, "productPrice": self.product_price, "productCategory": self.product_category, "storeID": self.store_id } engine = create_engine('sqlite:///stores.db') Base.metadata.create_all(engine)
For the last iteration of the project, we have been focusing on theresponsive of our application, so we have adopted theFlexbox Pattern. Now flexbox is a great technique that can be used to make your application looks good on different screens sizes, but you need to add all vendor prefixes to make sure that flexbox will work correctly on all browsers:
display: -webkit-box; /* iOS 6-, Safari 3.1-6 */display: -moz-box; /* Firefox 19- */display: -ms-flexbox; /* IE 10 */display: -webkit-flex; /* NEW - Chrome */display: flex;
Note: you might notice that we are using thevh andvw units for margins and padding, well that becausemargin-top, margin-bottom, paddin-top, andpadding-bottom don't work onFirefox when using the flexbox pattern.
vh: view heigh
vw: view width
Some Notes about working with BackboneJS
1-The Source Code is a great place to understand how things works behind the scene.
2- Whenever you need to update the value of a model use this technique:
var val = _.clone(model.get('attribute'));val = newVal;model.set('attribute', val);
=> by using theunderscore method (clone) we keep the attribute of the model at the same reference, that way thechange and thechange:attribute events will be triggered successfully otherwise it will not trigger.
3- To avoid firing the event multiple times, or what we call itzombies, you can use theunderscore method (debounce):
this.listenTo(collection, 'change', _.debounce(this.render, 300));