├── .gitignore
├── LICENSE.txt
├── readme.md
└── src
├── classes
├── sObjectRemote.cls
├── sObjectRemote.cls-meta.xml
├── sObjectRemoteTEST.cls
└── sObjectRemoteTEST.cls-meta.xml
├── components
├── sObjectRemote.component
└── sObjectRemote.component-meta.xml
├── package.xml
├── pages
├── sObjectRemoteExamples.page
└── sObjectRemoteExamples.page-meta.xml
└── staticresources
├── sObjectRemote.resource
└── sObjectRemote.resource-meta.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | /config/
2 | /sObjectRemote.sublime-project
3 | /sObjectRemote.sublime-workspace
4 | .DS_Store
5 | .DS_Store?
6 | ._*
7 | .Spotlight-V100
8 | .Trashes
9 | Icon?
10 | ehthumbs.db
11 | Thumbs.db
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 James Sullivan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | sObject-Remote
2 | ======
3 |
4 |
5 |
7 |
8 |
9 | **sObject-Remote** is a small JavaScript library for simplifying DML operations on sObjects when using the JavaScript Remoting feature of Visualforce. Here is a tasty little preview that creates and inserts a new Account record:
10 |
11 | ```js
12 | var acct = new sObject('Account',{Name: 'test', Industry: 'Aerospace'});
13 |
14 | acct.insert(function(result,event){
15 | console.log(result[0].Id);
16 | });
17 | ```
18 | This is a very new library and the current status should definitely be considered **Beta**. Options, syntax, and method names may change in the near term based on bugs and community feedback. Please report any issues you come across and I'll get them fixed as soon as possible, or feel free to fix and submit pull requests as well.
19 |
20 | ## Features
21 |
22 | * Super simple sObject management for JavaScript
23 | * Currently supports 4 basic CRUD operations (more DML operations to come)
24 | * Works with any JavaScript framework
25 | * No 3rd party JavaScript library dependencies
26 |
27 | ## Setup
28 |
29 | To use the **sObject-Remote** library you can pull the code from this repository to your target org or you can install the unmanaged package with this link: [sObject-Remote Unmanaged Package](https://login.salesforce.com/packaging/installPackage.apexp?p0=04ti000000050sf).
30 |
31 | Once installed you will need to add the sObjectRemote Visualforce Component to your page. This includes everything you need to get started. Be sure to add the component before any other JavaScript files that may use the **sObject-Remote** library.
32 |
33 | ```html
34 |
35 |
36 |
37 |
38 |
39 | ```
40 |
41 | #### Component Attributes
42 |
43 | * `escape` - This library defaults all JavaScript Remoting calls to be escaped, `true`. If you are in a trusted environment with trusted users you may set this default to `false` and all JavaScript Remoting calls will be unescaped. Any escape option set in a specific sObject() operation will override this component attribute.
44 |
45 | ## Usage
46 |
47 | ### Create New - new sObject(sObjectAPIname,[fieldValues]);
48 |
49 | You can create an sObject variable in two ways. The first requires two arguments, the API name of the sObject and a key value pair object of field names and values.
50 |
51 | ```js
52 | var acct = new sObject('Account',{Name: 'TehNrd Industries', BillingCity: 'Seattle'});
53 | ```
54 |
55 | The second option is to only provide the API name of the sObject and populate the field values with dot or bracket notation.
56 |
57 | ```js
58 | var acct = new sObject('Account');
59 | acct.Name = 'TehNrd Industries';
60 | acct.BillingCity = 'Seattle';
61 | acct['PostalCode'] = '98119';
62 | ```
63 | Now that we have created our Account sObject we are ready to insert it.
64 |
65 | ### Insert - sObject.insert(sObject || sObjectArray,[dmlOptions],callback);
66 |
67 | The first argument for ```sObject.insert()``` is always a single sObject or an array of sObjects. The last argument is always the callback function that will process the results of the insert. There is also a third, optional, argument for dmlOptions and other options specific to this library. The dmlOptions argument will be the second argument if present and the callback function will be the last argument. See the section below on DML Options.
68 |
69 | ##### Insert our Account
70 | ```js
71 | //If inserting one or many sObjects
72 | sObject.insert(acct,function(result,event){
73 | //Handle results, see call back section for more information
74 | });
75 |
76 | //Or if inserting one sObject only we can call the insert method directly on that variable/sObject.
77 | acct.insert(function(result,event){
78 | //Handle result, see call back section for more information
79 | });
80 | ```
81 |
82 | ### Query - sObject.query(queryString,callback);
83 |
84 | There are two arguments required by the `sObject.query()` method. The first is the SOQL query string and the second is a callback function that will handle the records returned by the query. In the callback function the sOjects argument is an array of sObject records and the event argument is the standard [event](http://www.salesforce.com/us/developer/docs/pages/Content/pages_js_remoting.htm#handling_remote_response) object returned by a JavaScript Remoting call.
85 |
86 | ```js
87 | sObject.query('select Id, Name, Owner.Name from Account where Industry = \'Aerospace\' limit 5',function(sObjects,event){
88 | //Loop through the records returned by the query
89 | for(var i = 0; i < sObjects.length; i++){
90 | console.log(sObjects[i]);
91 | }
92 | });
93 | ```
94 |
95 | ### Update - sObject.update(sObject || sObjectArray,[dmlOptions],callback);
96 |
97 | Updating a sObject is very similary to inserting a sObject. Notice here that we changed the variable name of the first callback argument to better describe the type of objects being returned.
98 |
99 | ```js
100 | var acctsToUpdate = new Array();
101 |
102 | //Query some accounts in which we want to change the Industry field
103 | sObject.query('select Industry from Account where Industry = \'Planes and Spaceships\'',function(accts,event){
104 | if(event.status){
105 | for(var i = 0; i < accts.length; i++){
106 | //Change the Industry add to array we will update
107 | accts[i].Industry = 'Aerospace';
108 | acctsToUpdate.push(accts[i]);
109 | }
110 | }else{
111 | //Error handling
112 | }
113 | });
114 |
115 | /*Pretend there is a long break here and these are two different operations. Or if you want this to update
116 | immediately after the query it should be placed in the callback function of the query*/
117 |
118 | //Update the accounts
119 | sObject.update(acctsToUpdate,function(results,event){
120 | if(event.status){
121 | //All good, update success
122 | }else{
123 | //Error handling.
124 | }
125 | });
126 | ```
127 |
128 | ### Delete - sObject.del(sObject || sObjectArray,[dmlOptions],callback);
129 |
130 | Delete follows the same structure as inserts and updates. Even though you pass an entire sObject or array of sObjects to the `sObject.delete()` method the sObject-Remote library will only send the Id values to the server as this is all that is needed for a delete operation and therefore also improves performance. In this example we also set some DML options to allow partial success of the records being deleted.
131 |
132 | It is **important** to note the method name for delete is `del`. This is because delete is a reserved keyword in JavaScript.
133 |
134 | ```js
135 | var acctsToDelete = new Array();
136 |
137 | //Query some accounts that are flagged for deletion
138 | sObject.query('select Id from Account where Flagged_for_Delete__c = true',function(accts,event){
139 | if(event.status){
140 | acctsToDelete = accts;
141 | }else{
142 | //Error handling
143 | }
144 | });
145 |
146 | //Pretend there is a long break here and these are two different operations
147 |
148 | //Create a DML options object
149 | var dmlOptions = {optAllOrNone: false};
150 |
151 | //Do the delete
152 | sObject.delete(acctsToDelete,dmlOptions,function(results,event){
153 | if(event.status){
154 | //Inpect the results object to determine which accounts were successfully deleted
155 | }else{
156 | //Error handling.
157 | }
158 | });
159 | ```
160 | ### Callback
161 |
162 | #### Query
163 | The callback function for a query has two arguments. The first is an Array of sObject objects which is the result of the query. An sObject is simply the JSON representation of a salesforce.com standard or custom object with a little extra functionality added on such as the insert() and update() instance methods that can be called on an sObject for easier DML. The second argument is the JavaScript Remoting event object and it can help you determine if the remoting call was successful. You can learn more about the event object [here](http://www.salesforce.com/us/developer/docs/pages/Content/pages_js_remoting.htm#handling_remote_response). Make note that you can name these arguments to what ever you want to make the code more understandable as the only thing that matters is the order.
164 |
165 | #### Insert, Update, and Delete
166 | The callback function for inserts, updates, and deletes also has two arguments. The first is an array of SaveResult or DeleteResult objects. You can learn more about these objects in the Apex Reference Guide [here](http://www.salesforce.com/us/developer/docs/apexcode/index.htm). Below is and example of what this may look like if you set the DML option `optAllOrNone` to false and their is partial success.
167 |
168 | ```js
169 | [{success:true,id:"001i0000003WM2lAAG"},
170 | {success:true,id:"001i0000003WM2mAAG"},
171 | {success:false,
172 | errors:[
173 | {status:"FIELD_CUSTOM_VALIDATION_EXCEPTION",message:"This account is locked and cannot be updated."}
174 | ]
175 | }]
176 | ```
177 |
178 | The second argument in the callback is the JavaScript Remoting event object and it can help you determine if the remoting call was successful. You can learn more about the event object here: [Handling the Remote Response](http://www.salesforce.com/us/developer/docs/pages/Content/pages_js_remoting.htm#handling_remote_response).
179 |
180 | ### DML Options / Options
181 | This is a JavaScript object that allows you to set DML options which you can learn more about [here](http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_methods_system_database_dmloptions.htm). Let's say we want to update a list of Lead records, allow partial success, and also force assignment rules to run.
182 |
183 | ```js
184 | var leads = //List of sObjects, queried or inserted
185 |
186 | //Create a DML options object
187 | var dmlOpt = {
188 | optAllOrNone: false,
189 | useDefaultRule: true
190 | };
191 |
192 | //Update leads and pass in dmlOptions object
193 | sObject.update(leads,dmlOpt,function(result,event){
194 | if(event.status){
195 | //Inspect result object to see which leads were updated successfully
196 | }else{
197 | //error handling
198 | }
199 | });
200 | ```
201 |
202 | #### Other Options
203 |
204 | In addition to DML Options this object can take special values unique to **sObject-Remote**.
205 |
206 | * `escape` - `true` or `false` Sets the escape option on the JavaScript Remoting call to control if response is escaped. Default to `true`.
207 | * `purge` - `true` or `false` For Deletes only. If the delete for a single sObject is successful it will set the original sObject to null. If Delete was an array of sObjects it will remove successfully deleted sObjects from the original array. Defaults to `false`.
208 | * `resultAsObject` - `true` or `false` Instead of the result object being returned as an array of SaveResult/DeleteResults objects this will return a JavaScript object with the record Id as the key and the value will be the result object. Depending on the use case this may make it easier to identity success and failures. This option only works with **update** and **delete** methods. Defaults to `false`. Example below:
209 |
210 | ```js
211 | //This is what a normal result array would look like
212 | [{success:true,id:"001i0000003WM2lAAG"},{success:true,id:"001i0000003WM2mAAG"}];
213 |
214 | //When the resultAsObject option is set to true
215 | {001i0000003WM2lAAG:{success:true},
216 | 001i0000003WM2mAAG:{success:true}}
217 | ```
218 |
219 | ### Tips, Tricks, and FAQ
220 |
221 | #### Case sensitivity
222 |
223 | Don't forget JavaScript is case sensitive and salesforce.com returns the API names as they are defined by the setup metadata. Let's say you have the following query:
224 |
225 | ```js
226 | sObject.query('select name, industry from account limit 1',function(acct,event){ //Query will work fine
227 | console.log(acct[0].name); //Will output undefined as Salesforce returns the name as 'Name'
228 | console.log(acct[0].Name); //Will output correct value
229 | });
230 | ```
231 | This is also true for custom fields so be sure the name is exactly the same as what is defined in the setup areas of salesforce.com.
232 |
233 | #### Extra Object Properties
234 |
235 | JavaScript makes it easy to add dynamic properties to an object but you need to take special care when doing this with sObjects. Salesforce.com will not be able to handle DML operations if you add invalid properties. To avoid problems **never add extra properties to the top level object** as this will cause errors. Instead add extra attributes to a child object. Every `sObject` already comes with a child object called `attributes` that can be used for this purpose. A classic example is keeping track of whether an object is selected.
236 |
237 | ```js
238 | sObject.query('select Name, Industry from Account limit 10',function(accts,event){
239 | //Default the selected property to false
240 | for(var i = 0; i < accts.length; i++){
241 | accts[i].attributes.selected = false; // GOOD
242 |
243 | //accts[i].selected = false; !!! BAD DO NOT DO THIS !!!
244 | }
245 | });
246 | ```
247 |
248 | #### Variable Name Conflict
249 |
250 | This library creates a global variable called `sObject`. You should not have any other variables in your script with this name. This may be an issue and I am open to community feedback regarding this.
251 |
252 | #### How is this different from [Force.com JavaScript REST Toolkit](https://github.com/developerforce/Force.com-JavaScript-REST-Toolkit)?
253 |
254 | Ya...so it's really not that different. I didn't realize the library already existed before I started working on this but some of the difference are:
255 |
256 | * sObject-Remote is focused only on Visualforce JavaScript Remoting
257 | * Does bulk DML. Insert, update, and delete many records at once. The JavaScript REST Toolkit is, in its purest form, a wrapper for the REST API and the REST API currently only supports single record DML for many operations.
258 | * Doesn't require a 3rd party library like jQuery,
259 | * Supports DML Options (maybe I missed it, but didn't see this in the Toolkit)
260 | * Has some other nifty utility options
261 |
262 | **sObject-Remote** is another tool in the toolbox depending on the functionality you require. Both are great so play with each and choose which ever meets your requirements, are most comfortable with, or meshes well with your development style.
263 |
--------------------------------------------------------------------------------
/src/classes/sObjectRemote.cls:
--------------------------------------------------------------------------------
1 | public with sharing class sObjectRemote{
2 |
3 | //---Insert method---
4 | @RemoteAction
5 | public static List insertSObjects(List objects, Map dmlOptions){
6 |
7 | //Process and set the DML options
8 | Database.DMLOptions dmo = sObjectRemote.setDMLoptions(dmlOptions);
9 |
10 | //Do the insert
11 | Database.SaveResult[] result = Database.insert(objects,dmo);
12 |
13 | //Return the result
14 | return result;
15 | }
16 |
17 | //---Query method---
18 | @RemoteAction
19 | public static QueryResult query(String soql){
20 | //QueryResult object that retruns sObjectType info and the records queried
21 | QueryResult qr = new QueryResult();
22 |
23 | //Perform the query
24 | for(sObject obj : Database.query(soql)){
25 | //Add sObject list of sObject returned in the QueryResult
26 | qr.sObjects.add(obj);
27 | }
28 |
29 | //Set the object type in the attributes of the QueryResult
30 | if(qr.sObjects.size() > 0){
31 | qr.sObjectAttributes.put('sObjectType',String.valueOf(qr.sObjects[0].getsObjectType()));
32 | }
33 | return qr;
34 | }
35 |
36 | //---Update Method---
37 | @RemoteAction
38 | public static List updateSObjects(List objects, Map dmlOptions){
39 | Database.DMLOptions dmo = sObjectRemote.setDMLoptions(dmlOptions);
40 |
41 | Database.SaveResult[] result = Database.update(objects,dmo);
42 | return result;
43 | }
44 |
45 | //---Delete Method---
46 | @RemoteAction
47 | public static List deleteSObjects(List sObjects, Map dmlOptions){
48 |
49 | Database.DMLOptions dmo = sObjectRemote.setDMLoptions(dmlOptions);
50 |
51 | //Default allOrNone options to true
52 | Boolean allOrNone = true;
53 | if(dmo.optAllOrNone == false){
54 | allOrNone = false;
55 | }
56 |
57 | Database.DeleteResult[] result = Database.delete(sObjects,allOrNone);
58 | return result;
59 | }
60 |
61 | //Parses a map/string of DML options and creates and Apex DML object
62 | public static Database.DMLOptions setDMLoptions(Map dmlOptions){
63 |
64 | //Create lowercase key value pairs map of the dml options provided
65 | Map options = new Map();
66 | for(String key : dmlOptions.keySet()){
67 | options.put(key.toLowerCase(),String.valueOf(dmlOptions.get(key)).toLowerCase());
68 | }
69 |
70 | //Instantiate a DMLOptions object
71 | Database.DMLOptions dmo = new database.DMLOptions();
72 |
73 | //---Set allowFieldTruncation property---
74 | if(options.get('allowfieldtruncation') != null){
75 | String fieldTrun = options.get('allowfieldtruncation');
76 | if(fieldTrun == 'true' || fieldTrun == '1'){
77 | dmo.allowFieldTruncation = true;
78 | }
79 | }
80 |
81 | //---Set assignmentRuleHeader property---
82 | if(options.get('assignmentruleheader') != null){
83 |
84 | //Parse assignment rule header options in to a map
85 | Map ashOptions = sObjectRemote.parseKeyValueString(options.get('assignmentruleheader'));
86 |
87 | //Set assignmentRuleId option
88 | if(ashOptions.get('assignmentruleid') != null){
89 | dmo.assignmentRuleHeader.assignmentRuleId = ashOptions.get('assignmentruleid');
90 | }
91 |
92 | //Set useDefaultRule option
93 | String useDefaultRule = ashOptions.get('usedefaultrule');
94 | if(useDefaultRule == 'true' || useDefaultRule == '1'){
95 | dmo.assignmentRuleHeader.useDefaultRule = true;
96 | }
97 | }
98 |
99 | //---Set emailHeader property---
100 | if(options.get('emailheader') != null){
101 |
102 | //Parse assignment rule header options in to a map
103 | Map emailOptions = sObjectRemote.parseKeyValueString(options.get('emailheader'));
104 |
105 | //Set triggerAutoResponseEmail option
106 | String autoResponse = emailOptions.get('triggerautoresponseemail');
107 | if(autoResponse == 'true' || autoResponse == '1'){
108 | dmo.EmailHeader.triggerAutoResponseEmail = true;
109 | }
110 |
111 | //Set triggerOtherEmail option
112 | String triggerOtherEmail = emailOptions.get('triggerotheremail');
113 | if(triggerOtherEmail == 'true' || triggerOtherEmail == '1'){
114 | dmo.EmailHeader.triggerOtherEmail = true;
115 | }
116 |
117 | //Set triggerUserEmail option
118 | String triggerUserEmail = emailOptions.get('triggeruseremail');
119 | if(triggerUserEmail == 'true' || triggerUserEmail == '1'){
120 | dmo.EmailHeader.triggerUserEmail = true;
121 | }
122 | }
123 |
124 | //---Set localeOptions property---
125 | if(options.get('localeoptions') != null){
126 | dmo.localeOptions = options.get('localeoptions');
127 | }
128 |
129 | //---Set optAllOrNone property---
130 | //Default behavior for allOrNone option to true
131 | dmo.optAllOrNone = true;
132 |
133 | if(options.get('optallornone') != null){
134 | String optAllOrNone = options.get('optallornone');
135 | if(optAllOrNone == 'false' || optAllOrNone == '0'){
136 | dmo.optAllOrNone = false;
137 | }
138 | }
139 |
140 | return dmo;
141 | }
142 |
143 | public static Map parseKeyValueString(String keyValueString){
144 | Map result = new Map();
145 |
146 | //Substring removes the {} on the ends and trim whitespace
147 | for(String s : keyValueString.substring(1,keyValueString.length() - 1).split(',')){
148 | List keyValue = s.split('=');
149 | result.put(keyValue[0].trim(),keyValue[1].trim());
150 | }
151 |
152 | return result;
153 | }
154 |
155 | //Wrapper class for a query that returns the sObject records queried and other metadata information
156 | public class QueryResult{
157 | public List sObjects {get; set;}
158 | public Map sObjectAttributes {get; set;}
159 |
160 | public QueryResult(){
161 | this.sObjects = new List();
162 | this.sObjectAttributes = new Map();
163 | }
164 | }
165 | }
--------------------------------------------------------------------------------
/src/classes/sObjectRemote.cls-meta.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 32.0
4 | Active
5 |
6 |
--------------------------------------------------------------------------------
/src/classes/sObjectRemoteTEST.cls:
--------------------------------------------------------------------------------
1 | @isTest
2 | private class sObjectRemoteTEST{
3 |
4 | //Test methods for sObjectRemote class
5 |
6 | //Insert
7 | private static testMethod void insertTest(){
8 | //Create 200 account sObjects but do not insert them
9 | List accts = createSObjects(false);
10 |
11 | //Start test and insert records
12 | Test.startTest();
13 | sObjectRemote.insertSObjects(accts,new Map());
14 | Test.stopTest();
15 |
16 | //Assert records were inserted successfully
17 | for(sObject acct : accts){
18 | system.assert(acct.Id != null);
19 | }
20 | }
21 |
22 | //Query
23 | private static testMethod void queryTest(){
24 | String query = 'select Id from User limit 1';
25 |
26 | //Start test
27 | Test.startTest();
28 | sObjectRemote.QueryResult qr = sObjectRemote.query(query);
29 | Test.stopTest();
30 |
31 | //Assert records were returned
32 | system.assert(qr.sObjects.size() > 0);
33 | }
34 |
35 | //Update
36 | private static testMethod void updateTest(){
37 | //Create and insert 200 accounts
38 | List accts = createSObjects(true);
39 |
40 | //Change the name field to something very unique
41 | for(sObject so : accts){
42 | so.put('Name','sObjectRemoteUpdateTEST4320930@#$#$#$#!@$Afasdfa3r');
43 | }
44 |
45 | //Start test and update records
46 | Test.startTest();
47 | sObjectRemote.updateSObjects(accts,new Map());
48 | Test.stopTest();
49 |
50 | //Assert all accounts were updated
51 | List acctsAfterUpdate = [select Id from Account where Name = 'sObjectRemoteUpdateTEST4320930@#$#$#$#!@$Afasdfa3r'];
52 | system.assert(acctsAfterUpdate.size() > 0);
53 | }
54 |
55 | //Delete
56 | private static testMethod void deleteTest(){
57 | //Create and insert 200 accounts
58 | List accts = createSObjects(true);
59 |
60 | //Start Test and delete records
61 | Test.startTest();
62 | sObjectRemote.deleteSObjects(accts,new Map());
63 | Test.stopTest();
64 |
65 | //Assert all accounts were deleted
66 | List acctsAfterUpdate = [select Id from Account where Id IN :accts];
67 | system.assert(acctsAfterUpdate.size() == 0);
68 | }
69 |
70 | //DML Options
71 | private static testMethod void dmlOptionsTest(){
72 | //Build a map representing every kind of dml option that can be set
73 | Map dmlOptions = new Map();
74 |
75 | dmlOptions.put('allowfieldtruncation','true');
76 | dmlOptions.put('assignmentruleheader','{assignmentruleid=1234,usedefaultrule=true}');
77 | dmlOptions.put('emailheader','{triggerautoresponseemail=true,triggerotheremail=true,triggeruseremail=true}');
78 | dmlOptions.put('localeoptions','en_us');
79 | dmlOptions.put('optallornone','false');
80 |
81 | //Build DMLOptions object based on map
82 | Test.startTest();
83 | Database.DMLOptions dmo = sObjectRemote.setDMLoptions(dmlOptions);
84 | Test.stopTest();
85 |
86 | //Assert DML options where set correctly
87 | system.assertEquals(true,dmo.allowFieldTruncation);
88 | system.assertEquals('1234',dmo.assignmentRuleHeader.assignmentRuleID);
89 | system.assertEquals(true,dmo.assignmentRuleHeader.useDefaultRule);
90 | system.assertEquals(true,dmo.emailHeader.triggerAutoResponseEmail);
91 | system.assertEquals(true,dmo.emailheader.triggerOtherEmail);
92 | system.assertEquals(true,dmo.emailHeader.triggerUserEmail);
93 | system.assertEquals('en_us',dmo.localeOptions);
94 | system.assertEquals(false,dmo.optAllOrNone);
95 | }
96 |
97 | //Test Utility Method to create sObjects
98 | private static List createSObjects(Boolean doInsert){
99 | List accts = new List();
100 |
101 | for(Integer i = 0; i < 200; i++){
102 | accts.add(new Account(Name = 'test')); //Add required fields per org to make sure inserts pass validation
103 | }
104 |
105 | if(doInsert == true){
106 | insert accts;
107 | }
108 |
109 | return accts;
110 | }
111 | }
--------------------------------------------------------------------------------
/src/classes/sObjectRemoteTEST.cls-meta.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 32.0
4 | Active
5 |
6 |
--------------------------------------------------------------------------------
/src/components/sObjectRemote.component:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
--------------------------------------------------------------------------------
/src/components/sObjectRemote.component-meta.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 32.0
4 | Includes the necessary JavaScript files and variable names to use the sobject-remote.js library.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | sObjectRemote
5 | sObjectRemoteTEST
6 | ApexClass
7 |
8 |
9 | sObjectRemote
10 | ApexComponent
11 |
12 |
13 | sObjectRemoteExamples
14 | ApexPage
15 |
16 |
17 | sObjectRemote
18 | StaticResource
19 |
20 | 32.0
21 |
22 |
--------------------------------------------------------------------------------
/src/pages/sObjectRemoteExamples.page:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/src/pages/sObjectRemoteExamples.page-meta.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 32.0
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/staticresources/sObjectRemote.resource:
--------------------------------------------------------------------------------
1 | var sObject = function (sObjectType,fieldValues){
2 | //Set the sObjectType
3 | this.attributes = {sObjectType: sObjectType};
4 |
5 | //If field values were passed in to constructor populate the field values in the sObject
6 | if(fieldValues) {
7 | for(var key in fieldValues) {
8 | this[key] = fieldValues[key];
9 | }
10 | }
11 | }
12 |
13 | //---Insert Methods---
14 | sObject.insert = function(sObjects,callbackOrOptions,callback){
15 |
16 | var objectsToInsert = sObject.buildArray(sObjects); //Create array of objects that will be inserted
17 | var objectStore = new Array(); //Create array that will store child objects of the sObject, these must be removed before DML
18 | var callback = sObject.setCallback(callbackOrOptions,callback); //Determine which argument is the callback function
19 | var options = sObject.setOptions(callbackOrOptions); //Set the options object
20 |
21 | //Prep the objects for DML by removing the unnecessary and adding necessary properties
22 | sObject.prepForDML(objectsToInsert,objectStore,'insert');
23 |
24 | //Perform the insert operation
25 | Visualforce.remoting.Manager.invokeAction(sObjectInsertMethodName, objectsToInsert, options, function(result, event){
26 |
27 | //Reattach the the child objects that were removed before the DML
28 | sObject.handleDMLResult(result,objectsToInsert,objectStore,'insert');
29 |
30 | //Call the provided callback function with result and event objects
31 | callback(result,event);
32 |
33 | },{escape: options.escape});
34 | }
35 |
36 | //Insert one instance of a sObject
37 | sObject.prototype.insert = function(callbackOrOptions,callback){
38 | sObject.insert(this,callbackOrOptions,callback);
39 | }
40 |
41 | //---Query Method---
42 | sObject.query = function(soql,callbackOrOptions,callback){
43 | var callback = sObject.setCallback(callbackOrOptions,callback);
44 | var options = sObject.setOptions(callbackOrOptions);
45 |
46 | //Perform the query
47 | Visualforce.remoting.Manager.invokeAction(sObjectQueryMethodName, soql, function(QueryResult, event){
48 | //Return array of queried sObjects
49 | var sObjects = [];
50 |
51 | //Populate list of sObjects if query was a success
52 | if(event){
53 | for(var i = 0; i < QueryResult.sObjects.length; i++){
54 | sObjects.push(new sObject(QueryResult.sObjectAttributes.sObjectType, QueryResult.sObjects[i]));
55 | }
56 | }
57 |
58 | //Return array of queried sObjects to the callback
59 | callback(sObjects,event);
60 |
61 | },{escape: options.escape});
62 | }
63 |
64 | //---Update Methods---
65 | sObject.update = function(sObjects,callbackOrOptions,callback){
66 |
67 | var objectsToUpdate = sObject.buildArray(sObjects);
68 | var objectStore = new Array();
69 | var callback = sObject.setCallback(callbackOrOptions,callback);
70 | var options = sObject.setOptions(callbackOrOptions);
71 |
72 | sObject.prepForDML(objectsToUpdate,objectStore,'update');
73 |
74 | //Perform the update operation
75 | Visualforce.remoting.Manager.invokeAction(sObjectUpdateMethodName, objectsToUpdate, options, function(result, event){
76 |
77 | //Convert result to object if necessary
78 | if(event.status && options.resultAsObject){
79 | result = sObject.convertResultToObject(result,objectsToUpdate);
80 | }
81 |
82 | sObject.handleDMLResult(result,objectsToUpdate,objectStore,'update');
83 | callback(result,event);
84 |
85 | },{escape: options.escape});
86 | }
87 |
88 | //Update one instance of a sObject
89 | sObject.prototype.update = function(callbackOrOptions,callback){
90 | sObject.update(this,callbackOrOptions,callback);
91 | }
92 |
93 | //---Delete Methods---
94 | sObject.del = function(sObjects,callbackOrOptions,callback){
95 |
96 | var objectsToDelete = sObject.buildArray(sObjects);
97 | var callback = sObject.setCallback(callbackOrOptions,callback);
98 | var options = sObject.setOptions(callbackOrOptions);
99 | var deleteObjects = new Array();
100 |
101 | //Construct a list of super simple objects that only have an Id attribute, as this is all that is needed for delete operation
102 | for(var i = 0; i < objectsToDelete.length; i++){
103 | deleteObjects.push({Id: objectsToDelete[i].Id});
104 | }
105 |
106 | //Perform the delete operation
107 | Visualforce.remoting.Manager.invokeAction(sObjectDeleteMethodName, deleteObjects, options, function(result, event){
108 |
109 | //Delete operation was success and if purge option was set to true, null object or clean out array
110 | if(event.status && options.purge){
111 |
112 | //If sObjects has length it was an array
113 | if(sObjects.length){
114 |
115 | var originalArrayLength = sObjects.length;
116 |
117 | //Add objects to the end of the array that were not deleted
118 | for(var i = 0; i < originalArrayLength; i++){
119 | if(result[i].success == false){
120 | sObjects.push(sObjects[i]);
121 | }
122 | }
123 |
124 | //Trim off original objects in the array, leaving only those objects that failed to delete
125 | sObjects.splice(0,originalArrayLength);
126 |
127 | }else{
128 | //Single sObject
129 | if(result[0].success == true){
130 | sObjects = null;
131 | }
132 | }
133 | }
134 |
135 | //Convert result to object if necessary
136 | if(event.status && options.resultAsObject){
137 | result = sObject.convertResultToObject(result,deleteObjects);
138 | }
139 |
140 | callback(result,event);
141 |
142 | },{escape: options.escape});
143 | }
144 |
145 | //Delete one instance of a sObject
146 | sObject.prototype.del = function(callbackOrOptions,callback){
147 | sObject.del(this,callbackOrOptions,callback);
148 | }
149 |
150 | //---Utility Methods---
151 | //If only one sObject is provided for DML it is converted to an array
152 | sObject.buildArray = function(objects){
153 | //If objects has length its already an array
154 | if(objects.length){
155 | return objects;
156 | }else{
157 | //Single record, add it to array
158 | return new Array(objects);
159 | }
160 | }
161 |
162 | //Inspects the callbackOrOptions to see if it is a function (callback) or object (options), sets callback appropriately
163 | sObject.setCallback = function(callbackOrOptions,callback){
164 | if(typeof callbackOrOptions == 'object'){
165 | return callback;
166 | }else{
167 | return callbackOrOptions;
168 | }
169 | }
170 |
171 | //Default options if none are set
172 | sObject.setOptions = function(callbackOrOptions){
173 | if(typeof callbackOrOptions == 'object'){
174 |
175 | //Default escape option to true if it was not set
176 | if(callbackOrOptions.escape == 'undefined'){
177 | if(sObjectEscapeOverride == 'false'){
178 | callbackOrOptions.escape = false;
179 | }else{
180 | callbackOrOptions.escape = true;
181 | }
182 | }
183 | return callbackOrOptions;
184 |
185 | }else{
186 | //No options define. Default escape option to true if component option was not set to false
187 | if(sObjectEscapeOverride == 'false'){
188 | return {escape: false};
189 | }else{
190 | return {escape: true};
191 | }
192 | }
193 | }
194 |
195 | //Salesforce does not like DML on sObjects if they have child objects. Remove them for the dml operation
196 | sObject.prepForDML = function(sObjects,objectStore,dmlType){
197 |
198 | for(var i = 0; i < sObjects.length; i ++){
199 |
200 | //Store the sObject we are currently processing in a var for easy access
201 | var sObject = sObjects[i];
202 |
203 | //Set sObjectType for INSERT operations
204 | if(dmlType == 'insert'){
205 | sObject.sObjectType = sObjects[i].attributes.sObjectType;
206 | }
207 |
208 | //Loop through sObjects keys and store any child objects in a childObjects object
209 | var childObjects = {};
210 |
211 | for(var key in sObject){
212 | if(typeof sObject[key] == 'object'){
213 | childObjects[key] = sObject[key];
214 | delete sObject[key];
215 | }
216 | }
217 |
218 | //Store the child objects in the objectStore array so they can be added back to the sObject after DML, http://jsperf.com/object-clone-vs-modify
219 | objectStore.push(childObjects);
220 | }
221 | }
222 |
223 | //Updated Id form inserted records and reattaches and child objects that where removed for the DML
224 | sObject.handleDMLResult = function(result,sObjects,objectStore,dmlType){
225 |
226 | //Loop through results, update the Id for inserts, add back child objects that were removed for DML
227 | for(var i = 0; i < sObjects.length; i++){
228 |
229 | //Remove the sObjectType property that may be present on the main sObject
230 | delete sObjects[i]['sObjectType'];
231 |
232 | //If record was inserted successfully, update the id
233 | if(dmlType == 'insert' && result[i].success){
234 | sObjects[i].Id = result[i].id;
235 | }
236 |
237 | //Add the child objects back to the main sObject
238 | var sObject = sObjects[i];
239 | var childObjects = objectStore[i];
240 |
241 | for(var key in childObjects[i]){
242 | sObject[key] = childObjects[key];
243 | }
244 | }
245 | }
246 |
247 | //Create save/delete result array to JavaScript object with the key as the record Id
248 | sObject.convertResultToObject = function(result,sObjects){
249 |
250 | var resultObj = {};
251 |
252 | for(var i = 0; i < result.length; i++){
253 | var id = sObjects[i].Id;
254 | delete result[i]['id'];
255 | resultObj[id] = result[i];
256 | }
257 | return resultObj;
258 | }
--------------------------------------------------------------------------------
/src/staticresources/sObjectRemote.resource-meta.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Private
4 | text/javascript
5 | Contains the sobject-remote.js library for simplifying JavaScript Remoting DML operations.
6 |
7 |
--------------------------------------------------------------------------------