Commit 63c7919ccc9a6695f72c022f3380671cf67b979f
Add new menu config and Fix many bugs
- Add a configuration system for writing customized navigation menus. The
user has total flexibility to incorporate any kind of menus. Write any HTML
and CSS you want.
- Fix many bugs in adding new pages and rendering them.
- Fix the backend API. Its more meaningful now.
| | | | 1 | Mouchak | 1 | Mouchak |
---|
2 | ======= | 2 | ======= |
---|
3 | | 3 | |
---|
4 | A Javascript framework for building single page websites or web apps. | | A Javascript framework for building single page websites or web apps. |
---|
| | 4 | A Javascript framework for building websites quickly. | 5 | | 5 | |
---|
6 | It takes the content of the website as a JSON. The JSON file can contain | | It takes the content of the website as a JSON. The JSON file can contain |
---|
7 | text or multimedia content(images, audio, video). | | text or multimedia content(images, audio, video). |
---|
| | 6 | It aims to provide a visual editing interface to create a website and edit its | | | 7 | content, primarily for non-technical users. |
---|
8 | | 8 | |
---|
9 | Mouchak can also load external JS/CSS files through the JSON. This gives the website to load | | Mouchak can also load external JS/CSS files through the JSON. This gives the website to load |
---|
10 | plugins to enhance/customize the website either through JS or CSS. | | plugins to enhance/customize the website either through JS or CSS. |
---|
| | 9 | Under the hood, Mouchak abstracts the content of the website into a JSON | | | 10 | structure and uses Backbone model and views to render them dynamically. |
---|
| | 11 | This makes the site entirely run on the client side and syncs with the server |
---|
| | 12 | whenever there is a update. |
---|
| | 13 | This also makes Mouchak quite extensible. The built-in types to represent HTML |
---|
| | 14 | elements and media and such can be used and extended to build more custom |
---|
| | 15 | types. |
---|
11 | | 16 | |
---|
| | 17 | Mouchak can also load external JS/CSS files. Any magic that can be done using |
---|
| | 18 | Javascript and CSS can be easily integrated into Mouchak. Just specify Mouchak |
---|
| | 19 | the plugin to load and the callback to execute. |
---|
12 | | 20 | |
---|
| | 21 | Additionally, Mouchak can also provide a semantic rich environment. It provides |
---|
| | 22 | the user to specify tags or keywords with each associated content. To |
---|
| | 23 | complement it, Mouchak provides an API (filterTags) to get all the related |
---|
| | 24 | content together. One can easily built a view if this kind of functionality is |
---|
| | 25 | necessary. |
---|
| | 26 | |
---|
| | 27 | |
---|
13 | How to use it | 28 | How to use it |
---|
14 | ============= | 29 | ============= |
---|
15 | | 30 | |
---|
| | 31 | Installing |
---|
| | 32 | ---------- |
---|
16 | Either download this codebase or git clone the repo. | 33 | Either download this codebase or git clone the repo. |
---|
17 | | 34 | |
---|
18 | Once you have downloaded or cloned the repo, load the index.html file in your browser. | | Once you have downloaded or cloned the repo, load the index.html file in your browser. |
---|
19 | This loads the example content from example.json. | | This loads the example content from example.json. |
---|
| | 35 | **Pre-requisites** | 20 | | 36 | |
---|
21 | Use the index.html file as the boilerplate file of your index.html file. | | Use the index.html file as the boilerplate file of your index.html file. |
---|
22 | Modify the code in the script tag, which loads the example.json, and change | | Modify the code in the script tag, which loads the example.json, and change |
---|
23 | the URL to point to your JSON file. | | the URL to point to your JSON file. |
---|
24 | Remember the JSON files is loaded in the client side. Hence your JSON file should | | Remember the JSON files is loaded in the client side. Hence your JSON file should |
---|
25 | be from the same domain where this app is loaded. | | be from the same domain where this app is loaded. |
---|
26 | See cross-domain policies for details. | | See cross-domain policies for details. |
---|
| | 37 | You need to have Python, MongoDB, Flask and PyMongo. | | | 38 | To install python and mongodb on your platform, please use search engines to |
---|
| | 39 | find instructions. As they are quite popular softwares, getting help online |
---|
| | 40 | should not be difficult. |
---|
27 | | 41 | |
---|
| | 42 | To install Flask and PyMongo - |
---|
| | 43 | > pip install flask pymongo |
---|
| | 44 | |
---|
| | 45 | Configuring |
---|
| | 46 | ----------- |
---|
| | 47 | Configuration of Mouchak consists of configuring which database to use, |
---|
| | 48 | hostname, port no, title of the website etc. |
---|
| | 49 | Open up mouchak.conf, edit the file according to your needs, and then save it. |
---|
| | 50 | Ideally, you should set up seperate db for every project. |
---|
| | 51 | |
---|
| | 52 | Running |
---|
| | 53 | ------- |
---|
| | 54 | Once you have installed all the dependencies, go to the directory where the |
---|
| | 55 | code is located and type: |
---|
| | 56 | > python server.py |
---|
| | 57 | |
---|
| | 58 | This starts the Mouchak server. You can now point your browser to |
---|
| | 59 | [http://localhost:5000](http://localhost:5000) |
---|
| | 60 | |
---|
| | 61 | This will load up and display the website. |
---|
| | 62 | To edit the website go to [/edit](http://localhost:5000/edit) |
---|
| | 63 | |
---|
| | 64 | |
---|
28 | The global object for this framework is exposed as the variable M. This can be | 65 | The global object for this framework is exposed as the variable M. This can be |
---|
29 | inspected in the console. | 66 | inspected in the console. |
---|
30 | | 67 | |
---|
… | | … | |
---|
76 | (in terms of type like text, audio or image) and also provide the content (in case of text | 76 | (in terms of type like text, audio or image) and also provide the content (in case of text |
---|
77 | the content is as it is, in case images urls are given). The JSON should also describe the | 77 | the content is as it is, in case images urls are given). The JSON should also describe the |
---|
78 | content semantically by giving it tags. | 78 | content semantically by giving it tags. |
---|
79 | More details about the JSON format in example.json file. | | More details about the JSON format in example.json file. |
---|
80 | | 79 | |
---|
| | 80 | Mouchak provides a very simple and easy to use editor to edit the JSON. |
---|
| | 81 | |
---|
81 | The framework provides an easy way to pull up related content by just specifying the tags. | 82 | The framework provides an easy way to pull up related content by just specifying the tags. |
---|
82 | | 83 | |
---|
83 | Backbone models are used to model the content. Different content has different types. | 84 | Backbone models are used to model the content. Different content has different types. |
---|
… | | … | |
---|
98 | Mouchak also leverages powerful libraries like Backbone.js and Underscore.js to manage and render | 98 | Mouchak also leverages powerful libraries like Backbone.js and Underscore.js to manage and render |
---|
99 | content. This gives more flexibility to the content of the website. | 99 | content. This gives more flexibility to the content of the website. |
---|
100 | | 100 | |
---|
101 | The main code resides in js/mouchak.js. The HTML markup it uses is in index.html. | | The main code resides in js/mouchak.js. The HTML markup it uses is in index.html. |
---|
102 | | 101 | |
---|
103 | Javascript libary files are in js/lib. We use backbone.js, underscore.js and jquery in this | | Javascript libary files are in js/lib. We use backbone.js, underscore.js and jquery in this |
---|
104 | framework. | | framework. |
---|
105 | | | |
---|
106 | Boilerplate code/files: | | Boilerplate code/files: |
---|
107 | 404.html - error template | | 404.html - error template |
---|
108 | crossdomain.xml - cross-domain policies to be obeyed by the client | | crossdomain.xml - cross-domain policies to be obeyed by the client |
---|
109 | css/bootstrap.css - boilerplate css | | css/bootstrap.css - boilerplate css |
---|
110 | css/normalize.css - boilerplate css | | css/normalize.css - boilerplate css |
---|
111 | css/main.css - boilerplate css | | css/main.css - boilerplate css |
---|
112 | humans.txt - write your own credits | | humans.txt - write your own credits |
---|
113 | img/ - directory for images | | img/ - directory for images |
---|
114 | robots.txt - crawl spider rules | | robots.txt - crawl spider rules |
---|
115 | | | |
---|
116 | | | |
---|
117 | Support | 102 | Support |
---|
118 | ======= | 103 | ======= |
---|
119 | | 104 | |
---|
120 | Email to rayanon at janastu dot org / arvind at janastu dot org for any kind of feedback. | | Email to rayanon at janastu dot org / arvind at janastu dot org for any kind of feedback. |
---|
| | 105 | Email to rayanon at servelots dot com / arvind at servelots dot com for any kind of feedback. | 121 | | 106 | |
---|
122 | | 107 | |
---|
123 | Issues | 108 | Issues |
---|
124 | ====== | 109 | ====== |
---|
125 | | 110 | |
---|
126 | Report issues [here](http://bugzilla.pantoto.org/bugzilla3/describecomponents.cgi?product=Mouchak) | | Report issues [here](http://bugzilla.pantoto.org/bugzilla3/describecomponents.cgi?product=Mouchak) |
---|
| | 111 | Report issues [here](http://trac.pantoto.org/mouchak/) | | | 112 | First, check if your issue is already submitted by anyone else, by clicking on |
---|
| | 113 | "View Tickets". |
---|
| | 114 | If your issue is not reported, you can report it by clicking on "New Ticket". |
---|
| | | | | | 1 | DB=test_mouchak |
---|
| | 2 | SITE_TITLE=Testing Mouchak |
---|
| | 3 | HOST=0.0.0.0 |
---|
| | 4 | PORT=5000 |
---|
| | | | | | 1 | #!/usr/bin/python |
---|
| | 2 | |
---|
| | 3 | # Read Mouchak Configuration |
---|
| | 4 | |
---|
| | 5 | |
---|
| | 6 | import re |
---|
| | 7 | |
---|
| | 8 | def readConfig(): |
---|
| | 9 | confFile = 'mouchak.conf' |
---|
| | 10 | fh = open(confFile, 'r') |
---|
| | 11 | contents = fh.read() |
---|
| | 12 | |
---|
| | 13 | match = re.search('DB=(.*)', contents) |
---|
| | 14 | dbName = match.group(1) |
---|
| | 15 | match = re.search('SITE_TITLE=(.*)', contents) |
---|
| | 16 | title = match.group(1) |
---|
| | 17 | match = re.search('HOST=(.*)', contents) |
---|
| | 18 | host = match.group(1) |
---|
| | 19 | match = re.search('PORT=(.*)', contents) |
---|
| | 20 | port = match.group(1) |
---|
| | 21 | |
---|
| | 22 | return {'db': dbName, 'site_title': title, 'host': host, 'port': int(port)} |
---|
| | | | 1 | #!/usr/bin/python | 1 | #!/usr/bin/python |
---|
| | 2 | |
---|
2 | # Mouchak Server - | 3 | # Mouchak Server - |
---|
3 | # A Flask Application (http://flask.pocoo.org/) | 4 | # A Flask Application (http://flask.pocoo.org/) |
---|
4 | | 5 | |
---|
5 | import flask | 6 | import flask |
---|
6 | import pymongo | 7 | import pymongo |
---|
7 | import bson | 8 | import bson |
---|
| | 9 | import readConfig |
---|
8 | | 10 | |
---|
9 | app = flask.Flask(__name__) | 11 | app = flask.Flask(__name__) |
---|
10 | | 12 | |
---|
| | 13 | |
---|
| | 14 | config = readConfig.readConfig() |
---|
| | 15 | |
---|
11 | dbClient = pymongo.MongoClient() | 16 | dbClient = pymongo.MongoClient() |
---|
12 | db = dbClient['mouchak'] | | db = dbClient['mouchak'] |
---|
13 | collection = db['content'] | | collection = db['content'] |
---|
| | 17 | db = dbClient[config['db']] | | | 18 | siteContent = db['content'] |
---|
| | 19 | siteMenu = db['menu'] |
---|
| | 20 | if siteMenu.find_one() == None: |
---|
| | 21 | siteMenu.insert({'customMenu': False}) |
---|
| | 22 | |
---|
14 | # handy reference to otherwise long name | 23 | # handy reference to otherwise long name |
---|
15 | bson.ObjId = bson.objectid.ObjectId | 24 | bson.ObjId = bson.objectid.ObjectId |
---|
16 | | 25 | |
---|
| | 26 | |
---|
17 | def getContent(): | 27 | def getContent(): |
---|
18 | content = [] | 28 | content = [] |
---|
19 | for i in collection.find(): | | for i in collection.find(): |
---|
| | 29 | for i in siteContent.find(): | 20 | objId = bson.ObjId(i['_id']) | 30 | objId = bson.ObjId(i['_id']) |
---|
21 | del(i['_id']) | 31 | del(i['_id']) |
---|
22 | i['id'] = str(objId) | 32 | i['id'] = str(objId) |
---|
23 | content.append(i) | 33 | content.append(i) |
---|
24 | return content | | return content |
---|
25 | | 34 | |
---|
| | 35 | menu = siteMenu.find_one() |
---|
| | 36 | objId = bson.ObjId(menu['_id']) |
---|
| | 37 | del(menu['_id']) |
---|
| | 38 | menu['id'] = str(objId) |
---|
26 | | 39 | |
---|
| | 40 | return {'content': content, 'menu': menu} |
---|
| | 41 | |
---|
| | 42 | |
---|
| | 43 | |
---|
27 | @app.route('/', methods=['GET']) | 44 | @app.route('/', methods=['GET']) |
---|
28 | def index(): | 45 | def index(): |
---|
29 | return flask.render_template('index.html', content=getContent()) | | return flask.render_template('index.html', content=getContent()) |
---|
| | 46 | return flask.render_template('index.html', content=getContent(), | | | 47 | title=config['site_title']) |
---|
30 | | 48 | |
---|
31 | | 49 | |
---|
32 | @app.route('/edit', methods=['GET', 'POST']) | | @app.route('/edit', methods=['GET', 'POST']) |
---|
| | 50 | @app.route('/edit', methods=['GET']) | 33 | def edit(): | 51 | def edit(): |
---|
34 | if flask.request.method == 'GET': | | if flask.request.method == 'GET': |
---|
35 | return flask.render_template('editor.html', content=getContent()) | | return flask.render_template('editor.html', content=getContent()) |
---|
| | 52 | return flask.render_template('editor.html', content=getContent(), | | | 53 | title=config['site_title']) |
---|
36 | | 54 | |
---|
37 | elif flask.request.method == 'POST': | | elif flask.request.method == 'POST': |
---|
38 | newpage = flask.request.json | | newpage = flask.request.json |
---|
39 | print newpage | | print newpage |
---|
40 | res = collection.insert(newpage) | | res = collection.insert(newpage) |
---|
41 | print res | | print res |
---|
42 | return flask.jsonify(status='success')#, content=getContent()) | | return flask.jsonify(status='success')#, content=getContent()) |
---|
43 | | 55 | |
---|
44 | | | |
---|
45 | @app.route('/edit/<_id>', methods=['PUT', 'DELETE']) | | @app.route('/edit/<_id>', methods=['PUT', 'DELETE']) |
---|
46 | def editPage(_id): | | def editPage(_id): |
---|
| | 56 | @app.route('/page', methods=['POST']) | | | 57 | def insertPage(): |
---|
| | 58 | newpage = flask.request.json |
---|
| | 59 | print newpage |
---|
| | 60 | res = siteContent.insert(newpage) |
---|
| | 61 | _id = bson.ObjId(res) |
---|
| | 62 | newpage['id'] = str(_id) |
---|
| | 63 | del(newpage['_id']) |
---|
| | 64 | print newpage |
---|
| | 65 | # FIXME: handle errors |
---|
| | 66 | return flask.jsonify(status='ok', page=newpage) |
---|
| | 67 | |
---|
| | 68 | |
---|
| | 69 | @app.route('/page/<_id>', methods=['PUT', 'DELETE']) |
---|
| | 70 | def updatePage(_id): |
---|
47 | if flask.request.method == 'PUT': | 71 | if flask.request.method == 'PUT': |
---|
48 | changedPage = flask.request.json | 72 | changedPage = flask.request.json |
---|
49 | print changedPage | 73 | print changedPage |
---|
50 | res = collection.update({'_id' : bson.ObjId(_id)}, | | res = collection.update({'_id' : bson.ObjId(_id)}, |
---|
| | 74 | print '=======' | | | 75 | res = siteContent.update({'_id': bson.ObjId(_id)}, |
---|
51 | changedPage) | 76 | changedPage) |
---|
52 | print res | 77 | print res |
---|
53 | #print collection.find({'name': changed['name']}) | | #print collection.find({'name': changed['name']}) |
---|
54 | #for i in collection.find({'name': changed['name']}): | | #for i in collection.find({'name': changed['name']}): |
---|
55 | #print i | | #print i |
---|
56 | return flask.jsonify(status='success')#, content=getContent()) | | return flask.jsonify(status='success')#, content=getContent()) |
---|
| | 78 | if res['err'] == None: | | | 79 | print changedPage |
---|
| | 80 | return flask.jsonify(status='ok', page=changedPage) |
---|
57 | | 81 | |
---|
58 | elif flask.request.method == 'DELETE': | 82 | elif flask.request.method == 'DELETE': |
---|
59 | delPage = flask.request.url | 83 | delPage = flask.request.url |
---|
60 | print delPage | 84 | print delPage |
---|
61 | print _id | 85 | print _id |
---|
62 | res = collection.remove({'_id': bson.ObjId(_id)}) | | res = collection.remove({'_id': bson.ObjId(_id)}) |
---|
| | 86 | res = siteContent.remove({'_id': bson.ObjId(_id)}) | 63 | print res | 87 | print res |
---|
64 | return flask.jsonify(status='success', msg='removed') | | return flask.jsonify(status='success', msg='removed') |
---|
| | 88 | if res['err'] == None: | | | 89 | return flask.jsonify(status='ok') |
---|
| | 90 | else: |
---|
| | 91 | return flask.jsonify(error=res['err'], status='error') |
---|
65 | | 92 | |
---|
66 | | 93 | |
---|
| | 94 | #@app.route('/menu', methods=['POST']) |
---|
| | 95 | #def insertMenu(): |
---|
| | 96 | # newmenu = flask.request.json |
---|
| | 97 | # print newmenu |
---|
| | 98 | # res = siteMenu.insert(newmenu) |
---|
| | 99 | # print res |
---|
| | 100 | # return flask.jsonify(status='success')#, content=getContent()) |
---|
| | 101 | # |
---|
| | 102 | |
---|
| | 103 | @app.route('/menu/<_id>', methods=['PUT']) |
---|
| | 104 | def updateMenu(_id): |
---|
| | 105 | if flask.request.method == 'PUT': |
---|
| | 106 | changedMenu = flask.request.json |
---|
| | 107 | print changedMenu |
---|
| | 108 | res = siteMenu.update({'_id': bson.ObjId(_id)}, changedMenu) |
---|
| | 109 | print res |
---|
| | 110 | return flask.jsonify(status='ok',menu=changedMenu) |
---|
| | 111 | |
---|
| | 112 | #elif flask.request.method == 'DELETE': |
---|
| | 113 | # delMenu = flask.request.url |
---|
| | 114 | # print delMenu |
---|
| | 115 | # print _id |
---|
| | 116 | # res = siteMenu.remove({'_id': bson.ObjId(_id)}) |
---|
| | 117 | # return flask.jsonify(status='deleted') |
---|
| | 118 | |
---|
| | 119 | |
---|
67 | if __name__ == "__main__": | 120 | if __name__ == "__main__": |
---|
68 | app.run(debug=True, host='0.0.0.0') | | app.run(debug=True, host='0.0.0.0') |
---|
| | 121 | print config | | | 122 | app.run(debug=True, host=config['host'], port=config['port']) |
---|
| | | | 97 | border: 1px solid #999; | 97 | border: 1px solid #999; |
---|
98 | padding: 20px; | 98 | padding: 20px; |
---|
99 | width: 400px; | 99 | width: 400px; |
---|
100 | height: 400px; | | height: 400px; |
---|
| | 100 | /*height: 400px;*/ | 101 | } | 101 | } |
---|
102 | #pages { | 102 | #pages { |
---|
103 | position: absolute; | 103 | position: absolute; |
---|
… | | … | |
---|
106 | border: 1px solid black; | 106 | border: 1px solid black; |
---|
107 | padding: 10px; | 107 | padding: 10px; |
---|
108 | width: 300px; | 108 | width: 300px; |
---|
109 | height: 80%; | | height: 80%; |
---|
| | 109 | /*height: 80%;*/ | 110 | } | 110 | } |
---|
111 | #page { | 111 | #page { |
---|
112 | position: absolute; | 112 | position: absolute; |
---|
… | | … | |
---|
137 | #specific-content { | 137 | #specific-content { |
---|
138 | padding: 3px; | 138 | padding: 3px; |
---|
139 | margin-bottom: 10px; | 139 | margin-bottom: 10px; |
---|
| | 140 | } |
---|
| | 141 | #addPage { |
---|
| | 142 | margin: 10px 0 0 200px; |
---|
140 | } | 143 | } |
---|
141 | | 144 | |
---|
142 | /* ========================================================================== | 145 | /* ========================================================================== |
---|
| | | | 94 | ========================================================================== */ | 94 | ========================================================================== */ |
---|
95 | | 95 | |
---|
96 | | 96 | |
---|
97 | | | |
---|
98 | /* ========================================================================== | 97 | /* ========================================================================== |
---|
99 | Helper classes | 98 | Helper classes |
---|
100 | ========================================================================== */ | 99 | ========================================================================== */ |
---|
| | | | 8 | events: { | 8 | events: { |
---|
9 | 'click .pagename .disp': 'showPage', | 9 | 'click .pagename .disp': 'showPage', |
---|
10 | 'click #addPage': 'addPage', | 10 | 'click #addPage': 'addPage', |
---|
11 | 'click .pagename .remove': 'removePage' | | 'click .pagename .remove': 'removePage' |
---|
| | 11 | 'click .pagename .remove': 'removePage', | | | 12 | 'click #menu-config': 'showMenu' |
---|
12 | }, | 13 | }, |
---|
13 | initialize: function() { | 14 | initialize: function() { |
---|
14 | _.bindAll(this); | 15 | _.bindAll(this); |
---|
… | | … | |
---|
20 | $('#content-container').append(this.$el); | 20 | $('#content-container').append(this.$el); |
---|
21 | this.$el.append(this.template()); | 21 | this.$el.append(this.template()); |
---|
22 | this.$pagelist = $('#pagelist'); | 22 | this.$pagelist = $('#pagelist'); |
---|
| | 23 | var menu = M.site_content.menu; |
---|
| | 24 | //console.log(menu); |
---|
| | 25 | this.menuconfig = new M.types.model.menu(menu); |
---|
| | 26 | this.menuconfigview = new MenuConfigView({model: this.menuconfig}); |
---|
23 | }, | 27 | }, |
---|
24 | render: function() { | 28 | render: function() { |
---|
25 | // append the page list | 29 | // append the page list |
---|
… | | … | |
---|
42 | M.editor.pageview = pageview; | 42 | M.editor.pageview = pageview; |
---|
43 | }, | 43 | }, |
---|
44 | addPage: function() { | 44 | addPage: function() { |
---|
45 | var newpage = new Page(); | | var newpage = new Page(); |
---|
| | 45 | var newpage = new M.types.model.Page(); | 46 | M.pages.add(newpage); | 46 | M.pages.add(newpage); |
---|
47 | var newpageview = new PageView({model: newpage}); | 47 | var newpageview = new PageView({model: newpage}); |
---|
48 | newpageview.render(); | 48 | newpageview.render(); |
---|
… | | … | |
---|
64 | console.log('failed', model, xhr); | 64 | console.log('failed', model, xhr); |
---|
65 | } | 65 | } |
---|
66 | }); | 66 | }); |
---|
67 | } | | } |
---|
68 | }); | | }); |
---|
69 | | | |
---|
70 | var Page = Backbone.Model.extend({ | | var Page = Backbone.Model.extend({ |
---|
71 | defaults: { | | defaults: { |
---|
72 | name: '', | | name: '', |
---|
73 | title: '', | | title: '', |
---|
74 | children: [], | | children: [], |
---|
75 | content: [] | | content: [] |
---|
76 | }, | 67 | }, |
---|
77 | initialize: function() { | | initialize: function() { |
---|
78 | this.id = this.get('id'); | | this.id = this.get('id'); |
---|
| | 68 | showMenu: function(event) { | | | 69 | this.menuconfigview.render(); |
---|
79 | } | 70 | } |
---|
80 | }); | 71 | }); |
---|
81 | | 72 | |
---|
82 | var Pages = Backbone.Collection.extend({ | 73 | var Pages = Backbone.Collection.extend({ |
---|
83 | model: Page, | | model: Page, |
---|
84 | url: '/edit' | | url: '/edit' |
---|
| | 74 | model: M.types.model.Page, | | | 75 | url: '/page' |
---|
85 | }); | 76 | }); |
---|
86 | | 77 | |
---|
87 | /* view to manage each page and their properties - change page properties, | 78 | /* view to manage each page and their properties - change page properties, |
---|
… | | … | |
---|
111 | console.log('name changed', page); | 111 | console.log('name changed', page); |
---|
112 | }, | 112 | }, |
---|
113 | render: function() { | 113 | render: function() { |
---|
114 | console.log(this.$el); | | console.log(this.$el); |
---|
115 | $('#page').html(''); | 114 | $('#page').html(''); |
---|
116 | console.log('content: ', this.model.get('content')); | | console.log('content: ', this.model.get('content')); |
---|
| | 115 | //console.log('content: ', this.model.get('content')); | 117 | | 116 | |
---|
118 | this.$el.html(this.template({ | 117 | this.$el.html(this.template({ |
---|
119 | name: this.model.get('name'), | 118 | name: this.model.get('name'), |
---|
120 | title: this.model.get('title'), | 119 | title: this.model.get('title'), |
---|
121 | children: this.model.get('children'), | 120 | children: this.model.get('children'), |
---|
122 | content: this.listContent() | | content: this.listContent() |
---|
| | 121 | content: this.listContent(), | | | 122 | checked: this.model.get('showNav') ? 'checked="checked"' : '' |
---|
123 | })); | 123 | })); |
---|
124 | | 124 | |
---|
125 | //hover effect | 125 | //hover effect |
---|
… | | … | |
---|
128 | }, function(event) { | 128 | }, function(event) { |
---|
129 | $(event.target).closest('.content-item').removeClass('alert-error') | 129 | $(event.target).closest('.content-item').removeClass('alert-error') |
---|
130 | }); | 130 | }); |
---|
131 | console.log('done'); | | console.log('done'); |
---|
132 | }, | 131 | }, |
---|
133 | listContent: function() { | 132 | listContent: function() { |
---|
134 | var content = ''; | 133 | var content = ''; |
---|
… | | … | |
---|
182 | console.log('recvd remove event..about to process..'); | 182 | console.log('recvd remove event..about to process..'); |
---|
183 | var content = this.model.get('content'); | 183 | var content = this.model.get('content'); |
---|
184 | var idx = $(event.target).parent().attr('for'); | 184 | var idx = $(event.target).parent().attr('for'); |
---|
185 | idx = Number(idx); | | idx = Number(idx); |
---|
| | 185 | idx = Number(idx); //is this a correct way of doing it? | 186 | console.log('remove content: ', content[idx]); | 186 | console.log('remove content: ', content[idx]); |
---|
187 | content.splice(idx, 1); | 187 | content.splice(idx, 1); |
---|
188 | this.model.set({'content': content}); | 188 | this.model.set({'content': content}); |
---|
… | | … | |
---|
197 | children = (children === '') ? [] : children.split(','); | 197 | children = (children === '') ? [] : children.split(','); |
---|
198 | this.model.set({'name': name, 'title': title, 'children': children}); | 198 | this.model.set({'name': name, 'title': title, 'children': children}); |
---|
199 | | 199 | |
---|
| | 200 | if($('#showNav').is(':checked')) { |
---|
| | 201 | this.model.set({'showNav': true}); |
---|
| | 202 | } |
---|
| | 203 | else { |
---|
| | 204 | this.model.set({'showNav': false}); |
---|
| | 205 | } |
---|
| | 206 | |
---|
200 | this.model.save({}, { | 207 | this.model.save({}, { |
---|
201 | success: function(model, response) { | 208 | success: function(model, response) { |
---|
202 | console.log('saved', model, response); | 209 | console.log('saved', model, response); |
---|
| | 210 | model.set(response.page); |
---|
| | 211 | model.id = response.page.id; |
---|
203 | }, | 212 | }, |
---|
204 | error: function(model, xhr) { | 213 | error: function(model, xhr) { |
---|
205 | console.log('failed', model, xhr); | 214 | console.log('failed', model, xhr); |
---|
… | | … | |
---|
325 | | 325 | |
---|
326 | /* view to configure custom navigation menu */ | 326 | /* view to configure custom navigation menu */ |
---|
327 | var MenuConfigView = Backbone.View.extend({ | 327 | var MenuConfigView = Backbone.View.extend({ |
---|
328 | el: '#menu-config', | | el: '#menu-config', |
---|
| | 328 | tagName: 'div', | | | 329 | id: 'page', |
---|
329 | events: { | 330 | events: { |
---|
| | 331 | 'change #custom-menu': 'customMenuChange', |
---|
| | 332 | 'click #updateMenu': 'saveMenu' |
---|
330 | }, | 333 | }, |
---|
331 | initialize: function() { | 334 | initialize: function() { |
---|
332 | _.bindAll(this); | 335 | _.bindAll(this); |
---|
333 | this.template = _.template($('#menu-config-template').html()); | 336 | this.template = _.template($('#menu-config-template').html()); |
---|
334 | }, | 337 | }, |
---|
335 | render: function() { | 338 | render: function() { |
---|
336 | $('#content-container').append(this.template({ | | $('#content-container').append(this.template({ |
---|
337 | menu: 'foo' | | menu: 'foo' |
---|
| | 339 | $('#page').remove(); | | | 340 | $('#content-container').append(this.$el); |
---|
| | 341 | console.log('rendering..', this.$el); |
---|
| | 342 | this.$el.html(this.template({ |
---|
| | 343 | pos: this.model.get('pos'), |
---|
| | 344 | menu: this.model.get('html') |
---|
338 | })); | 345 | })); |
---|
| | 346 | this.$menuOptions = $('.menu-options'); |
---|
| | 347 | |
---|
| | 348 | if(this.model.get('customMenu') === true) { |
---|
| | 349 | $('#custom-menu').attr('checked', true); |
---|
| | 350 | this.$menuOptions.show(); |
---|
| | 351 | } |
---|
| | 352 | }, |
---|
| | 353 | showMenuOptions: function(bool) { |
---|
| | 354 | if(bool === true) { |
---|
| | 355 | this.$menuOptions.show(); |
---|
| | 356 | } |
---|
| | 357 | else { |
---|
| | 358 | this.$menuOptions.hide(); |
---|
| | 359 | } |
---|
| | 360 | }, |
---|
| | 361 | customMenuChange: function(event) { |
---|
| | 362 | this.$menuOptions = $('.menu-options'); |
---|
| | 363 | if($('#custom-menu').is(':checked')) { |
---|
| | 364 | this.model.set({'customMenu': true}); |
---|
| | 365 | } |
---|
| | 366 | else { |
---|
| | 367 | this.model.set({'customMenu': false}); |
---|
| | 368 | } |
---|
| | 369 | this.showMenuOptions(this.model.get('customMenu')); |
---|
| | 370 | }, |
---|
| | 371 | saveMenu: function() { |
---|
| | 372 | var menuHTML = $('#menu').val().trim(); |
---|
| | 373 | this.model.set({'html': menuHTML}); |
---|
| | 374 | console.log(this.model.toJSON()); |
---|
| | 375 | this.model.save({}, { |
---|
| | 376 | success: function(model, response) { |
---|
| | 377 | }, |
---|
| | 378 | error: function(xhr, response) { |
---|
| | 379 | } |
---|
| | 380 | }); |
---|
339 | } | 381 | } |
---|
340 | }); | 382 | }); |
---|
341 | | 383 | |
---|
342 | M.editor = { | 384 | M.editor = { |
---|
343 | init: function() { | 385 | init: function() { |
---|
344 | M.pages = new Pages(M.site_content); | | M.pages = new Pages(M.site_content); |
---|
| | 386 | M.pages = new Pages(M.site_content.content); | 345 | var pagelistview = new PageListView(); | 387 | var pagelistview = new PageListView(); |
---|
346 | pagelistview.render(); | 388 | pagelistview.render(); |
---|
347 | M.pages.on('add', function(page) { | 389 | M.pages.on('add', function(page) { |
---|
348 | pagelistview.render(); | 390 | pagelistview.render(); |
---|
349 | }); | 391 | }); |
---|
350 | M.pagelistview = pagelistview; | 392 | M.pagelistview = pagelistview; |
---|
351 | | | |
---|
352 | //var menuconfig = new MenuConfigView(); | | //var menuconfig = new MenuConfigView(); |
---|
353 | //menuconfig.render(); | | //menuconfig.render(); |
---|
354 | } | 393 | } |
---|
355 | }; | 394 | }; |
---|
356 | | 395 | |
---|
357 | function escapeHtml(string) { | | function escapeHtml(string) { |
---|
| | 396 | var escapeHtml = function(string) { | 358 | var entityMap = { | 397 | var entityMap = { |
---|
359 | "&": "&", | 398 | "&": "&", |
---|
360 | "<": "<", | 399 | "<": "<", |
---|
… | | … | |
---|
403 | "/": '/' | 403 | "/": '/' |
---|
404 | }; | 404 | }; |
---|
405 | return String(string).replace(/[&<>"'\/]/g, function (s) { | 405 | return String(string).replace(/[&<>"'\/]/g, function (s) { |
---|
406 | return entityMap[s]; | | return entityMap[s]; |
---|
407 | }); | | }); |
---|
| | 406 | return entityMap[s]; | | | 407 | }); |
---|
408 | } | 408 | } |
---|
409 | | 409 | |
---|
410 | })(M); | 410 | })(M); |
---|
| | | | 86 | // model for each Page | 86 | // model for each Page |
---|
87 | var Page = Backbone.Model.extend({ | 87 | var Page = Backbone.Model.extend({ |
---|
88 | defaults: { | 88 | defaults: { |
---|
89 | name: "index", | | name: "index", |
---|
| | 89 | name: "", | 90 | title: "", | 90 | title: "", |
---|
91 | children: [], | 91 | children: [], |
---|
92 | content: [] | | content: [] |
---|
| | 92 | content: [], | | | 93 | showNav: true |
---|
93 | }, | 94 | }, |
---|
94 | initialize: function() { | 95 | initialize: function() { |
---|
95 | // adding the name of the model as its id. | | // adding the name of the model as its id. |
---|
96 | // look up of this model through the collection | | // look up of this model through the collection |
---|
97 | // is faster this way. | | // is faster this way. |
---|
98 | //this.set({id: M.sanitize(this.get('name'))}); | | //this.set({id: M.sanitize(this.get('name'))}); |
---|
| | 96 | this.id = this.get('id'); | 99 | this.set({id: this.get('id')}); | 97 | this.set({id: this.get('id')}); |
---|
100 | } | 98 | } |
---|
101 | }); | 99 | }); |
---|
… | | … | |
---|
102 | model: Page | 102 | model: Page |
---|
103 | }); | 103 | }); |
---|
104 | | 104 | |
---|
| | 105 | var Menu = Backbone.Model.extend({ |
---|
| | 106 | defaults: { |
---|
| | 107 | customMenu: false |
---|
| | 108 | }, |
---|
| | 109 | url: function() { |
---|
| | 110 | return '/menu/' + this.id; |
---|
| | 111 | }, |
---|
| | 112 | initialize: function() { |
---|
| | 113 | this.id = this.get('id'); |
---|
| | 114 | }, |
---|
| | 115 | }); |
---|
| | 116 | |
---|
105 | //export types to the typemap | 117 | //export types to the typemap |
---|
106 | M.types = M.types || {}; | 118 | M.types = M.types || {}; |
---|
107 | M.types.model = { | 119 | M.types.model = { |
---|
108 | 'text': Text, | 120 | 'text': Text, |
---|
109 | 'image': Image, | 121 | 'image': Image, |
---|
110 | 'video': Video, | 122 | 'video': Video, |
---|
| | 123 | 'menu': Menu, |
---|
111 | 'rss': RSS, | 124 | 'rss': RSS, |
---|
112 | 'table': Table, | 125 | 'table': Table, |
---|
113 | 'plugin': Plugin, | 126 | 'plugin': Plugin, |
---|
| | | | 13 | _.bindAll(this); | 13 | _.bindAll(this); |
---|
14 | }, | 14 | }, |
---|
15 | render: function() { | 15 | render: function() { |
---|
16 | var navview = new NavigationView(); | | var navview = new NavigationView(); |
---|
| | 16 | var menu = new M.types.model.menu(M.site_content.menu); | | | 17 | var navview = new NavigationView({model: menu}); |
---|
17 | navview.render(); | 18 | navview.render(); |
---|
18 | }, | 19 | }, |
---|
19 | updateBreadcrumbs: function(event) { | 20 | updateBreadcrumbs: function(event) { |
---|
… | | … | |
---|
33 | this.template = _.template($('#nav-bar-template').html()); | 33 | this.template = _.template($('#nav-bar-template').html()); |
---|
34 | }, | 34 | }, |
---|
35 | render: function() { | 35 | render: function() { |
---|
36 | this.$el.append(this.template({})); | | this.$el.append(this.template({})); |
---|
37 | this.$ul = $('.nav'); | | this.$ul = $('.nav'); |
---|
38 | this.populate(); | | this.populate(); |
---|
| | 36 | // if custom menu is not defined, render a default menu | | | 37 | console.log(this.model.toJSON()); |
---|
| | 38 | if(this.model.get('customMenu') === false) { |
---|
| | 39 | console.log('generating default menu..'); |
---|
| | 40 | this.$el.append(this.template({})); |
---|
| | 41 | this.$ul = $('.nav'); |
---|
| | 42 | this.populate(); |
---|
| | 43 | } |
---|
| | 44 | // else render the custom menu |
---|
| | 45 | else { |
---|
| | 46 | console.log('rendering custom menu..'); |
---|
| | 47 | this.$el.append(this.model.get('html')); |
---|
| | 48 | } |
---|
39 | }, | 49 | }, |
---|
40 | populate: function() { | 50 | populate: function() { |
---|
41 | var item_template = _.template($('#nav-item-template').html()); | 51 | var item_template = _.template($('#nav-item-template').html()); |
---|
42 | _.each(M.pages.models, function(page) { | 52 | _.each(M.pages.models, function(page) { |
---|
43 | console.log(_.isEmpty(page.get('children'))); | | console.log(_.isEmpty(page.get('children'))); |
---|
| | 53 | //console.log('no children?', _.isEmpty(page.get('children'))); | 44 | this.$ul.append(item_template({ | 54 | this.$ul.append(item_template({ |
---|
45 | cls: (_.isEmpty(page.get('children'))) ? '' : 'dropdown', | 55 | cls: (_.isEmpty(page.get('children'))) ? '' : 'dropdown', |
---|
46 | page: page.get('name') | 56 | page: page.get('name') |
---|
… | | … | |
---|
85 | M.rss_view.render(); | 85 | M.rss_view.render(); |
---|
86 | } | 86 | } |
---|
87 | var id = nameIdMap[page]; | 87 | var id = nameIdMap[page]; |
---|
| | 88 | if(!id) { |
---|
| | 89 | this.render404(); |
---|
| | 90 | return; |
---|
| | 91 | } |
---|
88 | $('#'+id).show(); | 92 | $('#'+id).show(); |
---|
89 | $('.'+page).show(); | 93 | $('.'+page).show(); |
---|
| | 94 | if(M.pages.get(id).get('showNav') === false) { |
---|
| | 95 | $('#navigation').hide(); |
---|
| | 96 | } |
---|
| | 97 | else { |
---|
| | 98 | $('#navigation').show(); |
---|
| | 99 | } |
---|
| | 100 | }, |
---|
| | 101 | render404: function() { |
---|
| | 102 | $('.pageview').hide(); |
---|
| | 103 | var notFound = "Sorry, a page corresponding to your URL was not found.\n" + |
---|
| | 104 | "Maybe you have typed the URL wrong, or this page is no longer available."; |
---|
| | 105 | alert(notFound); |
---|
90 | } | 106 | } |
---|
91 | }); | 107 | }); |
---|
92 | | 108 | |
---|
… | | … | |
---|
115 | M.tags = {}; //global tag cache | 115 | M.tags = {}; //global tag cache |
---|
116 | | 116 | |
---|
117 | // global collection of pages | 117 | // global collection of pages |
---|
118 | M.pages = new types.model.Pages(M.site_content); | | M.pages = new types.model.Pages(M.site_content); |
---|
| | 118 | M.pages = new types.model.Pages(M.site_content.content); | 119 | | 119 | |
---|
120 | // iterate through pages to get their content and render them using views and | 120 | // iterate through pages to get their content and render them using views and |
---|
121 | // models | 121 | // models |
---|
| | | | 6 | <head> | 6 | <head> |
---|
7 | <meta charset="utf-8"> | 7 | <meta charset="utf-8"> |
---|
8 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | 8 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> |
---|
9 | <title> Mouchak - ummm!</title> | | <title> Mouchak - ummm!</title> |
---|
| | 9 | <title> Editing.. | {{title}} </title> | 10 | <meta name="description" content=""> | 10 | <meta name="description" content=""> |
---|
11 | <meta name="viewport" content="width=device-width"> | 11 | <meta name="viewport" content="width=device-width"> |
---|
12 | | 12 | |
---|
… | | … | |
---|
24 | | 24 | |
---|
25 | <div class="container" id="container"> | 25 | <div class="container" id="container"> |
---|
26 | <div id="header"> </div> | 26 | <div id="header"> </div> |
---|
27 | | | |
---|
28 | <div id="content-container"></div> | 27 | <div id="content-container"></div> |
---|
29 | | | |
---|
30 | <div id="footer"> | 28 | <div id="footer"> |
---|
31 | </div> | 29 | </div> |
---|
32 | </div> | 30 | </div> |
---|
33 | | 31 | |
---|
34 | | | |
---|
35 | <script> | 32 | <script> |
---|
36 | // initialize editor | 33 | // initialize editor |
---|
37 | window.M = window.M || {}; | 34 | window.M = window.M || {}; |
---|
… | | … | |
---|
51 | | 51 | |
---|
52 | <!-- Underscore templates --> | 52 | <!-- Underscore templates --> |
---|
53 | <script type="text/template" id="menu-config-template"> | 53 | <script type="text/template" id="menu-config-template"> |
---|
54 | <div id="menu-config"> | | <div id="menu-config"> |
---|
55 | <textarea id="menu"> | | <textarea id="menu"> |
---|
56 | <%= menu %> | | <%= menu %> |
---|
57 | </textarea> | | </textarea> |
---|
| | 54 | <div class="page"> | | | 55 | <div class="menu-config"> |
---|
| | 56 | <h4> Menu Config </h4> |
---|
| | 57 | <form class="form-horizontal"> |
---|
| | 58 | <div class="control-group"> |
---|
| | 59 | <input id="custom-menu" type="checkbox"> |
---|
| | 60 | <span class=""><strong> Custom Menu </strong></span> |
---|
| | 61 | </div> |
---|
| | 62 | <button id="updateMenu" type="submit" class="btn btn-primary pull-right"> Update </button> |
---|
| | 63 | <div class="clearfix"></div> |
---|
| | 64 | <div class="menu-options" style="display: none;"> |
---|
| | 65 | <div class="control-group"> |
---|
| | 66 | <div class="input-prepend"> |
---|
| | 67 | <span class="add-on"> <strong> Position </strong></span> |
---|
| | 68 | <input id="pos" type="text" placeholder="[left, top]" |
---|
| | 69 | value="<%= pos %>"> |
---|
| | 70 | </div> |
---|
| | 71 | </div> |
---|
| | 72 | <label><strong> HTML </strong></label> |
---|
| | 73 | <textarea id="menu"> |
---|
| | 74 | <%= menu %> |
---|
| | 75 | </textarea> |
---|
| | 76 | </div> |
---|
| | 77 | </form> |
---|
| | 78 | </div> |
---|
58 | </div> | 79 | </div> |
---|
59 | </script> | 80 | </script> |
---|
60 | | 81 | |
---|
… | | … | |
---|
83 | <div id="pagelistview"> | 83 | <div id="pagelistview"> |
---|
84 | <h4> List of Pages </h4> | 84 | <h4> List of Pages </h4> |
---|
85 | <div id="pagelist"></div> | 85 | <div id="pagelist"></div> |
---|
86 | <button class="btn btn-primary pull-right" id="addPage">Add Page</button> | | <button class="btn btn-primary pull-right" id="addPage">Add Page</button> |
---|
87 | <p><a href="/" class="btn"> Go to site </a></p> | | <p><a href="/" class="btn"> Go to site </a></p> |
---|
| | 86 | <button class="btn btn-primary" id="addPage">Add Page</button> | | | 87 | <hr> |
---|
| | 88 | <p> <a href="javascript:void(0);" id="menu-config"> Site Menu </a> </p> |
---|
| | 89 | <p><a href="/"> Go to site </a></p> |
---|
88 | </div> | 90 | </div> |
---|
89 | </script> | 91 | </script> |
---|
| | 92 | |
---|
90 | <script type="text/template" id="page-list-item-template"> | 93 | <script type="text/template" id="page-list-item-template"> |
---|
91 | <div class="pagename"> | 94 | <div class="pagename"> |
---|
92 | <a class="disp" id="<%= id %>" href="#"><%= name %></a> | | <a class="disp" id="<%= id %>" href="#"><%= name %></a> |
---|
| | 95 | <a class="disp" id="<%= id %>" href="javascript:void(0);"><%= name %></a> | 93 | <span class="pull-right"> | 96 | <span class="pull-right"> |
---|
94 | <a href="#" class="remove" for="<%= id %>"> | | <a href="#" class="remove" for="<%= id %>"> |
---|
| | 97 | <a href="javascript:void(0);" class="remove" for="<%= id %>"> | 95 | <i class="icon-trash"></i> | 98 | <i class="icon-trash"></i> |
---|
96 | </a> | 99 | </a> |
---|
97 | </span> | 100 | </span> |
---|
| | 101 | <span class="clearfix"></span> |
---|
98 | </div> | 102 | </div> |
---|
99 | </script> | 103 | </script> |
---|
100 | | 104 | |
---|
… | | … | |
---|
126 | value="<%= children %>"> | 126 | value="<%= children %>"> |
---|
127 | </div> | 127 | </div> |
---|
128 | </div> | 128 | </div> |
---|
| | 129 | <div class="control-group"> |
---|
| | 130 | <div class="input-prepend"> |
---|
| | 131 | <span class="add-on"><strong> Show Navigation </strong></span> |
---|
| | 132 | <input id="showNav" type="checkbox" <%=checked%>> |
---|
| | 133 | </div> |
---|
| | 134 | </div> |
---|
129 | <label><strong> Content </strong></label> | 135 | <label><strong> Content </strong></label> |
---|
130 | <div id="content" class="content well"> | 136 | <div id="content" class="content well"> |
---|
131 | <%= content %> | 137 | <%= content %> |
---|
… | | … | |
---|
139 | <button class="addContent btn btn-mini btn-primary">Add Content</button> | 139 | <button class="addContent btn btn-mini btn-primary">Add Content</button> |
---|
140 | </div> | 140 | </div> |
---|
141 | <button id="updatePage" type="submit" class="btn btn-primary pull-right"> Update </button> | 141 | <button id="updatePage" type="submit" class="btn btn-primary pull-right"> Update </button> |
---|
| | 142 | <div class="clearfix"></div> |
---|
142 | </form> | 143 | </form> |
---|
143 | </div> | 144 | </div> |
---|
144 | </script> | 145 | </script> |
---|
… | | … | |
---|
153 | </span> | 153 | </span> |
---|
154 | </span> | 154 | </span> |
---|
155 | <span class="pull-right"> | 155 | <span class="pull-right"> |
---|
156 | <a href="#" class="remove" for="<%=no%>"><i class="icon-trash"></i></a> | | <a href="#" class="remove" for="<%=no%>"><i class="icon-trash"></i></a> |
---|
| | 156 | <a href="javascript:void(0);" class="remove" for="<%=no%>"><i class="icon-trash"></i></a> | 157 | </span> | 157 | </span> |
---|
| | 158 | <span class="clearfix"></span> |
---|
158 | </div> | 159 | </div> |
---|
159 | </script> | 160 | </script> |
---|
160 | | 161 | |
---|
| | | | 6 | <head> | 6 | <head> |
---|
7 | <meta charset="utf-8"> | 7 | <meta charset="utf-8"> |
---|
8 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | 8 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> |
---|
9 | <title> Mouchak - ummm!</title> | | <title> Mouchak - ummm!</title> |
---|
| | 9 | <title> {{title}} </title> | 10 | <meta name="description" content=""> | 10 | <meta name="description" content=""> |
---|
11 | <meta name="viewport" content="width=device-width"> | 11 | <meta name="viewport" content="width=device-width"> |
---|
12 | | 12 | |
---|