如何优雅的干掉VF的autofocus

How to remove VF input field’s autofocus?
最近在写StandardSetController Demo的时候遇到了一个逼死完美主义者的问题。
在VF加载之后,SFDC会非常Nice的,热心的,把第一个文本输入框设为焦点。
我推测sfdc的产品设计逻辑是下面这样的。
嗯。。。你设置一个输入框,代表着用户一定得输入点什么才能继续。
既然用户必须输入点什么的话,那么就必须用鼠标点一下这个输入框。
那么让我们来做点什么吧,帮用户先把输入框点上怎么样,节省用户的操作。
看呐,如果用户双手都在键盘上,他们连右手离开键盘的工夫都省下来了,用户一定会非常满意的。

对于大部分业务场景,这个贴心的小设计确实很有用。但有的时候会起反作用。
比如我的StandardSetController的Demo。
在List View画面,一般只有在数据列表最下方才有输入框,为了输入Page Size或者Page No。
但是不碰这几个输入框并不影响我使用List View。
不过,当默认Page Size非常大,画面超出一屏的时候,由于sfdc自动把第一个输入框设成焦点,导致画面滚动条直接被拖到了滚动条最下面。
还有当VF的第一个input项目是Date型的时候,sfdc的自动设置焦点机制,会触发datepicker,就像大晴天自动弹开的雨伞一样,造成另一场灾难。

研究了半天,虽然标准List View并没有这个问题,但还是没有找到比较官方的解决方法。
只好Hack一下了。

在VF中插入

   <script>
        function setFocusOnLoad() {}
    </script>

问题完美解决。

Salesforce的Custom Permission

Salesforce的权限体系我相信大家已经很熟了。
数据级别权限,表级别权限,字段级别权限,功能权限,分别坐落在Profile/Permission Set和role/Sharing rule当中,还有一些是作为Feature License放在了User上。

可是对于自定义功能,SFDC并没有提供太好的控制方式。
逼的广大开发人员不得不开各种脑洞。
比如在人事系统里,HR和领导能看见员工评价,普通员工看不到员工评价,
这用Profile的FLS简简单单地就能控制了。
但是在所有人都能看到的VF上,假如有个员工评价Section不想给普通员工看的话,就不好办了。
在VF里可以判断当前User的Profile,判断一下当前用户的Profile在不在允许列表里不就行了嘛。
好家伙,一打听,500个Profile能看。这不行啊,还是看看谁不能看吧,呦呵,800个Profile。
开发人员就开脑洞了,我把能看的Profile的末尾加一个“VIP”,不能看的都没有。接着就动手把500多个Profile一个一个的改了名字。
然后在VF里判断User的Profile结尾是VIP的才显示。
妥了,实现,下班。
第二天,经理过来搂住你的肩旁,说,昨天那个功能实现的很好,今天还要再加一个Section,和昨天的差不多,不过普通员工Level里的组长可以也可以看。开发人员想了想,总不能再给Profile加一个后缀吧。就算这次可以,那以后再来一个怎么办。。。。。
后来,听说开发人员被120拉走了。

像这种自定义功能的权限控制,SFDC提供了Custom Permission,作为Profile与Permission Set向自定义功能领域的延伸。
说白了,就是Profile与Permission Set原本能控制的都是标准功能,Custom Permission就是一个插件/拓展包,赋予了Profile与Permission Set控制自定义功能的能力。

所以遇见上面的场景,我们只需要针对每一个Section建一个Custom Permission,
然后将这些Custom Permission挂到需要此权限的Profile或者Permission Set,
然后在VF中写上Custom Permission的判断。
如下方官方文档提供的例子一样

<apex:pageBlock rendered="{!$Permission.[Your Custom Permission API Name]}">
   <!-- Confidential Content Here -->
</apex:pageBlock>

在Apex中想用Custom Permission控制业务逻辑的话,
就要使用下面的方法,官方文档

if(FeatureManagement.checkPermission([Your Custom Permission API Name])) {
    // Your Codes Here
}

这样一来,想控制一个群体,就挂在Profile上,想控制个别人,就挂在Permission Set上。
为什么叫“挂在”呢,因为只有在Profile和Permission Set下面才有添加Custom Permission的按钮。

那么,想创建一个Custom Permission,在哪创建呢?
第一种方式是官方文档常写的 —– 点击Setup,然后左上方Quick Find,输入Custom Permissions。
第二种方式是按照本博客的风格 —– Setup -> Build -> Develop -> Custom Permissions。
Custom Permission本身需要定义的内容倒是不多,就一个Label一个API Name是必须的,其他都可有可无。

虽然Custom Permission只是Profile与Permission Set的功能拓展,
但由于在VF中官方提供了方便的检查方式($Permission),倒是给我们提供了另外一个用途,
正所谓,有心栽花花不开,无心插柳柳成荫。

继续阅读“Salesforce的Custom Permission”

不小心Commit账户密码到本地仓库,并且push到了remote仓库怎么办

今天我就做了这个傻事。
不过还好是私有仓库,没有酿成泄密事件。

那么这种情况该怎么处理呢?
首先,把账户密码改掉,再Commit一次是肯定不行的。
看worktree,账户密码确实是没了,但是在Commit记录里仍然能看到的。

别急,Stackoverflow上我找到了一个简单粗暴的做法,直接抹掉全部历史!
https://stackoverflow.com/questions/13716658/how-to-delete-all-commit-history-in-github

首先你需要一个git命令行工具,Eclipse自带的git插件是玩具。

1.创建一个独立(孤儿)的分支,并Checkout

git checkout –orphan latest_branch

2.将目前的文件全部add到此分支

git add -A

3.全部Commit到孤儿分支

git commit -am “commit message”

4.删除带有历史信息的Master分支

git branch -D master

5.将没有任何历史的孤儿分支重命名为Master

git branch -m master

6.强制推送到Remote仓库

git push -f origin master

代价就是所有历史都没有了。
当然,我们有更精细的方式,可以剔除包含敏感信息的commit

关于example.com

每当刷完Sandbox,Salesforce为了防止从Sandbox发出的邮件使用户混肴,
所以将所有User的邮箱的@替换为=,然后加上@example.com
比如,User A的邮箱为[email protected],则变换为[email protected]

如果向这种邮箱发出邮件,邮件会发到哪去呢?

有同学说了,这个邮箱肯定不存在的,所以谁也收不到。
这可未必。如果了解SMTP服务器的话,就知道有种catch all账号,可以收到所有发往此邮件服务器的邮件,无论收件地址是否存在。
所以用公司邮箱乱发东西的同学们注意了,千万别用公司邮箱乱说话呦。

所以我们就来看看这个example.com
首先用浏览器访问,还真的能直接访问,白白的背景上,正中间方方正正的写着

Example Domain

This domain is established to be used for illustrative examples in documents. You may use this domain in examples without prior coordination or asking for permission.

More information…

大概意思是说,这个域名就是给你在文档里做说明示例用的。随便用,不用经过我同意。
呦!谁这么大方?

顺手Whois了一下这个example.com
注册商是RESERVED-Internet Assigned Numbers Authority

这个Internet Assigned Numbers Authority是哪路神仙?
原来是大名鼎鼎的IANA(互联网号码分配局)。
什么?没听过,那么ICANN总听过吧,
Internet Corporation for Assigned Names and Numbers,互联网名称与数字地址分配机构。
如果购买域名的话,每年都要向ICANN交管理费的。
这个IANA就是ICANN的下属机构。
这就是说,这个域名居然还是官方所有的。

随后,点了一下页面上的More information…
了解到了整个事情的始末。

原来根据RFC2606
一级域名
.test
.example
.invalid
.localhost
二级域名
example.com
example.net
example.org
都为特殊目的所保留。

RFC6761则规定了具体的做法。

// 小插曲,很Interesting的是,RFC2606的作者是IBM雇员 Donald E. Eastlake 3rd与Aliza R. Panitz, 而RFC6761的作者为Apple Inc.的雇员Stuart Cheshire与Apple Inc.的雇员Marc Krochmal

网页中明确的说明了Certain domains are set aside, and nominally registered to “IANA”, for specific policy or technical purposes.
甚至提供了IDN的保留域名。

就像好莱坞电影里以555区号开头的电话一样,互联网也保留了这种安全域名用来举例子。

根据RFC6761中的提到的,

IANA currently maintains a web server providing a web page explaining the purpose of example domains.

所以只有http请求才会被受理以展示那个说明页面。
你的邮件无法找到example.com的MX记录,
虽然可以落到A记录,顺利的拿到IP,但IANA并没有建立监听25端口的SMTP服务,
所以,邮件发送会失败。

Salesforce的Field Id与URL Hack

默认值是个好东西。
别说是普通用户,就算是开发者,在面对满满一屏的空白字段时,也是要不禁皱皱眉头的。

设置得当的默认值可以极大的提高业务流畅度与用户满意度。
反之,每次都不得不改的默认值,会带来加倍的苦恼。(甚至会惹恼用户)

当然,Salesforce提供了字段的Default Value功能。
我们可以为大部分字段类型设置默认值。

Default Value必须为公式,并且不能使用其他字段的值,只可以使用预设的系统变量和环境变量。
因为这样的限制,使得我们在使用自定义button或者link跳转到Object标准编辑画面的时候,无法根据情况显示不同的默认值。

举个例子,比如Case上面有个字段用来储存Case的类型,然后在Case的详细画面上放置一个自定义Button,这个button用来新建一个order。
为了提高Agent的工作效率,弹出order的新建页面之后,我们希望order的类型是Case上所选择的值,并且与Case关联同一个Account。

这种场景下,字段的Default Value已经无法满足需求。
我们需要使用一种Salesforce并不提倡的技巧———-URL Hack。

URL Hack就是通过在Salesforce的URL上加上额外的参数,来实现我们的需求。
其中一种技巧,就是给Standard Pagelayout赋值。

那么让我们来看看URL Hack到底是什么样子。
首先,点击Case标签里的新建Button。
跳转到Case的新建画面之后,观察地址栏的URL如下
无RecordType: https://[InstanceName].salesforce.com/500/e?retURL=%2F500%2Fo
有RecordType: https://[InstanceName].salesforce.com/500/e?retURL=%2F500%2Fo&RecordType=0126F000000v348&ent=Case

其中,500代表Case,每个Object都有一组三位码,同时也意味着所有的数据的Id都是由这三位码开头。
e代表编辑页面。 500/e代表新建数据,500XXXXXXXXX/e代表修改既存数据。
retURL代表回调页面,当点击取消Button之后,会返回到指定的页面。在例子中,%2F代表“/”,所以会返回500/o。
有RecordType的URL多了一个RecordType参数,值0126F000000v348就是recordType的Id。

所以,RecordType以外的字段,我们也可以通过同样的方式进行赋值。

首先,与RecordType不同的是,我们需要用的并不是字段的API名,而是Field ID。
如果不使用工具的话,Field Id并没有快捷的查看方式,所以应急的话,可以直接打开编辑画面,然后用浏览器的F12开发者工具直接查看。

标准字段是字母+数字的形式,自定义字段则是以00N开头的ID。
就拿上面的例子来说,如果将“123456”赋给Account Number,URL应写为
https://[InstanceName].salesforce.com/500/e?acc5=123456&retURL=%2F500%2Fo

如果是Lookup字段的话,上面那样写就行不通了,要在Field Id后面加上_lkid后缀
标准字段的话,我们以Account的Parent Account为例
https://[InstanceName].salesforce.com/001/e?acc3_lkid=001XXXXXXXXX

自定义字段的话,Lookup字段还需要在00N前加上CF
https://[InstanceName].salesforce.com/001/e?CF00NXXXXXXXXX_lkid=001XXXXXXXXX

有的时候只写lkid结尾的参数看起来并不管用,在编辑页面上,Lookup字段仍然是空白的,但实际上保存之后是已经被赋值的。
如果遇到这种现象,就要加额外加一个参数
https://[InstanceName].salesforce.com/001/e?acc3_lkid=001XXXXXXXXX&acc3=RecordName
https://[InstanceName].salesforce.com/001/e?CF00NXXXXXXXXX_lkid=001XXXXXXXXX&CF00NXXXXXXXXXX=RecordName

最后记住,一定要养成随时设置retURL的好习惯。

上述所有的值在Custome Button或者Link中,都可以用表达式替换,比如
00NXXXXXXXXXXXX={!Case.Status}等。

如果想了解URL Hack还能做哪些事情,之后会写一篇专访。

Salesforce的TestClass

虽然Salesforce从业人员应该尽量使用标准功能来实现需求,
但仍不可避免的要用到代码。

标准功能的话,Salesforce会自己负责质量(虽然Bug频发。。。),
对于自定义功能,Salesforce则制定了质量标准,
比如,如果总体代码覆盖率不到75%,无法Deploy。

一般的Apex Code,就像那些出现在Trigger里,出现在Controller里的代码,
测试类都很好写,就按照
1. 准备测试数据
2. 执行业务逻辑
3. 断言执行结果
按套路打就行了。

除此之外,另一些apex code,需要特别的测法。
Salesforce作为CRM系统,无法避免的要与其他系统进行数据交互。
大部分情况,我们只需要把salesforce的标准集成文档和权限已经配置妥当的账号,提供给对方就好,
但是,当我们需要将业务封装起来的时候,就需要自己建WebService。
况且,Salesforce主动调用外部系统接口的时候,又要区分是Rest还是SOAP。

对于Apex Rest Callout可以使用下面三种方式(代码示例点击链接)
1. HttpCalloutMock
比较常见的写法,有极大的可拓展性(毕竟连响应结果都要自己Code进去)。
2. StaticResourceCalloutMock
LoadData是一对好兄弟,都是在Test Class中利用StaticResource,可以很好的做到测试数据与测试代码分离。
3. MultiStaticResourceCalloutMock
StaticResourceCalloutMock的复数URL版。

对于Apex SOAP Callout,Salesforce也提供了一种方式。
1. WebServiceMock

以上是Callout的测试类写法,原理均是构造一个Mock类,然后提供一个假的response。
你总不能期待每次跑测试类的时候,让人家集成方的服务器真的去响应你吧?

那么Callin呢。
首先,处理Callin的Apex Class一定是WebService。
WebService也分为Rest Webservice和SOAP WebService。
对于Rest WebService,Salesforce并没有提供类似与Mock类的工具类,所以我们直接去构造上下文环境。
在实际情况种,上下文环境是由系统进行构造的,这里我们代替系统。

        .................
        RestRequest request = new RestRequest();
        request.requestURI = 'https://[InstanceName].salesforce.com/services/apexrest/xxxxxx/';
        request.httpMethod = 'GET';
        RestContext.request = request;
        /** Call your WebService like below **/
        MyWebService.MyFoo();
        .................

对于SOAP Webservice就简单多了。因为不需要处理上下文环境的参数等内容,直接处理方法中的参数就好,
所以就当作普通的Apex Code处理就可以,不需要特殊的操作。

除此之外。

虽然目前我还没见有人用过,但是Salesforce还是提供了一个Stub接口,用来做Mock框架。
它的好处是可以实现单元测试的完全解耦,对各个小模块进行单独的测试。
就目前市面上各个大公司的状况来看。。。能好好写个断言就不错了。(捂脸)(尴尬)

Salesforce的Rollback与SavePoint

之前对于Salesforce的RollBack机制没有进行深入的思考。
那天,闲来无事,有人问了我一个问题。

问说,下面的代码在匿名快执行完之后插进去了几条数据。

ObjectA__c objA = new ObjectA__c();
objA.Name = "TestA";
insert objA;

List<ObjectB__c> objBList = new List<ObjectB__c>
for(Integer i = 0; i < 200; i++) {
    ObjectB__c objB = new ObjectB__c();
    objB.Name = "TestB" + i;
    insert ojbB;
}

我大概扫了一眼,问的是Governor Limit嘛,答案是150,送分题,不谢。
但又隐约的觉得不大对劲,既然拿来问我,肯定有坑。
立马打开Developer Console试了一下。
看到了结果倒吸了一口冷气。。。。。。。。0,一条都没插进去。全都RollBack了?

咦?不对啊,那一定是匿名块做了特殊的设置。试试Controller。
要不然我以前在Controller里写个什么劲儿的SavePoint。
VF:

<apex:page controller="TestRollbackClass">
    <apex:form>
    	<apex:commandButton action="{!save}" value="Button"/>  
    </apex:form>
</apex:page>

Apex Class:

public class TestRollbackClass {

    public void save() {     
        ObjectA__c oa = new ObjectA__c();
        oa.Name = 'TestA';
        insert oa;

	ObjectB__c ob = new ObjectB__c();
        ob.Name = 'testB';
        insert ob;
    }
}

我在ObjectB上加了一个Validation Rule,保证新建ObjectB记录的一定会报错。
这次总该如预期一般只有ObjectA被插进去了吧。

结果一跑,唉? 还是都RollBack了?
瞬间怀疑人生。既然都自动RollBack了,那我以前为什么必须写SavePoint?
去翻了下Release Note,也没找到相关的变动。

没办法,一定是写法问题,我试着按照Coding Standard写一次

public class TestRollbackClass {

    public void save() {
	ObjectA__c oa = new ObjectA__c();
	oa.Name = 'Test2';
        insert oa;
        try {
            ObjectB__c ob = new ObjectB__c();
        	ob.Name = 'test1';
        	insert ob;
        } catch(Exception e) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error, e.getMessage()));
        }
    }
}

再次执行,第一条数据ObjectA居然插进去了。
我瞬间明白了点什么。

Salesforce的机制应该是这样的,
如果Transaction被异常中断,则Rollback全部的数据变动。
如果异常被捕获,则已经正常commit的数据变动会落实。出错的部分无效。

按照开发潜规则,应该展现给User更Friendly的错误信息,而不是可怕的Trace Log信息。
所以我以前的公司要求捕获所有可能的异常并替换成友好的错误信息。
这样的话,Transaction一定不会被异常中断,所以自动全部Rollback的机制从来没有启用过。
那么为了防止在出错时插入脏数据,Salesforce提供了SavePoint。
用法如下:

public class TestRollbackClass {

    public void save() {
        SavePoint sp = Database.setSavepoint();
		ObjectA__c oa = new ObjectA__c();
		oa.Name = 'Test2';
        insert oa;
        try {
            ObjectB__c ob = new ObjectB__c();
        	ob.Name = 'test1';
        	insert ob;
        } catch(Exception e) {
            Database.rollback(sp);
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error, e.getMessage()));
        }
    }
}

如果ObjectB插入出错,已insert的ObjectA也会回滚。达到目的。

Salesforce关于Debug

做Salesforce开发,最让人头疼的事情,莫过于Debug。

不同于Java开发,已经有了一系列成熟的Debug工具,
Salesforce开发者最常用的手段只有输出log,就如同Javascript开发一样。
不过随着现代浏览器的兴起,就算是Javascript开发也可以debug了。

对于一个老手来说,添加System.debug()然后查log,虽然已经足够。
但是,面对巨型transaction的时候,就要面临Debug Log的限制
1. log总体大小不能超过3MB,否则随机截断(不管官网怎么说,我觉得是随机的,囧)
2. log只能存留七天
3. 15分钟内产生的log不能超过250MB

为了回避此问题,Salesforce提供了Class和Trigger的Debug Log级别自定义功能。官网点此
启用这个功能,可以在Class和Trigger的详细页面,找到TraceFlag,在此设定需要的Debug log级别,(一般全开最详细,或者全关)
自定义完成后,transaction中class和trigger产生log时的级别设定,就会覆盖默认的debug log级别设定。
使用此功能可以极大的减少不需要的log输出,从而避免被随机截断。

但是,System.debug必然不是万能的。

所以在DeveloperConsole中,Salesforce给开发者提供了CheckPoint功能,注意,不是BreakPoint,是CheckPoint。
Checkpoint不同于BreakPoint那样可以互动。只提供了信息查看功能。
就是说,在代码中打上Checkpoint之后,程序执行并不会暂停,而是执行到结束,但Salesforce会将Checkpoint标记位置的内存状态保留下来供开发者查看。
需要值得注意的是,Checkpoint全局只能打5个。

如果代码真的复杂到checkpoint和Debug log都无法搞定,
Salesforce提供了一个真正的Debug工具——Apex Debugger
有了Apex Debugger,Salesforce开发者也可以像Java程序员一样debug了。
不过越强大的武器,限制越多。
Apex Debugger只提供了一个免费的License,并且是全Org通用,就说Production和旗下所有sandbox合起来同时只能有一个人用。

不管使用哪种Debug方式,都应该从设计伊始就避免设计出如此巨大的自定义功能。

Salesforce的SplashPage(启动页)-一天一个标准功能系列

最近有小盆友考验我,说客户想点进Tab的时候先进入一个Guide页面,然后点击继续进入list页面,点击Don’t show again下次不再显示此页面,直接进入List页面。
问我该怎么做。

我反问,”你想怎么做?”
“首先呢,建一个Tab”
“废话。哪种Tab。”
“当然是Visualforce Tabs。”
“然后呢。”
“然后我创建一个VF,并且使用标准标签和样式。”
“嗯,为了让用户觉得自己没跳进VF是么?”
“没错,然后我在VF里选中用户点的Tab,之后在页面里写上我要的内容。”
“这个简单,那俩按钮你想怎么实现呢?”
“当然是建两个Button,然后在Controller里实现两个Action。一个Button直接跳转到List页面。另一个Button。。。。”
“Don’t show again button你想怎么实现呢?”
“我在User表上加一个字段,如果用户点了Don’t show again,我就更新一下User身上的Flag,然后在VF初始化的时候就判断User身上的Flag,如果是true就直接跳转。”
“唔。。。可行,你觉得复杂么?”
“还好吧。”
“听说过Splash Page么?”
“啥?”

Splash Page,启动页,醒目页面。是Salesforce提供的标准功能之一。
其作用是在Tab上添加一个启动页面,并且原生支持Don’t show again。
其效果如图

点击Tab之后,会显示指定的内容,并提供Don’t show again与Continue按钮。
不过,我发现只有Custom Object的Tab才可以指定Splash Page,标准Object的Tab不可以。
可能Salesforce觉得标准Object应该不会像Custom Object那样复杂到需要启动页来介绍功能吧。

那么接下来介绍该如何创建一个启动页。

首先,你需要创建Custom Link,此Custom Link为Home的Custom Link,而不是该Custom Object上的Custom Link。

创建的过程与创建button或者link相同,种类有三种,JS,VF或者URL。

然后随便创建一个VF,在此选择。

值得注意的是,一定要记得关闭标准标签和标准样式,否则就会出现像下面这样奇怪的事情。

关闭标准标签标准样式的写法。

<apex:page showheader="false" standardStylesheets="false">
......
</apex:page>

Custom Link设定好之后,去Tab的设置页面,指定这个Custom Link,大功告成。

Salesforce作为云计算平台,提供了强大的自定义开发能力。但是,现代应用开发思想是轻开发,重配置。常年从事SI的前JAVA从业者,很容易就陷入开发解决一切的思维黑洞。付出高昂的成本反复造轮子,因为其价值也只是在反复造轮子上了。利用标准功能快速的实现客户需求,才是未来的发展方向。大型开发的市场会一直存在,但将将来绝对不会是主流。

Salesforce中Picklist与翻译的故事

不同与Java系统,salesforce给Select或者Drop down,whatever你叫它什么的那个元素提供了一个很好的组织方式———-Picklist。

Picklist作为一种基本数据类型,在salesforce里任何人都可以使用其轻松的创建下拉列表,并动态的控制其中的选项。要知道,在java系统里,为了达到这个目的,可是要上三大框架的。如果不能用自定义标签库,那么在不hard coding的前提下,在画面上可控的显示一个select对于一个新手来讲,将是一个堪比穿越撒哈拉沙漠的挑战。当然,大部分项目都选择直接写死,或者使用Js操纵DOM。

不过Salesforce的Picklist与我们印象中的Select元素还是不同的。

在Spring 17之前(Spring 17 release note),picklist的值只有一个value,所以表示文本也是它,值也是它。所以好多系统就顺水推舟的在代码,validation rule等地方直接用长长的value内容做判断。
突然有一天,客户想对选项的内容做一个小小的改动。。。。。灾难了。
以前的话,更改选项的值,既存数据是不会跟着变的,要手动修数据。现在已经能够做到既存的数据自动跟着变了。但是那些代码里写死的判断等就不行了。

我们知道,html中的select是要分别设定value和label的!在页面表示label,表单里传的是value。这样根据定义好的数据字典,在程序里随便判断value。无论画面表示的本文怎么改都不会影响到程序。

所以sfdc的先贤们发明了一个绝招————翻译。

比如picklist的一个项目的value是1,则在翻译控制台将1翻译成想显示的文本,比如“A”。那么就实现了HTML的Select的特性—–显示的文本和后台传值分开。

正当群众们洋洋得意的时候,Spring17来了。给我们带来了picklist选项的Api Name,参见官方博客

抱歉,说的不严谨,应该是给我们带来了Picklist选项的Label。

就是说现在Picklist的选项有两个设定值值,一个是Label,一个是Api Name。Api Name其实还是我们以前熟悉的那个选项value,老朋友。新增加的是Label。

那么好玩的地方来了。以前Picklist选项只有一个value的时候,翻译就直接那么翻译了。当Label和Api Name分开之后,翻译是针对谁来进行翻译呢?

这里直接公布答案—–是Label。

可以用下面的代码进行尝试。

 
设定好翻译之后

List<Schema.PicklistEntry> picklistValues = SomeSobject__c.SomePicklistField__c.getDescribe().getPicklistValues();
for (Schema.PicklistEntry pe: picklistValues) {
    system.debug('PicklistEntry Value:' + pe.getValue() + ' Translated Label:' + pe.getLabel());
}

value既Api Name,Label既Label

这样,我们是可以做到在Apex中获取Picklist选项的Label,而且获得的还是翻译后的Label。
可曾记起我们还有Formula呢,isPickVal函数判断的又是谁呢—————没错,还是老朋友,Api Name。
目前我还没有找到如何在formula里获取Label名的方法。