JavaScript: Using an object's property in another property? - javascript

In my Grunt build script I have an object that holds most of my paths, like so:
var project_config = {
project_paths: {
build: 'assets/css',
docs: 'docs',
src_scss: 'src/scss/**/*.scss',
yaml: grunt.file.readYAML('docs/config.yml')
}
};
As you can see, docs can be found again in 'yaml', because it is just a path.
How can I use docs in yaml?
Doing it like so didn't work, probably because the object can't access itself:
var project_config = {
project_paths: {
build: 'assets/css',
docs: 'docs',
src_scss: 'src/scss/**/*.scss',
yaml: grunt.file.readYAML(docs+'/config.yml')
}
};
Using Grunt templating also didn't work: yaml: grunt.file.readYAML('<%= project_paths.docs %>/config.yml')

At the moment you want to access the path, Grunt did not process the configuration object yet. You can process the template string "manually" with grunt.template.process:
var project_config = {
project_paths: {
build: 'assets/css',
docs: 'docs',
src_scss: 'src/scss/**/*.scss'
}
};
project_config.project_paths.yaml = grunt.file.readYAML(
grunt.template.process(
'<%= project_paths.docs %>/config.yml',
{data: project_config}
)
);

Related

How to access options inside apos.define?

apostrophe-workflow has the following:
public/js/user.js
apos.define('apostrophe-workflow', {
[...]
construct: function(self, options) {
self.locales = options.locales;
self.locale = options.locale;
[...]
I searched quite a while and did not manage to find the reason why this construct method has access to the options object. I tried browserCall but am not sure how to use this properly.
My assets are pushed using pushAsset, too. But they do not have access to the options after apos.create.
Edit: Example scenario:
A simple module that pushes one script to the browser.
module/index.js
construct: function(self, options) {
self.pushAsset('script', 'name', {when: 'always'});
}
And takes one option.
app.js
modules: {
'module': {
option: 'Option'
}
}
The script should use this option on construct.
module/public/js/script.js
apos.define('module-script', {
construct: function(self, options) {
console.log(options.option); // Print 'Option' to console.
}
});
Another module will call apos.create('module-script').
I hope it's clear.
You can solve (at least) this two ways depending on the structure you want.
1. Explicit browser options
You can explicitly pass options to the browser from your modules configuration by wrapping them in a browser object from the root of the module's config.
in lib/modules/layout-widgets/index.js
module.exports = {
extend: 'apostrophe-widgets',
label: 'Layout',
browser: {
coolArray: [3, 2, 1]
}
}
This will get merged into the options passed to your browser side JS of the module automatically.
then in /lib/modules/layout-widgets/public/js/always.js
apos.define('layout-widgets', {
extend: 'apostrophe-widgets',
construct: function (self, options) {
self.play = function ($widget, data, options) {
console.log(self.options.coolArray);
}
}
});
2. Super'ing getCreateSingletonOptions
If you don't like the syntax of separating your browser options from your main options, you can always override the method responsible for teeing up the browser side module's default options by copying it, invoking it, and adding on to it.
in lib/modules/layout-widgets/index.js
module.exports = {
extend: 'apostrophe-widgets',
label: 'Layout',
coolArray: [3,2,1],
construct: function(self, options) {
// copy the method
var superGetCreateSingletonOptions = self.getCreateSingletonOptions;
// redefine it
self.getCreateSingletonOptions = function (req) {
// invoke the original method and save the result
var browserOptions = superGetCreateSingletonOptions(req);
// add on to the default results with whatever you want
browserOptions.coolArray = self.options.coolArray;
browserOptions.somethingElse = 'hey this is fun';
return browserOptions;
};
}
};
then, again, in /lib/modules/layout-widgets/public/js/always.js
apos.define('layout-widgets', {
extend: 'apostrophe-widgets',
construct: function (self, options) {
self.play = function ($widget, data, options) {
console.log(self.options.coolArray);
console.log(self.options.somethingElse);
}
}
});

Vue.js exclude settings file from being bundled

I am using the vue-webpack template and I have created a settings.json file to store environment variables that should be changed when installing the script.
My settings.json (just store the absolute path to the API server):
{
"apiURL": "//localhost/app/server/API"
}
How can I keep the file from being minified/bundled in the production version such that I can change it and the updated file will be used next time the app is accessed (without having to build it again) ?
In my app I use this file via require:
const SETTINGS = require('../settings.json');
I understand that by requireing it webpack will bundle it as a module, but how can I include it in my app such that the settings file will still be a separated file in the production build that I can edit.
Is there a better format/way to store those settings (so that they can be edited in production without re-building) ?
You can define those settings in an object that can be referenced in the externals configuration in webpack.config.js.
The externals configuration option provides a way of excluding
dependencies from the output bundles. Instead, the created bundle
relies on that dependency to be present in the consumer's environment.
Example:
externals: {
appSettings: "appSettings",
"window.appSettings": "appSettings"
}
Where appSettings is a global variable containing the environment variables you want to manipulate.
Alternatively, if you do not like that method that exposes the settings in the global object, you can do the following:
Export a variable with the default settings, which will be included in webpack bundle.
export var appSettings = {
currentSettings: "",
settings: {},
getString: function(strName) {
var sett = this.currentSettings ?
this.settings[this.currentSettings] :
appDefaultStrings;
if (!sett || !sett[strName]) sett = appDefaultStrings;
return sett[strName];
},
getSettings: function() { //Gets all available settings
var res = [];
res.push("");
for (var key in this.settings) {
res.push(key);
}
res.sort();
return res;
}
};
export var appDefaultStrings = {
apiURL: "//localhost/app/server/API"
//...
}
appSettings.settings["default"] = appDefaultStrings;
You can then require or import this variable and use it like so:
import appSettings from "../path/to/appSettings";
appSettings.getString("apiURL"); //"//localhost/app/server/API"
Now that you have your default settings up and running, we will create another file containing the custom settings.
import appSettings from "../path/to/appSettings";
export var appProductionSettings = {
apiUrl: "http://example.com"
//...
}
appSettings.settings["production"] = appProductionSettings;
The last thing you need to do is handle which settings you want to use. I have not used vue.js yet, but hopefully this will lead you in the right direction:
import appSettings from "../path/to/appSettings";
export class MyApp {
constructor() {
this.settingsValue = "";
}
get settings() {
return this.settingsValue;
}
set settings(value) {
this.settingsValue = value;
appSettings.currentSettings = value;
}
}
Change the settings:
import "../path/to/productionSettings";
var app = new MyApp();
app.settings = "production";
With this method you can create and use as many settings files as you want.

Grunt-scaffold after() function access to prompt answers

The docs for the NPM package grunt-scaffold lacked any information really on its after() property/function.. I have a grunt file which creates a new directory for a new script and copies boilerplate files into it from the designated template folder.. The desire is to finish the grunt scaffold:new_script command and have it log out the location of the newly generated folder.
Gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
scaffold: {
new_script: {
options: {
questions: [{
name: 'script_name',
type: 'input',
message: 'Script name, catterpillar_case?(e.g. \'new_script\'):'
}],
template: {
"scripts/etl_template/": "scripts/{{script_name}}/",
},
after: function(){
console.log("New script generated in new folder scripts/{{script_name}}")
}
}
}
}
});
grunt.loadNpmTasks('grunt-scaffold');
grunt.registerTask('default', ['scaffold']);
};
However, the ouput is
-bash-4.1$ grunt scaffold:new_script
Running "scaffold:new_script" (scaffold) task
? Script name, catterpillar_case?(e.g. 'new_script'): test_grunt
New script generated in new folder scripts/{{script_name}}
Done.
This did not do the string replacing as it did when it created the scripts/test_grunt folder! As you can see the documentation almost doesn't exist for that after() functionality, and I'm wondering if I can use javascript"system argume
An example was not given in the documentation for the after() function, but if you use the same result parameter as in the example given for filter(), you can access the answer values via their names.
Gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
scaffold: {
new_script: {
options: {
questions: [{
name: 'script_name',
type: 'input',
message: 'Script name, catterpillar_case?(e.g. \'new_script\'):'
}],
template: {
"scripts/etl_template/": "scripts/{{script_name}}/",
},
after: function(result){
console.log("New script generated in new folder scripts/" + result.script_name)
}
}
}
}
});
grunt.loadNpmTasks('grunt-scaffold');
grunt.registerTask('default', ['scaffold']);
};
Output
-bash-4.1$ grunt scaffold:new_script
Running "scaffold:new_script" (scaffold) task
? Script name, catterpillar_case?(e.g. 'new_script'): test_grunt
New script generated in new folder scripts/test_grunt
Done.

Defining custom Grunt tasks and chain them with others

I have the following Gruntfile.js:
module.exports = function(grunt) {
var config = {
shell: {
...
},
copy: {
...
}
};
grunt.initConfig(config);
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-shell');
grunt.registerTask('default', ['shell:compile', 'copy:jsfiles']);
};
I am using grunt-contrib-x components which can be configured and then registered in a task as part of a chain.
What about a custom task?
I need to add another task whose work is performed by a function:
var customTask = function() {
// This will do something...
};
I need to run it after shell:compile and copy:jsfiles as part of another task, and also in other chains. I would like to have the same pattern and being able to do something like:
module.exports = function(grunt) {
var config = {
shell: { ... }, copy: { ... },
customTask: function() {
// Doing stuff
}
};
// ... some code ...
grunt.registerTask('default', ['shell:compile', 'copy:jsfiles']);
grunt.registerTask('advanced', ['shell:compile', 'copy:jsfiles', 'customTask']);
grunt.registerTask('advanced2', ['shell:compileComponent', 'copy:jsfilesComponent', 'customTask']);
};
The goal is having the possibility to create task chains and include my custom task as part of a list of sequential tasks to be executed.
How can I achieve this?
Call grunt.registerTask and pass in a name as the first argument and the function to run as the last argument.
grunt.registerTask('myTask', function () {
//do some stuff
});
Then you can chain it
grunt.registerTask('advanced', ['shell:compile', 'copy:jsfiles', 'myTask']);
Basically it's the same as in your example, except you define your custom task as a parameter to grunt.registerTask, not as a property in the config.
http://gruntjs.com/creating-tasks#custom-tasks

How do I configure different environments in Angular.js?

How do you manage configuration variables/constants for different environments?
This could be an example:
My rest API is reachable on localhost:7080/myapi/, but my friend that works on the same code under Git version control has the API deployed on his Tomcat on localhost:8099/hisapi/.
Supposing that we have something like this :
angular
.module('app', ['ngResource'])
.constant('API_END_POINT','<local_end_point>')
.factory('User', function($resource, API_END_POINT) {
return $resource(API_END_POINT + 'user');
});
How do I dynamically inject the correct value of the API endpoint, depending on the environment?
In PHP I usually do this kind of stuff with a config.username.xml file, merging the basic configuration file (config.xml) with the local environment configuration file recognised by the name of the user. But I don't know how to manage this kind of thing in JavaScript?
I'm a little late to the thread, but if you're using Grunt I've had great success with grunt-ng-constant.
The config section for ngconstant in my Gruntfile.js looks like
ngconstant: {
options: {
name: 'config',
wrap: '"use strict";\n\n{%= __ngModule %}',
space: ' '
},
development: {
options: {
dest: '<%= yeoman.app %>/scripts/config.js'
},
constants: {
ENV: 'development'
}
},
production: {
options: {
dest: '<%= yeoman.dist %>/scripts/config.js'
},
constants: {
ENV: 'production'
}
}
}
The tasks that use ngconstant look like
grunt.registerTask('server', function (target) {
if (target === 'dist') {
return grunt.task.run([
'build',
'open',
'connect:dist:keepalive'
]);
}
grunt.task.run([
'clean:server',
'ngconstant:development',
'concurrent:server',
'connect:livereload',
'open',
'watch'
]);
});
grunt.registerTask('build', [
'clean:dist',
'ngconstant:production',
'useminPrepare',
'concurrent:dist',
'concat',
'copy',
'cdnify',
'ngmin',
'cssmin',
'uglify',
'rev',
'usemin'
]);
So running grunt server will generate a config.js file in app/scripts/ that looks like
"use strict";
angular.module("config", []).constant("ENV", "development");
Finally, I declare the dependency on whatever modules need it:
// the 'config' dependency is generated via grunt
var app = angular.module('myApp', [ 'config' ]);
Now my constants can be dependency injected where needed. E.g.,
app.controller('MyController', ['ENV', function( ENV ) {
if( ENV === 'production' ) {
...
}
}]);
One cool solution might be separating all environment-specific values into some separate angular module, that all other modules depend on:
angular.module('configuration', [])
.constant('API_END_POINT','123456')
.constant('HOST','localhost');
Then your modules that need those entries can declare a dependency on it:
angular.module('services',['configuration'])
.factory('User',['$resource','API_END_POINT'],function($resource,API_END_POINT){
return $resource(API_END_POINT + 'user');
});
Now you could think about further cool stuff:
The module, that contains the configuration can be separated into configuration.js, that will be included at your page.
This script can be easily edited by each of you, as long as you don’t check this separate file into git. But it's easier to not check in the configuration if it is in a separate file. Also, you could branch it locally.
Now, if you have a build-system, like ANT or Maven, your further steps could be implementing some placeholders for the values API_END_POINT, that will be replaced during build-time, with your specific values.
Or you have your configuration_a.js and configuration_b.js and decide at the backend which to include.
For Gulp users, gulp-ng-constant is also useful combined with gulp-concat, event-stream and yargs.
var concat = require('gulp-concat'),
es = require('event-stream'),
gulp = require('gulp'),
ngConstant = require('gulp-ng-constant'),
argv = require('yargs').argv;
var enviroment = argv.env || 'development';
gulp.task('config', function () {
var config = gulp.src('config/' + enviroment + '.json')
.pipe(ngConstant({name: 'app.config'}));
var scripts = gulp.src('js/*');
return es.merge(config, scripts)
.pipe(concat('app.js'))
.pipe(gulp.dest('app/dist'))
.on('error', function() { });
});
In my config folder I have these files:
ls -l config
total 8
-rw-r--r--+ 1 .. ci.json
-rw-r--r--+ 1 .. development.json
-rw-r--r--+ 1 .. production.json
Then you can run gulp config --env development and that will create something like this:
angular.module("app.config", [])
.constant("foo", "bar")
.constant("ngConstant", true);
I also have this spec:
beforeEach(module('app'));
it('loads the config', inject(function(config) {
expect(config).toBeTruthy();
}));
To achieve that, I suggest you to use AngularJS Environment Plugin: https://www.npmjs.com/package/angular-environment
Here's an example:
angular.module('yourApp', ['environment']).
config(function(envServiceProvider) {
// set the domains and variables for each environment
envServiceProvider.config({
domains: {
development: ['localhost', 'dev.local'],
production: ['acme.com', 'acme.net', 'acme.org']
// anotherStage: ['domain1', 'domain2'],
// anotherStage: ['domain1', 'domain2']
},
vars: {
development: {
apiUrl: '//localhost/api',
staticUrl: '//localhost/static'
// antoherCustomVar: 'lorem',
// antoherCustomVar: 'ipsum'
},
production: {
apiUrl: '//api.acme.com/v2',
staticUrl: '//static.acme.com'
// antoherCustomVar: 'lorem',
// antoherCustomVar: 'ipsum'
}
// anotherStage: {
// customVar: 'lorem',
// customVar: 'ipsum'
// }
}
});
// run the environment check, so the comprobation is made
// before controllers and services are built
envServiceProvider.check();
});
And then, you can call the variables from your controllers such as this:
envService.read('apiUrl');
Hope it helps.
You could use lvh.me:9000 to access your AngularJS app, (lvh.me just points to 127.0.0.1) and then specify a different endpoint if lvh.me is the host:
app.service("Configuration", function() {
if (window.location.host.match(/lvh\.me/)) {
return this.API = 'http://localhost\\:7080/myapi/';
} else {
return this.API = 'http://localhost\\:8099/hisapi/';
}
});
And then inject the Configuration service and use Configuration.API wherever you need to access the API:
$resource(Configuration.API + '/endpoint/:id', {
id: '#id'
});
A tad clunky, but works fine for me, albeit in a slightly different situation (API endpoints differ in production and development).
We could also do something like this.
(function(){
'use strict';
angular.module('app').service('env', function env() {
var _environments = {
local: {
host: 'localhost:3000',
config: {
apiroot: 'http://localhost:3000'
}
},
dev: {
host: 'dev.com',
config: {
apiroot: 'http://localhost:3000'
}
},
test: {
host: 'test.com',
config: {
apiroot: 'http://localhost:3000'
}
},
stage: {
host: 'stage.com',
config: {
apiroot: 'staging'
}
},
prod: {
host: 'production.com',
config: {
apiroot: 'production'
}
}
},
_environment;
return {
getEnvironment: function(){
var host = window.location.host;
if(_environment){
return _environment;
}
for(var environment in _environments){
if(typeof _environments[environment].host && _environments[environment].host == host){
_environment = environment;
return _environment;
}
}
return null;
},
get: function(property){
return _environments[this.getEnvironment()].config[property];
}
}
});
})();
And in your controller/service, we can inject the dependency and call the get method with property to be accessed.
(function() {
'use strict';
angular.module('app').service('apiService', apiService);
apiService.$inject = ['configurations', '$q', '$http', 'env'];
function apiService(config, $q, $http, env) {
var service = {};
/* **********APIs **************** */
service.get = function() {
return $http.get(env.get('apiroot') + '/api/yourservice');
};
return service;
}
})();
$http.get(env.get('apiroot') would return the url based on the host environment.
Good question!
One solution could be to continue using your config.xml file, and provide api endpoint information from the backend to your generated html, like this (example in php):
<script type="text/javascript">
angular.module('YourApp').constant('API_END_POINT', '<?php echo $apiEndPointFromBackend; ?>');
</script>
Maybe not a pretty solution, but it would work.
Another solution could be to keep the API_END_POINT constant value as it should be in production, and only modify your hosts-file to point that url to your local api instead.
Or maybe a solution using localStorage for overrides, like this:
.factory('User',['$resource','API_END_POINT'],function($resource,API_END_POINT){
var myApi = localStorage.get('myLocalApiOverride');
return $resource((myApi || API_END_POINT) + 'user');
});
Very late to the thread, but a technique I've used, pre-Angular, is to take advantage of JSON and the flexibility of JS to dynamically reference collection keys, and use inalienable facts of the environment (host server name, current browser language, etc.) as inputs to selectively discriminate/prefer suffixed key names within a JSON data structure.
This provides not merely deploy-environment context (per OP) but any arbitrary context (such as language) to provide i18n or any other variance required simultaneously, and (ideally) within a single configuration manifest, without duplication, and readably obvious.
IN ABOUT 10 LINES VANILLA JS
Overly-simplified but classic example: An API endpoint base URL in a JSON-formatted properties file that varies per environment where (natch) the host server will also vary:
...
'svcs': {
'VER': '2.3',
'API#localhost': 'http://localhost:9090/',
'API#www.uat.productionwebsite.com': 'https://www.uat.productionwebsite.com:9090/res/',
'API#www.productionwebsite.com': 'https://www.productionwebsite.com:9090/api/res/'
},
...
A key to the discrimination function is simply the server hostname in the request.
This, naturally, can be combined with an additional key based on the user's language settings:
...
'app': {
'NAME': 'Ferry Reservations',
'NAME#fr': 'Réservations de ferry',
'NAME#de': 'Fähren Reservierungen'
},
...
The scope of the discrimination/preference can be confined to individual keys (as above) where the "base" key is only overwritten if there's a matching key+suffix for the inputs to the function -- or an entire structure, and that structure itself recursively parsed for matching discrimination/preference suffixes:
'help': {
'BLURB': 'This pre-production environment is not supported. Contact Development Team with questions.',
'PHONE': '808-867-5309',
'EMAIL': 'coder.jen#lostnumber.com'
},
'help#www.productionwebsite.com': {
'BLURB': 'Please contact Customer Service Center',
'BLURB#fr': 'S\'il vous plaît communiquer avec notre Centre de service à la clientèle',
'BLURB#de': 'Bitte kontaktieren Sie unseren Kundendienst!!1!',
'PHONE': '1-800-CUS-TOMR',
'EMAIL': 'customer.service#productionwebsite.com'
},
SO, if a visiting user to the production website has German (de) language preference setting, the above configuration would collapse to:
'help': {
'BLURB': 'Bitte kontaktieren Sie unseren Kundendienst!!1!',
'PHONE': '1-800-CUS-TOMR',
'EMAIL': 'customer.service#productionwebsite.com'
},
What does such a magical preference/discrimination JSON-rewriting function look like? Not much:
// prefer(object,suffix|[suffixes]) by/par/durch storsoc
// prefer({ a: 'apple', a#env: 'banana', b: 'carrot' },'env') -> { a: 'banana', b: 'carrot' }
function prefer(o,sufs) {
for (var key in o) {
if (!o.hasOwnProperty(key)) continue; // skip non-instance props
if(key.split('#')[1]) { // suffixed!
// replace root prop with the suffixed prop if among prefs
if(o[key] && sufs.indexOf(key.split('#')[1]) > -1) o[key.split('#')[0]] = JSON.parse(JSON.stringify(o[key]));
// and nuke the suffixed prop to tidy up
delete o[key];
// continue with root key ...
key = key.split('#')[0];
}
// ... in case it's a collection itself, recurse it!
if(o[key] && typeof o[key] === 'object') prefer(o[key],sufs);
};
};
In our implementations, which include Angular and pre-Angular websites, we simply bootstrap the configuration well ahead of other resource calls by placing the JSON within a self-executing JS closure, including the prefer() function, and fed basic properties of hostname and language-code (and accepts any additional arbitrary suffixes you might need):
(function(prefs){ var props = {
'svcs': {
'VER': '2.3',
'API#localhost': 'http://localhost:9090/',
'API#www.uat.productionwebsite.com': 'https://www.uat.productionwebsite.com:9090/res/',
'API#www.productionwebsite.com': 'https://www.productionwebsite.com:9090/api/res/'
},
...
/* yadda yadda moar JSON und bisque */
function prefer(o,sufs) {
// body of prefer function, broken for e.g.
};
// convert string and comma-separated-string to array .. and process it
prefs = [].concat( ( prefs.split ? prefs.split(',') : prefs ) || []);
prefer(props,prefs);
window.app_props = JSON.parse(JSON.stringify(props));
})([location.hostname, ((window.navigator.userLanguage || window.navigator.language).split('-')[0]) ] );
A pre-Angular site would now have a collapsed (no # suffixed keys) window.app_props to refer to.
An Angular site, as a bootstrap/init step, simply copies the dead-dropped props object into $rootScope, and (optionally) destroys it from global/window scope
app.constant('props',angular.copy(window.app_props || {})).run( function ($rootScope,props) { $rootScope.props = props; delete window.app_props;} );
to be subsequently injected into controllers:
app.controller('CtrlApp',function($log,props){ ... } );
or referred to from bindings in views:
<span>{{ props.help.blurb }} {{ props.help.email }}</span>
Caveats? The # character is not valid JS/JSON variable/key naming, but so far accepted. If that's a deal-breaker, substitute for any convention you like, such as "__" (double underscore) as long as you stick to it.
The technique could be applied server-side, ported to Java or C# but your efficiency/compactness may vary.
Alternately, the function/convention could be part of your front-end compile script, so that the full gory all-environment/all-language JSON is never transmitted over the wire.
UPDATE
We've evolved usage of this technique to allow multiple suffixes to a key, to avoid being forced to use collections (you still can, as deeply as you want), and as well to honor the order of the preferred suffixes.
Example (also see working jsFiddle):
var o = { 'a':'apple', 'a#dev':'apple-dev', 'a#fr':'pomme',
'b':'banana', 'b#fr':'banane', 'b#dev&fr':'banane-dev',
'c':{ 'o':'c-dot-oh', 'o#fr':'c-point-oh' }, 'c#dev': { 'o':'c-dot-oh-dev', 'o#fr':'c-point-oh-dev' } };
/*1*/ prefer(o,'dev'); // { a:'apple-dev', b:'banana', c:{o:'c-dot-oh-dev'} }
/*2*/ prefer(o,'fr'); // { a:'pomme', b:'banane', c:{o:'c-point-oh'} }
/*3*/ prefer(o,'dev,fr'); // { a:'apple-dev', b:'banane-dev', c:{o:'c-point-oh-dev'} }
/*4*/ prefer(o,['fr','dev']); // { a:'pomme', b:'banane-dev', c:{o:'c-point-oh-dev'} }
/*5*/ prefer(o); // { a:'apple', b:'banana', c:{o:'c-dot-oh'} }
1/2 (basic usage) prefers '#dev' keys, discards all other suffixed keys
3 prefers '#dev' over '#fr', prefers '#dev&fr' over all others
4 (same as 3 but prefers '#fr' over '#dev')
5 no preferred suffixes, drops ALL suffixed properties
It accomplishes this by scoring each suffixed property and promoting the value of a suffixed property to the non-suffixed property when iterating over the properties and finding a higher-scored suffix.
Some efficiencies in this version, including removing dependence on JSON to deep-copy, and only recursing into objects that survive the scoring round at their depth:
function prefer(obj,suf) {
function pr(o,s) {
for (var p in o) {
if (!o.hasOwnProperty(p) || !p.split('#')[1] || p.split('##')[1] ) continue; // ignore: proto-prop OR not-suffixed OR temp prop score
var b = p.split('#')[0]; // base prop name
if(!!!o['##'+b]) o['##'+b] = 0; // +score placeholder
var ps = p.split('#')[1].split('&'); // array of property suffixes
var sc = 0; var v = 0; // reset (running)score and value
while(ps.length) {
// suffix value: index(of found suffix in prefs)^10
v = Math.floor(Math.pow(10,s.indexOf(ps.pop())));
if(!v) { sc = 0; break; } // found suf NOT in prefs, zero score (delete later)
sc += v;
}
if(sc > o['##'+b]) { o['##'+b] = sc; o[b] = o[p]; } // hi-score! promote to base prop
delete o[p];
}
for (var p in o) if(p.split('##')[1]) delete o[p]; // remove scores
for (var p in o) if(typeof o[p] === 'object') pr(o[p],s); // recurse surviving objs
}
if( typeof obj !== 'object' ) return; // validate
suf = ( (suf || suf === 0 ) && ( suf.length || suf === parseFloat(suf) ) ? suf.toString().split(',') : []); // array|string|number|comma-separated-string -> array-of-strings
pr(obj,suf.reverse());
}
If you're using Brunch, the plugin Constangular helps you to manage variables for different environments.
Have you seen this question and its answer?
You can set a globally valid value for you app like this:
app.value('key', 'value');
and then use it in your services. You could move this code to a config.js file and execute it on page load or another convenient moment.

Categories

Resources