Commit 899c2c75511d9167407ddd7bcecfb1604cf87bdb

Add README and example json

  Intermediate commit to save the current work progress.
Added better README. Added a example.json file. Should make that more
comprehensive.
README.md
(86 / 2)
  
1mouchak
1Mouchak
22=======
33
4A framework for building websites.
4A Javascript framework for building single page websites or web apps.
5
6It takes the content of the website as a JSON. The JSON file can contain
7text or multimedia content(images, audio, video).
8
9Mouchak can also load external JS/CSS files through the JSON. This gives the website to load
10plugins to enhance/customize the website either through JS or CSS.
11
12
13How to use it
14=============
15
16Either download this codebase or git clone the repo.
17
18Once you have downloaded or cloned the repo, load the index.html file in your browser.
19This loads the example content from example.json.
20
21Use the index.html file as the boilerplate file of your index.html file.
22Modify the code in the script tag, which loads the example.json, and change
23the URL to point to your JSON file.
24Remember the JSON files is loaded in the client side. Hence your JSON file should
25be from the same domain where this app is loaded.
26See cross-domain policies for details.
27
28The global object for this framework is exposed as the variable M. This can be
29inspected in the console.
30
31The M.filterTags() method takes an array of strings representing the tags to filter.
32It returns an array of the content objects with the matched tags.
33
34
35How it works
36============
37
38It takes the content of the site in the form of JSON. The JSON should describe the content
39(in terms of type like text, audio or image) and also provide the content (in case of text
40the content is as it is, in case images urls are given). The JSON should also describe the
41content semantically by giving it tags.
42More details about the JSON format in example.json file.
43
44The framework provides an easy way to pull up related content by just specifying the tags.
45
46Backbone models are used to model the content. Different content has different types.
47Again all types are derived from one base type.
48Each model has a corresponding view as well. The views manage how the content are rendered.
49
50Each page is made of multiple content (content objects).
51Pages are also modeled in Backbone models and their view is managed via Backbone views.
52
53The framework also use Backbone router to handle client side routing.
54
55
56What it uses
57============
58
59Mouchak uses HTML5 Boilerplate and Bootstrap project as a boilerplate code for the website.
60Mouchak also leverages powerful libraries like Backbone.js and Underscore.js to manage and render
61content. This gives more flexibility to the content of the website.
62
63The main code resides in js/mouchak.js. The HTML markup it uses is in index.html.
64
65Javascript libary files are in js/lib. We use backbone.js, underscore.js and jquery in this
66framework.
67
68Boilerplate code/files:
69404.html - error template
70crossdomain.xml - cross-domain policies to be obeyed by the client
71css/bootstrap.css - boilerplate css
72css/normalize.css - boilerplate css
73css/main.css - boilerplate css
74humans.txt - write your own credits
75img/ - directory for images
76robots.txt - crawl spider rules
77
78
79Support
80=======
81
82Email to rayanon at janastu dot org for any kind of feedback.
83
84
85Issues
86======
87
88Report issues at http://bugzilla.pantoto.org/bugzilla3/
example.json
(42 / 0)
  
1[
2 {
3 "name": "index",
4 "children": ["Motivation", "The JSON file"],
5 "title": "Mouchak",
6 "content": [
7 {
8 "type": "image",
9 "src": "img/mouchak.jpg"
10 },
11 {
12 "type": "text",
13 "title": "What's this?",
14 "data": "<p> Mouchak is a Javascript framework for building websites quickly.</p><p>It takes the components and the content of the website in a JSON format. The content in the JSON can be of type text or multimedia like images and videos.</p> It also has the capabilities to load external JS and CSS files to customize the website."
15 }
16 ]
17 },
18 {
19 "name": "Motivation",
20 "title": "Motivation",
21 "children": [],
22 "content": [
23 {
24 "type": "text",
25 "title": "Why Mouchak?",
26 "data": "Explanation of why we built this framework..."
27 }
28 ]
29 },
30 {
31 "name": "The JSON file",
32 "title": "The JSON format",
33 "children": [],
34 "content": [
35 {
36 "type": "text",
37 "title": "",
38 "data": "{ } Explain the JSON format here.."
39 }
40 ]
41 }
42]
Binary files differ
index.html
(9 / 6)
  
2424
2525 <div class="container" id="container">
2626 <div id="header"></div>
27 <div id="nav">
28 <ul class="nav nav-pills"></ul>
29 </div>
30 <div class="clearfix"></div>
3127 <div id="content-container"></div>
3228 <div id="footer"></div>
3329 </div>
3232 //Code to initialize the framework
3333 window.M = window.M || {};
3434 window.onload = function() {
35 M.load("/maraa/data/maraa2.json");
35 M.load('example.json');
3636 };
3737 </script>
3838 <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
4141 <script src="js/lib/backbone.js"></script>
4242 <script src="js/lib/bootstrap.js"></script>
4343 <script src="js/plugins.js"></script>
44 <script src="js/framework.js"></script>
44 <script src="js/mouchak.js"></script>
4545
4646 <!-- Underscore templates -->
4747 <script type="text/template" id="news-template">
5555 <a href="<%= link %>">Read More</a>
5656 </div>
5757 </script>
58
59 <script type="text/template" id="nav-template">
60 <div class="navigation" id="nav-<%= page %>">
61 <ul class="nav nav-pills"></ul>
62 </div>
63 </script>
5864 <script type="text/template" id="nav-dropdown-template">
5965 <li class="dropdown">
6066 <a class="dropdown-toggle" data-toggle="dropdown" href="#/<%= M.sanitize(name) %>"
7878 </ul>
7979 </li>
8080 </script>
81
8182 <script type="text/template" id="image-view-template">
8283 <div class="img-wrapper">
8384 <img src="<%= src %>">
  
1(function(M) {
2/* Defining Backbone models, collections and views */
3
4var BaseType = Backbone.Model.extend({
5 defaults: {
6 tags: [],
7 title: "",
8 attr: {}
9 },
10 initialize: function() {
11 }
12});
13
14var Text = BaseType.extend({
15 defaults: {
16 data: "",
17 },
18 initialize: function() {
19 }
20});
21
22var TextView = Backbone.View.extend({
23 tagName: 'div',
24 className: '',
25 initialize: function() {
26 _.bindAll(this);
27 _.bind(this.render, this);
28 },
29 render: function(el) {
30 $(el).append(this.el);
31 var str = '<h4>'+ this.model.get('title') +'</h4> <p>' +
32 this.model.get('data') + '</p>';
33 $(this.el).html(str);
34 M.appendAttrs(this.model, this.el);
35 }
36});
37
38var Table = BaseType.extend({
39 defaults: {
40 data : {
41 th: [],
42 tr:[]
43 }
44 },
45 initialize: function() {
46 }
47});
48
49var TableView = Backbone.View.extend({
50 tagName: 'table',
51 className: 'table',
52 initialize: function() {
53 _.bindAll(this);
54 _.bind(this.render, this);
55 },
56 render: function(el) {
57 var heading = this.model.get('data').th;
58 var str = '<tr>';
59
60 for(var i = 0; i < heading.length; i++) {
61 str += '<th>' + heading[i] + '</th>';
62 }
63 str += '</tr>';
64
65 _.each(this.model.get('data').tr, function(row) {
66 str += '<tr>';
67 for(var i = 0; i < row.length; i++) {
68 if(row[i].match(/http.?:/)) {
69 //console.log(row[i].match(/http:/))
70 }
71 str += '<td>'+ row[i] + '</td>';
72 }
73 str += '</tr>';
74 });
75
76 $(el).append(this.el);
77 $(this.el).html(str);
78 M.appendAttrs(this.model, this.el);
79 }
80});
81
82var Image = BaseType.extend({
83 defaults: {
84 src: ""
85 },
86 initialize:function() {
87 }
88});
89
90var ImageView = Backbone.View.extend({
91 tagName: 'image',
92 className: '',
93
94 initialize: function() {
95 _.bindAll(this);
96 _.bind(this.render, this);
97 },
98 render: function(el) {
99 $(el).append(this.el);
100 $(this.el).attr('src', this.model.get('src'));
101 M.appendAttrs(this.model, this.el);
102 }
103});
104
105
106var Video = BaseType.extend({
107 defaults: {
108 src: ""
109 },
110 initialize:function() {
111 }
112});
113
114var VideoView = Backbone.View.extend({
115 initialize: function() {
116 _.bindAll(this);
117 _.bind(this.render, this);
118 // assuming cross-domain urls will have http in the src,
119 // so also assuming they are embedded flash urls,
120 // hence iframe
121 if(this.model.get('src').match('http')) {
122 this.tagName = 'iframe';
123 }
124 // otherwise, use html5 video tag, if the video is served
125 // from the same domain
126 else {
127 this.tagName = 'video';
128 }
129 },
130 render: function(el) {
131 $(el).append(this.el);
132 $(this.el).attr('src', this.model.get('src'));
133 M.appendAttrs(this.model, this.el);
134 }
135});
136
137var RSS = BaseType.extend({
138 defaults: {
139 src: ""
140 },
141 initialize:function() {
142 }
143});
144
145var RSSView = Backbone.View.extend({
146 el: '#feeds',
147 initialize: function() {
148 _.bindAll(this);
149 _.bind(this.render, this);
150 },
151 render: function() {
152 M.populateFeeds(this.model.get('src'));
153 }
154});
155
156// Plugin model can be used to load dynamic components
157// to the website by loading external JS files.
158// Also the website can be styled by using external CSS files,
159// which can also be loaded via this plugin model.
160var Plugin = BaseType.extend({
161 defaults: {
162 src: "",
163 data: {},
164 callback: ""
165 },
166 initialize: function() {
167 if(this.get('src').match(/\.js/)) {
168 var script = document.createElement('script');
169 var callback = this.get('callback');
170 script.src = this.get('src');
171 document.body.appendChild(script);
172 script.onload = function() {
173 eval(callback);
174 };
175 }
176 else if(this.get('src').match(/\.css/)) {
177 var link = document.createElement('link');
178 link.rel = 'stylesheet';
179 link.href = this.get('src');
180 link.type = 'text/css';
181 document.body.appendChild(link);
182 }
183 }
184});
185
186var PluginView = Backbone.View.extend({
187 initialize: function() {
188 return;
189 },
190 render: function(el) {
191 return;
192 }
193});
194
195var type_map;
196M.type_map = type_map = {
197 model : {
198 'text': Text,
199 'image': Image,
200 'video': Video,
201 'rss': RSS,
202 'table': Table,
203 'plugin': Plugin
204 },
205 view: {
206 'text': TextView,
207 'image': ImageView,
208 'video': VideoView,
209 'rss': RSSView,
210 'table': TableView,
211 'plugin': PluginView
212 }
213};
214
215// var Media = Backbone.Model.extend({
216// defaults:{
217// "audio": new Audio() ,
218// "video": new Video(),
219// "image": new Image()
220// }
221
222// });
223
224var Texts = Backbone.Collection.extend({model: Text});
225var Images = Backbone.Collection.extend({model: Image});
226var Videos = Backbone.Collection.extend({model: Video});
227var RSSs = Backbone.Collection.extend({model: RSS});
228
229
230var Page = Backbone.Model.extend({
231 defaults: {
232 name: "index",
233 title: "",
234 children: [],
235 content: []
236 },
237 initialize: function() {
238 // adding the name of the model as its id.
239 // look up of this model through the collection
240 // is faster this way.
241 this.set({id: M.sanitize(this.get('name'))});
242 }
243});
244
245
246var Pages = Backbone.Collection.extend({
247 model: Page
248});
249
250var PageView = Backbone.View.extend({
251 tagName: 'div',
252 className: 'page',
253 initialize: function() {
254 _.bindAll(this);
255 _.bind(this.render, this);
256 this.render();
257 $(this.el).hide();
258 },
259 render: function() {
260 $('#content-container').append(this.el);
261 $(this.el).append('<h3>'+this.model.get('title')+'</h3>');
262 var self = this;
263 _.each(this.model.get('content'), function(item) {
264 var view = type_map.view[item.get('type')];
265 if(!view) {
266 console.log('Error initing view', item);
267 return;
268 }
269 if(item.get('type') === 'rss') {
270 M.rss_view = new view({model: item});
271 $(self.el).append(_.template($('#news-template').html()));
272 }
273 else {
274 var item_view = new view({model: item});
275 item_view.render(self.el);
276 }
277 });
278 }
279});
280
281var AppView = Backbone.View.extend({
282 el: 'body',
283 events: {
284 'click .nav li a' : 'navClicked'
285 },
286 initialize: function() {
287 _.bindAll(this);
288 },
289 render: function() {
290 M.createNavigation();
291 $('#index').show();
292 },
293 navClicked: function(event) {
294 $('.nav li').removeClass('active');
295 $(event.currentTarget).parent().addClass('active');
296 }
297});
298
299var AppRouter = Backbone.Router.extend({
300 routes : {
301 'index' : 'index',
302 ':page' : 'showPage'
303 },
304 index: function() {
305 $('.page').hide();
306 $('#index').show();
307 },
308 showPage: function(page) {
309 $('.page').hide();
310 //news pages are rendered on the fly,
311 //as feeds have to be fetched.
312 if(page === 'news') {
313 M.rss_view.render();
314 }
315 $('#'+page).show();
316 $('.'+page).show();
317 }
318});
319
320/* Defining other necessary functions */
321M.init = function() {
322 M.tags = {}; //global tag cache
323 M.pages = new Pages(); //global collection of all pages
324
325 // iterate through the JSON to intialize models and views
326 _.each(M.site_content, function(page) {
327 var new_page = new Page(page);
328 var contents = [];
329 _.each(page.content, function(content) {
330 var Item = type_map.model[content.type];
331 if(!Item) {
332 console.log('Error initing item: ', content);
333 return;
334 }
335 var item = new Item(content);
336 contents.push(item);
337 });
338 new_page.set({content: contents});
339 var new_page_view = new PageView({model: new_page,
340 id: new_page.get('id')});
341 M.pages.add(new_page);
342 });
343
344 M.appView = new AppView();
345 M.appView.render();
346 var app_router = new AppRouter();
347 Backbone.history.start();
348 // start with index page
349 //app_router.index();
350 M.simHeir();
351};
352
353// hack to simulate heirarchy among the page views
354// basically add the parent id as class in all of its children
355// elements.
356M.simHeir = function() {
357 _.each(M.pages.models, function(page) {
358 if(page.id == 'index') return;
359 _.each(page.get('children'), function(child) {
360 child = M.sanitize(child);
361 $('#'+child).addClass(page.id);
362 });
363 });
364};
365
366
367// append attributes to elements from the model
368M.appendAttrs = function(model, el) {
369 _.each(model.get('attr'), function(val, key) {
370 $(el).attr(key, val);
371 });
372}
373
374//Helper method for making a list of id associated to tag
375 M.createTagList = function(item,x)
376 {
377
378 for(var i in item['tags'])
379 {
380 if( M.tags[item['tags'][i]] === undefined)
381 {
382 M.tags[item['tags'][i]] = [];
383 M.tags[item['tags'][i]].push(x);
384 }
385 else
386 M.tags[item['tags'][i]].push(x);
387 }
388 };
389
390//create navigational links
391M.createNavigation = function() {
392 var top_level = M.pages.get('index').get('children');
393 $('<li class="active"><a href="#/index">Home</a></li>').appendTo('.nav');;
394 _.each(top_level, function(child) {
395 child = M.sanitize(child);
396 var children = M.pages.get(child).get('children');
397 var page = M.pages.get(child);
398 var dropdown_template = _.template($('#nav-dropdown-template').html());
399 if(_.isEmpty(children)) {
400 li = '<li><a href="#/' + child + '">'+ M.humanReadable(child) +'</a></li>';
401 }
402 else {
403 li = dropdown_template({
404 //no: page.cid,
405 name: M.humanReadable(page.get('name')),
406 list: _.map(children, M.humanReadable)
407 });
408 }
409 $(li).appendTo('.nav');
410 });
411};
412
413// populate with news feeds in the news section
414// gets the feeds from server side script 'feed.py'
415M.populateFeeds = function(rss_url) {
416 $('#feeds-loader').show();
417 $('.news-item-wrapper').remove();
418 jQuery.getFeed({
419 url: 'feeds',
420 type: 'GET',
421 data: "rss_url="+encodeURIComponent(rss_url),
422 success: function(feed) {
423 $('#feeds-loader').hide();
424 var template = _.template($('#news-item-template').html());
425 _.each(feed.items, function(item) {
426 x = $('#feeds').append(template({
427 title: item.title,
428 link: item.link
429 }));
430 });
431 },
432 error: function(err) {
433 $('#feeds-loader').hide();
434 $('#feeds').append('Oops, something went wrong! <br/> Please try again.');
435 }
436 });
437};
438
439/* Other helper functions */
440
441M.contentList = []; //A list to hold out filtered content objects.
442
443//Check for the tags and return only those "content" objects which match a given tag.
444M.checkTags = function(tags){
445 if(_.isArray(tags))
446 {
447 var list = [];
448 _.each(tags,function(item){
449 list.push(M.tags[item]);
450 });
451 return _.uniq(list);
452 }
453 else
454 return false; //Failure code, the function will only accept a list as input
455};
456
457// change all '-' to spaces and capitalize first letter of
458// every word
459M.humanReadable = function(str) {
460 if(typeof str !== "string") {
461 str = '';
462 }
463 return '' + str.replace(/[-]+/g, ' ').replace(/[^\s]+/g, function(str) {
464 //return '' + str.replace(/[-]+/g, ' ').replace(/([A-Z])/g, function(s) { return ' '+s;}).replace(/[^\s]+/g, function(str) {
465 return str.substr(0,1).toUpperCase() + str.substr(1).toLowerCase();
466 });
467};
468
469// change all spaces to '-' and everything to lowercase
470M.sanitize = function(str) {
471 if(typeof str !== "string") {
472 str = '';
473 }
474 return '' + str.replace(/[\s]+/g,'-').replace(/[^\s]+/g, function(str) {
475 //TODO: handle special characters!
476 return str.replace('&', 'and').toLowerCase();
477 });
478};
479
480
481// Loader
482M.load = function(content_url) {
483 if(typeof content_url !== 'string') {
484 console.error('URL to load has to be of type string!!');//TODO: raise custom exception
485 return;
486 }
487 $.getJSON(content_url, function(data) {
488 M.site_content = data;
489 M.init();
490 });
491};
492
493})(M);
js/mouchak.js
(489 / 0)
  
1(function(M) {
2/* Defining Backbone models, collections and views */
3
4var BaseType = Backbone.Model.extend({
5 defaults: {
6 tags: [],
7 title: "",
8 attr: {}
9 },
10 initialize: function() {
11 }
12});
13
14var Text = BaseType.extend({
15 defaults: {
16 data: "",
17 },
18 initialize: function() {
19 }
20});
21
22var TextView = Backbone.View.extend({
23 tagName: 'div',
24 className: '',
25 initialize: function() {
26 _.bindAll(this);
27 _.bind(this.render, this);
28 },
29 render: function(el) {
30 $(el).append(this.el);
31 var str = '<h4>'+ this.model.get('title') +'</h4> <p>' +
32 this.model.get('data') + '</p>';
33 $(this.el).html(str);
34 M.appendAttrs(this.model, this.el);
35 }
36});
37
38var Table = BaseType.extend({
39 defaults: {
40 data : {
41 th: [],
42 tr:[]
43 }
44 },
45 initialize: function() {
46 }
47});
48
49var TableView = Backbone.View.extend({
50 tagName: 'table',
51 className: 'table',
52 initialize: function() {
53 _.bindAll(this);
54 _.bind(this.render, this);
55 },
56 render: function(el) {
57 var heading = this.model.get('data').th;
58 var str = '<tr>';
59
60 for(var i = 0; i < heading.length; i++) {
61 str += '<th>' + heading[i] + '</th>';
62 }
63 str += '</tr>';
64
65 _.each(this.model.get('data').tr, function(row) {
66 str += '<tr>';
67 for(var i = 0; i < row.length; i++) {
68 if(row[i].match(/http.?:/)) {
69 //console.log(row[i].match(/http:/))
70 }
71 str += '<td>'+ row[i] + '</td>';
72 }
73 str += '</tr>';
74 });
75
76 $(el).append(this.el);
77 $(this.el).html(str);
78 M.appendAttrs(this.model, this.el);
79 }
80});
81
82var Image = BaseType.extend({
83 defaults: {
84 src: ""
85 },
86 initialize:function() {
87 }
88});
89
90var ImageView = Backbone.View.extend({
91 tagName: 'image',
92 className: '',
93
94 initialize: function() {
95 _.bindAll(this);
96 _.bind(this.render, this);
97 },
98 render: function(el) {
99 $(el).append(this.el);
100 $(this.el).attr('src', this.model.get('src'));
101 M.appendAttrs(this.model, this.el);
102 }
103});
104
105
106var Video = BaseType.extend({
107 defaults: {
108 src: ""
109 },
110 initialize:function() {
111 }
112});
113
114var VideoView = Backbone.View.extend({
115 initialize: function() {
116 _.bindAll(this);
117 _.bind(this.render, this);
118 // assuming cross-domain urls will have http in the src,
119 // so also assuming they are embedded flash urls,
120 // hence iframe
121 if(this.model.get('src').match('http')) {
122 this.tagName = 'iframe';
123 }
124 // otherwise, use html5 video tag, if the video is served
125 // from the same domain
126 else {
127 this.tagName = 'video';
128 }
129 },
130 render: function(el) {
131 $(el).append(this.el);
132 $(this.el).attr('src', this.model.get('src'));
133 M.appendAttrs(this.model, this.el);
134 }
135});
136
137var RSS = BaseType.extend({
138 defaults: {
139 src: ""
140 },
141 initialize:function() {
142 }
143});
144
145var RSSView = Backbone.View.extend({
146 el: '#feeds',
147 initialize: function() {
148 _.bindAll(this);
149 _.bind(this.render, this);
150 },
151 render: function() {
152 M.populateFeeds(this.model.get('src'));
153 }
154});
155
156// Plugin model can be used to load dynamic components
157// to the website by loading external JS files.
158// Also the website can be styled by using external CSS files,
159// which can also be loaded via this plugin model.
160var Plugin = BaseType.extend({
161 defaults: {
162 src: "",
163 data: {},
164 callback: ""
165 },
166 initialize: function() {
167 if(this.get('src').match(/\.js/)) {
168 var script = document.createElement('script');
169 var callback = this.get('callback');
170 script.src = this.get('src');
171 document.body.appendChild(script);
172 script.onload = function() {
173 eval(callback);
174 };
175 }
176 else if(this.get('src').match(/\.css/)) {
177 var link = document.createElement('link');
178 link.rel = 'stylesheet';
179 link.href = this.get('src');
180 link.type = 'text/css';
181 document.body.appendChild(link);
182 }
183 }
184});
185
186var PluginView = Backbone.View.extend({
187 initialize: function() {
188 return;
189 },
190 render: function(el) {
191 return;
192 }
193});
194
195var type_map;
196M.type_map = type_map = {
197 model : {
198 'text': Text,
199 'image': Image,
200 'video': Video,
201 'rss': RSS,
202 'table': Table,
203 'plugin': Plugin
204 },
205 view: {
206 'text': TextView,
207 'image': ImageView,
208 'video': VideoView,
209 'rss': RSSView,
210 'table': TableView,
211 'plugin': PluginView
212 }
213};
214
215// model for each Page
216var Page = Backbone.Model.extend({
217 defaults: {
218 name: "index",
219 title: "",
220 children: [],
221 content: []
222 },
223 initialize: function() {
224 // adding the name of the model as its id.
225 // look up of this model through the collection
226 // is faster this way.
227 this.set({id: M.sanitize(this.get('name'))});
228 }
229});
230
231var Pages = Backbone.Collection.extend({
232 model: Page
233});
234
235var PageView = Backbone.View.extend({
236 tagName: 'div',
237 className: 'page',
238 initialize: function() {
239 _.bindAll(this);
240 _.bind(this.render, this);
241 this.render();
242 $(this.el).hide();
243 },
244 render: function() {
245 $('#content-container').append(this.el);
246 this.appendNavTemplate();
247 $(this.el).append('<h3>'+this.model.get('title')+'</h3>');
248 var self = this;
249 _.each(this.model.get('content'), function(item) {
250 var view = type_map.view[item.get('type')];
251 if(!view) {
252 console.log('Error initing view', item);
253 return;
254 }
255 if(item.get('type') === 'rss') {
256 M.rss_view = new view({model: item});
257 $(self.el).append(_.template($('#news-template').html()));
258 }
259 else {
260 var item_view = new view({model: item});
261 item_view.render(self.el);
262 }
263 });
264 },
265 appendNavTemplate: function() {
266 var li;
267 var nav_template = _.template($('#nav-template').html());
268 $(this.el).append(nav_template({
269 page: this.model.id
270 }));
271 }
272});
273
274var AppView = Backbone.View.extend({
275 el: 'body',
276 events: {
277 'click .nav li a' : 'navClicked'
278 },
279 initialize: function() {
280 _.bindAll(this);
281 },
282 render: function() {
283 $('#index').show();
284 _.each(M.pages.models, function(page) {
285 this.createNavigation(page.id);
286 }, this);
287 },
288 navClicked: function(event) {
289 $('.nav li').removeClass('active');
290 $(event.currentTarget).parent().addClass('active');
291 },
292 createNavigation: function(page) {
293 var li;
294 if(page === 'index') {
295 li = '<li class="active"><a href="#/index"> Home </a></li>';
296 $('#nav-index .nav').append(li);
297 }
298 var dropdown_template = _.template($('#nav-dropdown-template').html());
299 var children = M.pages.get(page).get('children');
300 _.each(children, function(child) {
301 child = M.sanitize(child);
302 var model = M.pages.get(child);
303 var children = model.get('children');
304 if(_.isEmpty(children)) {
305 li = '<li><a href="#/' + child + '">' + M.humanReadable(child) + '</a></li>';
306 }
307 else {
308 li = dropdown_template({
309 name: M.humanReadable(model.get('name')),
310 list: _.map(children, M.humanReadable)
311 });
312 }
313 $(li).appendTo('#nav-' + page + ' .nav');
314 });
315 },
316 updateBreadcrumbs: function(event) {
317 //TODO: write code to use bootstrap's breadcrumbs to render a
318 // navigational breadcrumb
319 }
320});
321
322var AppRouter = Backbone.Router.extend({
323 routes : {
324 'index' : 'index',
325 ':page' : 'showPage'
326 },
327 index: function() {
328 $('.page').hide();
329 $('#index').show();
330 },
331 showPage: function(page) {
332 $('.page').hide();
333 //news pages are rendered on the fly,
334 //as feeds have to be fetched.
335 if(page === 'news') {
336 M.rss_view.render();
337 }
338 $('#'+page).show();
339 $('.'+page).show();
340 }
341});
342
343/* Defining other necessary functions */
344M.init = function() {
345 M.tags = {}; //global tag cache
346 M.pages = new Pages(); //global collection of all pages
347
348 // iterate through the JSON to intialize models and views
349 _.each(M.site_content, function(page) {
350 var new_page = new Page(page);
351 var contents = [];
352 _.each(page.content, function(content) {
353 var Item = type_map.model[content.type];
354 if(!Item) {
355 console.log('Error initing item: ', content);
356 return;
357 }
358 var item = new Item(content);
359 contents.push(item);
360 M.createTagList(content, item);
361 });
362 new_page.set({content: contents});
363 var new_page_view = new PageView({model: new_page,
364 id: new_page.get('id')});
365 M.pages.add(new_page);
366 });
367
368 M.appView = new AppView();
369 M.appView.render();
370 var app_router = new AppRouter();
371 Backbone.history.start();
372 // start with index page
373 var location = window.location;
374 location.href = location.origin + location.pathname + '#/index';
375 M.simHeir();
376};
377
378// hack to simulate heirarchy among the page views
379// basically add the parent id as class in all of its children
380// elements.
381M.simHeir = function() {
382 _.each(M.pages.models, function(page) {
383 if(page.id == 'index') return;
384 _.each(page.get('children'), function(child) {
385 child = M.sanitize(child);
386 $('#'+child).addClass(page.id);
387 });
388 });
389};
390
391
392// append attributes to elements from the model
393M.appendAttrs = function(model, el) {
394 _.each(model.get('attr'), function(val, key) {
395 $(el).attr(key, val);
396 });
397}
398
399// create the list of tags and associate the objects with related tags
400M.createTagList = function(content, model) {
401 for(var i in content.tags) {
402 if(!M.tags[content.tags[i]]) {
403 M.tags[content.tags[i]] = [];
404 }
405 M.tags[content.tags[i]].push(model);
406 }
407};
408
409// Filter the tags and return only those "content" objects which match a given tag.
410// @tags should be an array
411M.filterTags = function(tags) {
412 if(!_.isArray(tags)) {
413 console.log('You have to pass an array'); //TODO: raise an exception
414 return false;
415 }
416 var list = [];
417 _.each(tags, function(item) {
418 if(M.tags[item]) {
419 list.push(M.tags[item]);
420 }
421 });
422 return _.uniq(_.flatten(list));
423};
424
425// populate with news feeds in the news section
426// gets the feeds from server side script 'feed.py'
427M.populateFeeds = function(rss_url) {
428 $('#feeds-loader').show();
429 $('.news-item-wrapper').remove();
430 jQuery.getFeed({
431 url: 'feeds',
432 type: 'GET',
433 data: "rss_url="+encodeURIComponent(rss_url),
434 success: function(feed) {
435 $('#feeds-loader').hide();
436 var template = _.template($('#news-item-template').html());
437 _.each(feed.items, function(item) {
438 x = $('#feeds').append(template({
439 title: item.title,
440 link: item.link
441 }));
442 });
443 },
444 error: function(err) {
445 $('#feeds-loader').hide();
446 $('#feeds').append('Oops, something went wrong! <br/> Please try again.');
447 }
448 });
449};
450
451
452/* Other helper functions */
453
454// change all '-' to spaces and capitalize first letter of
455// every word
456M.humanReadable = function(str) {
457 if(typeof str !== "string") {
458 str = '';
459 }
460 return '' + str.replace(/[-]+/g, ' ').replace(/[^\s]+/g, function(str) {
461 return str.substr(0,1).toUpperCase() + str.substr(1).toLowerCase();
462 });
463};
464
465// change all spaces to '-' and everything to lowercase
466M.sanitize = function(str) {
467 if(typeof str !== "string") {
468 str = '';
469 }
470 return '' + str.replace(/[\s]+/g,'-').replace(/[^\s]+/g, function(str) {
471 //TODO: handle special characters!
472 return str.replace('&', 'and').toLowerCase();
473 });
474};
475
476
477// Loader
478M.load = function(content_url) {
479 if(typeof content_url !== 'string') {
480 console.error('URL to load has to be of type string!!');//TODO: raise custom exception
481 return;
482 }
483 $.getJSON(content_url, function(data) {
484 M.site_content = data;
485 M.init();
486 });
487};
488
489})(M);