Hi, I'm trying to make my own Pokédex site, but sinds I'm not very good at databases, SQL, PHP and such, I'm doing it in JavaScript, JSON style. While doing this I bumped into a problem: I wanted to refer gen1.gps.Route_1.Exits.South to gen1.gps.Pallet_Town.Title but I didn't know how. Of course I searched the internet for a while first, to find a solution, but I didn't understand it in the context.
var gen1 = {
'gps': {
'Route_1': {
'Title': 'Route 1',
'Exits': {
'North': 0,
'South': // pallet town link here
},
},
'Pallet_Town': {
'Title': 'Pallet Town',
'Exits': {
'North': /* route 1 here */,
'South': 0
}
}
}
}
I expect to be able to call the gen1.gps.Route_1.Exits.South object to get the Title object of gen1.gps.Pallet_Town.
I haven't debugged anything yet so I have no idea what my errors are or will be.
I hope somebody can help me by posting a useful answer.
Need to implement some kind of id system. MySQL takes care of this for you, but like you said, you don't want to have to learn new languages just to see something come to life.
I added IDs to the routes, and then wrote a function to find routes by their IDs. The bottom bit of code (Object.size()) is to get the total number of routes in your gps object. (Same as array.length)
var gen1 = {
'gps': {
'Route_1': {
'Id': 1,
'Title': 'Route 1',
'Exits': {
'North': 0,
'South': 2
},
},
'Pallet_Town': {
'Id': 2,
'Title': 'Pallet Town',
'Exits': {
'North': 1,
'South': 0
}
}
}
};
function getRoute (id)
{
for (var i = 0; i < gen1.gps.size(); i++)
{
if (gen1.gps[i].id == id)
return gen1.gps[i];
}
return false;
}
Object.size = function (obj)
{
var size = 0, key;
for (key in obj)
if (obj.hasOwnProperty (key)) size++;
return size;
};
You can save a lot of work if you create your object dynamically, like this:
var gen1 = {
gps: {}
};
function checkPlace(a) {
if (!gen1.gps[a]) gen1.gps[a] = {};
if (!gen1.gps[a].Title) gen1.gps[a].Title = a.split("_").join(" ");
if (!gen1.gps[a].Exits) gen1.gps[a].Exits = {};
}
function connect(a, b, dir) {
checkPlace(a);
checkPlace(b);
if (dir == "ns") {
gen1.gps[a].Exits.South = gen1.gps[b];
gen1.gps[b].Exits.North = gen1.gps[a];
}
if (dir == "we") {
gen1.gps[a].Exits.East = gen1.gps[b];
gen1.gps[b].Exits.West = gen1.gps[a];
}
}
connect("Route_1", "Pallet_Town", "ns");
console.log(gen1.gps.Route_1.Exits.South.Title);
console.log(gen1.gps);
Related
My idea is to build an application that generates sudoku's and allows users to fill them in. I'm using Node and Mongo to do so. For generating the sudoku, I have imported my 'sudoku.js' into a route function. The sudoku generation in 'sudoku.js' runs fine only when I run it by itself, but not in the route.
In short, the sudoku generator picks a random number, checks whether it has already been used in the row/column/block, and if not, adds it to the array. If it has been used, the function is re-ran, until it does 'discover' a correct sudoku. The function should return an array consisting of nine arrays, each with 9 numbers.
It appears to go south when the function genSud() is called within itself. The function, as it is now, returns 'undefined'. When I comment out the function call within the function, it does return an array, but those are almost always unfinished sudoku's. If I leave out the return statement, which I think the issue is related to, it will just keep on rerunning the function until it hits the stack limit.
const createSudoku = {
getRandomNumber: function(array) {
return array[Math.floor(Math.random() * array.length)];
},
checkIfIn: function(array, blockNumber, blockAvailable, columnNumber, columnAvailable) {
let availableNumbers = [];
array.forEach(function(element) {
if (blockAvailable[blockNumber].includes(element) && columnAvailable[columnNumber].includes(element)) {
availableNumbers.push(element);
}
});
if (availableNumbers.length === 0) {
return false;
};
return availableNumbers;
},
genSud: function(callback) {
let availableNumbers = [];
let goodLines;
let blockAvailable = [ [1,2,3,4,5,6,7,8,9], [1,2,3,4,5,6,7,8,9], etc, etc ]
let columnAvailable = [ [1,2,3,4,5,6,7,8,9], etc, etc ]
let rowAvailable = [ [1,2,3,4,5,6,7,8,9], etc, etc ]
let blockNumber;
let randomNumber;
for (var i = 0; i < 9; i++) {
for (var j = 0; j < 9; j++) {
blockNumber = Math.floor(j / 3) + 3 * Math.floor(i / 3);
availableNumbers = this.checkIfIn(rowAvailable[i], blockNumber, blockAvailable, j, columnAvailable);
if (availableNumbers == false) {
this.genSud(callback);
return;
}
randomNumber = this.getRandomNumber(availableNumbers);
rowAvailable[i].splice(rowAvailable[i].indexOf(randomNumber), 1);
columnAvailable[j].splice(columnAvailable[j].indexOf(randomNumber), 1);
blockAvailable[blockNumber].splice(blockAvailable[blockNumber].indexOf(randomNumber), 1);
body[i].push(randomNumber);
}
}
callback(body);
}
}
// createSudoku.genSud();
module.exports = createSudoku;
Then, in my route:
var sudoku = require('../sudoku.js');
var completeSudoku = sudoku.genSud(function(result) {
return result;
});
I'm aware I could abandon the rerunning altogether by replacing numbers etc., but for now it's fast enough by itself. Also, I know I could store a bunch of sudoku's in a database and retrieve them, but I like the idea of generating them on the spot.
Thanks in advance!
Edit: I have created a CodePen here:
https://codepen.io/anon/pen/OjmGMy?editors=0000
You can run it in the console using:
createSudoku.genSud();
I have made it work by removing the callback and using #nstraub 's suggested edit. Changed:
if (availableNumbers == false) {
this.genSud();
return;
}
to
if (availableNumbers == false) {
return this.genSud();
}
I'm not sure why this works, but it does solve the issue.
I'm attempting to dynamically build a JavaScript object from a string path recursively. The end goal is to have an object that is dynamically generated on some event.
Here's what I have so far, but I can't figure out why it's not working:
jsfiddle link: https://jsfiddle.net/1Lo7uart/
HTML
<input type="text" data-path="/foo/bar/ni">
<input type="text" data-path="/foo/bar/ni">
<input type="text" data-path="/foo/bar/san">
...
JavaScript
var storage = {};
var fields = $('.fields');
fields.find('input').each(function() {
var field = $(this);
field.change(function(event) {
var currentField = $(this),
currentFieldPath = currentField.attr('data-path').split('/').slice(1),
currentFieldValue = currentField.val();
function bindData(path, val, store) {
if (store) {
if (store.hasOwnProperty(path[0])) {
if (path.length === 1) {
store[path[0]] = val;
} else {
path.shift();
bindData(path, val, store[path[0]]);
}
} else {
if (path.length === 1) {
store[path[0]] = val;
} else {
store[path[0]] = {};
var annex = path.shift();
bindData(path, val, store[annex]);
}
}
}
}
bindData(currentFieldPath, currentFieldValue, storage);
});
});
...
Ideally, this should result in something like;
{
foo: {
bar: {
ni: 'some value, that'
s been over written by the second input ',
san: 'some value here'
}
...
}
}
The recursion works the first time around, but not ever after, haha. Any thoughts?
You were using the wrong object path (store[path[0]) in your code (line 18 of the jsfiddle.
Use this:
} else {
var ann = path.shift();
//console.log('path', store, path[0], ann, //store[path[0]], store[ann][path[0]])
console.log('path', path, val)
bindData(path, val, store[[ann]]);
}
instead of :
} else {
path.shift();
bindData(path, val, store[path[0]]);
}
Your path is an array of ['foo', 'bar', 'blablah'], so shifting removes 'foo' from the array. But you are trying to store your inputs at store['foo']['bar'], so doing store[path[0]] is trying to access an object at store['bar'] which doesn't exist.
Here is a working version
https://jsfiddle.net/j87m4z71/
Explanation:
As a personal project, I'm trying to create my own lightweight version of Dependency Injection for JavaScript - Some would probably disagree with calling this DI because it has no interfaces, but I arrived at the conclusion that interfaces were overkill in JS since we can so easily type check. I have looked at the source of Angular, but I just feel like the complexity there may be overkill for my projects, and I'm interested in attempting my own for a learning experience anyway.
Question:
My question is, fundamentally, is the syntax I'm trying to implement impossible or not?
I'll explain my goal for the syntax, then provide the error and code snippet, and below that I'll post the full code.
Goal for Syntax:
I'd like the creation of a component, and injection of dependencies to work like this, where everything is a component, and anything can be a dependency. I created scope with a string path, using "/scopeName/subScopeName:componentName" to select a scope, so that code users can select the scope while defining the component in a simple way, using a ":" to select a component from the scope.
var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');
/* ...snip - see full code for the process component - snip ... */
JHTML.addComponent('/generate:init', function (jsonInput, process) {
var html = process(jsonInput);
return html;
}).inject([null, '/generate:process']);
The inject function just takes an array of component paths in the order the component's arguments are expected. null can be used to skip, allowing direct argument input instead, as shown above.
I also have something I call hooks, which are components stored in a certain place, and then there's a function returnUserHandle which will return an object consisting of just the hooks, so all of the functions are hidden in closures, and you can feed the code user just the usable methods, clean and easy, and can produce the final product as a library without the wiring, no need for my DI framework as a dependency. Hopefully that makes sense.
Error:
Right now, running the code (which is a very simple library to generate HTML by parsing a JSON structure) I get the error that process is undefined in the line var html = process(jsonInput);. I was having trouble understanding whether this is a fundamental design problem, or just a bug. Maybe this syntax is not possible, I'm hoping you can tell me.
Code:
Here's the code, and a link to the JS Bin.
/* Dependency Injection Framework - viziion.js */
function Viziion(appName) {
if (typeof appName == 'string') {
var that = this;
this.name = appName;
this.focus = null;
this.scope = {
'/': {
'subScopes': {},
'components': {}
}
};
this.hooks = {};
this.addScope = function(scopeName) {
if (typeof scopeName == 'string') {
var scopeArray = scopeName.split('/');
var scope = that.scope['/'];
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
scope.subScopes[scopeArray[i]] = {
'subScopes': {},
'components': {}
};
}
}
}
} else {
throw 'Scope path must be a string.';
}
return that;
};
this.addComponent = function(componentName, func) {
if (typeof componentName == 'string') {
var scopeArray = componentName.split(':');
if (scopeArray.length == 2) {
var scope = that.scope['/'];
var scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if ((i + 1) === scopeArray.length) {
scope.components[scopeName] = func;
that.focus = scope.components[scopeName];
} else if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
throw 'Scope path is invalid.';
}
}
}
} else {
throw 'Path does not include a component.';
}
} else {
throw 'Component path must be a string1.';
}
return that;
};
this.returnComponent = function(componentName, callback) {
if (typeof componentName == 'string') {
var scopeArray = componentName.split(':');
if (scopeArray.length == 2) {
var scope = that.scope['/'];
var scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if ((i + 1) === scopeArray.length) {
//console.log('yep1');
//console.log(scope.components[scopeName]);
callback(scope.components[scopeName]);
} else if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
throw 'Scope path is invalid.';
}
}
}
} else {
throw 'Path does not include a component.';
}
} else {
throw 'Component path must be a string2.';
}
};
this.addHook = function(hookName, func) {
if (typeof hookName == 'string') {
that.hooks[hookName] = func;
that.focus = that.hooks[hookName];
} else {
throw 'Hook name must be a string.';
}
return that;
};
this.inject = function(dependencyArray) {
if (dependencyArray) {
var args = [];
for (var i = 0; i < dependencyArray.length; i++) {
if (dependencyArray[i] !== null) {
that.returnComponent(dependencyArray[i], function(dependency) {
args.push(dependency);
});
}
}
console.log(that.focus);
that.focus.apply(null, args);
return that;
}
};
this.returnUserHandle = function() {
return that.hooks;
};
} else {
throw 'Viziion name must be a string.';
}
}
/* JSON HTML Generator - A Simple Library Using Viziion */
var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');
JHTML.addComponent('/generate:process', function(children) {
var html = [];
var loop = function() {
for (var i = 0; i < children.length; i++) {
if (children[i].tag) {
html.push('<' + tag + '>');
if (children[i].children) {
loop();
}
html.push('</' + tag + '>');
return html;
} else {
throw '[JHTML] Bad syntax: Tag type is not defined on node.';
}
}
};
}).inject();
JHTML.addComponent('/generate:init', function(jsonInput, process) {
console.log(process);
var html = process(jsonInput);
return html;
}).inject([null, '/generate:process']);
JHTML.addHook('generate', function(jsonInput, init) {
var html = init(jsonInput);
return html;
}).inject([null, '/generate:init']);
handle = JHTML.returnUserHandle();
/* HTML Generator Syntax - Client */
var htmlChunk = [{
tag: '!DOCTYPEHTML'
}, {
tag: 'html',
children: [{
tag: 'head',
children: []
}, {
tag: 'body',
children: []
}]
}];
console.log(handle.generate(htmlChunk));
is the syntax I'm trying to implement impossible or not?
It's absolutely possible, and I'm sure with a bit of bugfixing it'd work just fine.
What you're describing is essentially the same as Asynchronous Module Definition (AMD) which is used extensively for handling code dependencies.
Rather than continuing to pursue your own version of the same concept, I recommend that you give requirejs a try and follow the existing standards with your projects.
I've been working a project that allows a user to manage Option Types and Options. Basically user can add a new Option Type, let's say they name it Color and then they add the options - Black, Red, Purple, etc. When the collection first loads up the existing records, an empty option should be added at the end
When a user starts typing in the text field, I want to add a new empty option , thereby always giving the user a new field to work with.
I have this almost working, but can't figure how to properly add new empty option to a new Option Type or to existing option types. The push method keeps crashing Plunkr. Any input is appreciated, short sample review of the plunkr is below
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.optionTypeId = 0;
$scope.productOptionId = 0;
$scope.productEditorModel = {
"ProductOptions": [0],
"OptionTypes": [0]
};
$scope.productEditorModel.optionTypeName = '';
$scope.addEmptyOption = function (optionTypeId) {
var emptyOption = { ProductOptionId: 3, ProductId: '1066', OptionTypeId: 1, OptionValue: '', Price: '', IsStocked: true };
console.log(emptyOption);
//$scope.productEditorModel.ProductOptions.push(emptyOption);
};
$scope.loadData = function () {
$scope.productEditorModel.OptionTypes = [{ OptionTypeId: 1, OptionName: 'Color' },{ OptionTypeId: 2, OptionName: 'Size' },];
$scope.productEditorModel.ProductOptions = [{ ProductOptionId: 1, ProductId: '1066', OptionTypeId: 2, OptionValue: 'Medium', Price: '', IsStocked: true, },{ ProductOptionId: 2, ProductId: '1066', OptionTypeId: 1, OptionValue: 'Black', Price: '', IsStocked: true }];
angular.forEach($scope.productEditorModel.ProductOptions, function (item) {
//console.log(item.OptionTypeId);
$scope.addEmptyOption(item.OptionTypeId);
});
};
$scope.loadData();
$scope.removeOption = function (option) {
var index = $scope.productEditorModel.ProductOptions.indexOf(option);
$scope.productEditorModel.ProductOptions.splice(index, 1);
};
$scope.filterEmptyElements = function (optionTypeId) {
$scope.emptyElements = $.grep($scope.productEditorModel.ProductOptions, function (k) { return k.OptionValue === "" || angular.isUndefined(k.OptionValue) && k.OptionTypeId == optionTypeId });
};
$scope.update = function (option, index) {
var optionTypeId = option.OptionTypeId;
$scope.filterEmptyElements(optionTypeId);
if (!angular.isUndefined(option.OptionValue) && $scope.emptyElements.length == 1 && option.OptionValue.length > 0) {
$scope.addOption(optionTypeId);
} else if (angular.isUndefined(option.OptionValue)) {
$scope.removeOption(option);
}
};
$scope.addOptionType = function () {
var optionTypeId = --$scope.optionTypeId;
var optionName = $scope.productEditorModel.optionTypeName;
var newOptionType = { OptionTypeId: optionTypeId, OptionName: optionName };
$scope.productEditorModel.OptionTypes.push(newOptionType);
$scope.addEmptyOption(optionTypeId);
};
$scope.editOptionType = function (optionType) {
$scope.editing = true;
};
$scope.saveOptionType = function (optionType) {
$scope.editing = false;
};
$scope.trackOptionTypesCount = function () {
if ($scope.productEditorModel.OptionTypes.length == 3) {
$scope.isMaxOptionTypes = true;
} else {
$scope.isMaxOptionTypes = false;
}
};
$scope.removeOptionType = function (optionType) {
var index = $scope.productEditorModel.OptionTypes.indexOf(optionType);
$scope.productEditorModel.OptionTypes.splice(index, 1);
$scope.trackOptionTypesCount();
};
});
See the plunker below:
http://plnkr.co/edit/YHLtSwQWVb2swhNVTQzU?p=info
The error you get that $ is not defined is because you haven't included jQuery. You don't need jQuery for this though, array.map should be able to perform the same functionality.
$scope.emptyElements = $scope.productEditorModel.ProductOptions.map(function (k) {
return k.OptionValue === "" || angular.isUndefined(k.OptionValue) && k.OptionTypeId == optionTypeId
});
And it crashes because inside $scope.loadData you have
angular.forEach($scope.productEditorModel.ProductOptions, function (item) {
$scope.addEmptyOption(item.OptionTypeId);
});
and then inside $scope.addEmptyOption you try to
$scope.productEditorModel.ProductOptions.push(emptyOption);
So the foreach will loop for each item in $scope.productEditorModel.ProductOptions, which you keep adding options to so....? Infinite loop.
Non-crashing version: http://plnkr.co/edit/5Sc2sWfhKBs9kLCk83f1?p=preview
What you really should do though is look over your data structure. Make the ProductOptions a sub-object of OptionTypes and just rename it Options. Remove ALL code about creating id's here in your GUI, that should be handled by the backend. Instead in the GUI there should be a Sortorder property on the Options (which also of course gets stored by the backend). Then when you store, the ones without an id get inserted, the ones with an id get updated. Much easier to handle everything that way.
I'd also break out optionTypes and options to their own services/providers. Much easier to keep track of what needs to be done. And each just basically contains add, remove and maybe a find/getJSON or something.
Here's a restructured version. Much easier to keep track of what belongs where. And it has more features than the original with less code. http://plnkr.co/edit/BHcu6vAfcpEYQpZKHc5G?p=preview
I was looking at Twitter's static scripts and noticed that all variables and functions where just 1 character long, why and how do they do this? Has it something to do with performance? If so, why don't they give all elements on their website these kind of short names, maybe 2 characters long instead of 1 to avoid any collisions.
Example:
(function (A) {
A.fn.isScreenNameField = function () {
return this.each(function () {
var M = A(this);
var F = A("#signup_username_url");
var E = A("#screen_name_info");
var D = A("#avail_screenname_check_indicator");
var O;
var C;
var I;
var N = M.val();
var G = N;
var H = N != "";
var Q = /[a-zA-Z0-9_]/;
function K() {
if (H) {
F.html(M.val())
}
}
function L() {
M.trigger("show-info");
E.hide();
D.show()
}
function B() {
E.show();
D.hide()
}
function P() {
G = O;
jQuery.ajax({
type: "GET",
url: "/users/username_available",
data: {
username: O
},
dataType: "json",
success: function (R) {
if (C) {
var S = R.msg;
if (R.valid) {
M.trigger("is-valid");
F.removeClass("invalid").addClass("valid")
} else {
M.trigger("is-invalid", R.msg);
F.addClass("invalid").removeClass("valid")
}
}
},
beforeSend: null,
complete: function () {
clearTimeout(twttr.timeouts.availabilityTimeout);
B()
}
})
}
function J(R) {
O = M.val();
clearTimeout(twttr.timeouts.availabilityTimeout);
C = O.match(Q);
if (!C) {
G = O;
B();
return
}
if (O == G) {
return
}
L();
twttr.timeouts.availabilityTimeout = setTimeout(P, 2000)
}
M.isSignupFormField({
validateWith: function (R) {
if (isBlank(R)) {
return _("Please enter a user name")
} else {
P()
}
},
allowInput: Q
});
M.keyup(function (R) {
if (jQuery.inArray(R.keyCode, [16, 17, 18, 20, 27, 33, 34, 35, 37, 38, 39, 40, 144]) == -1) {
if (M.val() != "") {
H = true
} else {
M.trigger("show-info")
}
K();
J()
}
});
M.bind("value-changed", P);
M.bind("custom-validate", P)
})P
}
})
This script has been "minified", an automated technique of replacing variables with shorter names, without changing functionality. See JSMin, for example. The goal is to reduce download times and bandwidth when sending the script to a client.
They run their scripts through something like http://developer.yahoo.com/yui/compressor/ in order to reduce their size, and therefore the time the need to load.
This is all done in order to decrease the websites load times.
In recent years this topic has become something like a new field, especially due to things like the talks from Steve Stouders: http://stevesouders.com/
Javascript is client-side, so you have to load the script. Less text to download mean better performance, I'd think.
Many javascript projects run their code through a 'minifier' to make the code smaller. This improves the time the browser takes to download the library. Most projects also supply a non-minified version for developers to read:
Example here:
http://docs.jquery.com/Downloading_jQuery#Current_Release
Could be many reasons as to why they do this, to name a common one:
Decrease the filesize of the scripts as alot of people use twitter.