diff --git a/.gitignore b/.gitignore index 28bdc50..95b460a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ /dump/ /node_modules/jade/node_modules/transformers/test/ *.tgz -/node_modules/buster +/node_modules /node_modules/express /node_modules/.bin /node_modules/mocha diff --git a/AdminForm.js b/AdminForm.js index bfbde50..affb590 100644 --- a/AdminForm.js +++ b/AdminForm.js @@ -2,10 +2,11 @@ if (!module.parent) console.error('Please don\'t call me directly.I am just the main app\'s minion.') || process.process.exit(1); var forms = require('./forms') - , mongoose = require.main.require('mongoose') + , mongoose = require('mongoose') , fields = forms.fields , widgets = forms.widgets , MongooseForm = forms.forms.MongooseForm + ,_ = require('lodash') , jest = require('jest'); var api_loaded = false; @@ -36,7 +37,8 @@ var AdminForm = exports.AdminForm = MongooseForm.extend({ value.options.widget_options.data = value.options.widget_options.data || {}; value.options.widget_options.data.data = encodeURIComponent(JSON.stringify({ model: value.options.ref, - query: value.options.query || '/__value__/i.test(this.name || this.title || this._id.toString())' + query: value.options.query || '/__value__/i.test(this.name || this.title || this._id.toString())', + constraints:value.options.constraints })); value.widget = new widgets.AutocompleteWidget(value.options.widget_options); } @@ -45,7 +47,21 @@ var AdminForm = exports.AdminForm = MongooseForm.extend({ value.widget = new widgets.ComboBoxWidget(value.options.widget_options); } else if (value instanceof fields.ListField) { - self.scanFields(value.fields); + if(value.fields['__self__']){ + var innerField = value.fields['__self__']; + if(innerField instanceof fields.RefField){ + var options = _.extend({}, + innerField.options, + value.options,{ + required:value.required, + widget:null + + }); + form_fields[key] = new fields.MultiRefField(options,innerField.ref); + } + } + else + self.scanFields(value.fields); } }); }, @@ -86,9 +102,24 @@ var _JestAdminResource = jest.Resource.extend({ }); } else { - var escaped_filters = filters.query.replace(_escaper, "\\$&"); - var query = data.query.replace(/__value__/g, escaped_filters); - model.find({$where: query}).limit(40).exec(function (err, results) { + var qry; + var escaped_filters = filters.query.replace(_escaper, "\\$&") || '.'; + var obj = data.constraints || {}; + if(data.query.indexOf('__value__') > -1){ + var query = data.query.replace(/__value__/g, escaped_filters); + obj['$where'] = query; + qry = model.find(obj); + } + else if(Array.isArray(data.query)){ + qry = model.find(obj).or(data.query.map(function(field){ + var obj = {}; + obj[field] = new RegExp('^' + escaped_filters); + return obj; + })); + }else{ + qry = model.find(obj).where(data.query,new RegExp('^' + escaped_filters)); + } + qry.limit(20).exec(function (err, results) { if (results) { if (results.objects) { results = results.objects; @@ -109,14 +140,17 @@ exports.loadApi = function (app, path) { api_loaded = true; }; +exports.AdminForm.getApiPath = function(){ + return api_path; +} var crypt = require('./models/mongoose_admin_user').crypt; exports.AdminUserForm = AdminForm.extend({ - init:function(request,options) + init:function(request,options,model) { - this._super(request,options,mongoose.model('_MongooseAdminUser')); + this._super(request,options,model || mongoose.model('MongooseAdminUser')); } ,get_fields:function(){ this._super(); @@ -131,7 +165,12 @@ exports.AdminUserForm = AdminForm.extend({ this.fields['password_again'] = new forms.fields.StringField({widget:forms.widgets.PasswordWidget,label:'Again'}); - this.fieldsets[0].fields = ['username','is_superuser','permissions','current_password','password','password_again']; + let index = this.fieldsets[0].fields.indexOf('passwordHash'); + if(index < 0) + index = this.fieldsets[0].fields.length; + this.fieldsets[0].fields.splice(index,1,'current_password','password','password_again'); + if(this.fields['permissions']) + this.fields['permissions'].widget.limit = 500; return fields; }, diff --git a/MongooseAdmin.js b/MongooseAdmin.js index 6c85ff7..ecda60b 100644 --- a/MongooseAdmin.js +++ b/MongooseAdmin.js @@ -1,13 +1,16 @@ 'use strict'; var MongooseAdminUser = require('./models/mongoose_admin_user.js').MongooseAdminUser, - _ = require('lodash'), - async = require('async'), - permissions = require('./models/permissions'), - mongoose = require.main.require('mongoose'), AdminForm = require('./AdminForm').AdminForm, + _ = require('lodash'), forms = require('./forms').forms, + async = require('async'), + + dependencies = require('./dependencies'); + var permissions = require('./models/permissions'); + +var mongoose = require('mongoose'); /** * MongooseAdmin Constructor * @@ -18,6 +21,8 @@ var MongooseAdmin = module.exports = function (app, root) { this.root = root; this.models = {}; this.title = "Backoffice"; + this.tabs = []; + this.dialogs = {}; }; /** @@ -54,33 +59,51 @@ function buildModelFilters (model, filters, dict) { async.forEach( filters, function (filter, cbk) { - model.collection.distinct(filter, function (err, results) { - if (results) { - if (results[0] && Array.isArray(results[0])) { - results = _.flatten(results); - } - if (results.length > 30) - results.splice(5); - if (model.schema.paths[filter] && model.schema.paths[filter].options.ref) { - mongoose.model(model.schema.paths[filter].options.ref).find() - .where('_id').in(results) - .exec(function (err, refs) { - if (refs) - dict.push({ key: filter, isString: false, values: _.map(refs, function (ref) { - return { value: ref.id, text: ref.toString()}; - }) }); - cbk(err); - }); - } - else { - dict.push({key: filter, values: _.map(results, function (result) { - return { value: result, text: result, isString: model.schema.paths[filter] && model.schema.paths[filter].options && model.schema.paths[filter].options.type == String }; - })}); - cbk(); - } - } - else - cbk(err); + if(typeof(filter) == 'object'){ + dict.push(filter); + return cbk(); + } + if(model.schema.paths[filter] && (model.schema.paths[filter].type == Boolean || (model.schema.paths[filter].options && model.schema.paths[filter].options.type == Boolean))){ + dict.push({key:filter,values:[{value:true,text:'True'},{value:false,text:'False'}]}); + return cbk(); + } + if(model.schema.paths[filter] && model.schema.paths[filter].options && model.schema.paths[filter].options.enum){ + dict.push({key:filter,values:model.schema.paths[filter].options.enum.map(function(option) { return {value:option,text:option,isString:true}; })}); + return cbk(); + } + if(model.schema.paths[filter] && model.schema.paths[filter].options && model.schema.paths[filter].options.ref){ + var f = {key:filter}; + dict.push(f); + mongoose.model(model.schema.paths[filter].options.ref).find().limit(40).exec(function(err,docs){ + if(err) + return cbk(err); + + f.values = docs.map(function(doc){ + return {value:doc._id,text:doc + '',isString:false}; + }); + cbk(); + }); + return; + } + var sel = {}; + sel[filter] = 1; + // dont use distinct + model.find({}).select(sel).lean(true).limit(100).exec(function(err,docs){ + if(err) + return cbk(err); + var distinct = {}; + docs.forEach(function(doc){ + distinct[doc[filter]] = true; + }); + + var isStr = model.schema.paths[filter] && model.schema.paths[filter].options && model.schema.paths[filter].options.type == String; + dict.push({ + key: filter, + values: Object.keys(distinct).map(function (result) { + return { value: result, text: result,isString:isStr }; + }) + }); + cbk(); }); }, function () { @@ -101,27 +124,47 @@ MongooseAdmin.prototype.registerMongooseModel = function (name, model, fields, o value: 'delete', label: 'Delete', func: function (user, ids, callback) { + if(MongooseAdmin.singleton.ignoreDependencies) + return removeDocs(ids,callback); + //noinspection JSUnresolvedFunction async.map( ids, - _.partial(dependencies.check, models, name), + _.partial(dependencies.check,true, models, name), function (err, results) { if (err) return callback(err); results = _(results).compact().object().valueOf(); var with_deps = ids.filter(function (id) {return id in results;}); + if(with_deps.length) + return callback(new Error("can't delete " + with_deps.join(',') + " as they have dependencies")); + var no_dependencies = _.difference(ids, with_deps); - return model.remove({_id: {'$in': no_dependencies}}, function(err, docs) { - err = err || (!with_deps.length)? null : new Error("can't delete " + with_deps.join(',') + " as they have dependencies"); - return callback(err, docs) - }); + removeDocs(no_dependencies,callback); } ); + + function removeDocs(ids,cbk){ + model.find().where('_id').in(ids).exec(function(err,docs){ + if(err) + return cbk(err); + + async.each(docs,function(doc,cbk){ + doc.remove(cbk); + },function(err){ + cbk(err); + }); + }); + } } }); + if(model.schema.paths._preview){ + options.preview = model.schema.paths._preview.options.link; + initPreviewModel(model); + } var filters = []; buildModelFilters(model, options.filters, filters); - + options.limit = options.limit || function(user,query,cbk){ cbk(); }; this.models[name] = { model: model, filters: filters, @@ -131,6 +174,7 @@ MongooseAdmin.prototype.registerMongooseModel = function (name, model, fields, o fields: fields }; + console.log('\x1b[36mformage-admin:\x1b[0m %s', name); permissions.registerModel(name); @@ -190,7 +234,12 @@ MongooseAdmin.prototype.getRegisteredModels = function (user, callback) { }) .filter(function (model) { return permissions.hasPermissions(user, model.modelName, 'view'); - }); + }).map(function(model){ + return { + model:model, + createable:permissions.hasPermissions(user,model.modelName,'create') + }; + }); callback(null, out_models); }; @@ -202,21 +251,26 @@ MongooseAdmin.prototype.getRegisteredModels = function (user, callback) { * * @api public */ -MongooseAdmin.prototype.modelCounts = function(collectionName,filters, onReady) { - if(this.models[collectionName].is_single) { +MongooseAdmin.prototype.modelCounts = function(user,collectionName,filters, onReady) { + var ModelObj = this.models[collectionName]; + if(ModelObj.is_single) { onReady(null,1); return; } - var model = this.models[collectionName].model; - - this.models[collectionName].model.count(filters, function(err, count) { - if (err) { - console.error('Unable to get counts for model because: ' + err); - onReady(null,0); - } else { - onReady(null, count); - } - }); + var query = ModelObj.model.count(filters); + ModelObj.options.limit(user,query,function(err){ + if(err) + return onReady(err); + + query.exec(function(err, count) { + if (err) { + console.error('Unable to get counts for model because: ' + err); + onReady(null,0); + } else { + onReady(null, count); + } + }); + }) }; var IS_OLD_MONGOOSE = Number(mongoose.version.split('.')[0]) < 3; @@ -243,7 +297,7 @@ function mongooseSort(query,sort) { * @param sort * @param {Function} onReady */ -MongooseAdmin.prototype.listModelDocuments = function(collectionName, start, count,filters,sort, onReady) { +MongooseAdmin.prototype.listModelDocuments = function(user,collectionName, start, count,filters,sort, onReady) { var listFields = this.models[collectionName].options.list; if (!listFields) { return onReady(null, []); @@ -265,35 +319,72 @@ MongooseAdmin.prototype.listModelDocuments = function(collectionName, start, cou query.populate(populate); }); } - return query.skip(start).limit(count).execFind(function (err, documents) { - if (err) { - console.error('Unable to get documents for model because: ' + err); - onReady(null, []); - } else { - var filteredDocuments = []; - documents.forEach(function (document) { - var d = {}; - d['_id'] = document['_id']; - listFields.forEach(function (listField) { - d[listField] = typeof(document[listField]) == 'function' ? document[listField]() : document.get(listField); + query._admin = true; + this.models[collectionName].options.limit(user,query,function(err){ + if(err) + return onReady(err); + return query.skip(start).limit(count).exec(function (err, documents) { + if (err) { + console.error('Unable to get documents for model because: ' + err); + onReady(null, []); + } else { + var filteredDocuments = []; + documents.forEach(function (document) { + var d = {}; + d['_id'] = document['_id']; + listFields.forEach(function (listField) { + d[listField] = typeof(document[listField]) == 'function' ? document[listField]() : document.get(listField); + }); + filteredDocuments.push(d); }); - filteredDocuments.push(d); - }); - onReady(null, filteredDocuments); - } + onReady(null, filteredDocuments); + } + }); }); }; +MongooseAdmin.prototype.streamModelDocuments = function(user,collectionName, filters,sort, onReady) { + var query = this.models[collectionName].model.find(filters); -MongooseAdmin.prototype.getDocument = function(collectionName, documentId, onReady) { - this.models[collectionName].model.findById(documentId, function(err, document) { - if (err) { - console.log('Unable to get document because: ' + err); - onReady('Unable to get document', null); - } else { - onReady(null, document); - } + var sorts = this.models[collectionName].options.order_by || []; + var populates = this.models[collectionName].options.list_populate; + if (sort) + sorts.unshift(sort); + if (sorts) { + for (var i = 0; i < sorts.length; i++) + mongooseSort(query, sorts[i]); + } + if (populates) { + _.each(populates, function (populate) { + query.populate(populate); + }); + } + query._admin = true; + this.models[collectionName].options.limit(user,query,function(err){ + if(err) + return onReady(err); + var stream = query.stream(); + onReady(null,stream); + }); +}; + + +MongooseAdmin.prototype.getDocument = function(user,collectionName, documentId, filters, onReady) { + filters._id = documentId; + var query = this.models[collectionName].model.findOne(filters); + query._admin = true; + this.models[collectionName].options.limit(user,query,function(err){ + if(err) + return onReady(err); + query.exec(function(err, document) { + if (err) { + console.log('Unable to get document because: ' + err); + onReady('Unable to get document', null); + } else { + onReady(null, document); + } + }); }); }; @@ -309,7 +400,10 @@ MongooseAdmin.prototype.createDocument = function (req, user, collectionName, pa if (err) return onReady(err); if (!valid) return onReady(form, null); return form.save(function (err, document) { - if (err) return onReady(form); + if (err) { + console.error('Admin save error',err); + return onReady(form); + } if (self.models[collectionName].options && self.models[collectionName].options.post) { document = self.models[collectionName].options.post(document); } @@ -321,20 +415,84 @@ MongooseAdmin.prototype.createDocument = function (req, user, collectionName, pa }; -MongooseAdmin.prototype.updateDocument = function (req, user, collectionName, documentId, params, onReady) { +/** + * Sets the preview model so it will init the + * @param model + */ +function initPreviewModel(model){ + var _init = model.prototype.init; + model.prototype.init = function(doc,query,cbk){ + var self = this; + if(query._admin) + return _init.call(this,doc,query,cbk); + + _init.call(this,doc,query,function(err){ + if(err) return cbk(err); + if(self._preview){ + var previewDict; + try { + previewDict = JSON.parse(self._preview); + } + catch(e) { } + if(previewDict && previewDict._previewDate && new Date() - new Date(previewDict._previewDate) < 1000 * 60) { + delete previewDict._previewDate; + for(var key in previewDict) + self[key] = previewDict[key]; + // disable save for preview items + self.save = function(cbk) { return cbk && cbk(null,this); }; + } + else + self._preview = null; + } + cbk(); + }); + }; +} + + +MongooseAdmin.prototype.saveForPreview = function(form,cbk){ + + var previewDict = {}; + for (var field_name in form.clean_values) { + if(field_name != '_preview') + previewDict[field_name] = form.clean_values[field_name]; + } + + if(Object.keys(previewDict).length){ + previewDict._previewDate = new Date(); + form.instance._preview = JSON.stringify(previewDict); + form.instance.markModified('_preview'); + } + form.instance.save(cbk); +} + + +MongooseAdmin.prototype.updateDocument = function (req, user, collectionName, documentId, filters, params, onReady) { var self = this, model = this.models[collectionName].model; if (!permissions.hasPermissions(user, collectionName, 'update')) return onReady('unauthorized'); var FormType2 = this.models[collectionName].options.form || AdminForm; - return model.findById(documentId, function (err, document) { + filters._id = documentId; + var qry = model.findOne(filters); + qry._admin = true; + return qry.exec(function (err, document) { if (err) return onReady(err, null); + var preview = params['_preview']; + delete params['_preview']; var form = new FormType2(req, { instance: document, data: params }, model); form.init_fields(); return form.is_valid(function (err, valid) { if (err || !valid) return onReady(err || form, null); + if(preview){ + self.saveForPreview(form,onReady); + return; + } return form.save(function (err, document) { - if (err) return onReady(form, null); + if (err) { + console.error('Admin save error',err); + return onReady(form, null); + } if (self.models[collectionName].options && self.models[collectionName].options.post) { document = self.models[collectionName].options.post(document); } @@ -351,11 +509,16 @@ MongooseAdmin.prototype.deleteDocument = function(user, collectionName, document var self = this; var model = this.models[collectionName].model; if (!permissions.hasPermissions(user, collectionName, 'delete')) return onReady('unauthorized'); - return model.findById(documentId, function (err, document) { + var qry = model.findById(documentId); + qry._admin = true; + return qry.exec(function (err, document) { if (err) return onReady(err); if (!document) { return onReady('Document not found'); } + if(self.ignoreDependencies) + return document.remove(onReady); + return dependencies.unlink(self.models, collectionName, documentId, function (err) { if (err) return onReady('unlink dependencies failed'); document.remove(); @@ -381,11 +544,11 @@ MongooseAdmin.prototype.orderDocuments = function (user, collectionName, data, c }; -MongooseAdmin.prototype.actionDocuments = function (user, collectionName, actionId, data, onReady) { +MongooseAdmin.prototype.actionDocuments = function (user, collectionName, actionId, data, req, onReady) { if (!permissions.hasPermissions(user, collectionName, 'action')) return onReady('unauthorized'); var action = _.find(this.models[collectionName].options.actions, {value: actionId}); if (!action) return onReady('no action'); - return action.func(user, data.ids, onReady); + return action.func(user, data.ids, onReady,data.data, req); }; @@ -430,16 +593,9 @@ MongooseAdmin.prototype.login = function(username, password, onReady) { }); }; - -exports.loadApi = require('./AdminForm').loadApi; - -exports.AdminForm = AdminForm; - -exports.AdminUserForm = require('./AdminForm').AdminUserForm; - MongooseAdmin.prototype.registerAdminUserModel = function(name,options){ - this.registerMongooseModel(name || 'Admin Users',mongoose.model('_MongooseAdminUser'),null, _.extend({ - form:exports.AdminUserForm, + this.registerMongooseModel(name || 'Admin Users',mongoose.model('MongooseAdminUser'),null, _.extend({ + form:require('./AdminForm').AdminUserForm, list:['username'], order_by:['username'] },options||{})); diff --git a/dependencies.js b/dependencies.js index 10bae9f..5920990 100644 --- a/dependencies.js +++ b/dependencies.js @@ -1,7 +1,11 @@ var async = require('async'); var _ = require('lodash'); -exports.check = function (modelPrefs, modelName, id, callback) { +var NO_UNLINK_POLICY = 0; +var REMOVE_POLICY = 1; +var UPDATE_POLICY = 2; + +exports.check = function (remove,modelPrefs, modelName, id, callback) { //noinspection JSValidateTypes var models_with_deps = _(modelPrefs) .pluck('model') @@ -14,7 +18,7 @@ exports.check = function (modelPrefs, modelName, id, callback) { .pluck('path') .tap(function(paths) {ref_paths = paths}) // map each path to the id of our parent - .map(function(path) {return _.object([[path, {'$in': id}]]);}) + .map(function(path) {return _.object([[path,id]]);}) .valueOf(); if (!new_paths.length) return null; return {model:model, query:new_paths, paths:ref_paths}; @@ -25,53 +29,70 @@ exports.check = function (modelPrefs, modelName, id, callback) { return async.map( models_with_deps, function (tuple, cb) { - tuple.model.find({ '$or': tuple.paths}).limit(3).exec(cb); + tuple.model.find({ '$or': tuple.query}).exec(cb); }, function (err, results) { var all_dep_docs = _.flatten(results); if (!all_dep_docs.length) return callback(err, null); - return callback(null, [id, all_dep_docs]); + if(!remove) + return callback(err,[id, all_dep_docs]); + var policies = all_dep_docs.map(function(doc){ + return checkDependencyPolicy(doc,modelName,id); + }); + if(policies.indexOf(NO_UNLINK_POLICY) > -1) + return callback(null, [id, all_dep_docs]); + else{ + async.each(all_dep_docs,function(dep,cbk,i){ + var policy = policies[i]; + if(policy == REMOVE_POLICY) + dep.remove(cbk); + else + dep.save(cbk); + },function(err){ + return callback(err,[]); + }); + } + } ); -}; +} +/** + * Check the unlink policy of this document + * @param doc + * Dependent document + * @param modelName + * Root model name + * @param id + * Root document id + * @return {Number} + * 0 - no policy, + * 1 - remove dependency, + * 2 - update dependency + */ +function checkDependencyPolicy(doc,modelName,id){ + var schema = doc.schema, + shouldSave = false, + shouldRemove = false; -exports.unlink = function (models, model, id, callback) { - exports.check(models, model, id, function (err, args) { - if (err) return callback(err); - var id = args[0]; - var deps = args[1] || []; - return async.each(deps, function (dep, cbk) { - var schema = dep.schema, - shouldSave = false, - shouldRemove = false; + Object.keys(schema.paths).forEach(function (fieldName) { + if ((schema.paths[fieldName].options.ref) && (schema.paths[fieldName].options.ref === modelName) && (doc[fieldName] + '' === id)) { + //noinspection JSUnresolvedVariable + switch (schema.paths[fieldName].options.onDelete) { + case 'delete': + shouldRemove = true; + break; - Object.keys(schema.paths).forEach(function (fieldName) { - if ((schema.paths[fieldName].options.ref) && (schema.paths[fieldName].options.ref === model) && (dep[fieldName] + '' === id)) { - //noinspection JSUnresolvedVariable - switch (schema.paths[fieldName].options.onDelete) { - case 'delete': - shouldRemove = true; - break; - - case 'setNull': - dep[fieldName] = null; - shouldSave = true; - break; - } - } - }); - if (shouldRemove) { - dep.remove(cbk); - } - else { - if (shouldSave) { - dep.save(cbk); - } - else { - cbk(); - } + case 'setNull': + doc[fieldName] = null; + shouldSave = true; + break; } - }, callback); + } }); + return shouldRemove ? REMOVE_POLICY :( shouldSave ? UPDATE_POLICY : NO_UNLINK_POLICY); +} + +exports.unlink = function(modelPrefs,modelName,id,cbk){ + exports.check(true,modelPrefs,modelName,id,cbk); }; diff --git a/forms/fields.js b/forms/fields.js index 524b77f..7a23f5a 100644 --- a/forms/fields.js +++ b/forms/fields.js @@ -8,9 +8,18 @@ var widgets = require('./widgets'), common = require('./common'), path = require('path'), fs = require('fs'), - util = require('util'), - cloudinary = require('cloudinary'); -var mongoose = require.main.require('mongoose'); + util = require('util'); + + +var cloudinary; + +try { + cloudinary = require('cloudinary'); + } catch(ex) {} + +var mongoose = require('mongoose'); + +var UPLOAD_DIRECTROY = (path.join(__dirname, '..','..', '..', 'public', 'cdn') + '/'); exports.setAmazonCredentials = function (credentials) { try { @@ -18,14 +27,26 @@ exports.setAmazonCredentials = function (credentials) { module.knox_client = knox.createClient(credentials); } catch (e) { - util.puts('no knox'); + console.error('Amazon credentials added, but no knox package found'); } }; +exports.setUploadDirectory = function(folder){ + UPLOAD_DIRECTROY = folder; +} + exports.getKnoxClient = function () { return module.knox_client; }; +let FileStorage; +exports.setFileStorage = function(storage) { + FileStorage = storage; +} +exports.getFileStorage = function(){ + return FileStorage; +} + var global_counter = 0; @@ -36,13 +57,16 @@ var BaseField = exports.BaseField = Class.extend({ init: function (options) { options = options || {}; this.options = options; + this.emptyIsNull = typeof(options.emptyIsNull) == 'undefined' ? true : options.emptyIsNull; this['default'] = options['default']; this.required = options.required == null ? false : options.required; + this.readOnly = options.readOnly || false; this.validators = options.validators || []; var widget_options = _.extend({}, options, options.widget_options); options.widget_options = widget_options; widget_options.attrs = options.attrs || {}; widget_options.required = widget_options.required == null ? this.required : widget_options.required; + widget_options.help = options.help; this.widget = new options.widget(widget_options); this.value = null; this.errors = []; @@ -86,7 +110,7 @@ var BaseField = exports.BaseField = Class.extend({ res.write('\n'); }, render_with_label: function (res) { - res.write('
- -
- -- Underscore is a - utility-belt library for JavaScript that provides a lot of the - functional programming support that you would expect in - Prototype.js - (or Ruby), - but without extending any of the built-in JavaScript objects. It's the - tie to go along with jQuery's tux, - and Backbone.js's suspenders. -
- -- Underscore provides 80-odd functions that support both the usual - functional suspects: map, select, invoke — - as well as more specialized helpers: function binding, javascript - templating, deep equality testing, and so on. It delegates to built-in - functions, if present, so modern browsers will use the - native implementations of forEach, map, reduce, - filter, every, some and indexOf. -
- -- A complete Test & Benchmark Suite - is included for your perusal. -
- -- You may also read through the annotated source code. -
- -- The project is - hosted on GitHub. - You can report bugs and discuss features on the - issues page, - on Freenode in the #documentcloud channel, - or send tweets to @documentcloud. -
- -- Underscore is an open-source component of DocumentCloud. -
- -Development Version (1.4.4) | -40kb, Uncompressed with Plentiful Comments | -
Production Version (1.4.4) | -4kb, Minified and Gzipped | -
- | |
Edge Version | -Unreleased, current master, use at your own risk | -
- each_.each(list, iterator, [context])
- Alias: forEach
-
- Iterates over a list of elements, yielding each in turn to an iterator
- function. The iterator is bound to the context object, if one is
- passed. Each invocation of iterator is called with three arguments:
- (element, index, list). If list is a JavaScript object, iterator's
- arguments will be (value, key, list). Delegates to the native
- forEach function if it exists.
-
-_.each([1, 2, 3], alert); -=> alerts each number in turn... -_.each({one : 1, two : 2, three : 3}, alert); -=> alerts each number value in turn...- -
- map_.map(list, iterator, [context])
- Alias: collect
-
- Produces a new array of values by mapping each value in list
- through a transformation function (iterator). If the native map method
- exists, it will be used instead. If list is a JavaScript object,
- iterator's arguments will be (value, key, list).
-
-_.map([1, 2, 3], function(num){ return num * 3; }); -=> [3, 6, 9] -_.map({one : 1, two : 2, three : 3}, function(num, key){ return num * 3; }); -=> [3, 6, 9]- -
- reduce_.reduce(list, iterator, memo, [context])
- Aliases: inject, foldl
-
- Also known as inject and foldl, reduce boils down a
- list of values into a single value. Memo is the initial state
- of the reduction, and each successive step of it should be returned by
- iterator. The iterator is passed four arguments: the memo,
- then the value and index (or key) of the iteration,
- and finally a reference to the entire list.
-
-var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0); -=> 6 -- -
- reduceRight_.reduceRight(list, iterator, memo, [context])
- Alias: foldr
-
- The right-associative version of reduce. Delegates to the
- JavaScript 1.8 version of reduceRight, if it exists. Foldr
- is not as useful in JavaScript as it would be in a language with lazy
- evaluation.
-
-var list = [[0, 1], [2, 3], [4, 5]]; -var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []); -=> [4, 5, 2, 3, 0, 1] -- -
- find_.find(list, iterator, [context])
- Alias: detect
-
- Looks through each value in the list, returning the first one that
- passes a truth test (iterator). The function returns as
- soon as it finds an acceptable element, and doesn't traverse the
- entire list.
-
-var even = _.find([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); -=> 2 -- -
- filter_.filter(list, iterator, [context])
- Alias: select
-
- Looks through each value in the list, returning an array of all
- the values that pass a truth test (iterator). Delegates to the
- native filter method, if it exists.
-
-var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); -=> [2, 4, 6] -- -
- where_.where(list, properties)
-
- Looks through each value in the list, returning an array of all
- the values that contain all of the key-value pairs listed in properties.
-
-_.where(listOfPlays, {author: "Shakespeare", year: 1611}); -=> [{title: "Cymbeline", author: "Shakespeare", year: 1611}, - {title: "The Tempest", author: "Shakespeare", year: 1611}] -- -
- findWhere_.findWhere(list, properties)
-
- Looks through the list and returns the first value that matches
- all of the key-value pairs listed in properties.
-
-_.findWhere(publicServicePulitzers, {newsroom: "The New York Times"}); -=> {year: 1918, newsroom: "The New York Times", - reason: "For its public service in publishing in full so many official reports, - documents and speeches by European statesmen relating to the progress and - conduct of the war."} -- -
- reject_.reject(list, iterator, [context])
-
- Returns the values in list without the elements that the truth
- test (iterator) passes. The opposite of filter.
-
-var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); -=> [1, 3, 5] -- -
- every_.every(list, iterator, [context])
- Alias: all
-
- Returns true if all of the values in the list pass the iterator
- truth test. Delegates to the native method every, if present.
-
-_.every([true, 1, null, 'yes'], _.identity); -=> false -- -
- some_.some(list, [iterator], [context])
- Alias: any
-
- Returns true if any of the values in the list pass the
- iterator truth test. Short-circuits and stops traversing the list
- if a true element is found. Delegates to the native method some,
- if present.
-
-_.some([null, 0, 'yes', false]); -=> true -- -
- contains_.contains(list, value)
- Alias: include
-
- Returns true if the value is present in the list.
- Uses indexOf internally, if list is an Array.
-
-_.contains([1, 2, 3], 3); -=> true -- -
- invoke_.invoke(list, methodName, [*arguments])
-
- Calls the method named by methodName on each value in the list.
- Any extra arguments passed to invoke will be forwarded on to the
- method invocation.
-
-_.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); -=> [[1, 5, 7], [1, 2, 3]] -- -
- pluck_.pluck(list, propertyName)
-
- A convenient version of what is perhaps the most common use-case for
- map: extracting a list of property values.
-
-var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}]; -_.pluck(stooges, 'name'); -=> ["moe", "larry", "curly"] -- -
- max_.max(list, [iterator], [context])
-
- Returns the maximum value in list. If iterator is passed,
- it will be used on each value to generate the criterion by which the
- value is ranked.
-
-var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}]; -_.max(stooges, function(stooge){ return stooge.age; }); -=> {name : 'curly', age : 60}; -- -
- min_.min(list, [iterator], [context])
-
- Returns the minimum value in list. If iterator is passed,
- it will be used on each value to generate the criterion by which the
- value is ranked.
-
-var numbers = [10, 5, 100, 2, 1000]; -_.min(numbers); -=> 2 -- -
- sortBy_.sortBy(list, iterator, [context])
-
- Returns a sorted copy of list, ranked in ascending order by the
- results of running each value through iterator. Iterator may
- also be the string name of the property to sort by (eg. length).
-
-_.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); }); -=> [5, 4, 6, 3, 1, 2] -- -
- groupBy_.groupBy(list, iterator, [context])
-
- Splits a collection into sets, grouped by the result of running each
- value through iterator. If iterator is a string instead of
- a function, groups by the property named by iterator on each of
- the values.
-
-_.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); }); -=> {1: [1.3], 2: [2.1, 2.4]} - -_.groupBy(['one', 'two', 'three'], 'length'); -=> {3: ["one", "two"], 5: ["three"]} -- -
- countBy_.countBy(list, iterator, [context])
-
- Sorts a list into groups and returns a count for the number of objects
- in each group.
- Similar to groupBy, but instead of returning a list of values,
- returns a count for the number of values in that group.
-
-_.countBy([1, 2, 3, 4, 5], function(num) { - return num % 2 == 0 ? 'even' : 'odd'; -}); -=> {odd: 3, even: 2} -- -
- shuffle_.shuffle(list)
-
- Returns a shuffled copy of the list, using a version of the
- Fisher-Yates shuffle.
-
-_.shuffle([1, 2, 3, 4, 5, 6]); -=> [4, 1, 6, 3, 5, 2] -- -
- toArray_.toArray(list)
-
- Converts the list (anything that can be iterated over), into a
- real Array. Useful for transmuting the arguments object.
-
-(function(){ return _.toArray(arguments).slice(1); })(1, 2, 3, 4); -=> [2, 3, 4] -- -
- size_.size(list)
-
- Return the number of values in the list.
-
-_.size({one : 1, two : 2, three : 3}); -=> 3 -- -
- - Note: All array functions will also work on the arguments object. - However, Underscore functions are not designed to work on "sparse" arrays. - -
- -
- first_.first(array, [n])
- Alias: head, take
-
- Returns the first element of an array. Passing n will
- return the first n elements of the array.
-
-_.first([5, 4, 3, 2, 1]); -=> 5 -- -
- initial_.initial(array, [n])
-
- Returns everything but the last entry of the array. Especially useful on
- the arguments object. Pass n to exclude the last n elements
- from the result.
-
-_.initial([5, 4, 3, 2, 1]); -=> [5, 4, 3, 2] -- -
- last_.last(array, [n])
-
- Returns the last element of an array. Passing n will return
- the last n elements of the array.
-
-_.last([5, 4, 3, 2, 1]); -=> 1 -- -
- rest_.rest(array, [index])
- Alias: tail, drop
-
- Returns the rest of the elements in an array. Pass an index
- to return the values of the array from that index onward.
-
-_.rest([5, 4, 3, 2, 1]); -=> [4, 3, 2, 1] -- -
- compact_.compact(array)
-
- Returns a copy of the array with all falsy values removed.
- In JavaScript, false, null, 0, "",
- undefined and NaN are all falsy.
-
-_.compact([0, 1, false, 2, '', 3]); -=> [1, 2, 3] -- -
- flatten_.flatten(array, [shallow])
-
- Flattens a nested array (the nesting can be to any depth). If you
- pass shallow, the array will only be flattened a single level.
-
-_.flatten([1, [2], [3, [[4]]]]); -=> [1, 2, 3, 4]; - -_.flatten([1, [2], [3, [[4]]]], true); -=> [1, 2, 3, [[4]]]; -- -
- without_.without(array, [*values])
-
- Returns a copy of the array with all instances of the values
- removed.
-
-_.without([1, 2, 1, 0, 3, 1, 4], 0, 1); -=> [2, 3, 4] -- -
- union_.union(*arrays)
-
- Computes the union of the passed-in arrays: the list of unique items,
- in order, that are present in one or more of the arrays.
-
-_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); -=> [1, 2, 3, 101, 10] -- -
- intersection_.intersection(*arrays)
-
- Computes the list of values that are the intersection of all the arrays.
- Each value in the result is present in each of the arrays.
-
-_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); -=> [1, 2] -- -
- difference_.difference(array, *others)
-
- Similar to without, but returns the values from array that
- are not present in the other arrays.
-
-_.difference([1, 2, 3, 4, 5], [5, 2, 10]); -=> [1, 3, 4] -- -
- uniq_.uniq(array, [isSorted], [iterator])
- Alias: unique
-
- Produces a duplicate-free version of the array, using === to test
- object equality. If you know in advance that the array is sorted,
- passing true for isSorted will run a much faster algorithm.
- If you want to compute unique items based on a transformation, pass an
- iterator function.
-
-_.uniq([1, 2, 1, 3, 1, 4]); -=> [1, 2, 3, 4] -- -
- zip_.zip(*arrays)
-
- Merges together the values of each of the arrays with the
- values at the corresponding position. Useful when you have separate
- data sources that are coordinated through matching array indexes.
- If you're working with a matrix of nested arrays, zip.apply
- can transpose the matrix in a similar fashion.
-
-_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]); -=> [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]] -- -
- object_.object(list, [values])
-
- Converts arrays into objects. Pass either a single list of
- [key, value] pairs, or a list of keys, and a list of values.
-
-_.object(['moe', 'larry', 'curly'], [30, 40, 50]); -=> {moe: 30, larry: 40, curly: 50} - -_.object([['moe', 30], ['larry', 40], ['curly', 50]]); -=> {moe: 30, larry: 40, curly: 50} -- -
- indexOf_.indexOf(array, value, [isSorted])
-
- Returns the index at which value can be found in the array,
- or -1 if value is not present in the array. Uses the native
- indexOf function unless it's missing. If you're working with a
- large array, and you know that the array is already sorted, pass true
- for isSorted to use a faster binary search ... or, pass a number as
- the third argument in order to look for the first matching value in the
- array after the given index.
-
-_.indexOf([1, 2, 3], 2); -=> 1 -- -
- lastIndexOf_.lastIndexOf(array, value, [fromIndex])
-
- Returns the index of the last occurrence of value in the array,
- or -1 if value is not present. Uses the native lastIndexOf
- function if possible. Pass fromIndex to start your search at a
- given index.
-
-_.lastIndexOf([1, 2, 3, 1, 2, 3], 2); -=> 4 -- -
- sortedIndex_.sortedIndex(list, value, [iterator], [context])
-
- Uses a binary search to determine the index at which the value
- should be inserted into the list in order to maintain the list's
- sorted order. If an iterator is passed, it will be used to compute
- the sort ranking of each value, including the value you pass.
-
-_.sortedIndex([10, 20, 30, 40, 50], 35); -=> 3 -- -
- range_.range([start], stop, [step])
-
- A function to create flexibly-numbered lists of integers, handy for
- each and map loops. start, if omitted, defaults
- to 0; step defaults to 1. Returns a list of integers
- from start to stop, incremented (or decremented) by step,
- exclusive.
-
-_.range(10); -=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -_.range(1, 11); -=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -_.range(0, 30, 5); -=> [0, 5, 10, 15, 20, 25] -_.range(0, -10, -1); -=> [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] -_.range(0); -=> [] -- -
- bind_.bind(function, object, [*arguments])
-
- Bind a function to an object, meaning that whenever
- the function is called, the value of this will be the object.
- Optionally, pass arguments to the function to pre-fill them,
- also known as partial application.
-
-var func = function(greeting){ return greeting + ': ' + this.name }; -func = _.bind(func, {name : 'moe'}, 'hi'); -func(); -=> 'hi: moe' -- -
- bindAll_.bindAll(object, [*methodNames])
-
- Binds a number of methods on the object, specified by
- methodNames, to be run in the context of that object whenever they
- are invoked. Very handy for binding functions that are going to be used
- as event handlers, which would otherwise be invoked with a fairly useless
- this. If no methodNames are provided, all of the object's
- function properties will be bound to it.
-
-var buttonView = { - label : 'underscore', - onClick : function(){ alert('clicked: ' + this.label); }, - onHover : function(){ console.log('hovering: ' + this.label); } -}; -_.bindAll(buttonView); -jQuery('#underscore_button').bind('click', buttonView.onClick); -=> When the button is clicked, this.label will have the correct value... -- -
- partial_.partial(function, [*arguments])
-
- Partially apply a function by filling in any number of its arguments,
- without changing its dynamic this value. A close cousin
- of bind.
-
-var add = function(a, b) { return a + b; }; -add5 = _.partial(add, 5); -add5(10); -=> 15 -- -
- memoize_.memoize(function, [hashFunction])
-
- Memoizes a given function by caching the computed result. Useful
- for speeding up slow-running computations. If passed an optional
- hashFunction, it will be used to compute the hash key for storing
- the result, based on the arguments to the original function. The default
- hashFunction just uses the first argument to the memoized function
- as the key.
-
-var fibonacci = _.memoize(function(n) { - return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); -}); -- -
- delay_.delay(function, wait, [*arguments])
-
- Much like setTimeout, invokes function after wait
- milliseconds. If you pass the optional arguments, they will be
- forwarded on to the function when it is invoked.
-
-var log = _.bind(console.log, console); -_.delay(log, 1000, 'logged later'); -=> 'logged later' // Appears after one second. -- -
- defer_.defer(function, [*arguments])
-
- Defers invoking the function until the current call stack has cleared,
- similar to using setTimeout with a delay of 0. Useful for performing
- expensive computations or HTML rendering in chunks without blocking the UI thread
- from updating. If you pass the optional arguments, they will be
- forwarded on to the function when it is invoked.
-
-_.defer(function(){ alert('deferred'); }); -// Returns from the function before the alert runs. -- -
- throttle_.throttle(function, wait)
-
- Creates and returns a new, throttled version of the passed function,
- that, when invoked repeatedly, will only actually call the original function
- at most once per every wait
- milliseconds. Useful for rate-limiting events that occur faster than you
- can keep up with.
-
-var throttled = _.throttle(updatePosition, 100); -$(window).scroll(throttled); -- -
- debounce_.debounce(function, wait, [immediate])
-
- Creates and returns a new debounced version of the passed function that
- will postpone its execution until after
- wait milliseconds have elapsed since the last time it
- was invoked. Useful for implementing behavior that should only happen
- after the input has stopped arriving. For example: rendering a
- preview of a Markdown comment, recalculating a layout after the window
- has stopped being resized, and so on.
-
- Pass true for the immediate parameter to cause - debounce to trigger the function on the leading instead of the - trailing edge of the wait interval. Useful in circumstances like - preventing accidental double-clicks on a "submit" button from firing a - second time. -
- --var lazyLayout = _.debounce(calculateLayout, 300); -$(window).resize(lazyLayout); -- -
- once_.once(function)
-
- Creates a version of the function that can only be called one time.
- Repeated calls to the modified function will have no effect, returning
- the value from the original call. Useful for initialization functions,
- instead of having to set a boolean flag and then check it later.
-
-var initialize = _.once(createApplication); -initialize(); -initialize(); -// Application is only created once. -- -
- after_.after(count, function)
-
- Creates a version of the function that will only be run after first
- being called count times. Useful for grouping asynchronous responses,
- where you want to be sure that all the async calls have finished, before
- proceeding.
-
-var renderNotes = _.after(notes.length, render); -_.each(notes, function(note) { - note.asyncSave({success: renderNotes}); -}); -// renderNotes is run once, after all notes have saved. -- -
- wrap_.wrap(function, wrapper)
-
- Wraps the first function inside of the wrapper function,
- passing it as the first argument. This allows the wrapper to
- execute code before and after the function runs, adjust the arguments,
- and execute it conditionally.
-
-var hello = function(name) { return "hello: " + name; }; -hello = _.wrap(hello, function(func) { - return "before, " + func("moe") + ", after"; -}); -hello(); -=> 'before, hello: moe, after' -- -
- compose_.compose(*functions)
-
- Returns the composition of a list of functions, where each function
- consumes the return value of the function that follows. In math terms,
- composing the functions f(), g(), and h() produces
- f(g(h())).
-
-var greet = function(name){ return "hi: " + name; }; -var exclaim = function(statement){ return statement + "!"; }; -var welcome = _.compose(exclaim, greet); -welcome('moe'); -=> 'hi: moe!' -- -
- keys_.keys(object)
-
- Retrieve all the names of the object's properties.
-
-_.keys({one : 1, two : 2, three : 3}); -=> ["one", "two", "three"] -- -
- values_.values(object)
-
- Return all of the values of the object's properties.
-
-_.values({one : 1, two : 2, three : 3}); -=> [1, 2, 3] -- -
- pairs_.pairs(object)
-
- Convert an object into a list of [key, value] pairs.
-
-_.pairs({one: 1, two: 2, three: 3}); -=> [["one", 1], ["two", 2], ["three", 3]] -- -
- invert_.invert(object)
-
- Returns a copy of the object where the keys have become the values
- and the values the keys. For this to work, all of your object's values
- should be unique and string serializable.
-
-_.invert({Moe: "Moses", Larry: "Louis", Curly: "Jerome"}); -=> {Moses: "Moe", Louis: "Larry", Jerome: "Curly"}; -- -
- functions_.functions(object)
- Alias: methods
-
- Returns a sorted list of the names of every method in an object —
- that is to say, the name of every function property of the object.
-
-_.functions(_); -=> ["all", "any", "bind", "bindAll", "clone", "compact", "compose" ... -- -
- extend_.extend(destination, *sources)
-
- Copy all of the properties in the source objects over to the
- destination object, and return the destination object.
- It's in-order, so the last source will override properties of the same
- name in previous arguments.
-
-_.extend({name : 'moe'}, {age : 50}); -=> {name : 'moe', age : 50} -- -
- pick_.pick(object, *keys)
-
- Return a copy of the object, filtered to only have values for
- the whitelisted keys (or array of valid keys).
-
-_.pick({name : 'moe', age: 50, userid : 'moe1'}, 'name', 'age'); -=> {name : 'moe', age : 50} -- -
- omit_.omit(object, *keys)
-
- Return a copy of the object, filtered to omit the blacklisted
- keys (or array of keys).
-
-_.omit({name : 'moe', age : 50, userid : 'moe1'}, 'userid'); -=> {name : 'moe', age : 50} -- -
- defaults_.defaults(object, *defaults)
-
- Fill in null and undefined properties in object with values from the
- defaults objects, and return the object. As soon as the
- property is filled, further defaults will have no effect.
-
-var iceCream = {flavor : "chocolate"}; -_.defaults(iceCream, {flavor : "vanilla", sprinkles : "lots"}); -=> {flavor : "chocolate", sprinkles : "lots"} -- -
- clone_.clone(object)
-
- Create a shallow-copied clone of the object. Any nested objects
- or arrays will be copied by reference, not duplicated.
-
-_.clone({name : 'moe'}); -=> {name : 'moe'}; -- -
- tap_.tap(object, interceptor)
-
- Invokes interceptor with the object, and then returns object.
- The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain.
-
-_.chain([1,2,3,200]) - .filter(function(num) { return num % 2 == 0; }) - .tap(alert) - .map(function(num) { return num * num }) - .value(); -=> // [2, 200] (alerted) -=> [4, 40000] -- -
- has_.has(object, key)
-
- Does the object contain the given key? Identical to
- object.hasOwnProperty(key), but uses a safe reference to the
- hasOwnProperty function, in case it's been
- overridden accidentally.
-
-_.has({a: 1, b: 2, c: 3}, "b"); -=> true -- -
- isEqual_.isEqual(object, other)
-
- Performs an optimized deep comparison between the two objects, to determine
- if they should be considered equal.
-
-var moe = {name : 'moe', luckyNumbers : [13, 27, 34]}; -var clone = {name : 'moe', luckyNumbers : [13, 27, 34]}; -moe == clone; -=> false -_.isEqual(moe, clone); -=> true -- -
- isEmpty_.isEmpty(object)
-
- Returns true if object contains no values.
-
-_.isEmpty([1, 2, 3]); -=> false -_.isEmpty({}); -=> true -- -
- isElement_.isElement(object)
-
- Returns true if object is a DOM element.
-
-_.isElement(jQuery('body')[0]); -=> true -- -
- isArray_.isArray(object)
-
- Returns true if object is an Array.
-
-(function(){ return _.isArray(arguments); })(); -=> false -_.isArray([1,2,3]); -=> true -- -
- isObject_.isObject(value)
-
- Returns true if value is an Object. Note that JavaScript
- arrays and functions are objects, while (normal) strings and numbers are not.
-
-_.isObject({}); -=> true -_.isObject(1); -=> false -- -
- isArguments_.isArguments(object)
-
- Returns true if object is an Arguments object.
-
-(function(){ return _.isArguments(arguments); })(1, 2, 3); -=> true -_.isArguments([1,2,3]); -=> false -- -
- isFunction_.isFunction(object)
-
- Returns true if object is a Function.
-
-_.isFunction(alert); -=> true -- -
- isString_.isString(object)
-
- Returns true if object is a String.
-
-_.isString("moe"); -=> true -- -
- isNumber_.isNumber(object)
-
- Returns true if object is a Number (including NaN).
-
-_.isNumber(8.4 * 5); -=> true -- -
- isFinite_.isFinite(object)
-
- Returns true if object is a finite Number.
-
-_.isFinite(-101); -=> true - -_.isFinite(-Infinity); -=> false -- -
- isBoolean_.isBoolean(object)
-
- Returns true if object is either true or false.
-
-_.isBoolean(null); -=> false -- -
- isDate_.isDate(object)
-
- Returns true if object is a Date.
-
-_.isDate(new Date()); -=> true -- -
- isRegExp_.isRegExp(object)
-
- Returns true if object is a RegExp.
-
-_.isRegExp(/moe/); -=> true -- -
- isNaN_.isNaN(object)
-
- Returns true if object is NaN.
Note: this is not
- the same as the native isNaN function, which will also return
- true if the variable is undefined.
-
-_.isNaN(NaN); -=> true -isNaN(undefined); -=> true -_.isNaN(undefined); -=> false -- -
- isNull_.isNull(object)
-
- Returns true if the value of object is null.
-
-_.isNull(null); -=> true -_.isNull(undefined); -=> false -- -
- isUndefined_.isUndefined(value)
-
- Returns true if value is undefined.
-
-_.isUndefined(window.missingVariable); -=> true -- -
- noConflict_.noConflict()
-
- Give control of the "_" variable back to its previous owner. Returns
- a reference to the Underscore object.
-
-var underscore = _.noConflict();- -
- identity_.identity(value)
-
- Returns the same value that is used as the argument. In math:
- f(x) = x
- This function looks useless, but is used throughout Underscore as
- a default iterator.
-
-var moe = {name : 'moe'}; -moe === _.identity(moe); -=> true- -
- times_.times(n, iterator, [context])
-
- Invokes the given iterator function n times. Each invocation of
- iterator is called with an index argument.
-
- Note: this example uses the chaining syntax.
-
-_(3).times(function(n){ genie.grantWishNumber(n); });- -
- random_.random(min, max)
-
- Returns a random integer between min and max, inclusive.
- If you only pass one argument, it will return a number between 0
- and that number.
-
-_.random(0, 100); -=> 42- -
- mixin_.mixin(object)
-
- Allows you to extend Underscore with your own utility functions. Pass
- a hash of {name: function} definitions to have your functions
- added to the Underscore object, as well as the OOP wrapper.
-
-_.mixin({ - capitalize : function(string) { - return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase(); - } -}); -_("fabio").capitalize(); -=> "Fabio" -- -
- uniqueId_.uniqueId([prefix])
-
- Generate a globally-unique id for client-side models or DOM elements
- that need one. If prefix is passed, the id will be appended to it.
-
-_.uniqueId('contact_'); -=> 'contact_104'- -
- escape_.escape(string)
-
- Escapes a string for insertion into HTML, replacing
- &, <, >, ", ', and / characters.
-
-_.escape('Curly, Larry & Moe'); -=> "Curly, Larry & Moe"- -
- unescape_.unescape(string)
-
- The opposite of escape, replaces
- &, <, >,
- ", ', and /
- with their unescaped counterparts.
-
-_.unescape('Curly, Larry & Moe'); -=> "Curly, Larry & Moe"- -
- result_.result(object, property)
-
- If the value of the named property is a function then invoke it; otherwise, return it.
-
-var object = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }}; -_.result(object, 'cheese'); -=> "crumpets" -_.result(object, 'stuff'); -=> "nonsense"- -
- template_.template(templateString, [data], [settings])
-
- Compiles JavaScript templates into functions that can be evaluated
- for rendering. Useful for rendering complicated bits of HTML from JSON
- data sources. Template functions can both interpolate variables, using
- <%= … %>, as well as execute arbitrary JavaScript code, with
- <% … %>. If you wish to interpolate a value, and have
- it be HTML-escaped, use <%- … %> When you evaluate a template function, pass in a
- data object that has properties corresponding to the template's free
- variables. If you're writing a one-off, you can pass the data
- object as the second parameter to template in order to render
- immediately instead of returning a template function. The settings argument
- should be a hash containing any _.templateSettings that should be overridden.
-
-var compiled = _.template("hello: <%= name %>"); -compiled({name : 'moe'}); -=> "hello: moe" - -var list = "<% _.each(people, function(name) { %> <li><%= name %></li> <% }); %>"; -_.template(list, {people : ['moe', 'curly', 'larry']}); -=> "<li>moe</li><li>curly</li><li>larry</li>" - -var template = _.template("<b><%- value %></b>"); -template({value : '<script>'}); -=> "<b><script></b>"- -
- You can also use print from within JavaScript code. This is - sometimes more convenient than using <%= ... %>. -
- --var compiled = _.template("<% print('Hello ' + epithet); %>"); -compiled({epithet: "stooge"}); -=> "Hello stooge."- -
- If ERB-style delimiters aren't your cup of tea, you can change Underscore's - template settings to use different symbols to set off interpolated code. - Define an interpolate regex to match expressions that should be - interpolated verbatim, an escape regex to match expressions that should - be inserted after being HTML escaped, and an evaluate regex to match - expressions that should be evaluated without insertion into the resulting - string. You may define or omit any combination of the three. - For example, to perform - Mustache.js - style templating: -
- --_.templateSettings = { - interpolate : /\{\{(.+?)\}\}/g -}; - -var template = _.template("Hello {{ name }}!"); -template({name : "Mustache"}); -=> "Hello Mustache!"- -
- By default, template places the values from your data in the local scope - via the with statement. However, you can specify a single variable name - with the variable setting. This can significantly improve the speed - at which a template is able to render. -
- --_.template("Using 'with': <%= data.answer %>", {answer: 'no'}, {variable: 'data'}); -=> "Using 'with': no"- -
- Precompiling your templates can be a big help when debugging errors you can't - reproduce. This is because precompiled templates can provide line numbers and - a stack trace, something that is not possible when compiling templates on the client. - The source property is available on the compiled template - function for easy precompilation. -
- -<script> - JST.project = <%= _.template(jstText).source %>; -</script>- - -
- You can use Underscore in either an object-oriented or a functional style, - depending on your preference. The following two lines of code are - identical ways to double a list of numbers. -
- --_.map([1, 2, 3], function(n){ return n * 2; }); -_([1, 2, 3]).map(function(n){ return n * 2; });- -
- Calling chain will cause all future method calls to return - wrapped objects. When you've finished the computation, use - value to retrieve the final value. Here's an example of chaining - together a map/flatten/reduce, in order to get the word count of - every word in a song. -
- --var lyrics = [ - {line : 1, words : "I'm a lumberjack and I'm okay"}, - {line : 2, words : "I sleep all night and I work all day"}, - {line : 3, words : "He's a lumberjack and he's okay"}, - {line : 4, words : "He sleeps all night and he works all day"} -]; - -_.chain(lyrics) - .map(function(line) { return line.words.split(' '); }) - .flatten() - .reduce(function(counts, word) { - counts[word] = (counts[word] || 0) + 1; - return counts; - }, {}) - .value(); - -=> {lumberjack : 2, all : 4, night : 2 ... }- -
- In addition, the - Array prototype's methods - are proxied through the chained Underscore object, so you can slip a - reverse or a push into your chain, and continue to - modify the array. -
- -
- chain_.chain(obj)
-
- Returns a wrapped object. Calling methods on this object will continue
- to return wrapped objects until value is used.
-
-var stooges = [{name : 'curly', age : 25}, {name : 'moe', age : 21}, {name : 'larry', age : 23}]; -var youngest = _.chain(stooges) - .sortBy(function(stooge){ return stooge.age; }) - .map(function(stooge){ return stooge.name + ' is ' + stooge.age; }) - .first() - .value(); -=> "moe is 21" -- -
- value_(obj).value()
-
- Extracts the value of a wrapped object.
-
-_([1, 2, 3]).value(); -=> [1, 2, 3] -- -
- The Underscore documentation is also available in - Simplified Chinese. -
- -- Underscore.lua, - a Lua port of the functions that are applicable in both languages. - Includes OOP-wrapping and chaining. - (source) -
- -- Underscore.m, an Objective-C port - of many of the Underscore.js functions, using a syntax that encourages - chaining. - (source) -
- -- _.m, an alternative - Objective-C port that tries to stick a little closer to the original - Underscore.js API. - (source) -
- -- Underscore.php, - a PHP port of the functions that are applicable in both languages. - Includes OOP-wrapping and chaining. - (source) -
- -- Underscore-perl, - a Perl port of many of the Underscore.js functions, - aimed at on Perl hashes and arrays. - (source) -
- -- Underscore.cfc, - a Coldfusion port of many of the Underscore.js functions. - (source) -
- -- Underscore.string, - an Underscore extension that adds functions for string-manipulation: - trim, startsWith, contains, capitalize, - reverse, sprintf, and more. -
- -- Ruby's Enumerable module. -
- -- Prototype.js, which provides - JavaScript with collection functions in the manner closest to Ruby's Enumerable. -
- -- Oliver Steele's - Functional JavaScript, - which includes comprehensive higher-order function support as well as string lambdas. -
- -- Michael Aufreiter's Data.js, - a data manipulation + persistence library for JavaScript. -
- -- Python's itertools. -
- -
- 1.4.4 — Jan. 30, 2013 — Diff
-
- 1.4.3 — Dec. 4, 2012 — Diff
-
- 1.4.2 — Oct. 1, 2012 — Diff
-
- 1.4.1 — Oct. 1, 2012 — Diff
-
- 1.4.0 — Sept. 27, 2012 — Diff
-
- 1.3.3 — April 10, 2012
-
- 1.3.1 — Jan. 23, 2012
-
- 1.3.0 — Jan. 11, 2012
-
- 1.2.4 — Jan. 4, 2012
-
- 1.2.3 — Dec. 7, 2011
-
- 1.2.2 — Nov. 14, 2011
-
- 1.2.1 — Oct. 24, 2011
-
- 1.2.0 — Oct. 5, 2011
-
- 1.1.7 — July 13, 2011
- Added _.groupBy, which aggregates a collection into groups of like items.
- Added _.union and _.difference, to complement the
- (re-named) _.intersection.
- Various improvements for support of sparse arrays.
- _.toArray now returns a clone, if directly passed an array.
- _.functions now also returns the names of functions that are present
- in the prototype chain.
-
- 1.1.6 — April 18, 2011
- Added _.after, which will return a function that only runs after
- first being called a specified number of times.
- _.invoke can now take a direct function reference.
- _.every now requires an iterator function to be passed, which
- mirrors the ECMA5 API.
- _.extend no longer copies keys when the value is undefined.
- _.bind now errors when trying to bind an undefined value.
-
- 1.1.5 — Mar 20, 2011
- Added an _.defaults function, for use merging together JS objects
- representing default options.
- Added an _.once function, for manufacturing functions that should
- only ever execute a single time.
- _.bind now delegates to the native ECMAScript 5 version,
- where available.
- _.keys now throws an error when used on non-Object values, as in
- ECMAScript 5.
- Fixed a bug with _.keys when used over sparse arrays.
-
- 1.1.4 — Jan 9, 2011
- Improved compliance with ES5's Array methods when passing null
- as a value. _.wrap now correctly sets this for the
- wrapped function. _.indexOf now takes an optional flag for
- finding the insertion index in an array that is guaranteed to already
- be sorted. Avoiding the use of .callee, to allow _.isArray
- to work properly in ES5's strict mode.
-
- 1.1.3 — Dec 1, 2010
- In CommonJS, Underscore may now be required with just:
- var _ = require("underscore").
- Added _.throttle and _.debounce functions.
- Removed _.breakLoop, in favor of an ECMA5-style un-break-able
- each implementation — this removes the try/catch, and you'll now have
- better stack traces for exceptions that are thrown within an Underscore iterator.
- Improved the isType family of functions for better interoperability
- with Internet Explorer host objects.
- _.template now correctly escapes backslashes in templates.
- Improved _.reduce compatibility with the ECMA5 version:
- if you don't pass an initial value, the first item in the collection is used.
- _.each no longer returns the iterated collection, for improved
- consistency with ES5's forEach.
-
- 1.1.2
- Fixed _.contains, which was mistakenly pointing at
- _.intersect instead of _.include, like it should
- have been. Added _.unique as an alias for _.uniq.
-
- 1.1.1
- Improved the speed of _.template, and its handling of multiline
- interpolations. Ryan Tenney contributed optimizations to many Underscore
- functions. An annotated version of the source code is now available.
-
- 1.1.0
- The method signature of _.reduce has been changed to match
- the ECMAScript 5 signature, instead of the Ruby/Prototype.js version.
- This is a backwards-incompatible change. _.template may now be
- called with no arguments, and preserves whitespace. _.contains
- is a new alias for _.include.
-
- 1.0.4
- Andri Möll contributed the _.memoize
- function, which can be used to speed up expensive repeated computations
- by caching the results.
-
- 1.0.3
- Patch that makes _.isEqual return false if any property
- of the compared object has a NaN value. Technically the correct
- thing to do, but of questionable semantics. Watch out for NaN comparisons.
-
- 1.0.2
- Fixes _.isArguments in recent versions of Opera, which have
- arguments objects as real Arrays.
-
- 1.0.1
- Bugfix for _.isEqual, when comparing two objects with the same
- number of undefined keys, but with different names.
-
- 1.0.0
- Things have been stable for many months now, so Underscore is now
- considered to be out of beta, at 1.0. Improvements since 0.6
- include _.isBoolean, and the ability to have _.extend
- take multiple source objects.
-
- 0.6.0
- Major release. Incorporates a number of
- Mile Frawley's refactors for
- safer duck-typing on collection functions, and cleaner internals. A new
- _.mixin method that allows you to extend Underscore with utility
- functions of your own. Added _.times, which works the same as in
- Ruby or Prototype.js. Native support for ECMAScript 5's Array.isArray,
- and Object.keys.
-
- 0.5.8
- Fixed Underscore's collection functions to work on
- NodeLists and
- HTMLCollections
- once more, thanks to
- Justin Tulloss.
-
- 0.5.7
- A safer implementation of _.isArguments, and a
- faster _.isNumber,
thanks to
- Jed Schmidt.
-
- 0.5.6
- Customizable delimiters for _.template, contributed by
- Noah Sloan.
-
- 0.5.5
- Fix for a bug in MobileSafari's OOP-wrapper, with the arguments object.
-
- 0.5.4
- Fix for multiple single quotes within a template string for
- _.template. See:
- Rick Strahl's blog post.
-
- 0.5.2
- New implementations of isArray, isDate, isFunction,
- isNumber, isRegExp, and isString, thanks to
- a suggestion from
- Robert Kieffer.
- Instead of doing Object#toString
- comparisons, they now check for expected properties, which is less safe,
- but more than an order of magnitude faster. Most other Underscore
- functions saw minor speed improvements as a result.
- Evgeniy Dolzhenko
- contributed _.tap,
- similar to Ruby 1.9's,
- which is handy for injecting side effects (like logging) into chained calls.
-
- 0.5.1
- Added an _.isArguments function. Lots of little safety checks
- and optimizations contributed by
- Noah Sloan and
- Andri Möll.
-
- 0.5.0
- [API Changes] _.bindAll now takes the context object as
- its first parameter. If no method names are passed, all of the context
- object's methods are bound to it, enabling chaining and easier binding.
- _.functions now takes a single argument and returns the names
- of its Function properties. Calling _.functions(_) will get you
- the previous behavior.
- Added _.isRegExp so that isEqual can now test for RegExp equality.
- All of the "is" functions have been shrunk down into a single definition.
- Karl Guertin contributed patches.
-
- 0.4.7
- Added isDate, isNaN, and isNull, for completeness.
- Optimizations for isEqual when checking equality between Arrays
- or Dates. _.keys is now 25%–2X faster (depending on your
- browser) which speeds up the functions that rely on it, such as _.each.
-
- 0.4.6
- Added the range function, a port of the
- Python
- function of the same name, for generating flexibly-numbered lists
- of integers. Original patch contributed by
- Kirill Ishanov.
-
- 0.4.5
- Added rest for Arrays and arguments objects, and aliased
- first as head, and rest as tail,
- thanks to Luke Sutton's patches.
- Added tests ensuring that all Underscore Array functions also work on
- arguments objects.
-
- 0.4.4
- Added isString, and isNumber, for consistency. Fixed
- _.isEqual(NaN, NaN) to return true (which is debatable).
-
- 0.4.3
- Started using the native StopIteration object in browsers that support it.
- Fixed Underscore setup for CommonJS environments.
-
- 0.4.2
- Renamed the unwrapping function to value, for clarity.
-
- 0.4.1
- Chained Underscore objects now support the Array prototype methods, so
- that you can perform the full range of operations on a wrapped array
- without having to break your chain. Added a breakLoop method
- to break in the middle of any Underscore iteration. Added an
- isEmpty function that works on arrays and objects.
-
- 0.4.0
- All Underscore functions can now be called in an object-oriented style,
- like so: _([1, 2, 3]).map(...);. Original patch provided by
- Marc-André Cournoyer.
- Wrapped objects can be chained through multiple
- method invocations. A functions method
- was added, providing a sorted list of all the functions in Underscore.
-
- 0.3.3
- Added the JavaScript 1.8 function reduceRight. Aliased it
- as foldr, and aliased reduce as foldl.
-
- 0.3.2
- Now runs on stock Rhino
- interpreters with: load("underscore.js").
- Added identity as a utility function.
-
- 0.3.1
- All iterators are now passed in the original collection as their third
- argument, the same as JavaScript 1.6's forEach. Iterating over
- objects is now called with (value, key, collection), for details
- see _.each.
-
- 0.3.0
- Added Dmitry Baranovskiy's
- comprehensive optimizations, merged in
- Kris Kowal's patches to make Underscore
- CommonJS and
- Narwhal compliant.
-
- 0.2.0
- Added compose and lastIndexOf, renamed inject to
- reduce, added aliases for inject, filter,
- every, some, and forEach.
-
- 0.1.1
- Added noConflict, so that the "Underscore" object can be assigned to
- other variables.
-
- 0.1.0
- Initial release of Underscore.js.
-
- - - -
- -Hello ' + escape((interp = name) == null ? '' : interp) + '\n
'); - } - return buf.join(""); -} -``` - - Through the use of Jade's `./runtime.js` you may utilize these pre-compiled templates on the client-side _without_ Jade itself, all you need is the associated utility functions (in runtime.js), which are then available as `jade.attrs`, `jade.escape` etc. To enable this you should pass `{ client: true }` to `jade.compile()` to tell Jade to reference the helper functions - via `jade.attrs`, `jade.escape` etc. - -```js -function anonymous(locals, attrs, escape, rethrow) { - var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow; - var buf = []; - with (locals || {}) { - var interp; - buf.push('\nHello ' + escape((interp = name) == null ? '' : interp) + '\n
'); - } - return buf.join(""); -} -``` - - -## Public API - -```js -var jade = require('jade'); - -// Compile a function -var fn = jade.compile('string of jade', options); -fn(locals); -``` - -### Options - - - `self` Use a `self` namespace to hold the locals _(false by default)_ - - `locals` Local variable object - - `filename` Used in exceptions, and required when using includes - - `debug` Outputs tokens and function body generated - - `compiler` Compiler to replace jade's default - - `compileDebug` When `false` no debug instrumentation is compiled - - `pretty` Add pretty-indentation whitespace to output _(false by default)_ - - -## Syntax - - -### Line Endings - -**CRLF** and **CR** are converted to **LF** before parsing. - - -### Tags - -A tag is simply a leading word: - -```jade -html -``` - -for example is converted to `` - -tags can also have ids: - -```jade -div#container -``` - -which would render `` - -how about some classes? - -```jade -div.user-details -``` - -renders `` - -multiple classes? _and_ an id? sure: - -```jade -div#foo.bar.baz -``` - -renders `` - -div div div sure is annoying, how about: - -```jade -#foo -.bar -``` - -which is syntactic sugar for what we have already been doing, and outputs: - -```html - -``` - - -### Tag Text - -Simply place some content after the tag: - -```jade -p wahoo! -``` - -renders `wahoo!
`. - -well cool, but how about large bodies of text: - -```jade -p - | foo bar baz - | rawr rawr - | super cool - | go jade go -``` - -renders `foo bar baz rawr.....
` - -interpolation? yup! both types of text can utilize interpolation, -if we passed `{ name: 'tj', email: 'tj@vision-media.ca' }` to the compiled function we can do the following: - -```jade -#user #{name} <#{email}> -``` - -outputs `#{something}
` - -We can also utilize the unescaped variant `!{html}`, so the following -will result in a literal script tag: - -```jade -- var html = "" -| !{html} -``` - -Nested tags that also contain text can optionally use a text block: - -```jade -label - | Username: - input(name='user[name]') -``` - -or immediate tag text: - -```jade -label Username: - input(name='user[name]') -``` - -As an alternative, we may use a trailing `.` to indicate a text block, for example: - -```jade -p. - foo asdf - asdf - asdfasdfaf - asdf - asd. -``` - -outputs: - -```html -foo asdf -asdf - asdfasdfaf - asdf -asd. -
-``` - -This however differs from a trailing `.` followed by a space, which although is ignored by the Jade parser, tells Jade that this period is a literal: - -```jade -p . -``` - -outputs: - -```html -.
-``` - -It should be noted that text blocks should be doubled escaped. For example if you desire the following output. - -```html -foo\bar
-``` - -use: - -```jade -p. - foo\\bar -``` - - -### Comments - -Single line comments currently look the same as JavaScript comments, -aka `//` and must be placed on their own line: - -```jade -// just some paragraphs -p foo -p bar -``` - -would output - -```html - -foo
-bar
-``` - -Jade also supports unbuffered comments, by simply adding a hyphen: - -```jade -//- will not output within markup -p foo -p bar -``` - -outputting - -```html -foo
-bar
-``` - - -### Block Comments - - A block comment is legal as well: - -```jade -body - // - #content - h1 Example -``` - -outputting - -```html - - - -``` - -Jade supports conditional-comments as well, for example: - -```jade -head - //if lt IE 8 - script(src='/ie-sucks.js') -``` - -outputs: - -```html - - - -``` - - -### Nesting - - Jade supports nesting to define the tags in a natural way: - -```jade -ul - li.first - a(href='#') foo - li - a(href='#') bar - li.last - a(href='#') baz -``` - - -### Block Expansion - - Block expansion allows you to create terse single-line nested tags, - the following example is equivalent to the nesting example above. - -```jade -ul - li.first: a(href='#') foo - li: a(href='#') bar - li.last: a(href='#') baz -``` - - -### Case - - The case statement takes the following form: - -```jade -html - body - friends = 10 - case friends - when 0 - p you have no friends - when 1 - p you have a friend - default - p you have #{friends} friends -``` - - Block expansion may also be used: - -```jade -friends = 5 - -html - body - case friends - when 0: p you have no friends - when 1: p you have a friend - default: p you have #{friends} friends -``` - - -### Attributes - -Jade currently supports `(` and `)` as attribute delimiters. - -```jade -a(href='/login', title='View login page') Login -``` - -When a value is `undefined` or `null` the attribute is _not_ added, -so this is fine, it will not compile `something="null"`. - -```jade -div(something=null) -``` - -Boolean attributes are also supported: - -```jade -input(type="checkbox", checked) -``` - -Boolean attributes with code will only output the attribute when `true`: - -```jade -input(type="checkbox", checked=someValue) -``` - -Multiple lines work too: - -```jade -input(type='checkbox', - name='agreement', - checked) -``` - -Multiple lines without the comma work fine: - -```jade -input(type='checkbox' - name='agreement' - checked) -``` - -Funky whitespace? fine: - -```jade -input( - type='checkbox' - name='agreement' - checked) -``` - -Colons work: - -```jade -rss(xmlns:atom="atom") -``` - -Suppose we have the `user` local `{ id: 12, name: 'tobi' }` -and we wish to create an anchor tag with `href` pointing to "/user/12" -we could use regular javascript concatenation: - -```jade -a(href='/user/' + user.id)= user.name -``` - -or we could use jade's interpolation, which I added because everyone -using Ruby or CoffeeScript seems to think this is legal js..: - -```jade -a(href='/user/#{user.id}')= user.name -``` - -The `class` attribute is special-cased when an array is given, -allowing you to pass an array such as `bodyClasses = ['user', 'authenticated']` directly: - -```jade -body(class=bodyClasses) -``` - - -### HTML - - Inline html is fine, we can use the pipe syntax to - write arbitrary text, in this case some html: - -```jade -html - body - |foo bar baz
-``` - - Or we can use the trailing `.` to indicate to Jade that we - only want text in this block, allowing us to omit the pipes: - -```jade -html - body. -foo bar baz
-``` - - Both of these examples yield the same result: - -```html -foo bar baz
- -``` - - The same rule applies for anywhere you can have text - in jade, raw html is fine: - -```jade -html - body - h1 User #{name} -``` - - -### Doctypes - -To add a doctype simply use `!!!`, or `doctype` followed by an optional value: - -```jade -!!! -``` - -or - -```jade -doctype -``` - -Will output the _html 5_ doctype, however: - -```jade -!!! transitional -``` - -Will output the _transitional_ doctype. - -Doctypes are case-insensitive, so the following are equivalent: - -```jade -doctype Basic -doctype basic -``` - -it's also possible to simply pass a doctype literal: - -```jade -doctype html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" -``` - -yielding: - -```html - -``` - -Below are the doctypes defined by default, which can easily be extended: - -```js -var doctypes = exports.doctypes = { - '5': '', - 'default': '', - 'xml': '', - 'transitional': '', - 'strict': '', - 'frameset': '', - '1.1': '', - 'basic': '', - 'mobile': '' -}; -``` - -To alter the default simply change: - -```js -jade.doctypes.default = 'whatever you want'; -``` - - -## Filters - -Filters are prefixed with `:`, for example `:markdown` and -pass the following block of text to an arbitrary function for processing. View the _features_ -at the top of this document for available filters. - -```jade -body - :markdown - Woah! jade _and_ markdown, very **cool** - we can even link to [stuff](http://google.com) -``` - -Renders: - -```html -Woah! jade and markdown, very cool we can even link to stuff
-``` - - -## Code - -Jade currently supports three classifications of executable code. The first -is prefixed by `-`, and is not buffered: - -```jade -- var foo = 'bar'; -``` - -This can be used for conditionals, or iteration: - -```jade -- for (var key in obj) - p= obj[key] -``` - -Due to Jade's buffering techniques the following is valid as well: - -```jade -- if (foo) - ul - li yay - li foo - li worked -- else - p oh no! didnt work -``` - -Hell, even verbose iteration: - -```jade -- if (items.length) - ul - - items.forEach(function(item){ - li= item - - }) -``` - -Anything you want! - -Next up we have _escaped_ buffered code, which is used to -buffer a return value, which is prefixed by `=`: - -```jade -- var foo = 'bar' -= foo -h1= foo -``` - -Which outputs `barWelcome to my super lame site.
- - - -``` - -As mentioned `include` can be used to include other content -such as html or css. By providing an extension, Jade will -read that file in, apply any [filter](#a7) matching the file's -extension, and insert that content into the output. - -```jade -html - head - //- css and js have simple filters that wrap them in - '; - } else if (transformers[name].outputFormat === 'xml') { - res = res.replace(/'/g, '''); - } - } else { - throw new Error('unknown filter ":' + name + '"'); - } - return res; -} -filter.exists = function (name, str, options) { - return typeof filter[name] === 'function' || transformers[name]; -}; diff --git a/node_modules/jade/lib/index.js b/node_modules/jade/lib/index.js deleted file mode 100644 index 6a783c2..0000000 --- a/node_modules/jade/lib/index.js +++ /dev/null @@ -1 +0,0 @@ -jade.js \ No newline at end of file diff --git a/node_modules/jade/lib/inline-tags.js b/node_modules/jade/lib/inline-tags.js deleted file mode 100644 index 491de0b..0000000 --- a/node_modules/jade/lib/inline-tags.js +++ /dev/null @@ -1,28 +0,0 @@ - -/*! - * Jade - inline tags - * Copyright(c) 2010 TJ HolowaychukHello ' + escape((interp = name) == null ? '' : interp) + '\\n
');\r\n }\r\n return buf.join(\"\");\r\n}\r\n```\r\n\r\n Through the use of Jade's `./runtime.js` you may utilize these pre-compiled templates on the client-side _without_ Jade itself, all you need is the associated utility functions (in runtime.js), which are then available as `jade.attrs`, `jade.escape` etc. To enable this you should pass `{ client: true }` to `jade.compile()` to tell Jade to reference the helper functions\r\n via `jade.attrs`, `jade.escape` etc.\r\n\r\n```js\r\nfunction anonymous(locals, attrs, escape, rethrow) {\r\n var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;\r\n var buf = [];\r\n with (locals || {}) {\r\n var interp;\r\n buf.push('\\nHello ' + escape((interp = name) == null ? '' : interp) + '\\n
');\r\n }\r\n return buf.join(\"\");\r\n}\r\n```\r\n\r\n\r\n## Public API\r\n\r\n```js\r\nvar jade = require('jade');\r\n\r\n// Compile a function\r\nvar fn = jade.compile('string of jade', options);\r\nfn(locals);\r\n```\r\n\r\n### Options\r\n\r\n - `self` Use a `self` namespace to hold the locals _(false by default)_\r\n - `locals` Local variable object\r\n - `filename` Used in exceptions, and required when using includes\r\n - `debug` Outputs tokens and function body generated\r\n - `compiler` Compiler to replace jade's default\r\n - `compileDebug` When `false` no debug instrumentation is compiled\r\n - `pretty` Add pretty-indentation whitespace to output _(false by default)_\r\n\r\n\r\n## Syntax\r\n\r\n\r\n### Line Endings\r\n\r\n**CRLF** and **CR** are converted to **LF** before parsing.\r\n\r\n\r\n### Tags\r\n\r\nA tag is simply a leading word:\r\n\r\n```jade\r\nhtml\r\n```\r\n\r\nfor example is converted to ``\r\n\r\ntags can also have ids:\r\n\r\n```jade\r\ndiv#container\r\n```\r\n\r\nwhich would render ``\r\n\r\nhow about some classes?\r\n\r\n```jade\r\ndiv.user-details\r\n```\r\n\r\nrenders ``\r\n\r\nmultiple classes? _and_ an id? sure:\r\n\r\n```jade\r\ndiv#foo.bar.baz\r\n```\r\n\r\nrenders ``\r\n\r\ndiv div div sure is annoying, how about:\r\n\r\n```jade\r\n#foo\r\n.bar\r\n```\r\n\r\nwhich is syntactic sugar for what we have already been doing, and outputs:\r\n\r\n```html\r\n\r\n```\r\n\r\n\r\n### Tag Text\r\n\r\nSimply place some content after the tag:\r\n\r\n```jade\r\np wahoo!\r\n```\r\n\r\nrenders `wahoo!
`.\r\n\r\nwell cool, but how about large bodies of text:\r\n\r\n```jade\r\np\r\n | foo bar baz\r\n | rawr rawr\r\n | super cool\r\n | go jade go\r\n```\r\n\r\nrenders `foo bar baz rawr.....
`\r\n\r\ninterpolation? yup! both types of text can utilize interpolation,\r\nif we passed `{ name: 'tj', email: 'tj@vision-media.ca' }` to the compiled function we can do the following:\r\n\r\n```jade\r\n#user #{name} <#{email}>\r\n```\r\n\r\noutputs `#{something}
`\r\n\r\nWe can also utilize the unescaped variant `!{html}`, so the following\r\nwill result in a literal script tag:\r\n\r\n```jade\r\n- var html = \"\"\r\n| !{html}\r\n```\r\n\r\nNested tags that also contain text can optionally use a text block:\r\n\r\n```jade\r\nlabel\r\n | Username:\r\n input(name='user[name]')\r\n```\r\n\r\nor immediate tag text:\r\n\r\n```jade\r\nlabel Username:\r\n input(name='user[name]')\r\n```\r\n\r\nAs an alternative, we may use a trailing `.` to indicate a text block, for example:\r\n\r\n```jade\r\np.\r\n foo asdf\r\n asdf\r\n asdfasdfaf\r\n asdf\r\n asd.\r\n```\r\n\r\noutputs:\r\n\r\n```html\r\nfoo asdf\r\nasdf\r\n asdfasdfaf\r\n asdf\r\nasd.\r\n
\r\n```\r\n\r\nThis however differs from a trailing `.` followed by a space, which although is ignored by the Jade parser, tells Jade that this period is a literal:\r\n\r\n```jade\r\np .\r\n```\r\n\r\noutputs:\r\n\r\n```html\r\n.
\r\n```\r\n\r\nIt should be noted that text blocks should be doubled escaped. For example if you desire the following output.\r\n\r\n```html\r\nfoo\\bar
\r\n```\r\n\r\nuse:\r\n\r\n```jade\r\np.\r\n foo\\\\bar\r\n```\r\n\r\n\r\n### Comments\r\n\r\nSingle line comments currently look the same as JavaScript comments,\r\naka `//` and must be placed on their own line:\r\n\r\n```jade\r\n// just some paragraphs\r\np foo\r\np bar\r\n```\r\n\r\nwould output\r\n\r\n```html\r\n\r\nfoo
\r\nbar
\r\n```\r\n\r\nJade also supports unbuffered comments, by simply adding a hyphen:\r\n\r\n```jade\r\n//- will not output within markup\r\np foo\r\np bar\r\n```\r\n\r\noutputting\r\n\r\n```html\r\nfoo
\r\nbar
\r\n```\r\n\r\n\r\n### Block Comments\r\n\r\n A block comment is legal as well:\r\n\r\n```jade\r\nbody\r\n //\r\n #content\r\n h1 Example\r\n```\r\n\r\noutputting\r\n\r\n```html\r\n\r\n \r\n\r\n```\r\n\r\nJade supports conditional-comments as well, for example:\r\n\r\n```jade\r\nhead\r\n //if lt IE 8\r\n script(src='/ie-sucks.js')\r\n```\r\n\r\noutputs:\r\n\r\n```html\r\n\r\n \r\n\r\n```\r\n\r\n\r\n### Nesting\r\n\r\n Jade supports nesting to define the tags in a natural way:\r\n\r\n```jade\r\nul\r\n li.first\r\n a(href='#') foo\r\n li\r\n a(href='#') bar\r\n li.last\r\n a(href='#') baz\r\n```\r\n\r\n\r\n### Block Expansion\r\n\r\n Block expansion allows you to create terse single-line nested tags,\r\n the following example is equivalent to the nesting example above.\r\n\r\n```jade\r\nul\r\n li.first: a(href='#') foo\r\n li: a(href='#') bar\r\n li.last: a(href='#') baz\r\n```\r\n\r\n\r\n### Case\r\n\r\n The case statement takes the following form:\r\n\r\n```jade\r\nhtml\r\n body\r\n friends = 10\r\n case friends\r\n when 0\r\n p you have no friends\r\n when 1\r\n p you have a friend\r\n default\r\n p you have #{friends} friends\r\n```\r\n\r\n Block expansion may also be used:\r\n\r\n```jade\r\nfriends = 5\r\n\r\nhtml\r\n body\r\n case friends\r\n when 0: p you have no friends\r\n when 1: p you have a friend\r\n default: p you have #{friends} friends\r\n```\r\n\r\n\r\n### Attributes\r\n\r\nJade currently supports `(` and `)` as attribute delimiters.\r\n\r\n```jade\r\na(href='/login', title='View login page') Login\r\n```\r\n\r\nWhen a value is `undefined` or `null` the attribute is _not_ added,\r\nso this is fine, it will not compile `something=\"null\"`.\r\n\r\n```jade\r\ndiv(something=null)\r\n```\r\n\r\nBoolean attributes are also supported:\r\n\r\n```jade\r\ninput(type=\"checkbox\", checked)\r\n```\r\n\r\nBoolean attributes with code will only output the attribute when `true`:\r\n\r\n```jade\r\ninput(type=\"checkbox\", checked=someValue)\r\n```\r\n\r\nMultiple lines work too:\r\n\r\n```jade\r\ninput(type='checkbox',\r\n name='agreement',\r\n checked)\r\n```\r\n\r\nMultiple lines without the comma work fine:\r\n\r\n```jade\r\ninput(type='checkbox'\r\n name='agreement'\r\n checked)\r\n```\r\n\r\nFunky whitespace? fine:\r\n\r\n```jade\r\ninput(\r\n type='checkbox'\r\n name='agreement'\r\n checked)\r\n```\r\n\r\nColons work:\r\n\r\n```jade\r\nrss(xmlns:atom=\"atom\")\r\n```\r\n\r\nSuppose we have the `user` local `{ id: 12, name: 'tobi' }`\r\nand we wish to create an anchor tag with `href` pointing to \"/user/12\"\r\nwe could use regular javascript concatenation:\r\n\r\n```jade\r\na(href='/user/' + user.id)= user.name\r\n```\r\n\r\nor we could use jade's interpolation, which I added because everyone\r\nusing Ruby or CoffeeScript seems to think this is legal js..:\r\n\r\n```jade\r\na(href='/user/#{user.id}')= user.name\r\n```\r\n\r\nThe `class` attribute is special-cased when an array is given,\r\nallowing you to pass an array such as `bodyClasses = ['user', 'authenticated']` directly:\r\n\r\n```jade\r\nbody(class=bodyClasses)\r\n```\r\n\r\n\r\n### HTML\r\n\r\n Inline html is fine, we can use the pipe syntax to\r\n write arbitrary text, in this case some html:\r\n\r\n```jade\r\nhtml\r\n body\r\n |foo bar baz
\r\n```\r\n\r\n Or we can use the trailing `.` to indicate to Jade that we\r\n only want text in this block, allowing us to omit the pipes:\r\n\r\n```jade\r\nhtml\r\n body.\r\nfoo bar baz
\r\n```\r\n\r\n Both of these examples yield the same result:\r\n\r\n```html\r\nfoo bar baz
\r\n\r\n```\r\n\r\n The same rule applies for anywhere you can have text\r\n in jade, raw html is fine:\r\n\r\n```jade\r\nhtml\r\n body\r\n h1 User #{name}\r\n```\r\n\r\n\r\n### Doctypes\r\n\r\nTo add a doctype simply use `!!!`, or `doctype` followed by an optional value:\r\n\r\n```jade\r\n!!!\r\n```\r\n\r\nor\r\n\r\n```jade\r\ndoctype\r\n```\r\n\r\nWill output the _html 5_ doctype, however:\r\n\r\n```jade\r\n!!! transitional\r\n```\r\n\r\nWill output the _transitional_ doctype.\r\n\r\nDoctypes are case-insensitive, so the following are equivalent:\r\n\r\n```jade\r\ndoctype Basic\r\ndoctype basic\r\n```\r\n\r\nit's also possible to simply pass a doctype literal:\r\n\r\n```jade\r\ndoctype html PUBLIC \"-//W3C//DTD XHTML Basic 1.1//EN\"\r\n```\r\n\r\nyielding:\r\n\r\n```html\r\n\r\n```\r\n\r\nBelow are the doctypes defined by default, which can easily be extended:\r\n\r\n```js\r\nvar doctypes = exports.doctypes = {\r\n '5': '',\r\n 'default': '',\r\n 'xml': '',\r\n 'transitional': '',\r\n 'strict': '',\r\n 'frameset': '',\r\n '1.1': '',\r\n 'basic': '',\r\n 'mobile': ''\r\n};\r\n```\r\n\r\nTo alter the default simply change:\r\n\r\n```js\r\njade.doctypes.default = 'whatever you want';\r\n```\r\n\r\n\r\n## Filters\r\n\r\nFilters are prefixed with `:`, for example `:markdown` and\r\npass the following block of text to an arbitrary function for processing. View the _features_\r\nat the top of this document for available filters.\r\n\r\n```jade\r\nbody\r\n :markdown\r\n Woah! jade _and_ markdown, very **cool**\r\n we can even link to [stuff](http://google.com)\r\n```\r\n\r\nRenders:\r\n\r\n```html\r\nWoah! jade and markdown, very cool we can even link to stuff
\r\n```\r\n\r\n\r\n## Code\r\n\r\nJade currently supports three classifications of executable code. The first\r\nis prefixed by `-`, and is not buffered:\r\n\r\n```jade\r\n- var foo = 'bar';\r\n```\r\n\r\nThis can be used for conditionals, or iteration:\r\n\r\n```jade\r\n- for (var key in obj)\r\n p= obj[key]\r\n```\r\n\r\nDue to Jade's buffering techniques the following is valid as well:\r\n\r\n```jade\r\n- if (foo)\r\n ul\r\n li yay\r\n li foo\r\n li worked\r\n- else\r\n p oh no! didnt work\r\n```\r\n\r\nHell, even verbose iteration:\r\n\r\n```jade\r\n- if (items.length)\r\n ul\r\n - items.forEach(function(item){\r\n li= item\r\n - })\r\n```\r\n\r\nAnything you want!\r\n\r\nNext up we have _escaped_ buffered code, which is used to\r\nbuffer a return value, which is prefixed by `=`:\r\n\r\n```jade\r\n- var foo = 'bar'\r\n= foo\r\nh1= foo\r\n```\r\n\r\nWhich outputs `barWelcome to my super lame site.
\r\n \r\n \r\n\r\n```\r\n\r\nAs mentioned `include` can be used to include other content\r\nsuch as html or css. By providing an extension, Jade will\r\nread that file in, apply any [filter](#a7) matching the file's\r\nextension, and insert that content into the output.\r\n\r\n```jade\r\nhtml\r\n head\r\n //- css and js have simple filters that wrap them in\r\n