├── README.md ├── app ├── app_file.html ├── style.css ├── translations │ └── en.json └── zcrmsdk.js └── plugin-manifest.json /README.md: -------------------------------------------------------------------------------- 1 | # Archival Notice: 2 | 3 | This SDK is archived. You can continue to use it, but no new features or support requests will be accepted. For the new version, refer to 4 | https://www.zoho.com/crm/developer/docs/sdk/client-side/javascript-sdk.html 5 | 6 | # zcrm-js-sdk 7 | 8 | Zoho CRM offers REST APIs for communication between several clients. This SDK helps you to make API calls from the domains registered with accounts.zoho.{com/eu/com.cn}. 9 | 10 | Refer [Building Webapp](https://www.zoho.com/crm/help/developer/webapp-sdk/build-webapp.html) and [Install CLI](https://www.zoho.com/crm/help/developer/webapp-sdk/install-cli.html) using [ZET](https://www.npmjs.com/package/zoho-extension-toolkit) before proceeding further 11 | 12 | 13 | 14 | 15 | **Please follow the steps to work with JS SDK** 16 | * Register the client from CRM UI and note the client id 17 | * Create a new project using the command '**zet init**' via terminal/command line. Choose the option '**Catalyst**' and give the project name. 18 | * New folder will be created with the project name. Inside that, there will be a file **plugin\_manifest.json**. Update the client id in that file and required scopes to be used in the web app 19 | * Under the project folder, there will be another folder named '**app**'. This will act as the base. 20 | * Include the '**zcrmsdk.js**' file (available in _app_ folder) and use it in your html files. 21 | * **ZCRM.API.AUTH.getAccess()** will create a token by authenticating the user. 22 | * After the development, run the command '**zet pack**' from the project base folder and upload it in CRM UI. FYI: Only one app can be uploaded for each client. While updating with new app, old one has to be deleted. Also redirect url will be changed. 23 | * To know the redirect url, ZCRM.API.AUTH.getAccess() function has to be accessed from web app. It'll redirect to accounts.zoho.com/oauth/v2/auth along with a parameter redirect\_uri. Take that redirect\_uri and configure it in https://api-console.zoho.com/. 24 | Eg : if the redirect\_uri is "`https://99000000223015.zappscontents.com/appfiles/99000000223015/1.0/1dd62561c00429f2c4970bf4f2b4dc09142d08b6949a17a5c3388f30851ec9cf/redirect.html`" 25 | Then 26 | 27 | "**Authorized redirect URIs**" is "`https://99000000223015.zappscontents.com/appfiles/99000000223015/1.0/1dd62561c00429f2c4970bf4f2b4dc09142d08b6949a17a5c3388f30851ec9cf/redirect.html`" 28 | 29 | "**JavaScript Domain**" is "`https://99000000223015.zappscontents.com`" 30 | 31 | 32 | To test it in local machine:- 33 | * Create a redirect.html page within the app folder. 34 | * Run it using the '**zet run**' via terminal/command line. 35 | * Enter 127.0.0.1:{your_port_number} for eg. 127.0.0.1:5000 in the browser address bar and select the app_file.html 36 | * It'll redirect to accounts.zoho.com/oauth/v2/auth along with a parameter redirect\_uri. Take that redirect\_uri and configure it in https://api-console.zoho.com/. 37 | * If the page successfully redirects to the redirect.html page then the app works as intended. 38 | **Note** 39 | - If a single page uses many ajax calls at the same time and the token is not set. All the responses will be empty json object string `'{}'` . This one has to be handled for every request. 40 | - Once token is set for the first time, the page will be reloaded. 41 | 42 | 43 | --- 44 | **Object Hierarchy** 45 | 46 | \* - indicates mandatory param and input has to be passed as JSON for the functions 47 | 48 | 49 | **ZCRM** 50 | - **AUTH** 51 | - getAccess 52 | - revokeAccess 53 | - **RECORDS** 54 | - get - (\*input.module, input.params) 55 | - post - (\*input.module, \*input.body, \*headers['Content-Type']) 56 | - put - (\*input.module, \*input.body, \*headers['Content-Type']) 57 | - delete - (\*input.module, \*input.id) 58 | - getNotes - (\*input.module, \*input.id) 59 | - getRelated - (\*input.module, \*input.id, \*input.relatedModule) 60 | - getAllDeletedRecords - (\*input.module) 61 | - getRecycleBinRecords - (\*input.module) 62 | - getPermanentlyDeletedRecords - (\*input.module) 63 | - **SETTINGS** 64 | - getFields - (\*input.params, input.id) 65 | - getLayouts - (\*input.params, input.id) 66 | - getCustomViews - (\*input.params, input.id) 67 | - updateCustomViews - (\*input.params, input.id) 68 | - getModules - (input.module) 69 | - getRoles - (input.id) 70 | - getProfiles - (input.id) 71 | - getRelatedLists - (input.id) 72 | - **ACTIONS** 73 | - convert - (\*input.id, \*input.body) 74 | - **USERS** 75 | - get - (input.id) 76 | - **ORG** 77 | - get 78 | - **ATTACHMENTS** 79 | - uploadFile - (\*input.module, \*input.id, \*input.x\_file\_content) 80 | - deleteFile - (\*input.module, \*input.id, \*input.relatedId) 81 | - downloadFile - (\*input.module, \*input.id, \*input.relatedId) 82 | - uploadLink - (\*input.module, \*input.id, \*input.params) 83 | - uploadPhoto - (\*input.module, \*input.id, \*input.x\_file\_content) 84 | - downloadPhoto - (\*input.module, \*input.id) 85 | - deletePhoto - (\*input.module, \*input.id) 86 | - init 87 | -------------------------------------------------------------------------------- /app/app_file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 74 | 75 | 76 |
77 |
78 | 79 |
80 | 81 | 82 | 83 |
Last NameCompanyEmailLead Source
cbRaghu RamZoho12547Web
84 |
85 |
86 |
87 |
88 | Last Name : 89 |
90 |
91 | Company : 92 |
93 |
94 | Email : 95 |
96 |
97 | Lead Source : 98 |
99 | 100 |
101 |
102 |
103 |
104 | 105 | 106 | -------------------------------------------------------------------------------- /app/style.css: -------------------------------------------------------------------------------- 1 | .full{ 2 | position: relative; 3 | padding: 10px; 4 | width: 100%; 5 | min-height: 100%; 6 | } 7 | .left{ 8 | width: 10%; 9 | height: 100%; 10 | position: relative; 11 | float: left; 12 | } 13 | .right{ 14 | width: 90%; 15 | height: 100%; 16 | position: relative; 17 | float: right;; 18 | } 19 | .modules{ 20 | height: 100%; 21 | } 22 | .module{ 23 | height: 50px; 24 | padding: 10px; 25 | } 26 | table{ 27 | border: 1; 28 | } 29 | td,th{ 30 | padding: 5px; 31 | } 32 | #leadForm{ 33 | padding: 10px; 34 | } 35 | #formelement{ 36 | padding : 5px; 37 | } 38 | -------------------------------------------------------------------------------- /app/translations/en.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /app/zcrmsdk.js: -------------------------------------------------------------------------------- 1 | var libBase, headers, HTTP_METHODS, version; 2 | version = 2; 3 | HTTP_METHODS = { 4 | GET : "GET",//No I18N 5 | POST : "POST",//No I18N 6 | PUT : "PUT",//No I18N 7 | DELETE : "DELETE"//No I18N 8 | }; 9 | 10 | function promiseResponse(request) { 11 | return new Promise(function (resolve, reject) { 12 | var body, baseUrl, xhr, i, formData; 13 | libBase = localStorage.api_domain+"/crm/v"+version+"/"; 14 | baseUrl = libBase + request.url; 15 | 16 | var token = ZCRM.API.AUTH.getAccess(); 17 | if(token == null){ 18 | return resolve('{}'); // in case of no ticket, returns empty json 19 | } 20 | 21 | if (request.params) 22 | { 23 | baseUrl = baseUrl + '?' + request.params; 24 | } 25 | 26 | xhr = new XMLHttpRequest(); 27 | xhr.withCredentials = true 28 | xhr.open(request.type, baseUrl); 29 | xhr.setRequestHeader("Authorization", "Zoho-oauthtoken "+token) 30 | for (i in headers) 31 | { 32 | xhr.setRequestHeader(i, headers[i]); 33 | } 34 | 35 | if (request.download_file){ 36 | xhr.responseType = "blob";//No I18N 37 | } 38 | 39 | if (request.x_file_content) { 40 | formData = new FormData(); 41 | formData.append('file', request.x_file_content);//No I18N 42 | xhr.send(formData); 43 | } 44 | else{ 45 | body = request.body || null; 46 | xhr.send(body); 47 | } 48 | 49 | xhr.onreadystatechange = function() { 50 | if(xhr.readyState == 4){ 51 | if (xhr.status == 204) 52 | { 53 | var respObj = { 54 | "message" : "no data", //No I18N 55 | "status_code" : "204" //No I18N 56 | } 57 | resolve(JSON.stringify(respObj)); 58 | } 59 | else 60 | { 61 | if (request.download_file){ 62 | var filename; 63 | var disposition = xhr.getResponseHeader("Content-Disposition");//No I18N 64 | if (disposition && disposition.indexOf('attachment') !== -1) { 65 | var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/; 66 | var matches = filenameRegex.exec(disposition); 67 | if (matches != null && matches[1]) { 68 | filename = matches[1].replace(/['"]/g, ''); 69 | filename = filename.replace('UTF-8',''); 70 | } 71 | } 72 | var blob = xhr.response; 73 | var url = URL.createObjectURL(blob); 74 | var ttt = document.createElement('a'); 75 | ttt.href = url; 76 | ttt.download = filename; 77 | ttt.click(); 78 | } 79 | else{ 80 | resolve(xhr.response); 81 | } 82 | } 83 | } 84 | } 85 | }) 86 | }; 87 | function createParams(parameters) 88 | { 89 | var params, key; 90 | for (key in parameters) 91 | { 92 | if (parameters.hasOwnProperty(key)) { 93 | if (params) 94 | { 95 | params = params + key + '=' + parameters[key] + '&'; 96 | } 97 | else 98 | { 99 | params = key + '=' + parameters[key] + '&'; 100 | } 101 | } 102 | } 103 | 104 | return params; 105 | }; 106 | function constructRequestDetails(input, url, type, isModuleParam) 107 | { 108 | var requestDetails = {}; 109 | 110 | requestDetails.type = type; 111 | 112 | if (input != undefined) 113 | { 114 | if (input.id) 115 | { 116 | url = url.replace("{id}", input.id); 117 | // url = url + "/" + input.id; 118 | } 119 | else 120 | { 121 | url = url.replace("/{id}", ""); 122 | } 123 | if (input.params) 124 | { 125 | requestDetails.params = createParams(input.params) + (input.module && isModuleParam ? "module=" + input.module : "");//No I18N 126 | } 127 | if (!requestDetails.params && isModuleParam) 128 | { 129 | requestDetails.params = "module=" + input.module;//No I18N 130 | } 131 | if (input.body && (type == HTTP_METHODS.POST || type == HTTP_METHODS.PUT)) 132 | { 133 | requestDetails.body = JSON.stringify(input.body); 134 | } 135 | if (input.x_file_content) 136 | { 137 | requestDetails.x_file_content = input.x_file_content; 138 | } 139 | if (input.download_file) 140 | { 141 | requestDetails.download_file = input.download_file; 142 | } 143 | } 144 | requestDetails.url = url; 145 | 146 | return requestDetails; 147 | }; 148 | function getParameterByName(name, url) 149 | { 150 | if (!url) url = window.location.href; 151 | name = name.replace(/[\[\]]/g, "\\$&"); 152 | var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"); 153 | var results = regex.exec(url); 154 | if (!results) return null; 155 | if (!results[2]) return ''; 156 | return decodeURIComponent(results[2].replace(/\+/g, " ")); 157 | } 158 | function sdk() 159 | { 160 | return { 161 | initialize : function (configuration) 162 | { 163 | if(document.getElementById("zes_client_scope") == null){ 164 | var elem = document.createElement('div'); 165 | elem.setAttribute("data-scope",configuration.scopes); 166 | elem.setAttribute("data-clientid",configuration.client_id); 167 | elem.setAttribute("data-accounts-url",configuration.accounts_url); 168 | elem.setAttribute("id","zes_client_scope"); 169 | 170 | document.body.appendChild(elem); 171 | } 172 | 173 | var input = {}; 174 | ZCRM.API.USERS.get(input).then(function(resp){ 175 | }); 176 | } 177 | } 178 | } 179 | function actions() 180 | { 181 | 182 | return { 183 | convert : function (input) 184 | { 185 | return promiseResponse(constructRequestDetails(input, "Leads/{id}/actions/convert", HTTP_METHODS.POST, false));//No I18N 186 | } 187 | } 188 | } 189 | function attachments() 190 | { 191 | 192 | return { 193 | uploadFile : function (input) 194 | { 195 | return promiseResponse(constructRequestDetails(input, input.module+ "/{id}/Attachments", HTTP_METHODS.POST, false));//No I18N 196 | }, 197 | deleteFile : function (input) 198 | { 199 | return promiseResponse(constructRequestDetails(input, input.module+ "/{id}/Attachments/"+input.relatedId, HTTP_METHODS.DELETE, false));//No I18N 200 | }, 201 | downloadFile : function (input) 202 | { 203 | input.download_file = true; 204 | return promiseResponse(constructRequestDetails(input, input.module+ "/{id}/Attachments/"+input.relatedId, HTTP_METHODS.GET, false));//No I18N 205 | }, 206 | uploadLink : function (input) 207 | { 208 | return promiseResponse(constructRequestDetails(input, input.module+ "/{id}/Attachments", HTTP_METHODS.POST, false));//No I18N 209 | }, 210 | uploadPhoto : function (input) 211 | { 212 | return promiseResponse(constructRequestDetails(input, input.module+ "/{id}/photo", HTTP_METHODS.POST, false));//No I18N 213 | }, 214 | downloadPhoto : function (input) 215 | { 216 | input.download_file = true; 217 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}/photo", HTTP_METHODS.GET, false));//No I18N 218 | }, 219 | deletePhoto : function (input) 220 | { 221 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}/photo", HTTP_METHODS.DELETE, false));//No I18N 222 | } 223 | } 224 | } 225 | 226 | function org() 227 | { 228 | 229 | return { 230 | get : function (input) 231 | { 232 | return promiseResponse(constructRequestDetails(input, "org", HTTP_METHODS.GET, true));//No I18N 233 | } 234 | } 235 | } 236 | 237 | function records() 238 | { 239 | 240 | return { 241 | get : function(input) 242 | { 243 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}", HTTP_METHODS.GET, false));//No I18N 244 | }, 245 | post : function(input) 246 | { 247 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}", HTTP_METHODS.POST, false));//No I18N 248 | }, 249 | put : function(input) 250 | { 251 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}", HTTP_METHODS.PUT, false));//No I18N 252 | }, 253 | delete : function (input) 254 | { 255 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}", HTTP_METHODS.DELETE, false));//No I18N 256 | }, 257 | getNotes : function (input) 258 | { 259 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}/Notes", HTTP_METHODS.GET, false));//No I18N 260 | }, 261 | getRelated : function (input) 262 | { 263 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}/"+input.relatedModule, HTTP_METHODS.GET, false));//No I18N 264 | }, 265 | getAllDeletedRecords : function (input) 266 | { 267 | if (input.params) 268 | { 269 | input.params.type = "all"; 270 | } 271 | else 272 | { 273 | input.params = { 274 | "type" : "all"//No I18N 275 | }; 276 | } 277 | 278 | return promiseResponse(constructRequestDetails(input, input.module + "/deleted", HTTP_METHODS.GET, false));//No I18N 279 | }, 280 | getRecycleBinRecords : function (input) 281 | { 282 | if (input.params) 283 | { 284 | input.type = "recycle"; 285 | } 286 | else 287 | { 288 | input.params = { 289 | "type" : "recycle"//No I18N 290 | }; 291 | } 292 | 293 | return promiseResponse(constructRequestDetails(input, input.module + "/deleted", HTTP_METHODS.GET, false));//No I18N 294 | }, 295 | getPermanentlyDeletedRecords : function (input) 296 | { 297 | if (input.params) 298 | { 299 | input.type = "permanent"; 300 | } 301 | else 302 | { 303 | input.params = { 304 | "type" : "permanent"//No I18N 305 | }; 306 | } 307 | 308 | return promiseResponse(constructRequestDetails(input, input.module + "/deleted", HTTP_METHODS.GET, false));//No I18N 309 | }, 310 | search : function (input) 311 | { 312 | return promiseResponse(constructRequestDetails(input, input.module + "/search", HTTP_METHODS.GET, false));//No I18N 313 | } 314 | } 315 | } 316 | function settings() 317 | { 318 | 319 | return { 320 | getFields : function (input) 321 | { 322 | return promiseResponse(constructRequestDetails(input, "settings/fields/{id}", HTTP_METHODS.GET, true));//No I18N 323 | }, 324 | getLayouts : function (input) 325 | { 326 | return promiseResponse(constructRequestDetails(input, "settings/layouts/{id}", HTTP_METHODS.GET, true));//No I18N 327 | }, 328 | getCustomViews : function (input) 329 | { 330 | return promiseResponse(constructRequestDetails(input, "settings/custom_views/{id}", HTTP_METHODS.GET, true));//No I18N 331 | }, 332 | updateCustomViews : function (input) 333 | { 334 | return promiseResponse(constructRequestDetails(input, "settings/custom_views/{id}", HTTP_METHODS.PUT, true));//No I18N 335 | }, 336 | getModules : function (input) 337 | { 338 | return promiseResponse(constructRequestDetails(input, "settings/modules" + ((input && input.module) ? "/" + input.module : ""), HTTP_METHODS.GET, false));//No I18N 339 | }, 340 | getRoles : function (input) 341 | { 342 | return promiseResponse(constructRequestDetails(input, "settings/roles/{id}", HTTP_METHODS.GET, true));//No I18N 343 | }, 344 | getProfiles : function (input) 345 | { 346 | return promiseResponse(constructRequestDetails(input, "settings/profiles/{id}", HTTP_METHODS.GET, true));//No I18N 347 | }, 348 | getRelatedLists : function (input) 349 | { 350 | return promiseResponse(constructRequestDetails(input, "settings/related_lists/{id}", HTTP_METHODS.GET, true));//No I18N 351 | } 352 | } 353 | } 354 | function users() 355 | { 356 | 357 | return { 358 | get : function (input) 359 | { 360 | return promiseResponse(constructRequestDetails(input, "users/{id}", HTTP_METHODS.GET, true));//No I18N 361 | } 362 | } 363 | } 364 | var listener = 0; 365 | function auth() 366 | { 367 | return { 368 | getAccess : function(){ 369 | if(listener == 0){ 370 | window.addEventListener("storage", function(e){ 371 | if(e.key === 'access_token' && e.oldValue!=e.newValue && e.oldValue == null){ 372 | location.reload(); 373 | } 374 | if(e.key === 'access_token'){ 375 | localStorage.removeItem('__auth_process'); 376 | } 377 | }, false); 378 | listener = 1; 379 | if(localStorage.getItem('__auth_process')){ localStorage.removeItem('__auth_process'); } 380 | } 381 | 382 | 383 | var valueInStore = localStorage.getItem('access_token'); 384 | var token_init = localStorage.getItem('__token_init'); 385 | if(token_init != null && valueInStore != null && Date.now() >= parseInt(token_init)+55*60*1000){ // check after 55 mins 386 | valueInStore = null; 387 | localStorage.removeItem('access_token'); 388 | } 389 | var auth_process = localStorage.getItem('__auth_process'); 390 | 391 | if (valueInStore == null && auth_process == null) 392 | { 393 | var accountsUrl =document.getElementById("zes_client_scope").getAttribute("data-accounts-url"); 394 | var endPoint = "/oauth/v2/auth"; 395 | var full_grant = localStorage.getItem('full_grant'); 396 | if(full_grant != null && 'true' == full_grant && localStorage.getItem('__token_init') != null){ 397 | endPoint += '/refresh'; 398 | } 399 | var client_id = document.getElementById("zes_client_scope").getAttribute("data-clientid"); 400 | var scope = document.getElementById("zes_client_scope").getAttribute("data-scope"); 401 | 402 | var path = window.location.pathname; 403 | var redirect_url = window.location.origin; 404 | var pathSplit = path.split('/'); 405 | var length=pathSplit.length 406 | for (var i=0;i