├── .gitignore ├── .jsbeautifyrc ├── .npmrc ├── .vscode └── settings.json ├── alloy-parser.js ├── cli.js ├── commands ├── create.js ├── init.js └── pending │ ├── app.js │ └── init.js ├── controllers ├── baseController.js ├── flex.js ├── label.js ├── navigator.js ├── navigator2.js ├── page.js ├── tabbedWindow.js ├── tile.js └── window.js ├── core.js ├── core_tasks.js ├── docs ├── _config.yml ├── cli.md ├── create.md ├── index.md ├── mobile.md └── old.md ├── fix-config.js ├── lib ├── apm.js ├── baseController.js ├── bluebird.js ├── device.js ├── eventemitter2.js ├── events.js ├── fonts │ ├── mobilewin8.css │ ├── mobilewin8.js │ └── mobilewin8.ttf ├── fs.js ├── iconfonts.js ├── index.js ├── lodash.js ├── navigation.js ├── navigator.js ├── path.js ├── process.js ├── resolver.js ├── util.js ├── utils.js └── ux.js ├── license.md ├── npm-scripts ├── _postinstall.js ├── version.js └── version_old.js ├── package-lock.json ├── package.json ├── plugins ├── babeljs.js ├── backbone-fix.js ├── es2015.js ├── nodejs.js ├── npm.js ├── promises.js ├── remove-invalid-header.js ├── resolver │ └── resolve-fix.js ├── underscore-fix.js ├── widgets-add.js └── widgets-remove.js ├── readme.md ├── references ├── alloy.d.ts ├── globals.d.ts ├── node.d.ts └── titanium.d.ts ├── styles └── styles.tss ├── templates └── alloy.jmk ├── utils.js ├── views ├── flex.xml ├── label.xml ├── navigator.xml ├── page.xml ├── styles.xml ├── tabbedWindow.xml ├── tile.xml ├── widget.xml └── window.xml ├── widget.json ├── widget.yaml └── widgets.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Build folder and log file 2 | build/ 3 | build.log 4 | .DS_Store 5 | build.log 6 | build/* 7 | Resources/* 8 | npm-debug.log 9 | tmp 10 | .map 11 | *.svn 12 | *.suo 13 | *.user 14 | *.tmp 15 | bin 16 | Bin 17 | obj 18 | dirtyflag.txt 19 | .settings/ 20 | .fastdev.lock 21 | build 22 | node_modules 23 | Resources 24 | .ntvs_analysis.dat 25 | npm-debug.log 26 | .*.swp 27 | .lock-* 28 | build/ 29 | dist/* 30 | .vs -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "js": { 3 | "allowed_file_extensions": ["js", "json", "jshintrc", "jsbeautifyrc", "tss"], 4 | // Set brace_style 5 | // collapse: (old default) Put braces on the same line as control statements 6 | // collapse-preserve-inline: (new default) Same as collapse but better support for ES6 destructuring and other features. https://github.com/victorporof/Sublime-HTMLPrettify/issues/231 7 | // expand: Put braces on own line (Allman / ANSI style) 8 | // end-expand: Put end braces on own line 9 | // none: Keep them where they are 10 | "brace_style": "collapse-preserve-inline", 11 | "eol": "\n", 12 | "eval_code": false, 13 | "wrap_attributes": "auto", 14 | "wrap_attributes_indent_size": 4, 15 | "break_chained_methods": false, // Break chained method calls across subsequent lines 16 | "e4x": false, // Pass E4X xml literals through untouched 17 | "end_with_newline": false, // End output with newline 18 | "indent_char": " ", // Indentation character 19 | "indent_level": 0, // Initial indentation level 20 | "indent_size": 4, // Indentation size 21 | "indent_with_tabs": true, // Indent with tabs, overrides `indent_size` and `indent_char` 22 | "jslint_happy": false, // If true, then jslint-stricter mode is enforced 23 | "keep_array_indentation": true, // Preserve array indentation 24 | "keep_function_indentation": false, // Preserve function indentation 25 | "max_preserve_newlines": 10, // Maximum number of line breaks to be preserved in one chunk (0 disables) 26 | "preserve_newlines": true, // Whether existing line breaks should be preserved 27 | "space_after_anon_function": false, // Should the space before an anonymous function's parens be added, "function()" vs "function ()" 28 | "space_before_conditional": false, // Should the space before conditional statement be added, "if(true)" vs "if (true)" 29 | "space_in_empty_paren": false, // Add padding spaces within empty paren, "f()" vs "f( )" 30 | "space_in_paren": true, // Add padding spaces within paren, ie. f( a, b ) 31 | "unescape_strings": false, // Should printable characters in strings encoded in \xNN notation be unescaped, "example" vs "\x65\x78\x61\x6d\x70\x6c\x65" 32 | "wrap_line_length": 0 // Lines should wrap at next opportunity after this number of characters (0 disables) 33 | }, 34 | "html": { 35 | "allowed_file_extensions": ["htm", "html", "xhtml", "shtml", "xml", "svg", "xml"], 36 | "brace_style": "collapse", // [collapse|expand|end-expand|none] Put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line, or attempt to keep them where they are 37 | "end_with_newline": false, // End output with newline 38 | "indent_char": "\t", // Indentation character 39 | "indent_handlebars": false, // e.g. {{#foo}}, {{/foo}} 40 | "indent_inner_html": false, // Indent and sections 41 | "indent_scripts": "keep", // [keep|separate|normal] 42 | "indent_size": 1, // Indentation size 43 | "max_preserve_newlines": 0, // Maximum number of line breaks to be preserved in one chunk (0 disables) 44 | "preserve_newlines": true, // Whether existing line breaks before elements should be preserved (only works before elements, not inside tags or for text) 45 | "unformatted": ["a", "span", "img", "code", "pre", "sub", "sup", "em", "strong", "b", "i", "u", "strike", "big", "small", "pre", "h1", "h2", "h3", "h4", "h5", "h6"], // List of tags that should not be reformatted 46 | "wrap_line_length": 0 // Lines should wrap at next opportunity after this number of characters (0 disables) 47 | }, 48 | "css": { 49 | "allowed_file_extensions": ["css", "scss", "sass", "less"], 50 | "end_with_newline": false, // End output with newline 51 | "indent_char": "\t", // Indentation character 52 | "indent_size": 1, // Indentation size 53 | "newline_between_rules": true, // Add a new line after every css rule 54 | "selector_separator": " ", 55 | "selector_separator_newline": true // Separate selectors with newline or not (e.g. "a,\nbr" or "a, br") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | //-------- Editor configuration -------- 4 | 5 | // Configure file associations to languages (e.g. "*.extension": "html"). These have precedence over the default associations of the languages installed. 6 | "files.associations": { 7 | "*.tss": "javascript", 8 | "*.jmk": "javascript" 9 | }, 10 | 11 | // The number of spaces a tab is equal to. 12 | "editor.tabSize": 4, 13 | 14 | // Insert spaces when pressing Tab. 15 | "editor.insertSpaces": false, 16 | 17 | // When opening a file, `editor.tabSize` and `editor.insertSpaces` will be detected based on the file contents. 18 | "editor.detectIndentation": false, 19 | 20 | 21 | 22 | //-------- Files configuration -------- 23 | 24 | // Configure glob patterns for excluding files and folders. 25 | "files.exclude": { 26 | "**/.git": true, 27 | "**/.svn": true, 28 | "**/.DS_Store": true, 29 | "**/Resources": false, 30 | "**/obj": true 31 | }, 32 | 33 | //-------- Search configuration -------- 34 | 35 | // Configure glob patterns for excluding files and folders in searches. Inherits all glob patterns from the file.exclude setting. 36 | "search.exclude": { 37 | "**/node_modules": true, 38 | "**/bower_components": true, 39 | "**/build": true, 40 | "alloy.d.ts": true, 41 | "titanium.d.ts": true, 42 | "global.d.ts": true, 43 | "**/plugin.py": true, 44 | "**/Resources": true 45 | }, 46 | 47 | // HTML 48 | 49 | // Maximum amount of characters per line (0 = disable). 50 | "html.format.wrapLineLength": 150, 51 | 52 | // Format and indent {{#foo}} and {{/foo}}. 53 | "html.format.indentHandlebars": true, 54 | 55 | "html.format.extraLiners": true, 56 | 57 | // TypeScript 58 | 59 | // Defines space handling after opening and before closing non empty parenthesis 60 | "javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": true, 61 | 62 | // Defines space handling after opening and before closing non empty brackets 63 | "javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": true, 64 | 65 | 66 | // File extensions that can be beautified as HTML. 67 | "beautify.HTMLfiles": [ 68 | "htm", 69 | "html", 70 | "xml" 71 | ], 72 | 73 | // File extensions that can be beautified as javascript or JSON. 74 | "beautify.JSfiles": [ 75 | "js", 76 | "json", 77 | "jsbeautifyrc", 78 | "jshintrc", 79 | "tss" 80 | ], 81 | 82 | // Set path/file matchers to ignore when attempting to beautify on save. Uses glob path matching. 83 | "beautify.onSaveIgnore": [ 84 | "**/*+(.|_|-)min.*" 85 | ], 86 | 87 | // Use `.editorconfig` settings 88 | "beautify.editorconfig": false, 89 | 90 | // Link file types to the beautifier type 91 | "beautify.language": { 92 | "js": { 93 | "type": [ 94 | "javascript", 95 | "json" 96 | ], 97 | "filename": [ 98 | ".jshintrc", 99 | ".jsbeautify" 100 | ], 101 | "ext": [ "js", 102 | "json", 103 | "jsbeautifyrc", 104 | "jshintrc", 105 | "tss" 106 | ] 107 | }, 108 | "css": [ 109 | "css", 110 | "scss" 111 | ], 112 | "html": [ 113 | "htm", 114 | "html", 115 | "xml", 116 | "entitlements" 117 | ] 118 | }, 119 | 120 | } -------------------------------------------------------------------------------- /alloy-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*** 3 | * __ _ __ __ 4 | * ____ ___ ____ / /_ (_)/ /___ / /_ ___ _____ ____ 5 | * / __ `__ \ / __ \ / __ \ / // // _ \ / __ \ / _ \ / ___// __ \ 6 | * / / / / / // /_/ // /_/ // // // __// / / // __// / / /_/ / 7 | * /_/ /_/ /_/ \____//_.___//_//_/ \___//_/ /_/ \___//_/ \____/ 8 | * 9 | * mobile solutions for everyday heroes 10 | * 11 | * @file {nativeloop} plugin for parsing alloy xml views 12 | * @module nativeloop/plugins/alloy-parser 13 | * @author Brenton House 14 | * @copyright Copyright (c) 2017 by Superhero Studios Incorporated. All Rights Reserved. 15 | * @license Licensed under the terms of the MIT License (MIT) 16 | * 17 | */ 18 | 19 | var _ = require( 'lodash' ); 20 | 21 | var alloyParser = {}; 22 | module.exports = alloyParser; 23 | 24 | alloyParser.init = function( params ) { 25 | alloyParser.parserParams = params; 26 | } 27 | 28 | alloyParser.parse = function( params ) { 29 | console.error( '********* WRAPPING base.parse **********' ); 30 | return _.wrap( params.baseParser.parse, function( func, node, state, parser ) { 31 | var logger = console; 32 | var selectedParser = parser; 33 | var CONST = alloyParser.parserParams.CONST; 34 | var CU = alloyParser.parserParams.CU; 35 | var U = alloyParser.parserParams.U; 36 | 37 | console.error( '********* BASE:PARSE **********' ); 38 | 39 | if( CU[ CONST.DOCROOT_MODULE_PROPERTY ] && !node.hasAttribute( 'module' ) ) { 40 | node.setAttribute( 'module', CU[ CONST.DOCROOT_MODULE_PROPERTY ] ); 41 | } 42 | 43 | var fullname = CU.getNodeFullname( node ); 44 | var src = node.getAttribute( 'src' ); 45 | var name = node.getAttribute( 'name' ); 46 | var moduleName = node.getAttribute( 'module' ); 47 | node.nodeName = _.upperFirst( _.camelCase( node.nodeName ) ); 48 | // logger.error("name in parser: " + name); 49 | // logger.error("module in parser: " + moduleName); 50 | // logger.error("fullname in parser: " + fullname); 51 | // logger.error("nodeName in parser: " + _.lowerFirst(node.nodeName)); 52 | 53 | logger.error( 'moduleName: ' + JSON.stringify( moduleName, null, 2 ) ); 54 | if( moduleName === '/nativeloop' ) { 55 | moduleName = 'nativeloop'; 56 | node.setAttribute( 'module', 'nativeloop' ); 57 | } 58 | if( moduleName === 'nativeloop' ) { 59 | 60 | var nodeText = U.XML.getNodeText( node ); 61 | console.error( "nodeText: " + nodeText ); 62 | if( nodeText ) { 63 | nodeText = U.trim( nodeText.replace( /'/g, "\\'" ) ); 64 | node.setAttribute( 'text', nodeText ); 65 | } 66 | // _.forEach( node.attributes, function( attribute ) { 67 | // // logger.debug("attribute: " + attribute.name + ": " + attribute.value); 68 | // } ); 69 | 70 | //TODO: Add this to constants module 71 | node.setAttribute( '__nativeloop', true ); 72 | node.setAttribute( '__navigatorId', "_.get($,'nav.id')"); 73 | 74 | console.warn( 'you are here → Looking for widget node: ' + _.lowerFirst( node.nodeName ) ); 75 | if( _.includes( alloyParser.parserParams.nativeloop_widgets, _.lowerFirst( node.nodeName ) ) ) { 76 | 77 | console.error( 'you are here → Found widget node: ' + _.lowerFirst( node.nodeName ) ); 78 | 79 | !src && node.setAttribute( 'src', moduleName ); 80 | !name && node.setAttribute( 'name', _.lowerFirst( node.nodeName ) ); 81 | 82 | // make autoStyle default to true for nativeloop tags. 83 | if( _.isNil( node.getAttribute( 'autoStyle' ) ) || _.isEmpty( node.getAttribute( 'autoStyle' ) ) ) { 84 | node.setAttribute( 'autoStyle', true ); 85 | } 86 | 87 | // var nodeText = U.XML.getNodeText( node ); 88 | // console.error("nodeText: " + nodeText); 89 | // if( nodeText ) { 90 | // nodeText = U.trim( nodeText.replace( /'/g, "\\'" ) ); 91 | // node.setAttribute( 'text', nodeText ); 92 | // } 93 | // // _.forEach( node.attributes, function( attribute ) { 94 | // // // logger.debug("attribute: " + attribute.name + ": " + attribute.value); 95 | // // } ); 96 | 97 | // //TODO: Add this to constants module 98 | // node.setAttribute( '__nativeloop', true ); 99 | 100 | selectedParser = params.widgetParser.parse; 101 | 102 | } 103 | 104 | } else { 105 | // logger.debug("returning default parser"); 106 | // _.forEach( node.attributes, function( attribute ) { 107 | // logger.debug("attribute: " + attribute.name + " = " + attribute.value); 108 | // } ); 109 | } 110 | 111 | var args = CU.getParserArgs( node, state ), 112 | code = ''; 113 | 114 | // console.error( 'args.createArgs: ' + JSON.stringify( args.createArgs, null, 2 ) ); 115 | // Convert attributes that are in dot-notation to objects. 116 | _.forEach( _.keys( args.createArgs ), key => { 117 | console.error( 'key: ' + JSON.stringify( key, null, 2 ) ); 118 | if( _.includes( key, '.' ) ) { 119 | let value = args.createArgs[ key ]; 120 | delete args.createArgs[ key ]; 121 | let split = key.split( '.' ); 122 | if( _.last( split ) === 'class' ) { 123 | split[ split.length - 1 ] = 'classes'; 124 | key = split.join( '.' ); 125 | value = value.split( ' ' ); 126 | } 127 | _.set( args.createArgs, key, value ); 128 | delete args.createArgs[ key ]; 129 | } 130 | } ); 131 | 132 | 133 | // console.error( 'args.createArgs: ' + JSON.stringify( args.createArgs, null, 2 ) ); 134 | 135 | if( state.pre ) { 136 | code += state.pre( node, state, args ) || ''; 137 | delete state.pre; 138 | } 139 | var newState = selectedParser( node, state, args ); 140 | // logger.error( 'newState.code: ' + JSON.stringify( newState.code, null, 2 ) ); 141 | // newState.code = newState.code.replace( /((Alloy.createWidget[^{]+{)(apiName))/g, "$2 navigator: _.get($,'args.navigator'), $3" ) 142 | // logger.error( 'newState.code: ' + JSON.stringify( newState.code, null, 2 ) ); 143 | code += newState.code; 144 | if( state.post ) { 145 | logger.error( 'state.post: ' + JSON.stringify( state.post, null, 2 ) ); 146 | code += state.post( node, newState, args ) || ''; 147 | delete state.post; 148 | } 149 | newState.code = code; 150 | 151 | // if( node.getAttribute( '__nativeloop' ) && newState.code && newState.parent && newState.parent.symbol ) { 152 | if( node.getAttribute( '__nativeloop' ) && newState.code ) { 153 | // newState.parent.symbol = newState.parent.symbol.replace( /\.getViewEx[^\)]*\)/, '' ); 154 | newState.code = newState.code.replace( /\.getViewEx[^\)]*\)/g, '' ); 155 | newState.code = newState.code.replace( /(__navigatorId:\s*)"([^"]*)"/g, '$1$2' ); 156 | } 157 | 158 | return newState; 159 | 160 | // var newState = func( node, state, selectedParser ); 161 | // if( node.getAttribute( '__nativeloop' ) && newState.parent && newState.parent.symbol ) { 162 | // // newState.parent.symbol = newState.parent.symbol.replace( /\.getViewEx[^\)]*\)/, '' ); 163 | // newState.code = response.newState.replace( /\.getViewEx[^\)]*\)/g, '' ); 164 | // } 165 | // return newState; 166 | } ); 167 | } -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | const yargs = require( "yargs" ); 4 | var Promise = require( "bluebird" ); 5 | Promise.promisifyAll( require( "fs-extra" ) ); 6 | const _ = require( 'lodash' ); 7 | 8 | require( 'yargs' ) 9 | .version() 10 | .alias( 'v', 'version' ) 11 | .demand( 1, "must provide a valid command" ) 12 | .commandDir( 'commands', { 13 | recurse: false 14 | } ) 15 | .recommendCommands() 16 | .help() 17 | .argv -------------------------------------------------------------------------------- /commands/init.js: -------------------------------------------------------------------------------- 1 | var path = require( 'path' ); 2 | var fs = require( 'fs-extra' ); 3 | var _ = require( 'lodash' ); 4 | // var conf = require('rc')('nativeloop', {}); 5 | var pathExists = require( 'path-exists' ); 6 | 7 | exports.desc = 'Initializes a project for native development with {nativeloop}.' 8 | 9 | var builder = { 10 | 11 | "path": { 12 | alias: "p", 13 | default: process.cwd(), 14 | describe: "Specifies the directory where you want to initialize the project, if different from the current directory. The directory must already exist.", 15 | demand: false, 16 | type: "string" 17 | }, 18 | "force": { 19 | alias: "f", 20 | describe: "If set, applies the default project configuration and does not show the interactive prompt. ", 21 | demand: false, 22 | type: "string" 23 | } 24 | 25 | } 26 | var copy_template = function( argv ) { 27 | 28 | console.info( 'copy template ----------------------------------------------' ); 29 | console.info( 'path: ' + argv[ 'path' ] ); 30 | // process.chdir(argv['path']); 31 | console.info( '__dirname: ' + __dirname ); 32 | console.info( 'process.cwd(): ' + process.cwd() ); 33 | 34 | let filename = 'alloy.jmk'; 35 | let source = path.join( __dirname, "..", 'templates', filename ); 36 | let root = argv[ "path" ]; 37 | console.error( 'source: ' + source ); 38 | console.info( 'root: ' + root ); 39 | 40 | console.warn( 'pathExists.sync(source): ' + pathExists.sync( source ) ); 41 | console.warn( 'pathExists.sync(root): ' + pathExists.sync( root ) ); 42 | 43 | if( !pathExists.sync( source ) ) { 44 | console.error( 'source does not exist: ' + source ); 45 | return; 46 | } 47 | 48 | let tiapp = path.join( root, 'tiapp.xml' ); 49 | 50 | if( !pathExists.sync( tiapp ) ) { 51 | console.error( 'Cannot find tiapp.xml: ' + tiapp ); 52 | return; 53 | } 54 | 55 | let target = path.join( root, 'app', filename ); 56 | 57 | Promise.resolve( () => console.error( "pathExists.sync(target): " + pathExists.sync( root ) ) ) 58 | .then( () => { 59 | // check to see if the file is a nativeloop file. 60 | // if it is not, make a backup of the file. 61 | } ) 62 | .then( () => console.warn( "copying files to target directory: " + root ) ) 63 | .then( () => fs.copyAsync( source, target, { 64 | clobber: true 65 | } ) ) 66 | .then( () => console.warn( "all done." ) ) 67 | .catch( err => console.error( "Error occurred: " + err ) ); 68 | 69 | 70 | 71 | } 72 | 73 | var handler = function( argv ) { 74 | copy_template( argv ); 75 | } 76 | 77 | exports.handler = handler; 78 | exports.builder = builder; -------------------------------------------------------------------------------- /commands/pending/app.js: -------------------------------------------------------------------------------- 1 | // exports.command = 'app ' 2 | exports.desc = 'Manage nativeloop mobile apps' 3 | exports.builder = function(yargs) { 4 | return yargs.commandDir('./app') 5 | .demand(1, "must provide a valid command"); 6 | } 7 | exports.handler = function(argv) {} -------------------------------------------------------------------------------- /commands/pending/init.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var fs = require('fs-extra'); 3 | var _ = require('lodash'); 4 | // var conf = require('rc')('nativeloop', {}); 5 | var pathExists = require('path-exists'); 6 | var npm = require('@geek/npm'); 7 | 8 | 9 | exports.desc = 'Initializes a project for native development with {nativeloop}.' 10 | 11 | 12 | var builder = { 13 | 14 | "path": { 15 | alias: "p", 16 | default: process.cwd(), 17 | describe: "Specifies the directory where you want to initialize the project, if different from the current directory. The directory must already exist.", 18 | demand: false, 19 | type: "string" 20 | }, 21 | "force": { 22 | alias: "f", 23 | describe: "If set, applies the default project configuration and does not show the interactive prompt. ", 24 | demand: false, 25 | type: "string" 26 | } 27 | 28 | } 29 | var copy_template = function(argv) { 30 | 31 | console.info("copy template ----------------------------------------------"); 32 | console.info("path: " + argv["path"]); 33 | // process.chdir(argv["path"]); 34 | console.info("__dirname: " + __dirname); 35 | console.info("process.cwd(): " + process.cwd()); 36 | 37 | var source = path.join(__dirname, "..", "templates", "required"); 38 | var target = argv["path"]; 39 | console.error("source: " + source); 40 | console.info("target: " + target); 41 | 42 | console.warn("pathExists.sync(source): " + pathExists.sync(source)); 43 | console.warn("pathExists.sync(target): " + pathExists.sync(target)); 44 | 45 | 46 | if (!pathExists.sync(source)) { 47 | console.error("source does not exist: " + source); 48 | return; 49 | } 50 | 51 | if (!pathExists.sync(target)) { 52 | console.error("target does not exist: " + target); 53 | return; 54 | } 55 | 56 | 57 | Promise.resolve(() => console.error("pathExists.sync(target): " + pathExists.sync(target))) 58 | .then(() => console.warn("copying files to target directory: " + target)) 59 | .then(() => fs.copyAsync(source, target, { 60 | clobber: true 61 | })) 62 | .then(() => console.warn("installing nativeloop mobile")) 63 | .then(() => npm.install(['brentonhouse/nativeloop'], { 64 | cwd: target 65 | })) 66 | .then(() => console.warn("executing npm dedupe")) 67 | .then(() => npm.dedupe) 68 | .then(() => console.warn("all done.")) 69 | .catch(err => console.error("Error occurred: " + err)); 70 | 71 | 72 | 73 | } 74 | 75 | var handler = function(argv) { 76 | 77 | copy_template(argv); 78 | 79 | } 80 | 81 | exports.handler = handler; 82 | exports.builder = builder; -------------------------------------------------------------------------------- /controllers/baseController.js: -------------------------------------------------------------------------------- 1 | 2 | $.nav = $.args.params.navigator; -------------------------------------------------------------------------------- /controllers/flex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*** 3 | * __ _ __ __ 4 | * ____ ___ ____ / /_ (_)/ /___ / /_ ___ _____ ____ 5 | * / __ `__ \ / __ \ / __ \ / // // _ \ / __ \ / _ \ / ___// __ \ 6 | * / / / / / // /_/ // /_/ // // // __// / / // __// / / /_/ / 7 | * /_/ /_/ /_/ \____//_.___//_//_/ \___//_/ /_/ \___//_/ \____/ 8 | * 9 | * mobile solutions for everyday heroes 10 | * 11 | * @file This is the Alloy controller for managing groups of tiles. 12 | * @module nativeloop/controllers/flex 13 | * @author Brenton House 14 | * @version 1.0.0 15 | * @since 1.0.0 16 | * @copyright Copyright (c) 2017 by Superhero Studios Incorporated. All Rights Reserved. 17 | * @license Licensed under the terms of the MIT License (MIT) 18 | * 19 | */ 20 | 21 | 22 | const utils = require( 'nativeloop/utils' ); 23 | const device = require( 'nativeloop/device' ); 24 | 25 | // console.warn( '[tiles] $.args: ' + JSON.stringify( $.args, null, 2 ) ); 26 | 27 | // We can't handle 'fill' yet as we need actual widths. 28 | //TODO: add support for Ti.UI.FILL (with postlayout?) 29 | if( $.args.width === Ti.UI.FILL ) { 30 | $.args.width = device.width; 31 | } 32 | 33 | _.defaults( $.args, { 34 | width: device.width, 35 | height: Ti.UI.FILL, 36 | } ); 37 | 38 | // Find the actual width if a percentage was passed in. 39 | $.args.width = utils.parsePercentage( $.args.width ); 40 | 41 | // convert margin/padding into objects 42 | let margin = utils.parsePadding( $.args.margin, $.args.width ); 43 | let padding = utils.parsePadding( $.args.padding, $.args.width ); 44 | 45 | // console.error( '[flex] $.args: ' + JSON.stringify( $.args, null, 2 ) ); 46 | 47 | _.forEach( $.args.children, child => { 48 | 49 | let childArgs = $.args.child || {}; 50 | _.defaults( childArgs, { 51 | width: ( ( $.args.width / 12 ) * ( childArgs.flexWidth || childArgs.flexSize || childArgs.size || 0 ) ), 52 | height: ( ( $.args.width / 12 ) * ( childArgs.flexHeight || childArgs.flexSize || childArgs.size || 0 ) ), 53 | } ); 54 | 55 | 56 | child.applyDefaults( childArgs ); 57 | 58 | $._wrapper.add( child.getView() ); 59 | 60 | } ); 61 | 62 | let wrapperArgs = _.assignIn( _.pick( $.args, [ 'height', 'width', 'top', 'bottom', 'left', 'right', 'backgroundColor', 'layout' ] ), margin ) 63 | 64 | $._wrapper.applyProperties( wrapperArgs ); -------------------------------------------------------------------------------- /controllers/label.js: -------------------------------------------------------------------------------- 1 | var ux = require( "nativeloop/ux" ); 2 | 3 | ux.fixFontAttributes( $.args ); 4 | ux.fixLines( $.args ); 5 | 6 | var view = Ti.UI.createLabel( params ); 7 | $.addTopLevelView( view ); -------------------------------------------------------------------------------- /controllers/navigator.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * __ _ __ __ 3 | * ____ ___ ____ / /_ (_)/ /___ / /_ ___ _____ ____ 4 | * / __ `__ \ / __ \ / __ \ / // // _ \ / __ \ / _ \ / ___// __ \ 5 | * / / / / / // /_/ // /_/ // // // __// / / // __// / / /_/ / 6 | * /_/ /_/ /_/ \____//_.___//_//_/ \___//_/ /_/ \___//_/ \____/ 7 | * 8 | * mobile solutions for everyday heroes 9 | * 10 | * @overview 11 | * Widget that handles the actual implementation of the window navigation 12 | * -- Note -- This is a work in progress and is not fully functional! 13 | * 14 | * @module widgets/nativeloop/controllers/navigator 15 | * @author Brenton House 16 | * @version 0.1.0 17 | * @since 0.1.0 18 | * @copyright Copyright (c) 2017 by Superhero Studios Incorporated. All Rights Reserved. 19 | * @license Licensed under the terms of the MIT License (MIT) 20 | * 21 | */ 22 | 23 | 24 | // var apm = Alloy.Globals.apm; 25 | var apm = require( 'nativeloop/apm' ); 26 | var utils = require( 'nativeloop/utils' ); 27 | 28 | $.getViewAlloy = $.getView; 29 | $.getView = function( id ) { 30 | $.init(); 31 | return $.getViewAlloy( id ); 32 | } 33 | 34 | var createWindow = function( payload ) { 35 | return payload && payload.moduleName ? Alloy.createController( payload.moduleName, payload ).getViewEx( { recurse: true } ) : null; 36 | } 37 | 38 | /** 39 | * @function createWindow2 40 | * @summary create a window 41 | * @param {object} params - parameters used to initialize the creating of the window 42 | * @see {@link http://docs.appcelerator.com/platform/latest/#!/api/Modules.Performance | Appcelerator Titanium Window Documentation} 43 | * @since 1.0.0 44 | * @returns {object} - Returns the Titanium Window 45 | */ 46 | var createWindow2 = function( params ) { 47 | let __prefix = $.__controllerPath + ".createWindow2: "; 48 | apm.leaveBreadcrumb( __prefix + "entering" ); 49 | console.error( 'params: ' + utils.stringify( params, null, 2 ) ); 50 | 51 | let payload = _.defaults(_.cloneDeep(params.screen), params.params); 52 | payload.__navigatorId = params.__navigatorId; 53 | console.error( 'payload: ' + utils.stringify( payload, null, 2 ) ); 54 | return Alloy.createController( params.screen.name, payload ).getViewEx( { recurse: true } ); 55 | 56 | apm.leaveBreadcrumb( __prefix + "exiting" ); 57 | __prefix = null; 58 | } 59 | 60 | 61 | $.init = _.once( function() { 62 | var __prefix = $.__controllerPath + ".init: "; 63 | apm.leaveBreadcrumb( __prefix + "entering" ); 64 | var window_stack = []; 65 | var __local_prefix 66 | if( OS_IOS ) { 67 | 68 | console.error( '$.args: ' + utils.stringify( $.args, null, 2 ) ); 69 | // $.root = $.args.root ? Alloy.createController( $.args.root.moduleName, $.args.root ).getViewEx( { recurse: true } ) : Ti.UI.createWindow( { id: "window" } ); 70 | $.root = createWindow2( $.args ); 71 | $.nav = Ti.UI.iOS.createNavigationWindow( { 72 | window: $.root, 73 | id: "nav", 74 | navBarHidden: true, 75 | // backgroundColor: 'transparent', 76 | } ); 77 | 78 | $.addClass( $.root, 'navbar' ); 79 | 80 | // console.error('$.nav.barColor: ' + JSON.stringify($.nav.barColor, null, 2)); 81 | $.nav && $.addTopLevelView( $.nav ); 82 | $.push = function( params ) { 83 | __local_prefix = $.__controllerPath + ".push: "; 84 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 85 | var window; 86 | try { 87 | window = createWindow2( params ); 88 | } catch( err ) { 89 | console.error( err ); 90 | } 91 | window && $.nav.openWindow( window, { animated: params.animated } ); 92 | window_stack.push( window ); 93 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 94 | }; 95 | $.go = function( payload ) { 96 | __local_prefix = $.__controllerPath + ".go: "; 97 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 98 | var window; 99 | try { 100 | window = createWindow( payload ); 101 | } catch( err ) { 102 | console.error( err ); 103 | } 104 | window && $.nav.openWindow( window, { animated: payload.animated } ); 105 | window_stack.push( window ); 106 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 107 | }; 108 | $.goBack = function( payload ) { 109 | __local_prefix = $.__controllerPath + ".goBack: "; 110 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 111 | if( !payload ) { 112 | return; 113 | } 114 | var window = window_stack.pop(); 115 | window && $.nav.closeWindow( window, payload.options ); 116 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 117 | }; 118 | //TODO: This should probably be handled by a close function which is called by the navigator... 119 | $.goHome = function( payload ) { 120 | __local_prefix = $.__controllerPath + ".goHome: "; 121 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 122 | for( var i = 0; i < window_stack.length; i++ ) { 123 | window_stack[ i ].close( Ti.UI.iPhone.AnimationStyle.NONE ); 124 | } 125 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 126 | }; 127 | 128 | } 129 | 130 | 131 | if( OS_ANDROID ) { 132 | $.root = $.args.root ? Alloy.createController( $.args.root.moduleName, $.args.root ).getViewEx( { recurse: true } ) : Ti.UI.createWindow(); 133 | $.root && $.addTopLevelView( $.root ); 134 | $.go = function( payload ) { 135 | __local_prefix = $.__controllerPath + ".go: "; 136 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 137 | var window; 138 | try { 139 | window = createWindow( payload ); 140 | } catch( err ) { 141 | console.error( err ); 142 | } 143 | if( window ) { 144 | window.addEventListener( "open", function( evt ) { 145 | var actionBar = window.activity.actionBar; 146 | if( actionBar ) { 147 | actionBar.homeButtonEnabled = true; 148 | actionBar.displayHomeAsUp = true; 149 | actionBar.onHomeIconItemSelected = function() { 150 | evt.source.close(); 151 | window_stack.pop(); 152 | }; 153 | } else { 154 | console.warn( "No ActionBar found." ); 155 | } 156 | } ); 157 | 158 | window.open(); 159 | window_stack.push( window ); 160 | } 161 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 162 | }; 163 | $.goHome = function( payload ) { 164 | __local_prefix = $.__controllerPath + ".goHome: "; 165 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 166 | for( var i = 0; i < window_stack.length; i++ ) { 167 | console.error( "closing window: " + window_stack[ i ].id ); 168 | window_stack[ i ].close(); 169 | } 170 | }; 171 | } 172 | 173 | 174 | if( OS_MOBILEWEB ) { 175 | $.window = Ti.UI.createWindow( { id: "window" } ); 176 | $.addTopLevelView( $.window ); 177 | $.root = $.args.root ? Alloy.createController( $.args.root.moduleName, $.args.root ).getViewEx( { recurse: true } ) : Ti.UI.createWindow(); 178 | $.nav = Ti.UI.MobileWeb.createNavigationGroup( { 179 | window: $.root, 180 | id: "nav" 181 | } ); 182 | $.window.add( $.nav ); 183 | $.navigate = function( payload ) { 184 | __local_prefix = $.__controllerPath + ".navigate: "; 185 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 186 | var window; 187 | try { 188 | window = createWindow( payload ); 189 | } catch( err ) { 190 | console.error( err ); 191 | } 192 | window && $.nav.open( window ); 193 | window_stack.push( window ); 194 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 195 | }; 196 | //TODO: This should probably be handled by a close function which is called by the navigator... 197 | $.goHome = function( payload ) { 198 | __local_prefix = $.__controllerPath + ".goHome: "; 199 | for( var i = 0; i < window_stack.length; i++ ) { 200 | window_stack[ i ].close(); 201 | } 202 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 203 | }; 204 | } 205 | 206 | if( OS_WINDOWS ) { 207 | $.root = $.args.root ? Alloy.createController( $.args.root.moduleName, $.args.root ).getViewEx( { recurse: true } ) : Ti.UI.createWindow(); 208 | $.root && $.addTopLevelView( $.root ); 209 | $.navigate = function( payload ) { 210 | __local_prefix = $.__controllerPath + ".navigate: "; 211 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 212 | var window; 213 | try { 214 | window = createWindow( payload ); 215 | } catch( err ) { 216 | console.error( err ); 217 | } 218 | window && window.open(); 219 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 220 | } 221 | } 222 | apm.leaveBreadcrumb( __prefix + "exiting" ); 223 | } ); -------------------------------------------------------------------------------- /controllers/navigator2.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * __ _ __ __ 3 | * ____ ___ ____ / /_ (_)/ /___ / /_ ___ _____ ____ 4 | * / __ `__ \ / __ \ / __ \ / // // _ \ / __ \ / _ \ / ___// __ \ 5 | * / / / / / // /_/ // /_/ // // // __// / / // __// / / /_/ / 6 | * /_/ /_/ /_/ \____//_.___//_//_/ \___//_/ /_/ \___//_/ \____/ 7 | * 8 | * mobile solutions for everyday heroes 9 | * 10 | * @overview 11 | * Widget that handles the actual implementation of the window navigation 12 | * -- Note -- This is a work in progress and is not fully functional! 13 | * 14 | * @module widgets/nativeloop/controllers/navigator 15 | * @author Brenton House 16 | * @version 0.1.0 17 | * @since 0.1.0 18 | * @copyright Copyright (c) 2017 by Superhero Studios Incorporated. All Rights Reserved. 19 | * @license Licensed under the terms of the MIT License (MIT) 20 | * 21 | */ 22 | 23 | 24 | // var apm = Alloy.Globals.apm; 25 | var apm = require( 'nativeloop/apm' ); 26 | 27 | $.getViewAlloy = $.getView; 28 | $.getView = function( id ) { 29 | init(); 30 | return $.getViewAlloy( id ); 31 | } 32 | 33 | var createWindow = function( payload ) { 34 | return payload && payload.moduleName ? Alloy.createController( payload.moduleName, payload ).getViewEx( { recurse: true } ) : null; 35 | } 36 | 37 | var createWindow2 = function( params ) { 38 | let __prefix = $.__controllerPath + ".createWindow2: "; 39 | apm.leaveBreadcrumb( __prefix + "entering" ); 40 | console.error('params: ' + JSON.stringify(params, null, 2)); 41 | if( params && params.screen ) { 42 | console.error('you are here → '); 43 | let payload = params.passProps; 44 | payload.navigator = params.navigator; 45 | console.error( 'payload: ' + JSON.stringify( payload, null, 2 ) ); 46 | return Alloy.createController( params.screen, payload ).getViewEx( { recurse: true } ); 47 | } 48 | apm.leaveBreadcrumb( __prefix + "exiting" ); 49 | __prefix = null; 50 | } 51 | 52 | 53 | var init = _.once( function() { 54 | var __prefix = $.__controllerPath + ".init: "; 55 | apm.leaveBreadcrumb( __prefix + "entering" ); 56 | var window_stack = []; 57 | var __local_prefix 58 | if( OS_IOS ) { 59 | 60 | // $.root = $.args.root ? Alloy.createController( $.args.root.moduleName, $.args.root ).getViewEx( { recurse: true } ) : Ti.UI.createWindow( { id: "window" } ); 61 | $.root = createWindow2( $.args ); 62 | console.error($.root); 63 | $.nav = Ti.UI.iOS.createNavigationWindow( { 64 | window: $.root, 65 | id: "nav", 66 | // backgroundColor: 'transparent', 67 | } ); 68 | 69 | $.addClass( $.root, 'navbar' ); 70 | 71 | // console.error('$.nav.barColor: ' + JSON.stringify($.nav.barColor, null, 2)); 72 | $.nav && $.addTopLevelView( $.nav ); 73 | $.push = function( params ) { 74 | __local_prefix = $.__controllerPath + ".push: "; 75 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 76 | var window; 77 | try { 78 | window = createWindow2( params ); 79 | } catch( err ) { 80 | console.error( err ); 81 | } 82 | window && $.nav.openWindow( window, { animated: payload.animated } ); 83 | window_stack.push( window ); 84 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 85 | }; 86 | $.go = function( payload ) { 87 | __local_prefix = $.__controllerPath + ".go: "; 88 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 89 | var window; 90 | try { 91 | window = createWindow( payload ); 92 | } catch( err ) { 93 | console.error( err ); 94 | } 95 | window && $.nav.openWindow( window, { animated: payload.animated } ); 96 | window_stack.push( window ); 97 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 98 | }; 99 | $.goBack = function( payload ) { 100 | __local_prefix = $.__controllerPath + ".goBack: "; 101 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 102 | if( !payload ) { 103 | return; 104 | } 105 | var window = window_stack.pop(); 106 | window && $.nav.closeWindow( window, payload.options ); 107 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 108 | }; 109 | //TODO: This should probably be handled by a close function which is called by the navigator... 110 | $.goHome = function( payload ) { 111 | __local_prefix = $.__controllerPath + ".goHome: "; 112 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 113 | for( var i = 0; i < window_stack.length; i++ ) { 114 | window_stack[ i ].close( Ti.UI.iPhone.AnimationStyle.NONE ); 115 | } 116 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 117 | }; 118 | 119 | } 120 | 121 | 122 | if( OS_ANDROID ) { 123 | $.root = $.args.root ? Alloy.createController( $.args.root.moduleName, $.args.root ).getViewEx( { recurse: true } ) : Ti.UI.createWindow(); 124 | $.root && $.addTopLevelView( $.root ); 125 | $.go = function( payload ) { 126 | __local_prefix = $.__controllerPath + ".go: "; 127 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 128 | var window; 129 | try { 130 | window = createWindow( payload ); 131 | } catch( err ) { 132 | console.error( err ); 133 | } 134 | if( window ) { 135 | window.addEventListener( "open", function( evt ) { 136 | var actionBar = window.activity.actionBar; 137 | if( actionBar ) { 138 | actionBar.homeButtonEnabled = true; 139 | actionBar.displayHomeAsUp = true; 140 | actionBar.onHomeIconItemSelected = function() { 141 | evt.source.close(); 142 | window_stack.pop(); 143 | }; 144 | } else { 145 | console.warn( "No ActionBar found." ); 146 | } 147 | } ); 148 | 149 | window.open(); 150 | window_stack.push( window ); 151 | } 152 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 153 | }; 154 | $.goHome = function( payload ) { 155 | __local_prefix = $.__controllerPath + ".goHome: "; 156 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 157 | for( var i = 0; i < window_stack.length; i++ ) { 158 | console.error( "closing window: " + window_stack[ i ].id ); 159 | window_stack[ i ].close(); 160 | } 161 | }; 162 | } 163 | 164 | 165 | if( OS_MOBILEWEB ) { 166 | $.window = Ti.UI.createWindow( { id: "window" } ); 167 | $.addTopLevelView( $.window ); 168 | $.root = $.args.root ? Alloy.createController( $.args.root.moduleName, $.args.root ).getViewEx( { recurse: true } ) : Ti.UI.createWindow(); 169 | $.nav = Ti.UI.MobileWeb.createNavigationGroup( { 170 | window: $.root, 171 | id: "nav" 172 | } ); 173 | $.window.add( $.nav ); 174 | $.navigate = function( payload ) { 175 | __local_prefix = $.__controllerPath + ".navigate: "; 176 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 177 | var window; 178 | try { 179 | window = createWindow( payload ); 180 | } catch( err ) { 181 | console.error( err ); 182 | } 183 | window && $.nav.open( window ); 184 | window_stack.push( window ); 185 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 186 | }; 187 | //TODO: This should probably be handled by a close function which is called by the navigator... 188 | $.goHome = function( payload ) { 189 | __local_prefix = $.__controllerPath + ".goHome: "; 190 | for( var i = 0; i < window_stack.length; i++ ) { 191 | window_stack[ i ].close(); 192 | } 193 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 194 | }; 195 | } 196 | 197 | if( OS_WINDOWS ) { 198 | $.root = $.args.root ? Alloy.createController( $.args.root.moduleName, $.args.root ).getViewEx( { recurse: true } ) : Ti.UI.createWindow(); 199 | $.root && $.addTopLevelView( $.root ); 200 | $.navigate = function( payload ) { 201 | __local_prefix = $.__controllerPath + ".navigate: "; 202 | apm.leaveBreadcrumb( __local_prefix + "entering" ); 203 | var window; 204 | try { 205 | window = createWindow( payload ); 206 | } catch( err ) { 207 | console.error( err ); 208 | } 209 | window && window.open(); 210 | apm.leaveBreadcrumb( __local_prefix + "exiting" ); 211 | } 212 | } 213 | apm.leaveBreadcrumb( __prefix + "exiting" ); 214 | } ); -------------------------------------------------------------------------------- /controllers/page.js: -------------------------------------------------------------------------------- 1 | _.forEach($.args.children, child => $.window.add); -------------------------------------------------------------------------------- /controllers/tabbedWindow.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*** 3 | * __ _ __ __ 4 | * ____ ___ ____ / /_ (_)/ /___ / /_ ___ _____ ____ 5 | * / __ `__ \ / __ \ / __ \ / // // _ \ / __ \ / _ \ / ___// __ \ 6 | * / / / / / // /_/ // /_/ // // // __// / / // __// / / /_/ / 7 | * /_/ /_/ /_/ \____//_.___//_//_/ \___//_/ /_/ \___//_/ \____/ 8 | * 9 | * mobile solutions for everyday heroes 10 | * 11 | * @overview 12 | * Widget that handles the actual implementation of the bottom-tabbed window navigation 13 | * -- Note -- This is a work in progress and is not fully functional! 14 | * 15 | * @module widgets/nativeloop/controllers/navigator 16 | * @author Brenton House 17 | * @version 1.0.0 18 | * @since 1.0.0 19 | * @copyright Copyright (c) 2017 by Superhero Studios Incorporated. All Rights Reserved. 20 | * @license Licensed under the terms of the MIT License (MIT) 21 | * 22 | */ 23 | 24 | const apm = require( 'nativeloop/apm' ); 25 | const utils = require( 'nativeloop/utils' ); 26 | 27 | $.getViewAlloy = $.getView; 28 | $.getView = function( id ) { 29 | $.init(); 30 | return $.getViewAlloy( id ); 31 | }; 32 | 33 | /** 34 | * @function createTabbedWindow 35 | * @summary create a tabbed window 36 | * @param {object} params - parameters used to initialize the creating of the window 37 | * @see {@link http://docs.appcelerator.com/platform/latest/#!/api/Modules.Performance | Appcelerator Titanium Window Documentation} 38 | * @since 1.0.0 39 | * @returns {object} - Returns the Titanium Window 40 | */ 41 | var createTabbedWindow = function( params ) { 42 | let __prefix = $.__controllerPath + '.createTabbedWindow: '; 43 | apm.leaveBreadcrumb( __prefix + 'entering' ); 44 | console.error( 'params: ' + utils.stringify( params, null, 2 ) ); 45 | 46 | const Navigation = require( 'nativeloop/navigation' ); 47 | const tabs = []; 48 | 49 | _.forEach( params.tabs, tabParams => { 50 | let window = Navigation.createNavBasedWindow( tabParams.screen ); 51 | 52 | params.tabStyle = params.tabStyle || {}; 53 | _.defaults( tabParams, params.tabStyle ); 54 | 55 | tabParams.iconColor = tabParams.iconColor || tabParams.color; 56 | tabParams.imageColor = tabParams.imageColor || tabParams.color; 57 | tabParams.titleColor = tabParams.titleColor || tabParams.color; 58 | 59 | tabParams.activeIconColor = tabParams.activeIconColor || tabParams.activeColor; 60 | tabParams.activeImageColor = tabParams.activeImageColor || tabParams.activeColor; 61 | tabParams.activeTitleColor = tabParams.activeTitleColor || tabParams.activeColor; 62 | 63 | if( tabParams.icon ) { 64 | tabParams.image = utils.saveIconAsFile( { 65 | name: tabParams.icon, 66 | height: 30, 67 | color: tabParams.iconColor || tabParams.color || 'black', 68 | force: true, 69 | } ); 70 | } else if( tabParams.image ) { 71 | tabParams.image = tabParams.image; 72 | } 73 | 74 | if( tabParams.activeIcon ) { 75 | tabParams.activeImage = utils.saveIconAsFile( { 76 | name: tabParams.activeIcon, 77 | height: 30, 78 | color: tabParams.activeIconColor || tabParams.activeColor || 'black', 79 | force: true, 80 | } ); 81 | } else if( tabParams.activeImage ) { 82 | tabParams.activeImage = tabParams.activeImage; 83 | } else if( tabParams.icon ) { 84 | tabParams.activeImage = utils.saveIconAsFile( { 85 | name: tabParams.icon, 86 | height: 30, 87 | color: tabParams.activeIconColor || tabParams.activeColor || 'black', 88 | force: true, 89 | } ); 90 | } 91 | 92 | // if icon is defined but activeIcon is not, we need to define it. 93 | 94 | let tabOptions = { 95 | window: window, 96 | title: tabParams.title, 97 | icon: tabParams.image, 98 | activeIcon: tabParams.activeImage, 99 | activeIconIsMask: false, // need to see if color is defined 100 | iconIsMask: false, 101 | titleColor: tabParams.titleColor, 102 | activeTitleColor: tabParams.activeTitleColor, 103 | // activeTabIconTint: _.get(tabParams,'activeIcon.color'), 104 | }; 105 | 106 | console.error( 'tabOptions: ' + JSON.stringify( tabOptions, null, 2 ) ); 107 | 108 | let tab = Ti.UI.createTab( tabOptions ); 109 | 110 | tabs.push( tab ); 111 | } ); 112 | 113 | let tabGroup = Ti.UI.createTabGroup( { 114 | tabs: tabs, 115 | } ); 116 | 117 | return tabGroup; 118 | 119 | apm.leaveBreadcrumb( __prefix + 'exiting' ); 120 | __prefix = null; 121 | }; 122 | 123 | $.init = _.once( function() { 124 | var __prefix = $.__controllerPath + '.init: '; 125 | apm.leaveBreadcrumb( __prefix + 'entering' ); 126 | if( OS_IOS ) { 127 | // console.error( '$.args: ' + utils.stringify( $.args, null, 2 ) ); 128 | $.tabbedWindow = createTabbedWindow( $.args ); 129 | $.tabbedWindow && $.addTopLevelView( $.tabbedWindow ); 130 | } 131 | 132 | apm.leaveBreadcrumb( __prefix + 'exiting' ); 133 | } ); -------------------------------------------------------------------------------- /controllers/window.js: -------------------------------------------------------------------------------- 1 | _.forEach($.args.children, child => $.window.add); -------------------------------------------------------------------------------- /core.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*** 3 | * __ _ __ __ 4 | * ____ ___ ____ / /_ (_)/ /___ / /_ ___ _____ ____ 5 | * / __ `__ \ / __ \ / __ \ / // // _ \ / __ \ / _ \ / ___// __ \ 6 | * / / / / / // /_/ // /_/ // // // __// / / // __// / / /_/ / 7 | * /_/ /_/ /_/ \____//_.___//_//_/ \___//_/ /_/ \___//_/ \____/ 8 | * 9 | * mobile solutions for everyday heroes 10 | * 11 | * @file core module for {nativeloop}, a framework for building native mobile apps using nodejs style javascript. 12 | * @module nativeloop/core 13 | * @author Brenton House * 14 | * @version 1.0.0 15 | * @since 1.0.0 16 | * @copyright Copyright (c) 2017 by Superhero Studios Incorporated. All Rights Reserved. 17 | * @license Licensed under the terms of the MIT License (MIT) 18 | * 19 | */ 20 | 21 | const _ = require( 'lodash' ); 22 | const path = require( 'path' ); 23 | const resolve = require( 'resolve' ); 24 | const paths = require( 'global-paths' ); 25 | const hjson = require( 'hjson' ); 26 | const utils = require( './utils' ); 27 | // var config; 28 | var _event; 29 | const debug = require( 'debug' ); 30 | const log = debug( 'nativeloop' ); 31 | const warn = debug( 'nativeloop' ); 32 | debug.log = console.info.bind( console ); 33 | const alloyParser = require( './alloy-parser' ); 34 | const nativeloop_widgets = require( './widgets' ); 35 | 36 | var CONST; 37 | var CU; 38 | var U; 39 | 40 | 41 | function handler( params ) { 42 | init( params ); 43 | } 44 | module.exports = handler; 45 | 46 | /** 47 | * @function init 48 | * @summary Initialize the core handler 49 | * @param {object} params - Parameters to initialize core 50 | * @since 1.0.0 51 | */ 52 | var init = function( params ) { 53 | // console.debug("params: " + JSON.stringify(params, null, 2)); 54 | 55 | CONST = params.constants; 56 | CU = params.compilerUtils; 57 | U = params.utils; 58 | 59 | 60 | // var x = U.getWidgetDirectories.toString(); 61 | // console.error('x.length: ' + JSON.stringify(x.length, null, 2)); 62 | // console.error('U.getWidgetDirectories: ' + U.getWidgetDirectories.toString()); 63 | // console.error('U.getWidgetDirectories.findWidgetAsNodeModule: ' + U.getWidgetDirectories.findWidgetAsNodeModule.toString()); 64 | 65 | 66 | params.resolve.sync = _.wrap( params.resolve.sync, function( func, id, opts ) { 67 | console.error( 'you are here → resolve.sync: ' + id ); 68 | var response; 69 | try { 70 | response = func( id, opts ); 71 | console.error( 'you are here → resolve.sync: after 1st' ); 72 | console.error( 'response: ' + JSON.stringify( response, null, 2 ) ); 73 | return response; 74 | } catch( error ) { 75 | 76 | // var regex = new RegExp( CONST.NPM_WIDGET_PREFIX + '([^\/]*)\/widget' ); 77 | var regex = new RegExp( CONST.NPM_WIDGET_PREFIX + '(.*)\/widget' ); 78 | var found = regex.exec( id ); 79 | console.error( 'found[1]: ' + JSON.stringify( found[ 1 ], null, 2 ) ); 80 | if( found && found[ 1 ] ) { 81 | console.error( 'you are here → resolve.sync: before 2nd -- ' + found[ 1 ] ); 82 | response = func( found[ 1 ] + '/widget', opts ); 83 | console.error( 'you are here → resolve.sync: after 2nd' ); 84 | return response; 85 | } 86 | } 87 | } ); 88 | 89 | params.jsonlint.parse = _.wrap( params.jsonlint.parse, function( func, input, opts ) { 90 | debug.error( 'you are here → jsonlint.parse: ' ); 91 | debug.warn( 'input: ' + JSON.stringify( input, null, 2 ) ); 92 | 93 | var result = func( input ); 94 | return require( './fix-config' ).addWidgets( { input: result, logger: debug } ); 95 | // return hjson.parse( input, opts ); 96 | } ); 97 | 98 | 99 | U.getWidgetDirectories = _.wrap( U.getWidgetDirectories, function( func, options ) { 100 | console.log( "********* getWidgetDirectories **********" ); 101 | return func( options ); 102 | } ); 103 | 104 | 105 | // var widgetParser = { 106 | // parse: ( node, state ) => { 107 | // if( !node.getAttribute( 'src' ) && state.widgetId ) { 108 | // node.setAttribute( 'src', state.widgetId ); 109 | // } 110 | 111 | // console.error( 'you are here → inside widgetParser' ); 112 | // node.nodeName = 'Require'; 113 | // node.setAttribute( 'type', 'widget' ); 114 | 115 | // let response = params.requireParser.parse( node, state ); 116 | // return response; 117 | // } 118 | // } 119 | 120 | 121 | params.baseParser.parse = alloyParser.parse( { 122 | baseParser: params.baseParser, 123 | defaultParser: params.defaultParser, 124 | widgetParser: params.widgetParser, 125 | // widgetParser: widgetParser, 126 | } ); 127 | 128 | 129 | // params.requireParser.parse = _.wrap( params.requireParser.parse, ( func, node, state ) => { 130 | // console.error('you are here → inside the FAKE require parser'); 131 | // let response = func( node, state ); 132 | // // console.error( node ); 133 | // // console.error( node.attributes); 134 | // // console.error( state ); 135 | // // console.error('node.getAttribute("__nativeloop"): ' + JSON.stringify(node.getAttribute('__nativeloop'), null, 2)); 136 | 137 | // // console.error( 'response: ' + JSON.stringify( response, null, 2 ) ); 138 | // if( node.getAttribute( '__nativeloop' ) && response.parent && response.parent.symbol ) { 139 | // console.error( 'you are here → fixing parent.symbol' ); 140 | // // response.parent.symbol = response.parent.symbol.replace( /\.getViewEx[^\)]*\)/, '' ); 141 | // response.code = response.code.replace( /\.getViewEx[^\)]*\)/g, '' ); 142 | // } 143 | // console.error( 'response.code: ' + JSON.stringify( response.code, null, 2 ) ); 144 | // console.error( 'response.parent: ' + JSON.stringify( response.parent, null, 2 ) ); 145 | // return response; 146 | // } ); 147 | 148 | U.XML.getAlloyFromFile = handler.getAlloyFromFile; 149 | 150 | console.log( "********* WRAPPING uglifyjs.parse **********" ); 151 | params.uglifyjs.parse = _.wrap( params.uglifyjs.parse, function( func, code, options ) { 152 | console.log( "********* PRE:PARSE **********" ); 153 | var params = { 154 | code: code 155 | }; 156 | executeScripts( "preparse", params ); 157 | return func( params.code, options ); 158 | } ); 159 | 160 | alloyParser.init( { 161 | CONST: CONST, 162 | CU: CU, 163 | U: U, 164 | nativeloop_widgets: nativeloop_widgets, 165 | } ); 166 | 167 | 168 | 169 | params.task( "pre:load", handler.preload ); 170 | params.task( "pre:compile", handler.precompile ); 171 | params.task( "post:compile", handler.postcompile ); 172 | params.task( "compile:app.js", handler.appjs ); 173 | } 174 | 175 | /** 176 | * @function getAlloyFromFile 177 | * @summary summary 178 | * @param {string} filename - description 179 | * @since 1.0.0 180 | * @returns {object} - description 181 | */ 182 | handler.getAlloyFromFile = function( filename ) { 183 | 184 | // console.trace("***** INSIDE getAlloyFromFile()"); 185 | var doc = U.XML.parseFromFile( filename ); 186 | var docRoot = doc.documentElement; 187 | 188 | if( _.toLower( docRoot.nodeName ) === "nativeloop" ) { 189 | docRoot.nodeName = CONST.ROOT_NODE; 190 | docRoot.setAttribute( "module", "/nativeloop" ); 191 | } else if( docRoot.nodeName === CONST.ROOT_NODE.toLowerCase() ) { 192 | docRoot.nodeName = CONST.ROOT_NODE; 193 | } 194 | // Make sure the markup has a top-level tag 195 | else if( docRoot.nodeName !== CONST.ROOT_NODE ) { 196 | exports.die( [ 197 | 'Invalid view file "' + filename + '".', 198 | 'All view markup must have a top-level tag' 199 | ] ); 200 | } 201 | 202 | return docRoot; 203 | }; 204 | 205 | // handler.preparse = function ( func ) { 206 | // console.log( "********* WRAPPING uglifyjs.parse **********" ); 207 | // return _.wrap( func, function ( func, code, options ) { 208 | // console.log( "********* PRE:PARSE **********" ); 209 | // var params = { 210 | // code: code 211 | // }; 212 | // executeScripts( "preparse", params ); 213 | // return func( params.code, options ); 214 | // } ); 215 | // } 216 | 217 | // handler.alloyParser = alloyParser.parse; 218 | 219 | /** 220 | * @function splitTasks 221 | * @summary summary 222 | * @param {object} tasks - description 223 | * @since 1.0.0 224 | * @returns {object} - description 225 | */ 226 | function splitTasks( tasks ) { 227 | var results = []; 228 | if( !_.isArray( tasks ) ) { 229 | tasks = [ tasks ]; 230 | } 231 | // handler.logger.trace("splitting tasks..."); 232 | // handler.logger.trace("tasks: " + JSON.stringify(tasks, null, 2)); 233 | _.forEach( tasks, function( task ) { 234 | 235 | if( _.isArray( task.events ) && !_.isEmpty( task.events ) ) { 236 | // handler.logger.trace("found events to split"); 237 | _.forEach( task.events, function( event ) { 238 | var splitTask = _.cloneDeep( task ); 239 | splitTask.events = event; 240 | results.push( splitTask ); 241 | } ); 242 | } else { 243 | results.push( task ); 244 | } 245 | 246 | } ); 247 | return results; 248 | 249 | } 250 | 251 | var _init = _.once( function() { 252 | loadConfig(); 253 | configureTasks(); 254 | } ); 255 | 256 | 257 | /** 258 | * @function loadTasks 259 | * @summary summary 260 | * @since 1.0.0 261 | * @returns {object} - description 262 | */ 263 | function loadTasks() { 264 | var tasks = _.cloneDeep( _.get( handler.config, "nativeloop.tasks", [] ).concat( require( "./core_tasks" ) ) ); 265 | // handler.logger.debug("loaded Tasks: " + JSON.stringify(tasks, null, 2)); 266 | return tasks; 267 | } 268 | 269 | /** 270 | * @function configureTasks 271 | * @summary summary 272 | * @param {object} tasks - description 273 | * @since 1.0.0 274 | * @returns {object} - description 275 | */ 276 | function configureTasks( tasks ) { 277 | 278 | tasks = tasks || loadTasks(); 279 | 280 | var configuredTasks = []; 281 | var importedTasks = []; 282 | // handler.logger.trace("tasks coming into configureTasks(): " + JSON.stringify(tasks, null, 2)); 283 | _.forEach( tasks, function( task ) { 284 | // handler.logger.trace("task: " + JSON.stringify(task, null, 2)); 285 | if( _.isString( task ) ) { 286 | handler.logger.trace( "getting default tasks for module: " + task ); 287 | var target = require( resolve.sync( task, { 288 | basedir: handler.event.dir.project 289 | } ) ); 290 | importedTasks = importedTasks.concat( target.tasks || [] ); 291 | // handler.logger.error("target.tasks: " + JSON.stringify(target.tasks, null, 2)); 292 | // handler.logger.warn("imported tasks: " + JSON.stringify(importedTasks, null, 2)); 293 | return true; 294 | } else { 295 | // handler.logger.trace("adding task: " + JSON.stringify(task, null, 2)); 296 | configuredTasks.push( _.defaults( task, { 297 | weight: 1000, 298 | platforms: [ "ios", "android", "mobileweb", "windows" ] 299 | } ) ); 300 | } 301 | } ); 302 | 303 | 304 | if( !_.isEmpty( importedTasks ) ) { 305 | handler.logger.debug( "Configuring importedTasks" ) 306 | configuredTasks = configuredTasks.concat( configureTasks( importedTasks ) ); 307 | } else { 308 | handler.configuredTasks = splitTasks( configuredTasks ); 309 | // handler.logger.trace("handler.configuredTasks: " + JSON.stringify(handler.configuredTasks, null, 2)); 310 | } 311 | return configuredTasks; 312 | } 313 | 314 | /** 315 | * @function loadConfig 316 | * @summary Load alloy config file and set it to property in handler 317 | * @since 1.0.0 318 | */ 319 | var loadConfig = function() { 320 | 321 | handler.logger.debug( "Loading alloy config file" ); 322 | handler.config = require( path.join( handler.event.dir.resourcesPlatform, "alloy", "CFG" ) ); 323 | // handler.logger.trace(JSON.stringify(handler.event, null, 2)); 324 | // handler.logger.trace(JSON.stringify(handler.config, null, 2)); 325 | } 326 | 327 | Object.defineProperty( handler, "event", { 328 | 329 | get: function() { 330 | return _event; 331 | }, 332 | set: function( event ) { 333 | _event = event; 334 | event.dir.resourcesPlatform = path.join( event.dir.resources, event.alloyConfig.platform === 'ios' ? 'iphone' : event.alloyConfig.platform ); 335 | }, 336 | enumerable: true, 337 | configurable: false 338 | } ); 339 | 340 | /** 341 | * @function executeScripts 342 | * @summary summary 343 | * @param {string} eventName - description 344 | * @param {object} params - description 345 | * @since 1.0.0 346 | */ 347 | function executeScripts( eventName, params ) { 348 | 349 | // handler.logger.trace("task to execute: " + JSON.stringify(handler.configuredTasks, null, 2)); 350 | // handler.logger.trace("handler.configuredTasks: " + JSON.stringify(handler.configuredTasks, null, 2)); 351 | var tasks = _.sortBy( _.filter( handler.configuredTasks || [], ( task ) => { 352 | return task.events === eventName && _.includes( task.platforms, handler.event.alloyConfig.platform ); 353 | } ), "weight" ); 354 | params = params || {}; 355 | 356 | _.forEach( tasks, function( task ) { 357 | 358 | //TODO: Check to make sure task is an object... 359 | 360 | var taskParams = { 361 | event: handler.event, 362 | config: handler.config, 363 | logger: handler.logger, 364 | code: params.code, 365 | }; 366 | 367 | _.defaults( taskParams, task ); 368 | handler.logger.debug( "executing task: " + task.module ); 369 | // handler.logger.debug("taskParams: " + JSON.stringify(taskParams, null, 2)); 370 | var target = require( resolve.sync( task.module, { 371 | basedir: handler.event.dir.project, 372 | paths: paths(), 373 | } ) ); 374 | _.isFunction( target.execute ) && target.execute( taskParams ); 375 | 376 | if( taskParams.code ) { 377 | // handler.logger.trace(taskParams.code); 378 | params.code = taskParams.code; 379 | } 380 | 381 | } ); 382 | 383 | } 384 | 385 | var events = [ "preload", "precompile", "postcompile", "appjs" ]; 386 | _.forEach( events, function( eventName ) { 387 | handler[ eventName ] = function( event, logger ) { 388 | // handler.logger = logger; 389 | debug.trace = logger.trace.bind( logger ); 390 | debug.debug = logger.debug.bind( logger ); 391 | debug.info = logger.info.bind( logger ); 392 | debug.warn = logger.warn.bind( logger ); 393 | debug.error = logger.error.bind( logger ); 394 | handler.logger = debug; 395 | handler.event = event; 396 | _init(); 397 | 398 | handler.logger.warn( "********************* STARTING EVENT: " + eventName + " ***************************" ); 399 | handler.logger = logger; 400 | // debug.trace = logger.trace.bind(logger); 401 | // debug.warn = logger.debug.bind(logger); 402 | // debug.info = logger.info.bind(logger); 403 | // debug.warn = logger.warn.bind(logger); 404 | // debug.error = logger.error.bind(logger); 405 | // handler.logger = debug; 406 | 407 | executeScripts( eventName ); 408 | handler.logger.warn( "********************* FINISHED EVENT: " + eventName + " ***************************" ); 409 | } 410 | } ); -------------------------------------------------------------------------------- /core_tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = [ 4 | // { 5 | // "module": "nativeloop/plugins/npm", 6 | // "dirname": "${event.dir.lib}", 7 | // "args": ["install"], 8 | // "events": "preload", 9 | // "weight": 100, 10 | // }, 11 | { 12 | "module": "nativeloop/plugins/promises", 13 | "events": "postcompile", 14 | "weight": 100, 15 | }, { 16 | "module": "nativeloop/plugins/underscore-fix", 17 | "events": "postcompile", 18 | "weight": 110, 19 | }, { 20 | "module": "nativeloop/plugins/backbone-fix", 21 | "events": "postcompile", 22 | "weight": 120, 23 | }, 24 | 25 | // TODO: Need to figure out way to add hook after Titanium has completed 26 | // { 27 | // "module": "nativeloop/plugins/remove-invalid-header", 28 | // "events": "postcompile", 29 | // "weight": 120, 30 | // "platforms": ["mobileweb"], 31 | // }, 32 | { 33 | "module": "nativeloop/plugins/widgets-add", 34 | "events": "preload", 35 | "weight": 110, 36 | }, 37 | // { 38 | // "module": "nativeloop/plugins/widgets-remove", 39 | // "events": "postcompile", 40 | // "weight": 500, 41 | // }, 42 | 43 | { 44 | "module": "nativeloop/plugins/nodejs", 45 | "events": "postcompile", 46 | "includes": [ "**/*.js", "**/*.json", "!resolver.js", "!**/alloy/underscore.js" ], 47 | "weight": 200, 48 | }, { 49 | "module": "nativeloop/plugins/babeljs", 50 | "options": { 51 | // "presets": ["es2015"] 52 | 'presets': [ require( 'babel-preset-es2015' ) ], 53 | }, 54 | "includes": [ "**/*.js", "!backbone2.js", "**/alloy/lodash.js" ], 55 | "events": [ "preload" ], 56 | "weight": 200, 57 | }, { 58 | "module": "nativeloop/plugins/babeljs", 59 | "options": { 60 | // "presets": [ "es2015" ] 61 | 'presets': [ require( 'babel-preset-es2015' ) ], 62 | }, 63 | "includes": [ "**/*.js", "!backbone2.js", "**/alloy/lodash.js" ], 64 | "events": [ "preparse" ], 65 | "weight": 100, 66 | }, 67 | ] -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /docs/cli.md: -------------------------------------------------------------------------------- 1 |

2 | nativeloop logo 3 |

4 | 5 |
⚡ Developing native mobile apps just got a whole lot more awesome ⚡
6 | 7 | --- 8 | 9 | ## CLI Documentation 10 | 11 | --- 12 | 13 | ### General Usage 14 | 15 | Usage | Synopsis 16 | ------|------- 17 | General | `native [Command Parameters] [--command ]` 18 | Alias | `nativeloop [Command Parameters] [--command ]` 19 | 20 | ### Project Development Commands 21 | Command | Description 22 | ---|--- 23 | [create](create.md) | Creates a new project for native mobile development with {nativeloop}. 24 | [init]() | ![coming soon!](https://img.shields.io/badge/coming-soon-orange.png) Initializes an existing Appcelerator mobile project for development with {nativeloop}. 25 | 26 | ### Global Options 27 | Option | Description 28 | -------|--------- 29 | --help, -h, /? | Prints help about the selected command in the console. 30 | --path `` | Specifies the directory that contains the project. If not set, the project is searched for in the current directory and all directories above it. 31 | --version | Prints the client version. 32 | 33 | -------------------------------------------------------------------------------- /docs/create.md: -------------------------------------------------------------------------------- 1 |

2 | nativeloop logo 3 |

4 | 5 |
⚡ Developing native mobile apps just got a whole lot more awesome ⚡
6 | 7 | --- 8 | 9 | ## create 10 | 11 | --- 12 | 13 | ### Description 14 | 15 | Creates a new project for native mobile development with {nativeloop}. 16 | 17 | ### General Usage 18 | 19 | Usage | Description 20 | ---|--- 21 | Create from default template | `native create [--path ] [--id ]` 22 | Create from custom template | `native create [--path ] [--appid ] --template