knockout depandent variables: how to define structure properly - javascript

I am new to Knockout, and I have the following question:
I have Ids coming from database and each Id has its corresponding Description (this is actualy enum in .NET, but I do not think this is important in this question).
For example,
a) for variable "PType": 0 - Undefined; 1 - Low Structure; 2 - Hight Structure
b) for variable "ClientType": 0 - Undefined, 1 - P Type; 2 - S Type
etc. for some other variables also
How to properly define model for this dependency?
Currently I have only Ids like
PType: ko.observable();
ClientType: ko.observable();
and I show Ids on page:
<span data-bind="text: PType"></span>
<span data-bind="text: ClientType"></span>
However, I need to have something like: PTypeDescription and ClientTypeDescription to show for User. I believe that those are somehow dependant variables, but cannot get it working.

First i 'll suppose that you already know what enums you have and when you get data via AJAX, you get enums value represented as integer not string
You can simulate enums in Javascript easily (check this article):
var PType = { 0: "Undefined", 1: "Low Structure", 2: "Hight Structure" }
var ClientType = { 0: "Undefined", 1: "P Type", 2: "S Type" }
So, your view-model can be something like:
var itemObj = {
PType: ko.observable(0);
ClientType: ko.observable(0);
property1:ko.observable('')// put here the other properties if you have more
}
To get your enum represnted as enum you write call function that takes the value("your enum key") and which enum to use("you can use inline function for that").
JsFiddle Demo
Update
Check this SO answer to another implementaion for Enums in JS, it's simple and effective

Related

How to limit the usage of "===" operator in javascript for specific object property

For example, the following two javascript object have properties include "name", "age" and "phoneNumber".
const personA = {
name: Andy,
age: "24",
phoneNumber: "28173516374"
}
const personB = {
name: "Amy",
age: 25,
phoneNumber: "+85 28173516374"
}
For the property "phoneNumber", I don't want others use it with operator "===" to compare with other string like personA.phoneNumber === personB.phoneNumber, because for the value of a phoneNumber, it should be the same either with or without a prepended country code. For other developers who don't have this knowledge background, they possibly use === to do the compare between two variable with value of a phone number and it will raise some bugs.
So I need a way to limit the usage of === on specific property. I have came up with several solutions, but they still have more or less disadvantage.
Solution 1: Transform the type of all properties which represents a phone number from string to an class PhoneNumber, this class will handle all the comparation and value formatting. However, I have to use new to create that object and should modify many places. Because this property is used everywhere in my project. After trying several times, I forgave applying this solution.
Solution2: Use a common function to compare two phone number. As I have said before, not all developers and reviewers know that function. Even if I address it in document, still have someone forget that.
Now I am trying to develop an eslint rules to limit the usage of === on specific property. Do you have other idea?
Sorry for my weak English.
If the task is to control who can read the field value and who cannot, then this can be solved in several ways.
If you can change the structure of the Object, then getters and setters are suitable for you.
MDN: Getter MDN: Setter
A simplified example solution would look like this:
{
let admin;
const personA = {
age: "24",
sex: "man",
privateName: "Jhon",
get name() {
return (admin === true ? this.privateName : "***");
},
set name(v) {
return (admin === true ? this.privateName = v : "***")
}
}
admin = false;
personA.name = "Mary"
console.log(personA.name)
admin = true;
console.log(personA.name)
personA.name = "Mary"
console.log(personA.name)
}
Instead of outputting ***, we can return an exception.
If you cannot change the structure, then the same can be done using a proxy.
MDN: Proxy
If you cannot use a proxy, then you can change the behavior of the object's properties using
Object.defineProperty. That is, set the same getters and setters on an already existing object.
MDN: Object.defineProperty

Obtaining value via $ref / self reference

I'd like to to use a reference to refer to an existing object within my JSON structure; specifically access it's keys and values.
I thought it reasonable that I should be able to refer to an existing object and then use dot notation to access said keys and values - but more likely I'm misunderstanding the technology.
var courses = {
Engl101 : {
course : "English 101",
units : 5,
req_type : false ,
req: false
},
Engl112: {
course : "English 112",
units : 3,
req_type : "prerequisite" ,
req: { "$ref": "#/courses/Engl101"}
}
}
I expected I should be able to then access my prerequisite course with
courses.Engl112.req
What I actually get appears to be a string with no access to child keys/values.
Also, I tried to access via Object.keys(courses.Engl112.req) with variations of [0] numerated indices with no real progress.
Is this feasible and thank you in advance?
If your courses are unique by name you could put
req: "English101"
You could then access the required courses by
courses[courses.Engl112.req]
With pure JavaScript, you cannot do such a thing.
But some libraries exist to support internal and external references in JSON. A famous one: json-schema-ref-parser.

Assign dynamic array to object

I want to pass following payload to the API
params[field1]: value1
params[field2]: value1
....
params[fieldN]: valueN
I have field and value coming from an object.
var params = {};
jQuery.each($scope.linkParams, function(a, b) {
params.params[a] = b; // returns undefined variable error
// I also tried other options but all result in one or another error
// Some doesn't result into an erro but doesn't get merged. See below merge requirement
});
I also wants to merge the above created object to another object with
jQuery.extend(extraParams, params);
How to achieve the rquired object?
Update
$scope.linkParams = {
field1: 'value1',
field2: 'value2',
....
};
You have two questions, so I'll address them one at a time.
(For a TL;DR, I emboldened the solution text. Hopefully the rest is worth the read, though.)
Object Serialization is Pretty Magical, but Not Quite That Magical
If I had a JS object that I instantiated like the following:
var cat = {
'meow': 'loud',
'type': 'Persian',
'sex': 'male'
}
then it is certainly true that you get attribute reference for free. That is, you can say something like cat.meow, and your runtime environment will make sense of that. However, JS will not automatically create properties of an object that you have referenced do not exist, unless you are referencing them to create them.
cat.health = 'meek' will work, but cat.ears[0] = 'pointy' will not.
var cat = {
'meow': 'loud',
'type': 'Persian',
'sex': 'male'
}
cat.health = 'meek'
alert(cat.health)
cat.ears[0] = 'pointy'
alert(cat.ears[0])
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You'll notice that the first alert happens and contains the expected value, but the second alert never comes. This is because the code fails on the line with cat.ears[0] = 'pointy', and it stops execution at that point.
This may seem to contradict what I just said, but look closely at what's happening. When we attempt to initialize the first element of cat.ears, we must reference the cat.ears property, which does not exist.
JS implementations won't assume that you want to create items up the chain eternally, which is likely by design -- if it didn't throw errors and instead just created any properties or objects that needed to exist in order for your program to by syntactically sound, many pieces of software would silently break when they failed to include required libraries. If you forgot to include JQuery, it'd just create a $, a JQuery variable, and all of the properties of those objects you reference in your code. It'd be a proper mess to debug.
In short, that's what you're -- probably accidentally -- assuming will work here. params.params is analogous to cat.ears in the above example. You may have some reason for doing this, but assuming you don't, your code should function if you simply change params.params[a] to params[a].
JQuery.extend()
Assuming that extraParams is a valid array/object, the code you have written will work once params doesn't break your code anymore, however: do note that this will modify your extraParams object. If you want a new object to contain both params and extraParams, write something more like:
var args = $.extend({}, params, extraParams)
That will modify an empty object and add in the contents of params and extraParams. See the JQuery documentation for more information.
Some manipulations and I was able to achieve the required results.
I am posting the code for further reference:
var d = {};
jQuery.each($scope.linkParams, function(a,b) {
a = "params[" + a + "]";
d[a] = b;
});
jQuery.extend(extraParams, d);

Retrieving JS' Object value without knowing it's name

This is a fairly common question here in SO, and I've looked into quite a few of them before deciding to ask this question.
I have a function, hereby called CheckObjectConsistency which receives a single parameter, an object of the following syntax:
objEntry:
{
objCheck: anotherObject,
properties: [
{
//PropertyValue: (integer,string,double,whatever), //this won't work.
PropertyName: string,
ifDefined: function,
ifUndefined: function
}
,...
]
}
What this function does is... considering the given parameter is correctly designed, it gets the objCheck contained within it (var chk = objEntry.objCheck;), It then procedes to check if it contains the properties contained in this collection.
Like this
for(x=0;x<=properties.length;x++){
if(objCheck.hasOwnProperty(properties[x].PropertyName)){
properties[x].ifDefined();
}
else{
properties[x].ifUndefined();
}
What I want is... I want to bring it to yet another level of dynamicity: Given the propositions that IfDefined and IfUndefined are functions to be called, respectively, if the currently-pointed PropertyName exists, and otherwise, I want to call these functions while providing them, as parameters, the very objCheck.PropertyName's value, so that it can be treated before returning to the user.
I'll give a usage example:
I will feed this function an object I received from an external provider (say, a foreign JSON-returning-WebService) from which I know a few properties that may or may not be defined.
For example, this object can be either:
var userData1 = {
userID : 1
userName: "JoffreyBaratheon",
cargo: "King",
age: 12,
motherID : 2,
//fatherID: 5,--Not defined
Status: Alive
}
or
var userData2 = {
userID :
userName: "Gendry",
cargo: "Forger Apprentice",
//age: 35, -- Not Defined
//motherID: 4,-- Not Defined
fatherID: 3,
Status: Alive
}
My function will receive:
var objEntry=
{
objCheck: userData1,
properties: [
{
PropertyName: "age",
ifDefined: function(val){alert("He/she has an age defined, it's "+val+" !");},
ifUndefined: function(){alert("He/she does not have an age defined, so we're assuming 20.");},
},
{
PropertyName: "fatherID",
ifDefined: function(val){alert("He/she has a known father, his ID is "+val+" !");},
ifUndefined: function(){alert("Oh, phooey, we don't (blink!blink!) know who his father is!");},
}
]
}
CheckObjectConsistency(objEntry); // Will alert twice, saying that Joffrey's age is 12, and that his father is supposedly unknown.
ifDefined will only actually work if, instead of properties[x].ifDefined();, I somehow provide it with properties[x].ifDefined(PropertyValue);. And here, at last, lies my question.
Being inside the consistency-checking-function, I only know a given property's name if it's provided. Being inside it, I can't simply call it's value, since there is no such function as properties[x].ifUndefined(properties[x].GetValueFromProperty(properties[x].PropertyName)) ,... is there?
I'm sorry. Not being a native english speaker (I'm brazilian), I can't properly express my doubts in a short way, so I prefer to take my time writing a long text, in an (hopefully not wasted) attempt to make it clearer.
If, even so, my doubt is unclear, please let me know.
I think you're looking for the bracket notation here. It allows you to provide an arbitrary value as key to access the object. Also, you know its name. You have your properties object right?
objEntry.properties.forEach(function(property){
// Check if objCheck has a property with name given by PropertyName
if(!objEntry.objCheck.hasOwnProperty(property.PropertyName)){
// If it doesn't, call isUndefined
property.isUndefined();
} else {
// If it does, call isDefined and passing it the value
// Note the bracket notation, allowing us to provide an arbitrary key
// provided by a variable value to access objCheck which in this case is
// the value of PropertyName
property.isDefined(objEntry.objCheck[property.PropertyName]);
}
});
Oh yeah, forEach is a method of arrays which allows you to loop over them. You can still do the same with regular loops though.

AngularJS - complex filtering based on categories etc

I've done some google-fu but all I can find about AngularJS filters is simple examples about simple filters (mostly on a single field's value).
What I'm after thoguh is somewhat more complex, and I kinda look for help on how to tackle my situation.
Imagine you have an array of the following JSON objects:
{
"id":"1",
"title":"Title",
"categories":[
{"id":"14","name":"DIY"}
],
"topics":[
{"id":"12","name":"Junk Food"}
]
},
{
"id":"2",
"title":"Title 2",
"categories":[
{"id":"4","name":"Test"},
{"id":"14","name":"DIY"},
],
"topics":[
{"id":"2","name":"Food"}
]
}
[...]
so basically each object can have ANY number of "categories" and / or "topics".
Now, my goal is to create a frontend interface that allows me to cumulatively apply various kinds of filters to those JSON objects.
For example, I'd like to say: show only the entries that have category.id = 14 AND topic.id = 2 [etc] and still support deep-linking for the filtered results.
So here's where I'm stuck:
1) what's the best way to use routes for this (ie how would you structure the URLs to support ANY number of filter (based on different values)
2) how should i keep track of the filters added? (ie, how many and which filters have been selected by the user)
Looking at the documentation for the AngularJS filters I'll obviously use the 2nd example for the filtering parameter:
Object: A pattern object can be used to filter specific properties on objects contained by array. For example {name:"M", phone:"1"} predicate will return an array of items which have property name containing "M" and property phone containing "1". A special property name $ can be used (as in {$:"text"}) to accept a match against any property of the object. That's equivalent to the simple substring match with a string as described above.
But I'm not so sure on how to make sure i'm checking the right field (ie topic.id for topics vs category.id for categories)...
Simply put, I'd love to see an example for such a less-trivial filtering scenario.
I think you need something like this instead. See his 'other simple alternative'. I do complex filtering in a service that's injected into my controller, and expose the filtered list on my $scope to the View. I only use Angular filters for relatively simple tasks.
Re: the question about how to expose this on the URL, you'll need some way of representing those filters as strings, and can use $location and $routeParams to populate them into your controller.
This can work if you write a custom filter:
var module = angular.module('app', []);
module.filter("property", ["$filter", function($filter){
var parseString = function(input){
return input.split(".");
}
function getValue(element, propertyArray) {
var value = element;
angular.forEach(propertyArray, function(property) {
value = value[property];
});
return value;
}
return function (array, propertyString, target) {
var properties = parseString(propertyString);
return $filter('filter')(array, function(item){
return getValue(item, properties) == target;
});
}
}]);
HTML part can look like this:
<ul>
<li ng-repeat="data in items | property:'categories.id':<id_of_a_category_we_want>">
{{ data }}
</li>
</ul>
Credit: OnOFF-Switch blog

Categories

Resources