In angular, I have an input field for a user's full name. And a function called splitName
which extracts the first / last name from the field. Is there a way to improve this logic?
<input type="text" name="fullName" ng-model="data.full_name" />
On the backend, on form submit I do a POST
which requires both a first_name
and last_name
in the body.
scope.splitName = function(data, fullName) {
var formData = angular.copy(data),
nameArr,
_lastName;
if (fullName) {
nameArr = fullName.split(' ');
if (nameArr.length > 2) {
_lastName = nameArr.pop();
formData.first_name = nameArr.join(' ');
formData.last_name = _lastName;
} else {
formData.first_name = nameArr[0];
formData.last_name = nameArr[nameArr.length - 1];
}
}
// remove full name as its not needed for backend
delete formData.full_name;
// return the copy
return formData;
};
Therefore, these are some examples.
scope.data = {
full_name: 'Joe Middle Name Smith',
first_name: '',
last_name: '',
}
var example = scope.splitName(scope.data, scope.data.full_name);
//- which should return
{
first_name: 'Joe Middle Name',
last_name: 'Smith'
}
-
\$\begingroup\$ It would be helpful if you could clarify what exactly you want splitName to do? Based on the name of the function, it seems to me that splitName should take in a full name(possibly including middle name) and return a JSON object with just the first name and the last name, and disregard the middle name. However based on your example and the code in splitName, that is not what is happening. Specifically if fullName contains a middle name, should that be sent to the server or disregarded completely? If you can clarify a bit what you want splitName to accomplish I can give you a better answer. \$\endgroup\$tyler.hs– tyler.hs2015年07月22日 17:10:13 +00:00Commented Jul 22, 2015 at 17:10
4 Answers 4
First of all, you should be aware that "first name" and "last name" are very tricky concepts. An example in that linked Wikipedia article is José Luis Rodríguez Zapatero. José Luis is the first name, though it's in two words. Rodríguez Zapatero are both surnames (paternal and maternal). Or you have cultures where "name" means surname first and then given name, i.e. the opposite of Western tradition. Example in the linked article is Zhang Wei, Zhang being the family name and Wei being the given name.
Heck, royalty and aristocracy don't even have regular surnames, really (or they're almost never used). They're just "of" some place; Elizabeth II of England, for example. Or take someone like Albert Maria Lamoral Miguel Johannes Gabriel, 12th Prince of Thurn and Taxis (yes, that's the guy's full "name" though it's part-title and honorific). Rarely something you need to worry about of course, but just to show that names aren't simple.
Hence the most common - and most robust - solution is to simply use two text fields: First name and last name. Trying to split things in code is nigh impossible because there's no single, standard naming scheme for everyone, everywhere.
Even Facebook, which certainly has the brains and computing power to attempt something clever, just uses two separate input fields. It's just easier.
So short answer: Don't try. Just make two inputs. Especially as the backend needs two separate values anyway.
But hey, code review:
I don't see the need to pass in the
fullName
separately. It's already in the data object.I'm not sure why
_lastName
has an underscore in it. There's (as far as I can tell) nothing special about that variable.
I'm also not sure why it's there to begin with. You can just assign toformData.last_name
directly; no need to to store it in a local variable first.There's no need for the
if (nameArr.length > 2)
branching. Given an array of exactly two words, the first branch would do the same as theelse
branch does, just via a different route. So, really, thepop
andjoin
strategy covers both cases already.... what it doesn't cover is the case where there's only 1 word. If
fullName
is just"John"
, then that will (via theelse
branch) becomeformData.first_name
. ButformData.last_name
will justundefined
. If you only use thepop
andjoin
strategy, you'd get "John" as the last name, and empty string as the first name. Either way, it's not optimal.When you split, you'll split twice on double whitespace and split on leading/trailing whitespace, too. So given a full name like
" John Smith "
, you getnameArr = ["", "John", "", "Smith", ""]
.
I believe Angular can/will automatically trim leading/trailing whitespace, though. If not, you can useString.trim
. However, doubled whitespace will remain, so split with a regular expression like/\s+/
instead.
I'd do this:
var nameArr = formData.full_name.split(/\s+/);
formData.first_name = nameArr.slice(0, -1).join(" ");
formData.last_name = nameArr.pop();
That's pretty much it. You'll still want to copy the formData
, and perhaps delete the full_name
property, but otherwise that's the meat of it. It'll still be weird if full_name
is just "John", because you'd get "John" as both first and last name, so you should probably have some more validation somewhere.
Still, though: Just use two fields.
-
1\$\begingroup\$ Registered just to upvote this answer. Never use one input for both name and surname. It does not make sense as explained in this answer. It might sometimes be useful but then you should not split the full name on the server or db which would denormalize the db and you cannot index for e.g. on surname. Tl;dr Always use 2 input fields. \$\endgroup\$Floris– Floris2015年07月25日 19:08:11 +00:00Commented Jul 25, 2015 at 19:08
In case the nameArray
is lower or equal 2, both statements do the same. The result of nameArr.pop()
is the same as nameArr[nameArr.length - 1]
. If then there is one string left nameArr.join(' ')
will return it, so in this case nameArr.join(' ') === nameArr[0]
. I would also recommend to return a new object instead of cloning the form data and manipulating the clone, because that seems a bit unnecessary and the result would be the same.
scope.splitName = function(fullName) {
var result = {};
if (fullName) {
var nameArr = fullName.split(' ');
result.last_name = nameArr.pop();
result.first_name = nameArr.join(' ');
}
return result;
};
-
1\$\begingroup\$ @Malachi. Maybe it was somewhat obscure before the answer was edited, but now it turns out that IMO this is the better solution. First it points out the useless dual processing depending on
nameArr.length
, then it is clearly the shortest one. \$\endgroup\$cFreed– cFreed2015年07月23日 22:19:31 +00:00Commented Jul 23, 2015 at 22:19
Here's my improvment:
scope.splitName = function(fullName) {
// Maybe there's a reason but you don't need to clone the data object neither pass as a parameter
var formData = {},
nameArr = fullName.split(' ');
if (nameArr.length > 2) {
formData.last_name = nameArr.pop();
formData.first_name = nameArr.join(' ');
} else {
formData.first_name = nameArr[0];
formData.last_name = nameArr[nameArr.length - 1];
}
// return the copy
return formData;
};
scope.data = {
full_name: 'Joe Middle Name Smith',
first_name: '',
last_name: '',
}
var example = scope.splitName(scope.data.full_name);
//- which should return
{
first_name: 'Joe Middle Name',
last_name: 'Smith'
}
First, you're assuming that the person has only one last_name (in many countries people has more than one last name) but this will be just a problem you'll have with your user's data.
- Do not pass data as a parameter. You don't need it because you can create a new object and then, return it
- You don't need a deep copy of the data object, you can create a new one
- It's not neccesary to delete the full_name from the object, just don't use it on your backend.
- I've saved one if in the code, so it's more readable.
Sure there is a way to make it better.
First of all, you should never use delete
to remove a variable.
Note: I used lodash.
About your code:
var parseName = function(form) {
form = form || {};
var fullName = form.full_name || '',
names = ('' + fullName).split(' ') || [],
last = names.pop(),
first = names.join(' ');
form.last = !first && last ? '' : last;
form.first = first || last;
return _.omit(form, 'full_name');
};
var form = { full_name:'john middle doe', first:'', last:'' };
form = parseName(form);
console.log(form);
BTW, don't use this method in your controller. Create a service factory such utilsFactory
and put there all your helpers :)
-
\$\begingroup\$ Thanks. This is awesome. Can you explain why you do
('' + fullName).split(...
\$\endgroup\$cuserjuicer– cuserjuicer2015年07月23日 22:03:00 +00:00Commented Jul 23, 2015 at 22:03 -
\$\begingroup\$ @cuserjuicer Looks like it's there to forcibly convert
fullName
as a string in case it isn't already. I.e. iffullName
is a number, you wouldn't be able to callsplit
on it. Personally though, I'd useString(fullName).split(...
as it's more descriptive. \$\endgroup\$Flambino– Flambino2015年07月23日 22:50:14 +00:00Commented Jul 23, 2015 at 22:50 -
\$\begingroup\$ @cuserjuicer as flambino said, it's to cast the variable. The idea is avoid any error, also it helps in case you have to write unit tests. \$\endgroup\$Nestor Britez– Nestor Britez2015年07月27日 12:34:16 +00:00Commented Jul 27, 2015 at 12:34