Trying to implement accompanist pager with tabs to achieve something like instagram's page displaying followers, following and subscription - 3 tab menu with pager basically. This is the code I am using.
fun UsersPager(
myDBViewModel: MyDBViewModel
) {
val tabData = listOf(
"FOLLOWING" to Icons.Filled.PermIdentity,
"ALLUSERS" to Icons.Filled.PersonOutline,
"FOLLOWERS" to Icons.Filled.PersonOutline
)
val pagerState = rememberPagerState(
0
)
val tabIndex = pagerState.currentPage
val coroutineScope = rememberCoroutineScope()
Column {
TabRow(
selectedTabIndex = tabIndex,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier.pagerTabIndicatorOffset(pagerState, tabPositions)
)
}
) {
tabData.forEachIndexed { index, pair ->
Tab(
selected = tabIndex == index,
onClick = {
coroutineScope.launch {
Log.d("MP18", "click on Tab num: $index")
pagerState.animateScrollToPage(index)
}
},
text = {
Text(text = pair.first)
},
icon = {
Icon(imageVector = pair.second, contentDescription = null)
})
}
}
HorizontalPager(
state = pagerState,
itemSpacing = 1.dp,
modifier = Modifier
.weight(1f),
count = tabData.size
) { index ->
Column(
modifier = Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
when (index) {
1 -> ShowMyFollowees(myDBViewModel = myDBViewModel)
2 -> ShowMyUsers(myDBViewModel = myDBViewModel)
3 -> ShowMyFollowers(myDBViewModel = myDBViewModel)
}
}
}
}
}
Then 3 composables follow this pattern to fetch data from API and display them:
#Composable
fun ShowMyUsers(
myDBViewModel: MyDBViewModel,
) {
val pageLoadedTimes by myDBViewModel.pageLoadedTimes.observeAsState(initial = null)
val myUsersList by myDBViewModel.myUsersList.observeAsState(initial = emptyList())
val loading by myDBViewModel.loading.observeAsState(initial = myDBViewModel.loading.value)
if (myUsersList.isNullOrEmpty() && pageLoadedTimes == 0 && !loading!!) {
LaunchedEffect(key1 = Unit, block = {
Log.d("MP18", "launchedEffect in ScreenMyAccount.ShowMyUsers")
myDBViewModel.getFirstPageUsers()
})
}
ListMyUsers(myUsers = myUsersList, myDBViewModel = myDBViewModel)
}
#Composable
fun ListMyUsers(
myUsers: List<MyUser>,
myDBViewModel: MyDBViewModel
) {
val pageLoadedTimes by myDBViewModel.pageLoadedTimes.observeAsState(initial = myDBViewModel.pageLoadedTimes.value)
val loading by myDBViewModel.loading.observeAsState(initial = myDBViewModel.loading.value)
Log.d(
"MP18",
"comp ShowMyUsers and pageLoadedTimes is: $pageLoadedTimes and loading is: $loading"
)
Column(
modifier = Modifier
.fillMaxSize()
.background(color = Color.Red)
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp)
) {
itemsIndexed(
items = myUsers
) { index, user ->
myDBViewModel.onChangeProductScrollPosition(index)
val numRec = pageLoadedTimes?.times(PAGE_SIZE)
Log.d(
"MP188",
"in composable, page: $pageLoadedTimes, index: $index, loading: $loading, numRec: $numRec"
)
//we should query and display next page if this is true:
if ((index + 1) >= (pageLoadedTimes?.times(PAGE_SIZE)!!) && !loading!!) {
myDBViewModel.getNextPageUsers()
}
ShowSingleUser(
index = index,
pageLoadedTimes = pageLoadedTimes!!,
user = user,
myDBViewModel = myDBViewModel
)
}
}
}
}
In composables that are available, there's an API call (through ViewModel) which gets data from backend in order to populate some vars in viewModel. The problem I have is that when first tab is clicked, also the neighbouring composable gets composed and thus I'am making 2 API calls and "preparing" second tab data even if the user might never click on that tab. This is not what I want. I'd like to fetch data from tab2 and later tab3 only when there's a click on them. I hope I am clear in what's bothering me.
This is the expected behavior of the pager as the pager has been implemented by using LazyRow in accompanist pager. Basically, pager loads the second page before you scroll to it as LazyLayout is implemented in that way. If you want to cancel that you can do something like this, which I use in my code also:
// In anywhere of your composable
SideEffect {
if(currentShownItemIndex == pagerState.currentPage) {
// Make api call...
}
}
This should ensure that you are making your api call if and only if you are on the correct index
Edit: You can use Launched Effect if you want, I used SideEffect as it is easier to write and does not rely on any key and I needed a coroutine scope simply :d
Finally, this does not prevent the composition of the page in index+1 however prevents the unnecessary api call made by pager.
I found the solution for this. I added another variable in viewModel:
private val _pageInPager = MutableLiveData(0)
val pageInPager: LiveData<Int> = _pageInPager
fun setPageInPager(pageNum: Int) {
Log.d("MP188", "setPageInPager to: $pageNum")
_pageInPager.value = pageNum
}
Then in composable:
if user clicks on tab:
onClick = {
coroutineScope.launch {
Log.d("MP18", "click on Tab num: $index")
pagerState.animateScrollToPage(index)
myDBViewModel.setPageInPager(index)
}
},
or move the pager(slider):
myDBViewModel.setPageInPager(pagerState.currentPage)
I have the exact page in the variable: myDBViewModel.pageInPager, so I can add checker in LaunchedEffect before making an API call:
if (myUsersList.isNullOrEmpty() && pageLoadedTimes == 0 && !loading!! && pageInPager == 1) {
LaunchedEffect(key1 = Unit, block = {
Log.d("MP18", "launchedEffect in ScreenMyAccount.ShowMyUsers")
myDBViewModel.getFirstPageUsers()
})
I think this works ok now. Thank you #Subfly.
I'm trying to access a property of an object using a dynamic name. Is this possible?
const something = { bar: "Foobar!" };
const foo = 'bar';
something.foo; // The idea is to access something.bar, getting "Foobar!"
There are two ways to access properties of an object:
Dot notation: something.bar
Bracket notation: something['bar']
The value between the brackets can be any expression. Therefore, if the property name is stored in a variable, you have to use bracket notation:
var something = {
bar: 'foo'
};
var foo = 'bar';
// both x = something[foo] and something[foo] = x work as expected
console.log(something[foo]);
console.log(something.bar)
This is my solution:
function resolve(path, obj) {
return path.split('.').reduce(function(prev, curr) {
return prev ? prev[curr] : null
}, obj || self)
}
Usage examples:
resolve("document.body.style.width")
// or
resolve("style.width", document.body)
// or even use array indexes
// (someObject has been defined in the question)
resolve("part.0.size", someObject)
// returns null when intermediate properties are not defined:
resolve('properties.that.do.not.exist', {hello:'world'})
In javascript we can access with:
dot notation - foo.bar
square brackets - foo[someVar] or foo["string"]
But only second case allows to access properties dynamically:
var foo = { pName1 : 1, pName2 : [1, {foo : bar }, 3] , ...}
var name = "pName"
var num = 1;
foo[name + num]; // 1
// --
var a = 2;
var b = 1;
var c = "foo";
foo[name + a][b][c]; // bar
Following is an ES6 example of how you can access the property of an object using a property name that has been dynamically generated by concatenating two strings.
var suffix = " name";
var person = {
["first" + suffix]: "Nicholas",
["last" + suffix]: "Zakas"
};
console.log(person["first name"]); // "Nicholas"
console.log(person["last name"]); // "Zakas"
This is called computed property names
You can achieve this in quite a few different ways.
let foo = {
bar: 'Hello World'
};
foo.bar;
foo['bar'];
The bracket notation is specially powerful as it let's you access a property based on a variable:
let foo = {
bar: 'Hello World'
};
let prop = 'bar';
foo[prop];
This can be extended to looping over every property of an object. This can be seem redundant due to newer JavaScript constructs such as for ... of ..., but helps illustrate a use case:
let foo = {
bar: 'Hello World',
baz: 'How are you doing?',
last: 'Quite alright'
};
for (let prop in foo.getOwnPropertyNames()) {
console.log(foo[prop]);
}
Both dot and bracket notation also work as expected for nested objects:
let foo = {
bar: {
baz: 'Hello World'
}
};
foo.bar.baz;
foo['bar']['baz'];
foo.bar['baz'];
foo['bar'].baz;
Object destructuring
We could also consider object destructuring as a means to access a property in an object, but as follows:
let foo = {
bar: 'Hello World',
baz: 'How are you doing?',
last: 'Quite alright'
};
let prop = 'last';
let { bar, baz, [prop]: customName } = foo;
// bar = 'Hello World'
// baz = 'How are you doing?'
// customName = 'Quite alright'
You can do it like this using Lodash get
_.get(object, 'a[0].b.c');
UPDATED
Accessing root properties in an object is easily achieved with obj[variable], but getting nested complicates things. Not to write already written code I suggest to use lodash.get.
Example
// Accessing root property
var rootProp = 'rootPropert';
_.get(object, rootProp, defaultValue);
// Accessing nested property
var listOfNestedProperties = [var1, var2];
_.get(object, listOfNestedProperties);
Lodash get can be used in different ways, the documentation lodash.get
To access a property dynamically, simply use square brackets [] as follows:
const something = { bar: "Foobar!" };
const userInput = 'bar';
console.log(something[userInput])
The problem
There's a major gotchya in that solution! (I'm surprised other answers have not brought this up yet). Often you only want to access properties that you've put onto that object yourself, you don't want to grab inherited properties.
Here's an illustration of this issue. Here we have an innocent-looking program, but it has a subtle bug - can you spot it?
const agesOfUsers = { sam: 16, sally: 22 }
const username = prompt('Enter a username:')
if (agesOfUsers[username] !== undefined) {
console.log(`${username} is ${agesOfUsers[username]} years old`)
} else {
console.log(`${username} is not found`)
}
When prompted for a username, if you supply "toString" as a username, it'll give you the following message: "toString is function toString() { [native code] } years old". The issue is that agesOfUsers is an object, and as such, automatically inherits certain properties like .toString() from the base Object class. You can look here for a full list of properties that all objects inherit.
Solutions
Use a Map data structure instead. The stored contents of a map don't suffer from prototype issues, so they provide a clean solution to this problem.
const agesOfUsers = new Map()
agesOfUsers.set('sam', 16)
agesOfUsers.set('sally', 2)
console.log(agesOfUsers.get('sam')) // 16
Use an object with a null prototype, instead of the default prototype. You can use Object.create(null) to create such an object. This sort of object does not suffer from these prototype issues, because you've explicitly created it in a way that it does not inherit anything.
const agesOfUsers = Object.create(null)
agesOfUsers.sam = 16
agesOfUsers.sally = 22;
console.log(agesOfUsers['sam']) // 16
console.log(agesOfUsers['toString']) // undefined - toString was not inherited
You can use Object.hasOwn(yourObj, attrName) to first check if the dynamic key you wish to access is directly on the object and not inherited (learn more here). This is a relatively newer feature, so check the compatibility tables before dropping it into your code. Before Object.hasOwn(yourObj, attrName) came around, you would achieve this same effect via Object.prototype.hasOwnProperty.call(yourObj, attrName). Sometimes, you might see code using yourObj.hasOwnProperty(attrName) too, which sometimes works but it has some pitfalls that you can read about here.
// Try entering the property name "toString",
// you'll see it gets handled correctly.
const user = { name: 'sam', age: 16 }
const propName = prompt('Enter a property name:')
if (Object.hasOwn(user, propName)) {
console.log(`${propName} = ${user[propName]}`)
} else {
console.log(`${propName} is not found`)
}
If you know the key you're trying to use will never be the name of an inherited property (e.g. maybe they're numbers, or they all have the same prefix, etc), you can choose to use the original solution.
I came across a case where I thought I wanted to pass the "address" of an object property as data to another function and populate the object (with AJAX), do lookup from address array, and display in that other function. I couldn't use dot notation without doing string acrobatics so I thought an array might be nice to pass instead. I ended-up doing something different anyway, but seemed related to this post.
Here's a sample of a language file object like the one I wanted data from:
const locs = {
"audioPlayer": {
"controls": {
"start": "start",
"stop": "stop"
},
"heading": "Use controls to start and stop audio."
}
}
I wanted to be able to pass an array such as: ["audioPlayer", "controls", "stop"] to access the language text, "stop" in this case.
I created this little function that looks-up the "least specific" (first) address parameter, and reassigns the returned object to itself. Then it is ready to look-up the next-most-specific address parameter if one exists.
function getText(selectionArray, obj) {
selectionArray.forEach(key => {
obj = obj[key];
});
return obj;
}
usage:
/* returns 'stop' */
console.log(getText(["audioPlayer", "controls", "stop"], locs));
/* returns 'use controls to start and stop audio.' */
console.log(getText(["audioPlayer", "heading"], locs));
ES5 // Check Deeply Nested Variables
This simple piece of code can check for deeply nested variable / value existence without having to check each variable along the way...
var getValue = function( s, context ){
return Function.call( context || null, 'return ' + s )();
}
Ex. - a deeply nested array of objects:
a = [
{
b : [
{
a : 1,
b : [
{
c : 1,
d : 2 // we want to check for this
}
]
}
]
}
]
Instead of :
if(a && a[0] && a[0].b && a[0].b[0] && a[0].b[0].b && a[0].b[0].b[0] && a[0].b[0].b[0].d && a[0].b[0].b[0].d == 2 ) // true
We can now :
if( getValue('a[0].b[0].b[0].d') == 2 ) // true
Cheers!
Others have already mentioned 'dot' and 'square' syntaxes so I want to cover accessing functions and sending parameters in a similar fashion.
Code jsfiddle
var obj = {method:function(p1,p2,p3){console.log("method:",arguments)}}
var str = "method('p1', 'p2', 'p3');"
var match = str.match(/^\s*(\S+)\((.*)\);\s*$/);
var func = match[1]
var parameters = match[2].split(',');
for(var i = 0; i < parameters.length; ++i) {
// clean up param begninning
parameters[i] = parameters[i].replace(/^\s*['"]?/,'');
// clean up param end
parameters[i] = parameters[i].replace(/['"]?\s*$/,'');
}
obj[func](parameters); // sends parameters as array
obj[func].apply(this, parameters); // sends parameters as individual values
I asked a question that kinda duplicated on this topic a while back, and after excessive research, and seeing a lot of information missing that should be here, I feel I have something valuable to add to this older post.
Firstly I want to address that there are several ways to obtain the value of a property and store it in a dynamic Variable. The first most popular, and easiest way IMHO would be:
let properyValue = element.style['enter-a-property'];
however I rarely go this route because it doesn't work on property values assigned via style-sheets. To give you an example, I'll demonstrate with a bit of pseudo code.
let elem = document.getElementById('someDiv');
let cssProp = elem.style['width'];
Using the code example above; if the width property of the div element that was stored in the 'elem' variable was styled in a CSS style-sheet, and not styled inside of its HTML tag, you are without a doubt going to get a return value of undefined stored inside of the cssProp variable. The undefined value occurs because in-order to get the correct value, the code written inside a CSS Style-Sheet needs to be computed in-order to get the value, therefore; you must use a method that will compute the value of the property who's value lies within the style-sheet.
Henceforth the getComputedStyle() method!
function getCssProp(){
let ele = document.getElementById("test");
let cssProp = window.getComputedStyle(ele,null).getPropertyValue("width");
}
W3Schools getComputedValue Doc This gives a good example, and lets you play with it, however, this link Mozilla CSS getComputedValue doc talks about the getComputedValue function in detail, and should be read by any aspiring developer who isn't totally clear on this subject.
As a side note, the getComputedValue method only gets, it does not set. This, obviously is a major downside, however there is a method that gets from CSS style-sheets, as well as sets values, though it is not standard Javascript.
The JQuery method...
$(selector).css(property,value)
...does get, and does set. It is what I use, the only downside is you got to know JQuery, but this is honestly one of the very many good reasons that every Javascript Developer should learn JQuery, it just makes life easy, and offers methods, like this one, which is not available with standard Javascript.
Hope this helps someone!!!
For anyone looking to set the value of a nested variable, here is how to do it:
const _ = require('lodash'); //import lodash module
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// => 4
Documentation: https://lodash.com/docs/4.17.15#set
Also, documentation if you want to get a value: https://lodash.com/docs/4.17.15#get
You can do dynamically access the property of an object using the bracket notation. This would look like this obj[yourKey] however JavaScript objects are really not designed to dynamically updated or read. They are intended to be defined on initialisation.
In case you want to dynamically assign and access key value pairs you should use a map instead.
const yourKey = 'yourKey';
// initialise it with the value
const map1 = new Map([
['yourKey', 'yourValue']
]);
// initialise empty then dynamically assign
const map2 = new Map();
map2.set(yourKey, 'yourValue');
console.log(map1.get(yourKey));
console.log(map2.get(yourKey));
demo object example
let obj = {
name: {
first_name: "Bugs",
last_name: "Founder",
role: "Programmer"
}
}
dotted string key for getting the value of
let key = "name.first_name"
Function
const getValueByDottedKeys = (obj, strKey)=>{
let keys = strKey.split(".")
let value = obj[keys[0]];
for(let i=1;i<keys.length;i++){
value = value[keys[i]]
}
return value
}
Calling getValueByDottedKeys function
value = getValueByDottedKeys(obj, key)
console.log(value)
output
Bugs
const getValueByDottedKeys = (obj, strKey)=>{
let keys = strKey.split(".")
let value = obj[keys[0]];
for(let i=1;i<keys.length;i++){
value = value[keys[i]]
}
return value
}
let obj = {
name: {
first_name: "Bugs",
last_name: "Founder",
role: "Programmer"
}
}
let key = "name.first_name"
value = getValueByDottedKeys(obj, key)
console.log(value)
I bumped into the same problem, but the lodash module is limited when handling nested properties. I wrote a more general solution following the idea of a recursive descendent parser. This solution is available in the following Gist:
Recursive descent object dereferencing
Finding Object by reference without, strings,
Note make sure the object you pass in is cloned , i use cloneDeep from lodash for that
if object looks like
const obj = {data: ['an Object',{person: {name: {first:'nick', last:'gray'} }]
path looks like
const objectPath = ['data',1,'person',name','last']
then call below method and it will return the sub object by path given
const child = findObjectByPath(obj, objectPath)
alert( child) // alerts "last"
const findObjectByPath = (objectIn: any, path: any[]) => {
let obj = objectIn
for (let i = 0; i <= path.length - 1; i++) {
const item = path[i]
// keep going up to the next parent
obj = obj[item] // this is by reference
}
return obj
}
You can use getter in Javascript
getter Docs
Check inside the Object whether the property in question exists,
If it does not exist, take it from the window
const something = {
get: (n) => this.n || something.n || window[n]
};
You should use JSON.parse, take a look at https://www.w3schools.com/js/js_json_parse.asp
const obj = JSON.parse('{ "name":"John", "age":30, "city":"New York"}')
console.log(obj.name)
console.log(obj.age)
I'm creating a very simple CRUD app using Polymer js but facing some issues while editing the records.
Here is the code for add/edit:
_addTodo() {
if(this.user.id) {
let foundIndex = this.users.findIndex( x => x.id === this.user.id);
this.users[foundIndex] = this.user;
this.set('users', this.users);
console.log(this.users);
}
else {
this.user.id = Math.floor((Math.random() * 100000) + 1);
this.push('users', this.user);
}
this.user = {};
}
Although I could see the values inside the users object getting changed in the browser console but it's not getting changed in the DOM/UI.
If I'm using a static user object like below then it works:
_addTodo() {
if(this.user.id) {
var users = [
{
id: 1,
name: 'xyz',
age: 21
},
{
id: 2,
name: 'xyz123',
age: 5
}
]
this.set('users', users);
console.log(this.users);
}
else {
this.user.id = Math.floor((Math.random() * 100000) + 1);
this.push('users', this.user);
}
this.user = {};
}
Even I have used "notifyPath" instead of "set" but that is also not working.
Could anyone please suggest what I am doing wrong here for which the user object is not getting changed in DOM?
Update:
As suggested below, I'm using splice for updating the array but still it's not working.
JSfiddle - https://jsfiddle.net/ansumanmishra/8490y4q8/1/
this.users[foundIndex] = this.user;
this.set('users', this.users);
Updating the DOM takes performance. Whenever set is used, Polymer dirty checks every value in the array, but you have already set the array to it's new value so when it compares (basically, it compares with itself), Polymer wont detect any updates and therefor wont update the DOM.
You can't however do this as solution: var newUserArr = this.users, and then modify newUserArr because objects and arrays only create references to each other.
var a = [1]
var b = a
b[0] = 2
console.log(a) // gives [2]
You will only end up with the same thing as above: Polymer dirty checking the array with itself. Remove the reference with JSON.stringify, and then set the new array. I use this method all the time.
if(this.user.id) {
let foundIndex = this.users.findIndex( x => x.id === this.user.id);
// Remove references
var newUserArr = JSON.parse(JSON.stringify(this.users)));
newUserArr[foundIndex] = this.user;
this.set('users', newUserArr);
}
EDIT
However, when you want to edit something, you also create a reference from the object in the array, so when you type in your inputs, you will update the object in the existing array users.
I fiddled with your fiddle, and now it works. What I did was that I added JSON.parse(JSON.stringify()) in the method _editUser() too.
http://jsfiddle.net/c6h2hwch/
From "Set a property or subproperty by path": "calling set on an object property won't cause Polymer to pick up changes to the object's subproperties, unless the object itself changes." Note example:
// DOES NOT WORK
this.profile.name = Alex;
this.set('profile', this.profile);
You need to replace this.profile with a new profile object, or update the path of each individual member of profile.
This isn't an observable change:
this.users[foundIndex] = this.user;
this.set('users', this.users);
You're modifying the array that this.users points to (in a way Polymer can't detect) and then setting this.users to the same array—this.set('users', this.users) is the same operation as this.users = this.users.
You have a couple options. One is to use this.splice:
this.splice('users', foundIndex, 1, this.user);
This says, "remove 1 item at foundIndex and insert this.user in its place.
The other option is to create a copy of the array (with Array.prototype.slice—note that's slice, not splice) to make the change observable:
const nextUsers = this.users.slice();
nextUsers[foundIndex] = this.user;
this.users = nextUsers;
I recommend this.splice because it doesn't make Polymer do quite as much work when re-rendering e.g. a dom-repeat for the array.
I create from a json source a csv that I want to use to populate a memsql database with the help of LOAD DATA INFILE.
I have written a typescript script for the conversation and use the library json2csv.
It leaves the values for nulled entries empty though, creating a string like:
foo, bar, , barz, 11 ,
Yet I expect my output to be:
foo, bar, \N , barz, 11 , \N
for my nulled fields. Otherwise, my database will fill in different default values, such as 0 for a number that should be NULL.
I discovered myself doing:
const someEntitites.map((entity: Entity) => {
entity.foo = entity.foo === null ? '\\N' : entity.foo;
entity.bar = entity.bar === null ? '\\N' : entity.bar;
...
return entity;
}
So basically I am hardcoding my approach to my entity, and I also am prone to bug, as I might have forgotten to check a nullable property. And if I am to export another table, I have to repeat this all over again.
How can I generalize this, so I can use this on different entities where the script "discovers" the nullable fields and sets the marker accordingly?
I created a function that iterates over its own properties and sets its value to \N if the according value is null:
const handleNullCases = (record: any): any => {
for (let key in record) {
if (record.hasOwnProperty(key)) {
const value = record[key];
if (value === null) {
record[key] = "\\N";
}
}
}
return record;
};
That way I can reuse that snipplet for other entities as well:
const processedEntities = entities.map(handleNullCases);
const processedEntities2 = entities2.map(handleNullCases);
...
I find it a bit dirty, as that I just typehint for any and cast the value to a string even though it might have been declared as another type.
I'm going to assume all properties in Entity may be null. If so, this typing is a bit safer:
type Nullable<T> = {[K in keyof T]: T[K] | null};
type CSVSafe<T> = {[K in keyof T]: T[K] | '\\N'};
const handleNullCases = <E>(record: Nullable<E>): CSVSafe<E> => {
let ret = Object.assign(record) as CSVSafe<E>;
Object.keys(ret).forEach((key: keyof E) => {
if (record[key] === null) {
ret[key] = '\\N';
}
});
return ret;
};
type Entity = Nullable<{ a: number, b: string, c: boolean, d: number, e: string }>;
const entity: Entity = { a: 1, b: null, c: false, d: null, e: 'e' };
const safeEntity = handleNullCases(entity);
// type CSVSafe<{ a: number; b: string; c: boolean; d: number; e: string; }>
The handleNullCases function will take any object whose values might be null, and return a new object which is just the same except that null values have been replaced with "\\N". The output type will be a CSVSafe<> version of the Nullable<> input type.
Hope that helps.
I want to test if an object is a vector, any vector, not only a vector of a single type.
I ran a test:
var v:Vector.<int> = new Vector.<int>();
v.push(3);
v.push(1);
v.push(2);
trace(v is Array); // false
trace(v is Vector); // false
trace(v is Vector.<int>); // true
trace(v is Vector.<*>); // false
It seems that the only thing that returns true is the one which specifies the vector type, but I want to test it for ANY type.
I will need a very efficient method to compare, because getQualifiedClassName is too slow.
My current approach is:
private static function isVector(obj:Object):Boolean {
return (getQualifiedClassName(obj).indexOf('__AS3__.vec::Vector') == 0);
}
But it is 2x slower than the is operator.
I need speed because it's for a object serialization class, and it needs to be very fast.
The problem is that Vector.<*> is a different class than that used for <Number>, <int>, or <uint>. The numeric primitives have special classes for better efficiency. String and Boolean are also primitives, but unlike the numeric primitives they are detected by <*>. As a result, you need only test for the generic Vector and the 3 numeric types.
This solution is over 2 times as fast as getQualifiedClassName in the worst case where the object is either not a Vector, or is a Vector.<uint>, and 5 times faster if the object is a non-primitive base type Vector, like Vector.<Object>:
return (obj is Vector.<*>
|| obj is Vector.<Number>
|| obj is Vector.<int>
|| obj is Vector.<uint>);
Here's a simplistic test:
var moo:Vector.<uint> = new Vector.<uint>();
var timer:Timer = new Timer();
var b:Boolean;
timer.startTimer();
for (var i:int = 0; i < 1000000; i++)
{
b = (moo is Vector.<*>
|| moo is Vector.<Number>
|| moo is Vector.<int>
|| moo is Vector.<uint>);
}
logger.info(" is timer: " + timer.endTimer());
timer.startTimer();
for (i = 0; i < 1000000; i++)
{
b = (flash.utils.getQualifiedClassName(moo).indexOf('__AS3__.vec::Vector') == 0);
}
logger.info("gqcn timer: " + timer.endTimer());
[LOG] com.tcg.test: is timer: 320
[LOG] com.tcg.test: gqcn timer: 756
Change moo to Vector.<Object>:
[LOG] com.tcg.test: is timer: 158
[LOG] com.tcg.test: gqcn timer: 743
Other methods are way too inefficient, so I'm still using my approach:
private static function isVector(obj:Object):Boolean {
return (getQualifiedClassName(obj).indexOf('__AS3__.vec::Vector') == 0);
}
trace(new Array().fixed);//undefined
trace(new Object().fixed);//undefined
trace(new Vector.<Sprite>().fixed);//false
trace(new Vector.<*>().fixed);// false
If you need serialization for any kind of object, you have to iterate over all possible types anyway, so you could use a sequential approach to find your vector type:
v is ... (simple data types)
v is ... (object types that are not collections)
v is Array
v is XMLList
v is ... (all other collection types you can think of)
if none of the above is true, it must be a vector
serialize objects in the vector. If you have more than one type, it's Vector.<*>, otherwise set the vector type according to the object type of the content items.
Use
(obj as Vector.<*>) is Vector.<*>
/// Return Class of any Target
static public function getClass( Target:* ):Class
{
return getDefinitionByName ( getQualifiedClassName( Target ) ) as Class ;
}
/// Check if object is type of Vector.< * >
static public function isVector( any:* ):Boolean
{
return String( getClass( any ) ).indexOf( "[class Vector.<" ) > -1;
}
/// Get Vector < Class >
static public function getVectorType( vector:* ):Class
{
var c:String = String( getClass( vector ) );
var s:int = c.indexOf( '<' ) + 1;
var e:int = c.indexOf( '>' );
return getDefinitionByName( c.substring( s, e ) ) as Class;
}
private function getIsVector(obj:Object):Boolean
{
return String(obj.constructor).indexOf('[class Vector.<*>]') == 0;
}