$(function() {
var FileListModel = Backbone.Model.extend({
initialize: function() {
var docurium = this.get('docurium')
this.listenTo(docurium, 'change:data', this.extract)
},
extract: function() {
var docurium = this.get('docurium')
var data = docurium.get('data')
var version = docurium.get('version')
// Function groups
var funs = _.map(data['groups'], function(group, i) {
var name = group[0]
var link = groupLink(name, version)
return {name: name, link: link, num: group[1].length}
})
// Callbacks
var callbacks = _.map(_.keys(data['callbacks']), function(name) {
var link = functionLink('callback', name, version)
return {name: name, link: link}
})
// Types
var getName = function(type) {
var name = type[0];
var link = typeLink(name, version);
return {link: link, name: name};
}
var enums = _.filter(data['types'], function(type) {
return type[1]['block'] && type[1]['type'] == 'enum';
}).map(getName)
var structs = _.filter(data['types'], function(type) {
return type[1]['block'] && type[1]['type'] != 'enum'
}).map(getName)
var opaques = _.filter(data['types'], function(type) {
return !type[1]['block']
}).map(getName)
// File Listing
var files = _.map(data['files'], function(file) {
var url = this.github_file(file['file'])
return {url: url, name: file['file']}
}, docurium)
// Examples List
var examples = []
if(data['examples'] && (data['examples'].length > 0)) {
examples = _.map(data['examples'], function(file) {
return {name: file[0], path: file[1]}
})
}
this.set('data', {funs: funs, callbacks: callbacks, enums: enums, structs: structs,
opaques: opaques, files: files, examples: examples})
},
})
var FileListView = Backbone.View.extend({
el: $('#files-list'),
template: _.template($('#file-list-template').html()),
typeTemplate: _.template($('#type-list-template').html()),
events: {
'click h3': 'toggleList',
},
toggleList: function(e) {
$(e.currentTarget).next().toggle(100)
return false
},
initialize: function() {
this.listenTo(this.model, 'change:data', this.render)
},
render: function() {
var data = this.model.get('data')
var menu = $(this.template({funs: data.funs, files: data.files, examples: data.examples}))
if (data.enums.length) {
var enumList = this.typeTemplate({title: 'Enums', elements: data.enums})
$('#types-list', menu).append(enumList)
}
if (data.structs.length) {
var structList = this.typeTemplate({title: 'Structs', elements: data.structs})
$('#types-list', menu).append(structList)
}
if (data.opaques.length) {
var opaquesList = this.typeTemplate({title: 'Opaque Structs', elements: data.opaques})
$('#types-list', menu).append(opaquesList)
}
if (data.callbacks.length) {
var callbacksList = this.typeTemplate({title: 'Callbacks', elements: data.callbacks})
$('#types-list', menu).append(callbacksList)
}
this.$el.html(menu)
return this
},
})
var VersionView = Backbone.View.extend({
el: $('#version'),
initialize: function() {
this.listenTo(this.model, 'change:version', this.render)
this.listenTo(this.model, 'change:name', this.renderName)
this.title = $('#site-title')
},
render: function() {
var version = this.model.get('version')
this.$el.text(version)
this.title.attr('href', '#' + version)
return this
},
renderName: function() {
var name = this.model.get('name')
var title = name + ' API'
this.title.text(title)
document.title = title
return this
},
})
var VersionPickerView = Backbone.View.extend({
el: $('#versions'),
list: $('#version-list'),
template: _.template($('#version-picker-template').html()),
initialize: function() {
this.listenTo(this.model, 'change:versions', this.render)
},
events: {
'click #version-picker': 'toggleList',
'click': 'hideList',
},
hideList: function() {
this.list.hide(100)
},
toggleList: function(e) {
$(e.currentTarget).next().toggle(100)
return false
},
render: function() {
var vers = this.model.get('versions')
list = this.template({versions: vers})
this.list.html(list)
return this
},
})
var ChangelogView = Backbone.View.extend({
template: _.template($('#changelog-template').html()),
itemTemplate: _.template($('#changelog-item-template').html()),
initialize: function() {
// for every version, show which functions added, removed, changed - from HEAD down
var versions = this.model.get('versions')
var sigHist = this.model.get('signatures')
var lastVer = _.first(versions)
// fill changelog struct
var changelog = {}
for(var i in versions) {
var version = versions[i]
changelog[version] = {'deletes': [], 'changes': [], 'adds': []}
}
// figure out the adds, deletes and changes
_.forEach(sigHist, function(func, fname) {
var lastv = _.last(func.exists)
var firstv = _.first(func.exists)
changelog[firstv]['adds'].push(fname)
// figure out where it was deleted or changed
if (lastv && (lastv != lastVer)) {
var vi = _.indexOf(versions,lastv)
var delv = versions[vi-1]
changelog[delv]['deletes'].push(fname)
_.forEach(func.changes, function(_, v) {
changelog[v]['changes'].push(fname)
})
}
})
var vers = _.map(versions, function(version) {
var deletes = changelog[version]['deletes']
deletes.sort()
var additions = changelog[version]['adds']
additions.sort()
var adds = _.map(additions, function(add) {
var gname = this.model.groupOf(add)
return {link: functionLink(gname, add, version), text: add}
}, this)
return {title: version, listing: this.itemTemplate({dels: deletes, adds: adds})}
}, this)
this.el = this.template({versions: vers})
},
render: function() {
return this
}
})
var FunctionModel = Backbone.Model.extend({
initialize: function() {
var gname = this.get('gname')
var fname = this.get('fname')
var docurium = this.get('docurium')
var isCallback = gname === 'callback'
var group = docurium.getGroup(gname)
var fdata = docurium.get('data')['functions']
var ldata = fdata
if (isCallback) {
var cdata = docurium.get('data')['callbacks']
ldata = cdata
} else {
var functions = _.filter(group[1], function(f){ return f != fname})
}
// Function Arguments
var args = _.map(ldata[fname]['args'], function(arg) {
return {link: this.hotLink(arg.type), name: arg.name, comment: arg.comment}
}, docurium)
var data = ldata[fname]
// function return value
var ret = data['return']
var returns = {link: docurium.hotLink(ret.type), comment: ret.comment}
// function signature
var sig = docurium.hotLink(ret.type) + ' ' + fname + '(' + data['argline'] + ');'
// version history
if (!isCallback) {
var sigHist = docurium.get('signatures')[fname]
var version = docurium.get('version')
var sigs = _.map(sigHist.exists, function(ver) {
var klass = []
if (sigHist.changes[ver])
klass.push('changed')
if (ver == version)
klass.push('current')
return {url: '#' + functionLink(gname, fname, ver), name: ver, klass: klass.join(' ')}
})
}
// GitHub link
var fileLink = docurium.github_file(data.file, data.line, data.lineto)
// link to the group
if (!isCallback) {
var version = docurium.get('version')
var alsoGroup = '#' + groupLink(group[0], version)
var alsoLinks = _.map(functions, function(f) {
return {url: '#' + functionLink(gname, f, version), name: f}
})
}
this.set('data', {name: fname, data: data, args: args, returns: returns, sig: sig,
sigs: sigs, fileLink: fileLink, groupName: gname,
alsoGroup: alsoGroup, alsoLinks: alsoLinks})
}
})
var FunctionView = Backbone.View.extend({
template: _.template($('#function-template').html()),
argsTemplate: _.template($('#function-args-template').html()),
render: function() {
document.body.scrollTop = document.documentElement.scrollTop = 0;
var data = this.model.get('data')
data.argsTemplate = this.argsTemplate
var cont = this.template(data)
this.el = cont
return this
},
})
var GroupCollection = Backbone.Collection.extend({
initialize: function(o) {
this.docurium = o.docurium
this.listenTo(this.docurium, 'change:data', this.refill)
},
refill: function(o, doc) {
var data = o.changed.data
var sigHist = this.docurium.get('signatures')
var version = this.docurium.get('version')
var groups = _.map(data.groups, function(group) {
var gname = group[0]
var funs = _.map(group[1], function(fun) {
var klass = ''
if (sigHist[fun].changes[version])
klass = 'changed'
if (version == _.first(sigHist[fun].exists))
klass = 'introd'
return {name: fun, url: '#' + functionLink(gname, fun, version), klass: klass}
})
return {name: gname, funs: funs}
})
this.reset(groups)
},
})
var MainListView = Backbone.View.extend({
template: _.template($('#index-template').html()),
render: function() {
this.el = this.template({groups: this.collection.toJSON()})
return this
},
})
var TypeModel = Backbone.Model.extend({
initialize: function() {
var typename = this.get('typename')
var docurium = this.get('docurium')
var version = docurium.get('version')
var types = docurium.get('data')['types']
var tdata = _.find(types, function(g) {
return g[0] == typename
})
var tname = tdata[0]
var data = tdata[1]
var toFuncPair = function(fun) {
var gname = this.groupOf(fun)
var url = '#' + functionLink(gname, fun, version)
return {name: fun, url: url}
}
var toTypePair = function(type) {
var url = '#' + typeLink(type, version)
return {name: type, url: url}
}
var returns = _.map(data.used.returns, toFuncPair, docurium)
var needs = _.map(data.used.needs, toFuncPair, docurium)
var fields = _.map(data.used.fields, toTypePair, docurium)
var fileLink = {name: data.file, url: docurium.github_file(data.file, data.line, data.lineto)}
// Hot link our field types
data.fields = _.map(data.fields, function(field) {
return {type: this.hotLink(field.type), name: field.name, comments: field.comments}
}, docurium)
this.set('data', {tname: tname, data: data, returns: returns, needs: needs, fields: fields, fileLink: fileLink})
}
})
var TypeView = Backbone.View.extend({
enumTemplate: _.template($('#enum-template').html()),
structTemplate: _.template($('#struct-template').html()),
usesTemplate: _.template($('#uses-template').html()),
render: function() {
var type = this.model.get('data')
var uses = this.usesTemplate(type)
var template = type.data.type == 'struct' ? this.structTemplate : this.enumTemplate
var content = template({type: type, uses: uses})
this.el = content
return this
}
})
var GroupView = Backbone.View.extend({
template: _.template($('#group-template').html()),
initialize: function(o) {
var group = o.group
var gname = group[0]
var fdata = o.functions
var cdata = o.callbacks
var version = o.version
this.gname = gname.charAt(0).toUpperCase() + gname.substring(1).toLowerCase()
this.functions = _.map(group[1], function(name) {
var url = '#' + functionLink(gname, name, version)
var d = fdata[name]
return {name: name, url: url, returns: d['return']['type'], argline: d['argline'],
description: d['description'], comments: d['comments'], args: d['args']}
})
},
render: function() {
var content = this.template({gname: this.gname, functions: this.functions})
this.el = content
return this
},
})
var SearchFieldView = Backbone.View.extend({
tagName: 'input',
el: $('#search-field'),
events: {
'keyup': function() {
this.trigger('keyup')
if (this.$el.val() == '')
this.trigger('empty')
}
},
})
var SearchCollection = Backbone.Collection.extend({
defaults: {
value: '',
},
initialize: function(o) {
this.field = o.field
this.docurium = o.docurium
this.listenTo(this.field, 'keyup', this.keyup)
},
keyup: function() {
var newValue = this.field.$el.val()
if (this.value == newValue || newValue.length < 3)
return
this.value = newValue
this.refreshSearch()
},
refreshSearch: function() {
var docurium = this.docurium
var value = this.value
var data = docurium.get('data')
var searchResults = []
var version = docurium.get('version')
// look for functions (name, comment, argline)
_.forEach(data.functions, function(f, name) {
var gname = docurium.groupOf(name)
// look in the function name first
if (name.search(value) > -1) {
var gl = functionLink(gname, name, version)
var url = '#' + gl
searchResults.push({url: url, name: name, match: 'function', navigate: gl})
return
}
// if we didn't find it there, let's look in the argline
if (f.argline && f.argline.search(value) > -1) {
var gl = functionLink(gname, name, version)
var url = '#' + gl
searchResults.push({url: url, name: name, match: f.argline, navigate: gl})
}
})
// look for types
data.types.forEach(function(type) {
var name = type[0]
var tl = typeLink(name, version)
var url = '#' + tl
if (name.search(value) > -1) {
searchResults.push({url: url, name: name, match: type[1].type, navigate: tl})
}
})
// look for callbacks
_.each(data.callbacks, function(f, name) {
if (name.search(value) > -1) {
var gl = functionLink('callback', name, version)
var url = '#' + gl
searchResults.push({url: url, name: name, match: 'callback', navigate: gl})
return
}
})
this.reset(searchResults)
},
})
var SearchView = Backbone.View.extend({
template: _.template($('#search-template').html()),
// initialize: function() {
// this.listenTo(this.model, 'reset', this.render)
// },
render: function() {
var content = this.template({results: this.collection.toJSON()})
this.el = content
}
})
var MainView = Backbone.View.extend({
el: $('#content'),
setActive: function(view) {
view.render()
if (this.activeView) {
this.stopListening()
this.activeView.remove()
}
this.activeView = view
// make sure we know when the view wants to render again
this.listenTo(view, 'redraw', this.render)
this.$el.html(view.el)
// move back to the top when we switch views
document.body.scrollTop = document.documentElement.scrollTop = 0;
},
render: function() {
this.$el.html(this.activeView.el)
},
})
// our document model - stores the datastructure generated from docurium
var Docurium = Backbone.Model.extend({
defaults: {'version': 'unknown'},
initialize: function() {
this.loadVersions()
this.bind('change:version', this.loadDoc)
},
loadVersions: function() {
$.getJSON("project.json").then(function(data) {
docurium.set({'versions': data.versions, 'github': data.github, 'signatures': data.signatures, 'name': data.name})
docurium.setVersion()
})
},
setVersion: function (version, success) {
if(!version) {
version = _.first(docurium.get('versions'))
}
current = docurium.get('version')
if (current == version) {
if (success)
success();
return;
}
docurium.set({version: version})
p = this.loadDoc()
if (success)
p.then(success)
},
loadDoc: function() {
version = this.get('version')
return $.getJSON(version + '.json').then(function(data) {
docurium.set({data: data})
})
},
getGroup: function(gname) {
var groups = docurium.get('data')['groups']
return _.find(groups, function(g) {
return g[0] == gname
})
},
// look for structs and link them
hotLink: function(text) {
types = this.get('data')['types']
var version = this.get('version')
_.each(types, function(type) {
var typeName = type[0];
var typeData = type[1];
var re = new RegExp('\\b' + typeName + '\\b', 'gi');
var link = $('').attr('href', '#' + typeLink(typeName, version)).append(typeName)[0]
text = text.replace(re, link.outerHTML + ' ')
});
var callbacks = this.get('data')['callbacks']
_.each(callbacks, function(cb, typeName) {
var re = new RegExp(typeName + '$', 'gi');
var link = $('').attr('href', '#' + functionLink('callback', typeName, version)).append(typeName)[0]
text = text.replace(re, link.outerHTML + ' ')
});
return text
},
groupOf: function (func) {
if(func in this.get('data')['functions']) {
return this.get('data')['functions'][func]['group']
}
return 'callback'
},
github_file: function(file, line, lineto) {
var data = this.get('data')
url = ['https://github.com', docurium.get('github'),
'blob', docurium.get('version'), data.prefix, file].join('/')
if(line) {
url += '#L' + line.toString()
if(lineto) {
url += '-L' + lineto.toString()
}
} else {
url += '#files'
}
return url
},
})
var Workspace = Backbone.Router.extend({
routes: {
"": "index",
":version": "main",
":version/group/:group": "group",
":version/type/:type": "showtype",
":version/group/:group/:func": "groupFun",
":version/search/:query": "search",
"p/changelog": "changelog",
},
initialize: function(o) {
this.doc = o.docurium
this.search = o.search
this.mainView = o.mainView
this.groups = o.groups
},
index: function() {
// set the default version
this.doc.setVersion()
// and replate our URL with it, to avoid a back-button loop
this.navigate(this.doc.get('version'), {replace: true, trigger: true})
},
main: function(version) {
var self = this
this.doc.setVersion(version, function() {
var view = new MainListView({collection: self.groups})
self.mainView.setActive(view)
})
},
group: function(version, gname) {
var self = this
this.doc.setVersion(version, function() {
var group = self.doc.getGroup(gname)
var fdata = self.doc.get('data')['functions']
var cdata = self.doc.get('data')['callbacks']
var version = self.doc.get('version')
var view = new GroupView({group: group, functions: fdata, callbacks: cdata, version: version})
self.mainView.setActive(view)
});
},
groupFun: function(version, gname, fname) {
var self = this
this.doc.setVersion(version, function() {
var model = new FunctionModel({docurium: self.doc, gname: gname, fname: fname})
var view = new FunctionView({model: model})
self.mainView.setActive(view)
})
},
showtype: function(version, tname) {
var self = this
this.doc.setVersion(version, function() {
var model = new TypeModel({docurium: self.doc, typename: tname})
var view = new TypeView({model: model})
self.mainView.setActive(view)
})
},
search: function(version, query) {
var self = this
this.doc.setVersion(version, function() {
var view = new SearchView({collection: self.search})
$('#search-field').val(query).keyup()
self.mainView.setActive(view)
})
},
changelog: function(version, tname) {
// let's wait to process it until it's asked, and let's only do
// it once
if (this.changelogView == undefined) {
this.changelogView = new ChangelogView({model: this.doc})
}
this.doc.setVersion(undefined, function() {
this.mainView.setActive(this.changelogView)
})
},
});
function functionLink(gname, fname, version) {
return version + "/group/" + gname + '/' + fname
}
function groupLink(gname, version) {
return version + "/group/" + gname
}
function typeLink(tname, version) {
return version + "/type/" + tname
}
function searchLink(term, version) {
return version + "/search/" + term
}
//_.templateSettings.variable = 'rc'
var docurium = new Docurium
var searchField = new SearchFieldView({id: 'search-field'})
var searchCol = new SearchCollection({docurium: docurium, field: searchField})
var groupCol = new GroupCollection({docurium: docurium})
var mainView = new MainView()
var router = new Workspace({docurium: docurium, search: searchCol, mainView: mainView,
groups: groupCol})
searchField.on('empty', function() {
router.navigate(docurium.get('version'), {trigger: true})
})
docurium.once('change:data', function() {Backbone.history.start()})
var fileList = new FileListModel({docurium: docurium})
var fileListView = new FileListView({model: fileList})
var versionView = new VersionView({model: docurium})
var versionPickerView = new VersionPickerView({model: docurium})
searchCol.on('reset', function(col, prev) {
if (col.length == 1) {
router.navigate(col.pluck('navigate')[0], {trigger: true, replace: true})
} else {
var version = docurium.get('version')
// FIXME: this keeps recreating the view
router.navigate(searchLink(col.value, version), {trigger: true})
}
})
})