UNPKG

oj

Version:

A unified templating language for the people. Thirsty people.

1,647 lines (1,348 loc) 95.6 kB
// // oj.js v0.9.1 // ojjs.org // // Copyright 2013-2019, Evan Moran // Released under the MIT License // // =================================================================== // Unified templating for the people. Thirsty people. ;(function(root, factory){ var initDom = "<!DOCTYPE html><html><head></head><body></body></html>"; // CommonJS export for Node if (typeof module === 'object' && module.exports) { try {$ = root.$ || require('jquery')} catch (e){} var thedoc = root.document; try { if (!thedoc) { jsdom = require('jsdom'); dom = new jsdom.JSDOM(initDom); thedoc = dom.window.document; } } catch (e){} module.exports = factory(root, $, thedoc) } // else if (typeof define === 'function' && define.amd) // // AMD export for RequireJS // define(['jquery'], ['jsdom'], function($, jsdom){ // return factory(root, $, new jsdom.JSDOM(initDom).window.document) // }) // Global export for client side else root.oj = factory(root, (root.$ || root.jQuery || root.Zepto || root.ender), root.document) }(this, function(root, $, doc){ var ArrP = Array.prototype, FunP = Function.prototype, ObjP = Object.prototype, slice = ArrP.slice, unshift = ArrP.unshift, concat = ArrP.concat, pass = function(v){return v}, _udf = 'undefined' // oj function: highest level of capture for tag functions function oj(){ return oj.tag.apply(this, ['oj'].concat(slice.call(arguments)).concat([{__quiet__:1}])) } // Version oj.version = '0.9.1' // Configuration settings oj.settings = { defaultThemes: null } oj.isClient = !(typeof process !== _udf && process !== null ? process.versions != null ? process.versions.node : 0 : 0) // Detect jQuery globally or in required module if (typeof $ != _udf) oj.$ = $ // Reference ourselves for template files to see oj.oj = oj // oj.load: load the page specified generating necessary html, css, and client side events oj.load = function(page, data){ // Defer dom manipulation until the page is ready return oj.$(function(){ // Load through require and passing through template data var ojml = function(){require(page).call(data, data)} oj.$.ojBody(ojml) // Trigger events bound through onload return oj.onload() }) } // oj.onload: Enlist in onload action or run them. var _onLoadQueue = {queue: [], loaded: false} oj.onload = function(f){ // Call everything if no arguments if (oj.isUndefined(f)){ _onLoadQueue.loaded = true while ((f = _onLoadQueue.queue.shift())) f() } // Call load if already loaded else if (_onLoadQueue.loaded) f() // Queue function for later else _onLoadQueue.queue.push(f) } // oj.emit: Used by plugins to group multiple elements as if it is a single tag. oj.emit = function(){return oj.tag.apply(oj, ['oj'].concat(slice.call(arguments)))} // Type Helpers oj.isDefined = function(a){return typeof a !== _udf} oj.isOJ = function(obj){return !!(obj != null ? obj.isOJ : void 0)} oj.isOJType = function(a){return oj.isOJ(a) && a.type === a} oj.isOJInstance = function(a){return oj.isOJ(a) && !oj.isOJType(a)} oj.isEvented = function(a){return !!(a && a.on && a.off && a.trigger)} oj.isDOM = function(a){return !!(a && (a.nodeType != null))} oj.isDOMElement = function(a){return !!(a && a.nodeType === 1)} oj.isDOMAttribute = function(a){return !!(a && a.nodeType === 2)} oj.isDOMText = function(a){return !!(a && a.nodeType === 3)} oj.isjQuery = function(a){return !!(a && a.jquery)} oj.isUndefined = function(a){return a === void 0} oj.isBoolean = function(a){return a === true || a === false || ObjP.toString.call(a) === '[object Boolean]'} oj.isNumber = function(a){return !!(a === 0 || (a && a.toExponential && a.toFixed))} oj.isString = function(a){return !!(a === '' || (a && a.charCodeAt && a.substr))} oj.isDate = function(a){return !!(a && a.getTimezoneOffset && a.setUTCFullYear)} oj.isPlainObject = function(a){return oj.$.isPlainObject(a) && !oj.isOJ(a)} oj.isFunction = oj.$.isFunction oj.isArray = oj.$.isArray oj.isRegEx = function(a){return ObjP.toString.call(a) === '[object RegExp]'} oj.isArguments = function(a){return ObjP.toString.call(a) === '[object Arguments]'} oj.parse = function(str){ var n, o = str if (str === _udf) o = void 0 else if (str === 'null') o = null else if (str === 'true') o = true else if (str === 'false') o = false else if (!isNaN(n = parseFloat(str))) o = n return o } // unionArguments: Union arguments into options and args oj.unionArguments = function(argList){ var options = {}, args = [], v, ix = 0 for (; ix < argList.length; ix++){ v = argList[ix] if (oj.isPlainObject(v)) options = _extend(options, v) else args.push(v) } return {options: options, args: args} } // argumentShift: Shift argument out of options with key oj.argumentShift = function(options, key){ var value if ((oj.isPlainObject(options)) && (key != null) && (options[key] != null)){ value = options[key] delete options[key] } return value } // Utility Helpers var _keys = Object.keys, _extend = oj.$.extend function _isCapitalLetter(c){return !!(c.match(/[A-Z]/))} function _has(obj, key){return ObjP.hasOwnProperty.call(obj, key)} function _values(obj){ var keys = _keys(obj), len = keys.length, values = new Array(len), i = 0 for (; i < len; i++) values[i] = obj[keys[i]] return values } function _toArray(obj){ if (!obj) return [] if (oj.isArray(obj)) return slice.call(obj) if (oj.isArguments(obj)) return slice.call(obj) if (obj.toArray && oj.isFunction(obj.toArray)) return obj.toArray() return _values(obj) } function _isEmpty(o){ if (oj.isArray(o)) return o.length === 0 for (var k in o) if (_has(o, k)) return false return true } function _clone(o){ if (!(oj.isArray(o) || oj.isPlainObject(o))) return o if (oj.isArray(o)) return o.slice() else return _extend({}, o) } // _setObject(obj, k1, k2, ..., value): // Set object deeply key by key ensure each part is an object function _setObject(obj){ var args = arguments, o = obj, len = args.length, // keys are args ix: 1 to n-2 keys = 3 <= len ? slice.call(args, 1, len = len - 1) : (len = 1, []), // value is last arg value = args[len++], ix, k for (ix = 0; ix < keys.length; ix++){ k = keys[ix] // Initialize key to empty object if necessary if (typeof o[k] !== 'object') o[k] = {} // Set final value if this is the last key if (ix === keys.length - 1) o[k] = value else // Continue deeper o = o[k] } return obj } // uniqueSort: function uniqueSort(arr, isSorted){ if (isSorted == null) isSorted = false if (!isSorted) arr.sort() var out = [], ix, item for (ix = 0; ix < arr.length; ix++){ item = arr[ix] if (ix > 0 && arr[ix - 1] === arr[ix]) continue out.push(item) } return out } // _d(args...): initialization helper that returns first arg that isn't null function _d(){ for (var ix = 0;ix < arguments.length; ix++) if (arguments[ix] != null) return arguments[ix] return null } // _e: error by throwing msg with optional fn name function _e(fn, msg){ msg = _d(msg, fn, '') fn = _d(fn, 0) var pfx = "oj: " if (fn) pfx = "oj." + fn + ": " throw new Error(pfx + msg) } // _a: assert when cond is false with msg and with optional fn name function _a(cond, fn, msg){if (!cond) _e(fn,msg)} // _v: validate argument n with fn name and message function _v(fn, n, v, type){ n = {1:'first',2:'second',3: 'third', 4: 'fourth'}[n] _a(!type || (typeof v === type), fn, "" + type + " expected for " + n + " argument") } // _splitAndTrim: Split string by seperator and trim result function _splitAndTrim(str, seperator, limit){ return str.split(seperator, limit).map(function(v){ return v.trim() }) } // _decamelize: Convert from camal case to underscore case function _decamelize(str){return str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase()} // _dasherize: Convert from camal case or space seperated to dashes function _dasherize(str){return _decamelize(str).replace(/[ _]/g, '-')} // oj.addMethod: add multiple methods to an object oj.addMethods = function(obj, mapNameToMethod){ for (var methodName in mapNameToMethod) oj.addMethod(obj, methodName, mapNameToMethod[methodName]) } // oj.addMethod: Add method to object with name and fn oj.addMethod = function(obj, name, fn){ _v('addMethod', 2, name, 'string') _v('addMethod', 3, fn, 'function') // Methods are non-enumerable, non-writable properties Object.defineProperty(obj, name, { value: fn, enumerable: false, writable: false, configurable: true }) } // oj.removeMethod: Remove a method with name from an object oj.removeMethod = function(obj, name){ _v('removeMethod', 2, name, 'string') delete obj[name] } // oj.addProperties: add multiple properties to an object // Properties can be specified by get/set methods or by a value // Optionally you can include writable or enumerable settings oj.addProperties = function(obj, mapNameToInfo){ // Iterate over properties var propInfo, propName for (propName in mapNameToInfo){ propInfo = mapNameToInfo[propName] // Wrap the value if propInfo is not already a prop definition if (((propInfo != null ? propInfo.get : void 0) == null) && ((propInfo != null ? propInfo.value : void 0) == null)) propInfo = {value: propInfo, writable: true} oj.addProperty(obj, propName, propInfo) } } // oj.addProperty: Add property to object with name and info oj.addProperty = function(obj, name, info){ _v('addProperty', 2, name, 'string') _v('addProperty', 3, info, 'object') // Default properties to enumerable and configurable info = _extend({enumerable: true, configurable: true}, info) // Remove property if it already exists if (Object.getOwnPropertyDescriptor(obj, name) != null) oj.removeProperty(obj, name) // Add the property Object.defineProperty(obj, name, info) } // oj.removeProperty: remove property from object with name oj.removeProperty = function(obj, name){ _v('removeProperty', 2, name, 'string') delete obj[name] } // oj.isProperty: Determine property in object is a get/set property oj.isProperty = function(obj, name){ _v('isProperty', 2, name, 'string') return Object.getOwnPropertyDescriptor(obj, name).get != null } // oj.copyProperty: Copy source.propName to dest.propName oj.copyProperty = function(dest, source, propName){ var info = Object.getOwnPropertyDescriptor(source, propName) info = _d(info, {value: [], enumerable: false, writable: true, configurable: true }) if (info.value != null) info.value = _clone(info.value) return Object.defineProperty(dest, propName, info) } // _argsStack: Abstraction to wrap global arguments stack. // This makes me sad but it is necessary for div -> syntax var _argsStack = [] // Access the top of the stack oj._argsTop = function(){ if (_argsStack.length) return _argsStack[_argsStack.length - 1] else return null } // Push scope onto arguments oj._argsPush = function(args){ _argsStack.push(_d(args,[])) } // Pop scope from arguments oj._argsPop = function(){ if (_argsStack.length) return _argsStack.pop() return null } // Append argument oj._argsAppend = function(arg){ var top = oj._argsTop() if (top != null) top.push(arg) } // oj.tag (name, attributes, rest...) oj.tag = function(name){ _v('tag', 1, name, 'string') var rest = 2 <= arguments.length ? slice.call(arguments, 1) : [], u = oj.unionArguments(rest), args = u.args, attributes = u.options, isQuiet = attributes.__quiet__, arg, len, r, ix, // Build ojml starting with tag ojml = [name] if (isQuiet) delete attributes.__quiet__ // Add attributes to ojml if they exist if (!_isEmpty(attributes)) ojml.push(attributes) // Store current tag context oj._argsPush(ojml) // Loop over attributes for (ix = 0; ix < args.length; ix++){ arg = args[ix] if (oj.isPlainObject(arg)) continue else if (oj.isFunction(arg)){ len = oj._argsTop().length // Call the fn tags will append to oj._argsTop r = arg() // Use return value instead if oj._argsTop didn't change if (len === oj._argsTop().length && (r != null)) oj._argsAppend(r) } else oj._argsAppend(arg) } // Restore previous tag context oj._argsPop() // Append the final result to your parent's arguments // if there exists an argument to append to. // Do not emit when quiet is set, if (!isQuiet) oj._argsAppend(ojml) return ojml } // Define all elements as closed or open oj.tag.elements = { closed: 'a abbr acronym address applet article aside audio b bdo big blockquote body button canvas caption center cite code colgroup command datalist dd del details dfn dir div dl dt em embed fieldset figcaption figure font footer form frameset h1 h2 h3 h4 h5 h6 head header hgroup html i iframe ins keygen kbd label legend li map mark menu meter nav noframes noscript object ol optgroup option output p pre progress q rp rt ruby s samp script section select small source span strike strong style sub summary sup table tbody td textarea tfoot th thead time title tr tt u ul var video wbr xmp'.split(' '), open: 'area base br col command css !DOCTYPE embed hr img input keygen link meta param source track wbr'.split(' ') } // Keep track of all valid elements oj.tag.elements.all = (oj.tag.elements.closed.concat(oj.tag.elements.open)).sort() // tag.isClosed: Determine if an element is closed or open oj.tag.isClosed = function(tag){return oj.tag.elements.open.indexOf(tag) === -1} // _setTagName: Record tag name on a given tag function function _setTagName(tag, name){if (tag != null) tag.tagName = name } // _getTagName: Get a tag name on a given tag function function _getTagName(tag){return tag.tagName} // _getQuietTagName: Get quiet tag name function _getQuietTagName(tag){return '_' + tag} // _setInstanceOnElement: Record an oj instance on a given element function _setInstanceOnElement(el, inst){if (el != null) el.oj = inst} // _getInstanceOnElement: Get a oj instance on a given element function _getInstanceOnElement(el){ if ((el != null ? el.oj : 0) != null) return el.oj else return null } // Create tag methods for all elements for (var ix = 0; ix < oj.tag.elements.all.length; ix++){ t = oj.tag.elements.all[ix] ;(function(t){ // Define tag function named t oj[t] = function(){return oj.tag.apply(oj, [t].concat(slice.call(arguments)))} // Quiet tag functions do not emit // Define quiet tag function named qt var qt = _getQuietTagName(t) oj[qt] = function(){return oj.tag.apply(oj, [t, {__quiet__: 1 }].concat(slice.call(arguments)))} // Tag functions remember their name so the OJML syntax can use the function _setTagName(oj[t], t) _setTagName(oj[qt], t) })(t) } // oj.doctype: Method to define doctypes based on short names var dhp = 'HTML PUBLIC "-//W3C//DTD HTML 4.01', w3 = '"http://www.w3.org/TR/html4/', strict5 = 'html', strict4 = dhp + '//EN" ' + w3 + 'strict.dtd"', _doctypes = { '5': strict5, 'HTML 5': strict5, '4': strict4, 'HTML 4.01 Strict': strict4, 'HTML 4.01 Frameset': dhp + ' Frameset//EN" ' + w3 + 'frameset.dtd"', 'HTML 4.01 Transitional': dhp + ' Transitional//EN" ' + w3 + 'loose.dtd"' } // Define the method passing through to !DOCTYPE tag function oj.doctype = function(typeOrValue){ typeOrValue = _d(typeOrValue,'5') return oj['!DOCTYPE'](_d(_doctypes[typeOrValue], typeOrValue)) } // oj.extendInto (context): Extend all OJ methods into a context. // Defaults to root, which is either `global` or `window` // Methods that start with _ are not extended oj.useGlobally = oj.extendInto = function(context){ context = _d(context,root) var o = {}, k, qn, v // For all keys and values in oj for (k in oj){ v = oj[k] // Extend into new object if (k[0] !== '_' && k !== 'extendInto' && k !== 'useGlobally'){ o[k] = v // Export _tag and _Type methods qn = _getQuietTagName(k) if (oj[qn]) o[qn] = oj[qn] } } _extend(context, o) } // oj.compile(options, ojml) // --- // Compile ojml into meaningful parts // options: // * html - Compile to html // * dom - Compile to dom // * css - Compile to css // * cssMap - Record css as a javascript object // * styles -- Output css as style tags // * minify - Minify js and css // * ignore:{html:1} - Map of tags to ignore while compiling oj.compile = function(options, ojml){ // Options is optional ojml = _d(ojml, options) var css, cssMap, dom, html, // Default options to compile everything options = _extend({html:1, dom:0, css:0, cssMap:0, minify:0, ignore:{}}, options), // Init accumulator acc = _clone(options) acc.html = options.html ? [] : null acc.dom = options.dom && (typeof doc !== "undefined" && doc !== null) ? doc.createElement('OJ') : null acc.css = options.css || options.cssMap ? {} : null acc.indent = '' acc.data = options.data // Accumulate insert events per element acc.inserts = [] if (options.dom) acc.types = [] acc.tags = {} // Always ignore oj and css tags _extend(options.ignore, {oj:1, css:1}) // Recursive compile to accumulator _compileAny(ojml, acc) // Flatten CSS if (acc.css != null) cssMap = _flattenCSSMap(acc.css) // Generate css if necessary if (options.css) css = _cssFromPluginObject(cssMap, {minify: options.minify, tags: 0}) // Output cssMap if necessary if (!options.cssMap) cssMap = undefined // Generate HTML if necessary if (options.html) html = acc.html.join('') // Generate dom if necessary if (options.dom){ // Remove the <oj> wrapping from the dom element dom = acc.dom.childNodes // Cleanup inconsistencies of childNodes if (dom.length != null){ // Make dom a real array dom = _toArray(dom) // Filter out anything that isn't a dom element dom = dom.filter(function(v){return oj.isDOM(v)}) } // Ensure dom is null if empty if (dom.length === 0) dom = null // Single elements are not returned as a list else if (dom.length === 1) // Reasoning: The common cases don't have multiple elements // <html>,<body>, etc and this is abstracted for you anyway // with jQuery plugins dom = dom[0] } return { html: html, dom: dom, css: css, cssMap: cssMap, types: acc.types, tags: acc.tags, inserts: acc.inserts } } // _styleFromObject: Convert object to style string function _styleFromObject(obj, options){ options = _extend({ inline: true, indent: '' }, options) // Trailing semi should only exist on when we aren't indenting options.semi = !options.inline var out = "", // Sort keys to create consistent output keys = _keys(obj).sort(), // Support indention and inlining indent = options.indent != null ? options.indent : '', newline = options.inline ? '' : '\n', ix, k, kFancy, semi for (ix = 0; ix < keys.length; ix++){ kFancy = keys[ix] // Add semi if it is not inline or it is not the last key semi = options.semi || ix !== keys.length - 1 ? ";" : '' // Allow keys to be camal case k = _dasherize(kFancy) // Collect css result for this key out += "" + indent + k + ":" + obj[kFancy] + semi + newline } return out } // _attributesFromObject: Convert object to attribute string with no special conversions function _attributesFromObject(obj){ if (!oj.isPlainObject(obj)) return obj // Pass through non objects var k, v, ix, out = '', space = '', // Serialize attributes in order for consistent output attrs = _keys(obj).sort() for (ix = 0; ix < attrs.length; ix++){ k = attrs[ix] v = obj[k] // Boolean attributes have no value if (v === true) out += "" + space + k // Other attributes have a value else out += "" + space + k + "=\"" + v + "\"" space = ' ' } return out } // _flattenCSSMap: Take an OJ cssMap and flatten it into the form // `'plugin' -> '@media query' -> 'selector' ->'rulesMap'` // // This method vastly simplifies `_cssFromPluginObject` // Nested, media, and comma definitions are resolved and merged function _flattenCSSMap(cssMap){ var flatMap = {}, plugin, cssMap_ for (plugin in cssMap){ cssMap_ = cssMap[plugin] _flattenCSSMap_(cssMap_, flatMap, [''], [''], plugin) } return flatMap } // Recursive helper with accumulators (it outputs flatMapAcc) function _flattenCSSMap_(cssMap, flatMapAcc, selectorsAcc, mediasAcc, plugin){ // Built in media helpers var acc, cur, inner, isMedia, mediaJoined, mediasNext, next, outer, parts, rules, selector, selectorJoined, selectorsNext, o, i, medias = { 'widescreen': 'only screen and (min-width: 1200px)', 'monitor': '', 'tablet': 'only screen and (min-width: 768px) and (max-width: 959px)', 'phone': 'only screen and (max-width: 767px)' } for (selector in cssMap){ rules = cssMap[selector] // Base Case: Record our selector when `rules` is a value if (typeof rules !== 'object'){ // Join selectors and media accumulators with commas selectorJoined = selectorsAcc.sort().join(',') mediaJoined = mediasAcc.sort().join(',') // Prepend @media as that was removed previously when spliting into parts if (mediaJoined !== '') mediaJoined = "@media " + mediaJoined // Record the rule deeply in `flatMapAcc` _setObject(flatMapAcc, plugin, mediaJoined, selectorJoined, selector, rules) // Recursive Case: Recurse on `rules` when it is an object } else { // (r1) Media Query found: Generate the next media queries if (selector.indexOf('@media') === 0){ isMedia = true mediasNext = next = [] selectorsNext = selectorsAcc selector = (selector.slice('@media'.length)).trim() acc = mediasAcc // (r2) Selector found: Generate the next selectors } else { isMedia = false selectorsNext = next = [] mediasNext = mediasAcc acc = selectorsAcc } // Media queries and Selectors can be comma seperated parts = _splitAndTrim(selector, ',') // Media queries have convience substitutions like 'phone', 'tablet' if (isMedia){ parts = parts.map(function(v){ return _d(medias[v], v) }) } // Determine the next selectors or media queries for (o = 0; o < acc.length; o++){ outer = acc[o] for (i = 0; i < parts.length; i++){ inner = parts[i] // When `&` is not present just insert in front with the correct join operator cur = inner if ((inner.indexOf('&')) === -1 && outer !== '') cur = (isMedia ? '& and ' : '& ') + cur next.push(cur.replace(/&/g, outer)) } } // Recurse through objects after calculating the next selectors _flattenCSSMap_( rules, flatMapAcc, selectorsNext, mediasNext, plugin ) } } } // _styleClassFromPlugin: Abstract plugin <style> naming function _styleClassFromPlugin(plugin){return "" + plugin + "-style"} // _styleTagFromMediaObject: Abstract creating <style> tag oj._styleTagFromMediaObject = function(plugin, mediaMap, options){ var newline = (options != null ? options.minify : void 0) ? '' : '\n', css = _cssFromMediaObject(mediaMap, options) return "<style class=\"" + (_styleClassFromPlugin(plugin)) + "\">" + newline + css + "</style>" } // _cssFromMediaObject: Convert css from a flattened mediaMap rule object. // The rule object is of the form: // mediaQuery => selector => rulesObject function _cssFromMediaObject(mediaMap, options){ options = _d(options, {}) var indent, indentRule, media, rules, selector, selectorMap, space, styles, minify = options.minify != null ? options.minify : 0, tags = options.tags != null ? options.tags : 0, // Deterine what output characters are needed newline = minify ? '' : '\n', space = minify ? '' : ' ', inline = minify, css = '' // Build css for media => selector => rules for (media in mediaMap){ selectorMap = mediaMap[media] // Serialize media query if (media){ media = media.replace(/,/g, "," + space) css += "" + media + space + "{" + newline } for (selector in selectorMap){ styles = selectorMap[selector] indent = (!minify) && media ? '\t' : '' // Serialize selector selector = selector.replace(/,/g, "," + newline) css += "" + indent + selector + space + "{" + newline // Serialize style rules indentRule = !minify ? indent + '\t' : indent rules = _styleFromObject(styles, { inline: inline, indent: indentRule }) css += rules + indent + '}' + newline } // End media query if (media !== '') css += '}' + newline } try { css = oj._minifyCSS(css, options) } catch (e){ throw new Error("css minification error: " + e.message + "\nCould not minify:\n" + css) } return css } // _cssFromPluginObject: Convert flattened css selectors and rules to a string // pluginMaps are of the form: // pluginName => mediaQuery => selector => rulesObject // minify:false will output newlines // tags:true will output the css in `<style>` tags function _cssFromPluginObject(flatCSSMap, options){ options = _d(options, {}) var mediaMap, plugin, minify = options.minify != null ? options.minify : 0, tags = options.tags != null ? options.tags : 0, // Deterine what output characters are needed newline = minify ? '' : '\n', space = minify ? '' : ' ', inline = minify, css = '' for (plugin in flatCSSMap){ mediaMap = flatCSSMap[plugin] if (tags) css += "<style class=\"" + plugin + "-style\">" + newline // Serialize CSS with potential minification css += _cssFromMediaObject(mediaMap, options) if (tags) css += "" + newline + "</style>" + newline } return css } // _compileDeeper: Recursive helper for compiling that wraps indention function _compileDeeper(method, ojml, options){ var i = options.indent options.indent += '\t' method(ojml, options) options.indent = i } // _compileAny Recursive helper for compiling ojml or any type function _compileAny(any, options){ // Array if (oj.isArray(any)) _compileTag(any, options) // String else if (oj.isString(any)){ if (options.html != null) options.html.push(any) if (any.length > 0 && any[0] === '<'){ var root = doc.createElement('div') root.innerHTML = any if (options.dom != null) options.dom.appendChild(root) } else { if (options.dom != null) options.dom.appendChild(doc.createTextNode(any)) } // Boolean or Number } else if (oj.isBoolean(any) || oj.isNumber(any)){ if (options.html != null) options.html.push("" + any) if (options.dom != null) options.dom.appendChild(doc.createTextNode("" + any)) // Function } else if (oj.isFunction(any)){ // Wrap function call to allow full oj generation within any var data = options.data || {}; _compileAny(oj(function(){any.call(data, data)}), options); // Date } else if (oj.isDate(any)){ if (options.html != null) options.html.push("" + (any.toLocaleString())) if (options.dom != null) options.dom.appendChild(doc.createTextNode("" + (any.toLocaleString()))) // OJ Type or Instance } else if (oj.isOJ(any)){ if (options.types != null) options.types.push(any) if (options.html != null) options.html.push(any.toHTML(options)) if (options.dom != null) options.dom.appendChild(any.toDOM(options)) if (options.css != null) _extend(options.css, any.toCSSMap(options)) } // Do nothing for: null, undefined, object } // _compileTag: Recursive helper for compiling ojml tags function _compileTag(ojml, options){ // Empty list compiles to undefined if (ojml.length === 0) return // The first part of ojml is the tag var tag = ojml[0], tagType = typeof tag, u = oj.unionArguments(ojml.slice(1)), attributes = u.options, children = u.args, styles, selector // Allow the tag parameter to be 'table' (string) or oj.table (function) or oj.Table (object) if ((tagType === 'function' || tagType === 'object')) tag = _d(_getTagName(tag), tag) // Fail if no tag found if (!(oj.isString(tag) && tag.length > 0)) _e('compile', 'tag name is missing') // Record tag as encountered options.tags[tag] = true // Instance oj object if tag is capitalized if (_isCapitalLetter(tag[0])) return _compileDeeper(_compileAny, new oj[tag](ojml.slice(1)), options) // Compile to css if requested if (options.css && tag === 'css'){ // Extend options.css with rules for (selector in attributes){ styles = attributes[selector] options.css['oj'] = _d(options.css['oj'], {}) options.css['oj'][selector] = _d(options.css['oj'][selector], {}) _extend(options.css['oj'][selector], styles) } } // Compile DOCTYPE as special case because it is not really an element // It has attributes with spaces and cannot be created by dom manipulation // In this way it is HTML generation only. if (tag === '!DOCTYPE'){ _v('compile', 1, ojml[1], 'string') if (!options.ignore[tag]){ if (options.html) options.html.push("<" + tag + " " + ojml[1] + ">") // options.dom is purposely ignored } return } if (!options.ignore[tag]){ var events = _attributesProcessedForOJ(attributes), el // Compile to dom if requested // Add dom element with attributes if (options.dom && (typeof doc !== _udf && doc !== null)){ // Create element el = doc.createElement(tag) // Add self to parent if (oj.isDOMElement(options.dom)) options.dom.appendChild(el) // Push ourselves on the dom stack (to handle children) options.dom = el // Set attributes in sorted order for consistency if (oj.isPlainObject(attributes)){ var keys = _keys(attributes).sort(), ix, attrName, attrValue for (ix = 0; ix < keys.length; ix++){ attrName = keys[ix] attrValue = attributes[attrName] // Boolean attributes have no value if (attrValue === true) el.setAttributeNode(doc.createAttribute(attrName)) else el.setAttribute(attrName, attrValue) } } // Bind events _attributesBindEventsToDOM(events, el, options.inserts) } // Compile to html if requested // Add tag with attributes if (options.html){ var attr = _d(_attributesFromObject(attributes), ''), space = attr === '' ? '' : ' ' options.html.push("<" + tag + space + attr + ">") // Recurse through children if this tag isn't ignored deeply } } if (options.ignore[tag] !== 'deep'){ for (ix = 0; ix < children.length; ix++){ var child = children[ix] // Skip indention if there is only one child if (options.html != null && !options.minify && children.length > 1) options.html.push("\n\t" + options.indent) _compileDeeper(_compileAny, child, options) } } // Skip indention if there is only one child if (options.html != null && !options.minify && children.length > 1) options.html.push("\n" + options.indent) // End html tag if you have children or your tag closes if (!options.ignore[tag]){ // Close tag if html if (options.html != null && (children.length > 0 || oj.tag.isClosed(tag))) options.html.push("</" + tag + ">") // Pop ourselves if dom if (options.dom) options.dom = options.dom.parentNode } } // _attributesProcessedForOJ: Process attributes to make them easier to use function _attributesProcessedForOJ(attr){ var jqEvents = {bind:1, on:1, off:1, live:1, blur:1, change:1, click:1, dblclick:1, focus:1, focusin:1, focusout:1, hover:1, keydown:1, keypress:1, keyup:1, mousedown:1, mouseenter:1, mouseleave:1, mousemove:1, mouseout:1, mouseup:1, ready:1, resize:1, scroll:1, select:1, insert:1}, events, k, v // Allow attributes to alias c to class and use arrays instead of space seperated strings // Convert to c and class from arrays to strings if (oj.isArray(attr != null ? attr.c : void 0)) attr.c = attr.c.join(' ') if (oj.isArray(attr != null ? attr["class"] : void 0)) attr["class"] = attr["class"].join(' ') // Move c to class if ((attr != null ? attr.c : void 0) != null){ if ((attr != null ? attr["class"] : void 0) != null) attr["class"] += ' ' + attr.c else attr["class"] = attr.c delete attr.c } // Allow attributes to take style as an object if (oj.isPlainObject(attr != null ? attr.style : void 0)){ attr.style = _styleFromObject(attr.style, { inline: true }) } // Omit attributes with values of false, null, or undefined if (oj.isPlainObject(attr)){ for (k in attr){ v = attr[k] if (v === null || v === void 0 || v === false) delete attr[k] } } // Filter out jquery events events = {} if (oj.isPlainObject(attr)){ // Filter out attributes that are jquery events for (k in attr){ v = attr[k] // If this attribute (k) is an event if (jqEvents[k] != null){ events[k] = v delete attr[k] } } } // Returns bindable events return events } // Bind events to dom function _attributesBindEventsToDOM(events, el, inserts){ var ek, ev, _results = [], _defer = function(ev, el){return function(){ev.call(el,el)}} for (ek in events){ ev = events[ek] _a(oj.$ != null, "jquery is missing when binding a '" + ek + "' event") // accumulate insert events manually since DOMNodeInserted is slow and depreciated if (ek == 'insert' && inserts) inserts.push(_defer(ev,el)) else if (oj.isArray(ev)) _results.push(oj.$(el)[ek].apply(this, ev)) else _results.push(oj.$(el)[ek](ev)) } return _results } // oj.toHTML: Compile directly to HTML only oj.toHTML = function(options, ojml){ // Options is optional if (!oj.isPlainObject(options)){ ojml = options options = {} } // Create html only _extend(options, {dom: 0, js: 0, html: 1, css: 0}) return (oj.compile(options, ojml)).html } // oj.toCSS: Compile directly to CSS only oj.toCSS = function(options, ojml){ // Options is optional if (!oj.isPlainObject(options)){ ojml = options options = {} } // Create css only _extend(options, {dom: 0, js: 0, html: 0, css: 1}) return (oj.compile(options, ojml)).css } // _inherit: Inherit Child from Parent // Based on, but sadly incompatable with, coffeescript inheritance function _inherit(Child, Parent){ var Ctor, prop // Copy class properties and methods for (prop in Parent) oj.copyProperty(Child, Parent, prop) Ctor = function(){} Ctor.prototype = Parent.prototype Child.prototype = new Ctor() // Provide easy access for base class methods // Example: Parent.base.methodName(arguments...) Child.base = Child.__super__ = Parent.prototype } // _construct(Type, arg1, arg2, ...): Construct type as if using call function _construct(Type){ return new (FunP.bind.apply(Type, arguments)) } // oj.createType: Create OJ type with args object supporting: // base, constructor, properties, and methods oj.createType = function(name, args){ args = _d(args, {}) args.methods = _d(args.methods, {}) args.properties = _d(args.properties, {}) _v('createType', 1, name, 'string') _v('createType', 2, args, 'object') var methodKeys, propKeys, typeProps, // When auto newing you need to delay construct the properties // or they will be constructed twice. delay = '__DELAYED__', // Constructor to return Out = new Function("return function " + name + "(){\n var _t = this;\n if ( !(this instanceof " + name + ") ){\n _t = new " + name + "('" + delay + "');\n _t.__autonew__ = true;\n }\n\n if (arguments && arguments[0] != '" + delay + "')\n " + name + ".prototype.constructor.apply(_t, arguments);\n\n return _t;\n}")() // Default the constructor to call its base if (args.base != null && (args.constructor == null || (!args.hasOwnProperty('constructor')))){ args.constructor = function(){ return Out.base != null ? Out.base.constructor.apply(this, arguments) : void 0 } } // Inherit if necessary if (args.base != null) _inherit(Out, args.base) // Add the constructor as a method oj.addMethod(Out.prototype, 'constructor', args.constructor) // Mark new type and its instances with a non-enumerable type and isOJ properties typeProps = { type: { value: Out, writable: false, enumerable: false }, typeName: { value: name, writable: false, enumerable: false }, isOJ: { value: true, writable: false, enumerable: false } } oj.addProperties(Out, typeProps) oj.addProperties(Out.prototype, typeProps) // Add properties all oj Types have propKeys = (_keys(args.properties)).sort() if (Out.prototype.properties != null) propKeys = uniqueSort(Out.prototype.properties.concat(propKeys)) oj.addProperty(Out.prototype, 'properties', { value: propKeys, writable: false, enumerable: false }) // Add methods helper to instance methodKeys = (_keys(args.methods)).sort() if (Out.prototype.methods != null) methodKeys = uniqueSort(Out.prototype.methods.concat(methodKeys)) oj.addProperty(Out.prototype, 'methods', { value: methodKeys, writable: false, enumerable: false }) // Add methods all oj Types have _extend(args.methods, { // get: Get property by key or get all properties get: function(k){ // get specific property if (oj.isString(k)){ if (this.has(k)) return this[k] // get all properties } else { var out = {}, ix, p for (ix = 0; ix < this.properties.length; ix++){ p = this.properties[ix] out[p] = this[p] } return out } }, // set: Set property by key, or set all properties with object set: function(k, v){ var key, obj = k, value // Optionally take key, value instead of object if (!oj.isPlainObject(k)){ obj = {} obj[k] = v } // Set all keys that are valid properties for (key in obj){ value = obj[key] if (this.has(key)){ this[key] = value } } }, // has: Determine if property exists has: function(k){return this.properties.some(function(v){return v === k})}, // can: Determine if method exists can: function(k){return this.methods.some(function(v){return v === k})}, // toJSON: Use properties to generate json toJSON: function(){ var json = {}, prop, ix = 0 for (;ix < this.properties.length; ix++){ prop = this.properties[ix] json[prop] = this[prop] } return json } }) // Add methods oj.addMethods(Out.prototype, args.methods) // Add the properties oj.addProperties(Out.prototype, args.properties) return Out } // _createQuietType: Takes an OJ Type and creates the _Type that doesn't emit _createQuietType = function(typeName){ return oj[_getQuietTagName(typeName)] = function(){ return _construct.apply(null, [oj[typeName]].concat(slice.call(arguments), [{ __quiet__: 1 }])) } } // oj.createEnum oj.createEnum = function(name, args){_e('createEnum', 'NYI')} // View var View = oj.createType('View', { // Views are special objects map properties together. This is a union of arguments // With the remaining arguments becoming a list constructor: function(){ _a(oj.isDOM(this.el), this.typeName, 'constructor did not set this.el') // Set instance on @el _setInstanceOnElement(this.el, this) var u = oj.unionArguments(arguments), options = u.options, args = u.args // Emit as a tag if it isn't quiet or used new keyword if (this.__autonew__ && !options.__quiet__) this.emit() // Remove quiet flag as it has served its purpose if (options.__quiet__ != null) delete options.__quiet__ // Add class oj-typeName this.$el.addClass("oj-" + this.typeName) // Set default themes if setting is set if(oj.settings.defaultThemes) this.themes = oj.settings.defaultThemes // Views automatically set all options to their properties // arguments directly to properties this.set(options) // Remove options that were set options = _clone(options) this.properties.forEach(function(v){return delete options[v]}) // Views pass through remaining options to be attributes on the root element // This can include jquery events and interpreted arguments this.addAttributes(options) // Record if view is fully constructed return this._isConstructed = true }, properties: { // The element backing the View el: { get: function(){return this._el}, set: function(v){ // Set the element directly if this is a dom element if (oj.isDOMElement(v)){ this._el = v // Clear cache of $el this._$el = null } else { // Generate the dom element this._el = oj.compile({dom:1, css:0, cssMap:0, html:0}, v).dom } } }, // Get and cache jquery-enabled element (readonly) $el: { get: function(){ return this._$el != null ? this._$el : (this._$el = oj.$(this.el)) } }, // Get and set id attribute of view id: { get: function(){return this.$el.attr('id')}, set: function(v){return this.$el.attr('id', v)} }, // class: // get: -> @$el.attr 'class' // set: (v) -> // # Join arrays with spaces // if oj.isArray v // v = v.join ' ' // @$el.attr 'class', v // return // Alias for classes // c: // get: -> @class // set: (v) -> @class = v; return // Get all currently set attributes (readonly) attributes: { get: function(){ var out = {} slice.call(this.el.attributes).forEach(function(attr){ return out[attr.name] = attr.value }) return out } }, // Get all classes as an array (readwrite) classes: { get: function(){return this.$el.attr('class').split(/\s+/)}, set: function(v){this.$el.attr('class', v.join(' '))} }, // Get / set all currently set themes (readwrite) themes: { get: function(){ var thms = [], prefix = 'theme-', ix = 0, cls for (; ix < this.classes.length; ix++){ cls = this.classes[ix] if (cls.indexOf(prefix) === 0) thms.push(cls.slice(prefix.length)) } return thms }, set: function(v){ if (!oj.isArray(v)) v = [v] this.clearThemes() var theme, ix = 0 for (;ix < v.length; ix++){ theme = v[ix] this.addTheme(theme) } } }, theme: { get: function(){return this.themes}, set: function(v){this.themes = v} }, // Determine if this view has been fully constructed (readonly) isConstructed: {get: function(){return _d(this._isConstructed, false)}}, // Determine if this view has been fully inserted (readonly) isInserted: {get: function(){return _d(this._isInserted, false)}} }, methods: { // $: Find element from within root $: function(){return this.$el.find.apply(this.$el, arguments)}, // addAttribute: Add a single attribute addAttribute: function(name, value){ var attr = {} attr[name] = value this.addAttributes(attr) }, // addAttributes: Add attributes and apply the oj magic with jquery binding addAttributes: function(attributes){ var attr = _clone(attributes), events = _attributesProcessedForOJ(attr), k, v // Add attributes as object if (oj.isPlainObject(attr)){ for (k in attr){ v = attr[k] if (k === 'class') this.addClass(v) else if (v === true) // Boolean attributes have no value this.el.setAttributeNode(doc.createAttribute(k)) else // Otherwise add it normally this.$el.attr(k, v) } } // Bind events if (events != null) _attributesBindEventsToDOM(events, this.el) }, // Remove a single attribute removeAttribute: function(name){this.$el.removeAttr(name)}, // Remove multiple attributes removeAttributes: function(list){var _t = this; list.forEach(function(v){_t.removeAttribute(v)})}, // Add a single class addClass: function(name){this.$el.addClass(name)}, // Remove a single class removeClass: function(name){this.$el.removeClass(name)}, // Determine if class is applied hasClass: function(name){return this.$el.hasClass(name)}, // Add a single theme addTheme: function(name){this.addClass("theme-" + name)}, // Remove a single theme removeTheme: function(name){this.removeClass("theme-" + name)}, // Determine if theme is applied hasTheme: function(name){return this.hasClass("theme-" + name)}, // Clear all themes clearThemes: function(){var _t = this; this.themes.forEach(function(t){_t.removeTheme(t)}) }, // emit: Emit instance as a tag function would do emit: function(){oj._argsAppend(this)}, // Convert View to html toHTML: function(options){ return this.el.outerHTML + ((options != null ? options.minify : void 0) ? '' : '\n') }, // Convert View to dom (for compiling) toDOM: function(){return this.el}, // Convert toCSS: function(options){ return _cssFromPluginObject(_flattenCSSMap(this.cssMap), _extend({}, { minify: options.minify, tags: 0 })) }, // Convert toCSSMap: function(){return this.type.cssMap}, // Convert View to string (for debugging) toString: function(){return this.toHTML()}, // detach: -> throw 'detach nyi' // The implementation is to set el manipulate it, and remember how to set it back // attach: -> throw 'attach nyi' // The implementation is to unset el from detach // inserted is called the instance is inserted in the dom (override) inserted: function(){return this._isInserted = true} } }) // View.cssMap: remember css for this View View.cssMap = {} // View.css: set view's css with css object mapping, or raw css string View.css = function(css){ _a(oj.isString(css) || oj.isPlainObject(css), this.typeName, 'object or string expected for first argument') var cssMap, _base, _base1, _name, _name1, _ref2, _ref3